简介
我们要传达给用户 G2 是什么,要知道哪些信息就能用好 G2?需要确定几件事:
- G2 的用户是谁,要解决什么核心问题?
- G2 的设计思路是什么?
- G2 提供了什么?
-
G2 用户是谁?
首先明确 G2 的用户是可视化的开发者:
图表库的开发者,G2-Plot 作为我们自己的图表库,在 G2 的基础上开发出来。
- 数据分析工具开发:
- BI 场景下的多维分析开发者,交叉表、万能图
- 类似于 ggplot 的工具开发
- 可视化学术界的工具
解决的核心问题
- 数据到图形的映射(图形语法)
- 交互到数据更新的机制(交互语法)
设计思路
从一个图表的使用者来看如何看懂数据,要很好的解决下面几个问题:
- 数据是否已经准备好(清洗、加工)
- 看数据的那些方面(维度选择)
- 怎么展示(用什么样的图表)
- 如何观察细节(交互:过滤、下钻)
- 怎么切换数据(数据更新)、切换展示形式
数据准备
数据为什么要处理?主要考虑下面几个方面
- 有些数据存在空值
- 有些数据的组织方式图表不支持
- 数据的粒度太细
维度选择
对于图表的开发者来说,这个问题不大,产品方会确定用户要看哪些维度/度量(字段),但是在分析场景这是一个不小的问题,维度的信息需要能够很好的呈现出来,作为分析的依据:
- 字段类型:连续、分类、时间
- 统计量:最大值、最小值、分布等
一旦选择了字段,适合的图表类型并不多,但是作为一款工具来说 G2 并不限制用户的选择,但是在上层可以提供推荐工具,这就是怎么展示的问题。
怎么展示
将数据以图形的形式展示在画布上最重要的要做到:
- 能够区分不同的数据
- 不会造成歧义
由于数据以图形的形式展示,那么人的视觉能够对图形的识别能力就作为我们的考虑点:
- 位置
- 大小
- 颜色
- 形状
- 图案
- 角度
上面这些我们称为 视觉通道
,图案和角度暂不考虑,前者太花哨不适用,后者仅在个别图形上有意义。
视觉通道需要让用户无歧义还需要明确的文字进行说明,那就出现了常见的几个组件:
- 坐标轴,用于说明位置视觉通道,告诉用户位置表示的具体的值
- 图例,用户说明颜色、大小、形状在图上的含义,大概的告诉用户数据的范围
- 图形文本,每个图形上的文本,精确的显示每个图形代表的数据值
- 标注,使用直线、文本、区域标注特定的数据
如何观察细节
图表展示在用户的面前,跟一张图片没有任何差异,而图表真正的价值在于可以进行交互,让用户获取更多的信息:
- 通过 tooltip 可以看到更详细、更精确的数据
- 通过 图例、滑动条可以筛选出更少的数据,观察的更加清晰
- 可以通过点击下钻,切换到另一个图表(视图)
- 可以点击、框选选中部分图形进行高亮显示
所以 G2 需要提供常见的交互,并提供一套交互语法,可以让用户随意搭配,完成任意想要的交互。
怎么切换数据/展示
当数据发生变化时,图表需要刷新,有这么几种情况:
- 整个数据源发生变化,整体变化、局部变化等
- 数据在展示前发生过筛选,筛选条件变化后图表需要刷新
当通过一种图表观察过数据后,想换一种形式,换一种图表也就是改变数据映射到图形的方式
只有解决了上面所有的问题,同时又能应对各种场景,G2 才能成为可视化开发者的首选,才能再上层长出可视分析工具、各种各样的图表库才能成为学术界做可视化的工具。
G2 提供了什么
G2 给开发者提供了:
- 数据处理工具
- 图形映射语法
- 交互语法
- 默认的组件显示,同时提供扩展组件的方式
- 常见的图表对应的图形,自定义图形的能力
- 常见图表的默认动画,定制动画的能力
数据处理工具
G2 3.0 开始提供了 DataSet、DataView ,通过 transform 可以进行数据处理,后续这部分会持续改进,变成数据轻加工的工具。
G2 2.x 的统计函数,内嵌在数据映射的流程中,可以跟图表的交互无缝的集成,由于 3.0 认为用户理解成本过大未提供,但是如果 G2 作为可视化开发者的工具,可以作为一个特性提供出来。
chart.line().position(Stat.bin.rect('x*y'), 0.01).color(Stat.summary.count());
图形语法
G2 始终遵循一句话一个图表的理念,通过自然语言的方式生成图表,仅提供了 5 个视觉通道
- postion
- color
- shape
- size
- opacity (color 的补充)
严格来说 G2 仅提供了 4 个视觉通道,opacity 是 color 视觉通道的一个补充
交互语法
一个交互我们分为目的和过程。
一个交互必须要有目标,这也是交互的价值所在:
- 探索
- 更新
- 过滤
- 操作/编辑数据
而交互的过程是交互目标的一种实现方式,互过程为多个环节:
- 示能:告诉用户能够进行某个操作,例如可以点击时鼠标变成手的形状
- 开始
- 持续
- 中断
- 结束
- 回滚
触发
常见的触发就是事件(DOM事件、自定义事件),需要考虑触发的条件:
- 触发的范围
- 画布范围
- 绘图区域
- View
触发的元素,在 G2 的场景下,这些图形元素包括:
鼠标的样式
- 节点的样式
- 辅助图形
- 画布的状态
- 数据的更新
各个过程中画布/图形会有反馈:
- active:移动在单个图形上的响应,
- select: 选中某个图形,可以保持这个选中状态
- highlight: 高亮图形,其他的变暗
- mask:图形被遮罩
有时候图形/画布的反馈就是交互的目的,但是大多数情况下数据的变更才是用户的目的,常见的数据的反馈有:
- filter:数据过滤
- sort: 排序
- update:更新
- remove: 移除
- corelation:同时高亮、同时过滤等
- look up:上取
- drill down:下钻:
举例
结合几个交互我们来理解不同环节的触发和反馈,用户常见的交互有:
- click(mousedow + mouseup):点击,鼠标在画布上移动时,
- 只有移动到可以被点击的元素上,鼠标才会变成手的形状(示能),
- 鼠标按键按下去(触发),
- 若鼠标发生移动则触发拖拽(中断),
- 鼠标未移动(持续)按键抬起(结束)
- 图形元素样式发生改变(反馈)。
- brush: 框选功能,使用刷子进行区域选择。
- 鼠标移动到可框选区域内,鼠标变成十字(示能),
- 按下鼠标拖拽(触发),
- 拖拽过程中出现 mask(反馈),被覆盖的图形样式改变(反馈),
- 鼠标抬起(结束),mask 保留,
- 点击空白处mask 消失图形状态恢复(回滚)
- drag(mousedown + mousemove + mouseup): 拖拽节点,拖拽画布(移动端的滑动 pan、swip),以拖拽节点为例
- 鼠标移动到元素上,鼠标变成可以移动的符号(示能)
- 拖拽元素(持续),元素跟随移动,或者出现委托对象(反馈)
- 拖拽到另外一个元素上(持续),元素状态发生改变(示能、反馈),标识是否可以释放元素
- 释放元素(结束),如果在回收筐释放则销毁元素,如果在另一个图形上释放则合并元素,在空白处释放则移动元素(反馈)
- 按下 ctrl-z ,回滚
- zoom: 缩放, 移动端 pinch
在 G2 3.x 已经增加了 interaction ,仅初步解决交互的扩展问题,但是还称不上交互语法
- 图表的图形如何跟所有的组件进行交互都是硬编码的方式
- 多个交互有关联,如何避免冲突
组件
G2 4.0 中将提供以下组件,并提供每种组件的扩展机制
- tooltip
- legend
- axis
- annotation(guide)
- labels
- 其他:slider,timeLine
自定义图形
自定义图形,自 2.x 以来变化不大,4.0 中的一个重点是让用户更容易的理解自定义 shape,每种图形对交互的响应可以自己决定(active, selected),对于图形细节的掌控力更强。
4.0 中的自定义shape 其实构建的不仅仅是一个图形,而是一组图形、文本,包括在交互状态下的响应。
定制动画
动画值得再开一个章节单独去讲。
用户使用 G2 4.0
2.x 版本的特性
在之前的 2.x 版本中,我们主要的思路是一句话一张图表,专注于图形语法,其他的配置项能精简就精简,默认提供按照语法能够绘制出图表的样式、组件、交互等,但是由于展示报表的业务占比 90% 以上,很难满足样式调整的需求。
3.x 版的特性
在 3.x 版本中,我们改造了组件,提供了大量可以配置的细节,但是来自于重分析场景的业务要求我们提供更加灵活和自定义的功能,在后面的版本中我们提供了 Interaction 的初步支持(由于并不完善,所以 API 中很少体现)。
4.x 版的特性
在 G2 4.0 版本中由于我们在上层提供了 G2-Plot (通用图表),所以主要解决的是三个问题:
- 基于 G2 创建图表库
- 自助分析的交互需求
- 可视化领域的专业工具
所以4.0 给用户提供的将是 图形语法、交互语法、统计语法和组件的扩展能力,具体图表的精雕细琢是上层 G2-Plot 的工作,而可能性是这个版本的重点。
G2 的入口
之前版本中 G2 的入口是 Chart
const chart = new Chart({});
chart.line().position('x*y').color('z');
chart.render();
chart.on('xxx', ev => {});
这种方式在新的版本中继续保留,但是会开放从 View 的视角上看待可视化
const canvas = new Canvas();
const group = canvas.addGroup();
const view = new View({
container: group,
region: {}
});
view.source(data1);
view.line().position('x*y').color('z');
const view1 = view.addView({
region: {}
});
view1.source(data2);
view1.point().position('x*y').color('z);
view.render();
view1.changeData(data3);
可操作的实体
在3.x 之前的版本,能够操作的仅有 chart和最底层的 shape,而 view 、geometry 和 组件虽然放出来了但是基本不会操纵:
chart.clear();
chart.changeData(data);
chart.on('click', ev => {
const shape = ev.shape;
// 操作图形,很多黑盒
});
// 虽然 geometry 放出来了,但是基本没人用
var line = chart.line().position('x*y');
line.color('z');
// 组件上只能监听事件,如果这时候来操作 chart 可能会引起全局的刷新
chart.on('axis-label:click', ev => {});
4.x 版本中,我们希望所有的元素都可控,在需要操作的用户前面都是好理解,好操作的:
- chart,view,geometry, element, shape 层都开放操作,尽量减少 shape 层操作的机会
- axis, legend,guide(annotation)、 tooltip 等组件都可以自己决定生成,同时也可以直接操作
- 数据局部更新机制的支持,比如更新部分数据、增删改数据
- 所有可操作的元素(chart,view,geometry, element, shape, 组件) 都没有默认的交互,都通过统一的状态量管理
// 使用 chart 时,操作内置的状态管理器
const stateManager = chart.getStateManager();
stateManager.on('change', ev => {
if (ev.state == 'elementActive') {
const element = ev.element;
if (ev.value) {
} else {
}
}
});
stateManager.setState('elementActive', {element: element, value: true});
// 如果仅使用 view,则自己创建 stateManager
const stateManager = new StateManager();
// and so on
状态管理器,主要用于编写 interaction 时:
G2.registerInteraction('element-active', {
init() {
const stateManager = chart.getStateManager();
this.stateManager = stateManager;
chart.on('elment:mouseenter', bind(this, 'onMouseEnter');
chart.on('elment:mouseleave', bind(this, 'onMouseLeave');
},
onMouseEnter(ev) {
const element = ev.element;
elment.setState('active', true);
},
onMouseLeave(ev) {
const element = ev.element;
elment.setState('active', false);
},
destroy() {
chart.off('elment:mouseenter', bind(this, 'onMouseEnter');
chart.off('elment:mouseleave', bind(this, 'onMouseLeave');
}
});
未完,待续