跳到主要内容

组件开发方案选型参考

通过制定标准方案为范式组件开发的一些典型场景的方案设计提供参考和解决方案。

分析组件的可视化核心逻辑和视觉元素构成

由于设计师的设计为了突出创造性,不太会受可视化领域一些专业概念的约束,所以如何把发散的需求转化抽象成一些可视化领域的专业概念或者要素很重要。可以从以下几个方面进行分析,明确目标,防止需求迭代过程中出现大的技术方案变动。

  • 可视化的核心目标

    组件的核心逻辑是展示数据的什么信息?例如趋势、占比、分布。这个目标一定不能在需求迭代过程中发生变化,而且根据这些目标我们可以依据可视化领域一些图表推荐的资料来完成基础图表类型的选型和判断。

  • 数据的哪些纬度信息进行了可视化呈现

    可视化呈现的是数据的哪些纬度信息?例如数值大小、分类、统计分布。这些纬度信息呈现出的特征可以帮助我们确定选取的坐标系类型、轴类型等,完成常规布局。

  • 组件由哪类视觉要素构成

    组件的视觉效果中有哪类视觉元素,例如折线、散点、柱子等,这些构成元素结合可视化的核心目标一般对应了 ECharts 提供的一些常用的图表类型,比如折线图、柱状图。

TL;DR

影响因素/方案单系列(功能扩展)单系列(自定义类型)多系列
开发成本中等中等
局限性中等
场景/方案适用性
简单组件(视觉元素构成单一)-
- 现有实现契合需求 > 50%--
- 动画√(不推荐)-
- 交互--
- 布局√(简单布局)-
复杂组件(视觉元素构成两类以上)--

场景:组件的视觉元素构成单一

信息
  • 适用情况
    • 组件需求中视觉元素构成只有一类,例如折线、散点等
  • 不适用情况
    • 组件需求中视觉元素构成有两类以上

以下给出单一类型图表的 3 种典型实现方案。

比较简单的组件需求,视觉元素构成通常比较单一,可能只有折线或者只有散点,这时可以直接选取对应的 ECharts 提供的单一图表类型就可以满足要求。

信息
  • 适用情况
    • 组件需求与现有某种类型图表的契合度达到 50% 以上(或者用开发成本衡量)
  • 不适用情况
    • 有大量定制化需求
{
series: {
type: 'line',
// 扩展配置项
dvXXX: '',
}
}

如果现有的图表类型无法满足需求,可以采用自定义系列的方式开发。

信息
  • 适用情况
    • 组件需求在视觉元素构成上基于现有某种类型图表开发成本高于 50% 以上
  • 不适用情况
    • 有大量定制化需求,尤其是非基本视觉元素的定制需求,例如交互、动画、布局
{
series: {
// 不要对业务方暴露 type: 'custom',底层实现应该是基于 type: 'custom'
type: 'custom'
}
}

或者,我们实现一个新类型的图表插件。

信息
  • 适用情况
    • 组件需求与现有类型图表的契合度都低于 50%
    • 有大量的定制化需求,例如视觉渲染、交互、动画、布局等
{
series: {
type: 'pluginChartXXX'
}
}

💡 开发案例

  • 双向树图组件(王渊)
    • 对视觉渲染、布局、交互均有很强的定制化需求
    • 采用图表插件的方案
  • 矩形树图
    • ECharts 已支持该类型图表,且 90% 契合需求
    • 采用基于现有图表的实现对标签进行功能点扩展开发的方案
  • 子弹图(钱晓东)
    • 采用基于现有的散点图进行配置实现的方案
  • 蜂群图组件(李少杰)
    • 采用基于现有的关系图进行配置实现的方案

场景:组件的视觉元素构成复杂

信息
  • 适用情况
    • 组件需求中视觉元素构成有两类以上
    • 组件需求中视觉元素构成中某几类与现有某种类型图表的契合度达到 50% 以上(或者用开发成本衡量),例如折线、散点等
  • 不适用情况
    • 组件需求中视觉构成元素有两类以上

针对组件需求较为复杂,图表内元素种类比较多的问题,以往的思路是通过单个 series 完成组件开发:

// 基于现有的图表类型进行扩展开发
{
series: {
type: 'line',
// 扩展配置项
dvXXX: '',
}
}

// 或者采用自定义系列的方式开发
{
series: {
type: 'custom'
}
}

各自的优劣:

  • 基于现有的图表类型进行扩展开发

    • 优势
      • 初期开发成本低
      • API 标准统一
    • 劣势
      • 开发扩展的难度较高,受限于现有代码的约束
      • 在需求迭代过程中,现有图表类型的实现的约束性问题越来越明显,成本越来越高
      • 动画的定制需求开发成本异常的高
  • 自定义系列的方式开发

    • 优势
      • 代码实现的灵活度很高
    • 劣势
      • API 不统一
      • 前期开发成本很高
      • 需要对 ECharts 和 ZRender 足够了解
      • 需要自己处理动画等典型场景的需求

回到需求来看,如果图表本身的构成要素较为复杂的话,可以考虑使用多个 series 进行组合(实际上这也是 ECharts 的设计哲学):

{
series: [
{
type: 'line',
// 扩展配置项
dvXXX: '',
},
{
type: 'custom'
}
]
}
  • 多个系列组合开发的方案
    • 优势
      • 初期开发成本较低
      • 代码实现具备一定的灵活性
    • 劣势
      • 针对现有图表类型的扩展的开发难度依然很高
      • 现有图表类型的动画的定制需求开发成本依然很高
      • 需要对 ECharts 和 ZRender 足够了解
      • 对需求的拆解有一定难度

💡 开发案例

  • 动态折线图组件(钱晓东)
    • 组件需求视觉元素构成有折线和事件标记
    • 结合现有图表类型折线图系列和自定义系列实现的标记系列组合实现

多人协作

信息
  • 适用情况
    • 各个环节之间耦合性较低,可并行进行

分析组件开发到交付业务方使用的过程中多个环节,将一些耦合性低的环节进行拆分,以保证在排期紧张时可以多人协作开发。

TL;DR

事项&场景/方案单人开发多人协作AIGC
数据预处理(脚本代码生成)
程序主流程-(源码解析)
功能点(独立、低耦合)-
布局算法(算法推荐/代码生成)
交互功能√(逻辑实现)√(API 设计)-
多系列方案√(主要)√(次要)-
业务案例开发√(组件)√(业务)(代码生成)
  • 在组件开发完成的演示阶段需要一个实际的场景案例(业务开发)
  • 数据的预处理(临时性工作)
  • 布局算法的实现(耦合性低)

渐进式的开发方案

经过以上分析,无论是简单需求还是需要组合多个系列才能实现的复杂需求,最终编码都是基于单个系列的维度进行设计,最终将其组合起来即可。针对单个系列以下列举可能采用的所有方案,并从开发方案的由易到难(代码自由度由低到高)逐个列举优劣势。

TL;DR

影响因素/方案修改源码(patch-package)继承原有实现重写或者扩展生命周期 API自定义系列图表插件
开发成本中等极高
影响面极大中等--
局限性中等中等-
场景/方案适用性
程序核心机制扩展----
缺陷修复(Bug Fixes)----
功能点扩展---
- 动画-√(不推荐)√(简单动画)--
- 交互-√(不推荐)--
- 布局-√(简单布局)--
图表类型扩展---
- 新增元素(视图渲染)---√(不推荐)
- 强定制需求---√(不推荐)

修改源码

信息
  • 适用情况
    • 需要对程序的核心机制进行扩展
  • 不适用情况
    • 大部分场景
  • 优势
    • 成本低
  • 劣势
    • 不易维护
    • 侵入性强,可能破坏 ECharts 的源码架构设计,引发其它问题

不建议采用。

扩展机制:继承原有的类进行扩展实现并重新注册

信息
  • 适用情况
    • 独立功能点扩展
    • 简单需求
  • 不适用情况
    • 复杂需求
    • 要扩展的部分依赖关系复杂

可以继承原有的类重新实现新增实现,并重新通过类似 registerChartView()registerSeriesModel() API 注册新的实现。

  • 优势
    • 成本较低
  • 劣势
    • 不易维护,尤其是源码依赖很多的情况下
    • 侵入性强

谨慎采用,新增实现的情况可以考虑采用,但对原有的实现进行重新实现时要慎重考虑,尤其是依赖很多的情况。

💡 开发案例

  • axis
    • 添加轴内边距的配置 dvPadding
    • 添加数值轴的分割线数量配置 dvSplitLineNumber
    • 添加数值轴多轴对齐时 0 刻度对齐的配置 dvAlignZeroTick
  • tooltip
    • 添加立即隐藏的 action immediatelyHideTip 调用的方法实现

扩展机制:生命周期 API

信息
  • 适用情况
    • 独立功能点扩展
    • 复杂需求
  • 不适用情况
    • 要扩展的部分依赖关系复杂

可以利用暴露出来的生命周期 API 在各个阶段实现相应的逻辑,对原有的程序执行结果进行微调或者重新执行得到新的结果。

  • 优势
    • 成本一般
    • 灵活性高
    • 侵入性低,易维护
  • 劣势
    • 需要对源码比较熟悉,可能需要重新复写源码中本身已经实现的逻辑

