Odd behavior of d3.transition

…衆ロ難τιáo~ 提交于 2021-01-28 07:55:59

问题


I'm developing a personal web app with react and d3js.

The core concept is I receive a bunch of data via websocket every 5 seconds, and I use these data to draw real-time circle on the canvas. The data I receive is an array of object, which looks like this:

data = [{ name: 'alice', x: 10, y: 20, }...]

And this is how I draw the circles:

_renderCircles = () => {
    const t = d3.transition().duration(500);
    const radius = 3;

    const group = d3.select(this.refs.container)
                    .selectAll('g.ring')
                    .data(data);
    const circleGroup = group.enter().append('g')
                    .attr('class', 'cart')
                    .attr('transform', d => {
                        return `translate(${d.x}, ${d.y})`;
                    });
    circleGroup.append('circle');
    circleGroup.append('text')
                    .attr('text-anchor', 'middle')
                    .attr('fill', 'white');

    group.select('circle')
                    .attr('r', radius)
                    .style('fill', 'yellow');
    group.select('text')
                    .text(d => { return d.name; });

    group.transition(t)
                    .attr('transform', d => {
                        return `translate(${d.x}, ${d.y})`;
                    };

    group.exit().remove();
}

The odd behavior is once a while, some of the circles kind of "exchange" their locations. It doesn't happen all the time but still often enough to raise my concern.

example

To be more specific, let's say my data doesn't change (so it receives the same data), but during the transition, I can see circle B is moving to A, while circle A is moving to B. When the transition complete, circle B's text becomes 'A' and circle A's text becomes 'B', so if we look at the result, nothing changed. But I can still see circles moving during the transition.

I hope I explained my question clear enough, and I can upload a video if necessary. This is really weird to me, any thought would be so welcomed. Thanks in advance!


回答1:


The most probable explanation here is that you think the data is exactly the same, but the order of the objects within the data is different. That, togheter with the lack of a key function, makes the whole difference.

In D3, if you don't set a key function, the data is bound according to the indices. The API explains:

If a key function is not specified, then the first datum in data is assigned to the first selected element, the second datum to the second selected element, and so on.

We can see it in this simple demo, based on your code. Here, the data is almost the same, but the A and B objects are swapped in the newData array:

const data = [{
  name: "A",
  x: 20,
  y: 20
}, {
  name: "B",
  x: 20,
  y: 120
}, {
  name: "C",
  x: 100,
  y: 40
}, {
  name: "D",
  x: 260,
  y: 20
}, {
  name: "E",
  x: 210,
  y: 130
}];
const newData = [{
  name: "B",
  x: 20,
  y: 120
}, {
  name: "A",
  x: 20,
  y: 20
}, {
  name: "C",
  x: 100,
  y: 40
}, {
  name: "D",
  x: 260,
  y: 20
}, {
  name: "E",
  x: 210,
  y: 130
}];
const svg = d3.select("svg");

renderCircles(data);
d3.timeout(function() {
  renderCircles(newData);
}, 1000)

function renderCircles(data) {
  const t = d3.transition().duration(1000);
  const radius = 12;

  const group = svg.selectAll('g')
    .data(data);
  const circleGroup = group.enter().append('g')
    .attr('class', 'cart')
    .attr('transform', d => {
      return `translate(${d.x}, ${d.y})`;
    });
  circleGroup.append('circle')
    .attr('r', radius)
    .style('fill', 'tan');
  circleGroup.append('text')
    .attr('text-anchor', 'middle')
    .attr("dy", 4)
    .text(d => {
      return d.name;
    });

  group.select('text')
    .text(d => {
      return d.name;
    });

  group.transition(t)
    .attr('transform', d => {
      return `translate(${d.x}, ${d.y})`;
    });

  group.exit().remove();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>

Therefore, the solution is using a key function. For instance:

.data(data, d => d.name);

Now have a look at the same code, with that difference only... nothing will happen when we call the function with the new data:

const data = [{
  name: "A",
  x: 20,
  y: 20
}, {
  name: "B",
  x: 20,
  y: 120
}, {
  name: "C",
  x: 100,
  y: 40
}, {
  name: "D",
  x: 260,
  y: 20
}, {
  name: "E",
  x: 210,
  y: 130
}];
const newData = [{
  name: "B",
  x: 20,
  y: 120
}, {
  name: "A",
  x: 20,
  y: 20
}, {
  name: "C",
  x: 100,
  y: 40
}, {
  name: "D",
  x: 260,
  y: 20
}, {
  name: "E",
  x: 210,
  y: 130
}];
const svg = d3.select("svg");

renderCircles(data);
d3.timeout(function() {
  renderCircles(newData);
}, 1000)

function renderCircles(data) {
  const t = d3.transition().duration(1000);
  const radius = 12;

  const group = svg.selectAll('g')
    .data(data, d => d.name);
  const circleGroup = group.enter().append('g')
    .attr('class', 'cart')
    .attr('transform', d => {
      return `translate(${d.x}, ${d.y})`;
    });
  circleGroup.append('circle')
    .attr('r', radius)
    .style('fill', 'tan');
  circleGroup.append('text')
    .attr('text-anchor', 'middle')
    .attr("dy", 4)
    .text(d => {
      return d.name;
    });

  group.select('text')
    .text(d => {
      return d.name;
    });

  group.transition(t)
    .attr('transform', d => {
      return `translate(${d.x}, ${d.y})`;
    });

  group.exit().remove();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>


来源:https://stackoverflow.com/questions/57045959/odd-behavior-of-d3-transition

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!