Interactive-Bubble-Chart

Circle Pack and Force Layout

18_4_15_01.png

Demo

Click here: Demo

Code

HTML

1
<div id="bubble-word" class="bubble-word"></div>

CSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.bubble-word {
width: 100%;
height: 100%;
}
.bubble-word > svg {
width: 100%;
height: 100%;
display: block;
}
.bubble-word .bubble text{
text-anchor: middle;
fill: #333;
font-size: 16px;
}

JS运行环境:IE10+ 、d3.js (v4.13)

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
let svg,
pack,
bubble,
simulation;

const width = document.body.clientWidth;
const height = document.body.clientHeight;
const centerX = width * 0.5;
const centerY = height * 0.5;
const scaleColor = d3.scaleOrdinal(d3.schemeCategory20);
let forceCollide = d3.forceCollide(d => d.r + 1); // create a circle collision force.

getData();

function init(data) {
svg = d3.select("#bubble-word")
.append('svg')
.attr("xmlns", "http://www.w3.org/2000/svg");

// use pack to calculate radius of the circle
pack = d3.pack()
.size([width, height])
.padding(1.5);

// @v4 strength to apply to the position forces
const forceStrength = 0.05;

// @v4 We create a force simulation now and add forces to it.
// velocityDecay: 默认为 0.4,衰减系数类似于阻力。
// 较低的衰减系数可以使得迭代次数更多,其布局结果也会更理性,但是可能会引起数值不稳定从而导致震荡
simulation = d3.forceSimulation()
// .velocityDecay(0.3)
.force("x", d3.forceX(centerX).strength(forceStrength))
.force("y", d3.forceY(centerY).strength(forceStrength))
.force("charge", d3.forceManyBody())//-Math.pow(d.radius, 2.0) * forceStrength)
.force("collide", forceCollide);

// @v4 Force starts up automatically,
// which we don't want as there aren't any nodes yet.
// simulation.stop();

draw(data);
}

/**
* @method createNodes 创建节点
* @param {Object} source
*/
function createNodes(source) {
let root = d3.hierarchy({ children: source })
.sum(d => d.value);

const rootData = pack(root).leaves().map((d, i) => {
const data = d.data;
const color = scaleColor(data.name);
return {
x: centerX + (d.x - centerX) * 3,
y: centerY + (d.y - centerY) * 3,
id: "bubble" + i,
r: 0,
radius: d.r,
value: data.value,
name: data.name,
color: color,
}
});

// sort them to prevent occlusion of smaller nodes.
rootData.sort((a, b) => b.value - a.value);
return rootData;
}

/**
* @method draw 绘制bubble,动画初始化
* @param {Object} data
*/
function draw(data) {
const nodes = createNodes(data);
console.log(nodes);

// @v4 Once we set the nodes, the simulation will start running automatically!
simulation.nodes(nodes).on("tick", ticked);

bubble = svg.selectAll(".bubble")
.data(nodes)
.enter().append("g")
.attr("class", "bubble")
.call(d3.drag()
.on("start", (d) => {
if (!d3.event.active) { simulation.alphaTarget(0.2).restart(); }
d.fx = d.x;
d.fy = d.y;
})
.on("drag", (d) => {
d.fx = d3.event.x;
d.fy = d3.event.y;
})
.on("end", (d) => {
if (!d3.event.active) { simulation.alphaTarget(0); }
d.fx = null;
d.fy = null;
})
);
// .on("mouseover", d => {
// d3.select("#"+d.id).attr("fill", d3.color(d.color).brighter());
// }).on("mouseout", d => {
// d3.select("#"+d.id).attr("fill", d.color);
// });

var href = bubble.append("a")
.attr("href", d => "http://me.chjiyun.com/#"+d.name)
.attr("target", "_blank");

href.append("circle")
.attr("id", d => d.id)
.attr("r", 1e-6)
.attr("fill", d => d.color)
.transition().duration(2000).ease(d3.easeElasticOut)
.tween("circleIn", d => {
let i = d3.interpolateNumber(0, d.radius);
return (t) => {
d.r = i(t);
simulation.force("collide", forceCollide);
};
});

href.append("text")
.attr("dy", d => "0.35em")
.text(d => d.name);
}

function getData() {
d3.json("/data/bubble.json", function(err, data) {
if (err) throw err;

init(data.data);
});
}

function ticked() {
bubble.attr("transform", d => `translate(${d.x},${d.y})`)
.select("circle")
.attr("r", d => d.r);
}

注意:低版本浏览器无法解析此代码,需转译成es5运行

bubble.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
{
"code": 0,
"data":[
{
"name": "Lily",
"value": 1
},{
"name": "Shane",
"value": 2
},{
"name": "Sunshine",
"value": 8
},{
"name": "Moon",
"value": 10
},{
"name": "Meton",
"value": 6
},{
"name": "Huton",
"value": 12
},{
"name": "Ryan",
"value": 4
},{
"name": "Ryan",
"value": 4
},{
"name": "Cyan",
"value": 5
},{
"name": "Huston",
"value": 3
}
]
}

参考:
Bubble Chart
Interactive bubble chart combining Circle Pack and Force Layout
Force-Directed Graph