G2 代码示例运行全流程

2023-03-13  本文已影响0人  VioletJack

G2 源码版本 4.2.5

示例

以下是官网的一个柱状图实例:

import { Chart } from '@antv/g2'

const data = [
  { year: '1951 年', sales: 38 },
  { year: '1952 年', sales: 52 },
  { year: '1956 年', sales: 61 },
  { year: '1957 年', sales: 145 },
  { year: '1958 年', sales: 48 },
  { year: '1959 年', sales: 38 },
  { year: '1960 年', sales: 38 },
  { year: '1962 年', sales: 38 },
]

const chart = new Chart({
  container: 'container',
  autoFit: true,
  height: 500,
})

chart.data(data)
chart.scale('sales', {
  nice: true,
})

chart.tooltip({
  showMarkers: false,
})
chart.interaction('active-region')

chart.interval().position('year*sales')

chart.render()

下面,简单说下以上代码的源码实现:

1. 生成 chart 实例

G2 向外暴露了 Chart 构造函数,我们通过 Chart 函数生成 chart 对象实例。

const chart = new Chart({
  container: 'container',
  autoFit: true,
  height: 500,
})

在源码中,Chart 和 View、Event 都是直接导出暴露给用户的:

export { Chart, View, Event } from './chart'

下面是视图类的继承关系,其中 Base 类只有少数公共函数,Chart 类只有一些图表特有函数,核心逻辑都基本都在 View 中的。

export default class Chart extends View {}
export class View extends Base {}

在 Chart 中着重要说的配置项是渲染引擎的选择

const G = getEngine(renderer)

const canvas = new G.Canvas({
  container: wrapperElement,
  pixelRatio,
  localRefresh,
  supportCSSTransform,
  ...size,
})

这个 G 其实就是蚂蚁自家的 G 引擎,它可以分别用 canvas 和 svg 来渲染图形。

// 注册 G 渲染引擎
import * as CanvasEngine from '@antv/g-canvas'
import * as SVGEngine from '@antv/g-svg'
import { registerEngine } from './core'

registerEngine('canvas', CanvasEngine)
registerEngine('svg', SVGEngine)

除了渲染引擎比较特殊外,其他文档中提到的属性和函数都可以在 Chart 和 View 中发现。

2. 对 Chart 实例进行配置

chart.data(data)
chart.scale('sales', {
  nice: true,
})

chart.tooltip({
  showMarkers: false,
})
chart.interaction('active-region')

以上这些行为看似是在操作图表,实则都是在更新 chart 实例中的一些配置

3. 通过几何图形函数生成图形信息

在第一步中我们提到,大多数的图表的函数都是在 View 类中的。但也有特例,那就是几何图形(Geometry)。它们是通过注册的方式绑定到 View 类上的。

registerGeometry('Polygon', Polygon)
registerGeometry('Interval', Interval)
registerGeometry('Schema', Schema)
registerGeometry('Path', Path)
registerGeometry('Point', Point)
registerGeometry('Line', Line)
registerGeometry('Area', Area)
registerGeometry('Edge', Edge)
registerGeometry('Heatmap', Heatmap)
registerGeometry('Violin', Violin)

/**
 * 注册 geometry 组件
 * @param name
 * @param Ctor
 * @returns Geometry
 */
export function registerGeometry(name: string, Ctor: any) {
  // 语法糖,在 view API 上增加原型方法
  View.prototype[name.toLowerCase()] = function (cfg: any = {}) {
    const props = {
      /** 图形容器 */
      container: this.middleGroup.addGroup(),
      labelsContainer: this.foregroundGroup.addGroup(),
      ...cfg,
    }

    const geometry = new Ctor(props)
    this.geometries.push(geometry)

    return geometry
  }
}

可以看到,Geometry 构造函数的实例化是绑定在 Prototype 上的。当 View 函数通过 new 实例化后,Geometry 的函数就绑定在了实例对象的原型链上了。所以 chart.interval().position('year*sales') 其实可以拆分成下面这样:

const interval = chart.interval() // return new Interval(props)
interval.position('year*sales')

那么这个 position 函数干了什么呢?

export default class Interval extends Geometry {}

export default class Geometry<S extends ShapePoint = ShapePoint> extends Base {
  /** 图形属性映射配置 */
  protected attributeOption: Record<string, AttributeOption> = {};

  public position(cfg: string | string[] | AttributeOption): Geometry {
    let positionCfg = cfg;
    if (!isPlainObject(cfg)) {
      // 字符串字段或者数组字段
      positionCfg = {
        fields: parseFields(cfg),
      };
    }

    const fields = get(positionCfg, 'fields');
    if (fields.length === 1) {
      // 默认填充一维 1*xx
      fields.unshift('1');
      set(positionCfg, 'fields', fields);
    }
    set(this.attributeOption, 'position', positionCfg);

    return this;
  }
}

其实也是解析了属性信息,并保存到 attributeOption 对象中。

另外,函数最后通过 return this 来实现一系列的链式调用。

chart
  .interval()
  .adjust('stack')
  .position('time*value')
  .color('type', ['#40a9ff', '#1890ff', '#096dd9', '#0050b3'])

4. 真正的图表渲染

直到执行 chart.render() 这一句,G2 才真正开始进行渲染工作。

  /**
   * 生命周期:渲染流程,渲染过程需要处理数据更新的情况。
   * render 函数仅仅会处理 view 和子 view。
   * @param isUpdate 是否触发更新流程。
   * @param params render 事件参数
   */
  public render(isUpdate: boolean = false, payload?: EventPayload) {
    this.emit(VIEW_LIFE_CIRCLE.BEFORE_RENDER, Event.fromData(this, VIEW_LIFE_CIRCLE.BEFORE_RENDER, payload));
    // 递归渲染
    this.paint(isUpdate);

    this.emit(VIEW_LIFE_CIRCLE.AFTER_RENDER, Event.fromData(this, VIEW_LIFE_CIRCLE.AFTER_RENDER, payload));

    if (this.visible === false) {
      // 用户在初始化的时候声明 visible: false
      this.changeVisible(false);
    }
  }

这里的 emit 函数是用来上报 Chart / View 生命周期事件 的,而从生命周期钩子也可以看出 this.paint() 函数就是渲染的关键。

  protected paint(isUpdate: boolean) {
    this.renderDataRecursive(isUpdate);

    // 处理 sync scale 的逻辑
    this.syncScale();

    this.emit(VIEW_LIFE_CIRCLE.BEFORE_PAINT);

    // 初始化图形、组件位置,计算 padding
    this.renderPaddingRecursive(isUpdate);
    // 布局图形、组件
    this.renderLayoutRecursive(isUpdate);
    // 背景色 shape
    this.renderBackgroundStyleShape();
    // 最终的绘制 render
    this.renderPaintRecursive(isUpdate);

    this.emit(VIEW_LIFE_CIRCLE.AFTER_PAINT);

    this.isDataChanged = false; // 渲染完毕复位
  }

最后

到此,一次 G2 示例的渲染算是已经完成了。关于 G2 的具体渲染实现我们下期再聊。

上一篇 下一篇

猜你喜欢

热点阅读