d3 的即时值输入选择并转换为更新

instant values for d3 enter selection and transitioned for update

我试图避免在 d3 中重复输入和更新选择的代码。我已经成功地使用 merge 来正确选择新数据中存在的所有项目(在我使用 append 添加新数据之后)。现在我想设置一些属性,例如 rcycx。我可以在合并的选择上做到这一点,但如果我把它们放在 transition 后面,那么刚刚出现的项目就会从 0,0 开始动画,我希望它们出现在正确的位置。

我所希望的是通过某种方式将所有属性设置行写入一个函数,一次传递 enter() 选择,下一次传递过渡合并,这样我只需要编写设置 cx 一次的行。这是我已经遵循的伪代码

的猜测
 const valueCircles = this.chartGroup.selectAll('.valueCircle')
        .data(values);

    valueCircles.enter()
        .append('circle')
        .attr('stroke-width', '1px')
        .attr('stroke', '#ffffff')
        .attr('opacity', '1')
        .attr('class', 'dots valueCircle')
        .merge(valueCircles)
        .transition()
        .duration(100)
        .attr('r', (d) => {
            if (d.y < 0.5 ) {
                return 5;
            } else {
                return 15;
            }
        })
        .attr('cx', (d) => {
            return timescale(new Date(d.x));
        })
        .attr('cy', (d) => {
                return valueScale(d.y)
        })

这就是我想达到的目标

function setTheDynamicValues(aSelection) {
    aSelection
        .attr('cx', (d) => {
            return timescale(new Date(d.x));
        })
        .attr('cy', (d) => {
                return valueScale(d.y)
        })
}

function updateGraph(newData) {
   const valueCircles = this.chartGroup.selectAll('.valueCircle')
        .data(newData);

   setTheDynamicValues(valueCircles.enter());
   setTheDynamicValues(valueCircles.transition().duration(100));
}

另一种描述方式是使进入元素的过渡持续时间为 0,现有元素的过渡持续时间为 100,以便新元素显示正确并且现有元素具有可见过渡。

首先,D3 选择是不可变的:您的 merge 没有做任何事情。这将是正确的模式:

//here, pay attention to "let" instead of const
let valueCircles = this.chartGroup.selectAll('.valueCircle')
    .data(values);

//our enter selection is called "valuesCirclesEnter"
const valueCirclesEnter = valueCircles.enter()
    .append('circle')
    etc...

//now, you merge the selections
valueCircles = valueCirclesEnter.merge(valueCircles);

从那时起,valueCircles 包含您的更新和输入元素。

现在,回到你的问题:

地道的 D3 确实有很多重复,这也是很多人抱怨 D3 的地方。但是我们可以用selection.call这样的方法来避免一些重复。此外,由于您想要更新选择的过渡但输入选择的 none,您可以简单地删除 merge.

这是一个基本示例,使用了您的一些代码:

const data1 = [{
  x: 100,
  y: 100
}, {
  x: 230,
  y: 20
}];
const data2 = [{
  x: 10,
  y: 10
}, {
  x: 50,
  y: 120
}, {
  x: 190,
  y: 100
}, {
  x: 140,
  y: 30
}, {
  x: 270,
  y: 140
}];
const svg = d3.select("svg");
draw(data1);
setTimeout(() => draw(data2), 1000);

function draw(values) {
  const valueCircles = svg.selectAll('.valueCircle')
    .data(values);

  valueCircles.enter()
    .append('circle')
    .attr("class", "valueCircle")
    .attr("r", 10)
    .call(positionCircles);

  valueCircles.transition()
    .duration(1000)
    .call(positionCircles)
};

function positionCircles(selection) {
  selection.attr("cx", d => d.x)
    .attr("cy", d => d.y)
}
<script src="https://d3js.org/d3.v7.min.js"></script>
<svg></svg>

在上面的代码片段中,我使用名为 positionCircles 的函数定位输入和更新选择;但是,对于输入选择,我传递的是简单选择,而对于更新选择,我传递的是过渡选择。但是功能是一样的:

function positionCircles(selection) {
  selection.attr("cx", d => d.x)
    .attr("cy", d => d.y)
}