Update [2021-05-27: 8d later] I solved this myself -- see the accepted answer. I edited my original question below for brevity, removing portions that are no longer relevant to the issue/solution.
A copy of the original, problematic code (HTML file, which at that time contained the application-specific d3.js code embedded within that file) can be found here.
In that code, I used functions to load the JSON data and search and retrieve nodes.
function myOntology(node) { d3.json(ontology.json").then(function(treeData) {...}}
function findNode(node) { myOntology(node); }
function getNode(node) {...}
The solution simplifies that process, no longer relying on those functions.
Original question (paraphrased)
I am encountering an issue regarding the programmatic access of data loaded into a d3.js v6 collapsible tree via a promise.
The main issue is that once I load the JSON data into the d3 visualization, after the first access I can not re-acess those data.
That is, I appear to be "stuck" in the initial data load (which I interpret as being due to the promise/ callbacks not exiting - and/or other unidentified coding issues).
not working:
I managed to sort this out.
d3.js version 6 (v6) collapsible tree visualization; uses the d3.v6.min.js
library
My application-specific d3.js code, ontology JSON data, and the CSS stylesheet are loaded from external files.
Data views are accessed from the drop-down selector options (auto-populated).
Complete code follows.
working!
ontology-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>
<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;
}
ontology.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" }
]
}
]
}
ontology-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;
}
// ============================================================================