@逍为(hustcc)

背景

G2 的 View 是一个容器,它具备有自己的容器大小,容器中可以放置:

  • 子 view
  • Geometry 图形语法标记
  • Component 辅助组件

View 可以构造一个树形的结构。每个节点中可能存在有几何标记 Geometry 和组件 Component。

  • Geometry:折线图、柱形图 …
  • Component:axis、legend …

布局是对画布空间的管理和划分。包括两部分:

  • view 嵌套的布局
    • viewBBox:view 的实际绘图大小,除去 padding 之后的
  • view 内部组件的布局
    1. coordinateBBox:Geometry 的绘图大小
    2. Component 的位置信息

View 嵌套布局

最终计算的是 viewBBox 大小。

view 的嵌套布局会用到的场景:

  1. 分面
  2. 手动创建多 view(常在自定义场景,比如:组合饼图 / 字母饼图)

view 的布局主要通过两个机制:

  • view region
  1. export interface Region {
  2. readonly start: Point;
  3. readonly end: Point;
  4. }

确定了 view 的大小范围,start、end 中均值 0 ~ 1 的相对父 view 大小的比例。暂时不去支持按照绝对像素划分,后续按情况而定。

例如:

  1. view.createView({
  2. region: {
  3. start: { x: 0, y: 0 },
  4. end: { x: 1, y: 0.5 },
  5. }
  6. })

创建一个子 view,子 view 的占据的位置做父 view 的左半边。

  • view padding

默认为 0,指定 view 的内边距,和 dom 的概念是一样的。

image.png

view 的大小除去 padding 的大小,剩余的空间用来绘制 view 中的 Component 和 Geometry 组件。

view 布局的过程在 initial 阶段就完成了,结合父 view 的 viewBBox 以及子 view 的 region + padding 进行计算。这个过程不存在什么问题。

组件布局

组件布局的过程是在 viewBBox 的区域内,布局当前 view 中的组件和 Geometry。最终得到的是:

  • coordinateBBox:Geometry 的绘图大小
  • Component 的位置信息

约束

  • 技术概念约束

从技术概念上来看,Geometry 是整个的绘制图形的区域,会占用 Component 之后所有的区域面积。

  1. Geometry 尽可能占用 Component 之外的所有空间,由 coordinate 来处理空间。
  2. x Axis 的宽度 = Geometry 的宽度
  3. y Axis 的高度 = Geometry 的高度
  • 视觉设计约束

组件占用区域宽度或者高度不超过整体 viewBBox 的 25%。

过程

整个组件布局过程,是在 layout 阶段执行一个 layoutFunc 完成,这个 layoutFunc 可以由外部进行自定义。G2 会内置一个 defaultLayout。

PS:每个 view 都有自己的 layoutFunc,他们默认都是同一个 defaultLayout,可以按照特殊情况去指定 view 自己特定的 layout。

  1. 初始化 coordinateBBox = viewBBox
  2. 计算最大的组件空间位置
    1. widht = viewBBox.width * 0.25
    2. height = viewBBox.height * 0.25
  3. 布局 legend 类型组件
    1. 根据 direction 方位来判断 top、right、bottom、left 四个方位的组件数量,每个方向的组件平分 width、height 区域作为他的最大宽高,如果有指定宽高,则按照指定的宽高来分配
    2. 依次处理 top、right、bottom、left 四个方位的组件
      1. 根据 coordinateBBox 以及组件的 direction 将组件 move 到对应的 x、y 位置
      2. 裁剪到组件的空间,形成新的 coordinateBBox
  4. 布局其他自定义组件(逻辑和 legend 基本类似,都是固定宽高
  5. 布局 axis 类型组件
    1. 如果 x axis 没有指定高度、y axis 没有指定宽度(相当于是 auto),那么根据 axis 不遮挡的原则,计算 x axis 的 height,y axis 的 width。
    2. 更新 coordinateBBox,除去 x axis 的 height,y axis 的 width,更新 x,y,width,height。
    3. coordinateBBox.width 作为 x axis 的 width,coordinateBBox.height 作为 y axis 的 height。
    4. 依据紧贴 coordinateBBox 的区域的原则,更新 axis 的 x,y 位置,并进行 move 操作。

完成。达成:

  • 计算出最终的 coordinateBBox;
  • 所有的 Component 都有对应的宽高以及精准的 x,y 信息;

View + Facet 布局流程图

@逍为(hustcc) TODO

问题 & 脑洞

  1. 组件共享 0.25 宽高

如何让多个组件共享 0.25 的最大宽度:

  1. 都不超出
  2. 一个超出
  3. 都超出

且 legend 组件的大小之后在绘制之后才知道具体的高度,如果要做好 0.25 空间的分配,一定会有大量的尝试性重绘。

建议简化逻辑,每个组件最大宽高都是 0.25 区域大小。因为实际情况出现一个方向多个组件的情况并不多。

  1. 约束布局

view 是一个固定空间的大小的区域,在这个区域中的组件 x、y、width、height 之间互相有关系,这种情况下,非常适合使用约束布局的算法。

可以拿一个开源的 约束布局 算法来测试看看,性能、效果、代码维护上是否符合要求。

约束布局参考:https://yuque.antfin-inc.com/ii/monthly/ub66ob#yuNQF