【问题标题】:Filter nodes for D3 directed graph dynamically动态过滤 D3 有向图的节点
【发布时间】:2021-04-03 07:31:41
【问题描述】:

我正在使用 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>

【问题讨论】:

    标签: javascript d3.js visualization


    【解决方案1】:

    我对您的代码做了一些小改动,因为选择项的值似乎不正确;我让它适用于 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>

    【讨论】:

    • 是的,它有效...更正了我的 HTML 并添加了您建议的更改。谢谢
    • 它不适用于没有任何链接的单个节点:(,假设如果 beta 没有任何链接.. 那么只有单个节点应该查看,而不是图形保持不变. 在调试时发现node = svg.selectAll(".node") 无法追加g 对象.append("g")
    • 更新了我的答案以处理无边缘。请交叉检查
    • 真的很高兴,我错过了 document.getElementsByTagName("svg")[0].innerHTML = ""; 在 if 块中添加更改,为什么它之前对我不起作用。谢谢
    猜你喜欢
    • 2013-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-07
    • 2019-04-23
    • 2014-12-05
    • 2013-09-01
    • 1970-01-01
    相关资源
    最近更新 更多