1 | import pretty from 'prettysize';
|
2 | import schemes from './schemes';
|
3 | import d3 from 'd3';
|
4 | import { arc, initArc, bounceHigh, arcTween, hoverTween, rotateTween } from './utils';
|
5 | import createModes, { highlightMode } from './mode';
|
6 | import createPalette from './palette';
|
7 | import map from 'lodash/map';
|
8 | import each from 'lodash/each';
|
9 |
|
10 | export 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 |
|
38 |
|
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 |
|
52 |
|
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 |
|
66 |
|
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 |
|
79 |
|
80 |
|
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 |
|
92 |
|
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 |
|
151 |
|
152 |
|
153 |
|
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 |
|
206 |
|
207 |
|
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 |
|
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() {
|
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() {
|
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) {
|
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) {
|
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 |