上一篇: 河流图 https://blog.csdn.net/zjw_python/article/details/98592543
下一篇: 桑基图 https://blog.csdn.net/zjw_python/article/details/98611559
代码结构和初始化画布的Chart对象介绍,请先看 https://blog.csdn.net/zjw_python/article/details/98182540
本图完整的源码地址: https://github.com/zjw666/D3_demo/tree/master/src/gauge/basicGauge
1 图表效果
2 数据
仪表盘图的数据很简单,就是一个值,其被包含到config
配置中了,没有单独的数据文件
3 关键代码
一些样式配置、数值的范围、仪表盘的弧度分段等
const config = {
margins: {top: 80, left: 80, bottom: 50, right: 80},
textColor: 'black',
title: '仪表盘',
totalAngle: 270,
totalValue: 100,
showValue: 90,
width: 25,
domain: [0, 20, 80, 100],
lineColor: 'white',
animateDuration: 500
}
尺度转换,根据画布大小计算仪表盘的宽度
/* ----------------------------计算半径----------------------------- */
const radius = d3.min([chart.getBodyWidth()*0.95, chart.getBodyHeight()*0.95])/2;
渲染仪表盘轮廓,总体上就是一个具有内半径的大角度扇形,计算一下角度,用arc
渲染即可
/* ----------------------------渲染弧形仪表盘轮廓------------------------ */
chart.renderSlices = function(){
const drawAngles = config.domain.map((value, i, domain) => {
const angle = value / config.totalValue * config.totalAngle;
if (i !== domain.length-1){
return {
startAngle: (angle - config.totalAngle/2) * Math.PI/180,
endAngle: (domain[i+1] / config.totalValue * config.totalAngle - config.totalAngle/2) * Math.PI/180
}
}
});
drawAngles.pop();
const arc = d3.arc()
.outerRadius(radius)
.innerRadius(radius - config.width > 0 ? radius - config.width : 10);
const slices = chart.body()
.append('g')
.attr('transform', 'translate(' + chart.getBodyWidth()/2 + ',' + chart.getBodyHeight()/2 + ')')
.selectAll('.arc')
.data(drawAngles);
slices.enter()
.append('path')
.attr('class', (d,i) => 'arc arc-' + i)
.merge(slices)
.attr('fill', (d,i) => chart._colors(i % 10))
.attr('d', arc);
slices.exit()
.remove();
}
轮廓渲染好了,下一步该渲染坐标轴和文本标签,这里通过三角函数计算角度,然后直接渲染,比较简单,注意坐标轴的长度变化即可
/* -----------------------渲染环形坐标轴和标签------------------------ */
chart.renderTicks = function(){
const drawAngles = [];
for (let i=-config.totalAngle/2; i<=config.totalAngle/2+0.01; i+=config.totalAngle/50){
drawAngles.push(i * Math.PI/180);
}
const ticks = chart.body()
.append('g')
.attr('transform', 'translate(' + chart.getBodyWidth()/2 + ',' + chart.getBodyHeight()/2 + ')')
.selectAll('.ticks')
.data(drawAngles);
ticks.enter()
.append('g')
.attr('class', 'ticks')
.merge(ticks)
.each(drawTicks)
.each(drawLabels);
function drawTicks(d, i){
if (i === 0 || i === 50) return;
const innerRadius = (i % 5===0 ? radius-config.width : radius-config.width/3)
d3.select(this)
.append('line')
.attr('stroke', config.lineColor)
.attr('x1', Math.sin(d) * radius)
.attr('y1', -Math.cos(d) * radius)
.attr('x2', Math.sin(d) * innerRadius)
.attr('y2', -Math.cos(d) * innerRadius)
}
function drawLabels(d, i){
let textAnchor = 'end';
if (i === 25) textAnchor = 'middle';
if (i % 5 === 0){
const textRadius = radius - config.width - 10;
d3.select(this)
.append('text')
.attr('class', 'label')
.attr('x', Math.sin(d) * textRadius)
.attr('y', -Math.cos(d) * textRadius)
.attr('dy', 5.5)
.attr('stroke', config.textColor)
.attr('text-anchor', d<0?'start':textAnchor)
.text(i/50 * config.totalValue);
}
}
}
然后最后一步渲染指针,这里的指针是一个左右对称四边形,用polygon
渲染即可,通过控制其旋转角度显示指定的值,注意实现动画过渡效果,要保证指针的旋转方向正确
/* --------------------------渲染指针-------------------------- */
chart.renderPointer = function(){
const verticalLongOffset = Math.floor((radius-config.width-10) * 0.8);
const verticalShortOffset = Math.floor(verticalLongOffset * 0.12);
const horizontalOffset = Math.floor(verticalShortOffset * 0.6);
const points = [
"0," + verticalShortOffset,
horizontalOffset + ",0",
"0," + (-verticalLongOffset),
-horizontalOffset + ",0"
].join(" ");
const pointer = chart.body()
.selectAll(".pointer")
.data([config.showValue]);
pointer.enter()
.append('polygon')
.attr('class', 'pointer')
.attr('points', points)
.attr('shape-rendering', 'geometricPrecision')
.attr('stroke', 'none')
.attr('transform', 'translate(' + chart.getBodyWidth()/2 + ',' + chart.getBodyHeight()/2 + ') ' + 'rotate(' + (-0.5)*config.totalAngle +')')
.merge(pointer)
.attr('fill', (d) => {
let i = 0;
while (i<config.domain.length-1 && config.domain[i] < d){i++}
return chart._colors((i-1) % 10);
})
.transition().duration(config.animateDuration)
.attrTween('transform', rotateTween);
pointer.exit()
.remove();
function rotateTween(d){
let lastAngle = this.last || 0;
let angleDiff = d - lastAngle;
this.last = d;
return function(t){
return 'translate(' + chart.getBodyWidth()/2 + ',' + chart.getBodyHeight()/2 + ') ' + 'rotate(' + ((lastAngle + angleDiff*t)/config.totalValue-0.5)*config.totalAngle +')';
}
}
}