canvas 2d 环形统计图手写实现示例
正文
其实小程序上面也可以使用 echart 等开源图表库得,而且支持代码包得裁切功能,但是可能我不会用吧,效果不太好,而且我这就一个图,也没什么交互要写,就手撕了一个环形统计图,也算练习一下 canvas 2d 吧。
说到 canvas 2d 可真是头疼,微信官方不知道干嘛吃的,原接口不再维护了,但是 canvas 2d 得文档几乎没有更新,写起来摸不着头脑。如果你也对 canvas 2d 有疑惑,希望这个环形统计图能给你点帮助。
下面是 canvas 的官方文档,api使用也挺重要,可以先了解了解。
developers.weixin.qq.com/miniprogram…
developers.weixin.qq.com/miniprogram…
先看看效果
中间得环形图以及里面的文字就是通过 canvas 2d 绘制出来的,下面看代码。
看看代码
- WXML
<view class="row chart-container">
<canvas type="2d" class="chart" id="myChart2d" />
<view class="col center">
<view class="row-center" wx:for="{{chartData}}" wx:key="chartData" style="margin-top:{{index==0?'0':'16'}}rpx">
<view class="circle" style="background: {{item.color}}"></view>
<view class="project-item font-size-12 flex1 row">
<view>{{item.title}}</view>
<view class="flex1 margin-left-16"> {{item.numb}}</view>
<view class="margin-left-16"> {{item.percent}}%</view>
</view>
</view>
</view>
</view>
这里并不需要多少代码,但是 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 环形统计图的资料请关注编程网其它相关文章!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341