Я пытаюсь построить визуализацию иерархического дерева с помощью React и D3. Мой компонент получает иерархические данные в формате CSV в качестве свойств, которые я передаю через функцию stratify
D3, чтобы получить узел root моего дерева. Узел root установлен как состояние.
В настоящее время я использую один useEffect(()=>{},[root])
для построения дерева, которое повторно отображает любые изменения в root.
Это беспорядочно, поскольку все действия происходят внутри этого useEffect()
. Я хочу знать, как я могу отделить метод update()
и использовать его отдельно.
Поскольку я новичок, использую и React, и D3, я приветствую любые другие предложения о том, как обрабатывать состояние, как сделать он более декларативный и др.
Вот код:
useEffect(() => {
if (root) {
//Declare a tree layout
//nodeSize ensure each node has it's own space and does not overlap
const tree = d3
.tree()
.nodeSize([
attributes.nodeWidth,
attributes.nodeHeight + attributes.veritcalNodeGap,
]);
root.x0 = 0;
root.y0 = attributes.width / 2;
//Set children of nodes deeper than 2 to null;
root.descendants().forEach((d, i) => {
d.id = i;
d._children = d.children;
if (d.depth && d.data.child.length !== 7) d.children = null;
});
// append the svg object to the body of the page
// and define zoom behaviours
const svg = d3
.select(d3Ref.current)
.call(
d3
.zoom()
.scaleExtent([0.05, 3])
.on("zoom", () => svg.attr("transform", d3.event.transform))
)
.on("dblclick.zoom", null)
.append("svg")
.attr("viewBox", [0, 0, attributes.width, attributes.height])
.append("g")
.attr("transform", (d) => `translate(${attributes.width / 2},120)`);
//Group all links together
const gLink = svg
.append("g")
.attr("fill", "none")
.attr("stroke", "#555")
.attr("stroke-opacity", 1)
.attr("stroke-width", 1.5);
// .attr("x", "200 ");
//Group all nodes together
const gNode = svg
.append("g")
.attr("cursor", "pointer")
.attr("pointer-events", "all");
const diagonal = linkVertical()
.x((d) => d.x)
.y((d) => d.y);
update();
function update() {
const nodes = root.descendants().reverse();
const links = root.links();
tree(root);
//Define group and join the data
const node = gNode.selectAll("g").data(nodes, (d) => d.id);
let nodeEnter = node
.enter()
.append("g")
.attr("class", "node")
.attr("transform", (d) => `translate(${root.x0},${root.y0})`)
.on("click", (d) => {
d.children = d.children ? null : d._children;
update();
});
let nodeGroup = nodeEnter.append("g").attr("class", "node-group");
nodeEnter
.append("circle")
.attr("r", 7)
.attr("cursor", (d) => (d._children ? "pointer" : "none"))
.attr("fill", (d) => (d._children ? "lightsteelblue" : "#999"))
.attr("stroke", (d) => (d._children ? "steelblue" : "#999"))
.attr("stroke-width", 2);
//add text
nodeEnter
.append("text")
.attr("dy", ".35em")
.attr("x", 25)
.text((d) => d.data.child);
//Transition nodes to their new positions
const nodeUpdate = node //SVG.data()
.merge(nodeEnter)
.transition()
.duration(attributes.duration)
.attr("transform", (d) => `translate(${d.x},${d.y})`)
.attr("fill-opacity", 1)
.attr("stroke-opacity", 1);
//Transition exiting nodes to the parent's new position
const nodeExit = node
.exit()
.transition()
.duration(attributes.duration)
.remove()
.attr("transform", (d) => `translate(${root.x},${root.y})`);
// // Update the links…
const link = gLink.selectAll("path").data(links, (d) => d.target.id);
// // Enter any new links at the parent's previous position.
const linkEnter = link
.enter()
.append("path")
.attr("class", "link")
.attr("d", (d) => {
const o = { x: root.x0, y: root.y0 };
return diagonal({ source: o, target: o });
});
// //Transition links to their new position
link
.merge(linkEnter)
.transition()
.duration(attributes.duration)
.attr("d", diagonal);
// // Transition exiting nodes to the parent's new position.
link
.exit()
.transition()
.duration(attributes.duration)
.remove()
.attr("d", (d) => {
const o = { x: root.x, y: root.y };
return diagonal({ source: o, target: o });
});
root.eachBefore((d) => {
d.x0 = d.x;
d.y0 = d.y;
});
}
}
}, [root]);