以编程方式打开 d3.js v4 可折叠树节点?

Programmatically opening d3.js v4 collapsible tree nodes?

我修改了 d3noob 的 d3.js 版本 4

https://bl.ocks.org/d3noob/43a860bc0024792f8803bba8ca0d5ecd

Mike Bostock 的可折叠树的衍生物

https://bl.ocks.org/mbostock/4339083

添加基于鼠标的平移和缩放,并从外部文件加载 JSON 数据。

https://jsfiddle.net/vstuart/acx5zfgn/31/

虽然未在 fiddle 中显示,但在我的 HTML 前端(基于 AjaxSolr 框架)中,我有对应于 Apache Solr facet 的超链接,单击这些超链接会更新网页(通过更新的Solr 请求)

所有这些都运行良好。

但是,我还希望能够在单击那些方面超链接时展开相应的 d3.js 节点。

如有任何建议,我们将不胜感激。


附录。 对于那些对答案感兴趣的人,我更新了我的 JSFiddle、HTML 代码(下方)和 GitHub 要点——一分为二地点:在该代码中搜索“per answer at”以查看添加内容。

为了验证代码添加,我将 Gist 中更新的 HTML 内容转储到一个新的 index.html 文件,该文件在 Firefox v88.0 运行 上按预期运行本地主机(不需要网络服务器)。


单页代码

<!DOCTYPE html>
<html lang="en-US" xmlns:xlink="http://www.w3.org/1999/xlink">

<head>
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type">

  <style>
    .node {
      cursor: pointer;
    }

    .node circle {
      fill: #fff;
      stroke: steelblue;
      stroke-width: 3px;
    }

    .node text {
      font: 12px sans-serif;
    }

    .link {
      fill: none;
      stroke: #ccc;
      stroke-width: 2px;
    }

    #includedContent {
      position: static !important;
      display: inline-block;
    }

    #d3_object {
      width: 75%;
      margin: 0.5rem 0.5rem 1rem 0.25rem;
    }
  </style>

  <script type="text/javascript" src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>

  <script src="https://d3js.org/d3.v4.min.js"></script>
</head>

