Расширение d3.selection в v5 — правильный ли это способ?

avatar
spring
11 октября 2018 в 05:41
648
1
4

Предыстория: Я пытался решить проблему добавления существующих локальных файлов SVG в контейнер SVG d3 в настольном приложении Electron. Я обнаружил, что не могу использовать d3.svg() в локальных файлах, потому что fetch не работает с протоколом file (так сказано в сообщении об ошибке).

Я наткнулся на этот суть, на который ссылается этот вопрос, для расширения d3.selection, и, похоже, он делает именно то, что мне нужно, добавляя функции appendHTML и appendSVG.

Тем не менее, когда я протестировал код (внизу внизу), он выдал ошибку: «Невозможно прочитать свойство 'prototype of undefined» — задыхается в этой строке:

d3.selection.enter.prototype.appendHTML 

Я немного поковырялся в процессе входа в консоль и нашел это изменение, и, похоже, оно работает:

d3.selection.prototype.enter.prototype.appendHTML   

Мой вопрос: Правильно ли я делаю? Что-то изменилось в d3, что требует дополнительной ссылки prototype? Я не герой Javascript или d3 и хотел бы понять, в чем здесь разница.

d3.selection.prototype.appendHTML =
    d3.selection.prototype.enter.prototype.appendHTML = function (HTMLString) {
        return this.select(function () {
            return this.appendChild(document.importNode(new DOMParser().parseFromString(HTMLString, 'text/html').body.childNodes[0], true));
        });
    };

Исходный код

 d3.selection.prototype.appendHTML =
    d3.selection.enter.prototype.appendHTML = function(HTMLString) {
        return this.select(function() {
            return this.appendChild(document.importNode(new DOMParser().parseFromString(HTMLString, 'text/html').body.childNodes[0], true));
        });
    };


 d3.selection.prototype.appendSVG =
    d3.selection.enter.prototype.appendSVG = function(SVGString) {
        return this.select(function() {
            return this.appendChild(document.importNode(new DOMParser()
            .parseFromString('<svg xmlns="http://www.w3.org/2000/svg">' + SVGString + '</svg>', 'application/xml').documentElement.firstChild, true));
        });
    };
Источник
i alarmed alien
11 октября 2018 в 07:20
0

Возвращаясь к вопросу, который послужил толчком к исследованию, можете ли вы запустить приложение Electron на небольшом сервере, а затем получать локальные файлы, используя их расположение относительно: где бы вы ни определили корень localhost?

spring
11 октября 2018 в 14:23
1

@ialarmedalien спасибо. Я не хотел усложнять запуск локального сервера, если это вообще возможно. Похоже, расширение selection работает очень хорошо.

Ответы (1)

avatar
altocumulus
11 октября 2018 в 10:24
3

В ответах, которые вы упомянули, используется D3 v3, но все значительно изменилось с v3 на v4/v5. Основное различие, когда дело доходит до выбора, раскрывается всего одним предложением в журнале изменений:

Выборы больше не являются подклассами Array с использованием внедрения цепочки прототипов; теперь они являются простыми объектами, что повышает производительность.

Хотя это звучит довольно просто, тем не менее, потребовались значительные внутренние изменения. Все объекты выбора теперь являются экземплярами функции Selection, которая не отображается напрямую. d3.selection — это функция, возвращающая новый экземпляр Selection:

function selection() {
  return new Selection([[document.documentElement]], root);
}

Хотя и Selection, и d3.selection имеют один и тот же прототип, который содержит свойство .enter, не существует свойства .enter, если только не создано свойство .enter .

Правильный способ расширения объектов выбора D3 в v4/v5 будет следующим:

d3.selection    
  .prototype    // This prototype is shared across all types of selections.   
  .appendHTML = // Apply changes to the selection's prototype.

Поскольку свойство prototype Selection и d3.selection указывает на один и тот же объект, эти изменения повлияют как на обычный выбор, так и на ввод, поскольку оба они являются экземплярами функции Selection.

Как видите, это всего лишь первая строка вашего собственного кода, что совершенно нормально. Ваше расширение с использованием d3.selection.prototype.enter.prototype.appendHTML работает только так: оно не приносит ни вреда, ни пользы! Установка свойства для функции .enter бессмысленна, так как из этой функции никогда не создается экземпляр.

Посмотрите на следующую рабочую демонстрацию, которую я взял из сути, на которую вы ссылались в своем вопросе:

d3.selection.prototype.appendHTML =
  function(HTMLString) {
    return this.select(function() {
      return this.appendChild(
        document.importNode(
          new DOMParser().parseFromString(HTMLString, 'text/html').body.childNodes[0], true)
        );
    });
  };

d3.selection.prototype.appendSVG =
  function(SVGString) {
    return this.select(function() {
      return this.appendChild(
        document.importNode(
          new DOMParser()
            .parseFromString('<svg xmlns="http://www.w3.org/2000/svg">' + SVGString + '</svg>', 'application/xml').documentElement.firstChild, true));
    });
  };

d3.select('.container').appendHTML('<svg><g><rect width="50" height="50" /></g></svg>');

var svg = d3.select('.container')
  .appendHTML('<svg xmlns="http://www.w3.org/2000/svg"><g><circle class="circle1" cx="50" cy="50" r="50"></circle></g></svg>')
  .select('g');

svg.appendSVG('<circle class="circle2" cx="20" cy="20" r="20"></circle>');
svg.appendSVG('<rect width="30" height="30"></rect>');
div,
svg {
  border: 1px solid silver;
  margin: 10px;
}

rect {
  fill: skyblue;
}

.circle1 {
  fill: orange;
}

.circle2 {
  fill: lime;
}
<script src="https://d3js.org/d3.v5.js"></script>
<div class="container"></div>
spring
11 октября 2018 в 14:27
0

Спасибо за такой четкий ответ. У меня есть один вопрос, хотя (конечно...). Метод, который я придумал, похоже, работает: ` d3.selection.prototype.enter.prototype.appendHTML` Разница в том, что я расширяю selection.prototype, а вы расширяете его экземпляр?

altocumulus
11 октября 2018 в 16:07
0

@NoGrabbing Этот ответ получил отрицательную оценку, что заставило меня задуматься ... Хотя это было не совсем неправильно, оно как бы упустило суть и усложнило задачу, чем она есть на самом деле. С тех пор я скорректировал ответ, который теперь также проливает свет на то, почему ваше исходное решение сработало, хотя оно выглядело неуклюжим.

altocumulus
11 октября 2018 в 16:12
0

@NoGrabbing Примечание: способ расширения выбора D3 кажется довольно громоздким. Вероятно, вы могли бы сделать свою жизнь намного проще, если бы использовали Element.innerHTML или Element.insertAdjacentHTML() для вставки пользовательской разметки.