charticulator 详解

以下内容并不完全是Charticulator: Interactive Construction of Bespoke Chart Layouts这篇论文的翻译。而是结合了Charticulator官方使用说明、自己的感想和论文原文的综合介绍。

Charticulator 是微软研究院2018年推出的一款可视化工具,可以通过交互创作的方式制作图表类型模板,然后还可以导入 Power BI 去支持扩充其图表类型。Charticulator 特别之处在于其在图元布局的处理上所做的探索工作,对于此类工具的设计具有很强参考价值。

1. 相关工作

原文根据 Grammel et al. [13] and Mei et al. [21]等人对可视化图表创建工具的综述文章,将这可视化图表创建工具分为了如下5类:

  1. Imperative Programming (命令式编程)如Processing、ProtoVis、D3。Declarative Programming(声明式编程)如Vega、ggplot2、Vega-Lite,共同特点其自由度都以复杂性代价,具有陡峭的学习曲线。而Charticulator通过添加“布局约束”和“布局组合”的概念扩展了这些方法,用户不需要编码就可以清晰表达布局结构。
  2. 模板编辑( Template Editing), 如 Datamatic [44], RAWGraphs [19],Infogram [49], Easelly [45], Plot.ly [52], Piktochart [51], ChartBlocks [43], iCharts [48], and Quadrigram [53]. 共同特点是用户无法修改模板,只能在模板基础上修改一些属性或者小部件,无法实现新颖的图表设计。
  3. 货架配置( Shelf Configuration), 如Tableau(formerly Polaris [34]), Polestar [38], Voyager [38], and Voyager 2 [39],共同特点是用户可以把数据维度及其派生的变量绑定到X轴、Y轴或者mark的视觉属性上(如颜色、大小等)。但是,这些系统既不允许指定由多个标记组成的glyph,也不允许同时使用细粒度的标记样式,而其底层图表布局则由用户无法配置的模板来指定。 但是,由于这些工具的拖放数据绑定交互易于学习和使用,因此Charticulator将类似的交互应用于布局规范上。
  4. 可视化构建工具(visual builder)比如Lyra、iVisDesigner、InfoNice等工具,支持将数据与标记、图像和文本绑定,然后组合成复杂图元对象。但是这些工具布局功能很弱,通常只能通过数据绑定的类型推断布局。另一个最新的工具DataInk,虽然支持用户创建手绘布局,但依然很难创建复杂布局。与InfoNice相似,DataInk主要专注于表达glyph而不是布局。Adobe的Data Illustrator的重复和分割网格功能,只能处理正交布局的简单结构。VisComposer仅支持一组固定的组合图表,并且需要进行编程以创建其他视觉编码。而Charticulator将布局作为设计的优先选择,用户可以创建嵌套的多层次布局,并且依赖于多种坐标系。最后,charticulator可以让用户将他们的设计导出成可重用模板,然后导入到其他可视化系统中,如Microsoft Power BI。
  5. 基于约束的创作( Constraint-based Authoring),是使用数据约束设计用户界面、图表和其他图形内容。典型例子有ThingLab, GADGET, 还有Android的ConstraintLayout,这些工具主要用来创建用户界面,但只支持对一组固定的对象和组件创建约束。另一类工具是创卷图形可视化系统工具,如 GLIDE [29] Delaunay [10] TRIP system [35] ,他们或是专注于图形或树的可视化,要么使用编程语言编写约束规范。而Charticulator,无需编程只需要使用进行设计的用户交互就可以有效指定公共布局的约束。

2. Charticulator的设计原则

