[date: 2019-02-27 14:50] [visits: 25]

小程序-Canvas绘制折线图

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

目标

最终目标是达到官方小程序数据助手中折线图的效果:

除了UI目标外,还要便于在小程序其他地方使用或者在其他小程序中使用,所以API要有适当的通用性。

实现

实现的思路是在页面上放置指定大小的canvas,然后调用API传递'canvas id'与配置选项完成绘制,配置选项主要用来指定绘制数据、有关颜色、绘制大小等,绘制的API可以封装到一个单独的模块中便于公用。

接口

暂时设计了4个接口:init、draw、showLine、hideLine,分别用来初始化、绘制、显示和隐藏某条线,具体API描述如下所示:

init,返回一个LineChart实例,可以调用LineChart原型上的三个方法,其中:

{
    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                    // 文字大小
}

draw,按初始化给定的ctx和options绘制折线图

showLine,显示options.lines[index]中的该条线

hideLine,隐藏options.lines[index]中的该条线

tipsByX,根据x坐标显示提示内容,x坐标通过touchevent获得,详细请见下文

clearTips,清除提示信息

代码

完整代码移步Github,这里只描述一下自己用canvas绘制折线图的主要思路:

绘制x轴相对简单,但所有x轴label都绘制可能导致空间不一够,所以这里要依据label数量与宽度计算能够绘制的个数,然后按等步长的方式从中挑选。同时因为ctx.fillText的绘制位置是从左上角计算,为避免溢出,x轴label绘制位置在x轴方向要有适当的偏移

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将所有点连起来即可

将需要绘制的区域连接成一个闭合区域再使用ctx.fill填充即可,在绘制线的起点前添加其在x轴的投影点作为新起点,以及在绘制线的终点后添加其在x轴的投影点作为新终点,最后使用ctx.closePath串联起来并fill

在每个点的位置使用ctx.arc绘制

在图表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绘制出来的折线图效果如下: