d3.js tree: How do I get exiting nodes and links to transition to parent's x position?

家住魔仙堡 提交于 2019-12-24 06:45:57

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!