gpt4 book ai didi

javascript - 一旦父节点在 d3 图中折叠,需要将子节点的链接转移到父节点

转载 作者:行者123 更新时间:2023-12-04 07:38:17 25 4
gpt4 key购买 nike

这是我想用 d3 js 实现的屏幕。
actual requirement
单击具有子圆圈的圆圈可以切换(折叠/展开)。
我试过下面的代码片段。我唯一无法实现的是,一旦父圈崩溃,我需要将子圈的所有链接转移到其父圈。任何领先都会很棒。

const data = {
name: "root",
children: [
{
name: "A",
children: [
{name: "A1", value: 7}, {name: "A2", value: 8}, {name: "A3", value: 9}, {name: "A4", value: 10}, {name: "A5", value: 10}
]
},
{
name: "B",
children: [
{name: "B1", value: 11}, {name: "B2", value: 7}, {name: "B3", value: 8},
]
},
{
name: "C",
value: 10
},
{
name: "D",
value: 10
},
{
name: "E",
value: 10
}
],
links: [{from: "A3", to: "C"}, {from: "A2", to: "E"}, {from: "B1", to: "D"}, {from: "B2", to: "B3"}, {from: "B1", to: "C"}]
};

const findNode = (parent, name) => {
if (parent.name === name)
return parent;
if (parent.children) {
for (let child of parent.children) {
const found = findNode(child, name);
if (found) {
return found;
}
}
}
return null;
}

const svg = d3.select("svg");

const container = svg.append('g')
.attr('transform', 'translate(0,0)')

const onClickNode = (e, d) => {
e.stopPropagation();
e.preventDefault();

const node = findNode(data, d.data.name);
if(node.children && !node._children) {
node._children = node.children;
node.children = undefined;
node.value = 20;
updateGraph(data);
} else {
if (node._children && !node.children) {
node.children = node._children;
node._children = undefined;
node.value = undefined;
updateGraph(data);
}
}
}

const updateGraph = graphData => {
const pack = data => d3.pack()
.size([600, 600])
.padding(0)
(d3.hierarchy(data)
.sum(d => d.value * 3.5)
.sort((a, b) => b.value - a.value));

const root = pack(graphData);

const nodes = root.descendants().slice(1);
console.log('NODES: ', nodes);

const nodeElements = container
.selectAll("g.node")
.data(nodes, d => d.data.name);

const addedNodes = nodeElements.enter()
.append("g")
.classed('node', true)
.style('cursor', 'pointer')
.on('click', (e, d) => onClickNode(e, d));

addedNodes.append('circle')
.attr('stroke', 'black')

addedNodes.append("text")
.text(d => d.data.name)
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.style('visibility', 'hidden')
.style('fill', 'black');

const mergedNodes = addedNodes.merge(nodeElements);
mergedNodes
.transition()
.duration(500)
.attr('transform', d => `translate(${d.x},${d.y})`);

mergedNodes.select('circle')
.attr("fill", d => d.children ? "#ffe0e0" : "#ffefef")
.transition()
.duration(1000)
.attr('r', d => d.value)
mergedNodes.select('text')
.attr('dy', d => d.children ? d.value + 10 : 0)
.transition()
.delay(1000)
.style('visibility', 'visible')

const exitedNodes = nodeElements.exit()
exitedNodes.select('circle')
.transition()
.duration(500)
.attr('r', 1);
exitedNodes.select('text')
.remove();

exitedNodes
.transition()
.duration(750)
.remove();

const linkPath = d => {
const from = nodes.find(n => n.data.name === d.from);
const to = nodes.find(n => n.data.name === d.to);
if (!from || !to)
return null;

const length = Math.hypot(from.x - to.x, from.y - to.y);
const fd = from.value / length;
const fx = from.x + (to.x - from.x) * fd;
const fy = from.y + (to.y - from.y) * fd;

const td = to.value / length;
const tx = to.x + (from.x - to.x) * td;
const ty = to.y + (from.y - to.y) * td;
return `M ${fx},${fy} L ${tx},${ty}`;
};

const linkElements = container.selectAll('path.link')
.data(data.links.filter(linkPath));

const addedLinks = linkElements.enter()
.append('path')
.classed('link', true)
.attr('marker-end', 'url(#arrowhead-to)')
.attr('marker-start', 'url(#arrowhead-from)');

addedLinks.merge(linkElements)
.transition()
.delay(750)
.attr('d', linkPath)

linkElements.exit().remove();
}

