在d3.js v6中以编程方式打开嵌套,折叠(隐藏)的节点 [英] Programmatically open nested, collapsed (hidden) nodes in d3.js v6

查看:72
本文介绍了在d3.js v6中以编程方式打开嵌套,折叠(隐藏)的节点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

后续问题/问题

以编程方式打开嵌套,折叠(隐藏)的节点在d3.js v4中

已针对d3.js v6更新.问题是在d3可折叠菜单可视化中加载外部JSON数据,以及对嵌套(折叠,隐藏)节点的编程访问.

updated for d3.js v6. The issue is the loading of external JSON data in the d3 collapsible menu visualization, and the programmatic access of nested (collapsed, hidden) nodes.

看来,作为被加载对象的"treeData"没有被传递.

It appears that "treeData", which is the loaded Object, is not being delivered.

Uncaught ReferenceError: treeData is not defined

JSFiddle:: https://jsfiddle.net/vstuart/kant09hm/6/

JSFiddle(已更新答案): https://jsfiddle.net/vstuart/kant09hm/9/

ontology_for_d3_test.json

{ "name": "Root",
  "children": [
    { "name": "Culture",
      "children": [
        { "name": "Entertainment" },
        { "name": "LGBT" }
      ]
    },

    { "name": "Nature",
      "id": "nature",
      "children": [
        { "name": "Earth",
          "id": "earth",
          "children": [
            { "name": "Environment" },
            { "name": "Geography" },
            { "name": "Geology" },
            { "name": "Geopolitical" },
            { "name": "Geopolitical - Countries" },
            { "name": "Geopolitical - Countries - Canada" },
            { "name": "Geopolitical - Countries - United States" },
            { "name": "Nature" },
            { "name": "Regions" }
          ]
        },
        { "name": "Cosmos" },
        { "name": "Outer space" }
      ]
    },

    { "name": "Humanities",
      "children": [
          { "name": "History" },
          { "name": "Philosophy" },
          { "name": "Philosophy - Theology" }
      ]
    },

    { "name": "Miscellaneous",
      "children": [
          { "name": "Wikipedia",
            "url": "https://wikipedia.com" },
          { "name": "Example.com",
            "url": "https://example.com" }
      ]
    },
      
    { "name": "Science",
      "children": [
          { "name": "Biology" },
          { "name": "Health" },
          { "name": "Health - Medicine" },
          { "name": "Sociology" }
      ]
    },

    { "name": "Technology",
      "children": [
            { "name": "Computers" },
            { "name": "Computers - Hardware" },
            { "name": "Computers - Software" },
            { "name": "Computing" },
            { "name": "Computing - Programming" },
            { "name": "Internet" },
            { "name": "Space" },
          { "name": "Transportation" }
      ]
    },

  { "name": "Society",
      "children": [
            { "name": "Business" },
            { "name": "Economics" },
            { "name": "Economics - Business" },
            { "name": "Economics - Capitalism" },
            { "name": "Economics - Commerce" },
            { "name": "Economics - Finance" },
            { "name": "Politics" },
          { "name": "Public services" }
      ]
    }
  ]
}


index.html [JSFiddle的独立工作副本;已更新为答案]


index.html [standalone working copy of JSFiddle; edit: updated with answer]

<!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> -->
  <!-- <script src="https://d3js.org/d3.v5.min.js"></script> -->
  <script src="https://d3js.org/d3.v6.min.js"></script>
</head>

