feat: Add comparators/operators for selection params#9831
Conversation
Add a parameter value reference form for field predicates so
comparisons can use parameter-backed values directly, including
selection parameter fields via `{param, field}`.
For timer selections, expose the resolved param as a field object
(e.g. `{"x": ANIM_value}`) so filters can use `ANIM.x` semantics
through predicate compilation (`ANIM["x"]`).
Also update animation filter relocation and implicit parse inference
to handle parameter-value predicate references, and add unit tests
for expression generation and timer selection integration.
When field predicates use `{param, field}` with a selection parameter,
compile the comparison against the selection store value instead of
directly indexing the resolved selection signal object. This makes
non-timer point selections usable in `lt/lte/gt/gte` comparisons and
avoids invalid expressions like `PICK["x"]` for resolved selections.
Also thread parameter-value expression resolution through
`fieldFilterExpression` and preserve fallback behavior for variable
params. For timer data routing, include `_store` references as
animation-related filters so these expressions are moved to `*_curr`
frame datasets when needed.
Point selections can be attached to layered line charts where `mark.point=true` compiles to a line plus a symbol overlay. In that shape, line items were still receiving clicks while point overlays were non-interactive, causing invalid tuple values and broken comparator filters. Ignore path-mark clicks (`line`/`trail`/`area`) for point selection tuple updates, and make point-style symbol overlays interactive with a pointer cursor so clicks are captured by the visible points. Update point-selection tests to cover the stricter tuple guard and the interactive point overlay cursor behavior, including store-based timer frame expression expectations.
Extend `{param, field}` comparator references with an optional
`empty` flag so field predicates can define how empty selection
params should be handled without extra manual guards.
When a comparator references a selection parameter, compile the
expression with explicit empty-selection semantics:
- `empty: true` keeps rows while the selection is empty
- `empty: false` rejects rows while the selection is empty
Add coverage for both modes in selection predicate compilation tests.
Selection-parameter comparator refs previously evaluated against null
when the selection store was empty, which produced operator-dependent
coercion behavior at initialization (for example, `lte` keeping only
the first point or `gt` dropping it).
Default `{param, field}` comparator refs to `empty: true` semantics
for selection params so empty selections behave intuitively by passing
rows until a value is selected. Explicit `empty: false` remains
supported for strict filtering workflows.
Add regression coverage for default empty behavior and updated timer
frame filter expressions.
Regenerate the Vega-Lite JSON schema so field comparator predicates
(`lt/lte/gt/gte/equal`) accept `ParameterValueRef` objects in
validation, including `{param, field}` and the new optional
`empty` semantics.
This aligns editor/schema validation with the implemented compiler
behavior for parameter-based comparator predicates.
Comparator predicates that reference selection params by `{param, field}`
can appear in sibling views (for example, a `vconcat` line view filtered by
a bar selection defined in another view).
Lookup previously only walked local ancestors, which failed for sibling
selection definitions and produced fallback expressions against the raw
param signal. Traverse from the root model when needed so comparator
refs resolve against the correct selection component and store.
Add regression coverage for composed-view selection comparator filters.
Document `{param, field}` comparator predicates for field filters and
condition tests, including selection-empty semantics and default
behavior.
Add a new interactive gallery example demonstrating cross-view
selection comparators in a vconcat dashboard and register it in the
examples catalog.
Update selection clear and legend unit test expectations after point-selection tuple guards began excluding path mark types (`line`, `trail`, `area`). Also fix lint violations in predicate compilation introduced during comparator ref work by switching to template literals for store signal naming.
Deploying vega-lite with
|
| Latest commit: |
e3aea81
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://8436c88e.vega-lite.pages.dev |
| Branch Preview URL: | https://feat-param-comparisons.vega-lite.pages.dev |
Restore top-level selection param signal assembly to use
`vlSelectionResolve(...)` for timer selections.
A prior change emitted `{field: <param>_value}` at top level, but
`<param>_value` is a unit-scope signal. During example compilation this
produced parser errors (e.g. `Unrecognized signal name: "animation_frame_value"`)
for animated examples.
Update affected point selection expectation accordingly.
Add selection-comparator predicate tests to cover operator-specific
`{param, field}` handling (`equal`, `lt`, `gt`, `gte`) and the
non-selection fallback path when a referenced selection is missing.
These tests increase branch coverage for predicate compilation and
protect against regressions in empty/default semantics and store-based
value resolution.
| import {Model} from './model.js'; | ||
| import {parseSelectionPredicate} from './selection/parse.js'; | ||
|
|
||
| function findSelectionComponent(model: Model, param: string) { |
There was a problem hiding this comment.
Comparator refs can be evaluated in a different unit model than where the selection is declared (for example, vconcat sibling views). Ancestor-only lookup misses that case, so this helper first tries normal lookup and then scans from the root model to find the declared selection component.
| return undefined; | ||
| } | ||
|
|
||
| function resolveSelectionParameterValueExpr(model: Model, v: any): string { |
There was a problem hiding this comment.
This resolves {param, field} using the selection store’s projected value index (values[idx]) rather than assuming param[field] exists everywhere.
| return `(length(data(${store})) ? data(${store})[0].values[${idx}] : null)`; | ||
| } | ||
|
|
||
| function getParameterValueRef(predicate: FieldPredicate): ParameterValueRef { |
There was a problem hiding this comment.
This extracts the comparator’s value-ref payload uniformly across equal/lt/lte/gt/gte so empty-handling logic is operator-agnostic and stays in one place.
| } | ||
| ], | ||
| "update": "datum && item().mark.marktype !== 'group' && indexof(item().mark.role, 'legend') < 0 ? {unit: \"layer_2\", fields: org_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"origin\"]]} : null", | ||
| "update": "datum && item().mark.marktype !== 'group' && indexof(item().mark.role, 'legend') < 0 && indexof(['line', 'trail', 'area'], item().mark.marktype) < 0 ? {unit: \"layer_2\", fields: org_tuple_fields, values: [(item().isVoronoi ? datum.datum : datum)[\"origin\"]]} : null", |
There was a problem hiding this comment.
It's not that pretty that this change shows up in so many examples, but I didn't find another way to ensure that points received clicks/interactions instead of lines when plotted together.
| // find animation-related filters to be applied on the per-frame dataset | ||
| const timerValueSignal = `${selCmpt.name}_value`; | ||
| const timerObjectSignal = `${selCmpt.name}[`; | ||
| const timerStoreSignal = `${selCmpt.name}_store`; |
There was a problem hiding this comment.
Comparator refs for selection params compile to expressions using data('<param>_store'). For timer selections, those filters must be moved to *_curr alongside other animation filters; otherwise frame/dataflow behavior diverges.
| const store = stringValue(`${valueRef.param}_store`); | ||
| const isEmptyExpr = `!length(data(${store}))`; | ||
| const empty = valueRef.empty ?? true; | ||
| return empty ? `(${isEmptyExpr} || (${expr}))` : `(!${isEmptyExpr} && (${expr}))`; |
There was a problem hiding this comment.
Defaulting to true here matches the selection-predicate default (empty passes) and prevents surprising partial data at initialization before any click.
This PR extends field predicate comparators (
equal,lt,lte,gt,gte) to accept parameter value references and makes them practical for selection params. My original motivation came from #9830 where I had to use a not that intuitive syntax for gt/lt comparisons when using paramaters for the new time/animation functionality. As I was working on this I also thought it was a natural fit in the grammar that already existed for variable parameters and that it had benefits when used with selection parameters not related to time encodings, so I added a couple of example to illustrate that.Here is how the simple new example in the docs look upon interaction:

And here is how the new gallery example looks with a

ltcomparator and empty initial condition:A few details
{param: ...}and{param: ..., field: ...}support in field comparators.emptyon comparator refs to match variable params:empty: true=> empty selection passes predicateempty: false=> empty selection fails predicateempty: true, avoiding init-time null coercion surprises.vconcatwhere selection is defined in one view and used in another).line/trail/area) for tuple capture.mark.point=true) interactive with pointer cursor so clicks are captured as expected.ParameterValueRef(includingempty) in comparator definitions.Checklist
npm testruns successfullysite/docs/+ examples.Tips: