d3树图(tree)的变形:拥有多个父节点的子节点

Posted on 2017-06-27 |    

最近,有需要实现一种图,如下所示,很类似d3中的树图,但是子节点可以有多个父节点,查了一些资料,没有特别好的方式,最后自己根据需要还是实现了,分享一下自己的方法。

一开始,找资料时,很多人说到如果一个子节点拥有多个父节点时,那么就不算是树图了,建议使用力导向图,当然,直接使用力导向图是很方便的,然而要实现上图的效果,还需要整位置关系、连线方式,想想都感觉很麻烦呀,所以最后还是在树图的基础上稍作调整。

实现思路

  • 使用 tree.nodes()得到所有节点,过滤掉“name”属性相同的节点,只保留一个
  • 原本使用tree.links()即可获得连接关系,但是现在把重复的节点去了,所以需要重新写连接关系,这里用了递归,重新遍历数据,形成新的连接关系
  • 绘制

关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var nodesraw = tree.nodes(root);  
var count=nodesraw.length;
var nodes=[nodesraw[count-1]];
for(var i=count-1;i>-1;i--){
var m= d3.map(nodes,function(d){
return d.name;
})
if(!m.has(nodesraw[i].name)){ //判断如果之前存在name相同的节点,就不插入
nodes.push(nodesraw[i]);
}
}

var links=[];
m = d3.map(nodes,function(d){
return d.name;
})
function traverseTree(node){//递归,遍历所有连接数据
if(!node){
return;
}
if(node.children && node.children.length>0){
var current=m.get(node.name);
for (var i = 0; i < node.children.length; i++) {
var templink={source:current,target:m.get(node.children[i].name)};
console.log(i,node,templink);
links.push(templink);
traverseTree(node.children[i]);
}
}
}
traverseTree(root);
...其他代码

完整代码

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<html>    
<head>
<meta charset="utf-8">
<title>Tree </title>
</head>

<style>

.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}

.node {
font: 12px sans-serif;
}

.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}

</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>

var width = 800,
height = 800;

var tree = d3.layout.tree()
.size([width, height-200])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });



var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });

var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(40,0)");



d3.json("data.json", function(error, root) {

var nodesraw = tree.nodes(root);
var count=nodesraw.length;
var nodes=[nodesraw[count-1]];
for(var i=count-1;i>-1;i--){
var m= d3.map(nodes,function(d){
return d.name;
})
if(!m.has(nodesraw[i].name)){
nodes.push(nodesraw[i]);
}
}

var links=[];
m = d3.map(nodes,function(d){
return d.name;
})
function traverseTree(node){
if(!node){
return;
}
if(node.children && node.children.length>0){
var current=m.get(node.name);
for (var i = 0; i < node.children.length; i++) {
var templink={source:current,target:m.get(node.children[i].name)};
console.log(i,node,templink);
links.push(templink);
traverseTree(node.children[i]);
}
}
}
traverseTree(root);


var link = svg.selectAll(".link")
.data(links)
.enter()
.append("path")
.attr("class", "link")
.attr("d", diagonal);

var node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })

node.append("circle")
.attr("r", 4.5);

node.append("text")
.attr("dx", function(d) { return d.children ? -8 : 8; })
.attr("dy", 3)
.style("text-anchor", function(d) { return d.children ? "end" : "start"; })
.text(function(d) { return d.name; });
});

</script>

</body>
</html>

data.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
{
"name": "flare",
"children": [{
"name": "analytics",
"children": [{
"name": "cluster",
"coupling_id": 1,
"children": [{
"name": "AgglomerativeCluster",
"size": 3938
}, {
"name": "MergeEdge",
"size": 743
},{
"name": "JSONConverter",
"size": 2220
}]
}, {
"name": "graph",
"children": [{
"name": "BetweennessCentrality",
"size": 3534
}, {
"name": "LinkDistance",
"size": 5731
}, {
"name": "MaxFlowMinCut",
"size": 7840
}, {
"name": "ShortestPaths",
"size": 5914
}, {
"name": "SpanningTree",
"size": 3416
}]
}, {
"name": "optimization",
"children": [{
"name": "AspectRatioBanker",
"size": 7074
}]
}]
},{
"name": "data",
"children": [{
"name": "converters",
"children": [{
"name": "Converters",
"size": 721
}, {
"name": "JSONConverter",
"size": 2220
}]
}, {
"name": "DataField",
"size": 1759,
"coupling_id":2
}, {
"name": "DataSchema",
"size": 2165
}]
}
]
}

即可