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’ }

  1. G2 5.0 一方面会将内部的这套规范直接暴露给用户,让用户可以更加灵活的使用 G2。另一方面会对其进行全面增强,在更加声明式的基础上,除了解决 4.0 难解的问题之外,还会带来全新的特性**。**
  2. <a name="vidg5"></a>
  3. ## 目标
  4. 在设计 G2 5.0 Specification 的时候,主要有以下的目标:
  5. **报表搭建能力增强**:可以绘制更多图表,图表的表达力更强,可以绘制更加优雅的图表。
  6. - 更多几何元素(Geometry):TextImage
  7. - 更多比例尺(Scale):ThresholdQuantileQuantize,同时比例尺和几何元素的通道绑定,而不是和数据字段绑定。
  8. - 更丰富的视觉表现能力(Palette):更多的色板,甚至更智能的色板。
  9. - 更多坐标系(Coordinate):FisheyeParallel
  10. - **更强的标注(Annotation)**:和几何元素拥有相同的能力,同时用 Text 替代几何元素的 Label
  11. - 更丰富的视觉表现力:纹理(Pattern)和手绘风格的渲染器(Sketchy Renderer)。
  12. - **新增视图复合能力**:通过 Flex 布局去搭建 Dashboard
  13. **交互式的数据探索:**声明简单的配置就可以从不同角度去看数据(默认“好看”),高效获得不同的数据洞察。
  14. - 加入数据转换能力(Transform):FetchFilterBySortByWordCloud 等。
  15. - 加入统计能力(Statistic):StackYSymmetryYBin 等。
  16. - 全新的推断能力(Infer):推断比例尺(种类、值域、定义域、配置),统计函数。
  17. - 更强的分面的能力(Facet):可以嵌套分面,甚至可以绘制出单元可视化等。
  18. **可视化叙事:**有意义的数据驱动的动画。
  19. - 过渡动画(数据驱动):动画属性可以和数据字段绑定,以及有意义的过渡动画。
  20. - 帧动画:Keyframe
  21. - 时序动画:Sequence
  22. 上面的很多增强其实是弥补和 Vega 的差距,但是有三点是 G2 Spec 独有或者更强大的地方:
  23. - 视图树:空间上和时间上组织视图,数据从根节点流向叶子结点,让时序动画、帧动画和单元可视化成为可能。
  24. - 动画属性和数据绑定:可以做更多有意义的过渡动画。
  25. - 坐标系:更加容易和灵活地去声明一些图表,比如平行坐标系、极坐标系下的一些图表。
  26. > 目前只是简单的定义,后续会在开发中逐渐完善
  27. <a name="y26fK"></a>
  28. ## 视图树(View Tree)
  29. 理解 G2 Spec 有两个关键点:第一个就是它描述的是一棵**视图树**。(类似如下的结构)
  30. ```typescript
  31. type Tree = {
  32. type?: string, // 节点类型
  33. children?: Tree[], // 孩子
  34. [key: string]: any, // 其余属性
  35. };

它很类似于前端开发中的 DOM 树和渲染引擎里面的场景图,但是又有几点不同:

  • 节点类型不同:DOM 树的每一个元素是一个 DOM,渲染引擎里面是一个基本图形,这里是一个视图(View)。
  • 和数据绑定:DOM 树和场景图本身没有和数据绑定,但是视图树的每一节点都有绑定的数据,子节点会按照指定的方式从继承父亲节点的数据。
  • 时间上组织视图:DOM 树和场景图都是只是在空间上组织视图,但是这里同样会在时间上组织试图,让声明叙事可视化成为可能。

所以视图树的作用为:在时间上和空间上组织视图,并且每一个节点会按照指定的方式划分 data domain、space domain 和 time domain,然后被孩子节点继承。

  1. type LayoutOptions = {
  2. width?: number,
  3. height?: number,
  4. paddingLeft?: number,
  5. paddingRight?: number,
  6. paddingBottom?: number
  7. };
  8. type ThemeOptions = {
  9. type?: string,
  10. //...
  11. };
  12. type ViewTree = Container | View;
  13. 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; statistic?: Statistic[]; scale?: Record; coordinate?: Coordinate[]; adjust?: Adjust[]; component?: GuideComponent[]; style?: Record; animate?: Record; interaction?: Interaction[]; };

type View = LayoutOptions & InferOptions & EncodeableOptions;

  1. ```typescript
  2. type Transform = {
  3. type?: string;
  4. [key: string]: any
  5. };
  6. type FieldEncode = { type?: 'field'; value?: string };
  7. type TransformEncode = { type?: 'transform'; value?: string };
  8. type ConstantEncode = { type?: 'constant'; value?: string };
  9. type StatisticEncode = { type?: 'statistic'; value?: string };
  10. type Encode = FieldEncode | TransformEncode | ConstantEncode | StatisticEncode;
  11. type Statistic = {
  12. type?: string;
  13. [key: string]: any;
  14. }
  15. type Scale = {
  16. type?: string;
  17. domain?: any[];
  18. range?: any[];
  19. palette?: Palette;
  20. [key: string]: any;
  21. }
  22. type Palette = {
  23. type?: string;
  24. [key: string]: any;
  25. }
  26. type Coordinate = {
  27. type?: string;
  28. [key: string]: any;
  29. };
  30. type Adjust = {
  31. type?: string;
  32. [key: string]: any;
  33. }
  34. type GuideComponent = {
  35. type?: string;
  36. channel?: string;
  37. display?: boolean;
  38. position?: 'left' | 'right' | 'top' | 'bottom';
  39. [key: string]: any;
  40. }
  41. type Interaction = {
  42. type?: string | 'custom',
  43. showEnable?: any[];
  44. start?: any[];
  45. processing?: any[];
  46. end?: any[];
  47. rollback?: any[];
  48. };
  49. type Step = {
  50. trigger?: string,
  51. action?: Action[];
  52. };
  53. type Action = {
  54. type?: string;
  55. [key: string]: any;
  56. };

容器(Container)

  1. type Container = Spatial | Temporal;

空间容器(Spatial Container)

  1. type Spatial = Layout | Facet;

布局容器(Layout Container)

  1. type Layer = EncodeableOptions & LayoutOptions & {
  2. type?: 'layer',
  3. children?: (Container | View)[];
  4. }
  5. type Flex = EncodeableOptions & LayoutOptions & {
  6. type?: 'flex';
  7. direction?: [];
  8. flex?: number[];
  9. children?: (Container | View)[];
  10. padding?: number;
  11. };
  12. type Layout = Layer | Flex;

分面容器(Facet Container)

  1. type Facet = {
  2. type?: 'rect' | 'matrix',
  3. data?: any;
  4. children?: (Container | View)[];
  5. padding?: number;
  6. [key: string]: any;
  7. };

时间容器(Temporal)

  1. type Temporal = Keyframe | Sequence;

关键帧容器(Keyframe)

  1. type Keyframe = {
  2. type?: 'keyframe';
  3. duration?: number;
  4. slice?: number[];
  5. children?: (Spatial | View)[];
  6. [key: string]: any;
  7. };

分时容器(Sequence)

  1. type Sequence = {
  2. type?: 'sequence',
  3. duration?: number;
  4. data?: any;
  5. children?: (Spatial | View)[];
  6. [key: string]: any;
  7. };