还原设计稿是一个前端工程师的基本工作,可悲的是大部分前端工程师都不在意这些细枝末节,或者说不是他们不愿意去做好这件事,而是他们还没有准备好去做这件事。
很多人在面对炫酷的图表设计时,首先想到的用第三方库来还原设计,但是却忽略了第三方库和 UI 设计的出入,更为致命的是他们不知道如何进行还原。当然我也不算是什么厉害角色,但是我仍然记得还原设计是我的基本工作。
canvas 已经很多年了,api 各大网站上都有,我们不在关注这些问题。用 canvas 实现柱状图、折线图等基本图表,我相信看看 api,基本都没什么问题。可如果是绘制一个曲线图/趋势图呢?
我们先完成一个简单的折线图,鹅,不太顺利,一上来就遇到了惊喜:
在此之前我一直认为 canvas 的宽高是否写到元素上和写到样式表中是一样的,现在我才知道它们不一样,也算是对 canvas 的属性有了新的认识。
有了基础的折线图,下面就是怎么绘制曲线图的问题了,如果你的点足够多,用折线图就可以满足,但实际上我们并没有这么多数据。
根据 canvas 所提供的 api,我想大家都能猜出来,二次贝塞尔曲线和三次贝塞尔曲线。虽然弧线也是曲线,但这个地方弧线确实不太适合。
这里借用canvas 进阶——如何画出平滑的曲线的内容:
假设我们在一次绘画中共采集到 6 个鼠标坐标,分别是 A, B, C, D, E, F;
取前面的 A, B, C 三点,计算出 B 和 C 的中点 B1,以 A 为起点,B 为控制点,B1 为终点,利用 quadraticCurveTo 绘制一条二次贝塞尔曲线线段;
接下来,计算得出 C 与 D 点的中点 C1,以 B1 为起点、C 为控制点、C1 为终点继续绘制曲线;
依次类推不断绘制下去,当到最后一个点 F 时,则以 D 和 E 的中点 D1 为起点,以 E 为控制点,F 为终点结束贝塞尔曲线。
有了算法,代码都不是事:
1 | var drawCurve = function (ctx, data) { |
看起来还不错,但是拐点怎么对不上啊!
这里就要涉及到另一个问题了,拟合,下面到内容参考至平滑曲线生成:贝塞尔曲线拟合.
> 平滑
当前点 pt,当前点前后两点 p1、p2, pt 点对应的前后控制点 c1、c2,对于 p1-pt-p2 两段线,算法生成 p1 ~ pt 和 pt ~ p2 两条贝塞尔曲线
两条贝塞尔曲线在 pt 点对应的控制点分别为 c1、c2
因为贝塞尔曲线与该点和其控制点的连线相切,所以保证 c1、pt、c2 三点共线就可以产生平滑的曲线
控制点自动计算
1、取 c1、pt、c2 为角 p1-pt-p2 的角平分线的垂线
2、取 c1-pt 与 c2-pt 等长,为 p1 或 p2 在该垂线上的投影点,(参看图 3,当 p1-pt 长度大于 pt-p2 时,取 p11 点在垂线的投影点作为 c1,p11 到 pt 的距离与 pt-p2 等长)
3、对 c1、c2 做一定比例的缩放,实际取的控制点距 pt 的距离为投影点距离的 0.2-0.6 之间时都可以取得很好的平滑效果
代码实现:
1 | var drawBezier = function (ctx, data) { |
现在看起来就正常多了,当然上面的代码写得比较乱,在实际应用中应做适当的优化,此处仅作为参考。
参考文章:
平滑曲线生成:贝塞尔曲线拟合
基于 canvas 使用贝塞尔曲线平滑拟合折线段
[越努力,越幸运!]