G绘图引擎源码解读

1,152 阅读4分钟

前言

最近一段时间一直在学习可视化相关的知识,除了理论知识外,目前实际业务中最常用的可视化工具也需要加强学习,就比如Bizcharts、Bizcharts依赖的的G2,但是说到最核心的一层,还是得回到G绘图引擎,G绘图引擎也是G2、G6所依赖的基础,其中也对外暴露了许多来自G引擎的Api,因此熟悉好G的使用,对于掌握其他可视化库都是很有帮助的。

G源码解读

我这里学习的是G 3.5-prepare版本源码,也是3.0版本G中首先使用TS构建的版本,便于阅读。以下是我构建的源码中主要类的UML图,可以发现源码中使用了非常多继承关系,个中关系还是比较清晰的。

核心用法解读

Element元素

首先是Element,从上面的UML图可以看出的,Element类是Shape、Canvas、以及Group的共同父类,从这里其实可以得出,文档中指的Element是一个虚拟概念,即Shape、Group以及他们对应的子类Line、Rect、Canvas等,都是属于Element,那么Element的属性和方法在以上类的实例上都可以进行调用。

Container 容器

Container这个概念在文档中并没有详细的描述,但阅读源码不难发现,Container的TS接口定义如下:

export interface IContainer extends IElement {
...

很明显了,Container本质上也是Element元素,但Container其实是一种抽象的描述,落实到具体,属于Container类型的有两种元素

  • Canvas实例,也就是使用new Canvas()构建出来的最外层Canvas容器对象,它属于Container

    export interface ICanvas extends IContainer { ... }

  • addGroup方法的放回对象,addGroup()方法会返回一个Group类型对象,该Group对象的Ts接口定义如下

    export interface IGroup extends IElement, IContainer { /** * 是否是实体分组,即对应实际的渲染元素 * @return {boolean} 是否是实体分组 */ isEntityGroup(): boolean; }

可以看到,Group类型对象是属于Container的,同时也是一种Element。

Canvas 画布

Canvas 画布,这个概念比较好理解,也就是上图中的Canvas类,在实际开发中,创建一个Canvas类的实例也是进行绘图的第一步,同时Canvas方法会根据render属性的配置,决定是使用canvas或者svg来进行绘图。

export type Renderer = 'canvas' | 'svg';

每创建一个独立的图表,就会创建一个独立的Canvas实例

const chart = new Canvas({
  container: 'container',
  width: 600,
  height: 500,
});

Group 图形分组

Group在上图的UML图中也就是Group类,可以看到,Group也是Element类型。然后Group对象的Ts接口定义如下:

export interface IGroup extends IElement, IContainer {
    /**
     * 是否是实体分组,即对应实际的渲染元素
     * @return {boolean} 是否是实体分组
     */
    isEntityGroup(): boolean;
}

可以看到,Group对象继承了Elemnt以及Container的类型定义,所以文档中对Group的使用也是非常直接了当,直接让我们使用Element和Container的属性与方法即可。

除此之外,额外定义了一个方法isEntityGroup,这个方法的实现其实很简单,如果是使用SVG渲染模式,那么会返回true,因为在Svg中,会使用实体标签 来包裹一个分组,相反若使用Canvas模式,那么会直接返回false,因为Canvas中并没有实际的图形分组概念。

在G2中,Group的最直接用处就是用在初始化一个图表的时候,一共会先创建3个Group,然后对其进行叠加,形成最终的图表

// 调用 view 的创建
    super({
      parent: null,
      canvas,
      // create 3 group layers for views.
      backgroundGroup: canvas.addGroup({ zIndex: GROUP_Z_INDEX.BG }),
      // 图表最后面的容器
      middleGroup: canvas.addGroup({ zIndex: GROUP_Z_INDEX.MID }),
      // 图表所在的容器
      foregroundGroup: canvas.addGroup({ zIndex: GROUP_Z_INDEX.FORE }),
      // 图表前面的容器
      padding,
      appendPadding,
      visible,
      options,
      limitInPlot,
      theme,
      syncViewPadding,
    });

在G2中使用registerShape方法注册自定义图形时,一般也是在已有Container上使用addGroup方法添加新分组,并在新分组中添加图形,最后将新Group返回进行叠加。

// 自定义Shape 部分
registerShape('point', 'pointer', {
  draw(cfg, container) {
    const group = container.addGroup({});
    // 获取极坐标系下画布中心点
    const center = this.parsePoint({ x: 0, y: 0 });
    // 绘制指针
    group.addShape('line', {
      attrs: {
        x1: center.x,
        y1: center.y,
        x2: cfg.x,
        y2: cfg.y,
        stroke: cfg.color,
        lineWidth: 5,
        lineCap: 'round',
      },
    });
    group.addShape('circle', {
      attrs: {
        x: center.x,
        y: center.y,
        r: 9.75,
        stroke: cfg.color,
        lineWidth: 4.5,
        fill: '#fff',
      },
    });

    return group;
  },
});

Shape 图形

Shape在G中也就是Shape类,继承Shape,形成了Line、circle、Ellipse等基础图形。

使用方法:

group.addShape('circle', {
  attrs: {
    x: 300,
    y: 200,
    r: 100,
    fill: '#1890FF',
    stroke: '#F04864',
    lineWidth: 4,
    radius: 8,
  },
});