我试图显示带有群集节点的网络图,但我在D3中遇到了嵌套节点的问题。第一个"层"包含集群,第一层的每个节点可以包含多个节点。网络中的链接可能只发生在集群之间(意味着第一层节点之间)。
这是我到目前为止的代码。我能够显示第一级节点。我无法弄清楚如何显示嵌套节点(请参阅每个节点const data
中children
中的代码)。
const node_radius = 100;
const width = 800;
const height = 400;
const links = [
{ "source": 1, "target": 6}
] ;
const data = [
{
"id":1,
"level": "cluster",
"name": "analytics1",
"children": [
{
"id":2,
"name": "animate1",
"level": "leaf",
"size": 15,
"parent": 1
},
{
"id":3,
"name": "animate2",
"level": "leaf",
"size": 15,
"parent": 1
},
{
"id":4,
"name": "animate3",
"level": "leaf",
"size": 15,
"parent": 1
}
]
},
{
"id":6,
"name": "analytics2",
"level": "cluster",
"children": [
{
"id":7,
"name": "animate4",
"level": "leaf",
"size": 10,
"parent": 6
}
]
}
]
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var simulation = d3.forceSimulation()
// pull nodes together based on the links between them
.force("link", d3.forceLink().id(function(d) { return d.id; }).strength(0.0001))
// push nodes apart to space them out
.force("charge", d3.forceManyBody().strength(-10))
// add some collision detection so they don't overlap
.force("collide", d3.forceCollide().radius(node_radius))
// and draw them around the centre of the space
.force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links).enter().append("line")
.attr("stroke-width", 5)
.attr("stroke","#000");
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("id", function(d) {return "circle"+d.id;})
.attr("class", "node")
.attr("r", node_radius)
.style("opacity", 0.2)
.attr("dx", 12)
.attr("dy", ".35em");
var text = svg.append("g")
.attr("class", "label")
.selectAll("text")
.data(data)
.enter().append("text")
.text(function(d) { return d.name });
// Update and restart the simulation.
simulation.nodes(data).on("tick", ticked);
simulation.force("link").links(links);
simulation.alpha(1).restart();
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", positionNode);
text
.attr("dx", function(d) { return d.x - 30; })
.attr("dy", function(d) { return d.y + 15; });
}
// move the node based on forces calculations
function positionNode(d) {
// keep the node within the boundaries of the svg
if (d.x < node_radius) {
d.x = 2*node_radius
};
if (d.y < node_radius) {
d.y = 2*node_radius
};
if (d.x > width-node_radius) {
d.x = width-(2*node_radius)
};
if (d.y > height-node_radius) {
d.y = height-(2*node_radius)
};
return "translate(" + d.x + "," + d.y + ")";
}
&#13;
<script src="https://d3js.org/d3.v4.min.js"></script>
&#13;
我希望有类似下图的内容。显示两个集群,并且在每个组中,所有子节点(叶节点)由较小节点表示。节点大小应该可以为两个&#34;层定制。从数据输入。
Example I'm trying to follow on Fiddle.
我还尝试使用d3.pack()
将圈子打包到其他圈子中。这是一个example。我目前使用这种方法的问题是我没有成功地在第一层&#34;层的节点之间添加空间和链接。 (在集群之间)。高级集群也被打包在一起,不可能在它们之间添加可理解的链接。
答案 0 :(得分:0)
我终于成功地将d3.pack()
示例与聚类示例合并。这是我的解决方案。
var data = [
{
"id":1,
"level": "cluster",
"name": "analytics1",
"children": [
{
"id":2,
"name": "animate1",
"level": "leaf",
"size": 8,
"parent": 1,
"icon":"https://image.freepik.com/free-icon/apple-logo_318-40184.jpg"
},
{
"id":3,
"name": "animate2",
"level": "leaf",
"size": 10,
"parent": 1,
"icon": "https://www.freelogodesign.org/img/logo-ex-7.png"
},
{
"id":4,
"name": "animate3",
"level": "leaf",
"size": 5,
"parent": 1,
"icon": "http://brandmark.io/logo-rank/random/pepsi.png"
}
]
},
{
"id":6,
"name": "analytics2",
"level": "cluster",
"children": [
{
"id":7,
"name": "animate4",
"level": "leaf",
"size": 10,
"parent": 6,
"icon":"https://www.seoclerk.com/pics/558390-11FO8A1505384509.png"
}
]
}
]
var links = [
{ "source": 1, "target": 6}
] ;
var w = 1200, h = 500;
var cluster_padding = 5;
var node_padding = 2;
var size_ratio =100;
var color = d3.scaleOrdinal(d3.schemeCategory20c);
let sumSizes = 0;
data.forEach(function(cluster){
cluster.children.forEach(function(node){
sumSizes += node.size;
});
});
// Compute sum of sizes for cluster size.
data.forEach(function(cluster){
cluster.size = (
cluster.children.map(function(d){return d.size;})
.reduce(function(acc, val){ return acc+val+node_padding; })/sumSizes
)*size_ratio + cluster_padding;
cluster.children = cluster.children.sort(function(a,b){
return (a.size < b.size) ? 1 : ((b.size < a.size) ? -1 : 0);
})
cluster.children.forEach(function(node){
node.parentSize = cluster.size;
node.size = node.size*size_ratio/sumSizes;
});
});
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
////////////////////////
// outer force layout
var outerSimulation = d3.forceSimulation()
// pull nodes together based on the links between them
.force("link", d3.forceLink().id(function(d) { return d.id; }).strength(0.001))
// push nodes apart to space them out
.force("charge", d3.forceManyBody().strength(-5))
// add some collision detection so they don't overlap
.force("collide", d3.forceCollide().radius(function(d){return d.size+cluster_padding;}))
// and draw them around the centre of the space
.force("center", d3.forceCenter(w / 2, h / 2));
var outerLinks = svg.selectAll("line")
.data(links)
.enter().append("line")
.attr("class", "links")
.attr("stroke-width", 5);
var outerNodes = svg.selectAll("g.outer")
.data(data, function (d) {return d.id;})
.enter()
.append("g")
.attr("class", "outer")
.attr("id", function (d) {return "cluster"+d.id;})
.attr("x", w/2)
.attr("y", w/2)
.call(d3.drag());
outerNodes.append("circle")
.style("fill", function(d,i){return color(i);})
.style("stroke", "blue")
.attr("r", function(d){return d.size});
// Update and restart the simulation.
outerSimulation.nodes(data).on("tick", outerTick);
outerSimulation.force("link").links(links);
outerSimulation.alpha(1).restart();
////////////////////////
// inner force layouts
var innerNodes = [];
var innerTexts = [];
var packs = [];
var margin = 20;
data.forEach(function(n){
// Pack hierarchy definition
var pack = d3.pack()
.size([2*n.size, 2*n.size])
.padding(cluster_padding);
var root = d3.hierarchy(n)
.sum(function(d) { return d.size; })
.sort(function(a, b) { return b.value - a.value; });
var nodes = pack(root).descendants();
// Round images
var defs = svg.append("defs").attr("id", "imgdefs")
var pattern = defs
.selectAll("pattern")
.data(nodes.filter(function(d) { return d.parent }))
.enter().append("pattern")
.attr("id", function(d){return "photo"+d.data.name})
.attr("height", 1)
.attr("width", 1)
.attr("x", "0")
.attr("y", "0");
var image = pattern.append('image')
.attr("class","roundImg")
.attr("id", function(d){return "photo"+d.data.name;})
.attr("xlink:href", function(d){return d.data.icon ? d.data.icon : "";})
.attr("height", function(d){return 3.2*d.r ;})
;
// Nodes
var circle = svg.select("g.outer#cluster"+n.id).selectAll("g.inner")
.data(nodes.filter(function(d) { return d.parent }))
.enter().append("circle")
.attr("class", "node node--leaf ")
.attr("id", function(d) {return d.data.name})
.style("fill", function(d) { return "url(#photo"+d.data.name+")";})
.attr("r", function(d) { return d.r; })
.attr("transform", function(d) { return "translate("+(d.x-n.size) +","+ (d.y-n.size)+")"; })
;
});
////////////////////////
// functions
function outerTick (e) {
outerNodes.attr("transform", positionNode);
outerLinks
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
}
function positionNode(d) {
// keep the node within the boundaries of the svg
if (d.x - d.size < 0) {
d.x = d.size + 2
};
if (d.y - d.size < 0) {
d.y = d.size + 2
};
if (d.x + d.size > w) {
d.x = w - d.size - 2
};
if (d.y + d.size > h) {
d.y = h - d.size - 2
};
return "translate(" + d.x + "," + d.y + ")";
}
&#13;
<!DOCTYPE html>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<link rel="stylesheet" type="text/css" href="css/pack.css">
<body>
<div class="packed" id="packed"></div>
</body>
&#13;