Манипулирование элементами путем привязки новых данных


Я пытаюсь понять, как обновить некоторые 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 17

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 мог отслеживать, какие данные являются новыми, а какие-старыми.