uni-app 之canvas繪制餅狀圖
一開始,對於canvas我是拒絕的,后來,我發現我愛上了它,像愛上小哥哥一樣~~
說起canvas,是css3新增的標簽。而餅狀圖又是canvas經典,我們公司現在正在用uni-app框架去研發APP,平常我們使用canvas標簽時,只需在HTML中增加一個canvas標簽,然后再script中獲取標簽屬性,var canvas = document.getElementById('cavsElem') 就可以了!但是,咱們也說了,使用uni-app,有過了解的人也知道,咱們uni-app是不支持document和window對象的,所以呢uni-app官網給我們提供了一個API --uni.createCanvasContext,他會創建一個畫布,你要定義他的寬高,給這個畫布一個id,然后你就可以隨心多欲了~~~
我們想要的圖是這樣的:
我之前在調研這個canvas的時候,發現大家都做了那個就是解釋說明,我就在延展的地方給大家寫出來了,emmmmm,假如我有這么多錢,買了這么多化妝品~~
這個canvas把,我結合一下我當初調研的時候的感覺,就感覺還是給大家直接上代碼比較好,解說什么的當時我都看不懂,我就一步一步的跟大家解釋一下,讓大家拿下他!!!像拿下心儀的小哥哥一樣!!!加油,我們是最棒的!!(什么鬼~)
我們是在vue項目中寫的 , 首先我先在一個vue組件中使用了canvas標簽:
定義canvas的大小寬高,給canvas定義一個id,在獲取的時候能用到,我給了一個class樣式,就是想讓他居中的 ,,可忽略
<canvas style="width: 350px; height: 300px;" canvas-id="homeownerCanvas" class="homeowner-canvas_charts"></canvas>
其次我在js中定義了所需要展示的數據,類型,所占比例的:
let data = [{
"money": 30 + '萬',
"value": 0.3,
"color": "#afb4db",
"title": "口紅"
},
{
"money": 20 + '萬',
"value": 0.2,
"color": "#ffce7b",
"title": "眼影"
},
{
"money": 30 + '萬',
"value": 0.3,
"color": "#f8aba6",
"title": "粉底"
},
{
"money": 20 + '萬',
"value": 0.2,
"color": "#afdfe4",
"title": "眉筆"
}
];
這個東西我就不詳細解釋了 ,他這個的意思就是說定義要顯示的數據,比如你看到的金錢,顏色(我看別人家的顏色有的是生成的,我就直接定義了顏色,畢竟咱們主題不是他),比值(比值這個問題就是說你自己當前的這個金錢在你所有的金錢中所占的比例,說白了就是1/10 = 0.1的事情),還有他的title? 就是他這個塊是什么東西啦~~·
然后就是在vue組件中引用關於canvas的js文件,現在vue組件中調用 :
字段說明~參數一: canvasID 參數二:定義好的data 參數三:總數(自己可以計算啦~我直接寫死了)這個是便於以后有別的組件也會使用canvas這個js文件,所以把會使用到的數據當做參數傳送過去
canvas.canvasGraph('homeownerCanvas',data,100)
接下來就是重頭戲 :canvas這個js文件了!!
// 圖表封裝 export default{ canvasGraph(canvasID,data,summation){ function PieChart(ctx,radius){ //定義起始角度 let tempAngle=0; //定圓心位置 let x0=182,y0=150;
//伸出長度 let outLine = 18; PieChart.prototype.init = function(data){ this.drawPie(data); }; // 繪畫扇形 及中心圓 PieChart.prototype.drawPie = function(data){ for (let i = 0; i < data.length; i++) { // 開始一個新路徑 ctx.beginPath(); // 移動到中心點 ctx.moveTo(x0,y0); //計算當前扇形角度 所占比例*360 let angle = data[i].value*360; //當前扇形起始繪制弧度 360 = 2π 等於6.28 let startAngle = tempAngle*Math.PI/180; //當前扇形借結束繪制弧度 let endAngle = (tempAngle + angle)*Math.PI/180; //繪制扇形 x y中心 半徑 開始弧度 結束弧度 ctx.arc(x0,y0,radius,startAngle,endAngle); //填充扇形 ctx.fillStyle = data[i].color; // 填充 ctx.fill(); // 調用添加標題解釋方法 this.drawTitle(startAngle,angle,data[i].color, data[i].title + data[i].money) //當前扇形結束繪制角度,即下一個扇形開始繪制角度 tempAngle += angle; } // 開始一個新路徑 ctx.beginPath(); // 開始畫圓 ctx.arc(x0, y0, 65, 0, 2 * Math.PI) // 填充顏色 白色 ctx.setFillStyle('white') // 調用繪制中心圓文字方法 this.drawCenterTitle() } // 伸出線條方法 PieChart.prototype.drawTitle = function(startAngle,angle,color,title){ // 伸出去的長度 斜邊長度 等於半徑加上定義好的長度 let out = radius; // 當前弧度的二分之一 let du = startAngle+(angle/2)*Math.PI/180; // 計算伸出的點x坐標 let outX = x0+out*Math.cos(du); // 計算伸出的點y坐標 let outY = y0+out*Math.sin(du); // 開始一個新路徑 ctx.beginPath(); // 移動到中心點 ctx.moveTo(x0,y0); // 畫出點到伸出點的一條線 ctx.lineTo(outX,outY); // 線條顏色 ctx.strokeStyle = color; //設置標題 ctx.font = 'bold 14px Microsoft Yahei'; // 計算出標題文字寬度 let textWidth = ctx.measureText(title).width; // 計算標題樣式 ctx.textBaseline = "bottom"; // 刷新配置項 象限判斷 與 對應符號 let optionArr=[ { quadrant:outX>x0 && outY<y0, symbol:['+','-','left'] }, { quadrant:outX<x0 && outY<y0, symbol:['-','-','right'] }, { quadrant:outX<x0 && outY>y0, symbol:['-','+','right'] }, { quadrant:outX>x0 && outY>y0, symbol:['+','+','left'] } ] // 渲染的配置項 let {symbol} = optionArr.find(el=>el.quadrant&&el.symbol) // 斜線起始點 let slashState = eval(outX+symbol[0]+outLine) // 橫線起始點 let lineState = eval(outX+symbol[0]+textWidth+symbol[0]+outLine) // 終點 let lineEnd = eval(outY+symbol[1]+outLine) // 標題文字樣式 ctx.textAlign = symbol[2]; // 畫出伸出的斜線 ctx.lineTo(slashState,lineEnd); // 接上斜線畫出標題下面的直線 ctx.lineTo(lineState,lineEnd); // 填充標題 ctx.fillText(title,slashState,lineEnd); // 填充 ctx.stroke(); } // 繪制中心文字 PieChart.prototype.drawCenterTitle = function(){ // 填充 ctx.fill(); // 文字大小 ctx.setFontSize(24) // 文字顏色 ctx.fillStyle = "#333333" // 文字位置 ctx.setTextAlign('center') // 插入文字 ctx.fillText(`${summation}萬`, x0, y0+5) // 文字大小 ctx.setFontSize(14) // 合計字體樣式 ctx.font = '14px Microsoft Yahei'; // 文字顏色 ctx.fillStyle = "#999999" // 插入文字 ctx.fillText('合計(元)', x0, y0+30) // 開始畫圖 ctx.draw() } } var ctx = uni.createCanvasContext(canvasID) var PieChart = new PieChart(ctx,90) PieChart.init(data) } }
詳細解析:
我使用了構造函數的原型添加方式
//根據canvasID 獲取當前傳參的canvas信息
//定義起始角度(決定你是從哪里開始畫) let tempAngle=0; //定圓心位置 let x0=100,y0=150;
//定義伸出長度(指的是解釋說明伸出的長度) let outLine = 18;
使用的原型的方法:
PieChart.prototype.init = function(data){} ------集合調用初始化函數創建對象后 參數為傳參的data
PieChart.prototype.drawPie = function(data){} ------畫餅狀圖的扇形和畫白色小圓的方法
PieChart.prototype.drawTitle = function(startAngle,angle,color,title){} -----畫伸出的線條和文字
PieChart.prototype.drawCenterTitle = function(){} ---畫白色圓上的文字
來張圖詳細解析