updateGraph(data);
text {
font-family: "Ubuntu";
font-size: 12px;
}

.link {
stroke: blue;
fill: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>

<svg width="600" height="600">
<defs>
<marker id="arrowhead-to" markerWidth="10" markerHeight="7"
refX="10" refY="3.5" orient="auto">
<polygon fill="blue" points="0 0, 10 3.5, 0 7" />
</marker>
<marker id="arrowhead-from" markerWidth="10" markerHeight="7"
refX="0" refY="3.5" orient="auto">
<polygon fill="blue" points="10 0, 0 3.5, 10 7" />
</marker>
</defs>
</svg>

最佳答案

替换折叠节点的链接:

const links = data.links.map(link => {
let from = nodes.find(n => n.data.name === link.from);
if (!from) {
const ancestors = findNodeAncestors(data, link.from);
for (let index = 1; !from && index < ancestors.length -1; index++) {
from = nodes.find(n => n.data.name === ancestors[index].name)
}
}
let to = nodes.find(n => n.data.name === link.to);
if (!to) {
const ancestors = findNodeAncestors(data, link.to);
for (let index = 1; !to && index < ancestors.length -1; index++) {
to = nodes.find(n => n.data.name === ancestors[index].name)
}
}
return {from, to};
});
这是 findNodeAncestors 的代码:
const findNodeAncestors = (parent, name) => {
if (parent.name === name)
return [parent];
const children = parent.children || parent._children;
if (children) {
for (let child of children) {
const found = findNodeAncestors(child, name);
if (found) {
return [...found, parent];
}
}
}
return null;
}

const data = {
name: "root",
children: [
{
name: "A",
children: [
{name: "A1", value: 7}, {name: "A2", value: 8}, {name: "A3", value: 9}, {name: "A4", value: 10}, {name: "A5", value: 10}
]
},
{
name: "B",
children: [
{name: "B1", value: 11}, {name: "B2", value: 7}, {name: "B3", value: 8},
]
},
{
name: "C",
value: 10
},
{
name: "D",
value: 10
},
{
name: "E",
value: 10
}
],
links: [{from: "A3", to: "C"}, {from: "A2", to: "E"}, {from: "B1", to: "D"}, {from: "B2", to: "B3"}, {from: "B1", to: "C"}, {from: "B1", to: "A5"}]
};

const findNode = (parent, name) => {
if (parent.name === name)
return parent;
if (parent.children) {
for (let child of parent.children) {
const found = findNode(child, name);
if (found) {
return found;
}
}
}
return null;
}

const findNodeAncestors = (parent, name) => {
if (parent.name === name)
return [parent];
const children = parent.children || parent._children;
if (children) {
for (let child of children) {
const found = findNodeAncestors(child, name);
//console.log('FOUND: ', found);
if (found) {
return [...found, parent];
}
}
}
return null;
}

const svg = d3.select("svg");

const container = svg.append('g')
.attr('transform', 'translate(0,0)')

const onClickNode = (e, d) => {
e.stopPropagation();
e.preventDefault();

const node = findNode(data, d.data.name);
if(node.children && !node._children) {
node._children = node.children;
node.children = undefined;
node.value = 20;
updateGraph(data);
} else {
if (node._children && !node.children) {
node.children = node._children;
node._children = undefined;
node.value = undefined;
updateGraph(data);
}
}
}

const updateGraph = graphData => {
const pack = data => d3.pack()
.size([600, 600])
.padding(0)
(d3.hierarchy(data)
.sum(d => d.value * 3.5)
.sort((a, b) => b.value - a.value));

const root = pack(graphData);
const nodes = root.descendants().slice(1);

const nodeElements = container
.selectAll("g.node")
.data(nodes, d => d.data.name);

const addedNodes = nodeElements.enter()
.append("g")
.classed('node', true)
.style('cursor', 'pointer')
.on('click', (e, d) => onClickNode(e, d));

addedNodes.append('circle')
.attr('stroke', 'black')

addedNodes.append("text")
.text(d => d.data.name)
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.style('visibility', 'hidden')
.style('fill', 'black');

const mergedNodes = addedNodes.merge(nodeElements);
mergedNodes
.transition()
.duration(500)
.attr('transform', d => `translate(${d.x},${d.y})`);

mergedNodes.select('circle')
.attr("fill", d => d.children ? "#ffe0e0" : "#ffefef")
.transition()
.duration(1000)
.attr('r', d => d.value)
mergedNodes.select('text')
.attr('dy', d => d.children ? d.value + 10 : 0)
.transition()
.delay(1000)
.style('visibility', 'visible')

const exitedNodes = nodeElements.exit()
exitedNodes.select('circle')
.transition()
.duration(500)
.attr('r', 1);
exitedNodes.select('text')
.remove();

exitedNodes
.transition()
.duration(750)
.remove();

const linkPath = d => {
const length = Math.hypot(d.from.x - d.to.x, d.from.y - d.to.y);
const fd = d.from.value / length;
const fx = d.from.x + (d.to.x - d.from.x) * fd;
const fy = d.from.y + (d.to.y - d.from.y) * fd;

const td = d.to.value / length;
const tx = d.to.x + (d.from.x - d.to.x) * td;
const ty = d.to.y + (d.from.y - d.to.y) * td;

return `M ${fx},${fy} L ${tx},${ty}`;
};

const links = data.links.map(link => {
let from = nodes.find(n => n.data.name === link.from);
if (!from) {
const ancestors = findNodeAncestors(data, link.from);
for (let index = 1; !from && index < ancestors.length -1; index++) {
from = nodes.find(n => n.data.name === ancestors[index].name)
}
}
let to = nodes.find(n => n.data.name === link.to);
if (!to) {
const ancestors = findNodeAncestors(data, link.to);
for (let index = 1; !to && index < ancestors.length -1; index++) {
to = nodes.find(n => n.data.name === ancestors[index].name)
}
}
return {from, to};
});

const linkElements = container.selectAll('path.link')
.data(links.filter(l => l.from && l.to));

const addedLinks = linkElements.enter()
.append('path')
.classed('link', true)
.attr('marker-end', 'url(#arrowhead-to)')
.attr('marker-start', 'url(#arrowhead-from)');

addedLinks.merge(linkElements)
.style('visibility', 'hidden')
.transition()
.delay(750)
.attr('d', linkPath)
.style('visibility', 'visible')

linkElements.exit().remove();
}

updateGraph(data);
text {
font-family: "Ubuntu";
font-size: 12px;
}

.link {
stroke: blue;
fill: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>

<svg width="600" height="600">
<defs>
<marker id="arrowhead-to" markerWidth="10" markerHeight="7"
refX="10" refY="3.5" orient="auto">
<polygon fill="blue" points="0 0, 10 3.5, 0 7" />
</marker>
<marker id="arrowhead-from" markerWidth="10" markerHeight="7"
refX="0" refY="3.5" orient="auto">
<polygon fill="blue" points="10 0, 0 3.5, 10 7" />
</marker>
</defs>
</svg>

关于javascript - 一旦父节点在 d3 图中折叠,需要将子节点的链接转移到父节点,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67633143/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com