如何在有很多子节点时显示所有子节点
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在有很多子节点时显示所有子节点相关的知识,希望对你有一定的参考价值。
我正在D3可视化上工作,以显示父子关系。
当我的孩子数较少时,我就可以将数据完美地可视化,但是当孩子数较多时,子节点会重叠。如何修改图表,以便可以看到根(左侧)左侧的所有节点?例如,使节点围绕根以圆形方式存在,一些节点靠近根,而某些节点以某种顺序排列。蜜蜂最好的展示方式是什么?
这是我的代码。
var data =
"name": "Root",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png",
"children": [
"name": "3",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
"name": "4",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
"name": "2",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
"name": "2",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
"name": "2",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
"name": "2",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
"name": "2",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
"name": "2",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
"name": "2",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
],
"parent": [
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
]
;
var bgColors = ['#fd90b5', '#6ca1e9', '#fa975c', '#eb7092', '#f88962', '#a094ed', '#7f8de1'];
var dr = 0;
// Left data
var data1 =
"name": data.name,
"img": data.img,
"children": JSON.parse(JSON.stringify(data.children))
;
// Right data
var data2 =
"name": data.name,
"img": data.img,
"children": JSON.parse(JSON.stringify(data.parent))
;
// Create d3 hierarchies
var right = d3.hierarchy(data1);
var left = d3.hierarchy(data2);
// Render both trees
drawTree(right, "right")
drawTree(left, "left")
// draw single tree
function drawTree(root, pos)
var refType;
if (pos == 'left')
refType = 'left';
else
refType = 'right';
var SWITCH_CONST = 1;
if (pos === "left")
SWITCH_CONST = -1;
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height")
var g = svg.append("g").attr("transform", "translate(" + width / 2 + ",0)");
var tree = d3.tree()
.size([height, SWITCH_CONST * (width - 150) / 2]);
tree(root)
var nodes = root.descendants();
var links = root.links();
nodes[0].x = height / 2
// Create links
var link = g.selectAll(".link")
.data(links)
.enter()
link.append("path")
.attr("class", "link")
.attr("d", function(d)
//first return returns a curve and the second will return straight lines in
//return "M" + d.target.y + "," + d.target.x + "C" + (d.target.y + d.source.y) / 2.5 + "," + d.target.x + " " + (d.target.y + d.source.y) / 2 + "," + d.source.x + " " + d.source.y + "," + d.source.x;
return "M" + d.target.y + "," + d.target.x + "A" + dr + "," + dr + " 1,0 0 " + d.source.y + "," + d.source.x;
);
link.append("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("transform", function(d)
return "translate(" +
((d.source.y + d.target.y) / 2) + "," +
((d.source.x + d.target.x) / 2) + ")";
)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.data(nodes)
.text(refType);
// Create nodes
var node = g.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", function(d)
return "node" + (d.children ? " node--internal" : " node--leaf");
)
.attr("transform", function(d)
return "translate(" + d.y + "," + d.x + ")";
)
node.append('circle')
.attr('class', 'icon-wrap')
.attr('x', 0)
.attr('y', 0)
.attr('r', 25)
.style('fill', 'black');
node.append('image')
.attr('href', d => d.data.img)
.attr('x', '-25')
.attr('y', '-25')
.attr('height', '50')
.attr('width', '50');
node.append("text")
.attr("dy", 45)
.style("text-anchor", "middle")
.text(d => d.data.name);
.node circle
fill: #999;
.node text
font: 12px sans-serif;
.node--internal circle
fill: #555;
.link
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg width="700" height="400"></svg>
这里是孩子人数少的样子。
请让我知道如何优化代码以适合最多30个子节点。
谢谢
答案
一种可能的解决方案是创建一个递归函数,该函数将在root
函数中调整drawTree
数据坐标。
这里是一个递归函数,它将使节点左右错开。请注意注释,其中提到了代码的哪一部分控制坐标的计算。
function adjustClashes(data, siblings = 1, index = 1, radius = 20, height = 400)
//can the node fit in the current x level?
// if not adjust it
let heightneeded = siblings * radius * 2;
if (heightneeded > height)
// the code in this if statement will control the calculations for your new coordinates
// In the simplest case we adjust the nodes by staggering odd and even nodes
if (index % 2 != 0)
data.y = data.y + (radius * 2)
else
data.y = data.y - (radius * 2)
// if there are children go deeper and perform same adjustment
if (data.children)
data.children.forEach((f, i) =>
return adjustClashes( f, data.children.length, i )
)
else
return;
// finally return the data
return data
完整代码段:
var data =
name: "Root",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png",
children: [
name: "3",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
name: "4",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
name: "1",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
name: "2",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
name: "1",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
name: "2",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
name: "1",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
name: "2",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
name: "1",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
name: "2",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
name: "1",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
name: "2",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
name: "1",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
name: "2",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
name: "1",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
,
name: "2",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
],
parent: [
name: "1",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
]
;
var bgColors = [
"#fd90b5",
"#6ca1e9",
"#fa975c",
"#eb7092",
"#f88962",
"#a094ed",
"#7f8de1"
];
var dr = 0;
// Left data
var data1 =
name: data.name,
img: data.img,
children: JSON.parse(JSON.stringify(data.children))
;
// Right data
var data2 =
name: data.name,
img: data.img,
children: JSON.parse(JSON.stringify(data.parent))
;
// Create d3 hierarchies
var right = d3.hierarchy(data1);
var left = d3.hierarchy(data2);
// Render both trees
drawTree(right, "right");
drawTree(left, "left");
// draw single tree
function drawTree(root, pos)
var refType;
if (pos == "left") refType = "left";
else refType = "right";
var SWITCH_CONST = 1;
if (pos === "left")
SWITCH_CONST = -1;
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var g = svg.append("g").attr("transform", "translate(" + width / 2 + ",0)");
var tree = d3.tree().size([height, (SWITCH_CONST * (width - 150)) / 2]);
tree(root);
function adjustClashes(
data,
siblings = 1,
index = 1,
radius = 20,
height = 400
)
//can the node fit in the current x level?
// if not adjust it
let heightneeded = siblings * radius * 2;
if (heightneeded > height)
// the code in this if statement will control the calculations for your new coordinates
// In the simplest case we adjust the nodes by staggering odd and even nodes
if (index % 2 != 0)
data.y = data.y + radius * 2;
else
data.y = data.y - radius * 2;
// if there are children go deeper and perform same adjustment
if (data.children)
data.children.forEach((f, i) =>
return adjustClashes(f, data.children.length, i);
);
else
return;
// finally return the data
return data;
root = adjustClashes(root);
var nodes = root.descendants();
var links = root.links();
nodes[0].x = height / 2;
// Create links
var link = g.selectAll(".link").data(links).enter();
link
.append("path")
.attr("class", "link")
.attr("d", function (d)
//first return returns a curve and the second will return straight lines in
//return "M" + d.target.y + "," + d.target.x + "C" + (d.target.y + d.source.y) / 2.5 + "," + d.target.x + " " + (d.target.y + d.source.y) / 2 + "," + d.source.x + " " + d.source.y + "," + d.source.x;
return (
"M" +
d.target.y +
"," +
d.target.x +
"A" +
dr +
"," +
dr +
" 1,0 0 " +
d.source.y +
"," +
d.source.x
);
);
link
.append("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("transform", function (d)
return (
"translate(" +
(d.source.y + d.target.y) / 2 +
"," +
(d.source.x + d.target.x) / 2 +
")"
);
)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.data(nodes)
.text(refType);
// Create nodes
var node = g
.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", function (d)
return "node" + (d.children ? " node--internal" : " node--leaf");
)
.attr("transform", function (d)
return "translate(" + d.y + "," + d.x + ")";
);
node
.append("circle")
.attr("class", "icon-wrap")
.attr("x", 0)
.attr("y", 0)
.attr("r", 25)
.style("fill", "black");
node
.append("image")
.attr("href", (d) => d.data.img)
.attr("x", "-25")
.attr("y", "-25")
.attr("height", "50")
.attr("width", "50");
node
.append("text")
.attr("dy", 45)
.style("text-anchor", "middle")
.text((d) => d.data.name);
.node circle
fill: #999;
.node text
font: 12px sans-serif;
.node--internal circle
fill: #555;
.link
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg width="700" height="400"></svg>
以上是关于如何在有很多子节点时显示所有子节点的主要内容,如果未能解决你的问题,请参考以下文章