正文
其实小程序上面也可以使用 echart 等开源图表库得,而且支持代码包得裁切功能,但是可能我不会用吧,效果不太好,而且我这就一个图,也没什么交互要写,就手撕了一个环形统计图,也算练习一下 canvas 2d 吧。
说到 canvas 2d 可真是头疼,微信官方不知道干嘛吃的,原接口不再维护了,但是 canvas 2d 得文档几乎没有更新,写起来摸不着头脑。如果你也对 canvas 2d 有疑惑,希望这个环形统计图能给你点帮助。
下面是 canvas 的官方文档,api使用也挺重要,可以先了解了解。
developers.weixin.qq.com/miniprogram…
developers.weixin.qq.com/miniprogram…
先看看效果
中间得环形图以及里面的文字就是通过 canvas 2d 绘制出来的,下面看代码。
看看代码
- WXML
{{item.title}} {{item.numb}} {{item.percent}}%
这里并不需要多少代码,但是 type 和 id 一定要,而且记得 class 指定宽高。
- WXSS
.chart { width: 112px; height: 112px; } .row{ display:flex; flex-direction:row; } .col{ display:flex; flex-direction:column; } .row-center{ display:flex; flex-direction:row; align-items: center; } .flex1{ flex: 1; } .center{ margin: auto; width: fit-content; } .circle { width: 18rpx; height: 18rpx; border-radius: 9rpx; box-sizing: border-box; } .project-item { font-family: PingFangSC-Regular, PingFang SC; font-weight: 400; color: #616161; line-height: 34rpx; margin-left: 8rpx; } .margin-left-16{ margin-left: 16rpx; } .font-size-12{ font-size: 24rpx; }
这里就是上面说的指定宽高了,暂时先用 px 作为单位,其他不知道会不会有问题。
- JS
Component({ properties: { show: { type: Boolean, value: false, observer: function (newVal, oldVal) { // 首次进来页面图标无法加载,监听页面切换来显示 let isFirstComeIn = this.data.isFirstComeIn if (isFirstComeIn) { this.getCanvas() this.data.isFirstComeIn = false } } } }, lifetimes: { attached: function () { // 初始化加载数据 this.getData() }, }, data: { // 画布相关 isFirstComeIn: true, context: null, height: 0, width: 0, // 图表数据 chartData: [{ title: '待检查项目', color: '#FF9000', numb: 0, percent: 0 }, { title: '进行中项目', color: '#1FD55C', numb: 0, percent: 0 }, { title: '已完成项目', color: '#0B7BFB', numb: 0, percent: 0 }, { title: '已终止项目', color: '#616161', numb: 0, percent: 0 }], } methods: { getCanvas() { // 有的手机下拉刷新会造成画两个不同大小的饼图 let that = this; let query = wx.createSelectorQuery().in(this) query.select('#myChart2d') .fields({ node: true, size: true }) .exec((res) => { const canvas = res[0].node const ctx = canvas.getContext('2d') const dpr = wx.getSystemInfoSync().pixelRatio canvas.width = res[0].width * dpr canvas.height = res[0].height * dpr ctx.scale(dpr, dpr) that.setData({ width: res[0].width * dpr, height: res[0].height * dpr, context: ctx }) // 首次进来画图 that.drawPieChart2d() }) }, // 下拉刷新 onPullDownRefresh() { this.getData() }, // 获取数据 getData() { app.request({ url: 'you/url', data: {}, finish: function () { wx.stopPullDownRefresh(); }, success: function (res) { let count = res.undoCount + res.doingCount + res.finishCount + res.stopCount let chartData = that.data.chartData if (count != 0) { chartData[0].numb = res.undoCount chartData[0].percent = (res.undoCount * 100 / count).toFixed(2) chartData[1].numb = res.doingCount chartData[1].percent = (res.doingCount * 100 / count).toFixed(2) chartData[2].numb = res.finishCount chartData[2].percent = (res.finishCount * 100 / count).toFixed(2) chartData[3].numb = res.stopCount chartData[3].percent = (res.stopCount * 100 / count).toFixed(2) } else { chartData[0].numb = 0 chartData[0].percent = 0 chartData[1].numb = 0 chartData[1].percent = 0 chartData[2].numb = 0 chartData[2].percent = 0 chartData[3].numb = 0 chartData[3].percent = 0 } that.setData({ chartData: chartData, }) // 因为本页作为组件隐藏了,首次进来无法获取canvas高度,首次进来另外处理 if (!that.data.isFirstComeIn) { that.drawPieChart2d() } } }) } // 一次性使用,前面是旧 canvas,注释的是一次性调用 canvas 2d 代码 drawPieChart() { // 组件中使用需要增加 this const ctx = wx.createCanvasContext('myChart', this); //设置半径 let radius = 56; let center = { x: 56, y: 56 }; // 设置数据、总数 let data = this.data.chartData let count = 0; data.forEach(element => { count += element.numb }); for (let i = 0; i < data.length; i++) { //计算占比,总长为 2PI let start = 0; for (let j = 0; j < i; j++) { start += data[j].numb / count * 2 * Math.PI } var end = start + data[i].numb / count * 2 * Math.PI ctx.beginPath() ctx.arc(center.x, center.y, radius, start, end) ctx.setLineWidth(1) ctx.lineTo(center.x, center.y) ctx.setStrokeStyle('#fff') ctx.setFillStyle(data[i].color) ctx.fill(); ctx.closePath(); ctx.stroke(); } ctx.beginPath() radius = 40; ctx.arc(center.x, center.y, radius, 0, 2 * Math.PI) ctx.setFillStyle('#fafafa') ctx.fill() ctx.closePath(); ctx.stroke(); ctx.fillStyle = "#2E2E2E"; ctx.setFontSize(20) ctx.setTextAlign('center') ctx.fillText('' + count, 56, 50); ctx.setFontSize(14) ctx.setTextAlign('center') ctx.fillText('评估项目数', 56, 70);; ctx.draw() // let query = wx.createSelectorQuery().in(this) // query.select('#myChart2d') // .fields({ // node: true, // size: true // }) // .exec((res) => { // const canvas = res[0].node // const ctx = canvas.getContext('2d') // const dpr = wx.getSystemInfoSync().pixelRatio // canvas.width = res[0].width * dpr // canvas.height = res[0].height * dpr // ctx.scale(dpr, dpr) // //设置半径 // let radius = 56; // let center = { // x: 56, // y: 56 // }; // // 设置数据、总数 // let data = this.data.chartData // let count = 0; // data.forEach(element => { // count += element.numb // }); // // 开始画图 // ctx.clearRect(0, 0, res[0].width * dpr, res[0].height * dpr) // for (let i = 0; i < data.length; i++) { // //计算占比,总长为 2PI // let start = 0; // for (let j = 0; j < i; j++) { // start += data[j].numb / count * 2 * Math.PI // } // var end = start + data[i].numb / count * 2 * Math.PI // ctx.beginPath() // ctx.arc(center.x, center.y, radius, start, end) // ctx.lineWidth = 1 // ctx.lineTo(center.x, center.y) // ctx.strokeStyle = '#fff' // ctx.fillStyle = data[i].color // ctx.closePath(); // ctx.fill(); // } // radius = 40; // ctx.beginPath() // ctx.arc(center.x, center.y, radius, 0, 2 * Math.PI) // ctx.fillStyle = '#fafafa' // ctx.closePath() // ctx.fill() // ctx.fillStyle = "#2E2E2E"; // ctx.font = "20px Arial"; // ctx.textAlign = 'center' // ctx.fillText('' + count, 56, 50) // ctx.font = "14px Arial"; // ctx.fillText('评估项目数', 56, 70) // }) }, drawPieChart2d() { let ctx = this.data.context //设置半径 let radius = 56; let center = { x: 56, y: 56 }; // 设置数据、总数 let data = this.data.chartData let count = 0; data.forEach(element => { count += element.numb }); // 开始画图 ctx.beginPath() ctx.clearRect(0, 0, this.data.width, this.data.height); for (let i = 0; i < data.length; i++) { //计算占比,总长为 2PI let start = 0; for (let j = 0; j < i; j++) { start += data[j].numb / count * 2 * Math.PI } var end = start + data[i].numb / count * 2 * Math.PI ctx.beginPath() ctx.lineWidth = 1 ctx.strokeStyle = '#fff' ctx.fillStyle = data[i].color ctx.arc(center.x, center.y, radius, start, end) ctx.lineTo(center.x, center.y) ctx.closePath(); ctx.fill(); } radius = 40; ctx.beginPath() ctx.fillStyle = '#fafafa' ctx.arc(center.x, center.y, radius, 0, 2 * Math.PI) ctx.closePath() ctx.fill() ctx.fillStyle = "#2E2E2E"; ctx.font = "20px Arial"; ctx.textAlign = 'center' ctx.fillText('' + count, 56, 50) ctx.font = "14px Arial"; ctx.fillText('评估项目数', 56, 70) }, } })
这里写的有些复杂了,但是复杂的东西能学到的也多吧,在组件中使用都掌握了,在 Page 中使用那就得心应手了,下面详细讲讲。
绘制图表
实际上绘制图表并不需要这么多的代码,在Page也好,在组件页面也好,其实只需要在需要绘制的时候调用上面 js 中 drawPieChart 代码即可,前面是旧版本的canvas,后面注释的是 canvas 2d的写法,可以对比看看,还是有些去别的,特别是字体大小坑了我一把。
但是为什么要写这么多代码呢?还是解决一些出现的问题,下面详细介绍。
解决问题
- 下拉刷新会造成画两个不同大小的饼图
问题很奇怪,而且只在某些机型出现。仔细研究一下发现这个问题是因为绘制图表的时候,多次调用一次性生成图表函数造成的,即每次获取到的 canvas 对象可能不太一样了,具体什么不一样了,我就没有仔细研究了,可能是页面发生了变化造成的。
这里的解决办法就是只获取一次 canvas,后面就用它不停的绘制图表,当绘需要制新的图表时,清空原来内容并绘制。首先提取出一个函数获取 canvas,这个函数要在 page 的 onReady中监听,这里再组件中也可以在 lifetimes 的 ready 方法中监听,都是一样的。获取到 canvas 对象后设置为全局变量,后面绘制的的时候取这个变量绘制就可以了。
这里我们把 getCanvas 写在了组件页面第一次显示时触发,原因看下面问题。
- 首次进来页面图表无法加载
这个问题是我们自定义的底部导航栏引入的,因为组件页面的出现后就被设置成了隐藏状态,所以 canvas 并没有获得到宽高,导致图表不显示。
解决办法就是在组件页面第一次显示的时候触发 getCanvas 函数,这里监听 show 属性的写法可以参考我前面自定义底部导航栏的博客,就不详述了。第一次显示的问题,用到了一个全局变量,一旦触发了,这个变量就永久设置为 false,使 getCanvas 函数不会再次执行。
同时,在第一次获取数据时因为 canvas 未获取到,应该暂时不绘制图表,当第一次进入页面后,拿到 canvas 对象了,再进行绘制。后面在拉取数据,例如下拉刷新,因为 canvas 已经获取到了,就不用特殊处理了。
getCanvas() { ... // 首次进来画图 that.drawPieChart2d() } getData() { ... // 因为本页作为组件隐藏了,首次进来无法获取canvas高度,首次进来另外处理 if (!that.data.isFirstComeIn) { that.drawPieChart2d() } }
- 图表数据处理
这里还碰到一个很奇怪的问题,就是我一开始把数据的百分比算成四位小数,在页面绑定的时候乘上100加上百分号再显示,按理来说应该显示小数点后两位的百分比值,可实际却是取小数点后两位并不生效,小数点后面取了十几位,可能时在页面计算的时候出了问题。
所以这里最好在 JS 中算好值,保留小数点后几位,再进行数据绑定。计算的时候,分母不为零千万别忘了。
结语
都说代码是最好的老师,canvas 2d的使用都在代码中蕴含了,这个图表用起来还是挺不错的。
以上就是canvas 2d 环形统计图手写实现示例的详细内容,更多关于canvas 2d 环形统计图的资料请关注脚本之家其它相关文章!