问题
The following d3.js (v4) interactive tree layout I've put together as a proof of concept for a user interface project is not behaving as expected. This is my first d3.js visualisation and I'm still getting my head around all the concepts.
The snippet highlights the issue I'm struggling with. If you click on yellow node labeled "F: 3", I'd expect the children ("F: 4", "T: 5" and it's two links) to transition back to the real time x,y coordinate of "F: 3". However, it seems to only transition to "F: 3" node's initial x,y coordinate. Any idea what I'm missing?
var id = 0;
var data = {
source: {
id: id++,
type: 'dataSource',
name: 'Data Source: ' + id,
silos: [
{ name: 'Silo 1', selected: true },
{ name: 'Silo 2', selected: false },
{ name: 'Silo 3', selected: false }
],
union: {
id: id++,
type: 'union',
name: 'Union:' + id,
count: null,
cardinalities: [
{
id: id++, type: 'cardinality', positive: false, name: 'F: ' + id, count: 40, cardinalities: [
{ id: id++, type: 'cardinality', positive: false, name: 'F: ' + id, count: 40, cardinalities: [] },
{ id: id++, type: 'cardinality', positive: true, name: 'T: ' + id, count: 60, cardinalities: [] }
]
},
{ id: id++, type: 'cardinality', positive: true, name: 'T: ' + id, count: 60, cardinalities: [] }
]
}
}
}
// global variables
var containerPadding = 20;
var container = d3.select('#container').style('padding', containerPadding + 'px'); // contains the structured search svg
var svg = container.select('svg'); // the canvas that displays the structured search
var group = svg.append('g'); // contains the tree elements (nodes & links)
var nodeWidth = 40, nodeHeight = 30, nodeCornerRadius = 3, verticalNodeSeparation = 150, transitionDuration = 600;
var tree = d3.tree().nodeSize([nodeWidth, nodeHeight]);
var source;
function nodeClicked(d) {
source = d;
switch (d.data.type) {
case 'dataSource':
// todo: show the data source popup and update the selected values
d.data.silos[0].selected = !d.data.silos[0].selected;
break;
default:
// todo: show the operation popup and update the selected values
if (d.data.cardinalities && d.data.cardinalities.length) {
d.data.cardinalities.splice(-2, 2);
}
else {
d.data.cardinalities.push({ id: id++, type: 'cardinality', positive: false, name: 'F: ' + id, count: 40, cardinalities: [] });
d.data.cardinalities.push({ id: id++, type: 'cardinality', positive: true, name: 'T: ' + id, count: 60, cardinalities: [] });
}
break;
}
render();
}
function renderLink(source, destination) {
var x = destination.x + nodeWidth / 2;
var y = destination.y;
var px = source.x + nodeWidth / 2;
var py = source.y + nodeHeight;
return 'M' + x + ',' + y
+ 'C' + x + ',' + (y + py) / 2
+ ' ' + x + ',' + (y + py) / 2
+ ' ' + px + ',' + py;
}
function render() {
// map the data source to a heirarchy that d3.tree requires
// d3.tree instance needs the data structured in a specific way to generate the required layout of nodes & links (lines)
var hierarchy = d3.hierarchy(data.source, function (d) {
switch (d.type) {
case 'dataSource':
return d.silos.some(function (e) { return e.selected; }) ? [d.union] : undefined;
default:
return d.cardinalities;
}
});
// set the layout parameters (all required for resizing)
var containerBoundingRect = container.node().getBoundingClientRect();
var width = containerBoundingRect.width - containerPadding * 2;
var height = verticalNodeSeparation * hierarchy.height;
svg.transition().duration(transitionDuration).attr('width', width).attr('height', height + nodeHeight);
tree.size([width - nodeWidth, height]);
// tree() assigns the (x, y) coords, depth, etc, to the nodes in the hierarchy
tree(hierarchy);
// get the descendants
var descendants = hierarchy.descendants();
// ensure source is set when rendering for the first time (hierarchy is the root, same as descendants[0])
source = source || hierarchy;
// render nodes
var nodesUpdate = group.selectAll('.node').data(descendants, keyFunction);
var nodesEnter = nodesUpdate.enter()
.append('g')
.attr('class', 'node')
.attr('transform', 'translate(' + source.x + ',' + source.y + ')')
.style('opacity', 0)
.on('click', nodeClicked);
nodesEnter.append('rect')
.attr('rx', nodeCornerRadius)
.attr('width', nodeWidth)
.attr('height', nodeHeight)
.attr('class', function (d) { return 'box ' + d.data.type; });
nodesEnter.append('text')
.attr('dx', nodeWidth / 2 + 5)
.attr('dy', function (d) { return d.parent ? -5 : nodeHeight + 15; })
.text(function (d) { return d.data.name; });
nodesUpdate
.merge(nodesEnter)
.transition().duration(transitionDuration)
.attr('transform', function (d) { return 'translate(' + d.x + ',' + d.y + ')'; })
.style('opacity', 1);
nodesUpdate.exit()
.transition().duration(transitionDuration)
.attr('transform', function (d) { return 'translate(' + source.x + ',' + source.y + ')'; })
.style('opacity', 0)
.remove();
// render links
var linksUpdate = group.selectAll('.link').data(descendants.slice(1), keyFunction);
var linksEnter = linksUpdate.enter()
.append('path')
.attr('class', 'link')
.classed('falsey', function (d) { return d.data.positive === false })
.classed('truthy', function (d) { return d.data.positive === true })
.attr('d', function (d) { var o = { x: source.x, y: source.y }; return renderLink(o, o); })
.style('opacity', 0);
linksUpdate
.merge(linksEnter)
.transition().duration(transitionDuration)
.attr('d', function (d) { return renderLink({ x: d.parent.x, y: d.parent.y }, d); })
.style('opacity', 1);
linksUpdate.exit()
.transition().duration(transitionDuration)
.attr('d', function (d) { var o = { x: source.x, y: source.y }; return renderLink(o, o); })
.style('opacity', 0)
.remove();
}
function keyFunction(d) { return d.data.id; }
window.addEventListener('resize', render); // todo: use requestAnimationFrame (RAF) for this
render();
.link {
fill:none;
stroke:#555;
stroke-opacity:0.4;
stroke-width:1.5px
}
.truthy {
stroke:green
}
.falsey {
stroke:red
}
.box {
stroke:black;
stroke-width:1;
cursor:pointer
}
.dataSource {
fill:blue
}
.union {
fill:orange
}
.cardinality {
fill:yellow
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="container" style="background-color:gray">
<svg style="background-color:#fff" width="0" height="0"></svg>
</div>
来源:https://stackoverflow.com/questions/40974290/d3-js-tree-how-do-i-get-exiting-nodes-and-links-to-transition-to-parents-x-pos