作者:musiq1989,前端开发工程师,专注于前端技术研究和内容分享,Github地址:https://github.com/xiaolin3303
由于微信小程序本身框架的限制,很难集成目前已有的图表工具,显示图表目前有两种方案:
服务器端渲染图表,输出图片,微信小程序中直接显示渲染好的图片,比如Highcharts提供了服务端渲染的能力hightcharts server render,这种方式需要后台有一套渲染服务,并且有一定的网络开销。
微信小程序API中提供了canvas的支持,利用canvas自行绘制图表。
下面我们来看下怎么在微信小程序中绘制图表。
API
查看微信小程序详细 Canvas API 文档。
在模板文件中使用<canvas></canvas>
声明一个canvas组件。
使用wx.createContext
获取绘图上下文 context。
调用wx.drawCanvas进行绘制。
wx.drawCanvas({
开始图表的绘制
绘制折线图
// 获取绘图上下文 context
说明: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);
效果图:
说明:避免之前绘制的折线路径影响到标识图案的路径,这里包裹在了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);
效果图:
效果不错,除了文字没有居中……
查看微信小程序官方提供的文档并没有提供HTML5 Canvas
中的mesureText
(获取文案宽度)方法,下面我们自己简单的实现,并不是绝对精确,但是误差基本可以忽略。
function mesureText (text) { var text = text.split('');
这里分别处理了字母
、数字
、.
、-
、汉字
这几个常用字符。
上面的代码稍微修改下:
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)
// 确定Y轴取值范围
好了,初步的确定范围已经完成了,但是细想一下这个范围还是不是很理想,比如用户传入的数据都是小数级别的,比如 (0.2, 0.8),我们输出的范围是(0, 5)这个范围偏大,图表展现的效果则会是上面有大部分的留白,同样用户输入的数据很大,比如(10000, 18000),我们得到的范围是(10000, 18010),这个范围则没什么意义,所以我们需要根据传入的数据的范围来分别确定我们的最小单位刻度。
规定我们的参数格式是这样的:
opts = {
让我们继续进行优化。
// 合并数据,将series中的每项data整合到一个数组当中
现在我们动态的确定除了合适的最小刻度范围,接下来我们接着优化一下上面的findRange
方法,主要是增加对小数的支持。
function findRange (num, type, limit) { limit = limit || 10;
现在我们已经确定好了Y轴的取值范围,关于如何画出Y轴可以参看上文中X轴的绘制方法,此处不再累赘。
Y轴效果图:
opts = {
opts = {
效果还不错,我们接着往下。
根据真实数据绘制折线
问题的关键在于确定每个数据点的(x, y)坐标,x坐标比较好确定,我们根据画布的宽度以及opts.categories
即可确定。
规定我们的配置为:
config = { xAxisHeight: 30, // X轴高度 yAxisWdith: 30 // Y轴宽度
var data = [15, 20, 45, 37, 4, 80];
y坐标稍微会复杂一点,需要根据Y轴的范围已经本身的数值进行计算得出。
所以我们计算出的y应该为:
y = validHeight * (data - min) / (max - min);
代码如下:
var data = [15, 20, 45, 37, 4, 80];
现在我们已经确定了数据点在画布上的绘制坐标,关于如何绘制折现请查看 part1 中相关内容,此处不再累赘。
最终效果图如下:
饼图绘制
先看一下API。
下面开始(使用ES6
语法编写,后面我们可以使用rollup
编译成ES5
的语法)
假设我们有这样的数据
const series = [ {data: 15, color: '#7cb5ec'}, {data: 35, color: '#f7a35c'}, {data: 78, color: '#434348'}, {data: 63, color: '#90ed7d'} ];
计算出各项所占的比例和开始的弧度。
calPieData.js
export function calPieAngle (series) { // 计算数据总和 let count = 0; series.forEach((item) => { count += item.data; });
数据已经计算出来了,下面让我开始绘制吧。
drawPieChart.js
import { calPieAngle } from 'calPieData'export default function drawPieChart (series) {
调用drawPieChart(series)就可以得到下面的结果:
很简单是不是,下面我们给各区块加上一个白色的分割线。
因为arc实际上是绘制了一条路径,所以我们简单的stroke描边一下就可以了。
...
添加动画效果
首先让我们创建一个动画工具,这个动画工具能够传入一些自定义的参数,比如动画时间,能够有动画每一步的回调以及动画结束的回调。
animation.js
export default function Animation (opts) { // 处理用户传入的动画时间,默认为1000ms // 因为用户有可能传入duration为0,所以不能用opts.duration = opts.duration || 1000 来做默认值处理 // 否则用户传入0也会处理成默认值1000 opts.duration = typeof opts.duration === 'undefined' ? 1000 : opts.duration;
动画使用了requestAnimationFrame
,并且已经满足了我们上面定义的需求
在实战中,此处的动画都是线性的,一般我们还会加入缓动选项,比如缓入,缓出,还有一点,在微信小程序真机中IOS设备是不支持requestAnimationFrame
的,所以要做降级处理,使用setTimeout
,。
下面我们调用animation来完成动画效果。
app.js
修改一下drawPieDataChart
function,能够接受process
参数。
...
同样,修改一下calPieAngle
function,能够接受process
参数。
export function calPieAngle (series, process = 1) {
使用rollup构建项目
Rollup is a next-generation JavaScript module bundler. Author your app or library using ES2015 modules, then efficiently bundle them up into a single file for use in browsers and Node.js.
也就是说rollup是一个前端构建工具,能够将我们的整个项目合并输出成一个最终的编译结果,上面我们编写代码的时候都是按照不同的功能放到不同的文件中,这样有利于后期的可持续性开发和维护,rollup正好能帮助我们构建出最后的编译结果。
先安装rollup
。
npm install -g rollup
添加对ES6的支持。
npm install --save-dev rollup-plugin-babel
创建.babelrc
文件在项目根目录,告诉babel
转义时使用哪个presets
。
{ "presets": ["es2015-rollup"], }
好了剩下最后一步,定义我们的rollup.config.js
配置文件:
import babel from 'rollup-plugin-babel'; export default {
rollup
会从入口文件开始,查找我们的依赖(import
),逐级往下深入,把依赖的文件全部收集起来并合并到一起,最后输出到我们定义的dest
文件中
执行:
rollup -c
好了,我们就得到了我们最后的项目编译文件charts.js
。
本文暂时没有评论,来添加一个吧(●'◡'●)