圖表是數據表示的一種直觀的形式,前端開發常常回合圖表打交道,通常都會借用第三方的js庫,如echats、bizchats等,
但若是隻是很簡單的圖標,咱們徹底能夠本身繪製,如今咱們來繪製一個簡單的百分比展現圖表javascript
<canvas id="charts"></canvas>
額,很簡單,隨便放在html某個地方html
從最終效果來看,這個圖表由6部分組成
1.最大的外圈
2.最中心向外的內二圈
3.最裏面的內圈
4.帶顏色的多個圓弧組成的彩圈
5.中心的指針
6.數字文字
前端
下來就按照順序繪製出來java
首先仍是寫一些可配置的變量canvas
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π等分,中間有相同的間隔spa
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.下一個圓弧的起始點是上一個圓弧的終點加上間隔3d
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>
還有許多能夠優化的地方,如代碼中顏色,局部位置的距離等等,能夠作成配置項,有時間在優化吧^_^