Vega 堆叠条形图实战:基于 stacked-bar-chart 示例解析 stack 变换与矩形标记编码
2026/9/24 20:18:35 网站建设 项目流程

Vega 堆叠条形图实战:基于 stacked-bar-chart 示例解析 stack 变换与矩形标记编码

【免费下载链接】vegaA visualization grammar.项目地址: https://gitcode.com/gh_mirrors/ve/vega

堆叠条形图(Stacked Bar Chart)通过分层矩形展示多个定量序列的总和,同时保留对单个序列的观察能力,是 Vega 中最具代表性的基础图表之一。本文以仓库中 stacked-bar-chart.vg.json 示例为骨架,完整拆解其数据、变换、比例尺与标记编码结构,并结合 stack 变换文档 与 Stack 算子源码 讲透堆叠的底层原理。读完本文,你将能够独立编写、修改并扩展 Vega 堆叠条形图,并掌握三种堆叠基准(zero / center / normalize)的适用场景。

示例概述:一个最小可运行的堆叠条形图

仓库中的 stacked-bar-chart.md 对该示例给出了精炼定义:堆叠条形图用分层条形描绘多个定量序列的加总,同时仍允许检视每个独立序列。配套的 stacked-bar-chart.vg.json 是一个自包含(inline data)的 Vega v6 规范,无需任何外部数据文件即可直接渲染,非常适合作为学习堆叠布局的起点。

整个规范的顶层结构如下:

  • width: 500height: 200padding: 5:画布尺寸与内边距;
  • data:一张名为table的内联数据表,共 20 条记录,字段为x(类别位置,0–9)、y(堆叠高度值)、c(分组类别,0 或 1),并在数据层直接应用stack变换;
  • scalesx(band 比例尺)、y(linear 比例尺)、color(ordinal 比例尺)三把比例尺;
  • axes:底部x轴与左侧y轴;
  • marks:单个rect标记,通过y0/y1定位矩形的上下边界。

该示例的关键设计是:堆叠计算发生在数据变换阶段,而非标记编码阶段stack变换为每条数据追加y0(堆叠起点)与y1(堆叠终点)两个字段,后续比例尺与标记编码只需直接消费这两个字段。

stack 变换:堆叠布局的核心

示例在数据定义内使用了stack变换:

{ "type": "stack", "groupby": ["x"], "sort": {"field": "c"}, "field": "y" }

按照 stack 变换文档,stack通过对值分组执行堆叠布局,最常见的用途就是生成堆叠条形图与流图(stream graph)。它向每条数据写入两个属性,分别表示堆叠的起始值与结束值。该变换支持五个参数:

属性类型说明
fieldField决定堆叠高度的数据字段
groupbyField[]用于将数据划分为独立堆叠栈的字段数组
sortCompare每个栈内部值的排序条件
offsetString基线偏移,取"zero"(默认)、"center""normalize""zero"从 0 开始堆叠;"center"将堆叠居中;"normalize"计算每个栈点的百分比值,输出范围在 [0, 1]
asString[]计算得到的堆叠起止字段名,默认为["y0", "y1"]

对照 Stack.js 源码 的Stack.Definition可以看到,这五个参数正是算子的参数化定义:field为 field 类型、groupby为 field 数组、sort为 compare 类型、offset为枚举类型(默认zero)、as为长度 2 的字符串数组(默认['y0', 'y1'])。

分组与排序语义

在本示例中,groupby: ["x"]意味着按x字段的每个唯一值(0–9)各建立一个独立的堆叠栈;sort: {"field": "c"}则保证每个栈内按类别c排序,使同类别矩形在视觉上保持一致的相对顺序;field: "y"指明用y字段的数值决定每个矩形的高度。结果默认写入y0y1字段,即每个矩形下沿与上沿所对应的累加值。

Stack.js 源码 中的partition函数揭示了底层分组机制:当未指定groupby时,全部数据被当作一个栈;否则通过JSON.stringify(groupby.map(...))构造分组键(因此null''0false等键会被区分对待,见 stack-test.js 中 "stacks null and empty string groups separately" 与 "stacks zero and false groups separately" 两个测试用例),同时计算每个组的绝对值之和g.sum以及全局最大值groups.max(供centernormalize偏移使用),并在排序条件存在时对组内元素排序。

三种堆叠基准的实现

stackoffset参数在源码中对应三个内部函数(Stack.js):

  • stackZero(默认"zero"):从 0 开始堆叠。特别注意它按正负值分开累加——负值向下堆叠(lastNeg),正值向上堆叠(lastPos),这与测试用例 "Stack stacks negative values" 中-12被分别堆到 0 线两侧的断言完全一致(stack-test.js);
  • stackCenter"center"):先计算(max - group.sum) / 2作为偏移起点,使整个栈以全局最大栈为中心居中,适合制作流图;
  • stackNormalize"normalize"):以1 / group.sum为缩放系数,将每个栈的起止值归一化到 [0, 1],即百分比堆叠。

另外,源码 Stack.js 使用stableCompare(_.sort)包装排序比较器,保证排序是稳定的,即使多条数据的排序键相同也不会意外交换顺序。field未指定时默认使用one常量函数(即每个值视为 1),字符串数值会被+field(t)强转为数字(测试用例 "Stack stacks coerced string values" 验证了这一点)。