這樣的解釋可還ok? 哈哈哈哈哈~~~~
接下來就是這個方法的分析
主要呢就是這幾個方法,不過呢,建議大家先去看一下這個canvas基本的方法什么的,要不然就會像我最開始接觸的時候一團懵,不知道都是干啥的,大家可以先去了解一下方法都是什么意思,這樣在捋的時候及不會亂了
PieChart.prototype.drawPie = function(data){} 繪畫扇形與白色圓
PieChart.prototype.drawPie = function(data){
for (let i = 0; i < data.length; i++) {
// 開始一個新路徑
ctx.beginPath();
// 移動到中心點
ctx.moveTo(x0,y0);
//計算當前扇形角度 所占比例*360
let angle = data[i].value*360;
//當前扇形起始繪制弧度 360 = 2π 等於6.28
let startAngle = tempAngle*Math.PI/180;
//當前扇形借結束繪制弧度
let endAngle = (tempAngle + angle)*Math.PI/180;
//繪制扇形 x y中心 半徑 開始弧度 結束弧度
ctx.arc(x0,y0,radius,startAngle,endAngle);
//填充扇形
ctx.fillStyle = data[i].color;
// 填充
ctx.fill();
// 調用添加標題解釋方法
this.drawTitle(startAngle,angle,data[i].color, data[i].title + data[i].money)
//當前扇形結束繪制角度,即下一個扇形開始繪制角度
tempAngle += angle;
}
// 開始一個新路徑
ctx.beginPath();
// 開始畫圓
ctx.arc(x0, y0, 65, 0, 2 * Math.PI)
// 填充顏色 白色
ctx.setFillStyle('white')
// 調用繪制中心圓文字方法
this.drawCenterTitle()
}
這里的知識點就是咱們關於弧度與角度之間的換算 1弧度=180/π 1度=π/180 方法主要是寫了 根據傳入data中value所代表的百分比計算出各自所占有的角度,在通過換算計算出所占弧度,先計算出開始弧度,再計算結束弧度,畫出扇形,然后遍歷data,畫出各自的扇形,一定切記,當你要畫一個新的扇形或者別的形狀的時候,一定要開啟一個新路徑,白色的小圓同樣是這樣,開啟一個新路徑然后用畫圓的方法畫個圓,另外在方法里面還調用了繪制標題的drawTitle()和繪制中心圓文字drawCenterTitle()方法
PieChart.prototype.drawTitle = function(startAngle,angle,color,title){} @param {startAngle,angle,color,title} 開始弧度 當前角度 顏色 標題
自我感覺這是個重點! 我當初在調研,在看別人寫的時候入了一個不小的坑 ,后來仔細一算 發現他們寫錯了 我在這里給大家指正一下, 來張圖片解釋一下~~ 要是有看不懂的,歡迎大家提出來,我會給大家解釋的,嗯嗯嗯(若是看不清楚請見上面詳細代碼)