建议采用。

💡 开发案例

  • axis
    • 添加轴的首尾标签向内偏移对齐刻度线的配置 axisLabel.dvAlignEdge
  • axisPointer
    • 添加没有渲染任何数据内容时不显示的配置 dvShowOnlyWhenRenderingContent
  • tooltip
    • 添加立即隐藏的 action immediatelyHideTip
  • 矩形树图
    • 对标签进行功能点扩展开发
  • 折线图(钱晓东)
    • 添加呼吸点动画的配置 dvShowBreathPoint
  • 蜂群图组件(李少杰)
    • 添加新的布局算法实现

扩展机制:自定义系列

信息
  • 适用情况
    • 一个完整的系列扩展
    • 复杂的视觉渲染需求
  • 不适用情况
    • 独立功能点扩展
    • 有额外的交互、动画定制需求

如果组件的视图层很复杂,没有现有的图表类型能实现,可以考虑使用自定义系列来实现,其可以完全针对视图层自由的进行实现。

  • 优势
    • 元素渲染逻辑的灵活性很高
    • 部分 ECharts 提供的能力开箱即用
  • 劣势
    • 成本较高
    • 需要对 ZRender 的 API 很熟悉
    • 处理除元素渲染以外的需求受限性比较强,比如定制动画等

建议采用。

💡 开发案例

  • 标记图组件(钱晓东)
    • 对视觉渲染、布局有很强的定制化需求
  • 词云组件(李少杰)
    • 对布局有很强的定制化需求
  • 动态气泡图组件(李少杰)
    • 对视觉元素有很强的定制化需求

扩展机制:图表插件

信息
  • 适用情况
    • 一个完整的系列扩展
    • 复杂定制化需求
  • 不适用情况
    • 独立功能点扩展

我们可以通过类似 registerChartView()registerSeriesModel() API 注册一个新类型的图表实现。

  • 优势
    • 代码灵活性非常高
    • 可实现任意需求功能
  • 劣势
    • 成本很高
    • 需要熟悉 ECharts 源码设计和 ZRender API

建议采用。

💡 开发案例

  • 双向树图组件(王渊)
    • 对视觉渲染、布局、交互均有很强的定制化需求
  • 动态排名图组件(钱晓东)
    • 对视觉渲染、布局、交互、动画均有很强的定制化需求

典型场景的解决方案

针对一些典型场景的问题,这里给出参考方案,一般可直接使用,属于行业通用做法或者惯例。

TL;DR

动画

影响因素/场景类型周期(关键帧)动画(连续)帧动画
开发成本
关注点/适用性
动画控制
- 动画循环可选择外部循环实现仅由内部循环实现
- 播放开始位置:当前周期/下一个周期当前位置
- 暂停完成当前周期动画后暂停立即当前位置
- 跳转指定周期的开始位置任意位置
组件动画
离散数据轴-
连续数据轴

布局算法

布局一般来说都有现成的算法可以支持,例如 D3.js 提供的力导向算法。这里列举一下目前涉及到的布局算法:

  • ECharts 内部已经实现的一些布局算法,例如树的数据
  • D3.js 的力导向布局、包布局算法等
  • 碰撞检测,根据业务需求已经实现的算法
  • 词云布局算法

如果引入第三方包来实现布局算法,可以利用 registerLayout() API 注册新的布局实现。

💡 开发案例

  • 双向树图组件(王渊)
    • 复用了 ECharts 中已经实现的单向树组件的布局算法
  • 词云组件(李少杰)
    • 词云布局算法
  • 标记图组件(钱晓东)
    • 碰撞检测算法
  • 蜂群图组件(李少杰)
    • 引用 D3.js 力导向布局算法

动画

类比 D3.js 和一些动画系统的设计,动画的实现一般分为两类:

  • 常规线性插值

常规的线性插值动画一般工具库都有默认的支持,仅需要指定几个关键帧的动画状态即可,例如 ECharts 中的关键帧动画,实际上这是对 ZRender when API 的一个抽象的声明式设计。

  • 自定义插值实现

而自定义插值实现的动画实际上需要我们提供一个每一帧都运行的回调函数来执行视图更新的操作,ZRender during API 提供了这个能力,对应的 ECharts 配置项为 during

以上是实现动画的两种方式和相应的 ZRender API 介绍,均是基于 ECharts 提供的配置项。如果我们使用 ZRender 自己编写动画逻辑则直接使用相关 API 即可。

💡 开发案例

  • 桑基图组件(熊鹏)
    • 在视图层基于 ECharts 的数据 diff 机制添加进场、更新、退场动画实现
  • 折线图(钱晓东)
    • 添加呼吸点动画的配置 dvShowBreathPoint