Vega ResolveFilter 变换深度解析:基于 crossfilter 位掩码的高效按维度过滤数据流
【免费下载链接】vegaA visualization grammar.项目地址: https://gitcode.com/gh_mirrors/ve/vega
ResolveFilter 是 Vega 可视化语法中与 crossfilter 变换配对使用的数据流过滤变换:它读取 crossfilter 生成的"过滤器位掩码(filter mask)",针对每个下游图表选择性地输出通过(或忽略某些维度后通过)的数据元组。读完本文你将掌握filter/ignore两个参数的位运算语义、三字段交叉过滤的完整可运行配置,以及从 ResolveFilter.js 源码到 crossfilter-test.js 测试用例的底层实现原理,从而能够基于单个 crossfilter 实例搭建多图表联动刷选(cross-filtering)场景。
一、定位:为什么需要 resolvefilter
在 Vega 中,crossfilter变换通过一组有序索引(sorted index)为多个维度查询维护一个过滤器位掩码,用来支持大数据集上的快速交互式查询(该设计受 Mike Bostock 等人的 Crossfilter 库 启发)。但 crossfilter 本身只负责"计算并维护过滤状态",并不直接产出可供下游图表消费的过滤后数据流。
resolvefilter变换正是补上这一环:它使用 crossfilter 生成的过滤掩码,高效地生成过滤后的数据流。关键设计意图是:
- 一个 crossfilter 实例可以被多个图表共享,实现"多图表交叉过滤";
- 每个图表的过滤数据流由该图表自己的一个 resolvefilter 变换生成;
- 每个 resolvefilter 可以通过
ignore位掩码决定忽略哪些维度查询,从而为不同图表呈现不同的过滤组合(例如延迟面板忽略 delay 维度、时间面板忽略 time 维度等)。
┌─────────────────────────────────────────┐ flights 数据 ────► │ crossfilter (signal: "xfilter") │ │ 维护 fields × query 的过滤器位掩码 │ └───────────────┬─────────────────────────┘ │ filter mask(绑定到信号 xfilter) ┌────────────────────────┼────────────────────────┐ ▼ ▼ ▼ resolvefilter(ignore:1) resolvefilter(ignore:2) resolvefilter(ignore:4) 只按 time+distance 只按 delay+distance 只按 delay+time │ │ │ aggregate / collect ... aggregate / collect ... aggregate / collect ...一个resolvefilter变换后面必须紧跟一个消费变换,例如 aggregate(聚合)或 collect(仅收集过滤后的数据对象),否则过滤结果无处落地。
二、变换参数
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
| filter | Signal | 是 | 对底层 crossfilter 变换的信号引用,即配置{"signal": "xfilter"}形式的信号引用 |
| ignore | Number | 是 | 位掩码(bit mask),指明在 crossfilter 中应忽略哪些字段。每一位(bit)对应 crossfilter 变换fields与query数组中的一个字段/查询。若对应位为 1(on),解析过滤器时忽略该字段与查询;元组要进入下游数据流,必须通过所有未被忽略的查询 |
ignore的位对应关系由 crossfilter 中fields/query数组的索引顺序决定。以文档示例(字段依次为delay、time、distance)为例:
| ignore 值 | 二进制 | 忽略的维度 | 保留生效的查询 |
|---|---|---|---|
1 | 0b001 | 第 0 位 →delay | time、distance |
2 | 0b010 | 第 1 位 →time | delay、distance |
4 | 0b100 | 第 2 位 →distance | delay、time |
因此ignore的取值本质上是 2 的幂序列:1 << 维度索引。当有 n 个维度时,合法取值为0(不忽略任何维度,所有查询都需通过)到(1 << n) - 1(全部忽略)。
三、完整实战示例:flights 三字段交叉过滤
文档给出的示例对航班数据集的delay(延误)、time(时间)、distance(距离)三个字段做交叉过滤,数据文件为 flights-200k.json。crossfilter变换负责建立过滤器,三个范围查询由信号值定义;三个派生数据集则各用一个resolvefilter变换,分别忽略其中一个字段。
例如"filterTimeDistance"数据集只按time与distance查询过滤,忽略delay查询:
{ "signals": [ { "name": "delayRange", "value": [-60, 180] }, { "name": "timeRange", "value": [0, 24] }, { "name": "distanceRange", "value": [0, 2400] } ], "data": [ { "name": "flights", "url": "data/flights-200k.json", "transform": [ { "type": "crossfilter", "signal": "xfilter", "fields": ["delay", "time", "distance"], "query": [ { "signal": "delayRange" }, { "signal": "timeRange" }, { "signal": "distanceRange" } ] } ] }, { "name": "filterTimeDistance", "source": "flights", "transform": [ { "type": "resolvefilter", "filter": { "signal": "xfilter" }, "ignore": 1 } ] }, { "name": "filterDelayDistance", "source": "flights", "transform": [ { "type": "resolvefilter", "filter": { "signal": "xfilter" }, "ignore": 2 } ] }, { "name": "filterDelayTime", "source": "flights", "transform": [ { "type": "resolvefilter", "filter": { "signal": "xfilter" }, "ignore": 4 } ] } ] }说明:为保持 JSON 合法,上文已移除原文档 signals 数组中残留的尾随逗号(
distanceRange条目后的,)。实际使用时,每个派生数据集内resolvefilter之后应继续接aggregate、collect等消费变换(示例中以...占位)。
同一套配置的完整可运行版本见 packages/vega/test/specs-valid/crossfilter.vg.json,其中同样使用ignore: 1、ignore: 2、ignore: 4三个 resolvefilter 消费同一个 crossfilter 输出。类型声明可参考 packages/vega-typings/types/spec/transform.d.ts,交互示例可参考 docs/examples/crossfilter-flights.md 及其 vg.json。
四、位掩码语义与底层实现
4.1 crossfilter 侧:掩码如何产生
CrossFilter.js 的Definition声明了两个必填参数:
| 属性 | 类型 | 说明 |
|---|---|---|
| fields | Field[] | 待过滤的字段数组,同一字段可重复出现以支持同一字段上的多个查询 |
| query | Array[] | 逐字段的范围查询数组,每一项必须解析为两元素数字数组,表示应通过过滤的最小值(含)与最大值(不含),即半开区间[min, max) |
| signal | String | 若定义,将计算出的过滤掩码绑定到指定名称的信号(如xfilter) |
每个元组在 Bitmaps.js 中维护一张按位编码的状态表:curr记录当前每个元组通过哪些查询,prev记录上一轮状态,seen用于增量去重。位图宽度根据维度数自适应——少于 8 维用 8 位、少于 16 维用 16 位、否则用 32 位(见array(n, m, array)的分支逻辑),从而用极小的内存承载海量元组的过滤状态。crossfilter 还会在每个输入数据对象上写入_index属性作为元组在位图中的下标,若其他变换覆盖该属性,行为将不确定(见 crossfilter.md)。
4.2 resolvefilter 侧:如何消费掩码
ResolveFilter.js 的核心在transform(_, pulse)方法:
const ignore = ~(_.ignore || 0), // 位取反:0 位即"要忽略"的维度 bitmap = _.filter, mask = bitmap.mask; // exit early if no relevant filter changes if ((mask & ignore) === 0) return pulse.StopPropagation; const output = pulse.fork(pulse.ALL), data = bitmap.data(), curr = bitmap.curr(), prev = bitmap.prev(), pass = k => !(curr[k] & ignore) ? data[k] : null; output.filter(output.MOD, pass);三个关键机制值得展开:
- 按位取反实现"忽略":
ignore = ~(_.ignore || 0)对用户传入的掩码取反,于是用户置 1 的位在内部变成 0。pass判定!(curr[k] & ignore),即元组的当前位图与"未忽略维度掩码"做与运算必须为 0——也就是说所有未忽略的查询都必须通过,任一未忽略查询不满足则被过滤掉。 - 无相关变化的快速退出:若本轮 crossfilter 的
mask与有效位没有交集((mask & ignore) === 0),说明变化的查询都是本 resolvefilter 已忽略的维度,直接返回pulse.StopPropagation,避免无意义的级联计算。 - ADD/REM 的增量推导:当只有一个查询变化时(
(mask & (mask-1)) === 0判断 mask 只有一个置位),ADD 直接复用pass函数、REM 判定元组恰因该位变化而失效;当多个查询同时变化时,则用当前位图与上一轮位图的异或(c ^ (prev[k] & ignore))精确推导哪些元组"新通过"(ADD)与"新失效"(REM),从而把每次刷选的数据搬运量压缩到最小。
最后一行output.filter(output.SOURCE, t => pass(t._index))还会把过滤逻辑附加到源数据脉冲上,保证在数据重排(reflow)场景下过滤依然一致。
4.3 增量更新:crossfilter 的单查询/多查询路径
CrossFilter.js 在update方法中同样区分了两种查询变化场景:incrementOne处理单一查询变化(直接对add/rem数组做增量维护,最快路径),incrementAll处理多查询同时变化(借助seen时间戳去重,并复制 add/rem 供下游 resolvefilter 消费)。查询边界用二分查找(dim.bisect(query))定位新旧范围[lo0, hi0)与[lo1, hi1),仅在边界变化的区间内翻转位,这是整个交叉过滤体系性能的核心来源。
五、测试用例佐证
packages/vega-crossfilter/test/crossfilter-test.js 用{a, b, c}四条数据验证了完整行为:
- 构建两个维度
[a, b],初始查询均为[0, 5],三个 resolvefilter 分别取ignore: 2(忽略 b)、ignore: 1(忽略 a)、ignore: 0(全部生效),数据加入后三个输出均含全部 4 条元组; - 更新
r2 → [1, 3]后:忽略 a 的输出仍有 4 条(只按 b 过滤时 b∈[1,3) 无影响,因为 a 查询未变……实际验证的是忽略位是否生效),忽略 b 的输出剩 2 条,全部生效的输出剩 2 条; - 连续更新两个查询、删除数据、重新插入、修改非索引字段(
c字段)等场景下,输出长度与mod增量数量均与位掩码语义严格一致; - 另一用例
'Crossfilter range is half-open [min, max)'直接验证了查询区间的左闭右开语义。
这些断言(如单查询变化后o1.value.length保持 4、on.value.length降为 2)正是上文位运算规则的数值化证明,可作为读者验证自己配置行为的参考基准。
六、深入阅读
- 变换文档:resolvefilter.md、crossfilter.md
- 消费变换:aggregate.md、collect.md
- 源码实现:ResolveFilter.js、CrossFilter.js、Bitmaps.js、Dimension.js、SortedIndex.js
- 测试与示例:crossfilter-test.js、crossfilter.vg.json、crossfilter-flights.vg.json
【免费下载链接】vegaA visualization grammar.项目地址: https://gitcode.com/gh_mirrors/ve/vega
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考