效果圖
源碼
https://github.com/leicj/d3/tree/master/src/components/sunburst
初始化變量
const width = 500;
const height = 500;
const radius = Math.min(width, height) / 2;
const color = d3.scaleOrdinal(d3.schemeCategory10);
不同于canvas, 在svg中我們需要手動(dòng)設(shè)置繪圖的寬度和高度. 這里我們也設(shè)置了餅狀圖的半徑大小以及顏色(使用d3.schemeCategory10).
設(shè)定svg繪圖區(qū)域
const g = d3.select('svg')
.attrs({ width, height })
.append('g')
.attr('transform', `translate(${width / 2},${height / 2})`);
設(shè)定svg的寬高后, 給予一個(gè)<g>用于繪圖, 將其中心設(shè)置到繪圖區(qū)域的中心位置.
格式化數(shù)據(jù)
const partition = d3.partition()
.size([2 * Math.PI, radius]);
d3.partition用于切割繪圖區(qū)域. 這里由于是餅狀圖, 所以需要size函數(shù)設(shè)定其"寬高", 由于圓形的面積為2 * Math.PI * r, 所以2 * Math.PI為弧度, r為半徑, 通過(guò)size函數(shù)則達(dá)到完全分割的狀態(tài).
const root = d3.hierarchy(nodeData)
.sum(d => d.size);
這里使用d3.hierarchy, 則nodeData的數(shù)據(jù)必須滿(mǎn)足"父子層級(jí)"關(guān)系, 例如:
{
"name": "Eve",
"children": [
{
"name": "Cain"
},
{
"name": "Seth",
"children": [
{
"name": "Enos"
},
{
"name": "Noam"
}
]
},
{
"name": "Abel"
},
{
"name": "Awan",
"children": [
{
"name": "Enoch"
}
]
},
{
"name": "Azura"
}
]
}
根據(jù)sum函數(shù)來(lái)判斷子節(jié)點(diǎn)的大小順序. 經(jīng)過(guò)partition(root)后, root會(huì)演化成一個(gè)可供計(jì)算的父子結(jié)構(gòu):
計(jì)算弧度及長(zhǎng)度
const arc = d3.arc()
.startAngle(d => d.x0)
.endAngle(d => d.x1)
.innerRadius(d => d.y0)
.outerRadius(d => d.y1);
由于經(jīng)過(guò)partition后, root已經(jīng)保存了每個(gè)節(jié)點(diǎn)的信息, 這里d.x0為開(kāi)始的角度, d.x1為結(jié)束的角度. d.y0為開(kāi)始的長(zhǎng)度, d.y1為結(jié)束的長(zhǎng)度.
繪圖
g.selectAll('g')
.data(root.descendants())
.enter().append('g').attr('class', 'node').append('path')
.attr('display', d => d.depth ? null : 'none')
.attr('d', arc)
.style('stroke', '#fff')
.style('fill', d => color((d.children ? d : d.parent).data.name))
通過(guò)root.descendants(), 我們將數(shù)據(jù)按size的大小順序傳入. 通過(guò)arc, 進(jìn)行繪制圖形, 而填充的顏色由name決定, 同一個(gè)父結(jié)構(gòu)下的圖形具有相同的顏色.
添加文本
g.selectAll('.node')
.append('text')
.attr('transform', d => `translate(${arc.centroid(d)})rotate(${this.computeTextRotation(d)})`)
.attr('dx', '-20')
.attr('dy', '.5em')
.text(d => d.parent ? d.data.name : '');
arc.centroid將計(jì)算arc的中心點(diǎn), rotate進(jìn)行角度的調(diào)整.
如果角度在120~270區(qū)間, 則字體要進(jìn)行旋轉(zhuǎn), 否則為顛倒的字:
computeTextRotation = (d) => {
const angle = (d.x0 + d.x1) / Math.PI * 90;
return (angle < 120 || angle > 270) ? angle : angle + 180;
}