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执行时不会显示的,因为当时的画布并没有加载完成