D3力导向图实现人际关系图

业精于勤荒于嬉,行成于思毁于随


说明

  目标:一个单人关系的可视化图表
  实现思路:通过力导向图来实现,存在一个关键点master,其他所有点都仅与它有关,master点居中其他点根据关系权重分布在master点四周,权重不同则与master点距离不同,最终演示效果如下图所示:
力导向图示意图

实现

获取数据

  首先我们先制造数据:

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
  var dataset={
nodes:[//节点
{name:"master",values:12},
{name:"das",values:25},
{name:"fd",values:39},
{name:"dsadsa",values:45},
{name:"htr",values:25},
{name:"vbcx",values:24},
{name:"ht",values:90},
{name:"nbv",values:80},
{name:"yrt",values:68},
{name:"jff",values:63},
{name:"nbv",values:25},
{name:"jyt",values:45},
{name:"mb",values:35},
{name:"jyt",values:78},
{name:"gtre",values:79},
{name:"bvc",values:25},
{name:"mjh",values:58},
{name:"ughf",values:86},
{name:"juy",values:79},
{name:"mjh",values:53},
{name:"njm",values:52},
],
edges:[//边
{ source:0,target:1},
{ source:0,target:2},
{ source:0,target:3},
{ source:0,target:4},
{ source:0,target:5},
{ source:0,target:6},
{ source:0,target:7},
{ source:0,target:8},
{ source:0,target:9},
{ source:0,target:10},
{ source:0,target:11},
{ source:0,target:12},
{ source:0,target:13},
{ source:0,target:14},
{ source:0,target:15},
{ source:0,target:16},
{ source:0,target:17},
{ source:0,target:18},
{ source:0,target:19},
{ source:0,target:20}
]
};

  这是D3力导向图布局接受的数据格式,其中nodes代表点的数据,name字段为master的点为中心点,edges代表边的数据,source代表线的起点,target代表线的终点。(边对象的source和target字段必须存在)

力布局

  接下来就是添加力导向图布局,将我们的数据转换成适合D3生成力导向图的对象数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var force=d3.layout.force()
.nodes(dataset.nodes)//加载节点数据
.links(dataset.edges)//加载边数据
.size([w,h])//设置有效空间的大小
.linkDistance(function(d) {// 根据权重不同连接线段的长度也不同
if(d.target.values < 33) {
return 60;
} else if(d.target.values > 33 && d.target.values < 67) {
return 120;
} else if(d.target.values > 67) {
return 180;
}
})//连线的长度
.friction(0.6) // 摩擦阻力,数值越大阻力越小,范围[0.1],默认值为0.9
.charge(-100)//负电荷量,相互排斥设置的负值越大越排斥
.start();//设置生效

画线

  创建作为连线的svg的line元素

1
2
3
4
5
6
7
8
9
10
11
var edges=svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.style("stroke",function(d){// 设置线的颜色
return colors(d.color);
})
.style("opacity", 0.5)
.style("stroke-width",function(d,i){//设置线的宽度
return 1;
});

画点

  创建作为点的svg的circle元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var nodes=svg.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
.attr("r",function(d){//设置圆点的半径,master为10,其他为5
if(d.name == "master") {
return 10;
}
return 5;
})
.style("fill",function(d){
return colors(d.weight*d.weight*d.weight);
})
.call(force.drag);//可以拖动

增加tick事件

  tick事件在D3中代表运动的每一步,与它类似的还有start:刚开始运动end:运动停止,因此一般只要设置tick事件的监听器即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
force.on("tick",function(){
//边
edges.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;
});

//节点
nodes.attr("cx",function(d){
return d.x;
})
.attr("cy",function(d){
return d.y;
});

控制中心点不动

   我们为了做到控制中心点不动需要在每次计算位置是重置中心点的坐标为[w/2,h/2],所以我们在tick事件中添加以下代码:

1
2
dataset.nodes[0].x = w/2;
dataset.nodes[0].y = h/2;

  这样我们就能保证无论其他点如何移动,中心点始终在svg页面中心保持不动。

完整代码

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
 <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>testD3-22-force.html</title>
<script src="https://cdn.bootcss.com/d3/3.5.17/d3.min.js"></script>
<style type="text/css">
svg {
border: 1px solid red;
}
</style>
</head>
<body>
<script type="text/javascript">
var h=500;
var w=500;
// 颜色函数
var colors=d3.scale.category20()//创建序数比例尺和包括20中颜色的输出范围

//(1)定义节点和联系对象数组
var dataset={
nodes:[//节点
{name:"master",values:12},
{name:"das",values:25},
{name:"fd",values:39},
{name:"dsadsa",values:45},
{name:"htr",values:25},
{name:"vbcx",values:24},
{name:"ht",values:90},
{name:"nbv",values:80},
{name:"yrt",values:68},
{name:"jff",values:63},
{name:"nbv",values:25},
{name:"jyt",values:45},
{name:"mb",values:35},
{name:"jyt",values:78},
{name:"gtre",values:79},
{name:"bvc",values:25},
{name:"mjh",values:58},
{name:"ughf",values:86},
{name:"juy",values:79},
{name:"mjh",values:53},
{name:"njm",values:52},
],
edges:[//边
{ source:0,target:1},
{ source:0,target:2},
{ source:0,target:3},
{ source:0,target:4},
{ source:0,target:5},
{ source:0,target:6},
{ source:0,target:7},
{ source:0,target:8},
{ source:0,target:9},
{ source:0,target:10},
{ source:0,target:11},
{ source:0,target:12},
{ source:0,target:13},
{ source:0,target:14},
{ source:0,target:15},
{ source:0,target:16},
{ source:0,target:17},
{ source:0,target:18},
{ source:0,target:19},
{ source:0,target:20}
]
};

//(2)转化数据为适合生成力导向图的对象数组
var force=d3.layout.force()
.nodes(dataset.nodes)//加载节点数据
.links(dataset.edges)//加载边数据
.size([w,h])//设置有效空间的大小
.linkDistance(function(d) {// 根据权重不同连接线段的长度也不同
if(d.target.values < 33) {
return 60;
} else if(d.target.values > 33 && d.target.values < 67) {
return 120;
} else if(d.target.values > 67) {
return 180;
}
})//连线的长度
.friction(0.6) // 摩擦阻力,数值越大阻力越小,范围[0.1],默认值为0.9
.charge(-100)//负电荷量,相互排斥设置的负值越大越排斥
.start();//设置生效

console.log(dataset);

var svg=d3.select("body")
.append("svg")
.attr("width",w)
.attr("height",h);

//(3)创建作为连线的svg直线
var edges=svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.style("stroke",function(d){// 设置线的颜色
return colors(d.color);
})
.style("opacity", 0.5)
.style("stroke-width",function(d,i){//设置线的宽度
return 1;
});

//(4) 创建作为连线的svg圆形
var nodes=svg.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
.attr("r",function(d){//设置圆点的半径,master为10,其他为5
if(d.name == "master") {
return 10;
}
return 5;
})
.style("fill",function(d){
return colors(d.weight*d.weight*d.weight);
})
.call(force.drag);//可以拖动

//(5)打点更新,没有的话就显示不出来了
force.on("tick",function(){
console.log("tick");
dataset.nodes[0].x = w/2;
dataset.nodes[0].y = h/2;
//边
edges.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;
});

//节点
nodes.attr("cx",function(d){
return d.x;
})
.attr("cy",function(d){
return d.y;
});

})
</script>

</body>
</html>