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')
- 通过 data 函数来装载数据源到 chart.options 对象中。
- 通过 scale 和 tooltip 函数来保存配置信息到 chart.options 对象中。
- 通过 interaction 函数来创建交互示例,并保存到 chart.interactions 对象中。
以上这些行为看似是在操作图表,实则都是在更新 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 的具体渲染实现我们下期再聊。
网友评论