防止D3在更新时添加重复项

时间:2017-01-17 14:30:26

标签: d3.js graph collapse force-layout

以下是“工作”D3动画。

子节点在鼠标单击时成功消失...但是,添加了重复的节点(“圆圈”)。如果您正在运行以下代码,然后折叠并打开节点,您将看到节点出现在其他节点之上!

圆形元素的重复也可以在chrome-inspection中看到。

简单的崩溃和开放就是我们在这里所追求的。非常感谢您的帮助 !谢谢。

(忽略节点标签 - 它们并不重要)

<html>
<head>
<style>
    .node {
        cursor: pointer;
        font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
        font-weight: 300;
    }

    .node .text {
        fill: white;
    }

    .ORG .circle {
        fill: #1d3649;
    }

    .EMR .circle {
        fill: #B2D0F5;
        stroke: #5596e6;
        stroke-dasharray: 3px, 3px;
        opacity: .5;
    }

    .EMR .circle:hover {
        fill: #5596e6;
    }

    .link {
        fill: none;
        stroke: #eee;
        stroke-width: 1.5px;
        font: 10px sans-serif;
    }

    .link.active {
        stroke: #ddd;
        stroke-width: 2;
    }

    .arrow {
        fill: #666;
    }

    .arrow.active {
        stroke-width: 0 !important;
    }

