# 如何在小程序中绘制图表？

- 作者：知晓程序
- 发布时间：2017-02-07 12:06
- 原文：https://www.ifanr.com/minapp/783579

![charts-featured](https://s3.ifanr.com/wp-content/uploads/2017/02/charts-featured.jpg)

文 \| musiq1989

由于微信小程序本身框架的限制，很难集成目前已有的图表工具，显示图表目前有两种方案：

1. 服务器端渲染图表，输出图片，微信小程序中直接显示渲染好的图片；
2. 利用微信小程序 API 中提供的 `canvas` 组件支持，自行绘制图表。

前一种方案已经有非常多类似服务可选，比如 Highcharts 提供了服务端渲染的能力。**但这种方式需要后台有一套渲染服务，并且有一定的网络开销。**

那么，如何利用 `canvas` 组件，在小程序中绘制图表呢？下面，我们就来尝试一下。

### API

首先，我们在模板文件中使用 `<canvas></canvas>` 声明一个 canvas 组件，再使用 `wx.createContext()` 获取绘图上下文 context。

接下来，我们调用 `wx.drawCanvas()` 进行绘制：

```
wx.drawCanvas({
    canvasId: <span class="hljs-string">'firstCanvas'</span>,
    actions: context.getActions() <span class="hljs-comment">// 获取绘图动作数组</span>
});
```

### 开始图表的绘制

#### 绘制折线图

```
<span class="hljs-comment">// 获取绘图上下文 context</span>
<span class="hljs-keyword">var</span> context = wx.createContext();
<span class="hljs-comment">// 设置描边颜色</span>
context.setStrokeStyle(<span class="hljs-string">"#7cb5ec"</span>);
<span class="hljs-comment">// 设置线宽</span>
context.setLineWidth(<span class="hljs-number">4</span>);

context.moveTo(<span class="hljs-number">50</span>, <span class="hljs-number">70</span>);
context.lineTo(<span class="hljs-number">150</span>, <span class="hljs-number">150</span>);
context.lineTo(<span class="hljs-number">250</span>, <span class="hljs-number">30</span>);
context.lineTo(<span class="hljs-number">350</span>, <span class="hljs-number">120</span>);
context.lineTo(<span class="hljs-number">450</span>, <span class="hljs-number">150</span>);
context.lineTo(<span class="hljs-number">550</span>, <span class="hljs-number">95</span>);
<span class="hljs-comment">// 对当前路径进行描边</span>
context.stroke();
wx.drawCanvas({
    canvasId: <span class="hljs-string">'testCanvas'</span>,
    actions: context.getActions()
});
```

`<span style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif;">需要注意的是，</span>moveTo()` **方法不会记录到路径中。**

我们来看看效果图：

![charts-1](https://s3.ifanr.com/wp-content/uploads/2017/02/charts-1.jpg)

好像没有想象中难，看上去效果还不错。

#### 绘制每个数据点的标识图案

```
...

context.beginPath();
<span class="hljs-comment">// 设置描边颜色</span>
context.setStrokeStyle(<span class="hljs-string">"#ffffff"</span>);
<span class="hljs-comment">// 设置填充颜色</span>
context.setFillStyle(<span class="hljs-string">"#7cb5ec"</span>);
context.moveTo(<span class="hljs-number">50</span> + <span class="hljs-number">7</span>, <span class="hljs-number">70</span>);
<span class="hljs-comment">// 绘制圆形区域</span>
context.arc(<span class="hljs-number">50</span>, <span class="hljs-number">70</span>, <span class="hljs-number">8</span>, <span class="hljs-number">0</span>, <span class="hljs-number">2</span> * <span class="hljs-built_in">Math</span>.PI, <span class="hljs-literal">false</span>);

context.moveTo(<span class="hljs-number">150</span> + <span class="hljs-number">7</span>, <span class="hljs-number">150</span>);
context.arc(<span class="hljs-number">150</span>, <span class="hljs-number">150</span>, <span class="hljs-number">8</span>, <span class="hljs-number">0</span>, <span class="hljs-number">2</span> * <span class="hljs-built_in">Math</span>.PI, <span class="hljs-literal">false</span>);

...

context.closePath();
<span class="hljs-comment">// 填充路径</span>
context.fill();
context.stroke();
```

效果图：

![charts-2](https://s3.ifanr.com/wp-content/uploads/2017/02/charts-2.jpg)

**为了避免之前绘制的折线路径影响到标识图案的路径，这一部分包裹在了 `beginPath()` 和 `closePath()` 之间。**

#### 绘制横坐标

我们规定的参数格式是这样的：

```
opts = {
    width: <span class="hljs-number">640</span>,    <span class="hljs-comment">// 画布区域宽度</span>
    height: <span class="hljs-number">400</span>,   <span class="hljs-comment">// 画布区域高度</span>
    categories: [<span class="hljs-string">'2016-08'</span>, <span class="hljs-string">'2016-09'</span>, <span class="hljs-string">'2016-10'</span>, <span class="hljs-string">'2016-11'</span>, <span class="hljs-string">'2016-12'</span>, <span class="hljs-string">'2017'</span>]
}
```

我们根据参数中的 `categories` 来绘制横坐标。先稍微整理下思路：

1. 根据 `categories` 数均分画布宽度；
2. 计算出横坐标中每个分类的起始点；
3. 绘制文案（这儿会多一些代码，后面会具体提到）。

```
<span class="hljs-keyword">var</span> eachSpacing = <span class="hljs-built_in">Math</span>.floor(opts.width / opts.categories.length);
<span class="hljs-keyword">var</span> points = [];
<span class="hljs-comment">// 起始点x坐标</span>
<span class="hljs-keyword">var</span> startX = <span class="hljs-number">0</span>;
<span class="hljs-comment">// 起始点y坐标</span>
<span class="hljs-keyword">var</span> startY = opts.height - <span class="hljs-number">30</span>;
<span class="hljs-comment">// 终点x坐标</span>
<span class="hljs-keyword">var</span> endX = opts.width;
<span class="hljs-comment">// 终点y坐标</span>
<span class="hljs-keyword">var</span> endY = opts.height;

<span class="hljs-comment">// 计算每个分类的起始点x坐标</span>
opts.categories.forEach(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(item, index)</span> </span>{
    points.push(startX + index * eachSpacing);
});
points.push(endX);

<span class="hljs-comment">// 绘制横坐标</span>
context.beginPath();
context.setStrokeStyle(<span class="hljs-string">"#cccccc"</span>);
context.setLineWidth(<span class="hljs-number">1</span>);

<span class="hljs-comment">// 绘制坐标轴横线</span>
context.moveTo(startX, startY);
context.lineTo(endX, startY);
<span class="hljs-comment">// 绘制坐标轴各区块竖线</span>
points.forEach(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(item, index)</span> </span>{
    context.moveTo(item, startY);
    context.lineTo(item, endY);
});
context.closePath();
context.stroke();

context.beginPath();
<span class="hljs-comment">// 设置字体大小</span>
context.setFontSize(<span class="hljs-number">20</span>);
<span class="hljs-comment">// 设置字体填充颜色</span>
context.setFillStyle(<span class="hljs-string">'#666666'</span>);
opts.categories.forEach(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(item, index)</span> </span>{
    context.fillText(item, points[index], startY + <span class="hljs-number">28</span>);
});
context.closePath();
context.stroke();
```

效果图：

![chart-3](https://s3.ifanr.com/wp-content/uploads/2017/02/chart-3.jpg)

效果不错，除了文字没有居中…….

查阅微信小程序官方提供的文档，小程序并没有提供 HTML 5 `canvas` 中的 `mesureText`（获取文案宽度）的方法。

下面是我们自己简单的实现，并不是绝对精确，但误差基本可以忽略。

```
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mesureText</span> <span class="hljs-params">(text)</span> </span>{
    <span class="hljs-keyword">var</span> text = text.split(<span class="hljs-string">''</span>);
    <span class="hljs-keyword">var</span> width = <span class="hljs-number">0</span>;
    text.forEach(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(item)</span> </span>{
        <span class="hljs-keyword">if</span> (<span class="hljs-regexp">/[a-zA-Z]/</span>.test(item)) {
            width += <span class="hljs-number">14</span>;
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-regexp">/[0-9]/</span>.test(item)) {
            width += <span class="hljs-number">11</span>;
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-regexp">/\./</span>.test(item)) {
            width += <span class="hljs-number">5.4</span>;
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-regexp">/-/</span>.test(item)) {
            width += <span class="hljs-number">6.5</span>;
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-regexp">/[\u4e00-\u9fa5]/</span>.test(item)) {
            width += <span class="hljs-number">20</span>;
        }
    });
    <span class="hljs-keyword">return</span> width;
}
```

这里分别处理了字母、数字、点（.）、横线（-）以及汉字这几个常用字符。

上面的代码稍微修改下：

```
opts.categories.forEach(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(item, index)</span> </span>{
    <span class="hljs-keyword">var</span> offset = eachSpacing / <span class="hljs-number">2</span> - mesureText(item) / <span class="hljs-number">2</span>;
    context.fillText(item, points[index] + offset, startY + <span class="hljs-number">28</span>);
});
```

![charts-4](https://s3.ifanr.com/wp-content/uploads/2017/02/charts-4.jpg)

**大功告成！**

如何在折线上绘制出每个数据点的数值文案呢？大家可以自己动手，尝试一下。

原文地址：https://segmentfault.com/a/1190000007649376  
项目地址：https://github.com/xiaolin3303/wx-charts

**本文由知晓程序授权转载，关注微信号 zxcx0101，在知晓程序后台回复「1228」获得全网第一本《小程序入门指南》电子书。**

![zxcx-qun](https://s3.ifanr.com/wp-content/uploads/2017/01/zxcx-qun.png)

![new&#95;zx](https://s3.ifanr.com/wp-content/uploads/2017/01/new_zx-1.png)
