Манипулирование элементами путем привязки новых данных
Я пытаюсь понять, как обновить некоторые D3.элементы js просто связывают новые данные. Я не совсем уверен, возможно ли это или нет, но мне кажется, что так и должно быть.
Итак, сначала я создал четыре круга SVG и установил смещение cx
в зависимости от данных:
<body><div id="container"></div></body>
var svg = d3.select("div.container").append("svg")
.attr("class", "chart")
.attr("width", 1000)
.attr("height", 500);
// Create initial data and display
var data = [0, 10, 20, 30];
var circle = svg.selectAll("circle")
.data(data)
.enter()
.append('circle')
.attr("cx", function(d) {
return d*10;
})
.attr("cy", 100)
.attr("r", 10)
.style("fill", "steelblue");
Далее я добавляю новые данные и переход. Я ожидал бы увидеть, как круги медленно перемещаются в новое положение (это то, чего я пытаюсь достичь), но они этого не делают:
var data1 = [40, 50, 60, 70];
circle.data(data1).transition().duration(2500);
Делаю ли я основная ошибка? Возможно, я ошибся в выборе. Или просто невозможно обновить элементы только путем манипулирования данными?
UPDATE: если я сделаю console.log(circle)
, то я увижу массив элементов SVG circle, чего я и ожидал.
3 ответа:
Мощно (но раздражающе) D3.js иногда заставляет вас повторять себя для простых визуализаций. Если вы скажете ему, что хотите создать элемент с атрибутами, полученными из данных определенным образом, а затем позже вы хотите перенести эти атрибуты в новые данные, вы должны снова сказать ему, как получить значения (в случае, если вы хотите сделать что-то другое, например, другой визуальный макет). Как говорит @Andrew, вы должны сказать ему, что делать, когда он переходит.
Ты можно обойти эту "проблему", следуя следующей схеме:
var foo = d3.select('foo'); function redraw(someArray){ var items = foo.selectAll('bar').data(someArray); items.enter().append('bar'); items.exit().remove(); items .attr('foo',function(d){ return d }); }
В 2.0, Когда вы
append()
добавляете новые элементы вenter()
, они автоматически добавляются к исходному выделению с привязкой к данным, поэтому вызовыattr()
и что-то еще позже будут применяться к ним. Это позволяет использовать один и тот же код как для установки начальных значений, так и для обновления значений.Поскольку вы не хотите повторно создавать оболочку SVG для каждого обновления, вы должны создать ее вне функции
redraw
.Если вы хотите выполнить переходы:
Обратите внимание, что вышеописанные объекты ассоциируются с данными по индексу. Если вы хотите удалить промежуточную точку данных, чтобы она исчезла перед ее удалением (вместо удаления последнего элемента и преобразования всех остальных элементов в такой же), то вам следует указать уникальное (неиндексное) строковое значение для связи каждого элемента с:function redraw(someArray){ var items = foo.selectAll('bar').data(someArray); items.enter().append('bar') .attr('opacity',0) .attr('foo',initialPreAnimationValue); items.exit().transition().duration(500) .attr('opacity',0) .remove(); items.transition.duration(500) .attr('opacity',1) .attr('foo',function(d){ return d }); }
var data = [ {id:1, c:'red', x:150}, {id:3, c:'#3cf', x:127}, {id:2, c:'green', x:240}, {id:4, c:'red', x:340} ]; myItems.data(data,function(d){ return d.id; });
Вы можете посмотреть пример этого и играть с ним в прямом эфире на my D3.JS playground .
Первый, посмотрите, что происходит, когда вы комментируете одну из строк данных, а затем возвращаете ее обратно. Затем удалите параметр
ƒ('id')
из вызоваdata()
в строке 4 и снова попробуйте закомментировать и в строках данных.Edit : в качестве альтернативы, как прокомментировал знаменитый mbostock, вы можете использовать
selection.call()
вместе с многоразовой функцией как способ высушить свой код:var foo = d3.select('foo'); function redraw(someArray){ var items = foo.selectAll('bar').data(someArray); items.enter().append('bar').call(setEmAll); items.exit().remove(); items.call(setEmAll); } function setEmAll(myItems){ myItems .attr('foo',function(d){ return d*2 }) .attr('bar',function(d){ return Math.sqrt(d)+17 }); }
Как показано выше,
.call()
вызывает функцию и передает выделение в качестве аргумента, так что вы можете выполните одну и ту же настройку для выбранного объекта в нескольких местах.
Хорошо, теперь я понял! Вам нужно сказать ему, что переходить:
circle.data(data1).transition().duration(2500).attr("cx", function(d) { return d*10; });
Очень хорошим ресурсом для добавления новых данных в существующую визуализацию является официальный учебник по обновлению данных.
Http://bl.ocks.org/mbostock/3808221
Основной вывод заключается в том, что вы хотите определить ключевую функцию при вызове данных, чтобы d3 мог отслеживать, какие данные являются новыми, а какие-старыми.