javascript - 根据过滤器下拉列表在 d3 中的力图中添加和删除节点和链接

标签 javascript d3.js force-layout

我正在尝试制作一个力图,其中有几个下拉框可以过滤显示的数据。 第一个(我现在几乎就在这个位置)检查类型,并且仅显示具有与该类型匹配的源或目标的节点和链接。

我现在拥有的是选择过滤器的能力,并且图形更新,它删除不必要的节点,并将剩余的节点重新格式化为正确的。但它只在第一次有效。如果我“重新过滤”,它就会开始变得困惑。

这是我的完整代码,我对 javascript (&d3) 非常陌生,而且我一直厚颜无耻地从 bl.ocks.org 窃取,所以请随意用“菜鸟”回答。提前致谢。

此外,我已将其放在 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>

最佳答案

在更新代码中,您应该在与新数据进行数据绑定(bind)之前重新选择现有节点。在当前代码中,您使用变量 circlepath这实际上指的是 enter 中新添加的节点。选择。

在每次数据加入之前重新选择是确保加入 DOM 中最新实际状态的最佳方法:

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

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

以某种方式对圆和路径进行分类可能是个好主意,这样您就可以更直接地选择它们,这样您就不会意外地在 svg 中选择其他路径或圆。

此外,在输入选择和更新选择上进行操作时要小心。考虑到您没有为数据连接定义键,这尤其如此,这意味着默认情况下将使用索引,导致现有节点被新数据更新。例如,在您的代码中,您只需设置 class您可能希望在所有节点上更新它的新附加节点上的属性。

本教程是更好地理解这一点的一个很好的起点:Thinking with Joins .

关于javascript - 根据过滤器下拉列表在 d3 中的力图中添加和删除节点和链接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18334704/

相关文章:

javascript - 如何根据路由参数从众多用户中只导入一个用户?

javascript - 尝试在ElasticSearch中进行查询和汇总,但无法正常工作-Elasticsearch.js客户端

javascript - 瓷砖重叠有什么好处?

javascript - jqgrid 在更改另一个单元格时更改可编辑单元格的值(相关列)

javascript - D3.js折线图: axis with fixed position

javascript - 如何使用d3.js从json文件中获取数据来绘制动态图

javascript - 强制布局中的 D3 CSS 自定义颜色

d3.js - d3.js 中力图链接上的节点

d3.js - dartlang interop js 和 d3 集成在 dart 中不起作用

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