<body>

  <div id="d3_object">
    <object>
      <div id="includedContent"></div>
    </object>
  </div>

  <!-- **** ADD, e.g., A HYPERLINK HERE THAT WILL OPEN
            the "data" NODE IN THE d3.js TREE ***** -->

  <script>
    // Set the dimensions and margins of the diagram
    var margin = {top: 20, right: 90, bottom: 30, left: 90},
        width = 960 - margin.left - margin.right,
        height = 500 - margin.top - margin.bottom;

    // ----------------------------------------
    /* Pan, zoom! */
    // https://www.d3-graph-gallery.com/graph/interactivity_zoom.html
    var svg = d3.select("body").append("svg")
        .attr("width", width + margin.right + margin.left)
        .attr("height", height + margin.top + margin.bottom)
        .call(d3.zoom().on("zoom", function () {
          svg.attr("transform", d3.event.transform)
        }))
      .append("g")
        .attr("transform", "translate("
              + margin.left + "," + margin.top + ")");
    // ----------------------------------------

    var i = 0,
        duration = 250,
        root;

    // declares a tree layout and assigns the size
    var treemap = d3.tree().size([height, width]);

    // load the external data
    d3.json("https://gist.githubusercontent.com/mbostock/4339083/raw/9585d220bef18a0925922f4d384265ef767566f5/flare.json", function(error, treeData) {
      if (error) throw error;

      // Assigns parent, children, height, depth
      root = d3.hierarchy(treeData, function(d) { return d.children; });
      root.x0 = height / 2;
      root.y0 = 0;

      // Collapse after the second level
      root.children.forEach(collapse);

      update(root);

    });

    // Collapse the node and all it's children
    function collapse(d) {
      if(d.children) {
        d._children = d.children
        d._children.forEach(collapse)
        d.children = null
      }
    }

    function update(source) {

      // Assigns the x and y position for the nodes
      var treeData = treemap(root);

      // Compute the new tree layout.
      var nodes = treeData.descendants(),
          links = treeData.descendants().slice(1);

      // Normalize for fixed-depth.
      nodes.forEach(function(d){ d.y = d.depth * 180});

      // ********** Nodes section *******************

      // Update the nodes...
      var node = svg.selectAll('g.node')
          .data(nodes, function(d) {return d.id || (d.id = ++i); });

      // Enter any new modes at the parent's previous position.
      var nodeEnter = node.enter().append('g')
          .attr('class', 'node')
          // --------------------------------------------
          // per answer at
          // 
          .attr('node-name', d => d.data.name)
          // --------------------------------------------
          .attr("transform", function(d) {
            return "translate(" + source.y0 + "," + source.x0 + ")";
        })
        .on('click', click);

      // Add Circle for the nodes
      nodeEnter.append('circle')
          .attr('class', 'node')
          .attr('r', 1e-6)
          .style("fill", function(d) {
              return d._children ? "lightsteelblue" : "#fff";
          });

      // Add labels for the nodes
      nodeEnter.append('text')
          .attr("dy", ".35em")
          .attr("x", function(d) {
              return d.children || d._children ? -13 : 13;
          })
          .attr("text-anchor", function(d) {
              return d.children || d._children ? "end" : "start";
          })
          .text(function(d) { return d.data.name; });

      // UPDATE
      var nodeUpdate = nodeEnter.merge(node);

      // Transition to the proper position for the node
      nodeUpdate.transition()
        .duration(duration)
        .attr("transform", function(d) { 
            return "translate(" + d.y + "," + d.x + ")";
        });

      // Update the node attributes and style
      nodeUpdate.select('circle.node')
        .attr('r', 10)
        .style("fill", function(d) {
            return d._children ? "lightsteelblue" : "#fff";
        })
        .attr('cursor', 'pointer');


      // Remove any exiting nodes
      var nodeExit = node.exit().transition()
          .duration(duration)
          .attr("transform", function(d) {
              return "translate(" + source.y + "," + source.x + ")";
          })
          .remove();

      // On exit reduce the node circles size to 0
      nodeExit.select('circle')
        .attr('r', 1e-6);

      // On exit reduce the opacity of text labels
      nodeExit.select('text')
        .style('fill-opacity', 1e-6);

      // ********** links section *******************

      // Update the links...
      var link = svg.selectAll('path.link')
          .data(links, function(d) { return d.id; });

      // Enter any new links at the parent's previous position.
      var linkEnter = link.enter().insert('path', "g")
          .attr("class", "link")
          .attr('d', function(d){
            var o = {x: source.x0, y: source.y0}
            return diagonal(o, o)
          });

      // UPDATE
      var linkUpdate = linkEnter.merge(link);

      // Transition back to the parent element position
      linkUpdate.transition()
          .duration(duration)
          .attr('d', function(d){ return diagonal(d, d.parent) });

      // Remove any exiting links
      var linkExit = link.exit().transition()
          .duration(duration)
          .attr('d', function(d) {
            var o = {x: source.x, y: source.y}
            return diagonal(o, o)
          })
          .remove();

      // Store the old positions for transition.
      nodes.forEach(function(d){
        d.x0 = d.x;
        d.y0 = d.y;
      });

      // Creates a curved (diagonal) path from parent to the child nodes
      function diagonal(s, d) {

        path = `M ${s.y} ${s.x}
                C ${(s.y + d.y) / 2} ${s.x},
                  ${(s.y + d.y) / 2} ${d.x},
                  ${d.y} ${d.x}`

        return path
      }

      // ----------------------------------------
      // Toggle children on click.

      function click(d) {
        if (d.children) {
          d._children = d.children;
          d.children = null;
        } else if (d._children) {
          d.children = d._children;
          d._children = null;
        } else {
          // This was a leaf node, so redirect.
          console.log('d:', d)
          console.log('d.data:', d.data)
          console.log('d.name:', d.name)
          console.log('d.data.name:', d.data.name)
          console.log('urlMap[d.data.name]:', urlMap[d.data.name])
          window.location = d.data.url;
          // window.open("https://www.example.com", "_self");
        }
        update(d);
      }
      // ----------------------------------------
    }
    // per answer at
    // 
    setTimeout(() => {
      const node = d3.select('.node[node-name="analytics"]').node();
      console.log('NODE: ', node);
      node.dispatchEvent(new Event('click'));
    }, 2500);
  </script>
</body>
</html>

可以通过在特定节点上触发点击事件来完成。 Here is an example 在初始渲染后几秒展开节点“Analytics”。

首先,分配一个唯一的属性(或class名称id) 到每个节点在 DOM:

中找到它
node.enter().append('g')
  .classed('node', true)
  .attr('node-name', d => d.data.name)
  ...
  .on('click', click);

然后,在特定节点上触发点击事件:

d3.select('.node[node-name="analytics"]').node().dispatchEvent(new Event('click'));