高级指引
VGrammar 作为图形语法,提供了非常强大的自定义的能力,方便用户去扩展,本教程将会介绍一些自定义拓展的能力
自定义 transform
实现
数据变换 tranform ,其实就是一个纯函数;我们要实现自定义的 transform,就是实现一下类型的纯函数:
export type IFunctionTransform<Options = any, Input = any, Output = any> = ( options?: Options, data?: Input, params?: Record<string, any>, view?: IView ) => Output | Promise<Output> | IProgressiveTransformResult<Output>;
所有的 transform 都接收四个参数:
- opionts 配置
- data 执行变换的数据
- params 配置数据变换的语法元素,依赖的其他语法元素的结果
- view 全局的 VGrammar 可视化实例,一般只有少数布局算法,可能需要用到,不建议使用
以内置的filter transform 的实现为例:
export const filter = (
options: {
callback: (entry: any, params: any) => boolean;
},
data: any[],
parameters?: any
) => {
return data.filter(entry => {
return options.callback(entry, parameters);
});
};
注册
注册 transform 只需要调用Factory.registerTransform()进行注册
示例:
import { Factory } from '@visactor/vgrammar';
Factory.registerTransform('filter2', {
transform: filter,
markPhase: 'beforeJoin'
});
使用
注册完毕,可以和内置的 transform 类似使用,只需要传入需要的配置即可
{ data: [ { id: 'table', values: [{ a: 1 }], transform: [ { type: 'filter2', callback: datum => { return datum.a > 0; } } ] } ]; }
自定义组合图元
VGrammar 内置了一些常见的组合图元,当基础图元以及内置的组合图元均不能满足需求的时候,用户可以通过自定义组合图元的方式,来进行扩展
注册
以内置的wave组合图元的实现为例,在实现自定义组合图元的时候,首先我们需要注册一个全局唯一的name,以及组成图元的基础图元的类型和name;
例如wave图元是由三条面积填充线组成的,所以我们实现的代码如下:
import { Factory } from '@visactor/vgrammar'; const waveGlyphMeta = Factory.registerGlyph('wave', { wave0: 'area', wave1: 'area', wave2: 'area' });
接下来,我们可以设置组合图元需要支持的视觉通道,VGrammar 支持三种类型的视觉通道设置:
registerDefaultEncoder()设置子图元的默认图形属性registerChannelEncoder()设置自定义的图形通道,用户需要实现当组合图元设置了该视觉通道的时候,哪些子图元需要更新对应的图形属性registerFunctionEncoder()当组合图元的多个图形属性,最终只需要映射到某个子图元的单个属性的时候,可以注册函数类型的视觉通道解析函数;
registerDefaultEncoder() 和 registerChannelEncoder() 的实现示例如下:
waveGlyphMeta .registerChannelEncoder('wave', (channel, encodeValue, encodeValues, datum, element) => { const originPoints: IPointLike[] = new Array(21).fill(0).map((v, index) => { const waveHeight = index % 2 === 0 ? 20 : 0; return { x: -500 + 50 * index, y: encodeValues.y + waveHeight, y1: encodeValues.y + encodeValues.height }; }); const points0 = originPoints.map(point => { return { x: point.x + encodeValue * 100, y: point.y, y1: point.y1 }; }); const points1 = originPoints.map(point => { return { x: point.x + encodeValue * 200 - 40, y: point.y, y1: point.y1 }; }); const points2 = originPoints.map(point => { return { x: point.x + encodeValue * 300 - 20, y: point.y, y1: point.y1 }; }); return { wave0: { points: points0, x: 0, y: 0 }, wave1: { points: points1, x: 0, y: 0 }, wave2: { points: points2, x: 0, y: 0 } }; }) .registerDefaultEncoder(() => { return { wave0: { curveType: 'monotoneX', fillOpacity: 1 }, wave1: { curveType: 'monotoneX', fillOpacity: 0.66 }, wave2: { curveType: 'monotoneX', fillOpacity: 0.33 } }; });
registerFunctionEncoder() 的实现可以参考linkPath组合图元的实现:
linkPathGlyphMeta.registerFunctionEncoder( (encodeValues: LinkPathEncodeValues, datum: any, element: IElement, config: LinkPathConfig) => { const direction = encodeValues.direction ?? config?.direction; const parsePath = ['vertical', 'TB', 'BT'].includes(direction) ? getVerticalPath : getHorizontalPath; const isRatioShow = typeof encodeValues.ratio === 'number' && encodeValues.ratio >= 0 && encodeValues.ratio <= 1; const encodeChannels = Object.keys(encodeValues); // parse path when all required channels are included if (['x0', 'y0', 'x1', 'y1'].every(channel => encodeChannels.includes(channel))) { return { back: { path: isRatioShow ? parsePath(encodeValues, 1) : '' }, front: { path: parsePath(encodeValues, isRatioShow ? encodeValues.ratio : 1) } }; } return {}; } );
使用
组合图元注册完成后,就可以和内置的组合图元一样使用了:
{ marks: [ { type: 'glyph', glyphType: 'wave', encode: { update: { y: 100, height: 100, fill: 'DarkOrange', wave: 0 } } } ]; }
自定义语法元素
VGrammar 所有的语法元素,都是按照依赖关系进行运行的,当现有的语法元素不能满足要求的时候,用户可以试着实现自定义的语法元素;接下来以vgrammar-projection中实现的语法元素Projection为例,讲述如何自定义语法元素,并在 VGrammar 可视化图表中使用;
实现语法类
我们实现自定义的语法元素的时候,需要继承语法元素的基类GrammarBase,实现一下三个主要的方法:
parse(spec: CustomizedSpec)解析配置的方法,配置的类型需要定义evaluate(upstream: any, parameters: any)执行逻辑,在语法元素被执行的时候,会传入上游依赖的数据,和其他依赖参数output()返回语法元素的输出对象,会被下游节点获取到并执行后续的逻辑
示例:
import { GrammarBase } from '@visactor/vgrammar';
export class Projection extends GrammarBase implements IProjection {
readonly grammarType: GrammarType = 'projection';
private projection: any;
constructor(view: IView) {
super(view);
}
parse(spec: ProjectionSpec) {
super.parse(spec);
this.spec = mergeConfig(this.spec, spec);
this.attach(parseProjection(spec, this.view));
return this;
}
evaluate(upstream: any, parameters: any) {
if (!this.projection || this.projection.type !== this.spec.type) {
this.projection = create(this.spec.type);
this.projection.type = this.spec.type;
}
projectionProperties.forEach(prop => {
if (!isNil(this.spec[prop])) {
set(this.projection, prop, invokeFunctionType(this.spec[prop], parameters, projection));
}
});
if (!isNil(this.spec.pointRadius)) {
this.projection.path.pointRadius(invokeFunctionType(this.spec.pointRadius, parameters, projection));
}
if (!isNil(this.spec.fit)) {
const fit = invokeFunctionType(this.spec.fit, parameters, projection);
const data = collectGeoJSON(fit);
if (this.spec.extent) {
this.projection.fitExtent(invokeFunctionType(this.spec.extent, parameters, projection), data);
} else if (this.spec.size) {
this.projection.fitSize(invokeFunctionType(this.spec.size, parameters, projection), data);
}
}
return this.projection;
}
output() {
return this.projection;
}
}
注册
接下来只需要调用注册方法对自定义的语法元素进行注册:
import { registerGrammar } from '@visactor/vgrammar'; registerGrammar('projection', Projection, 'projections');
registerGrammar 接收三个参数:
- type: 唯一标志符,对应了语法元素实例中的
grammarType - grammarClass: 自定义语法类
- specKey: 可选参数;在 spec 模式下,申明语法元素的
key,注意,不能和内置的语法元素重复,如果不传则保持和type一致
使用
在 Spec 模式下,可以通过注册语法元素时候,申明的specKey进行设置
vGrammarView.parseSpec({ projections: [ { id: 'firstProjection', size: [10, 10] } ] });
在 API 模式下,可以通过customized() API 创建自定义的语法元素
vGrammarView .customized('projection', { size: [10, 10] }) .id('firstProjection');