由于微信小程序本身框架的限制,很难集成目前已有的图表工具,显示图表目前有两种方案:
-
服务器端渲染图表,输出图片,微信小程序中直接显示渲染好的图片,比如Highcharts提供了服务端渲染的能力hightcharts server render,这种方式需要后台有一套渲染服务,并且有一定的网络开销。
-
微信小程序API中提供了canvas的支持,利用canvas自行绘制图表。
下面我们来看下怎么在微信小程序中绘制图表。
API
查看微信小程序详细 Canvas API 文档。
在模板文件中使用<canvas></canvas>声明一个canvas组件。
使用wx.createContext获取绘图上下文 context。
调用wx.drawCanvas进行绘制。
wx.drawCanvas({
canvasId: 'firstCanvas',
actions: context.getActions() // 获取绘图动作数组
});
开始图表的绘制
绘制折线图
// 获取绘图上下文 context
var context = wx.createContext();
// 设置描边颜色
context.setStrokeStyle("#7cb5ec");
// 设置线宽
context.setLineWidth(4);
context.moveTo(50, 70);
context.lineTo(150, 150);
context.lineTo(250, 30);
context.lineTo(350, 120);
context.lineTo(450, 150);
context.lineTo(550, 95);
// 对当前路径进行描边
context.stroke();
wx.drawCanvas({
canvasId: 'testCanvas',
actions: context.getActions()
});
说明:moveTo方法不记录到路径中
效果图:

好像没有想象中难,看上去效果还不错。
绘制每个数据点的标识图案
...
context.beginPath();
// 设置描边颜色
context.setStrokeStyle("#ffffff");
// 设置填充颜色
context.setFillStyle("#7cb5ec");
context.moveTo(50 + 7, 70);
// 绘制圆形区域
context.arc(50, 70, 8, 0, 2 * Math.PI, false);
context.moveTo(150 + 7, 150);
context.arc(150, 150, 8, 0, 2 * Math.PI, false);
...
context.closePath();
// 填充路径
context.fill();
context.stroke();
效果图:

说明:避免之前绘制的折线路径影响到标识图案的路径,这里包裹在了beginPath和closePath中。
绘制横坐标
规定我们的参数格式是这样的。
opts = {
width: 640, // 画布区域宽度
height: 400, // 画布区域高度
categories: ['2016-08', '2016-09', '2016-10', '2016-11', '2016-12', '2017']
}
我们根据参数中的categories来绘制横坐标。
稍微整理下思路:
-
根据categories数均分画布宽度;
-
计算出横坐标中每个分类的起始点;
-
绘制文案(这儿会多一些代码,后面会具体提到)。
var eachSpacing = Math.floor(opts.width / opts.categories.length);
var points = [];
// 起始点x坐标
var startX = 0;
// 起始点y坐标
var startY = opts.height - 30;
// 终点x坐标
var endX = opts.width;
// 终点y坐标
var endY = opts.height;
// 计算每个分类的起始点x坐标
opts.categories.forEach(function(item, index) {
points.push(startX + index * eachSpacing);
});
points.push(endX);
// 绘制横坐标
context.beginPath();
context.setStrokeStyle("#cccccc");
context.setLineWidth(1);
// 绘制坐标轴横线
context.moveTo(startX, startY);
context.lineTo(endX, startY);
// 绘制坐标轴各区块竖线
points.forEach(function(item, index) {
context.moveTo(item, startY);
context.lineTo(item, endY);
});
context.closePath();
context.stroke();
context.beginPath();
// 设置字体大小
context.setFontSize(20);
// 设置字体填充颜色
context.setFillStyle('#666666');
opts.categories.forEach(function(item, index) {
context.fillText(item, points[index], startY + 28);
});
context.closePath();
context.stroke();
效果图:

效果不错,除了文字没有居中……
查看微信小程序官方提供的文档并没有提供HTML5 Canvas中的mesureText(获取文案宽度)方法,下面我们自己简单的实现,并不是绝对精确,但是误差基本可以忽略。
function mesureText (text) {
var text = text.split('');
var width = 0;
text.forEach(function(item) {
if (/[a-zA-Z]/.test(item)) {
width += 14;
} else if (/[0-9]/.test(item)) {
width += 11;
} else if (/\./.test(item)) {
width += 5.4;
} else if (/-/.test(item)) {
width += 6.5;
} else if (/[\u4e00-\u9fa5]/.test(item)) {
width += 20;
}
});
return width;
}
这里分别处理了字母、数字、 .、 -、汉字这几个常用字符。
上面的代码稍微修改下:
opts.categories.forEach(function(item, index) {
var offset = eachSpacing / 2 - mesureText(item) / 2;
context.fillText(item, points[index] + offset, startY + 28);
});

大功告成!
如何在折线上绘制出每个数据点的数值文案大家可以动手自己实现下。
确定纵坐标的范围并绘制
为了避免纵坐标的刻度出现小数的情况,我们把纵坐标分为5个区块,我们取最小单位刻度为例如10(能够被5整除),当然真实情况会比这复杂,待会儿我们再讨论。
所以我们的处理输入输出应该是下面的结果。
(5, 34.1) => (10, 40)
(10, 34) => (10, 40)
(-5.1, 40) => (-10, 40)
// 确定Y轴取值范围
function findRange (num, type, limit) {
limit = limit || 10;
// upper向上查找,lower向下查找
type = type ? type : 'upper';
// 进行取整操作,避免while时进入死循环
if (type === 'upper') {
num = Math.ceil(num);
} else {
num = Math.floor(num);
}
while (num % limit !== 0) {
if (type === 'upper') {
num++;
} else {
num--;
}
}
return num;
}
好了,初步的确定范围已经完成了,但是细想一下这个范围还是不是很理想,比如用户传入的数据都是小数级别的,比如 (0.2, 0.8),我们输出的范围是(0, 5)这个范围偏大,图表展现的效果则会是上面有大部分的留白,同样用户输入的数据很大,比如(10000, 18000),我们得到的范围是(10000, 18010),这个范围则没什么意义,所以我们需要根据传入的数据的范围来分别确定我们的最小单位刻度。
规定我们的参数格式是这样的:
opts = {
...
series: [{
...
data: [15, 20, 45, 37, 4, 80]
}, {
|