原发于知乎: zhuanlan.zhihu.com/p/32740553
引言, 为什么想要研究 Chartjs
继之前我们研究了SVG.js 和 Frappe Charts 后, 我们对于 svg 的图表库已经有了初步的了解, 但是对于可视化世界的 canvas, 我们更应该投入精力去了解学习.
在看到 chartist.js 讲到自己的优势的时候, 提到一些图表库使用了错误的技术 canvas, 那我们就更有兴趣去了解, 为什么会有这种说法. 首先让我们一起来了解一下 Chartjs.
Chartjs 介绍
Chartjs 的官方介绍是一个简单灵活的图表库, 相对而言 Chartjs 在图表库中的优势, 主要是配置简单, 动画比较优雅, 而基于 canvas 的特性, 让 Chartjs 性能会更有优势. Chartjs 目前拥有 34.4 K的 star, 几乎已经是 canvas 版本的图表代名词, 也是最流行的基于 canvas 的图表库. 在 GitHub 上搜索 chart, 可以看到 Chartjs 的流行度排名仅次于 D3.
先来看一下 Chartjs 如何使用吧?
<canvas id="myChart"></canvas> 复制代码
var ctx = document.getElementById('myChart').getContext("2d");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL"],
datasets: [{
label: "Data",
borderColor: "#80b6f4",
fill: false,
data: [50, 120, 150, 170, 180, 170, 160]
}]
}
});复制代码
得到如图的趋势图
Chart.js 代码组织方式
Chart.js 图表创建过程
从源文件的 core.controller.js 分析, 来看 Chartjs 初始化图表的过程如下:
左侧为触发的插件机制的事件.
Chartjs 插件机制
Chartjs 的插件机制看起来很简单, 但是也很有效. 插件直接注册到 plugin 里面, 拥有全部的执行的生命周期, 而且可以直接访问 Chart 的全局变量, 拥有所有 API 的访问权限.
Chart.plugins.register({
beforeInit() {}
afterInit() {}
afterUpdate() {}
afterLayout() {}
afterDatasetsUpdate() {}
afterDatasetUpdate() {}
afterRender() {}
afterDraw() {}
afterDatasetsDraw() {}
afterDatasetDraw() {}
afterEvent() {}
resize() {}
destroy() {}
});复制代码
Chartjs 鼠标事件和动画
对于 canvas 类型的图表而言, 处理相应的鼠标时间一直是件比较麻烦的事情. 让我们来看下 Chartjs 是怎么做的吧? 从源文件的core.controller.js 文件的 handleEvent 可以看出, Chartjs 在根元素位置, 监听对应的鼠标事件, 然后通过之前记录的元素位置, 找到最近的对应元素, 响应对应的事件.
if (e.type === 'mouseout') {
me.active = [];
} else {
me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
}
...
me.updateHoverStyle(me.active, hoverOptions.mode, true);复制代码
从代码中可以看出, Chartjs 是通过 getElementsAtEventForMode 方法去获取对应的元素. Chartjs 提供了 6 种模式, 来响应交互. 我们一起来看一下这六种模式.
-
point: 找到 鼠标位置相交的 对应元素
-
nearest: 找到对应距离最近的元素
-
index: 根据位置, 找到不同数据集中 对应 index 的数据
-
dataset: 根据位置, 找到只在同一数据集的元素
-
x: 只根据鼠标位置的 x 轴值, 找到与 x 轴值相交的元素, 适应于垂直光标的场景
-
y: 只根据鼠标位置的 y 轴值, 找到与 y 轴值相交的元素, 适应于垂直光标的场景
而对于动画, 在core.animation.js文件的实现了对于 animation 的堆栈, 针对动画依次使用 requestAnimationFrame 进行动画的调用. 动画中也内置了常见的各种缓动函数, 用于常见的动画效果. 我们可以看一下动画的核心实现, 里面的 advance 方法.
while (i < animations.length) {
animation = animations[i];
chart = animation.chart;
animation.currentStep = (animation.currentStep || 0) + count;
animation.currentStep = Math.min(animation.currentStep, animation.numSteps);
helpers.callback(animation.render, [chart, animation], chart);
helpers.callback(animation.onAnimationProgress, [animation], chart);
if (animation.currentStep >= animation.numSteps) {
helpers.callback(animation.onAnimationComplete, [animation], chart);
chart.animating = false;
animations.splice(i, 1);
} else {
++i;
}
}复制代码
上述代码中的, animation.render 根据动画中的当前动画的进度, 来绘制出动画所涉及元素的中间状态.
Chartjs 浮点数问题
在阅读 Chartjs 源码的过程中, 发现源码部分没有针对浮点数问题做任何处理, 很多地方也都没有考虑过浮点数问题. 所以 Chartjs 在使用过程中可能会有如下的问题:
One more thing
在使用很多通用图表的时候, 相信大家都会遇到通用图表的定制化困难这种问题, 下期我们将分析一下
可视化图形语法G2, 看下 G2 是怎么实现对于图表的高度的易用性和扩展性.
在看 Chartjs源码的同时, 我们也动手用 canvas 实践了一些基础图表, 具体可以参见
Taco.
如果想来和我们一起研究可视化,欢迎投递简历
linhui.wlh@alibaba-inc.com