UNPKG

10.6 kBJavaScriptView Raw
1import pretty from 'prettysize';
2import schemes from './schemes';
3import d3 from 'd3';
4import { arc, initArc, bounceHigh, arcTween, hoverTween, rotateTween } from './utils';
5import createModes, { highlightMode } from './mode';
6import createPalette from './palette';
7import map from 'lodash/map';
8import each from 'lodash/each';
9
10export default function renderElectrify(stats) {
11 const root = stats,
12 height = 500,
13 width = 850,
14 radius = Math.min(width, height) * 0.45,
15 deg = 120
16
17 const modeInitial = stats.mode || 'size'
18 const modeFns = {
19 count: () => 1
20 , size: (d) => d.size
21 }
22
23 const svg = d3.select('.chart').append('svg')
24 .attr("preserveAspectRatio", "xMinYMin meet")
25 .attr("viewBox", `0 0 ${width} ${height}`)
26 .append('g')
27 .attr('transform', `translate(${width / 2},${height * .52})`)
28
29 createPalette(schemes, useScheme, changeAssetScheme);
30
31 const partition = d3.layout.partition()
32 .sort(null)
33 .size([2 * Math.PI, radius * radius])
34 .value(modeFns[modeInitial])
35
36 //
37 // Creates the title text in
38 // the center of the rings.
39 //
40 const title = svg.append('text')
41 .text(root.name)
42 .attr('x', 0)
43 .attr('y', -5)
44 .style('font-size', '18px')
45 .style('fill', 'white')
46 .style('font-weight', 500)
47 .style('alignment-baseline', 'middle')
48 .style('text-anchor', 'middle')
49
50 //
51 // Likewise, this is the file
52 // percentage size stat below the title
53 //
54 const percentageSize = svg.append('text')
55 .text(pretty(root.value || root.size))
56 .attr('x', 0)
57 .attr('y', 20)
58 .style('fill', 'white')
59 .style('font-size', '16px')
60 .style('font-weight', 300)
61 .style('alignment-baseline', 'middle')
62 .style('text-anchor', 'middle')
63
64 //
65 // Likewise, this is the file
66 // size stat below the title
67 //
68 const size = svg.append('text')
69 .text('(' + pretty(root.value || root.size) + ')')
70 .attr('x', 0)
71 .attr('y', 40)
72 .style('fill', 'white')
73 .style('font-size', '16px')
74 .style('alignment-baseline', 'middle')
75 .style('text-anchor', 'middle')
76
77 //
78 // Each arc is wrapped in a group element,
79 // to apply rotation transforms while
80 // changing size and shape.
81 //
82 const groups = svg.datum(root).selectAll('g')
83 .data(partition.nodes)
84 .enter()
85 .append('g')
86 .attr('transform', `rotate(${deg})`)
87
88 const maxdepth = groups[0].reduce((max, el) => Math.max(max, el.__data__.depth), 0)
89
90 //
91 // Actually create the arcs for each
92 // file.
93 //
94 const path = groups.append('path')
95 .attr('d', initArc)
96 .attr('display', d => d.depth ? null : 'none')
97 .style('stroke', '#2B2B2B')
98 .style('stroke-width', '0')
99 .style('fill-rule', 'evenodd')
100 .each(function(d) {
101 d.x0 = d.x
102 d.dx0 = d.dx
103 d.el = this
104 })
105
106 let found = [];
107 const _select = (node, selector) => {
108 node.enabled = selector(node);
109 if (node.enabled) {
110 found.push(node);
111 }
112 if (node.children) {
113 for (let c of node.children) {
114 _select(c, selector);
115 }
116 }
117 }
118 _select(root, () => true);
119
120 d3.select('#search').on('keyup', function() {
121 const text = this.value.replace(/^\s+/, "").replace(/\s+$/, "")
122 if (text.length > 0) {
123 found = [];
124 const re = new RegExp(text, 'i');
125 _select(root, (node) => node.name.match(re) !== null);
126 if (found.length === 1) {
127 title.text(found[0].name)
128 size.text(pretty(found[0].value || found[0].size))
129 } else {
130 title.text("Multiple found")
131 let completeSize = 0
132 for (let n of found) {
133 completeSize += n.size;
134 }
135 size.text(`${pretty(completeSize)} total`)
136 }
137 } else {
138 _select(root, () => true);
139 }
140 groups
141 .select('path')
142 .transition()
143 .duration(200)
144 .style('opacity', d => {
145 return d.enabled ? 1.0 : 0.2
146 })
147 })
148
149 //
150 // Colour scheme functionality.
151 //
152 // Triggered immediately with the default
153 // scheme, must be passed a d3 selection.
154 //
155 let background
156 , scheme = 0
157 , specials
158 , color
159
160 useScheme(scheme)
161 function useScheme(n) {
162 specials = schemes[n].specials
163
164 const colors = schemes[n].main
165 Object.keys(specials).forEach((key) => {
166 const idx = colors.indexOf(specials[key].toLowerCase())
167 if (idx === -1) return
168 colors.splice(idx, 1)
169 })
170
171 color = d3.scale
172 .ordinal()
173 .range(colors)
174
175 let _path = path.transition()
176 .duration(600)
177 .ease(bounceHigh, 1000)
178 .delay(d => d.x * 100 + d.y / maxdepth * 0.06125);
179
180 _path.style('fill', (d) => {
181 const name = d.children ? d.name : d.parent.name
182 d.c = schemes[n].modifier.call(d
183 , specials[name] || color(name)
184 , root
185 )
186 return d.c
187 })
188 }
189
190 let ptrans = 0
191 path.transition()
192 .duration(1000)
193 .each(() => ptrans++)
194 .ease('elastic', 2, 1)
195 .delay((d, i) => d.x * 100 + (i % 4) * 250 + d.y / maxdepth * 0.25)
196 .attr('d', arc)
197 .each('interrupt', () => {
198 d3.select('#search').transition().duration(200).style('opacity', 1)
199 })
200 .each('end', () => {
201 ptrans--;
202 })
203
204 //
205 // Rotates the newly created
206 // arcs back towards their original
207 // position.
208 //
209 let gtrans = 0
210 groups.transition()
211 .duration(3250)
212 .each(() => gtrans++)
213 .delay((d, i) => d.x * 100 + (i % 4) * 250 + d.y / maxdepth * 0.25 + 250)
214 .attrTween('transform', rotateTween(deg))
215 .each('end', () => {
216 gtrans--;
217 if (ptrans === 0 && gtrans === 0) {
218 d3.select('#search').transition().duration(200).style('opacity', 1)
219 }
220 })
221
222 groups.on('mouseover', (d) => {
223 highlight(d)
224 title.text(d.name)
225
226 let sizeInPercentage = (d.value/root.value*100).toFixed(2);
227 percentageSize.text(sizeInPercentage + " %")
228
229 size.text('(' + pretty(d.value || d.size) + ')')
230 }).on('mouseout', (d) => {
231 unhighlight(d)
232 title.text(root.name)
233 size.text(pretty(root.value || root.size))
234 })
235
236 highlight.tween = hoverTween(1)
237 function highlight(d) {
238 if (d.el) d3.select(d.el)
239 .transition()
240 .delay(d => (d.depth - 1) * 300 / maxdepth)
241 .ease('back-out', 10)
242 .duration(500)
243 .attrTween('d', highlight.tween)
244 .style('fill', d => d.c)
245
246 if (d.children) {
247 let i = d.children.length
248 while (i--) highlight(d.children[i])
249 }
250 }
251
252 unhighlight.tween = hoverTween(0)
253 function unhighlight(d) {
254 if (d.el) d3.select(d.el)
255 .transition()
256 .delay(d => (d.depth - 1) * 300 / maxdepth)
257 .ease('back-out', 4)
258 .duration(500)
259 .attrTween('d', unhighlight.tween)
260 .style('fill', d => d.c)
261
262 if (d.children) {
263 let i = d.children.length
264 while (i--) unhighlight(d.children[i])
265 }
266 }
267
268 createModes(updateMode);
269
270 updateMode(modeInitial, false)
271
272 function updateMode(mode, update) {
273 highlightMode(mode);
274
275 if (!update) return
276
277 groups
278 .data(partition.value(modeFns[mode]).nodes)
279 .select('path')
280 .transition()
281 .duration(1500)
282 .attrTween('d', arcTween)
283 }
284 /////////////////////////// ASSET VISUALIZATION //////////////////////////////////////////
285 const barHeight = 70;
286 const assetData = stats.assets.sort((x,y) => d3.ascending(y.size, x.size));
287 const maxAssetFileSize = d3.max(map(assetData, (d)=>d.size));
288 const minAssetFileSize = d3.min(map(assetData, (d)=>d.size));
289 const logScale = d3.scale
290 .log()
291 .domain([minAssetFileSize, maxAssetFileSize])
292 .range([0, width])
293
294 each(assetData, (asset)=> asset.logScaledSize = logScale(asset.size))
295
296 const chart = d3.select('.assets').append("svg");
297 chart.attr("preserveAspectRatio", "xMinYMin meet")
298 .attr("viewBox", `0 0 ${width} ${barHeight*assetData.length*2}`)
299 .append("g")
300 .classed("asset")
301
302 const asset = chart.selectAll("g.asset")
303 .data(assetData)
304 .enter()
305 .append("g")
306
307 asset.append("text")
308 .attr("y", (d,i) => i*barHeight*2)
309 .attr("dy", "1em")
310 .text((d) => d.name)
311 .style("font-size", "2em")
312 .style('fill', 'white')
313
314 let bars = asset.append('rect')
315 .attr("transform", (d,i) => `translate(30,${i*barHeight*2+barHeight})`)
316 .attr('height', barHeight*0.7)
317 .attr('width', (d => d.size))
318
319 bars.style('fill', changeAssetScheme(0))
320 .attr('width', '0')
321 .transition()
322 .duration(2000)
323 .attr('width', (d) => d.logScaledSize)
324
325 bars
326 .on('mouseover', function() { //do not use arrow fn
327 d3.select(this)
328 .transition()
329 .ease('back-out', 5)
330 .duration(500)
331 .attr('height', barHeight*0.8)
332 })
333 .on('mouseleave', function() { //do not use arrow fn
334 d3.select(this)
335 .transition()
336 .ease('back-out', 5)
337 .duration(500)
338 .attr('height', barHeight*0.7)
339 })
340
341 asset.append("text")
342 .attr("x", "50")
343 .attr("y", (d,i) => i*barHeight*2+barHeight*1.35)
344 .attr("dy", ".35em")
345 .text((d) => pretty(d.size))
346 .style("font-size", "2.6em")
347 .style('fill', 'white')
348 .on('mouseover', function(d, i) { //do not use arrow fn
349 bars.each(function(e, j){
350 if( j == i){
351 d3.select(this)
352 .transition()
353 .ease('back-out', 5)
354 .duration(500)
355 .attr('height', barHeight*0.8)
356 }
357 })
358 })
359 .on('mouseleave', function(d, i) { //do not use arrow fn
360 bars.each(function(e, j){
361 if (j == i){
362 d3.select(this)
363 .transition()
364 .ease('back-out', 5)
365 .duration(500)
366 .attr('height', barHeight*0.7)
367 }
368 })
369 })
370
371 function changeAssetScheme(n) {
372 const mainColor = schemes[n].main[0];
373 if(bars) {
374 bars.transition()
375 .duration(1000)
376 .ease(bounceHigh, 1000)
377 .delay(() => Math.random()*800)
378 .style("fill", mainColor)
379 }
380 return mainColor;
381 }
382 }
\No newline at end of file