问题
Below code represents a flow chart with connecting links that was built using D3.js Things work fine, but one issue I am seeing is with the links, right now the links are drawn first and then the nodes are transitioned/animated to their respective positions.
How can I have both links and nodes transition to their positions simultaneously ( smoothly ) ?
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120,
},
width = 960 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
var treeData = [{
"name": "Root",
"type": "decision",
"id": "root",
"children": [{
"name": "analytics",
"condition": "False",
"type": "decision",
"value": "a+b",
"id": "child1",
"children": [{
"name": "distinction",
"type": "action",
"id": "child2",
"condition": "True",
"value": "5",
}, {
"name": "nonDistinction",
"type": "action",
"id": "child3",
"condition": "False",
"value": "4",
"children": [],
}],
},
{
"condition": "True",
"name": "division",
"type": "decision",
"value": "a-b",
"id": "child33",
"children": [{
"condition": "True",
"name": "division1",
"type": "decision",
"value": "a-b",
"id": "child44",
"children": [{
"condition": "True",
"name": "division1.1",
"type": "decision",
"value": "a-b",
"id": "child599",
"children": [{
"condition": "True",
"name": "division1.1.34",
"type": "decision",
"value": "a-b",
"id": "child234234",
"children": [{
"condition": "True",
"name": "division1.1.434",
"type": "decision",
"value": "a-b",
"id": "child35343",
"children": [],
}],
}, {
"condition": "True",
"name": "division1.1.2",
"type": "decision",
"value": "a-b",
"id": "child77",
"children": [{
"condition": "True",
"name": "division1.1.1",
"type": "decision",
"value": "a-b",
"id": "child1222",
"children": [],
}, {
"condition": "True",
"name": "division1.1.1",
"type": "decision",
"value": "a-b",
"id": "child66",
"children": [],
}],
}],
}, {
"condition": "True",
"name": "NODE HAVING OVERLAP ISSUE",
"type": "decision",
"value": "a-b",
"id": "child9090",
"children": [{
"condition": "True",
"name": "division1.1.1",
"type": "decision",
"value": "a-b",
"id": "child909090",
"children": [],
}],
}],
},
{
"condition": "True",
"name": "division2",
"type": "decision",
"value": "a-b",
"id": "child55",
"children": [{
"condition": "True",
"name": "division2.1",
"type": "decision",
"value": "a-b",
"id": "child88",
"children": [{
"condition": "True",
"name": "division2.1.1",
"type": "decision",
"value": "a-b",
"id": "child99",
"children": [],
}],
}],
},
],
},
],
}];
var i = 0,
duration = 1000,
rectW = 120,
rectH = 60;
var treeMap = d3.tree()
.nodeSize([140, 120])
.separation(function(a, b) {
// If they have the same parent
if(a.parent === b.parent) {
// and are either both leaf nodes or both not leaf nodes
// or have only one child (which results in a straight line down)
if((a.children === undefined || a.children.length <= 1) ===
(b.children === undefined || b.children.length <= 1)) {
return 1;
}
// else, increase the size between them
return 2;
}
// If they have the same depth, mark them as such so we can avoid them later
if(a.depth === b.depth) {
a.data.avoidRight = b;
b.data.avoidLeft = a;
}
return 2;
});
//LINK FUNCTION TO DRAW LINKS
var linkFunc = function(d) {
var source = {
x: d.source.x + rectW / 2,
y: d.source.y + (rectH / 2),
};
var target = {
x: d.target.x + (rectW / 2),
y: d.target.y + 3,
};
// This is where the line bends
var inflection = {
x: target.x,
y: source.y,
};
var radius = 5;
var result = "M" + source.x + ',' + source.y;
if(!d.source.data.type) {
return;
}
if(source.x < target.x) {
// Child is to the right of the parent
if(d.source.data.avoidRight !== undefined && inflection.x > d.source.data.avoidRight.x) {
// There is some node that we should try to avoid first
result += ' H' + (d.source.data.avoidRight.x - 2 * radius);
result += ' V' + (d.source.data.avoidRight.y + rectH + radius);
inflection.y = d.source.data.avoidLeft.y + rectH + radius;
}
result += ' H' + (inflection.x - radius);
} else {
// Child is to the left of parent
if(d.source.data.avoidLeft !== undefined && inflection.x < d.source.data.avoidLeft.x + rectW) {
result += ' H' + (d.source.data.avoidLeft.x + rectW + 2 * radius);
result += ' V' + (d.source.data.avoidLeft.y + rectH + radius);
inflection.y = d.source.data.avoidLeft.y + rectH + radius;
}
result += ' H' + (inflection.x + radius);
}
// Curve the line at the bend slightly
result += ' Q' + inflection.x + ',' + inflection.y + ' ' + inflection.x + ',' + (inflection.y + radius);
result += 'V' + target.y;
return result;
};
// END OF LINK FUNC //
const zoomSvg = d3.select('.tree-diagram')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g');
const svg = zoomSvg.append('g')
.attr('transform', 'translate(' + 300 + ',' + 20 + ')');
const attachZoom = d3.select('svg');
attachZoom.call(d3.zoom().on('zoom', () => {
zoomSvg.attr('transform', d3.event.transform)
}))
// ADD ARROW TO THE BOTTOM POINTING TO THE NEXT DECISION.
svg.append("svg:defs")
.selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter()
.append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", 0.5)
.attr("markerWidth", 4)
.attr("markerHeight", 4)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
root = d3.hierarchy(treeData[0], function(d) {
return d.children;
});
root.x0 = 0;
root.y0 = 0;
update(root);
d3.select(".tree-diagram")
.style("height", "1000px");
// END OF DRAW TREEE //
function update(source) {
const treeData = treeMap(root);
// Compute the new tree layout.
var nodes = treeData.descendants(),
links = treeData.links();
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 90;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.data.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter()
.append("g")
.attr('transform', 'translate(' + source.x0 + ', ' + source.y0 + ')')
.attr("class", "node")
nodeEnter.append('path')
.attr('d', function(d) {
if (d.data.type === 'decision') {
return 'M 60 0 120 30 60 60 0 30 Z';
} else if (d.data.type === 'action') {
return 'M 0 0 120 0 120 60 0 60 Z';
} else {
return 'M -100 -10 -10 -10 -10 -10 -10 -10Z';
}
})
.attr("stroke-width", 1)
.attr('class', 'myPaths')
.style("fill", function(d) {
return "lightsteelblue";
});
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.data.name;
});
// UPDATE
var nodeUpdate = nodeEnter.merge(node);
// Transition to the proper position for the node
nodeUpdate.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + (d.x) + "," + (d.y) + ")";
});
nodeUpdate.select('path.myPaths')
.attr("d", function(d) {
if (d.data.type === 'decision') {
return 'M 60 0 120 30 60 60 0 30 Z';
} else if (d.data.type === 'action') {
return 'M 0 0 120 0 120 60 0 60 Z';
} else {
return 'M -100 -10 -10 -10 -10 -10 -10 -10Z';
}
});
var nodeExit = node.exit()
.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
// Update the links…
var link = svg.selectAll(".link")
.data(links, function(d) {
return d.source.data.id + " " + d.target.data.id;
})
.classed('link1', true);
// Enter any new links at the parent's previous position.
var linkEnter = link.enter()
.insert("g", "g")
.attr("class", "link");
linkEnter.append('path')
.attr("marker-end", "url(#end)");
// Add Link Texts.
linkEnter.append('text');
// Merge the new and the existing links before setting `d` and `text` on all of them
link = linkEnter.merge(link);
link.select('path')
.attr("d", linkFunc);
link.select('text')
.text(function(d, i) {
if (d.source.x < d.target.x) {
return 'True';
} else {
return 'False';
}
})
.attr('transform', function(d) {
if (d.source.x < d.target.x && d.target.data.type) {
return 'translate(' + (d.target.x + rectW / 2) + ',' + (d.source.y + rectH) + ')';
} else {
return null;
}
});
//LinkUpdate
var linkUpdate = linkEnter.merge(link);
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", linkFunc);
// Transition links to their new position.
// Transition exiting nodes to the parent's new position.
link.exit()
.transition()
.duration(duration)
.attr("d", linkFunc)
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
.node {
cursor: pointer;
outline: none !important;
}
.node text {
font: 10px sans-serif;
}
.button>path {
stroke: blue;
stroke-width: 1.5;
/* outline: none; */
}
.button>rect {
fill: #ddd;
stroke: grey;
stroke-width: 1px;
}
.conditionalSvg {
/* outline: none; */
display: none;
}
.hide {
/* display: none; */
opacity: 0 !important;
/* pointer-events: none; */
}
.link:hover {
outline: none !important;
cursor: pointer;
stroke-width: 3px;
}
.link path {
/* outline: none !important; */
fill: none;
stroke: darkgray;
stroke-width: 2px;
}
.link path:hover {
cursor: pointer;
stroke-width: 4px;
}
.link text {
font: 10px sans-serif;
}
.colorBlue {
background-color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div class="tree-diagram">
</div>
来源:https://stackoverflow.com/questions/65210665/d3-make-links-flow-along-with-nodes-when-tree-loads