d3.js tree:如何将退出节点和链接转换为父x的位置?

时间:2016-12-05 12:27:22

标签: d3.js d3.js-v4

以下d3.js(v4)交互式树布局我已经放在一起作为用户界面项目的概念证明,其行为不符合预期。这是我的第一个d3.js可视化,我仍然围绕着所有的概念。

该片段突出了我正在努力解决的问题。如果你点击标有“F:3”的黄色节点,我希望孩子们(“F:4”,“T:5”和它的两个链接)转换回实时x,y坐标“F” :3“。但是,它似乎只转换到“F:3”节点的初始x,y坐标。知道我错过了什么吗?

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>

0 个答案:

没有答案