d3 的即时值输入选择并转换为更新
instant values for d3 enter selection and transitioned for update
我试图避免在 d3 中重复输入和更新选择的代码。我已经成功地使用 merge
来正确选择新数据中存在的所有项目(在我使用 append
添加新数据之后)。现在我想设置一些属性,例如 r
、cy
和 cx
。我可以在合并的选择上做到这一点,但如果我把它们放在 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)
}
我试图避免在 d3 中重复输入和更新选择的代码。我已经成功地使用 merge
来正确选择新数据中存在的所有项目(在我使用 append
添加新数据之后)。现在我想设置一些属性,例如 r
、cy
和 cx
。我可以在合并的选择上做到这一点,但如果我把它们放在 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)
}