效果图
源码
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中我们需要手动设置绘图的宽度和高度. 这里我们也设置了饼状图的半径大小以及颜色(使用d3.schemeCategory10).
设定svg绘图区域
const g = d3.select('svg')
.attrs({ width, height })
.append('g')
.attr('transform', `translate(${width / 2},${height / 2})`);
设定svg的宽高后, 给予一个<g>用于绘图, 将其中心设置到绘图区域的中心位置.
格式化数据
const partition = d3.partition()
.size([2 * Math.PI, radius]);
d3.partition用于切割绘图区域. 这里由于是饼状图, 所以需要size函数设定其"宽高", 由于圆形的面积为2 * Math.PI * r, 所以2 * Math.PI为弧度, r为半径, 通过size函数则达到完全分割的状态.
const root = d3.hierarchy(nodeData)
.sum(d => d.size);
这里使用d3.hierarchy, 则nodeData的数据必须满足"父子层级"关系, 例如:
{
"name": "Eve",
"children": [
{
"name": "Cain"
},
{
"name": "Seth",
"children": [
{
"name": "Enos"
},
{
"name": "Noam"
}
]
},
{
"name": "Abel"
},
{
"name": "Awan",
"children": [
{
"name": "Enoch"
}
]
},
{
"name": "Azura"
}
]
}
根据sum函数来判断子节点的大小顺序. 经过partition(root)后, root会演化成一个可供计算的父子结构:
计算弧度及长度
const arc = d3.arc()
.startAngle(d => d.x0)
.endAngle(d => d.x1)
.innerRadius(d => d.y0)
.outerRadius(d => d.y1);
由于经过partition后, root已经保存了每个节点的信息, 这里d.x0为开始的角度, d.x1为结束的角度. d.y0为开始的长度, d.y1为结束的长度.
绘图
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))
通过root.descendants(), 我们将数据按size的大小顺序传入. 通过arc, 进行绘制图形, 而填充的颜色由name决定, 同一个父结构下的图形具有相同的颜色.
添加文本
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将计算arc的中心点, rotate进行角度的调整.
如果角度在120~270区间, 则字体要进行旋转, 否则为颠倒的字:
computeTextRotation = (d) => {
const angle = (d.x0 + d.x1) / Math.PI * 90;
return (angle < 120 || angle > 270) ? angle : angle + 180;
}