小程序-Canvas绘制折线图
自己在一个小程序项目中,希望通过绘制折线图展示不同时间的数值变化趋势,搜索了一番后没找到特别好的第三方库,故打算自己实现一个简单的绘制折线图方法,本文记录自己如何在小程序中通过canvas绘制折线图。
目标
最终目标是达到官方小程序数据助手中折线图的效果:

除了UI目标外,还要便于在小程序其他地方使用或者在其他小程序中使用,所以API要有适当的通用性。
实现
实现的思路是在页面上放置指定大小的canvas,然后调用API传递'canvas id'与配置选项完成绘制,配置选项主要用来指定绘制数据、有关颜色、绘制大小等,绘制的API可以封装到一个单独的模块中便于公用。
接口
暂时设计了4个接口:init、draw、showLine、hideLine,分别用来初始化、绘制、显示和隐藏某条线,具体API描述如下所示:
- init(ctx: string, options: object): LineChart
init,返回一个LineChart实例,可以调用LineChart原型上的三个方法,其中:
- ctx: canvas的id字符串
- options:
{
width: 320, // canvas的宽度
height: 200, // canvas的高度
labelColor: '#888888', // label的颜色
axisColor: '#d0d0d0', // 轴的颜色
xUnit: '', // x轴label的单位
yUnit: '', // y轴label的单位
xAxis: [1, 2, 3, 4], // x轴label数组
lines: [{ // 需要绘制的线
color: '#1296db', // 线的颜色
points: [10, 29, 18, 20], // 线的y轴值
}],
margin: 20, // 内容与边界的距离
fontSize: 12 // 文字大小
}
- LineChart.prototype.draw()
draw,按初始化给定的ctx和options绘制折线图
- LineChart.prototype.showLine(index: number)
showLine,显示options.lines[index]中的该条线
- LineChart.prototype.hideLine(index: number)
hideLine,隐藏options.lines[index]中的该条线
- LineChart.prototype.tipsByX(x: number)
tipsByX,根据x坐标显示提示内容,x坐标通过touchevent获得,详细请见下文
- LineChart.prototype.clearTips()
clearTips,清除提示信息
代码
完整代码移步Github,这里只描述一下自己用canvas绘制折线图的主要思路:
- 绘制x轴与label
绘制x轴相对简单,但所有x轴label都绘制可能导致空间不一够,所以这里要依据label数量与宽度计算能够绘制的个数,然后按等步长的方式从中挑选。同时因为
ctx.fillText
的绘制位置是从左上角计算,为避免溢出,x轴label绘制位置在x轴方向要有适当的偏移
- 绘制y轴label以及水平标度线
y轴label数可以根据cavans高度与希望的标度线间距计算,而每条水平标度线的取值则需要考虑options.lines在y轴的最大值。数量与取值决定后,绘制就相对容易
- 绘制线
绘制轴与label需要收集4个信息:xOffset、yOffset、xStep、yStep,分别表示原点的偏移量(坐标)和单位步长,此时线上的点:(index, value)对应到的canvas中坐标为:(xOffset + index * xStep, yOffset - value * yStep),最后再使用
ctx.moveTo
结合ctx.lineTo
将所有点连起来即可
- 绘制线与x轴的阴影面积
将需要绘制的区域连接成一个闭合区域再使用
ctx.fill
填充即可,在绘制线的起点前添加其在x轴的投影点作为新起点,以及在绘制线的终点后添加其在x轴的投影点作为新终点,最后使用ctx.closePath
串联起来并fill
- 绘制空心数据点
在每个点的位置使用
ctx.arc
绘制
- 绘制tips
在图表canvas上叠加一个专门用于显示tips的canvas,再监听tips-canvas的有关touch事件并从touchevent中获取x位置信息,然后绘制tips信息
demo
前面讲解了API与实现思路,下面展示一个使用的demo。
页面内容
<view class="wrap" style="width: 320px; height: 200px;">
<canvas canvas-id="chart" style="width: 320px; height: 200px;"></canvas>
<!-- 用于触摸提示,保持与上一个canvas大小与位置完全一致,不需要的话可以省略 -->
<canvas canvas-id="chart-tips" style="width: 320px; height: 200px;"
disable-scroll
bindtouchstart="tipsStart"
bindtouchmove="tipsMove"
bindtouchend="tipsEnd">
</canvas>
</view>
样式内容
.wrap {
position: relative;
margin: 0 auto;
}
.wrap canvas {
position: absolute;
top: 0;
left: 0;
}
js内容
const lineChart = require('./line-chart/index.js');
let chart = null;
Page({
onReady: function() {
chart = lineChart.init('chart', {
tipsCtx: 'chart-tips',
width: 320,
height: 200,
margin: 10,
yUnit: 'h'
xAxis: ['2.18', '2.19', '2.20', '2.21', '2.22', '2.23', '2.24', '2.25', '2.26', '2.27']
lines: [{
color: '#1296db',
points: [0, 0, 0, 0, 0, 0, 0, 105, 63, 40]
}]
});
chart.draw();
},
tipsStart: function(e) {
let x = e.changedTouches[0].x;
this.chartTipsShowing = true;
chart.tipsByX(x);
},
tipsMove: function(e) {
let x = e.changedTouches[0].x;
if (this.chartTipsShowing) {
chart.tipsByX(x);
}
},
tipsEnd: function() {
this.chartTipsShowing = false;
chart.clearTips();
}
});
绘制效果
最后通过LineChart绘制出来的折线图效果如下:
