d3.js - 防止 D3 在更新时添加重复项

标签 d3.js graph force-layout

下面是一个“有效”的 D3 动画。

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

在 chrome-inspection 中也可以看到 circle-elements 的重复。

只是简单的折叠和打开是我们在这里所追求的。非常感谢您的帮助 !谢谢。

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

<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/

最佳答案

问题出在这里:

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 元素。

关于d3.js - 防止 D3 在更新时添加重复项,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41699724/

相关文章:

javascript - 为什么 d3.json 返回 "null"?

path - 将空值传递给 SVG 路径(使用 d3.js)以抑制丢失的数据

algorithm - 英国旅游者

python - 在其他内部字典键的每个组合以及外部字典键的每个组合中搜索内部字典键的每个组合

python - Igraph/networkx 中的 k 最短路径实现(Yen 算法)

graph - D3 : Force Layout with fixed Y ranges - Reducing link overlap

javascript - 每次将元素添加到 SVG 时播放音频剪辑 - d3.js

javascript - d3 过渡重叠导致奇怪的行为

javascript - 在 D3 力导向图中高效计算凸包

javascript - 在 D3 强制布局中的节点上显示鼠标悬停时的 div 元素