图表是数据表示的一种直观的形式,前端开发经常回合图表打交道,一般都会借用第三方的js库,如echats、bizchats等,
但如果只是很简单的图标,我们完全可以自己绘制,现在我们来绘制一个简单的百分比展示图表
最终效果如下
html代码
<canvas id="charts"></canvas>
额,很简单,随便放在html某个地方
分析
从最终效果来看,这个图表由6部分组成
1.最大的外圈
2.最中心向外的内二圈
3.最里面的内圈
4.带颜色的多个圆弧组成的彩圈
5.中心的指针
6.数字文字
下来就按照顺序绘制出来
配置
首先还是写一些可配置的变量
let r = 100, //半径
cx = 0, //圆心位置
cy = 0,
parts = [ //彩条
'#78C77C','#F3D263','#FC9136','#FB574A','#AE1A51','#8F66DD'
],
texts = [ //文字段落
0,30,60,90,120,150
],
pad = 1/100, //间隙
data = 0.5 //数据,百分比,20%应写成0.2
初始化
let canvas = document.getElementById('chart')
canvas.width = 262;
canvas.height = 262;
let ctx = canvas.getContext('2d')
ctx.translate(canvas.width/2,canvas.height/2);
ctx.save()
ctx.rotate(0.5*Math.PI)
为什么要旋转0.5π呢,因为为了便于计算,需要把圆的起始位置转至最下
绘制-外圈
ctx.beginPath()
ctx.arc(cx,cy,r+30,0,2*Math.PI)
ctx.fillStyle="#fff"
ctx.strokeStyle="gray"
ctx.shadowOffsetX = 0; // 阴影Y轴偏移
ctx.shadowOffsetY = 0; // 阴影X轴偏移
ctx.shadowBlur = 1; // 模糊尺寸
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)'; // 颜色
ctx.stroke();
ctx.fill();
效果
绘制-内二圈
ctx.beginPath()
ctx.arc(cx,cy,r-20,0,2*Math.PI)
ctx.fillStyle="#F2F1F3"
ctx.fill();
效果
注意
为什么不先绘制最内层的圈,因为canvas的层级关系是后来者居上
,所以有重叠
的部分最上面的图形最后绘制
绘制-内圈
ctx.beginPath()
ctx.arc(cx,cy,r-20-30,0,2*Math.PI)
ctx.fillStyle="#fff"
ctx.strokeStyle="#fff"
ctx.stroke();
ctx.fill();
效果
绘制-彩圈
这里可以理解为多个同心圆弧,把2π等分,中间有相同的间隔
let startRad = 0
let partRad = ( 2 - (pad*parts.length) ) / parts.length
parts.forEach((color,index)=>{
if (index===0) {
startRad += pad/2
} else {
startRad += pad + partRad
}
ctx.beginPath()
ctx.strokeStyle = color
ctx.lineWidth = 20
ctx.arc(cx,cy,r,startRad * Math.PI,(startRad+partRad)*Math.PI)
ctx.stroke()
})
ctx.restore();
注意
1.等分2π的时候别忘了间隔
2.下一个圆弧的起始点是上一个圆弧的终点加上间隔
效果
绘制-指针
ctx.save();
ctx.rotate(data*2*Math.PI)
ctx.beginPath()
ctx.moveTo(-6,0)
ctx.lineTo(0,r-30)
ctx.lineTo(6,0)
ctx.closePath();
ctx.fillStyle = '#4394F8';
ctx.fill();
ctx.beginPath()
ctx.arc(0,0,10,0,2*Math.PI)
ctx.fillStyle = '#F4F6F9';
ctx.strokeStyle = 'gray'
ctx.strokeWidth = 1
ctx.shadowOffsetX = 0; // 阴影Y轴偏移
ctx.shadowOffsetY = 0; // 阴影X轴偏移
ctx.shadowBlur = 10; // 模糊尺寸
ctx.shadowColor = '#F4F6F9'; // 颜色
ctx.stroke()
ctx.fill();
ctx.restore();
注意
1.指针由2部分构成,中心的圆形
和三角形
2.注意是圆形在上,三角形在下,所以最后绘制圆形
效果
绘制-文字
ctx.save()
ctx.rotate(0.5*Math.PI)
let beginRotate = 0,
textPartRad = 2/(texts.length)
texts.forEach((text,index)=>{
if (index!=0) {
beginRotate = beginRotate + textPartRad
}
ctx.save();
ctx.beginPath()
ctx.font = "15px"
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.translate(cx+Math.cos(beginRotate*Math.PI)*(r-35),cy+Math.sin(beginRotate*Math.PI)*(r-35));
ctx.rotate(beginRotate*Math.PI+(-0.5*Math.PI))
ctx.rotate(Math.PI)
ctx.fillText(text,0,0)
ctx.restore();
})
ctx.restore();
注意
1.每个文字先放到最中间(0,0)
2.然后根据圆的半径
和文字放置地点的旋转角度
,通过三角函数
计算出位置,然后移动
3.根据旋转角度将文字旋转到合适位置
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Chart</title>
</head>
<body>
<canvas id="chart"></canvas>
</body>
<script type="text/javascript">
let r = 100, //半径
cx = 0, //圆心位置
cy = 0,
parts = [ //彩条
'#78C77C','#F3D263','#FC9136','#FB574A','#AE1A51','#8F66DD'
],
texts = [ //文字段落
0,35,75,115,130,250
],
pad = 1/100, //间隙
data = 0.7 //数据,百分比,20%应写成0.2
let canvas = document.getElementById('chart')
canvas.width = 262;
canvas.height = 262;
let ctx = canvas.getContext('2d')
ctx.translate(canvas.width/2,canvas.height/2);
ctx.save()
ctx.rotate(0.5*Math.PI)
//最大的外圈
ctx.beginPath()
ctx.arc(cx,cy,r+30,0,2*Math.PI)
ctx.fillStyle="#fff"
ctx.strokeStyle="gray"
ctx.shadowOffsetX = 0; // 阴影Y轴偏移
ctx.shadowOffsetY = 0; // 阴影X轴偏移
ctx.shadowBlur = 1; // 模糊尺寸
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)'; // 颜色
ctx.stroke();
ctx.fill();
//内二圈
ctx.beginPath()
ctx.arc(cx,cy,r-20,0,2*Math.PI)
ctx.fillStyle="#F2F1F3"
ctx.fill();
//内圈
ctx.beginPath()
ctx.arc(cx,cy,r-20-30,0,2*Math.PI)
ctx.fillStyle="#fff"
ctx.strokeStyle="#fff"
ctx.stroke();
ctx.fill();
// 彩圈
let startRad = 0
let partRad = ( 2 - (pad*parts.length) ) / parts.length
parts.forEach((color,index)=>{
if (index===0) {
startRad += pad/2
} else {
startRad += pad + partRad
}
ctx.beginPath()
ctx.strokeStyle = color
ctx.lineWidth = 20
ctx.arc(cx,cy,r,startRad * Math.PI,(startRad+partRad)*Math.PI)
ctx.stroke()
})
ctx.restore();
//指针
ctx.save();
ctx.rotate(data*2*Math.PI)
ctx.beginPath()
ctx.moveTo(-6,0)
ctx.lineTo(0,r-30)
ctx.lineTo(6,0)
ctx.closePath();
ctx.fillStyle = '#4394F8';
ctx.fill();
ctx.beginPath()
ctx.arc(0,0,10,0,2*Math.PI)
ctx.fillStyle = '#F4F6F9';
ctx.strokeStyle = 'gray'
ctx.strokeWidth = 1
ctx.shadowOffsetX = 0; // 阴影Y轴偏移
ctx.shadowOffsetY = 0; // 阴影X轴偏移
ctx.shadowBlur = 10; // 模糊尺寸
ctx.shadowColor = '#F4F6F9'; // 颜色
ctx.stroke()
ctx.fill();
ctx.restore();
// 文字
ctx.save()
ctx.rotate(0.5*Math.PI)
let beginRotate = 0,
textPartRad = 2/(texts.length)
texts.forEach((text,index)=>{
if (index!=0) {
beginRotate = beginRotate + textPartRad
}
ctx.save();
ctx.beginPath()
ctx.font = "15px"
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.translate(cx+Math.cos(beginRotate*Math.PI)*(r-35),cy+Math.sin(beginRotate*Math.PI)*(r-35));
ctx.rotate(beginRotate*Math.PI+(-0.5*Math.PI))
ctx.rotate(Math.PI)
ctx.fillText(text,0,0)
ctx.restore();
})
ctx.restore();
</script>
</html>
还有许多可以优化的地方,如代码中颜色,局部位置的距离等等,可以做成配置项,有时间在优化吧^_^
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。