Charticulator是为缺乏编程技能的人设计的,这些人可能包括设计师、记者和分析师。为了支持多种图表布局设计,我们确定了以下三个指导设计原则:

  1. Charticulator将布局视为重要的设计内容,并在用户界面中进行呈现。
  2. 为了能够灵活地指定布局,我们将布局规范分解为可组合的局部约束(partial specification)。例如,下图的图表可以分解为几个层次:最顶层是沿着螺旋线布局的整体结构PlotSegment(图表区块),其中的子结构是楔形的Glyph(字形),楔形内部则包括一些Mark(标记)及其之间的位置关系(例如五角星必须在楔形的中心位置)。以上这不同层次的布局规范,就是局部约束,组合在一起就能构建具有复杂布局的原创图表。为了便于用户处理局部约束,Charticulator将布局规范分为两个级别彼此独立地进行处理:图表级别(chart-level)和字形级别(Glyph-level)。为此提供两个独立的编辑画布:一个用于字形级编辑,另一个用于图表级编辑。更改其中一级的内容,不需要用户重新指定另一级的内容。
  3. 平衡直接操作和配置面板。如同大多数矢量绘图工具(如Adobe Illustrator )一样, Charticulator使用户能够直接操作布局参数,如锚点(anchor)、边距(margin)和间隙(gap)。但是,也有许多布局相关的操作不能简单地显示为可操作对象。例如,glyph可以水平或垂直堆叠,也可以在网格中布局。由于布局被指定为许多约束的组合,Charticulator设计了一组简洁的菜单和面板来表示这些不能直接操作的选项,具体详见第四节。

3. Charticulator的概念框架

原文中用公式描述了charticulator的概念框架,包含三大类内容:字形级元素(Glyph-level )、图表级元素(Chart-level )以及布局依赖(LayoutConstraint)。如上文所述,charticulator为了方便布局组合的定义,针对图表级别(Chart-level)元素和字形级别(Glyph-level)元素分别提供了一个画布,来做该级别元素的布局和属性的修改。这是charticulator相对于其他工具上的最大创新,这样用户就能以较低操作成本和理解成本,来实现这种具有两级嵌套结构的图表。

Charticulator将Glyph与Chart的编辑面板分开。在上图中,左边是Glyph面板,确定了Glyph中组件的关系(一个text文本在rect方形上侧),然后在右侧的Chart面板,这个Glyph按照Chart面板中指定的径向布局生成了一个南丁格尔玫瑰图。在Chart面板中用户不能调节Glyph的元素位置,但可以设置Chart级别的一些属性和布局依赖,例如Glyph之间的间距大小。所有这些可调节数值都可以与具体数据集绑定。

以下是charticulator三大类概念的详细介绍:

1. Glyph-level 字形级别元素

  1. Mark := Rectangle | Symbol | Line | Text mark是不可分割的标记,包括方形、标记(例如五角星,圆点等,此外用户还可以导入位图作为标记)、线和文本
  2. GlyphElement := Mark | Guide | GuideCoordinator | DataDrivenGuide 字形元素,包括标记,辅助线(是仅在设计阶段可见的指示线,用于对齐对象,例如用于对齐标记顶部的水平引导线),辅助线坐标系(可以一次添加多条辅助线,例如水平五等分辅助线),DDG(一种按数据标识图元大小的比例尺)
  3. Glyph := GlyphElement*, LayoutConstraint<GlyphElement>*(字形,包括数个字形元素和字形元素的布局依赖)

2.Chart-level 图表级别的编辑和布局结构

  1. ChartElement := PlotSegment | Link | Mark | Legend | Guide | GuideCoordinator 图表元素,包含图表分块,链接,标记(这里是图表级标记),图例,辅助线,坐标系
  2. PlotSegment := Glyph, (Scaffold | Axis){0..2}, Sublayout, CoordinateSystem 图表分块是构成图表的基本元素,包含字形,布局脚手架和坐标轴(至多2个),子布局,坐标系统
  3. Chart := ChartElement*, Scale*, LayoutConstraint<ChartElement>* 图表,通常包含数个图表元素,数个比例尺,和数个图表元素的布局依赖