<body>

  <div id="d3_object">
    <object>
      <p>apple</p>
      <div id="includedContent"></div>
      <p>banana</p>
    </object>
  </div>

  <script type="text/javascript">
    // 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")

    var svg = d3.select("#includedContent").append("svg")
        .attr("width", width + margin.right + margin.left)
        .attr("height", height + margin.top + margin.bottom)
        // d3.js v4, v5:
        // .call(d3.zoom().on("zoom", function () {
        //   svg.attr("transform", d3.event.transform)
        // d3.js v6:
        .call(d3.zoom().on("zoom", function (event) {
          svg.attr("transform", 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) {

    // ----------------------------------------
    // d3.js v4 [ https://d3js.org/d3.v4.min.js ]
    /*
      d3.json("https://gist.githubusercontent.com/victoriastuart/abbcf355bf1590be02f6dec297be2706/raw/2418e5f6b7626b3c5842665a51b7d0d27f74e909/ontology_for_d3_test.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);
      });
    */
    // ----------------------------------------

    // ----------------------------------------
    // d3.js v5   https://d3js.org/d3.v5.min.js
    //            https://gist.github.com/d3noob/1a96af738c89b88723eb63456beb6510 
    // d3.js v6   https://d3js.org/d3.v5.min.js
    //            https://gist.github.com/d3noob/9de0768412ac2ce5dbec430bb1370efe

    // https://stackoverflow.com/questions/49768165/code-within-d3-json-callback-is-not-executed
    // https://www.tutorialsteacher.com/d3js/loading-data-from-file-in-d3js
    // https://stackoverflow.com/questions/47664292/d3-json-method-doesnt-return-my-data-array

    // ----------------------------------------
    // LOAD EXTERNAL JSON DATA FILE (via PROMISE):

    //   https://stackoverflow.com/questions/49768165/code-within-d3-json-callback-is-not-executed
    //   https://stackoverflow.com/questions/49534470/d3-js-v5-promise-all-replaced-d3-queue
    //   https://www.roelpeters.be/explaining-promises-in-d3-js-the-what-and-the-why/
    //
    //   https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call

    // This callback function should return the "treeData" Object:
    d3.json("https://gist.githubusercontent.com/victoriastuart/abbcf355bf1590be02f6dec297be2706/raw/2418e5f6b7626b3c5842665a51b7d0d27f74e909/ontology_for_d3_test.json")
      .then(function(treeData) { 
        console.log('[d3.js] treeData:', treeData, '| type:', typeof(treeData), '| length:', treeData['children'].length)
        for (let i = 0; i < treeData['children'].length; i++) {
          console.log('node:', treeData['children'][i].name);
          if ( treeData['children'][i].id !== undefined ) {
            console.log('  id:', treeData['children'][i].id);
          }
        }

        // ASSIGN 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);

        // ----------------------------------------
        // Per answers at
        //   https://stackoverflow.com/questions/67527258/programmatically-open-nested-collapsed-hidden-node-in-d3-js-v4/67530786?noredirect=1#comment119390942_67530786
        //   https://jsfiddle.net/mrovinsky/ujwsd7qz/
        //   https://stackoverflow.com/questions/67549992/accessing-promised-data-in-d3-js-v6-programmatically-opening-nested-collapsed-n

        const findNodeAncestors = (root, name) => {
          if (root.name === name) {
            return [name];
          }
          if (root.children)
            for (let i = 0; i < root.children.length; i++) {
              const chain = findNodeAncestors(root.children[i], name);
              if (chain) {
                chain.push(root.name);
                return chain;
              }
            }
          return null;
        }; 

        const chain = findNodeAncestors(treeData, 'Earth');

        if (chain) {
          console.log('[d3.js] chain:', chain)

          for (let i = chain.length - 1; i >= 0; i--) {
            const node = d3.select(`.node[node-name="${chain[i]}"]`);
            const nodeData = node.datum();
            if (!nodeData.children && nodeData.data.children) {
              node.node().dispatchEvent(new Event('click'));
            }
          }
        }
        else {
          console.log('[d3.js] "chain" is either "null" or "undefined"')
        }
        // ----------------------------------------

      })
      // IF (ERROR) THROW ERROR:
      .catch(function(error) {
        console.log('[d3.js] JSON callback function error')
        if (error) throw error;
      });
    // ----------------------------------------


    // 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
                  //   https://stackoverflow.com/questions/67480339/programmatically-opening-d3-js-v4-collapsible-tree-nodes
          .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;
      });

      // CREATE 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) {
      // ***** New in d3.js v6: *****
      function click(event, 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
    //   https://stackoverflow.com/questions/67480339/programmatically-opening-d3-js-v4-collapsible-tree-nodes
    ///*
      setTimeout(() => {
        //const node = d3.select('.node[node-name="Earth"]').node();
        //const node = d3.select('.node[node-name="Nature"]').node();
        const node = d3.select('.node[node-name="Society"]').node();
        console.log('[setTimeout()] NODE: ', node);
        node.dispatchEvent(new Event('click'));
      }, 2500);
    //*/
    // ----------------------------------------

    // ----------------------------------------
    // Per answer at
    //   https://stackoverflow.com/questions/67527258/programmatically-open-nested-collapsed-hidden-node-in-d3-js-v4/67530786?noredirect=1#comment119390942_67530786
    //   https://jsfiddle.net/mrovinsky/ujwsd7qz/

    const findNodeAncestors = (root, name) => {
      if (root.name === name) {
        return [name];
      }
      if (root.children)
        for (let i = 0; i < root.children.length; i++) {
          const chain = findNodeAncestors(root.children[i], name);
          if (chain) {
            chain.push(root.name);
            return chain;
          }
        }
      return null;
    }; 

    const chain = findNodeAncestors(treeData, 'Earth');
    // Console: "Uncaught ReferenceError: treeData is not defined"
    
    for (let i = chain.length - 1; i >= 0; i--) {
      const node = d3.select(`.node[node-name="${chain[i]}"]`);
      const nodeData = node.datum();
      if (!nodeData.children && nodeData.data.children) {
        node.node().dispatchEvent(new Event('click'));
      }
    }
    // ----------------------------------------

  </script>
</body>
</html>

推荐答案

treeData 变量只能在定义为参数的函数范围内使用:

The treeData variable can be used only in the scope of the function where it's defined as an argument:

d3.json("https://...json")
  .then(function(treeData) {
    // Scope of the function, treeData can be used here
  });  

// Out of scope, treeData is not defined

解决方案是将以 const findNodeAncestors ... (〜25行)开头的块移动到函数体内(最后一次出现 update 之后,右括号):

The solution is to move the block that starts with const findNodeAncestors... (~25 lines) inside the function body (right after the last occurence of update with a closing brace):

        update(d);
      }
      // ----------------------------------------
      // HERE
    }

这篇关于在d3.js v6中以编程方式打开嵌套,折叠(隐藏)的节点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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