比例尺配置:band 定位与线性映射

示例定义了如下三把比例尺:

{ "name": "x", "type": "band", "range": "width", "domain": {"data": "table", "field": "x"} }, { "name": "y", "type": "linear", "range": "height", "nice": true, "zero": true, "domain": {"data": "table", "field": "y1"} }, { "name": "color", "type": "ordinal", "range": "category", "domain": {"data": "table", "field": "c"} }

三个关键点值得注意:

  1. x使用band比例尺:堆叠条形图按离散类别排列矩形,band比例尺将x的 10 个取值均匀映射到width范围内,并天然提供band宽度用于矩形的width编码;
  2. y的 domain 取自y1字段"domain": {"data": "table", "field": "y1"}意味着比例尺输入域由堆叠变换产出的累加上界决定,这样整组堆叠的总高度恰好对应比例尺的顶值;配合"zero": true确保从 0 开始、"nice": true让坐标轴刻度取整;
  3. color使用ordinal比例尺与category配色c的取值 0、1 映射到 Vega 默认分类色板中的前两个颜色,用于区分堆叠的两个序列。

标记编码:用 y0 / y1 绘制堆叠矩形

marks中只定义了一个rect标记,其编码逻辑是堆叠条形图的精髓:

{ "type": "rect", "from": {"data": "table"}, "encode": { "enter": { "x": {"scale": "x", "field": "x"}, "width": {"scale": "x", "band": 1, "offset": -1}, "y": {"scale": "y", "field": "y0"}, "y2": {"scale": "y", "field": "y1"}, "fill": {"scale": "color", "field": "c"} }, "update": { "fillOpacity": {"value": 1} }, "hover": { "fillOpacity": {"value": 0.5} } } }
  • 水平定位x取数据字段x经 band 比例尺映射后的位置;width使用"band": 1表示占满整个 band 带宽,再以"offset": -1收窄 1 像素,避免相邻条形粘连;
  • 垂直定位:这是堆叠的关键——yy2分别绑定堆叠变换产出的y0y1字段。y定义矩形的顶部位置、y2定义底部位置(Vega 中矩形以y为顶端、y2为底端),两者经y线性比例尺映射后,恰好构成一段从上一序列上沿到本序列上沿的色块,实现"层叠"效果;
  • 颜色fill绑定color比例尺,按c字段区分序列;
  • 交互状态:编码按enter/update/hover三阶段组织,鼠标悬停时fillOpacity从 1 降为 0.5,用于高亮当前矩形,这也是 Vega 场景图中标记状态(vega-scenegraph)的标准用法。

与普通条形图的差异

对照仓库中非堆叠的 bar-chart.vg.json 可以发现:普通条形图的y直接绑定原始值字段、y2绑定零点(或基准值),而堆叠条形图则把"起点"信息完全交给stack变换生成的y0字段,标记编码本身无需理解堆叠逻辑。这种"变换计算布局、编码消费结果"的分层设计贯穿整个 Vega 架构。

运行与渲染方式

该示例是自包含 JSON 规范,可通过以下方式查看运行效果:

  1. 浏览器渲染:仓库 docs 目录 下提供了预构建的 vega.js 与压缩版 vega.min.js。在 HTML 页面中引入后调用vega.parse(spec)解析规范、new vega.View(parser)创建视图并绑定 DOM 容器即可渲染;
  2. 命令行渲染:使用 vega-cli 包,可执行vg2png stacked-bar-chart.vg.jsonvg2svg直接输出静态图片;
  3. 示例页:该示例与全部示例图片(如 stacked-bar-chart.png)配套收录在 examples 目录 中,specs-valid 测试集 也包含同源规范用于回归验证,可参考 vega 包测试 的构建流程。

进阶扩展:从堆叠条形到堆叠面积图

堆叠布局并不局限于条形图。仓库中的 stacked-area-chart.vg.json 使用了完全相同的数据与stack变换groupby: ["x"]sort: {"field": "c"}field: "y"),差别仅在于:

  • x比例尺类型由band改为point(面积图按连续位置连接);
  • 标记由单个rect改为group分面(按c分面为series)内嵌的area标记,并使用"interpolate": "monotone"平滑插值;
  • y/y2同样消费y0/y1

这充分说明:stack变换产出的y0/y1是与可视化编码解耦的通用布局结果,同一份堆叠数据可以驱动条形、面积乃至流图(offset: "center")等多种图形形态。若需百分比堆叠,只需将offset改为"normalize";若数据包含负值,stackZero会自动将负值堆叠到基线以下,无需额外处理。

小结

通过 stacked-bar-chart.vg.json 这个最小示例,我们完整走通了 Vega 堆叠可视化的标准流程:数据声明 →stack变换产出y0/y1band/linear/ordinal三把比例尺 →rect标记以y/y2消费堆叠字段。对照 Stack.js 源码 与 stack-test.js 可以确认:分组基于键值哈希、正负值分开累加、center/normalize依赖组和与全局最大值、排序稳定——这些实现细节共同保证了堆叠布局的正确性与可预测性。以此为模板,你可以轻松扩展到百分比堆叠、负值堆叠、流图乃至堆叠面积图。

【免费下载链接】vegaA visualization grammar.项目地址: https://gitcode.com/gh_mirrors/ve/vega

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询