Interaction reference
Searchable interaction contract. Chart-local state and callbacks by default. createPlotInteraction() for shared semantic state across plots and UI.
Static default
No capture layer, tooltip, selection, or zoom until a capability is enabled. Page scroll is not hijacked by unused tools.
Capability props
inspect
Enables inspection, the default HTML tooltip, semantic crosshair, keyboard traversal, and optional pinning. Inputs are true or options with mode, pin, maxDistance, content, and contentMode.
Point selection
select={{ type: "point", multiple: true }} stores stable semantic keys. Supply key for every row.
Interval selection
select={{ type: "interval", mode: "x" | "y" | "xy", persistent: true }} enables an explicit Select area tool and emits domain and pixel bounds. In facets, add preset: "independent" | "union" | "cross-panel" to replace one panel, combine panel selections, or project one domain through compatible panels.
zoom
zoom={{ mode: "x" | "y" | "xy" }} enables the explicit Zoom area tool. Reset zoom and double-click return to the natural domains.
legendFocus
legendFocus={true} enables discrete legend preview and committed focus. Use legendFocus={{ preview: false }} to disable hover/focus preview while retaining click, touch, Enter, Space, Escape, and arrow-key controls. It requires stable row key values and does not make continuous ramps interactive.
legendFilter
legendFilter={true} adds native Show-group checkboxes to discrete color and fill legends. It changes the rows supplied to facets, statistics, scales, and rendering while preserving the full legend catalog and categorical color identity. Configure mode: "exclude" | "include" and multiple; receive typed clauses through onlegendfilter. It is independent of presentation-only legendFocus.
Controlled tool
tool and ontoolchange control the active Inspect, Select area, or Zoom area mode. Keep the value in Svelte state when application controls and the plot tool rail must stay synchronized:
<script lang="ts">
import type { InteractionTool } from "@ggsvelte/svelte";
let activeTool = $state<InteractionTool>("inspect");
</script>
<GGPlot
inspect={true}
select={{ type: "interval" }}
tool={activeTool}
ontoolchange={(next) => (activeTool = next)}
/>
A controlled unavailable tool requests a change and emits a diagnostic; it does not silently arm a different drag behavior. The active tool remains local to one chart; shared controllers coordinate data semantics, not UI modes.
Shared controller
createPlotInteraction<Key>({ onchange? }) returns a reactive PlotInteractionController<Key>. Pass it through the interaction prop and name the semantic channels with the required interactionScope={{ keys, x?, y? }}; controlled plots never fall back to a generic scope or infer x/y channel names from encodings. Controlled zoom requires an explicit scope for every active channel (x, y, or both).
- Reads:
selected(scope),emphasized(scope),intervals(scope),isSelected(key, scope),zoom(scope),snapshot, andrevision. - Selection:
setSelection,toggleSelection, andclearSelection. - Lightweight presentation:
setEmphasisandclearEmphasis. - Facet intervals:
setInterval,clearInterval, andclearIntervals. - Domains:
setZoomandresetZoomfor finite numeric x/y pairs. - Data replacement:
reconcileKeys(validKeys, { scope })explicitly removes selected or emphasized keys that no longer exist.
Scopes are application-level names. Reuse a key scope only where keys mean the same thing, and reuse x/y scopes only where their data domains are compatible. Every mutation returns one immutable transition or null for a no-op. Passive consumers never republish controller state, preventing linked-view feedback loops. Do not mutate the controller inside its synchronous onchange callback; schedule a later Svelte application update instead. See the linked views example.
Identity
key is a field name or accessor returning a unique stable PropertyKey. Public events expose semantic keys, aggregate sourceKeys, and lineageCount, never renderer indices.
Events
oninspect
Receives PlotInspection: change with transient or pinned focus and members, or clear.
onselect
Receives PlotSelection. Point selection emits end and clear. Interval selection emits start, change, end, and clear.
onzoom
Receives ZoomEvent: end with explicit domains or clear with null domains.
onlegendfocus
Receives LegendFocusEvent: a transient or committed change carrying the raw encoded value, formatted label, scale channel, and stable row keys, or a small clear event. The same object is included in oninteraction.
onlegendfilter
Receives LegendFilterEvent: a change with one typed LegendFilterClause, or clear with clause: null. Legend filtering changes pipeline input and is not folded into the presentation interaction union.
oninteraction
Receives the same discriminated PlotInteractionEvent union emitted by the focused callbacks. Narrow on type and phase.
ondiagnostic
Receives structured PlotDiagnostic objects (InteractionDiagnostic or DeprecationDiagnostic) with severity, code, message, prop, suggestions, and docUrl. Deprecation advisories also carry since and removeIn.
<GGPlot
ondiagnostic={(diagnostic) =>
console.warn(diagnostic.code, diagnostic.message, diagnostic.suggestions)}
/>
Every event has a source: pointer, keyboard, touch, or programmatic.
Diagnostics
INTERACTION_INTERVAL_FACET_UNSUPPORTED
Brush zoom currently requires one unfaceted panel.
- Prop:
zoom - Severity:
warning - Try: Remove the facet; Use faceted interval selection; Zoom a linked detail view
- More: https://ggsvelte.sh/guide/interaction-reference#interaction-interval-facet-unsupported
INTERACTION_INVALID_MAX_DISTANCE
inspect.maxDistance must be a finite non-negative CSS-pixel distance.
- Prop:
inspect.maxDistance - Severity:
error - Try: Use a finite number greater than or equal to zero
- More: https://ggsvelte.sh/guide/interaction-reference#interaction-invalid-max-distance
INTERACTION_POINT_REQUIRES_KEY
Durable point selection requires a stable key field or accessor.
- Prop:
key - Severity:
warning - Try: Pass key="id"; Pass a stable key accessor
- More: https://ggsvelte.sh/guide/interaction-reference#interaction-point-requires-key
INTERACTION_INTERVAL_PRESET_REQUIRES_KEY
Coordinated interval presets (union, cross-panel) require a stable key field or accessor; without one they combine no rows.
- Prop:
key - Severity:
warning - Try: Pass key="id"; Pass a stable key accessor
- More: https://ggsvelte.sh/guide/interaction-reference#interaction-interval-preset-requires-key
INTERACTION_INVALID_KEY
A key accessor returned null, undefined, or a non-PropertyKey value.
- Prop:
key - Severity:
error - Try: Return a stable string, number, or symbol for every row
- More: https://ggsvelte.sh/guide/interaction-reference#interaction-invalid-key
INTERACTION_DUPLICATE_KEY
The key accessor returned a duplicate value; durable interaction is disabled for that value.
- Prop:
key - Severity:
error - Try: Use a field that uniquely identifies each source row
- More: https://ggsvelte.sh/guide/interaction-reference#interaction-duplicate-key
INTERACTION_UNSTABLE_KEY
The key accessor returned a different value for the same source row.
- Prop:
key - Severity:
error - Try: Return an immutable field that uniquely identifies each row
- More: https://ggsvelte.sh/guide/interaction-reference#interaction-unstable-key
INTERACTION_MISSING_LINEAGE
A synthetic or aggregate mark did not expose source-row lineage.
- Prop:
layers - Severity:
warning - Try: Use a stat that preserves source-row lineage
- More: https://ggsvelte.sh/guide/interaction-reference#interaction-missing-lineage
INTERACTION_LEGEND_REQUIRES_KEY
Legend focus requires stable row keys so encoded legend values never become identities.
- Prop:
key - Severity:
warning - Try: Pass key="id"; Pass a stable key accessor
- More: https://ggsvelte.sh/guide/interaction-reference#interaction-legend-requires-key
INTERACTION_LEGEND_DISCRETE_ONLY
Legend focus currently applies to discrete color and fill legends; continuous ramps remain static.
- Prop:
legendFocus - Severity:
advisory - Try: Use a discrete color or fill mapping; Keep the continuous ramp static
- More: https://ggsvelte.sh/guide/interaction-reference#interaction-legend-discrete-only
INTERACTION_INTERVAL_SCALE_UNSUPPORTED
Interval domains and brush zoom require continuous linear, log, or time scales.
- Prop:
scales - Severity:
warning - Try: Use a continuous positional scale; Use point inspection for band data
- More: https://ggsvelte.sh/guide/interaction-reference#interaction-interval-scale-unsupported
INTERACTION_TOOL_UNAVAILABLE
The requested interaction tool is unavailable for the enabled capabilities.
- Prop:
tool - Severity:
warning - Try: Enable the matching capability; Choose an available interaction tool
- More: https://ggsvelte.sh/guide/interaction-reference#interaction-tool-unavailable
INTERACTION_SCOPE_WITHOUT_CONTROLLER
interactionScope is ignored without an interaction controller; chart-local scope is derived from key and aes.
- Prop:
interactionScope - Severity:
advisory - Try: Pass interaction={createPlotInteraction()} to control this plot; Remove interactionScope from uncontrolled plots
- More: https://ggsvelte.sh/guide/interaction-reference#interaction-scope-without-controller
INTERACTION_HANDLER_WITHOUT_CAPABILITY
An interaction handler is set but its capability prop is not enabled, so the handler never fires.
- Prop:
oninspect / onselect / onzoom / onlegendfocus / onlegendfilter - Severity:
advisory - Try: Enable the matching capability prop (for example select for onselect); Remove the unused handler
- More: https://ggsvelte.sh/guide/interaction-reference#interaction-handler-without-capability
INTERACTION_INSPECT_X_ON_COL
inspect.mode draws a vertical guide through column marks; columns already encode x as a filled band, so the guide cuts the bar body and rarely adds information.
- Prop:
inspect.mode - Severity:
advisory - Try: Use inspect={{ mode: "exact" }} (or leave mode as "auto") for GeomCol; Prefer muteSiblings for sibling de-emphasis instead of an axis guide
- More: https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-x-on-col
INTERACTION_INSPECT_X_ON_BAR
inspect.mode draws a vertical guide through bar marks; bars already encode the band axis as a filled region, so the guide cuts the bar body and rarely adds information.
- Prop:
inspect.mode - Severity:
advisory - Try: Use inspect={{ mode: "exact" }} (or leave mode as "auto") for GeomBar; Prefer muteSiblings for sibling de-emphasis instead of an axis guide
- More: https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-x-on-bar
INTERACTION_INSPECT_X_BISECTS_COL_LABELS
inspect.mode draws a vertical guide through GeomCol marks that also carry GeomText/GeomLabel values; the guide bisects the on-bar totals and makes them hard to read.
- Prop:
inspect.mode - Severity:
warning - Try: Use inspect={{ mode: "exact" }} (or leave mode as "auto") when columns have value labels; Keep value labels; drop the x/xy guide rather than dropping the labels
- More: https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-x-bisects-col-labels
INTERACTION_INSPECT_X_BISECTS_BAR_LABELS
inspect.mode draws a vertical guide through GeomBar marks that also carry GeomText/GeomLabel values; the guide bisects the on-bar totals and makes them hard to read.
- Prop:
inspect.mode - Severity:
warning - Try: Use inspect={{ mode: "exact" }} (or leave mode as "auto") when bars have value labels; Keep value labels; drop the x/xy guide rather than dropping the labels
- More: https://ggsvelte.sh/guide/interaction-reference#interaction-inspect-x-bisects-bar-labels
Accessibility
The plot surface is named and keyboard focusable when interaction is enabled. Arrow keys or brackets traverse data; Enter or Space pins or commits the active tool; Escape dismisses. A polite live region announces concise state while pinned HTML remains labelled, navigable DOM. Area tools remain explicit so ordinary page scrolling is available until a user chooses a drag mode.
Committed interval and zoom state exposes precise Edit-bounds buttons in the tool rail. Their inline form uses labelled native inputs, stages drafts until Apply, validates log/time/category constraints, restores trigger focus after Apply or Cancel, and supports Escape. Linear and reversed domains use ascending data values; time uses ISO 8601 text; band intervals use inclusive native selects. Clear panel selection, Clear all selections, Reset zoom, and Reset legend filters remain separate operations.