Adding and removing nodes and links from force diagram in d3 based on filter dropdown

◇◆丶佛笑我妖孽 提交于 2020-01-01 19:23:11

问题


I'm trying to make a force diagram, with a couple of drop down boxes which filter the data on display. The first one (which is where I'm almost at now) checks for the type, and only shows nodes & links which have a source or target matching the type.

What I have now, is the ability to select the filter, and the graph updates, it removes unnecessary nodes, and reformats the remaining ones to be correct. But it only works the first time. If I 're-filter' it starts to go haywire.

Here's my full code, I'm very new to javascript (&d3), and I've been unashamedly stealing from bl.ocks.org, so please feel free to answer in 'noob'. Thanks in advance.

Also, I've put this on a jsfiddle: http://jsfiddle.net/J85Vu/

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    <title>Enterprise Collaboration Map</title>
    <script type="text/javascript" src="d3.v3.js"></script>
    <script type="text/javascript" src="jquery-1.10.2.min.js"></script>

    <style type="text/css">

        path.link {
          fill: none;
          stroke: #666;
          stroke-width: 1.5px;
        }

        marker#a {
          fill: green;
        }
        path.link.a {
          stroke: green;
        }
        circle.a {
          fill: green;
          stroke: #333;
          stroke-width: 1.5px;
        }
        marker#b {
          fill: blue;
        }
        path.link.b {
          stroke: blue;
        }
        circle.b {
          fill: blue;
          stroke: #333;
          stroke-width: 1.5px;
        }
        marker#c {
          fill: orange;
        }
        path.link.c {
          stroke: orange;
        }
        circle.c {
          fill: orange;
          stroke: #333;
          stroke-width: 1.5px;
        }

        circle {
          fill: #ccc;
          stroke: #333;
          stroke-width: 1.5px;
        }


        text {
          font: 10px sans-serif;
          pointer-events: none;
        }

        text.shadow {
          stroke: #fff;
          stroke-width: 3px;
          stroke-opacity: .8;
        }

    </style>
  </head>
  <body>
    <select class="BU">
        <option value="a">A</option>
        <option value="b">B</option>
        <option value="c">C</option>
    </select>


    <script type="text/javascript">


        var links = [
        {source:"one",target:"two", type:"a", typeKBP:"a"},
        {source:"two",target:"three", type:"a", typeKBP:"a"},
        {source:"three",target:"four", type:"a", typeKBP:"a"},
        {source:"four",target:"five", type:"a", typeKBP:"b"},
        {source:"five",target:"six", type:"b", typeKBP:"b"},
        {source:"six",target:"seven", type:"b", typeKBP:"b"},
        {source:"seven",target:"eight", type:"b", typeKBP:"b"},
        {source:"eight",target:"nine", type:"b", typeKBP:"c"},
        {source:"nine",target:"ten", type:"c", typeKBP:"c"},
        {source:"ten",target:"one", type:"c", typeKBP:"a"},
        {source:"one",target:"three", type:"a", typeKBP:"a"},
        {source:"two",target:"four", type:"a", typeKBP:"a"},
        {source:"three",target:"five", type:"a", typeKBP:"b"},
        {source:"four",target:"six", type:"a", typeKBP:"b"},
        {source:"five",target:"seven", type:"b", typeKBP:"b"},
        {source:"six",target:"eight", type:"b", typeKBP:"b"},
        {source:"seven",target:"nine", type:"b", typeKBP:"c"},
        {source:"eight",target:"ten", type:"b", typeKBP:"c"},
        {source:"nine",target:"one", type:"c", typeKBP:"a"},
        {source:"ten",target:"two", type:"c", typeKBP:"a"},
        {source:"one",target:"four", type:"a", typeKBP:"a"},
        {source:"two",target:"five", type:"a", typeKBP:"b"},
        {source:"three",target:"six", type:"a", typeKBP:"b"},
        {source:"four",target:"seven", type:"a", typeKBP:"b"},
        {source:"five",target:"eight", type:"b", typeKBP:"b"},
        {source:"six",target:"nine", type:"b", typeKBP:"c"},
        {source:"seven",target:"ten", type:"b", typeKBP:"c"},
        {source:"eight",target:"one", type:"b", typeKBP:"a"},
        {source:"nine",target:"two", type:"c", typeKBP:"a"},
        {source:"ten",target:"three", type:"c", typeKBP:"a"}
        ];

        var inputlinks=[];
        var nodes = {};

        inputlinks.push(links);
        // Compute the distinct nodes from the links.
        links.forEach(function(link) {
          link.source = nodes[link.source] || (nodes[link.source] = {name: link.source, type:link.type});
          link.target = nodes[link.target] || (nodes[link.target] = {name: link.target, type:link.typeKBP});
        });

        var w = 1024,
            h = 800;
        //setup initial force layout
        var force = d3.layout.force()
            .gravity(0.4)
            .size([w, h])
            .nodes(d3.values(nodes))
            .links(links)
            .linkDistance(100)
            .charge(-1000)
            .on("tick", tick)
            .start();

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

        // Per-type markers, as they don't inherit styles.
        svg.append("svg:defs").selectAll("marker")
            .data(["a","b","c"])
          .enter().append("svg:marker")
            .attr("id", String)
            .attr("viewBox", "0 -5 10 10")
            .attr("refX", 15)
            .attr("refY", -1.5)
            .attr("markerWidth", 6)
            .attr("markerHeight", 6)
            .attr("orient", "auto")
          .append("svg:path")
            .attr("d", "M0,-5L10,0L0,5");

        var path = svg.append("svg:g").selectAll("path")
            .data(force.links())
          .enter().append("svg:path")
            .attr("class", function(d) { return "link " + d.type; })
            .attr("marker-end", function(d) { return "url(#" + d.type + ")"; });

        var circle = svg.append("svg:g").selectAll("circle")
            .data(force.nodes())
          .enter().append("svg:circle")
            .attr("r", 6)
            .attr("class", function(d) { return d.type; })
            .call(force.drag);

        var text = svg.append("svg:g").selectAll("g")
            .data(force.nodes())
          .enter().append("svg:g");

        // A copy of the text with a thick white stroke for legibility.
         text.append("svg:text")
            .attr("x", 8)
            .attr("y", ".31em")
            .attr("class", "shadow")
            .text(function(d) { return d.name; });

         text.append("svg:text")
            .attr("x", 8)
            .attr("y", ".31em")
            .attr("class","write")
            .text(function(d) { return d.name; });

        //jQuery update parts for drop downs.
        $(document).ready(function(){
            $('.BU').on('change',function(){
                curBU=$('.BU').val();
                //alert('The selected BU is ' + curBU);

                //Filter links and rebuild nodes based on this.
                minLinks={};
                minLinks=links.filter(function(d){
                    if ((d.type==curBU) || (d.typeKBP==curBU)) {
                        return d
                    }
                })
                //new nodes
                nodes2={};
                nodes2=force.nodes().filter(function(d){return d3.keys(minLinks.filter(function(e){return e.source.name==d.name || e.target.name==d.name;})).length>0});

                // minLinks.forEach(function(d) {
                    // d.source = nodes2[d.source] || (nodes2[d.source] = {name: d.source, type:d.type});
                    // d.target = nodes2[d.target] || (nodes2[d.target] = {name: d.target, type:d.typeKBP});
                // });

                force
                    .nodes(nodes2)
                    .links(minLinks)
                    .start();

                //circle.remove();
                newCirc=circle.data(force.nodes());
                newCirc.enter().append("svg:circle")
                    .attr("r", 6)
                    .attr("class", function(d) { return d.type; })
                    .call(force.drag);
                newCirc.exit().remove();

                newPath=path.data(force.links());
                newPath
                    .enter().append("svg:path")
                    .attr("class", function(d) { return "link " + d.type; })
                    .attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
                newPath.exit().remove();

                newText=text.data(force.nodes());
                newText.exit().remove();
                newText.select(".shadow").text(function(d){return d.name;});
                newText.select(".write").text(function(d){return d.name;});

            });

        });

        // Use elliptical arc path segments to doubly-encode directionality.
        function tick() {
          path.attr("d", function(d) {
            var dx = d.target.x - d.source.x,
                dy = d.target.y - d.source.y,
                dr = Math.sqrt((dx * dx)/2 + (dy * dy));
            return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
          });

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

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


    </script>
  </body>
</html>

回答1:


In your update code you should be reselecting the existing nodes before doing a data bind with the new data. In the current code you're using the variables circle and path which are actually referring to the newly appended nodes of the enter selection.

Reselecting before every data join is the best way to ensure you are joining with the very latest actual state in the DOM:

svg.selectAll("path")
    .data(force.links());

svg.selectAll("circle")
    .data(force.nodes());

It is probably a good idea for you to class your circles and paths in some way that will let you select for them more directly so you don't accidentally pick up other paths or circles in the svg.

Also, be careful about operating on the enter selection versus the update selection. This is especially true considering that you are not defining a key for your data join, which means that index will be used by default resulting in existing nodes being updated with new data. In your code, for example, you're only setting the class attribute on the newly appended nodes where you probably want to update it on all nodes.

This tutorial is a good starting point for understanding this better: Thinking with Joins.



来源:https://stackoverflow.com/questions/18334704/adding-and-removing-nodes-and-links-from-force-diagram-in-d3-based-on-filter-dro

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