PieChart.prototype.drawCenterTitle = function(){} 繪制中心文字
PieChart.prototype.drawCenterTitle = function(){
// 填充
ctx.fill();
// 文字大小
ctx.setFontSize(24)
// 文字顏色
ctx.fillStyle = "#333333"
// 文字位置
ctx.setTextAlign('center')
// 插入文字
ctx.fillText(`${summation}萬`, x0, y0+5)
// 文字大小
ctx.setFontSize(14)
// 合計字體樣式
ctx.font = '14px Microsoft Yahei';
// 文字顏色
ctx.fillStyle = "#999999"
// 插入文字
ctx.fillText('合計(元)', x0, y0+30)
// 開始畫圖
ctx.draw()
}
具體方法就是這樣了,如果你們有什么不會的或者還沒有懂得,歡迎留言,或者你們想要什么效果的,都可以留言,大家一起交流,一起拿下“小哥哥”!!!
更正:由於當時的考慮不周的原因,發現當如果在你的data中有比例為零時,會不顯示canvas圖表,會報出一個'symbol is not defined' 的問題,后來尋找了一下錯誤點,發現了自己在象限的判斷中考慮的並不周到,沒有考慮到0的原因 ,故象限判斷做出如下更改,其他不變:
// 象限判斷
let optionArr=[
{
quadrant:outX>=x0 && outY<=y0,
symbol:['+','-','left']
},
{
quadrant:outX<x0 && outY<=y0,
symbol:['-','-','right']
},
{
quadrant:outX<x0 && outY>y0,
symbol:['-','+','right']
},
{
quadrant:outX>=x0 && outY>y0,
symbol:['+','+','left']
}
]
注意:另外之前有人扣扣私聊我說他按照我這個代碼寫的,但是並沒有顯示圖表,后來他把代碼發給我,我發現他把canvas畫圖方法放在了onLoad上,后來給他放到onReady就顯示了,在這里我講一下為什么、
onReady要比onLoad先執行,onLoad必須等到頁面內包括圖片的所有元素加載完畢之后才能執行,而onReady不需要,canvas說白了就是一張畫布,所以在onLoad執行時不會顯示的,因為當時的畫布並沒有加載完成
