之前的布局逻辑中,过分强调参考 DOM 一套,提供强大灵活布局能力,但是忽略有可视化组件和图形的特殊性,以及一些场景的布局易用性。虽然也能达成,但是难用,舍本逐末。
场景
可视化图形中,除了基本单图图形,还有一些其他的场景,也是比较常见的,这些场景概括了来说,需要一个图形对齐的能力:
- 混合图形
混合图形的机制,都是通过多个图形叠加,保证位置大小一致的方式来绘制。比如双轴图,线面积混合图(例如:折线表问题趋势,面积表上下限问题趋势)
- 分面
根据一个或者多个维度,分面数据,拆分 view 画布空间,然后可以对比不同维值下面不同的指标关系。比如在 rect 分面中,会要求行列反向的图形不受 Axis 的影响,而保证对齐。
- 统计元素
使用图形作为统计元素的情况下,一定需要对齐,才有参考意义。
- …
方案
回归到图形的本体上来,图形 Geometry 才是可视化布局中的核心元素。
核心要点:
- view padding
依旧保持: view padding 是 Geometry 的 padding,也就是 viewBBox - padding = plotBBox = coordinateBBox。
padding 可以是 ‘auto’、nil、number、number[]。
- BBox
- viewBBox:整个画布大小(包括图形和组件的绘图空间),子 view 通过 region 来拆分父 view 的 plotBBox
- plotBBox:图形区域的大小,直接等于 coordinateBBox,仅保留一个变量即可。
plotBBox = viewBBox - padding
- 布局
- absolute padding
指定 padding 的情况下,就只是对应方向的剩余空间在 axis、legend 等分配而已。默认都是居中放置
会给组件传入 x、y、maxWidht、maxHeight。组件给出 getLayoutBBox()
- auto padding
auto 情况下,先根据各个方向的 LayoutBBox,计算出 padding 值。然后走 absolute padding 逻辑。
使用示例
const chart = new Chart({
padding: 48,
});
chart.line('x*y');
const v1 = chart.createView({
region: {
start: { x: 0, y: 1 },
end: { x: 0.5, y: 1 },
},
// padding: 'auto',
});
v1.line('x*y');
v1.point('x*y');
const v2 = chart.createView({
region: {
start: { x: 0.5, y: 1 },
end: { x: 1, y: 1 },
},
});
v1.line('x*y');
v1.area('x*ys');
chart.render();
这个例子中,v1 和 v2 平分 chart 的 plotBBox,然后各自进行 autoPadding,并绘制图形。另外,chart 层也有 x 轴 y 轴,然后放置到 48 的 padding 中。
如果要 v1, v2 对齐,则设置他们两个的 padding 一致即可。
实现
见代码 PR。
大概逻辑是在 layout 中:
- 增加 padding-cal 方法,用于获取 absolute padding
- 如果 padding 不是 auto,那么直接用已有设置的 padding
- 否则,通过组件的方位和 width、height,计算 出 padding
- 根据 absolute padding 和 viewBBox 计算出 plotBBox(coordinateBBox),更新坐标系
- 布局组件,绘制 Geometry