通过选择器以编程方式访问 d3.js v6 可折叠树中的数据

问题描述 投票:0回答:3

更新 [2021-05-27:8天后]我自己解决了这个问题——请参阅已接受的答案。 为了简洁起见,我编辑了下面的原始问题,删除了与问题/解决方案不再相关的部分。

可以在此处找到原始的有问题的代码(HTML 文件,当时包含嵌入该文件中的特定于应用程序的 d3.js 代码)的副本。

https://gist.githubusercontent.com/victoriastuart/83f1de548ff2de4dda60ccbd0da937aa/raw/38a343e11bead986f0acb38fcb33f64c730bc103/ontology-d3jsv6.html

在该代码中,我使用函数来加载 JSON 数据并搜索和检索节点。

  • function myOntology(node) { d3.json(ontology.json").then(function(treeData) {...}}

  • function findNode(node) { myOntology(node); }

  • function getNode(node) {...}

该解决方案简化了该过程,不再依赖这些功能。


原始问题(转述)

我遇到了有关通过 Promise 加载到 d3.js v6 可折叠树中的数据的编程访问的问题。

主要问题是,一旦我将 JSON 数据加载到 d3 可视化中,第一次访问后我就无法重新访问这些数据。

也就是说,我似乎被“卡住”在初始数据加载中(我将其解释为由于承诺/回调未退出 - 和/或其他未识别的编码问题)。

不工作:

d3.js ontology demo - 2021.05.19

javascript html d3.js promise callback
3个回答
2
投票

我设法解决了这个问题。

  • d3.js 版本 6 (v6) 可折叠树可视化;使用

    d3.v6.min.js

  • 我的应用程序特定的 d3.js 代码、本体 JSON 数据和 CSS 样式表是从外部文件加载的。

  • 数据视图可通过下拉选择器选项访问(自动填充)。

完整代码如下。


工作中!

d3 ontology demo - 2021.05.27


本体-d3jsv6e.html

<!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">
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />

  <title>My Ontology</title>

  <link rel="stylesheet" href="d3jsv6.css">

  <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.v6.min.js"></script>
</head>

<body>
  <div id="d3_object">
    <object>
      <div id="includedContent"></div>
    </object>
  </div>
    <div id="controls">
      <ul>
        <span class="dropdown_item">
          Node:
          <select class="dropdown_item" name="dropdown_item"></select>
        </span>

        <button class="reset">Reset</button>&nbsp;
        <button onclick="window.location.reload(false)">Reload</button>
      </ul>
    </div>

    <script src="ontology-d3jsv6e.js"></script>

</body>
</html>


d3jsv6.css

.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 {
  border: 1px solid darkgray;
  overflow: auto;
  width: 80%;
  margin: 0.5rem 0.5rem 1rem 0.25rem;
}

本体.json

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

    { "name": "Nature",
      "children": [
        { "name": "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" },

    { "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" }
      ]
    }
  ]
}

本体-d3jsv6e.js

// ============================================================================
// INITIALIZATION:
    var i = 0,
        duration = 250,
        root;

    var margin = {top: 20, right: 90, bottom: 30, left: 90},
        width = 1500 - margin.left - margin.right,
        height = 600 - margin.top - margin.bottom;

    var svg = d3.select("#includedContent").append("svg")
        .attr("width", width + margin.right + margin.left)
        .attr("height", height + margin.top + margin.bottom)

        // ----------------------------------------
        // PAN, ZOOM:
        .call(d3.zoom()
            .scaleExtent([0.25, 3])
            .on("zoom", function (event) {
            svg.attr("transform", event.transform)
            }))
        // ----------------------------------------
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
        // ----------------------------------------

    // ----------------------------------------
    // DECLARE A TREE LAYOUT, SIZE:
    var treemap = d3.tree().size([height, width]);

// ============================================================================
// LOAD DATA:

d3.json('../../../../js/ontology.json', hello())
    .then(d => {
        setup(d);
        update(d);
    })
    .catch(function(error) {
        console.log('Error: failed promise')
        if (error) throw error;
    });

function hello() {
  console.log('*********************\n** Hello Victoria! **\n*********************')
};

// ============================================================================
// SETUP VISUALIZATION:

function setup(myTree) {
    // console.log('[setup()] myTree:', this.myTree)

    // ASSIGN PARENT, CHILDREN, HEIGHT, DEPTH:
    root = d3.hierarchy(myTree, function(d) { return d.children; });

    root.x0 = height / 2;
    root.y0 = 0;

    // COLLAPSE AFTER THE SECOND LEVEL:
    root.children.forEach(collapse);

    update(root);

    let variables = myTree.children;

    // POPULATE DROPDOWN SELECTOR:
    d3.select('select.dropdown_item')
        .on("change", function() {
            // console.log('value:', this.value)
            // const node = d3.select(`.node[node-name="Nature"]`);
            const node = d3.select(`.node[node-name=${this.value}]`);
            const nodeData = node.datum();
            if (!nodeData.children && nodeData.data.children) {
                node.node().dispatchEvent(new Event('click'));
            }
        })
        .selectAll('option')
        .data(variables)
        .enter()
        .append('option')
        // .attr('value', d => d.children[0].name)
        .attr('value', d => d.children.name)
        .text(d => d.name);

    // RESET ONTOLOGY:
    d3.select('button.reset')
        .on("click", function() {
            setup(myTree)
        });
};

// ============================================================================
// 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
    }
}

// ============================================================================
// UPDATE THE VISUALIZATION:

function update(source) {

    // ASSIGN 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')
        .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:

    // d3.js v5:
    //   function click(d) {
    // 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:
        window.location = d.data.url;
        // window.open("https://www.example.com", "_self");
    }
    update(d);
    }
    // ----------------------------------------
    // return;
}
// ============================================================================

1
投票

这是一个简单的代码,它递归地枚举树中的所有项目并填充组合框(省略根项目):

const jsonUrl = 'https://gist.githubusercontent.com/victoriastuart/abbcf355bf1590be02f6dec297be2706/raw/2418e5f6b7626b3c5842665a51b7d0d27f74e909/ontology_for_d3_test.json';

const enumerateItems = (root, items) => {
  items.push(root.name);
  if (root.children)
    root.children.forEach(child => enumerateItems(child, items));
}

d3.json(jsonUrl).then(treeData => {
  const items = [];
  enumerateItems(treeData, items);
  const combobox = d3.select('select')
  items.slice(1).forEach(item => combobox.append('option').text(item));
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<select>
</select>


0
投票

如何从sql server动态获取树上的数据?

© www.soinside.com 2019 - 2024. All rights reserved.