D3, вложенные добавления и поток данных
Я в процессе окончательного изучения D3, и я наткнулся на проблему, на которую я не смог найти ответ. Я не уверен, что мой вопрос заключается в том, что я не думаю об идиоматическом отношении с библиотекой, или если это из-за процедуры, о которой я в настоящее время не знаю. Я должен также упомянуть, что я только начал заниматься веб-вещами в июне, поэтому я довольно новичок в javascript.
Скажем, что мы создаем инструмент, который дает пользователю список продуктов с соответствующими изображениями. И добавим дополнительное ограничение, чтобы каждый элемент списка был помечен уникальным идентификатором, чтобы его можно было связать с другим видом. Моя первая интуиция для решения этой проблемы - создать список <div>
, каждый со своим собственным идентификатором, где каждый div
имеет свои собственные <p>
и <img>
. Полученный HTML-код будет выглядеть примерно так:
<div id="chocolate">
<p>Chocolate Cookie</p>
<img src="chocolate.jpg" />
</div>
<div id="sugar">
<p>Sugar Cookie</p>
<img src="sugar.jpg" />
</div>
Данные для этого инструмента находятся в массиве JSON, где отдельный JSON выглядит следующим образом:
{ "label": "sugar", "text": "Sugar Cookie", "img": "sugar.jpg" }
Есть ли способ сделать HTML-код одним махом? Начиная с базового случая добавления div, код может выглядеть примерно так:
d3.select(containerId).selectAll('div')
.data(food)
.enter().append('div')
.attr('id', function(d) { return d.label; });
Теперь, как насчет добавления <div>
с <p>
в нем? Моя первоначальная мысль заключалась в том, чтобы сделать что-то вроде:
d3.select(containerId).selectAll('div')
.data(food)
.enter().append('div')
.attr('id', function(d) { return d.label; })
.append('p').text('somethingHere');
Но я вижу две проблемы с этим: (1) как вы получаете данные из элемента div
и (2) как вы можете добавить несколько дочерних элементов к одному и тому же родительскому объекту в одной декларативной цепочке? Я не могу придумать способ сделать третий шаг, на котором я бы добавил на img
.
Я нашел упоминание о вложенном выборе в другом сообщении, которое указывало на http://bost.ocks.org/mike/nest/. Но является ли вложенный выбор и, следовательно, разбивает приложения на три части, подходящие/идиоматические для этой ситуации? Или есть действительно хорошо построенный способ сформировать эту структуру в одной цепочке деклараций? Похоже, что может быть способ с подвыборками, упомянутыми в https://github.com/mbostock/d3/wiki/Selections, но я недостаточно хорошо знаком с языком для проверки этой гипотезы.
С концептуального уровня эти три объекта (div
, p
и img
) обрабатываются больше как одна группа, а не отдельные сущности, и было бы неплохо, если бы код тоже отражал это.
Ответы
Ответ 1
Вы не можете добавить несколько дочерних элементов в одну цепочку. Вам нужно будет сохранить родительский выбор в переменной. Это должно делать то, что вы хотите:
var data = [{ "label": "chocolate", "text": "Chocolate Cookie", "img": "chocolate.jpg" },
{ "label": "sugar", "text": "Sugar Cookie", "img": "sugar.jpg" }];
var diventer = d3.select("body").selectAll("div")
.data(data)
.enter().append("div")
.attr("id", function(d) { return d.label; });
diventer.append("p")
.text(function(d) { return d.text; });
diventer.append("img")
.attr("src", function(d) { return d.img; });
См. рабочую скрипту: http://jsfiddle.net/UNjuP/
Вам интересно, как дочерний элемент типа p
или img
получает доступ к данным, привязанным к его родительскому элементу. Эти данные автоматически наследуются от родителя при добавлении нового элемента. Это означает, что элементы p и img будут иметь те же данные, которые связаны с ними как родительский div.
Это распространение данных не является уникальным для метода append
. Это происходит со следующими методами selection
: append
, insert
и select
.
Например, для selection.append:
selection.append(имя)
Добавляет новый элемент с указанным именем в качестве последнего дочернего элемента каждый элемент в текущем выборе. Возвращает новый выбор содержащий прилагаемые элементы. Каждый новый элемент наследует данные текущих элементов, если таковые имеются, таким же образом, как и для subselections. Имя должно быть указано как константа, хотя в в будущем мы могли бы позволить добавлять существующие элементы или функцию к генерировать имя динамически.
Не стесняйтесь спрашивать о деталях, если что-то неясно.
ИЗМЕНИТЬ
Вы можете добавить несколько дочерних элементов без сохранения выбора в переменной с помощью метода selection.each
. Затем вы также можете напрямую обращаться к данным от родителя:
var data = [{ "label": "chocolate", "text": "Chocolate Cookie", "img": "chocolate.jpg" },
{ "label": "sugar", "text": "Sugar Cookie", "img": "sugar.jpg" }];
d3.select("body").selectAll("div")
.data(data)
.enter().append("div")
.attr("id", function(d) { return d.label; })
.each(function(d) {
d3.select(this).append("p")
.text(d.text);
d3.select(this).append("img")
.attr("src", d.img);
});
Ответ 2
Опять же, не существенно отличается, но моим предпочтительным методом было бы использовать "вызов"
var data = [{ "label": "chocolate", "text": "Chocolate Cookie", "img": "chocolate.jpg" },
{ "label": "sugar", "text": "Sugar Cookie", "img": "sugar.jpg" }];
d3.select("body").selectAll("div")
.data(data)
.enter().append("div")
.attr("id", function(d) { return d.label; })
.call(function(parent){
parent.append('p').text(function(d){ return d.text; });
parent.append('img').attr("src", function(d) { return d.img; });
});
вам не нужно сохранять какие-либо переменные, и вы можете разделить вызываемую функцию, если хотите использовать аналогичную структуру в другом месте.
Ответ 3
Это не принципиально отличается от ответа nautat, но я думаю, что код можно сделать немного чище, сохранив выбор обновлений, а не выбор ввода, и получить от него выбор ввода для одной операции, которая ему нужна (добавление окружающего div).
Когда вы вставляете или добавляете элемент в выбор enter(), он добавляется к выбору обновлений, с которым вы можете работать позже. Это означает, что вы можете присоединиться к данным, а затем добавить div с выбором ввода, а затем, когда вы добавите его с выбором обновления, вы будете добавлять в div, которые вы добавили в поле ввода:
var cookies = [
{ "label": "sugar", "text": "Sugar Cookie", "img": "sugar.jpg" },
{ "label": "chocolate", "text": "Chocolate Cookie", "img": "chocolate.jpg" }];
var cookie = d3.select("#cookie-jar").selectAll().data(cookies);
cookie.enter().append("div");
cookie.append("p").text(function(d){ return d.text });
cookie.append("img").attr("src",function(d){ return d.img });
#cookie-jar div { border: solid 1px black; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="cookie-jar"></div>