</style>
</head>
<body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
    var dataset = {
        "nodes": [{
            "id": 223,
            "type": "Parent",
            "properties": {

            }
        }, {
            "id": 136525,
            "type": "Child",
            "properties": {
                "patient": "6090",
                "batch": "70"
            }
        }, {
            "id": 146525,
            "type": "Child",
            "properties": {
                "patient": "6090",
                "batch": "70"
            }
        }, {
            "id": 156525,
            "type": "Child",
            "properties": {
                "patient": "6090",
                "batch": "70"
            }
        }, {
            "id": 166525,
            "type": "Child",
            "properties": {
                "patient": "6090",
                "batch": "70"
            }
        }, {
            "id": 176525,
            "type": "Child",
            "properties": {
                "patient": "6090",
                "batch": "70"
            }
        }, {
            "id": 136448,
            "type": "Child",
            "properties": {
                "patient": "6094",
                "batch": "70"
            }
        }, {
            "id": 136328,
            "type": "Child",
            "properties": {
                "patient": "6082",
                "batch": "70"
            }
        }, {
            "id": 136305,
            "type": "Child",
            "properties": {
                "patient": "6096",
                "batch": "70"
            }
        }, {
            "id": 136303,
            "type": "Child",
            "properties": {
                "patient": "6093",
                "batch": "70"
            }
        }, {
            "id": 136299,
            "type": "Child",
            "properties": {
                "patient": "6091",
                "batch": "70"
            }
        }, {
            "id": 136261,
            "type": "Child",
            "properties": {
                "patient": "6089",
                "batch": "70"
            }
        }, {
            "id": 136212,
            "type": "Child",
            "properties": {
                "patient": "6087",
                "batch": "70"
            }
        }, {
            "id": 136115,
            "type": "Child",
            "properties": {
                "patient": "6078",
                "batch": "70"
            }
        }, {
            "id": 136113,
            "type": "Child",
            "properties": {
                "patient": "6088",
                "batch": "70"
            }
        }, {
            "id": 135843,
            "type": "Child",
            "properties": {
                "patient": "6072",
                "batch": "70"
            }
        }, {
            "id": 555,
            "type": "Grandchild",
            "properties": {

            }
        }],
        "edges": [{
            "id": 0,
            "from": 136113,
            "to": 555,
            "properties": {

            }

        },{
            "id": 0,
            "from": 136525,
            "to": 555,
            "properties": {

            }
        },{
            "id": 0,
            "from": 146525,
            "to": 555,
            "properties": {

            }
        },{
            "id": 0,
            "from": 156525,
            "to": 555,
            "properties": {

            }
        },{
            "id": 0,
            "from": 166525,
            "to": 136448,
            "properties": {

            }
        },{
            "id": 0,
            "from": 176525,
            "to": 223,
            "properties": {

            }
        },{
            "id": 0,
            "from": 223,
            "to": 136525,
            "properties": {

            }
        }, {
            "id": 0,
            "from": 223,
            "to": 136448,
            "properties": {

            }
        }, {
            "id": 0,
            "from": 223,
            "to": 136328,
            "properties": {

            }
        }, {
            "id": 0,
            "from": 223,
            "to": 136305,
            "properties": {

            }
        }, {
            "id": 0,
            "from": 136525,
            "to": 136303,
            "properties": {

            }
        }, {
            "id": 0,
            "from": 223,
            "to": 136299,
            "properties": {

            }
        }, {
            "id": 0,
            "from": 223,
            "to": 136261,
            "properties": {

            }
        }, {
            "id": 0,
            "from": 223,
            "to": 136212,
            "properties": {

            }
        }, {
            "id": 0,
            "from": 223,
            "to": 136115,
            "properties": {

            }
        }, {
            "id": 0,
            "from": 223,
            "to": 136113,
            "properties": {

            }
        }, {
            "id": 0,
            "from": 223,
            "to": 135843,
            "properties": {

            }
        }]
    }

    var width = 0.975 * $(window).width(),
        height = 0.95 * $(window).height();

    var svg = d3.select("body").append("svg")
        .attr("width", width)
        .attr("height", height);

    var force = d3.layout.force()
        .size([width, height])
        //gravity(0.2)
        .linkDistance(height / 6)
        .charge(function(node) {
            if (node.type !== 'ORG') return -2000;
            return -30;
        });

    // build the arrow.
    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", function(d) {
            return d;
        })
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", 12)
        .attr("refY", 0)
        .attr("markerWidth", 9)
        .attr("markerHeight", 5)
        .attr("orient", "auto")
        .attr("class", "arrow")
        .append("svg:path")
        .attr("d", "M0,-5L10,0L0,5");

    var json = dataset;

    var edges = [];
    json.edges.forEach(function(e) {
        var sourceNode = json.nodes.filter(function(n) {
                return n.id === e.from;
            })[0],
            targetNode = json.nodes.filter(function(n) {
                return n.id === e.to;
            })[0];

        edges.push({
            source: sourceNode,
            target: targetNode,
            value: e.id
        });
    });

    var colors = {};
    colors[23] = "lightblue";
    colors[25] = "lightgreen";
    colors[48] = "lightyellow";
    colors[28] = "lightblue";
    colors[5] = "lightgreen";
    colors[3] = "lightyellow";
    colors[99] = "lightblue";
    colors[61] = "lightgreen";
    colors[12] = "lightyellow";

    for(var i=0; i<json.nodes.length; i++) {
        json.nodes[i].collapsing = 0;
        json.nodes[i].collapsed = false;
        json.nodes[i].radius = json.nodes[i].id % 100;
        if(colors[json.nodes[i].radius] != undefined)
            json.nodes[i].color = colors[json.nodes[i].radius];
        else
            json.nodes[i].color = "lightbrown";
    }

    var link = svg.selectAll(".link");
    var node = svg.selectAll(".node");

    force.on("tick", function() {
        // make sure the nodes do not overlap the arrows
        link.attr("d", function(d) {
            // Total difference in x and y from source to target
            diffX = d.target.x - d.source.x;
            diffY = d.target.y - d.source.y;

            // Length of path from center of source node to center of target node
            pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));

            // x and y distances from center to outside edge of target node
            offsetX = (diffX * d.target.radius) / pathLength;
            offsetY = (diffY * d.target.radius) / pathLength;

            return "M" + d.source.x + "," + d.source.y + "L" + (d.target.x - offsetX) + "," + (d.target.y - offsetY);
        });

        node.attr("transform", function(d) {
            return "translate(" + d.x + "," + d.y + ")";
        });
    });

    update();

    function update(){
        var nodes = json.nodes.filter(function(d) {
            return d.collapsing == 0;
        });

        var links = edges.filter(function(d) {
            return d.source.collapsing == 0 && d.target.collapsing == 0;
        });

        link = svg.selectAll(".link").data(links);
        link.exit().remove();

        link.enter().append("path")
            .attr("class", "link")
            .attr("marker-end", "url(#end)");

        node = svg.selectAll(".node").data(nodes);
        node.exit().remove();

        node.enter().append("g")
            .attr("class", function(d) {
                return "node " + d.type
            });

        node.append("circle")
            .attr("class", "circle")
            .attr("r", function(d) {
                //d.radius = 30;
                return d.radius
            })
            .attr("fill", function(d) {
                //d.radius = 30;
                return d.color;
            })
            .attr("stroke", function(d) {
                //d.radius = 30;
                return "darkgray";
            });
         // return a radius for path to use



        node.append("text")
            .attr("x", 0)
            .attr("dy", ".35em")
            .attr("text-anchor", "middle")
            .attr("class", "text")
            .text(function(d) {
                return d.type
            });

        // On node hover, examine the links to see if their
        // source or target properties match the hovered node.
        node.on('mouseover', function(d) {
            link.attr('class', function(l) {
                if (d === l.source || d === l.target)
                    return "link active";
                else
                    return "link inactive";
            });
        });

        // Set the stroke width back to normal when mouse leaves the node.
        node.on('mouseout', function() {
            link.attr('class', "link");
        })
            .on('click', click);

        /** this is NOT the problem **/
        function click(d) {
            if (!d3.event.defaultPrevented) {
                var inc = d.collapsed ? -1 : 1;
                recurse(d);

                function recurse(sourceNode){
                    //check if link is from this node, and if so, collapse
                    edges.forEach(function(l) {
                        if (l.source.id === sourceNode.id){

                            l.target.collapsing += inc;
                            recurse(l.target);


                        }
                    });
                }
                d.collapsed = !d.collapsed;
            }
            update();
        }
        force
            .nodes(nodes)
            .links(links)
            .start();
    }

</script>

</body>
</html>

[基于此的代码:http://jsfiddle.net/sheilak/9wvmL8q8/

1 个答案:

答案 0 :(得分:3)

问题在于:

node.enter().append("g")...

node.append("circle")...

node.append("text")...

虽然您要将论坛添加到enter选项中,但您要将圈子和文字添加到node选项中,这是转换元素

尝试:

var nodeEnter = node.enter().append("g")...

nodeEnter.append("circle")...

nodeEnter.append("text")...

在这种情况下,nodeEnter将等于链中最后创建的元素,在本例中为g元素。