首页 > 解决方案 > 动态过滤 D3 有向图的节点

问题描述

我正在使用D3对我来说新的库,D3 根据图形详细信息可视化有向图。由于我拥有大量数据,因此很难在一页上描绘所有内容。

我正在尝试通过添加下拉选项来过滤具有其向内和向外依赖关系的节点,但我被卡住了,不知道如何继续。

以下是我正在努力实现的目标。例如,Beta 节点依赖于 gamma,但同样 Alpha 和 Delta 依赖于 Beta。下图更多地关注 Beta 节点及其内部和外部依赖关系。

在此处输入图像描述

<!DOCTYPE html>

<html>

<head>
  <style type="text/css">
    .node {}
    
    .link {
      stroke: #999;
      stroke-opacity: .6;
      stroke-width: 1px;
    }
    
    svg {
      border: 1px solid black
    }
  </style>

</head>

<body>
  <script src="https://d3js.org/d3.v4.min.js" type="text/javascript"></script>
  <script src="https://d3js.org/d3-selection-multi.v1.js"></script>
  
  <select name="cars" id="cars">
    <option value="volvo">All</option>
    <option value="volvo">Alpha</option>
    <option value="saab">Beta</option>
    <option value="volvo">Charlie</option>
    <option value="saab">Delta</option>
    <option value="audi">Echo</option>
    <option value="opel">Foxtrot</option>
    <option value="audi">Gamma</option>
    <option value="opel">Hotel</option>    
  </select>
  <br><br>
  <svg width="400" height="200"></svg>
  <script>
    var colors = d3.scaleOrdinal(d3.schemeCategory10);

    var svg = d3.select("svg"),
      width = +svg.attr("width"),
      height = +svg.attr("height"),
      node,
      link;

    svg.append('defs').append('marker')
      .attrs({
        'id': 'arrowhead',
        'viewBox': '-0 -5 10 10',
        'refX': 13,
        'refY': 0,
        'orient': 'auto',
        'markerWidth': 13,
        'markerHeight': 13,
        'xoverflow': 'visible'
      })
      .append('svg:path')
      .attr('d', 'M 0,-5 L 10 ,0 L 0,5')
      .attr('fill', '#999')
      .style('stroke', 'none');

    var simulation = d3.forceSimulation()
      .force("link", d3.forceLink().id(function(d) {
        return d.id;
      }).distance(100).strength(1))
      .force("charge", d3.forceManyBody())
      .force("center", d3.forceCenter(width / 2, height / 2));

    var graph = {
      "nodes": [{
          "name": "Alpha",
          "label": "Person",
          "id": 1
        },
        {
          "name": "Beta",
          "label": "Person",
          "id": 2
        },
        {
          "name": "Gamma",
          "label": "Database",
          "id": 3
        },
        {
          "name": "Delta",
          "label": "Database",
          "id": 4
        },
        {
          "name": "Echo",
          "label": "Person",
          "id": 5
        },
        {
          "name": "Foxtrot",
          "label": "Person",
          "id": 6
        },
        {
          "name": "Golf",
          "label": "Database",
          "id": 7
        },
        {
          "name": "Hotel",
          "label": "Database",
          "id": 8
        }
      ],
      "links": [{
          "source": 1,
          "target": 2,
          "type": "KNOWS",
          "since": 2010
        },
        {
          "source": 1,
          "target": 3,
          "type": "FOUNDED"
        },
        {
          "source": 2,
          "target": 3,
          "type": "WORKS_ON"
        },
        {
          "source": 3,
          "target": 4,
          "type": "IS_A"
        },
        {
          "source": 4,
          "target": 2,
          "type": "KNOWS",
          "since": 2010
        },
        {
          "source": 5,
          "target": 6,
          "type": "FOUNDED"
        },
        {
          "source": 3,
          "target": 4,
          "type": "WORKS_ON"
        },
        {
          "source": 1,
          "target": 4,
          "type": "IS_A"
        }
      ]
    };

    update(graph.links, graph.nodes);

    function update(links, nodes) {
      link = svg.selectAll(".link")
        .data(links)
        .enter()
        .append("line")
        .attr("class", "link")
        .attr('marker-end', 'url(#arrowhead)')

      link.append("title")
        .text(function(d) {
          return d.type;
        });

      edgepaths = svg.selectAll(".edgepath")
        .data(links)
        .enter()
        .append('path')
        .attrs({
          'class': 'edgepath',
          'fill-opacity': 0,
          'stroke-opacity': 0,
          'id': function(d, i) {
            return 'edgepath' + i
          }
        })
        .style("pointer-events", "none");

      edgelabels = svg.selectAll(".edgelabel")
        .data(links)
        .enter()
        .append('text')
        .style("pointer-events", "none")
        .attrs({
          'class': 'edgelabel',
          'id': function(d, i) {
            return 'edgelabel' + i
          },
          'font-size': 10,
          'fill': '#aaa'
        });

      edgelabels.append('textPath')
        .attr('xlink:href', function(d, i) {
          return '#edgepath' + i
        })
        .style("text-anchor", "middle")
        .style("pointer-events", "none")
        .attr("startOffset", "50%")
        .text(function(d) {
          return d.type
        });

      node = svg.selectAll(".node")
        .data(nodes)
        .enter()
        .append("g")
        .attr("class", "node")
        .call(d3.drag()
          .on("start", dragstarted)
          .on("drag", dragged)
          //.on("end", dragended)
        );

      node.append("circle")
        .attr("r", 5)
        .style("fill", function(d, i) {
          return colors(i);
        })

      node.append("title")
        .text(function(d) {
          return d.id;
        });

      node.append("text")
        .attr("dy", -3)
        .text(function(d) {
          return d.name;
        });

      simulation.nodes(nodes)
        .on("tick", ticked);

      simulation.force("link")
        .links(links);
    }

    function ticked() {

      link
        .attr("x1", function(d) {
          checkBounds(d.source);
          return d.source.x;
        })
        .attr("y1", function(d) {
          return d.source.y;
        })
        .attr("x2", function(d) {
          checkBounds(d.target);
          return d.target.x;
        })
        .attr("y2", function(d) {
          return d.target.y;
        });

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

      edgepaths.attr('d', function(d) {
        return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
      });

      edgelabels.attr('transform', function(d) {
        if (d.target.x < d.source.x) {
          var bbox = this.getBBox();

          rx = bbox.x + bbox.width / 2;
          ry = bbox.y + bbox.height / 2;
          return 'rotate(180 ' + rx + ' ' + ry + ')';
        } else {
          return 'rotate(0)';
        }
      });
    }

    function dragstarted(d) {
      if (!d3.event.active) simulation.alphaTarget(0.3).restart()
      d.fx = d.x;
      d.fy = d.y;
    }

    function dragged(d) {
      d.fx = d3.event.x;
      d.fy = d3.event.y;
    }

    function checkBounds(d) {
      if (d.x < 0) d.x = 0;
      if (d.x > (width - 20)) d.x = width - 20;
      if (d.y < 0) d.y = 0;
      if (d.y > (height - 20)) d.y = height - 20;
    }
  </script>
