5

可视化引擎antv系列(一)

 2 years ago
source link: https://segmentfault.com/a/1190000040405995
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

5年前的时候,接触过一点可视化开发,因为要做用户画像,那个时候很自然了选择了百度的echarts,感觉很酷炫,很好用,纯配置写法。当时可选择的可视化引擎大概有echarts,d3,hightcharts等,相比,echarts文档最全,最易上手,功能也最强大。不过大概也就是用用api的程度,就没有然后了,感觉做的东西还花里胡哨的很好看。

时隔5年,再次开始接触到可视化,发现多了些新的选择,时代在变化。这次满脑子都是阿里开源的antv系列。

为什么是antv?

因为它真的很好看,我们的设计师也很喜欢。
啊哈。
因为它的简介写的非常棒

G2 是一套基于图形语法理论的可视化底层引擎,以数据驱动,提供图形语法与交互语法,具有高度的易用性和扩展性。
这根本看不懂在说啥呀。。。
嗯,很有生活美感 和 哲学的味道,整了一大推的术语和理念。
哦,原来可视化不仅仅是渲染,还有底层理论支撑?
还有什么比新事物更吸引人的呢

刚接触这个,文档看起来很完善,排版也很清晰。
但是想搞懂还是比较难得,起码得读3遍吧,非常多的 术语 和 概念。
当你认为你懂了一些去开发的时候,会发现文档很不完善。。。
很多东西文档并没有写出来,需要去源码里寻找,而且社区还不是很完善。

antv家族

很完善的一整套解决方案。

react技术栈系列

由下至上,技术架构链条如下:
G ---> G2 ---> G2Plot ---> Ant Design Charts
当然G绘图引擎底层还有其他库.
我们从上到下,先做个大方向的技术分析,对整套技术架构有个基本的认知。

Ant Design Charts

这个非常简单,就是对G2Plot封装了一个 React版本,通用一个useChart的hook完成,通过props透传属性。
优点如下:

  • 进一步封装了G2Plot的api,弱化底层逻辑
  • 扩展了一些额外的能力
const PieChart = forwardRef((props: PieConfig, ref) => {
  const {
    chartRef,
    style = {
      height: 'inherit',
    },
    className,
    loading,
    loadingTemplate,
    errorTemplate,
    ...rest
  } = props;
  const { chart, container } = useChart<G2plotPie, PieConfig>(G2plotPie, rest);
  useEffect(() => {
    getChart(chartRef, chart.current);
  }, [chart.current]);
  useImperativeHandle(ref, () => ({
    getChart: () => chart.current,
  }));
  return (
    <ErrorBoundary errorTemplate={errorTemplate}>
      {loading && <ChartLoading loadingTemplate={loadingTemplate} />}
      <div className={className} style={style} ref={container} />
    </ErrorBoundary>
  );
});

export default PieChart;

然后在组件初始化后实例化 G2Plot实例即可,其他做一个事件传递工作。

 useEffect(() => {
    if (!container.current) {
      return () => null;
    }
    processConfig();
    const chartInstance: T = new (ChartClass as any)(container.current, {
      ...config,
    });
 }, [])

当然,如果有不满足需求的地方,需要操作G2Plot实例,依然可以通过 ref 等方式来获取g2plot的实例,直接调用其属性和方法。

G2Plot

G2本身是声明式语法,使用起来还是有点琐碎,G2Plot封装了G2的指令语法,通过 配置式的方式使用,大大简化了上手成本。本质就是把json配置转化成G2命令语法。

G2Plot的技术实现架构特别好,逻辑清晰,层次非常分明,通用性极高。

所有图形类都继承自 base 基类。
代码如下:非常简单,拿到参数配置,实例化G2,绑定事件。
image.png

然后再看一个图形类的封装,比如柱状图:
image.png
很简单吧,其他图形基本都是这个模式的,核心只需要实现两个点:

  • 适配器
    参数配置没啥好讲的,核心看下 适配器:
    image.png
    这里的flow实质就是一个类似高阶函数reduce的聚合操作,核心逻辑就是 函数式编程的 组合方式。本质就是流式传输参数options和实例chart,去调用转换各个环节的api。

G2也比较意思,它依赖底层绘图引擎G,所以它本身也是隐藏了绘图api命令,做了一个参数的转换操作。

/**
 * Chart 类,是使用 G2 进行绘图的入口。
 */
export default class Chart extends View {}

/**
 * G2 视图 View 类
 */
export class View extends Base {}

/**
 * G2 Chart、View、Geometry 以及 Element 等的基类,提供事件以及一些通用的方法。
 */
export default class Base extends EE {}

EE 就是一个发布订阅的事件类

看上面的继承逻辑,已经很清晰了表达了各个类处理的事情。这里面需要理解view容器层的概念,后续再做详细解释。

接着往下看,比较有意思的public 方法api。
image.png
认真看这些方法的调用,仅仅是进行了 options的set操作,然后就没有然后了。
看到这里,会疑惑,那G2到底在哪里进行界面绘制呢?我们看render函数:
image.png
这个方法进行真正的递归绘制,内部调用G引擎,各组件维护自身绘制,细节后续再讲。

到了这里产生了一个疑惑,既然需要调用render函数才能绘制,那么交互动作并没有调用render是如何实现界面重绘的呢?

交互的绘制时机

在找答案之前,先思考了一番,以为它是通过循环机制来控制的,因为我之前搞游戏开发就是这个模式。
结果只猜对了一半,G引擎的实现真的很机智,找了很久才找到,如下:
image.png
image.png
竟然使用 监听模式来启动 帧频绘制,而不是传统意义上的主循环,这对性能是个很大的提升。
里面还涉及了局部绘制,这个逻辑就相当复杂了。
这个运用之妙,自省体会。
下期再见!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK