最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    正文概述 掘金(字节前端)   2021-03-16   1650

    “线”是可视化展现中最常见的图形元素,最直观的就是折线图,如图一。

    可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图一 折线图

    一条线由多个点来定义,按照点与点之间的连接方式,通常将线分为“折线”和“曲线”,在画法上又分为“实线”和“虚线”,如图二:

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图二 折线和曲线

    我们也经常使用线来绘制闭合的路径,从而形成可填充区域,比如面积图和雷达图,如图三和图四。

    可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图三 面积图

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图四 雷达图

    本篇文章在 Canvas API 的基础上,为大家讲解可视化研发中线的画法封装和线的动画实现方案(整体方案建立在图形学基础上,同样适用于WegGL 和3D场景)。

    * 0.1 线的定义

    前面我们提到过线的基本组成单位是“点(Point)”,两个相邻的点连接在一起成为一个“段(Segment)”,多个段拼装在一起组成一条线。如图六,这条线由7个点划分成的6个段组合而成。

    可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图六 点和段

    曲线的每个段的起止点会因为插值算法的不同而不同,后面我们会详细介绍。

    图七所示的伪代码展示了我们对线的基本定义。

    可视化研发之线的画法:直线,曲线,动画(Canvas版)可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图七 线的定义

    线的绘制是以段为单位的,不同的形状的线对段的拆分逻辑和画法都是有区别的,我们从最简单的折线开始。

    0.2 折线画法

    0.2.1 获取段

    折线对段的拆分很简单,根据传入的点数据,相邻两点划为一段。

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图八 折线段的拆分

    如上面的代码,实现很简单,依次遍历点数据,初始化段对象。这里有一个计算段的长度的操作,段的长度在动画场景是必须参数,在非动画场景则可以不用关心。折线的段的长度计算,就是计算一个线段的长度(两点间距离),如图九所示。

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图九 线段的距离计算

    另外图八的代码中,有一段是否是空段的判断逻辑。在实际的线图应用中,我们在某些情况需要隐藏线的某些段,比如传入了空数据或者用户指定了过滤条件。

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图十 空段

    0.2.2 Canvas中线段的画法

    在Canvas中画线段只需要两个api——moveTo 和 lineTo。图十一展示了连接[(0,0),(300,150),(400,150)]三个点的折线。

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图十一  moveTo 与 lineTo

    从上面的示例可以看到,Canvas 中绘制线段,只需要通过moveTo将画笔(Canvas 绘图上下文)定位到线段的起点,然后通过lineTo 绘制到线段的终点即可。多个首位相接的线段可以省略moveTo,直接lineTo。 要实现图十的空段效果,只需要moveTo到新段的起点即可,例如:

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图十二  绘制空段

    理解了基本的api之后,我们回到我们的折线上来,看看以段为单位的绘制方法。

    0.2.3 折线绘制

    基于上面画线的方法,我们只需要遍历一条线中的所有段,依次连接就可以了。为了处理空段的绘制,设置一个lineStart的标记变量,如果处于start状态,会先moveTo到新的点,而不是lineTo。大致的绘图流程如下:

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图十三 线的绘制基本流程

    drawSegment方法如下:

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图十四 drawSegment

    这里你可能要疑惑,这里将线拆成段并没有什么优势,为什么不直接连接各个点呢?分成段完成了一个线的绘制的骨架,在这个骨架基础上,很多功能都会很容易的扩展。比如,线的每一段都有不同的含义,可视化层面要展现这些不同的含义需要给线赋予不同的样式。这里我们可以给LineSegment配置一个LineSegConfig,独立配置每个段的样式,在绘制的过程中如果发现新的段的样式发生了变化,就可以立即进行渲染,然后开始绘制新段,灵活拼装。比如下图,末尾的红色虚线用来表示预测数据。

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图十五  分段渲染不同样式

    另外,分段会大大降低动画效果的实现成本,后面我们详细介绍。

    了解了折线的基本画法之后,我们来看看曲线。

    0.3 曲线画法

    0.3.1 贝塞尔曲线

    曲线有很多种,画曲线的方法也有很多种。由于Canvas 支持贝塞尔二次和三次曲线画法,曲线图表通常使用三次贝塞尔曲线画法,本文也将重点放在三次贝塞尔曲线的应用讲解上。那么什么是贝塞尔曲线呢?

    Bézier curve(贝塞尔曲线)是应用于二维图形应用程序的数学曲线。贝塞尔曲线点的数量决定了曲线的阶数,一般N个点构成的N-1阶贝塞尔曲线,即3个点为二阶。一般我们都会要求曲线至少包含3个点,因为两个点的贝塞尔曲线是一条直线。按顺序,第一个点为 起点 ,最后一个点为 终点 ,其余点都为 控制点

    下面我们以二次贝塞尔曲线为例,讨论其生成过程。

    二次贝塞尔曲线

    给定点P0,P1,P2 ,P0 和 P2 为起点和终点,P1为控制点。从P0到P2的弧线即为一条二次贝塞尔曲线。

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图十六 二次贝塞尔曲线

    在这里我们要将整个曲线的绘制量化为从0~1的过程,用t为当前过程的进度,t的区间即0~1。每一条线都需要根据t生成一个点,如下图,一个点从P0移动到P1,这是这条线从0~1的过程。

    可视化研发之线的画法:直线,曲线,动画(Canvas版)

    下面我们还原一下一个二次贝塞尔曲线的生成过程。

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图十七 绘制二次贝塞尔曲线(1)

    如图十七,首先我们链接P0P1,P1P2,得到两条线段。然后我们对进度t进行取值,比如0.3,取一个Q0点,使得P0Q0的长度为P0P1总长度的0.3倍。

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图十八 绘制二次贝塞尔曲线(2)

    同时我们在P1P2上取一点Q1,使得  P0Q0: P0P1 = P1Q1: P1P2。接下来我们再在Q0Q1上取一点B,使得  P0Q0: P0P1 = P1Q1: P1P2 = Q0B:Q0Q1,如图十九

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图十九 绘制二次贝塞尔曲线(3)

    现在我们得到的点B就是二次贝塞尔曲线的上的一个点,如果我们使t=0开始取值,逐步递增进行插值,就会得到一系列的点B,进行连接就会形成一条完整的曲线,如图二十。


    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图二十 二次贝塞尔曲线绘制过程

    上面展示了完整的二次贝塞尔曲线的产生过程,这个过程我们经过数学推导,最终可以得到如下公式:

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    根据这个公式,我们只要变更t值,就可以得到对应的点。

    三次贝塞尔曲线

    对应的,三次贝塞尔曲线由四个点组成,通过更多的迭代步骤来确定曲线的上点,如图二十一所示。完整的生成如果如图二十二所示。

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图二十一 三次贝塞尔曲线


    可视化研发之线的画法:直线,曲线,动画(Canvas版)可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图二十二 三次贝塞尔曲线生成过程

    三次贝塞尔曲线的数学公式为:

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    0.3.2 Canas中如何绘制贝塞尔曲线

    在canvas中绘制二次贝塞尔曲线使用的是 quadraticCurveTo  函数,参数定义如下:

    可视化研发之线的画法:直线,曲线,动画(Canvas版)

    函数只定义了控制点和终点,起点需要我们使用moveTo来确定,如图二十三的代码示例。

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图二十三 canvas绘制二次贝塞尔曲线

    三次贝塞尔曲线使用 bezierCurveTo() 方法来绘制,参数定义如下:

    可视化研发之线的画法:直线,曲线,动画(Canvas版)

    和二次曲线的绘制方式类似,如图二十四。

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图二十四 canvas绘制三次贝塞尔曲线

    下面的动图展示了控制点对贝塞尔曲线形状的影响。


    可视化研发之线的画法:直线,曲线,动画(Canvas版)可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图28 控制点对贝塞尔曲线的影响

    0.3.3 样条曲线 与 获取段

    我们了解了如何绘制三次贝塞尔曲线,但是回到我们的线图,一个线图会有不确定数量的点被平滑的连接起来,但是目前三次贝塞尔曲线显然无法满足这个需求。我们前面谈到了分段的概念,一条完整的曲线被分成了多段,如果每一段都是一条三次贝塞尔曲线,问题就解决了。那么问题就转化成了如何构造多条能依次平滑拼接的贝塞尔曲线。在图形学中有个概念叫“样条曲线”,专业的概念有点难懂,我们这里简单理解就是将一个点的集合,分成多段曲线,各曲线处的连接点处有可以平滑连接(有连续的一次和二次导数)。关于样条曲线的连续性以及贝塞尔曲线的更多特性,读者可以参考《计算机图形学(第四版)》一书第14章——《样条表示》,这里我们就不深入解释了,直接看例子。

    可视化研发之线的画法:直线,曲线,动画(Canvas版)可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图29 一段由四条三次贝塞尔曲线拼接而成的曲线

    以图29为例,如我我们要将这条曲线分成四条三次贝塞尔曲线,我们要确定两个参数:

    1. 每条三次贝塞尔曲线的起点和终点
    2. 每条三次贝塞尔曲线的两个控制点

    只有选取合适的起点、终点和控制点,我们才能使得相邻的两条曲线可以平滑连接。样条曲线的拆分算法有很多种,这里也不详细介绍了,感兴趣的同学可以参考图形学相关书籍;JavaScript 实现可以参考 d3-shape 的 Curves 接口(github.com/d3/d3-shape),d3-shape Curves 中的curveBasis、curveBasisClosed、curveBasisOpen、curveBundle、curveCardinal、curveCardinalClosed、curveCardinalOpen、curveCatmullRom、curveCatmullRomClosed、curveCatmullRomOpen、curveNatural、curveMonotoneXcurveMonotoneY都是基于三次贝塞尔曲线的样条实现。

    下面我们以Basis 算法的实现为例,进行讲解曲线如何获取“段”。

    主流程

    Basis 算法要求点集中的点的数量至少为3个,然后我们利用如下逻辑进行段的获取:

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    • 图30 获取曲线的 “段” 的主流程
    • 我们的主流程逻辑很简单,循环给到的点,从当前索引位置开始向后取出3个点,然后根据这三个点以及当前段的起始点计算结束点和控制点。每个新段的起点是上一个相邻段的终点。随后计算当前段的长度。 当前的循环逻辑不会计算到最后一个点,所以会少一个段,最后加个单独的逻辑来处理。

    点的计算

    下面来看看 Basis 算法点的计算:

    可视化研发之线的画法:直线,曲线,动画(Canvas版)可视化研发之线的画法:直线,曲线,动画(Canvas版)

    • 图31  basis 样条算法

    如图31,我们基于很简单的公式来计算各个点的值,这个公式是怎么来的呢?简单说是结合了B样条曲线和三次贝塞尔曲线在端点处的一阶和二阶导出得来的。这里就不深入了,否则本篇文章会严重偏离主题,感兴趣的读者请参阅计算机图形学相关书籍。总之,我们通过公式计算可以得到我们需要的点。

    曲线分割与长度计算

    计算曲线的长度并不是一件容易的事情,由于贝塞尔函数是插值函数,所以计算方法就是先对曲线进行切割,切割到足够小的范围,然后计算这一小段的曲线近似长度,再累加。0.3.1节给出了三次贝塞尔曲线的函数,我们只需要将变量t取足够小的值,然后计算两个点之间的直线距离进行累加就可以,但是这种方案的性能消耗比较大。我在

    community.khronos.org/t/3d-cubic-…

    看到一种近似方法,利用该方法可以缩减切割次数。 基于三次贝塞尔曲线的函数,对一个贝塞尔曲线进行切割,很简单。我们再把图21拿来说明一下各点的计算。

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图21

    第一步:找到连接点

    如图21,假设我要在t=0.25的位置将当前曲线切分成两条曲线,首先我们要知道点B的位置。根据公式带入即可:

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图33 根据t计算3次贝塞尔的点

    第二步:获取控制点

    拿到点P之后,P就是第一段的终点,第二段的起点,这样我们只需要计算控制点即可。根据我们之前对贝塞尔曲线绘制过程的理解,我们可以得出如下结论:

    1. 第一段曲线的第一个控制点的运动轨迹是线段P0P1,和t线性相关
    2. 第一段曲线的第二个控制点的运动轨迹是线段Q0Q1,和t线性相关
    3. 第二段曲线的第一个控制点的运动轨迹是线段Q1Q2,和t线性相关
    4. 第二段曲线的第二个控制点的运动轨迹是线段P2P3,和t线性相关

    依据上面的结论,三次贝塞尔曲线拆分的方法就很容易实现了:

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图34 贝塞尔曲线拆分

    图34 所示代码中 pointAt 方法为根据t获取直线上点的方法。如下: 可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图35 根据t获直线上的点

    第三步 长度计算

    我们可以在任意位置对三次贝塞尔曲线进行拆分了,结合二分法,控制迭代次数,结合近似长度计算函数,我们可以得到想要精度的长度值了。如图36。

    可视化研发之线的画法:直线,曲线,动画(Canvas版)可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图36  三次贝塞尔曲线的分割

    获取段

    内部细节我们都梳理清楚了,获取所有的段也很简单了。现在需要特殊处理的是最后一个点数据,这里我们将第二个点和第三个点都用最后一个点表示。

    可视化研发之线的画法:直线,曲线,动画(Canvas版)可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图37  basis 最后一段生成方法

    0.3.4  曲线画法

    关于曲线的所有准备工作都完成了,下面我们要把它画出来。和画折线的方法类似,我们只需要循环调用"段" 的绘制方法进行绘图即可。内部,只需要调用bezierCurveTo即可。如下:

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版) 图38 绘制曲线的段

    0.4 动画

    我们完成了折线和曲线的绘制,想要线通过动画的方式画出来,只需要做少量的改动。首先不论直线还是曲线我们都分成了多段,每一段都是和t相关的函数。

    0.4.1  基本方案

    动画和非动画的本质区别就是一次画多少的问题,我们将整条线图的绘制放置在[0,1]区间内,启动一个动画循环,每次绘图的时候更新的t的值,在我们上面循环绘制segment 的代码中,将整条线图的t转化为每一个段内部的t值。段 内部根据传入的t值,对自身进行切割,只画应该绘制的那部分。

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图39  t值换算

    因为我们已经计算了每个段的长度,和总长度,所以每个段的占比由长度可以获得,此占比在和整个线图的t值进行换算即可。

    以图39为例,比如我们传入的t值为0.1,整条线图的0.1 换算到第一个段是0.4,那么第一个段只需绘制前40% 部分即可。我们在图39的基础上,做少量的改动。

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图40  支持局部绘制

    如图40,我们将外部计算的t(percent)传入绘制段的方法内,该方法会使用我们之前介绍过的 divideCubic 方法对当前曲线进行切割,然后进行局部绘制。效果如下:


    可视化研发之线的画法:直线,曲线,动画(Canvas版)可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图41  动画

    0.4.2  和其他动画方案的对比

    实现线和面积的动画的方案还有整体Clip和生成点集两种方案,下面我们简单对比一下,以说明我们的分段绘制的优势。

    方案简介函数调用基于曲线的轨迹动画不规则线分段扩展预生成点集是利用曲线函数,预生成足够密度的点,然后将各点连接较多。会产生大量的绘图函数的调用支持支持可以支持,比较麻烦,也要有段的概念整体clip绘制之前设置一个裁剪窗口,调整裁剪窗口的大小来实现动画较少不支持,不能动态计算当前t值的x,y不支持。只能在一个方向上clip,不能照顾x,y坐标值无序情况。不支持分段模型一个图最多调用n-1 次支持支持支持

    0.4.3  动画同步

    上面我们看到的动画不同的线之间虽然可以再同一时间到大终点,但是过程中在x方向的位移是不同步的。同步和不同步都各有需求,尤其是在面积图情况下,单个面积图实际被拆分了上下两组segment。如图41.

    可视化研发之线的画法:直线,曲线,动画(Canvas版) 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    图41 基本面积图的segement 

    我们观察上面面积图的绘图动画,它是从左到右推进的,比如当前的t值绘制到图41的矩形框的位置,那么首先会绘制第一段,计算第12段应该被绘制的区间,最后填充上下两段的闭合区间。这里有一个问题,如果是相同的t值,带入1和12的函数,产生的x值是不一样的,那么绘制出来的效果就不对了,切面可能是斜的。

    解决这个问题做法是根据x或者y值反求t值,再带入目标函数中。对于三次贝塞尔曲线来说,这又是一个大难题,由于篇幅所限及代码实现的比较复杂,这里就不再讲解了,大家可以参考文后的参考资料。

    0.5 参考资料:

    一个超酷的贝塞尔类库:pomax.github.io/bezierjs/

    一本超级棒的贝塞尔电子书 pomax.github.io/bezierinfo/

    关于根据x或y反算t的讨论:www.zhihu.com/question/30…

    图形学必读书物:《计算机图形学》

    本文例子来源(字节跳动自研图表库):bytecharts.web.bytedance.net/


    数据平台前端团队,在公司内负责风神、TEA、Libra、Dorado等大数据相关产品的研发。我们在前端技术上保持着非常强的热情,除了数据产品相关的研发外,在数据可视化、海量数据处理优化、web excel、sql编辑器、私有化部署、工程工具都方面都有很多的探索和积累,有兴趣可以与我们联系。对产品有任何建议和反馈也可以直接找我们进行反馈~

    欢迎关注「 字节前端 ByteFE 」简历投递联系邮箱「 tech@bytedance.com 」
    

    起源地下载网 » 可视化研发之线的画法:直线,曲线,动画(Canvas版)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元