G2 5.0 将以 Specification 为架构核心,底层为渲染 Specification 为图的能力,上层为经典命令式 API 的封装。
以 Specification 为核心,可以让 G2 成为一个真正的数据可视化引擎,即可直接开箱即用,也可以二次封装,满足业务灵活需求,同时为未来智能可视化打下基础。
所以 Specification 是至关重要的一环,单独将它文档描述。
为什么
- 架构合理:Spec 一直都在代码中存在,只不过是否将它作为第一视角去开发。比如在 G2 4.0 中一直存在内部存储的 options 结构,但是因为一直以命令式 API 为第一视角,所以 options 结构非常凌乱。
- 智能可视化:一旦涉及到智能推荐,就需要设计一个媒介规范(DSL)去描述,spec 就是可视化领域的 DSL。
- 持久化存储、跨语言:G2 要支持 SSR,必然涉及到多语言的支持,到时候依然会需要设计一套 spec 去描述图表,这本质就是用一个可持久化的描述去实现跨语言。
- 生态建设:上层基于 spec 去封装 G2Plot,BizCharts 等比当前基于命令 API,要方便非常非常多。
背景
G2 5.0 Specification 不是一个新的东西,它本身就存在于 G2 4.0 的中。G2 4.0 经典命令式的 API 本身就有一套规范,会将 API 接受的参数按照规范拼接成一个对图表的描述,下面举一个简单的例子: ```javascript // API chart.interval().position(‘genre*value’).color(‘name’);
// 转换成下面的 options // 注:实际的格式可能和这个有所出入,但是大同小异 const options = { type: ‘interval’, positon: ‘genre*value’, color: ‘name’ }
G2 5.0 一方面会将内部的这套规范直接暴露给用户,让用户可以更加灵活的使用 G2。另一方面会对其进行全面增强,在更加声明式的基础上,除了解决 4.0 难解的问题之外,还会带来全新的特性**。**
<a name="vidg5"></a>
## 目标
在设计 G2 5.0 的 Specification 的时候,主要有以下的目标:
**报表搭建能力增强**:可以绘制更多图表,图表的表达力更强,可以绘制更加优雅的图表。
- 更多几何元素(Geometry):Text、Image。
- 更多比例尺(Scale):Threshold、Quantile、Quantize,同时比例尺和几何元素的通道绑定,而不是和数据字段绑定。
- 更丰富的视觉表现能力(Palette):更多的色板,甚至更智能的色板。
- 更多坐标系(Coordinate):Fisheye、Parallel。
- **更强的标注(Annotation)**:和几何元素拥有相同的能力,同时用 Text 替代几何元素的 Label。
- 更丰富的视觉表现力:纹理(Pattern)和手绘风格的渲染器(Sketchy Renderer)。
- **新增视图复合能力**:通过 Flex 布局去搭建 Dashboard。
**交互式的数据探索:**声明简单的配置就可以从不同角度去看数据(默认“好看”),高效获得不同的数据洞察。
- 加入数据转换能力(Transform):Fetch、FilterBy、SortBy、WordCloud 等。
- 加入统计能力(Statistic):StackY、SymmetryY、Bin 等。
- 全新的推断能力(Infer):推断比例尺(种类、值域、定义域、配置),统计函数。
- 更强的分面的能力(Facet):可以嵌套分面,甚至可以绘制出单元可视化等。
**可视化叙事:**有意义的数据驱动的动画。
- 过渡动画(数据驱动):动画属性可以和数据字段绑定,以及有意义的过渡动画。
- 帧动画:Keyframe
- 时序动画:Sequence
上面的很多增强其实是弥补和 Vega 的差距,但是有三点是 G2 Spec 独有或者更强大的地方:
- 视图树:空间上和时间上组织视图,数据从根节点流向叶子结点,让时序动画、帧动画和单元可视化成为可能。
- 动画属性和数据绑定:可以做更多有意义的过渡动画。
- 坐标系:更加容易和灵活地去声明一些图表,比如平行坐标系、极坐标系下的一些图表。
> 目前只是简单的定义,后续会在开发中逐渐完善
<a name="y26fK"></a>
## 视图树(View Tree)
理解 G2 Spec 有两个关键点:第一个就是它描述的是一棵**视图树**。(类似如下的结构)
```typescript
type Tree = {
type?: string, // 节点类型
children?: Tree[], // 孩子
[key: string]: any, // 其余属性
};
它很类似于前端开发中的 DOM 树和渲染引擎里面的场景图,但是又有几点不同:
- 节点类型不同:DOM 树的每一个元素是一个 DOM,渲染引擎里面是一个基本图形,这里是一个视图(View)。
- 和数据绑定:DOM 树和场景图本身没有和数据绑定,但是视图树的每一节点都有绑定的数据,子节点会按照指定的方式从继承父亲节点的数据。
- 时间上组织视图:DOM 树和场景图都是只是在空间上组织视图,但是这里同样会在时间上组织试图,让声明叙事可视化成为可能。
所以视图树的作用为:在时间上和空间上组织视图,并且每一个节点会按照指定的方式划分 data domain、space domain 和 time domain,然后被孩子节点继承。
type LayoutOptions = {
width?: number,
height?: number,
paddingLeft?: number,
paddingRight?: number,
paddingBottom?: number
};
type ThemeOptions = {
type?: string,
//...
};
type ViewTree = Container | View;
type G2Spec = ViewTree & LayoutOptions & ThemeOptions;
视图(View)
理解 G2 Spec 的第二个关键点就是:每一个视图是通过一系列可视化组件描述的,大部分组件都是用于数据转换的。
- transform
- encode
- statistic
- scale
- coordinate
- adjust
- component
- style
- animate
- interaction ```typescript type LayoutOptions = { width?: number, height?: number, paddingLeft?: number, paddingRight?: number, paddingBottom?: number };
type InferOptions = { axis?: boolean; tooltip?: boolean; filter?: boolean; grid?: boolean; legend?: boolean; stack?: boolean; };
type EncodeableOptions = {
data?: any;
transform?: Transform[];
encode?: Record
type View = LayoutOptions & InferOptions & EncodeableOptions;
```typescript
type Transform = {
type?: string;
[key: string]: any
};
type FieldEncode = { type?: 'field'; value?: string };
type TransformEncode = { type?: 'transform'; value?: string };
type ConstantEncode = { type?: 'constant'; value?: string };
type StatisticEncode = { type?: 'statistic'; value?: string };
type Encode = FieldEncode | TransformEncode | ConstantEncode | StatisticEncode;
type Statistic = {
type?: string;
[key: string]: any;
}
type Scale = {
type?: string;
domain?: any[];
range?: any[];
palette?: Palette;
[key: string]: any;
}
type Palette = {
type?: string;
[key: string]: any;
}
type Coordinate = {
type?: string;
[key: string]: any;
};
type Adjust = {
type?: string;
[key: string]: any;
}
type GuideComponent = {
type?: string;
channel?: string;
display?: boolean;
position?: 'left' | 'right' | 'top' | 'bottom';
[key: string]: any;
}
type Interaction = {
type?: string | 'custom',
showEnable?: any[];
start?: any[];
processing?: any[];
end?: any[];
rollback?: any[];
};
type Step = {
trigger?: string,
action?: Action[];
};
type Action = {
type?: string;
[key: string]: any;
};
容器(Container)
type Container = Spatial | Temporal;
空间容器(Spatial Container)
type Spatial = Layout | Facet;
布局容器(Layout Container)
type Layer = EncodeableOptions & LayoutOptions & {
type?: 'layer',
children?: (Container | View)[];
}
type Flex = EncodeableOptions & LayoutOptions & {
type?: 'flex';
direction?: [];
flex?: number[];
children?: (Container | View)[];
padding?: number;
};
type Layout = Layer | Flex;
分面容器(Facet Container)
type Facet = {
type?: 'rect' | 'matrix',
data?: any;
children?: (Container | View)[];
padding?: number;
[key: string]: any;
};
时间容器(Temporal)
type Temporal = Keyframe | Sequence;
关键帧容器(Keyframe)
type Keyframe = {
type?: 'keyframe';
duration?: number;
slice?: number[];
children?: (Spatial | View)[];
[key: string]: any;
};
分时容器(Sequence)
type Sequence = {
type?: 'sequence',
duration?: number;
data?: any;
children?: (Spatial | View)[];
[key: string]: any;
};