這次要來練習用 D3
搭配 Vue
做 SVG
甜甜圈圖囉~如果要做中間不要洞的圓餅圖,把內圈半徑改成 0 即可。
一開始想說動畫的部分希望由 CSS
transition
來主導 stroke-dasharray
進行動畫,但後來發現沒有實際的圖形範圍做 Tooltip
的功能(因為 CSS
做的圓圈每個都一樣,只是透過 stroke-dasharray
長度不同造成甜甜圈的假象)。所以後來透過 D3
計算每個區塊的 <path>
才得以完成 Tooltip
的功能。因為以上種種的考量後,下面就拆分成兩個部分來解釋,一個部分是執行動畫的 <circle>
部分,另一個則是製作 Tooltip
功能的 <path>
部分。
執行動畫的 <circle>
1 2 3 4 5 6 7 8 9 10 11 12
| <circle class="circle" v-for="(c, key) in donut" ref="circles" :key="`${key}${c.percentage}${c.offset}`" :r="radius" :cx="chart.outerRadius" :cy="chart.outerRadius" :stroke-width="chart.outerRadius - chart.innerRadius" :stroke-dashoffset="c.offset" :stroke="c.color" fill= "transparent"/>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| .donutChart { .detail { color: gray; } .chartContain { .... .chartWrap { ... .chart { ... transform: rotate(-90deg); .circle { transition: 1s; } } } } }
|
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
| export default { computed: { circum() { return 2 * Math.PI * this.radius; }, color() { return d3.scaleOrdinal(d3.schemeCategory20c); }, totalSum() { let sum = 0;
if (this.data.length) { this.data.forEach((e, i) => { sum = sum + e.value; }); }
return sum; }, donut() { let newArray = []; let afterPer = 0;
if (this.data.length) { this.data.forEach((e, i) => { newArray.push({ percentage: e.value / this.totalSum * 100, offset: this.circum * (1 - afterPer), color: this.color(i) });
afterPer = afterPer + e.value / this.totalSum; }); }
return newArray; }, }, ... methods: { randomData() { ...
this.$nextTick().then(() => { this.donut.forEach((el, index) => { let totalTime = 100; let stroke = this.dasharray(el.percentage);
this.$refs.circles[index].style.cssText = `stroke-dasharray: 0 ${this.circum}; opacity: 0`;
setTimeout(() => { this.$refs.circles[index].style.cssText = `stroke-dasharray: ${stroke.dash} ${stroke.gap}; opacity: 1`; }, totalTime); }); }); }, dasharray(percentage) { let dash = this.circum / 100 * percentage; let gap = this.circum / 100 * (100 - percentage);
return { dash: dash, gap: gap }; }, } }
|
這邊你可能會發現很奇怪的事情,就是為什麼上面已經算過百分比了,但這邊還要再算一次?如果仔細看的話,你應該會發現這邊的百分比是用在甜甜圈上方顯示的文字。想一想,如果統計圖出現小數點後十六位數的百分比,是不是超奇怪!!!所以這邊文字其實是透過 D3
格式換算成我們較易閱讀的百分比,並不是真實的比例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <g class="arc" v-for="(p, key) in pie" :key="`${key}${p.d}`" :transform="`translate(${chart.outerRadius},${chart.outerRadius}) rotate(90)`" v-on:mouseover="showTooltip(key, $event)" v-on:mouseout="hiddenTooltip"> <path fill="transparent" :d="p.d"> </path> <text :transform="`translate(${p.centroid})`" text-anchor="middle" fill="white">{{ p.percentage }} </text> </g>
|
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
| pie() { let newArray = []; let format = d3.format(".0%"); let pie = d3 .pie() .sort(null) .value(function(d) { return d.value; })(this.data);
if (this.data.length) { this.data.forEach((e, i) => { let arc = d3 .arc() .innerRadius(this.chart.innerRadius) .outerRadius(this.chart.outerRadius);
newArray.push({ d: arc({ startAngle: pie[i].startAngle, endAngle: pie[i].endAngle }), centroid: arc.centroid(pie[i]), percentage: format(e.value / this.totalSum) }); }); }
return newArray; }
|
全部畫好的樣子如下圖,可以去 Github 查看完整程式碼喔!也可以在這裡查看直條圖的 Demo,我們下回見~