Vega ResolveFilter 变换深度解析基于 crossfilter 位掩码的高效按维度过滤数据流【免费下载链接】vegaA visualization grammar.项目地址: https://gitcode.com/gh_mirrors/ve/vegaResolveFilter 是 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) 只按 timedistance 只按 delaydistance 只按 delaytime │ │ │ aggregate / collect ... aggregate / collect ... aggregate / collect ...一个resolvefilter变换后面必须紧跟一个消费变换例如 aggregate聚合或 collect仅收集过滤后的数据对象否则过滤结果无处落地。二、变换参数属性类型必填说明filterSignal是对底层 crossfilter 变换的信号引用即配置{signal: xfilter}形式的信号引用ignoreNumber是位掩码bit mask指明在 crossfilter 中应忽略哪些字段。每一位bit对应 crossfilter 变换fields与query数组中的一个字段/查询。若对应位为 1on解析过滤器时忽略该字段与查询元组要进入下游数据流必须通过所有未被忽略的查询ignore的位对应关系由 crossfilter 中fields/query数组的索引顺序决定。以文档示例字段依次为delay、time、distance为例ignore 值二进制忽略的维度保留生效的查询10b001第 0 位 →delaytime、distance20b010第 1 位 →timedelay、distance40b100第 2 位 →distancedelay、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声明了两个必填参数属性类型说明fieldsField[]待过滤的字段数组同一字段可重复出现以支持同一字段上的多个查询queryArray[]逐字段的范围查询数组每一项必须解析为两元素数字数组表示应通过过滤的最小值含与最大值不含即半开区间[min, max)signalString若定义将计算出的过滤掩码绑定到指定名称的信号如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),仅供参考