概述

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

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
竟然使用 监听模式来启动 帧频绘制,而不是传统意义上的主循环,这对性能是个很大的提升。
里面还涉及了局部绘制,这个逻辑就相当复杂了。
这个运用之妙,自省体会。
下期再见!


donglegend
910 声望82 粉丝

长安的风何时才能吹到边梁?


引用和评论

0 条评论