3.图元属性和布局依赖定义

  1. Attribute := "x1" | "y1" | "x2" | "y2" | ... 一般意义上的属性,可以与某个数据维度或其衍生变量进行绑定
  2. ElementAttribute<ElementType> := ElementType, Attribute 图元属性,由元素类型和其对应的属性定义,不同类型的元素对应的属性不同
  3. ParentAttribute := Attribute 父级属性,跟普通属性定义相同
  4. ConstraintType := "equals" 依赖类型,主要指布局依赖的等式, Charticulator当前仅支持相等约束
  5. LayoutConstraint<ElementType> := (ParentAttribute | ElementAttribute<ElementType>){2}, ConstraintType布局依赖,由2个父级属性或元素属性以及依赖类型定义

4. 布局的组合方式

图表分块(PlotSegment)是构成charticulator的图表元素(chart-level)结构的基本模块。一个图表至少包含一个PlotSegment,PlotSegmen 默认是长方形。而每个PlotSegment内部的字形(Glyph)的摆放位置,由坐标系统(CoordinateSystem )决定。CoordinateSystem有三种:

  1. 正交布局,即由水平轴X轴和垂直轴Y轴定义的笛卡尔坐标系
  2. 径向布局,即由角度和半径定义的极坐标系
  3. 自由布局,也叫曲线布局,即由曲线的切线和法线方向定义的沿着曲线路径的坐标系( “Tangent” and “Normal” for coordinates along custom curves ),注意它跟天文学曲线坐标系(Curvilinear coordinate system)不是一个东西。

注意:如果一个Glyph要从正交布局的笛卡尔坐标系,转换到其他非笛卡尔坐标系中(如径向布局和自由布局),那么就要进行形变。例如在下图中,Glyph本来是长方形,但是在切换城第二个径向布局和第三个自由布局时,长方形变成了楔形。注意文字并没有跟着一起变形。具体的形变方法charticulator参考了这篇论文:Transmogrification: Casual Manipulation of Visualizations

在确定CoordinateSystem后,每个PlotSegment都会有两个独立的布局方向,例如当采用正交布局时这两个方向就是X轴和Y轴,在采用径向布局时是角度和半径。布局方向根据使用方式不同又分为三类:

  1. scaffold,脚手架,在PlotSegment内简单地按顺序摆放Glyph字形。
  2. categorical axis ,分类轴,可以在PlotSegment内根据Glyph绑定的分类属性对其进行分组,以便确定其显示位置。
  3. numerical axis ,数字轴,可以在PlotSegment内根据Glyph绑定的数值属性确定其显示位置。

下图展示了在正交布局的两个布局方向上,使用不同的scaffold 、categorical axis和numerical axis 时布局的组合效果。

特别的,对于分类轴,Charticulator使用子布局(sub-layout)来决定一个分类下的元素布局。这里Charticulator对sub-layout进行了简化处理,只提供如下四种子布局方式:基于水平堆叠、垂直堆叠、表格堆叠、圆形装箱堆叠,如下图所示;

不过子布局只有当两个布局方向上都是使用的是categorical axis 分类轴的时候才会起作用。比如下图左边的例子,X轴为分类轴,Y轴为脚手架,最后结果不显示子布局。而下图右边的例子,只有一个X轴,并且是分类轴,因此显示了子布局:水平堆叠。需要说明的是这个图上面的连线以单独的link方式来展示,并不直接由坐标系统控制。

5. 关于创建节点链接图

从上图右侧的例子可以看到链接(link)的使用。这个链接是专门创建节点链接图的,它的锚点必须在字形(Glyph)上,展示方式有线(line)或者带(band),形状有直线、贝塞尔曲线和圆弧。

节点之间的链接关系有三种建立模式:

  • 由同一个图表分块上的数据序列直接建立。如下图左侧的例子,链接连接了堆积条形图的同类数据项。
  • 在多个图表分块(PlotSegment)上的数据对象之间建立。如下图中间的例子,两个平行坐标轴分属于两个PlotSegment,但是在其上定义了同一组数据。然后在相同的数据项之间建立了连接。用这种方法可以创建平行坐标系。
  • 使用独立的图结构数据集(Link data)定义。上面那两种其实只能建立简单的对应关系,要定义有复杂关系的节点链接图,这种方式其实最为常用。如下图右侧例子,圆弧其实是用悲惨世界的人物关系数据集建立的,跟显示节点用的人物名称数据集是两个不同的数据集。

 

6. 用户界面和交互

界面详解

Charticulator的用户界面如下图所示,可以看到基本上跟Photoshop等软件类似,包括数据面板(左侧)、Glyph画布(中间上部)、图层面板(中间中部)、可视化属性面板(中间下部)和Chart画布(右侧大面积区域)。

然后有一系列密密麻麻的按钮排列在上侧,从左到右分别是Mark(各种标记)、DDG、nestchart(为了做多层嵌套图表而设计新按钮,在论文中并未涉及,是后期加上的实验性功能)、Guide(辅助线)、PlotSegment(图表分块)、Scaffold(布局脚手架)。与数据绑定的交互一致,这些按钮都遵循拖拽的方式使用。但我个人认为这些按钮太多了,并且有些按钮只做用于Chart画布,比如Scaffold和PlotSegment。不如将这些按钮根据其作用域分类显示。

Glyph级别的画图、布局与数据绑定

具体的制图过程,通常是从在Glyph画布编辑Glyph开始。用户拖动mark标记到Glyph画布,调节其位置,这里可以用到Guide参考线。有点像Photoshop,在图层面板中,加入mark将作为Glyph的layer显示。在Glyph画布或图层面板上都可以选中mark,然后下方就会显示对应的mark的属性编辑面板。不过与Photoshop不同之处在于,Charticulator的图层面板是截然地分为Chart和Glyph两部分,二者井水不犯河水。

Charticulator模仿了tableau的操作,可以将右侧的数据面板中的数据维度,拖放到对应的视觉属性上,从而实现数据绑定。当然也可以点击属性对应的数据绑定按钮实现同样的操作。下图展示了一个Glyph中的mark(类型是symbol,小标记),其三个视觉属性(size, fill, visibility)分别绑定了三个数据维度(count, category, count)。

最后特别需要指出的是,Glyph本身的高度、宽度不支持数据绑定。

Chart级别的画图、布局与数据绑定

然后用户可以接着编辑Chart画布,一个Chart画布中可以添加多个PlotSegment。用户要做的是拖动Scaffold、Guide到PlotSegment里,调节Glyph在PlotSegment中的布局。需要强调的是,PlotSegment与Glyph是多对1的关系。这就是说在,每个PlotSegment对应最多1个Glyph。例如下图的例子:左边的PlotSegment1对应着Glyph1, 右边的PlotSegment1对应着Glyph2。 当然这两个PlotSegment也可以设置为同时对应一个Glyph,但是反过来不行,1个PlotSegment不能对应两个Glyph。

Glyph的创作数量最多5个,PlotSegment最多能创建多少个我没试过,但是可以想见,仅凭借PlotSegment与Glyph多对1关系,想画图元组合丰富的儿童简笔画,即使勉强做到也会非常难办。Charticulator主要设计目的还是创建那些嵌套结构不多的、图元组合不多的标准图表。

Chart画布中也可以实现基于拖拽操作的数据绑定。如下面这个例子,拖动mouth这个数据维度,两个PlotSegment中可绑定的坐标轴都高亮显示。由于这里的两个PlotSegment都采用的是径向布局,所以可绑定的坐标轴只有角度和半径。

特别地,我们再看一下采用的自由布局(或者称之为沿曲线布局)时的效果。效果图如下。可以看到图元相对曲线位置贴合得并不十分紧密,并且还有重叠现象。说明Charticulator对自由布局的处理还是较为简单的。不过倒是可以给法线轴和切线轴绑定数据。

最后,关于数据处理

Charticulator对数据的处理在同类可视化构建工具中较为先进,尤其超过了data illustrator、dataink等。数据以CSV格式导入(如果有中文注意使用UTF-8编码),然后会在数据面板以数据维度的方式罗列,每个数据维度前的小标记表明了它是类名型还是数值型,并且可以查看具体数值。在给PlotSegment绑定数据时,可以进行过滤(Filter by)和分组(group by)。在给Glyph中的mark绑定数据时,根据数据是数值型还是类名型,显示不同的筛选器。如下图所示,visibility这个视觉属性绑定分类数据时,可以通过一组复选框设置筛选器。指定某些类别时显示。

对于数值型数据,提供一个简单的公式编辑器,例如下图这个针对visibility这个视觉属性而特别设计的简单布尔查询(下图的意思是,当count > 0时才显示)。对于别的视觉属性,比如size,还有诸如数值映射范围的函数(类似D3js中的scale函数)

7. 多层次的约束求解

约束的定义

在系统层面,局部约束(partial specification)之间并不互相独立。例如要做一个柱状图,将数据绑定到柱形的宽度,和所有矩形的水平总宽度是两个互相依赖的局部约束:即每个矩形宽度的缩放参数,依赖于水平总宽度。因此,需要将用户指定的约束转化为数学的方程式,然后使用计算约束的方法进行计算。

Charticulator的约束包括4个层次

  1. 图表层次(chart-level)的约束:指定绘图区块(PlotSegment)、图例(Legend)、参考线(guide)和图表级标记(chart-level marks)的位置约束关系;
  2. 绘图区块(PlotSegment-level)的约束:指定其中字形(Glyph)的位置约束关系,并且这个依赖关系还与布局方式相关(scaffold)
  3. 字形层次(Glyph-level)的约束:标指定字形中的标记(mark)、参考线(guide)之间的约束关系
  4. 标记层次(mark-level)的约束:标记的属性具有内在的依赖关系。例如对于长方形,它的X轴左右坐标X1, X2、中心的横坐标Xcenter以及宽度width这4个变量,只需要指定两个则另外两个可以通过计算得出。

具体的约束求解例子

当数据维度绑定到Chart的坐标轴或mark的某一属性上时,将影响整个约束求解的过程。以下用一个简单的例子来描述这一过程:

假设有一组矩形被正交布局指定为水平排列的彼此相邻的一排矩形。然后有一个数据维度D绑定到些矩形的属性width上,那么构成了依赖用数学公式可以表达如下:

在第一个公式中,中di是数据维度D每一个数据项的值。Scale.factor是缩放因子。Gi.width是每个矩形的宽度。第二个公式是每个矩形mark的内在约束。第三到第五个公式是绘图区块(PlotSegment-level)的水平布局约束。根据以上公式,约束求解器就可以计算出缩放因子Scale.factor的值:

两步的约束求解

由于大部分依赖都是用等式计算,在Charticulator中大部分布局依赖求解都是线性求解。但是也有一些非线性的依赖,比如力导向布局(force-directed layout,用在节点链接图)和圆形装箱问题(circle packing,用在子布局中)。对此,Charticulator的做法是先算线性依赖,根据其结果再算非线性依赖。

8. 实现

Charticulator是一个HTML5 web应用,主要实现技术是TypeScript + React + webAssembly。开发思路是基于Flux application architecture  ,简单的说,每个图表都对应一个JSON对象,这个对象称之为图表状态(chart state),其中保存这个图表所有元素的属性。然后描述图表的规范,可以按照前文所述的概念框架解释此JSON。这个图表状态JSON会保存在Flux架构中,每次图表发生编辑操作后,一个“Action”被发出,并通过全局“Dispatcher”发送到“Store”,然后Store修改图表规范并调用约束求解器组件来更新图表状态。成功更新状态后,“存储”将发出一个更新事件,该事件将导致用户界面组件更新。

然后还有个约束求解器。有许多算法求解线性约束。值得注意的是,Cassowary算法[2]扩展了单纯形法以允许对约束进行优先排序,而共轭梯度算法[33]对于求解稀疏线性系统非常有效。我们对这两种算法进行了实验,并确定了一种稀疏最小二乘共轭梯度算法。我们选择稀疏解算器是因为大多数布局约束只涉及很少的变量,因此产生一个非常稀疏的矩阵;我们使用最小二乘最小化技术来处理加权约束(如果有的话)。然而,该算法只支持等式约束,因此Charticulator只允许指定此类约束。