根据过滤器下拉列表在d3中添加和删除强制图中的节点和链接 [英] Adding and removing nodes and links from force diagram in d3 based on filter dropdown

查看:105
本文介绍了根据过滤器下拉列表在d3中添加和删除强制图中的节点和链接的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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.

这是我的完整代码,我是javascript(& d3)的新手,而且我已经从bl.ocks.org无耻地窃取,所以请随时在'noob'回答。在此先感谢。

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.

另外,我把它放在一个jsfiddle: http://jsfiddle.net/J85Vu/

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>


推荐答案

在更新代码中,您应该重新选择现有节点在使用新数据进行数据绑定之前。在当前代码中,您使用的变量 circle path 实际上是指新添加的节点输入选择。

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.

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

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());

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

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.

这篇关于根据过滤器下拉列表在d3中添加和删除强制图中的节点和链接的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