javascript - Edit svgs instead of recreating
问题描述
I have an apparently simple misunderstanding of d3, and can't find a matching explanation, although lots of stuff I look for seems close.
Basically, I have an array of data, and I want to render a in an for each item. I want each to have its own children elements (a and a text />) that depends on the datum bound to each .
My problem is that when the data updates, additional and elements are added to each , rather than replacing it.
A minimal reproduction codepen is attached. Any assistance would be greatly appreciated!
https://codepen.io/kamry-bowman/pen/GaLvKJ
html:
<body>
<svg />
<button>Increment</button>
</body>
js:
let state = [];
for (let i = 0; i < 5; i++) {
state.push({ id: i, value: (i + 1) * 2 });
}
function render() {
console.log('running', state)
const svg = d3
.select("svg")
.attr("width", "1000")
.attr("height", "200");
const gGroup = svg
.selectAll("g")
.data(state, d => d.id)
.join("g")
.attr("transform", (d, i) => `translate(${100 * i})`);
gGroup
.append("circle")
.attr("cx", "50")
.attr("cy", "50")
.attr("r", "50")
.attr("fill", "none")
.attr("stroke", "black");
gGroup
.append("text")
.text(d => d.value)
.attr("stroke", "black")
.attr("x", "40")
.attr("y", "55")
.attr("style", "font: bold 30px sans-serif");
}
function clickHandler() {
state = state.map(obj => ({ ...obj, value: obj.value + 1 }))
render()
}
document.querySelector('button').addEventListener('click', clickHandler)
render()
I have found a hacky fix of calling gGroup.html(''), but that seems like I'm missing something with how the library is supposed to work.
解决方案
Add this line directly after you create your svg in the render method:
svg.selectAll("*").remove();
This will remove all children of the root svg. Otherwise you'll just keep drawing on top of the same svgs.
And the docs:
d3.selectAll() selection.remove()
EDIT
To update just the text values themselves, call this update()
function in the click handler instead:
function update() {
const svg = d3.select("svg")
d3.selectAll("svg text").each(function(d, i) {
d3.select(this).text(state[i].value);
});
}
The codepen has been updated to use this method
EDIT AGAIN
If you want to bind the values to their id
, just access it from d
in .each()
method instead of i
(which is the index in the collection):
function update() {
const svg = d3.select("svg")
d3.selectAll("svg text").each(function(d, i) {
d3.select(this).text(
state.find((e) => e.id === d.id).value
);
});
}
Since the entries can be in any order, we'll have to use Array.find()
to find the element with the matching id. Here's another codepen for this.
推荐阅读
- optaplanner - Optaplanner FieldAccessingSolutionCloner:如何建模外化的走时矩阵?
- css - 为什么填充会影响相邻元素的定位?
- flutter - 颤振:没有设备
- ruby-on-rails - Rails + Webpacker + React.js / 找不到模块
- matlab - 八度编码 - 我需要帮助编码多项式的系数
- windows - How can my application access the list of files and folders that Windows Explorer displays under "Quick Access"?
- angular - Ionic Contact plugin
- angular - 获取对象角度 CLI 的数组
- javascript - 告诉 forEach 等待,并且主要避免将所有内容包装在异步函数中以使用等待?
- sass - How to import fonts using Gulp/Sass?