-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathscript2.js
241 lines (220 loc) · 6.69 KB
/
script2.js
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// input data
const data = [
{
name: 'Public Sale',
percentage: 20, // percentage
value: 80, // millions
color: '#0789F8',
},
{
name: 'Reserved',
percentage: 35,
value: 140,
color: '#F9BA00',
},
{
name: 'Advisors',
percentage: 10,
value: 40,
color: '#FE8C00',
},
{
name: 'Foundation',
percentage: 10,
value: 40,
color: '#A6A8F8',
},
{
name: 'Option pool',
percentage: 12.5,
value: 50,
color: '#47D7A8',
},
{
name: 'Team Tokens',
percentage: 12.5,
value: 50,
color: '#3BCB60',
},
];
// retrieve the svg in which to plot the viz
const svg = d3
.select('svg');
// identify the dimensions of the viewBox to establish the svg canvas
const viewBox = svg.attr('viewBox');
const regexViewBox = /\d+ \d+ (\d+) (\d+)/;
// ! .match() returns string values
const [, viewBoxWidth, viewBoxHeight] = viewBox.match(regexViewBox).map(item => Number.parseInt(item, 10));
// with the margin convention include a group element translated within the svg canvas
const margin = {
top: 20,
right: 20,
bottom: 20,
left: 20,
};
// compute the width and height of the actual viz from the viewBox dimensions and considering the margins
// this to later work with width and height attributes directly through the width and height variables
const width = viewBoxWidth - (margin.left + margin.right);
const height = viewBoxHeight - (margin.top + margin.bottom);
// compute the radius as half the minor size between the width and height
const radius = Math.min(width, height) / 2;
// initialize a variable to have the multiple elements share the same stroke-width property
const strokeWidth = 10;
const group = svg
.append('g')
.attr('transform', `translate(${margin.left} ${margin.top})`);
// DEFAULT CIRCLE
// circle used as a background for the colored donut chart
// add a group to center the circle in the canvas (this to rotate the circle from the center)
const groupDefault = group
.append('g')
.attr('transform', `translate(${width / 2} ${height / 2})`);
// append the circle showing only the stroke
groupDefault
.append('circle')
.attr('cx', 0)
.attr('cy', 0)
.attr('r', radius)
.attr('transform', 'rotate(-90)')
.attr('fill', 'none')
.attr('stroke', 'hsla(0, 0%, 0%, 0.08')
.attr('stroke-width', strokeWidth)
.attr('stroke-linecap', 'round')
// hide the stroke of the circle using the radius
// this to compute the circumference of the shape
.attr('stroke-dasharray', radius * 3.14 * 2)
.attr('stroke-dashoffset', radius * 3.14 * 2);
// COLORED CIRCLES
// pie function to compute the arcs
const pie = d3
.pie()
.sort(null)
.padAngle(0.12)
// use either the value or the percentage in the dataset
.value(d => d.value);
// arc function to create the d attributes for the path elements
const arc = d3
.arc()
// have the arc overlaid on top of the stroke of the circle
.innerRadius(radius)
.outerRadius(radius);
/* for each data point include the following structure
g // wrapping all arcs
g // wrapping each arc
arc // actual shape
line // connecting line
text // text label
g
arc
...
*/
// wrapping group, horizontally centered
const groupArcs = group
.append('g')
.attr('transform', `translate(${width / 2} ${height / 2})`);
const groupsArcs = groupArcs
.selectAll('g')
.data(pie(data))
.enter()
.append('g');
// include the arcs specifying the stroke with the same width of the circle element
groupsArcs
.append('path')
.attr('d', arc)
.attr('fill', 'none')
.attr('stroke', d => d.data.color)
.attr('stroke-width', strokeWidth * 0.8)
.attr('stroke-linecap', 'round')
.attr('stroke-linejoin', 'round')
// hide the segments by applying a stroke-dasharray/stroke-dashoffset equal to the circle circumference
// ! the length of the element varies, and it considered afterwords
// for certain the paths are less than the circumference of the entire circle
.attr('stroke-dasharray', radius * 3.14 * 2)
.attr('stroke-dashoffset', radius * 3.14 * 2);
// include line elements visually connecting the text labels with the arcs
groupsArcs
.append('line')
.attr('x1', 0)
.attr('x2', (d) => {
const [x] = arc.centroid(d);
return x > 0 ? '25' : '-25';
})
.attr('y1', 0)
.attr('y2', 0)
.attr('stroke', ({ data: d }) => d.color)
.attr('stroke-width', 1.5)
.attr('transform', (d) => {
const [x, y] = arc.centroid(d);
const offset = x > 0 ? 20 : -20;
return `translate(${x + offset} ${y})`;
})
.attr('stroke-dasharray', 25)
.attr('stroke-dashoffset', 25);
// include text elements associated with the arcs
groupsArcs
.append('text')
.attr('x', 0)
.attr('y', 0)
.attr('font-size', 8)
.attr('text-anchor', (d) => {
const [x] = arc.centroid(d);
return x > 0 ? 'start' : 'end';
})
.attr('transform', (d) => {
const [x, y] = arc.centroid(d);
const offset = x > 0 ? 50 : -50;
return `translate(${x + offset} ${y})`;
})
.html(({ data: d }) => `
<tspan x="0">${d.name}:</tspan><tspan x="0" dy="10" font-size="6">${d.percentage}% / ${d.value}M</tspan>
`)
.style('opacity', 0)
.style('visibility', 'hidden');
// TRANSITIONS
// once the elements are set up
// draw the stroke of the larger circle element
groupDefault
.select('circle')
.transition()
.ease(d3.easeExp)
.delay(200)
.duration(2000)
.attr('stroke-dashoffset', '0')
// once the transition is complete
// draw the smaller strokes one after the other
.on('end', () => {
// immediately set the stroke-dasharray and stroke-dashoffset properties to match the length of the path elements
// using vanilla JavaScript
const paths = document.querySelectorAll('svg g g path');
paths.forEach((path) => {
const length = path.getTotalLength();
path.setAttribute('stroke-dasharray', length);
path.setAttribute('stroke-dashoffset', length);
});
const duration = 1000;
// transition the path elements to stroke-dashoffset 0
d3
.selectAll('svg g g path')
.transition()
.ease(d3.easeLinear)
.delay((d, i) => i * duration)
.duration(duration)
.attr('stroke-dashoffset', 0);
// transition the line elements elements to stroke-dashoffset 0
d3
.selectAll('svg g g line')
.transition()
.ease(d3.easeLinear)
.delay((d, i) => i * duration + duration / 2.5)
.duration(duration / 3)
.attr('stroke-dashoffset', 0);
// transition the text elements to opacity 1 and visibility visible
d3
.selectAll('svg g g text')
.transition()
.ease(d3.easeLinear)
.delay((d, i) => i * duration + duration / 2)
.duration(duration / 2)
.style('opacity', 1)
.style('visibility', 'visible');
});