Javascript 常规更新模式d3,删除旧数据
我目前正在尝试使用d3(d3层次/d3力)在我的React应用程序上构建一个图表,我在理解常规模式更新的工作原理方面遇到了一个问题 我读了很多关于它的文章,但仍然对如何在代码中实现它感到困惑。更多信息,我目前的d3版本是5.15 下面是我当前用于绘制图表的代码:Javascript 常规更新模式d3,删除旧数据,javascript,reactjs,d3.js,svg,Javascript,Reactjs,D3.js,Svg,我目前正在尝试使用d3(d3层次/d3力)在我的React应用程序上构建一个图表,我在理解常规模式更新的工作原理方面遇到了一个问题 我读了很多关于它的文章,但仍然对如何在代码中实现它感到困惑。更多信息,我目前的d3版本是5.15 下面是我当前用于绘制图表的代码: drawChart(data) { const root = d3.hierarchy(data); const links = root.links(); const nodes = root.descendants();
drawChart(data) {
const root = d3.hierarchy(data);
const links = root.links();
const nodes = root.descendants();
const width = this.props.width;
const height = this.props.height;
const leavesNumber = root.leaves().length;
const initScale = 1 / Math.log(root.descendants().length);
const initTransX = 1;
const initTransY = initTransX * initScale;
console.log("root =>", root);
console.log("links =>", links);
console.log("nodes =>", nodes);
console.log("leavesNumber =>", leavesNumber);
console.log("initScale =>", initScale)
console.log("ancestors =>", root.depth);
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id).distance(
d => {
if (d.source.parent === null) {
return 400
} else {
return 0
}
})
.strength(1))
.force("charge", d3.forceManyBody().strength(-50))
.force("x", d3.forceX())
.force("y", d3.forceY())
//forceCollide create a radius around the node which will reject elements entering in
.force("collide", d3.forceCollide(d => d.depth === 0 ? 400 : d.depth === 1 ? 200 : 100))
.force('center', d3.forceCenter(0, 50))
const zoom = d3.zoom()
.scaleExtent([1, 5])
.on("zoom", () => {
const currentTransform = d3.event.transform;
g.attr("transform", currentTransform.scale(initScale));
slider.property("value", currentTransform.k);
});
const svg = d3.select("#container").append("svg")
.attr("viewBox", [-width / 2, -height / 2, width, height])
.attr("width", '100%')
.attr("height", '100%')
.attr("class", "carto-svg-container")
.call(zoom)
let g = svg.append("g").attr("id", "main-g-container").attr("transform","translate("+initTransX+","+initTransY+")scale("+initScale+","+initScale+")");
function slided(d) {
zoom.scaleTo(svg, d3.select(this).property("value"));
}
const slider = d3.select('#container').append("p").append("input")
.datum({})
.attr("type", "range")
.attr("value", zoom.scaleExtent()[0])
.attr("min", zoom.scaleExtent()[0])
.attr("max", zoom.scaleExtent()[1])
.attr("step", (zoom.scaleExtent()[1] - zoom.scaleExtent()[0]) / 100)
.attr("class", "carto-slidebar")
.on("input", slided)
d3.select('#container').append("svg").attr("width", 24).attr("height", 24)
.attr("class", "carto-zoom-out-button")
.on("click", () => zoom.scaleBy(svg.transition().duration(750), 0.70))
.append("g").attr("stroke", 'none').attr("stroke-width", "1").attr("fill", "none").attr("fill-rule", "evenodd")
.append("g")
.append("polygon").attr("fill", "#9AA5B8").attr("fille-rule", "nonzero").attr("points", "19 13 5 13 5 11 19 11")
.append("polygon").attr("points", "0 0 24 0 24 24 0 24")
d3.select('#container').append("svg").attr("width", 24).attr("height", 24)
.attr("class", "carto-zoom-in-button")
.on("click", () => zoom.scaleBy(svg.transition().duration(750), 1.5))
.append("g").attr("stroke", 'none').attr("stroke-width", "1").attr("fill", "none").attr("fill-rule", "evenodd")
.append("g")
.append("polygon").attr("fill", "#647592").attr("fille-rule", "nonzero").attr("points", "19 13 13 13 13 19 11 19 11 13 5 13 5 11 11 11 11 5 13 5 13 11 19 11")
.append("polygon").attr("points", "0 0 24 0 24 24 0 24")
let location = d3.select('#container')
.append("svg")
.attr("viewBox", [0, 0, 24, 24])
.attr("focusable", "false")
.attr("aria-hidden", "true")
.attr("role", "presentation")
.attr("class", "carto-reset-zoom-button")
.on("click", () => svg.transition().duration(750).call(zoom.transform, d3.zoomIdentity.scale(1)))
location.append("path").attr("fill", "none").attr("d", "M0 0h24v24H0z")
location.append("path").attr("d", "M12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm8.94 3c-.46-4.17-3.77-7.48-7.94-7.94V1h-2v2.06C6.83 3.52 3.52 6.83 3.06 11H1v2h2.06c.46 4.17 3.77 7.48 7.94 7.94V23h2v-2.06c4.17-.46 7.48-3.77 7.94-7.94H23v-2h-2.06zM12 19c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z")
const link = g.append("g")
.attr("id", "links-container")
.data(nodes)
.attr("stroke", "#999")
.selectAll("line")
.data(links)
.join("line");
link.each(function(d, i) {
d3.select(this)
.style("stroke-dasharray", d => {
if (d.target.data.children) {
return ("5, 5")
} else {
return "none"
}
})
.attr("stroke", d => {
if (d.target.data.children) {
return "#333857";
}
})
.attr("stroke-width", d => {
if (d.target.data.children) {
return 2;
} else {
return 1
}
})
})
const node = g.append("g")
.attr("id", "nodes-container")
.attr("stroke-width", 2)
.selectAll("g")
.data(nodes)
.enter()
.append("g")
.call(this.drag(simulation));
node.filter((d, i) => i === 0 || d.data.children)
.append("circle")
.attr("id", "node-circle")
.attr("r", (d, i) => i === 0 ? 114 : 90)
.attr("fill", (d, i) => i === 0 ? "#333857" : !d.data.children ? "transparent" : "#505D73")
.append("title").text(d => d.data.name)
node.filter(d => !d.data.children)
.append("rect")
.attr("fill", "#E0E3E9")
.attr("stroke", "#647592")
.attr("stroke-width", 1)
.attr("width", 141)
.attr("height", 24)
.append("title").text(d => d.data.name)
node.append("text")
.attr("dy", ".35em")
.style("text-anchor", (d, i) => i === 0 || d.data.children ? "middle" : "left")
.style("font-size", (d, i) => i === 0 ? "32px" : d.data.children ? '14px' : "12px")
.style("font-family", (d, i) => i === 0 ? "Roboto Medium" : !d.data.children ? "Roboto" : "Roboto Regular")
.style("fill", (d, i) => !(i === 0 || d.data.children) ? "#647592" : "#fff")
.text((d, i) => i === 0 ? leavesNumber + " results" : d.data.name)
simulation.on("tick", () => {
// console.log("this.props.data in tick=>", this.props.data)
let textsWidth = [];
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
d3.selectAll("#node-circle")
.attr("cx", d => d.x)
.attr("cy", d => d.y);
const texts = d3.selectAll("text")
// + 10 because + 20 in the final width to make padding
.attr("x", d => d.data.children ? d.x : d.x + 10)
// + 12 to center the text in height, rect height is currently 24
.attr("y", d => d.data.children ? d.y : d.y + 12)
texts.each(function(d, i) {
if (!d.data.children) {
textsWidth.push({width: this.getBBox().width, name: d.data.name})
}})
texts.each(function(d, i) {
d3.select(this)
.attr("x", d => {
if (d.data.children) {
return d.x
} else {
// set text position after rect width has been set
const currentElement = textsWidth.filter(item => d.data.name === item.name)
return ((d.x) - (Math.round(currentElement[0].width) / 2))
}
})
})
d3.selectAll("rect")
.attr("x", d => d.x)
.attr("y", d => d.y)
.each(function(d, i) {
// re set rect size after txt size has been known
let currentElement = textsWidth.filter(item => d.data.name === item.name)
d3.select(this)
.attr("width", Math.round(currentElement[0].width) + 20)
.attr("x", d => (d.x) - ((Math.round(currentElement[0].width) + 20) / 2))
.attr("height", 24)
})
});
// invalidation.then(() => simulation.stop());
return svg.node();
}
updateData(data) {
const root = d3.hierarchy(data);
const links = root.links();
const nodes = root.descendants();
console.log("nodes =>", nodes)
const leavesNumber = root.leaves().length;
const initScale = 1 / Math.log(root.descendants().length);
const initTransX = 1;
const initTransY = initTransX * initScale;
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id).distance(
d => {
if (d.source.parent === null) {
return 400
} else {
return 0
}
})
.strength(1))
.force("charge", d3.forceManyBody().strength(-50))
.force("x", d3.forceX())
.force("y", d3.forceY())
//forceCollide create a radius around the node which will reject elements entering in
.force("collide", d3.forceCollide(d => d.depth === 0 ? 400 : d.depth === 1 ? 200 : 100))
.force('center', d3.forceCenter(0, 50))
console.log("root 2 =>", root)
console.log("links 2 =>", links)
// get main element
let g = d3.select("#main-g-container")
.attr("transform","translate("+initTransX+","+initTransY+")scale("+initScale+","+initScale+")")
d3.select("#main-g-container")
.join("#main-g-container")
.attr("transform","translate("+initTransX+","+initTransY+")scale("+initScale+","+initScale+")")
d3.select("#nodes-container")
.selectAll("g")
.data(nodes)
.join(
enter => enter.append("g")
.attr("id", "node-element")
.call(this.drag(simulation))
.filter((d, i) => i === 0 || d.data.children)
.append("circle"),
update => update,
exit => exit.remove(),
)
这里是更新数据的函数(发送到此函数的参数中的数据是我想在图表中显示的新数据):
我要显示和更新的数据=>
const mockData = {
"name": "Eve",
"children": [
{
"name": "Cain",
"children": [
{
"name": "Ragnar",
},
{
"name": "Freya",
}
]
},
{
"name": "Seth",
"children": [
{
"name": "Enos le castor des prairies",
},
{
"name": "Noam",
}
]
},
{
"name": "Awan",
"children": [
{
"name": "Enoch",
}
]
},
]
}
我目前正在尝试更新svg中的圆圈(首先,在更新其余元素之前)。
到目前为止,我能够创建g元素,并在需要时在其中放置圆。因此,我迷路了,我不知道如何通过.join()给我的圆指定位置,因为我不知道如何选择这些圆。我应该创建一个d3.selectAll(#节点圆)还是选择父节点,然后找到更新其子节点的方法
我看到退出将包含更新集团不会更新的任何元素,因此我理解更新在加入中的重要性,我认为我得到了全球原则;但遗憾的是,我还是太困惑了
我是d3的新手,所以任何建议都将不胜感激
提前感谢,这可能会有帮助:
在最后有一个链接指向.join()的后续文章,该文章随版本5提供