Specialized visualization interaction and motion
SpecializedData validates raw measurements and builds normalized geometry.
SpecializedChart renders the geometry, exact-value key, selection, hierarchy
navigation, and optional visual transitions. ContinuousHeatmap maps an explicit
raw domain to colors. Plot owns measured frame/input/readout/label placement;
it does not infer a second scale system.
New experiences animate by default; legacy presentation stays compatible
SpecializedChart and ContinuousHeatmap default to theme-aware data animation,
consistent with raw Cartesian experiences. .animate(false) directly snaps
updates; .motion(bool) is the same switch retained as a compatibility alias.
.animation(MotionSpec) overrides timing without bypassing reduced motion.
Changing timing starts from displayed geometry/color rather than reinterpreting
elapsed time under a new curve. Hover, exact readouts and controlled proposals
work independently of animation. Labels remain a separate density decision.
MotionPolicy supplies Resize timing for specialized geometry and Sankey,
and StateChange for heatmap color, entry/exit and keyed axis movement. The
normalized legacy SankeyChart retains static defaults: opt into .animate(true)
once for the layout experience. Discrete Heatmap levels 0–4 are unchanged.
The earlier frozen specialist milestone's opt-in default was a rollout stage,
not an enduring API requirement. Hosts need no extra switches for new charts.
Downstream composition, lifecycle and budgets
For a normal bounded overview, mount one stable chart identity, supply complete
validated caller data, and pass controlled selection
plus proposal handlers. The hierarchy example below is the current API. Add
.labels(true) when measured label density is useful; neither this flag nor
an on_hover callback is required for geometric picking or native readouts.
For continuous heatmaps, provide explicit domain, stable cell/axis identities,
rows/columns; never animate numbers in a separate host loop.
- Keep a chart identity stable across accepted revisions. Replace it when the dataset represents a different business object, not to force a repaint.
- Publish only accepted data. Rejecting selection or drill proposals leaves
the accepted state unchanged; any already-running accepted transition keeps
progressing. A rejected update is not an animated excursion followed by a
rollback. Use
Stalewith the last verified data and refusal reason when a refresh fails. - Builders validate the supplied dataset, not a patch stream. Merge partial server updates into caller-owned data and validate that complete revision before publishing it. Invalid inputs are errors, not silently dropped marks. Partially accepting a revision is a host decision and must be represented by the actual accepted data and an honest status, not manufactured interpolation.
- For specialized shapes, an accepted revision retargets from the current
display. Removed shapes immediately lose interaction/semantics while their
decorative exit may finish. Pass a valid empty
SpecializedDatainReadyto retain those exits while reporting Empty; explicitPlotState::Empty, Loading, Error and Unavailable reset specialized animation state. - Reduced motion settles endpoints and discards exits.
.animate(false)snaps data presentation without disabling input. Unmounting is not a promise to complete an exit: keyed slots use the existing window-scoped two-generation grace and need normal root semantic frame publication for pruning. Remounting after pruning starts fresh; do not rely on visual state as stored data.
These are overview components, not virtualized large-data engines. There is no benchmarked universal mark cap or automatic animation budget in this milestone. Specialized animation stores transitions per displayed vertex, including exits; painting and exact picking scan geometry. Measured label placement adds pairwise collision checks over candidate rows. Heatmaps materialize the row × column grid, not only cells with observations. Sankey refinement costs O(V E²).
Downstream must bound the visible hierarchy depth, bin/aggregate distributions
using explicit statistical semantics, and page/filter heatmap axes before
mounting an unbounded dataset. Disable labels first for density, and use
.animate(false) for high-churn views whose measured budget is exceeded. Neither
switch removes geometry construction or hit-testing cost. Coalesce accepted
revisions upstream rather than enqueueing every intermediate server packet.
Choose limits from profiling the actual viewport/data on target platforms;
the 32-frame regression proves correctness, not large-data throughput.
Caller-owned state
Use .selected(Some(id)) or .selected(None) for controlled selection and
.on_current(...) to receive proposals. Refusing a proposal does not change
painted or semantic selection. The legacy .current(id) retains local keyboard
selection behavior. .on_hover(...) reports geometric hover separately; it does
not accept selection. Hover emphasizes the shape and feeds a native floating
tooltip; the header and persistent key provide exact values without hovering.
Hierarchy navigation is explicit:
let data = SpecializedData::sunburst_at(&root, &focus_id)?;
SpecializedChart::new("allocation", "Allocation", PlotState::Ready(data))
.selected(selection)
.on_current(propose_selection)
.on_navigate(propose_focus)
.motion(true)
.labels(true)
treemap_at and sunburst_at validate the entire tree, then lay out the
selected subtree. Ancestor controls navigate back; child controls drill down.
Their identities and labels come from the supplied nodes. The focused control
is disabled and has no handler. The host accepts navigation by rebuilding data
with the proposed identity; no hidden component focus path changes. Unknown
focus is an error, not an empty tree. Plain treemap and sunburst remain
available without navigation chrome.
Exact geometry and semantic envelopes
SpecializedData::geometry(id) exposes actual normalized vertices.
hit_test(point, pixel_size, stroke_width) uses the same polygon fill and stroke
geometry as painting, including annular holes and reverse paint-order overlap.
Points outside the plot are rejected. Stroke tolerance is half the actual
pixel width, not a guessed normalized rectangle. The shared Plot::hit_test
callback also receives the measured pixel frame and can only return live mark
identities. Removed animated shapes cannot be returned by that callback.
Platform accessibility regions remain tight axis-aligned envelopes, measured during prepaint. They are not claims of polygon-shaped accessibility support. Visual labels and their leader lines are not shape hit regions. During motion, semantic envelopes and pointer picking use the displayed geometry, while raw semantic values already describe the latest verified input. Selection/hover outlines are decorative emphasis, not expanded data hit regions.
Visual motion never invents raw readings
New charts use identity-keyed geometry and enter/exit opacity using
the existing Transition and MotionPolicy contracts. Updates retarget from
the current display. A reinserted identity resumes its retained visual state;
removed shapes may finish fading but immediately leave keys, semantics, and
picking. Empty data publishes Empty while the last decorative exit finishes.
Error/loading/unavailable clears animation state rather than displaying an old
measurement as current. Reduced motion settles at the latest endpoint and
discards exits immediately.
Polygon tessellation-count changes interpolate from the displayed vertex sequence. This is a visual morph, not a claim of interpolated statistical area or conserved totals at intermediate times. Exact values never interpolate.
Heatmap motion interpolates colors and fades in changed exact value text. It
does not tween numbers. None remains missing, zero remains a verified
observation, out-of-domain values are errors, and the visible domain legend
always reports the declared current scale. Legacy discrete levels 0–4 remain
unchanged. Tokens and the active locale continue to own styling and Kit wording.
Heatmap row/column identity already belongs to the caller: HeatAxis.id is
independent of its label, and ContinuousHeatCell.id is independent of its
row/column coordinates. Reorder axis vectors without changing those ids.
When migrating positional data, introduce domain keys upstream; do not derive
ids from the new index after sorting. Existing rows(["east", "west"]) treats
those strings as stable identities and labels, not positions.
Live headers, row labels and cells use measured shared FLIP movement.
Reordering paths can cross; later-painted live cells win exact pointer picking
and consume their action so an underlying cell cannot overwrite selection.
Flexible layout slots use current column allocation. Their children use
flip_size with assigned border-box dimensions, so column-count changes also
animate displayed widths and reflow text. The first natural layout measures
the matrix; subsequent width changes request the next measured frame. Pointer
and semantic bounds follow displayed children, not the settled slots.
Removed measured cells/axis labels fade as clipped decorations without ids,
tooltips, focus or actions. Reinsertion within the retained exit resumes opacity
and position; direct/reduced updates discard exits immediately. Labels and
values belong to the current revision even during visual interpolation.
Measured labels and conserved flows
.labels(true) on a specialized chart enables measured on-plot labels and
leader lines, in addition to the persistent exact-value key. Plot shapes
each string through the text system, prioritizes the current readout, and tries
nonoverlapping slots within the frame. Labels that cannot fit are omitted;
their values remain accessible in the key/header/tooltip. This is a bounded
placement heuristic, not a global optimization or a polygon-inscription claim.
SankeyOrder::Barycenter starts from stable identity order, alternates weighted
sweeps, refines adjacent swaps, and retains the best weighted center-line
crossing score. Skip-layer links participate; zero flows and shared endpoints
do not manufacture crossings. Stable link order makes accumulation and tie
behavior reproducible. All layouts retain the single raw-weight-to-height
scale; crossing refinement changes ordering, never amounts or ribbon widths.
The crossing proxy does not guarantee minimum curved-ribbon intersections.
Refinement costs O(V E²): use it for overview-sized flows, not enormous DAGs.
Sankey pointer picking inverts the same cubic x curve used by painting and tests
the actual upper/lower ribbon edges. Nodes take precedence over ribbons. Its
semantic identities are node.<id> and link.<id>. .labels(true) preserves
the existing persistent key and also enables measured floating plot labels.
Animated Sankey nodes and ribbons are keyed separately. Ribbon attachment x coordinates follow displayed node edges; vertical offsets and thickness tween in plot units, avoiding the false taper produced by multiplying separately interpolated node heights and flow fractions. Raw readings remain the exact accepted endpoint values. A link whose endpoint identity changes crossfades its old decorative connection and new live connection rather than traversing unrelated nodes. Removed connections retain their old endpoint nodes only for decorative retirement; neither is added back to the live picker or readout.
Review surfaces
specialized-exploration: live hierarchy/distribution updates, drill/back, refusal, removal/reinsertion, labels, and interrupted motion.continuous-heatmap-transition: negative → positive → missing color/text changes beside a stable zero reading.heatmap-reordering: row/column crossing, entry, exit, reinsertion and direct updates.sankey-motion: keyed node/ribbon updates, current readouts, retirement and selection.specialized,specialized-distribution,continuous-heatmap, andsankey-layout: baseline raw-value, state, and conservation presentations.
Use explicit simulated frame advancement for intermediate/retarget/exit review. Static baseline capture deliberately uses reduced motion and is not evidence of animation correctness. No network, persistence, product models, or financial data authority belongs to these components.
The opt-in actual-renderer regression uses the local headless playback protocol:
cargo build --manifest-path tools/headless-visual/Cargo.toml
python3 crates/gpui-kit/tests/specialized_playback.py
python3 crates/gpui-kit/tests/specialized_layout_playback.py
It records 32 light/dark frames and redacted semantic snapshots under
target/specialized-playback, checking interrupted geometry continuity, exact
raw readings, absent removed targets, separated measured labels and changed
intermediate pixels. Supply an output directory to retain review artifacts.
These are simulated offscreen frames, not native-window timing or FPS evidence.