</body>

</html>

标签: javascriptd3.jsvisualization

解决方案


我对您的代码进行了细微更改,因为选择项的值似乎不正确;我让它适用于 Beta,你需要修复其余的值才能让一切正常工作。

    var graph = {
        "nodes": [{
            "name": "Alpha",
            "label": "Person",
            "id": 1
          },
          {
            "name": "Beta",
            "label": "Person",
            "id": 2
          },
          {
            "name": "Gamma",
            "label": "Database",
            "id": 3
          },
          {
            "name": "Delta",
            "label": "Database",
            "id": 4
          },
          {
            "name": "Echo",
            "label": "Person",
            "id": 5
          },
          {
            "name": "Foxtrot",
            "label": "Person",
            "id": 6
          },
          {
            "name": "Golf",
            "label": "Database",
            "id": 7
          },
          {
            "name": "Hotel",
            "label": "Database",
            "id": 8
          }
        ],
        "links": [{
            "source": 1,
            "target": 2,
            "type": "KNOWS",
            "since": 2010
          },
          {
            "source": 1,
            "target": 3,
            "type": "FOUNDED"
          },
          {
            "source": 2,
            "target": 3,
            "type": "WORKS_ON"
          },
          {
            "source": 3,
            "target": 4,
            "type": "IS_A"
          },
          {
            "source": 4,
            "target": 2,
            "type": "KNOWS",
            "since": 2010
          },
          {
            "source": 5,
            "target": 6,
            "type": "FOUNDED"
          },
          {
            "source": 3,
            "target": 4,
            "type": "WORKS_ON"
          },
          {
            "source": 1,
            "target": 4,
            "type": "IS_A"
          }
        ]
      };

      // here is what you need to filter on
      document.getElementById("cars").addEventListener("change", event => {
        var val = parseInt(event.target.value, 10);
        var links = graph.links.filter(link => link.source.id === val || link.target.id === val);
        document.getElementsByTagName("svg")[0].innerHTML = "";

        if (links.length === 0) {
          update([], graph.nodes.filter(n => n.id === val));
        } else {
          var selectedNodes = Object.keys(links.reduce((acc, curr) => {
            acc[curr.source.id] = true;
            acc[curr.target.id] = true;
            return acc;
          }, {}));
          var nodes = graph.nodes.filter(n => selectedNodes.indexOf("" + n.id) !== -1);
          update(links, nodes);
        }
      });
 
      var colors = d3.scaleOrdinal(d3.schemeCategory10);
      var svg = d3.select("svg"),
        width = +svg.attr("width"),
        height = +svg.attr("height"),
        node,
        link;
      var simulation = null;


      update(graph.links, graph.nodes);

      function update(links, nodes) {

        svg.append('defs').append('marker')
          .attrs({
            'id': 'arrowhead',
            'viewBox': '-0 -5 10 10',
            'refX': 13,
            'refY': 0,
            'orient': 'auto',
            'markerWidth': 13,
            'markerHeight': 13,
            'xoverflow': 'visible'
          })
          .append('svg:path')
          .attr('d', 'M 0,-5 L 10 ,0 L 0,5')
          .attr('fill', '#999')
          .style('stroke', 'none');

        simulation = d3.forceSimulation()
          .force("link", d3.forceLink().id(function(d) {
            return d.id;
          }).distance(100).strength(1))
          .force("charge", d3.forceManyBody())
          .force("center", d3.forceCenter(width / 2, height / 2));

        link = svg.selectAll(".link")
          .data(links)
          .enter()
          .append("line")
          .attr("class", "link")
          .attr('marker-end', 'url(#arrowhead)')

        link.append("title")
          .text(function(d) {
            return d.type;
          });

        edgepaths = svg.selectAll(".edgepath")
          .data(links)
          .enter()
          .append('path')
          .attrs({
            'class': 'edgepath',
            'fill-opacity': 0,
            'stroke-opacity': 0,
            'id': function(d, i) {
              return 'edgepath' + i
            }
          })
          .style("pointer-events", "none");

        edgelabels = svg.selectAll(".edgelabel")
          .data(links)
          .enter()
          .append('text')
          .style("pointer-events", "none")
          .attrs({
            'class': 'edgelabel',
            'id': function(d, i) {
              return 'edgelabel' + i
            },
            'font-size': 10,
            'fill': '#aaa'
          });

        edgelabels.append('textPath')
          .attr('xlink:href', function(d, i) {
            return '#edgepath' + i
          })
          .style("text-anchor", "middle")
          .style("pointer-events", "none")
          .attr("startOffset", "50%")
          .text(function(d) {
            return d.type
          });

        node = svg.selectAll(".node")
          .data(nodes)
          .enter()
          .append("g")
          .attr("class", "node")
          .call(d3.drag()
            .on("start", dragstarted)
            .on("drag", dragged)
            //.on("end", dragended)
          );

        node.append("circle")
          .attr("r", 5)
          .style("fill", function(d, i) {
            return colors(i);
          })

        node.append("title")
          .text(function(d) {
            return d.id;
          });

        node.append("text")
          .attr("dy", -3)
          .text(function(d) {
            return d.name;
          });

        simulation.nodes(nodes)
          .on("tick", ticked);

        simulation.force("link")
          .links(links);
      }

      function ticked() {

        link
          .attr("x1", function(d) {
            checkBounds(d.source);
            return d.source.x;
          })
          .attr("y1", function(d) {
            return d.source.y;
          })
          .attr("x2", function(d) {
            checkBounds(d.target);
            return d.target.x;
          })
          .attr("y2", function(d) {
            return d.target.y;
          });

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

        edgepaths.attr('d', function(d) {
          return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
        });

        edgelabels.attr('transform', function(d) {
          if (d.target.x < d.source.x) {
            var bbox = this.getBBox();

            rx = bbox.x + bbox.width / 2;
            ry = bbox.y + bbox.height / 2;
            return 'rotate(180 ' + rx + ' ' + ry + ')';
          } else {
            return 'rotate(0)';
          }
        });
      }

      function dragstarted(d) {
        if (!d3.event.active) simulation.alphaTarget(0.3).restart()
        d.fx = d.x;
        d.fy = d.y;
      }

      function dragged(d) {
        d.fx = d3.event.x;
        d.fy = d3.event.y;
      }

      function checkBounds(d) {
        if (d.x < 0) d.x = 0;
        if (d.x > (width - 20)) d.x = width - 20;
        if (d.y < 0) d.y = 0;
        if (d.y > (height - 20)) d.y = height - 20;
      }
.node {}

.link {
  stroke: #999;
  stroke-opacity: .6;
  stroke-width: 1px;
}

svg {
  border: 1px solid black
}
<!DOCTYPE html>

<html>
  <head>
    <script src="https://d3js.org/d3.v4.min.js" type="text/javascript"></script>
    <script src="https://d3js.org/d3-selection-multi.v1.js"></script>
  </head>

  <body>
    <select name="cars" id="cars">
      <option value="volvo">All</option>
      <option value="volvo">Alpha</option>
      <option value="2">Beta</option>
      <option value="volvo">Charlie</option>
      <option value="saab">Delta</option>
      <option value="audi">Echo</option>
      <option value="opel">Foxtrot</option>
      <option value="audi">Gamma</option>
      <option value="opel">Hotel</option>
    </select>
    <br><br>
    <svg width="400" height="200"></svg>
  </body>
</html>


推荐阅读