以编程方式打开 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/
- [已更新,有解决方案] https://jsfiddle.net/vstuart/acx5zfgn/62/
虽然未在 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'));
我修改了 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/
- [已更新,有解决方案] https://jsfiddle.net/vstuart/acx5zfgn/62/
虽然未在 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'));