上一篇: 环状饼图 https://blog.csdn.net/zjw_python/article/details/98480632
下一篇: 基础散点图 https://blog.csdn.net/zjw_python/article/details/98483109
代码结构和初始化画布的Chart对象介绍,请先看 https://blog.csdn.net/zjw_python/article/details/98182540
本图完整的源码地址: https://github.com/zjw666/D3_demo/tree/master/src/pieChart/nightingale
date,money
Mon,120
Tue,200
Wed,150
Thu,80
Fri,70
Sat,110
Sun,130
导入数据,转换为对象数组
d3.csv('./data.csv', function(d){
return {
date: d.date,
money: +d.money
};
}).then(function(data){
一些样式参数配置,由于南丁格尔图是以扇形半径作为映射点,因此扇形外半径不再是定值
const config = {
margins: {top: 80, left: 80, bottom: 50, right: 80},
textColor: 'black',
title: '南丁格尔图',
innerRadius: 0,
textOffsetH: 10,
lineColor: 'black',
animateDuration: 1000
}
尺度转换,与基础饼图相比,新增一个半径值得转换函数
/* ----------------------------尺度转换------------------------ */
chart.arcAngle = d3.pie()
.sort((d,i) => i)
.value((d) => d.money);
chart.scaleRadius = d3.scaleLinear()
.domain([0, d3.max(data.map((d) => d.money))])
.range([0, d3.min([chart.getBodyWidth(), chart.getBodyHeight()]) * 0.5])
渲染扇形,与基础饼图相比,南丁格尔图新增一个扇形半径的映射点,在这里,我们动画效果以半径的变化为特征,适当修改中间帧函数中的代码
/* ----------------------------渲染扇形------------------------ */
chart.renderSlices = function(){
const slices = chart.body().append('g')
.classed('pie', true)
.attr('transform', 'translate(' + chart.getBodyWidth()/2 + ',' + chart.getBodyHeight()/2 + ')')
.selectAll('.arc')
.data(chart.arcAngle(data));
slices.enter()
.append('path')
.attr('class', (d,i) => 'arc arc-' + i)
.merge(slices)
.attr('fill', (d,i) => chart._colors(i))
.transition().duration(config.animateDuration)
.attrTween("d", arcTween)
slices.exit()
.remove();
function arcTween(d){
let currentR = this._current;
if (!currentR){
currentR = 0
}
const interpolate = d3.interpolate(currentR, chart.scaleRadius(d.value));
this._current = interpolate(1); //当饼图更新时,从当前半径到新半径
return function(t){
let arc = d3.arc()
.outerRadius(interpolate(t))
.innerRadius(config.innerRadius);
return arc(d);
}
}
}
渲染文本标签及连线,与基础饼图相比,值得注意的是,由于各扇形的外半径不一致,因此我们连线的长度也要根据对应的半径大小做出相应调整,在此我们使用sqrt
函数,半径大的扇形,文本标签的连线反而短
/* ----------------------------渲染文本标签和线条------------------------ */
chart.renderText = function(){
// ----渲染文本标签-----
const scaleTextDx = d3.scaleLinear()
.domain([0, Math.PI/2])
.range([config.textOffsetH, config.textOffsetH * 3]);
const labels = d3.select('.pie')
.selectAll('.label')
.data(chart.arcAngle(data));
labels.enter()
.append('text')
.classed('label', true)
.merge(labels)
.attr('stroke', config.textColor)
.attr('fill', config.textColor)
.attr('text-anchor', (d) => {
return (d.endAngle + d.startAngle)/2 > Math.PI ? 'end' : 'start';
})
.attr('dy', '0.35em')
.attr('dx', computeTextDx)
.transition().duration(0).delay(config.animateDuration)
.attr('transform', (d) => {
return 'translate(' + getArcCentorid(chart.scaleRadius(d.value)*2.5, d, true) + ')'
})
.text((d) => d.data.date+': '+d.data.money);
labels.exit()
.remove();
// ----渲染标签连线-----
const points = getLinePoints();
const generateLine = d3.line()
.x((d) => d[0])
.y((d) => d[1]);
const lines = d3.select('.pie')
.selectAll('.line')
.data(points);
lines.enter()
.insert('path',':first-child')
.classed('line', true)
.merge(lines)
.transition().duration(0).delay(config.animateDuration)
.attr('fill', 'none')
.attr('stroke', config.lineColor)
.attr('d', generateLine);
lines.exit()
.remove();
function computeTextDx(d){ //计算文本水平偏移
const middleAngle = (d.endAngle + d.startAngle)/2;
let dx = ''
if (middleAngle < Math.PI){
dx = scaleTextDx(Math.abs(middleAngle - Math.PI/2));
}else{
dx = -scaleTextDx(Math.abs(middleAngle - Math.PI*3/2));
}
return dx;
}
function getLinePoints(){ //生成连线的点
return chart.arcAngle(data).map((d) =>{
const radius = chart.scaleRadius(d.value);
const line = [];
const tempPoint = getArcCentorid(radius * 2.5, d, true);
const tempDx = computeTextDx(d);
const dx = tempDx > 0 ? tempDx - config.textOffsetH : tempDx + config.textOffsetH;
line.push(getArcCentorid(radius, d));
line.push(tempPoint);
line.push([tempPoint[0] + dx, tempPoint[1]]);
return line;
})
}
function getArcCentorid(outerRadius, d, averageLength){ //获取指定半径弧的中心点,并可以均一化长度
if (averageLength) outerRadius = Math.sqrt(outerRadius*300);
return d3.arc()
.outerRadius(outerRadius)
.innerRadius(config.innerRadius)
.centroid(d);
}
}
最后绑定鼠标交互事件,鼠标悬停时,扇形略微变大
/* ----------------------------绑定鼠标交互事件------------------------ */
chart.addMouseOn = function(){
d3.selectAll('.arc')
.on('mouseover', function(d){
const e = d3.event;
d3.select(e.target)
.attr('d', generateArc(chart.scaleRadius(d.value)*1.1));
})
.on('mouseleave', function(d){
const e = d3.event;
d3.select(e.target)
.attr('d', generateArc(chart.scaleRadius(d.value)));
});
function generateArc(outerRadius){
return d3.arc()
.outerRadius(outerRadius)
.innerRadius(config.innerRadius);
}
}