Skip to content

Add pan/zoom interaction switches for static-viewport charts#103

Merged
Alek99 merged 3 commits into
mainfrom
feat/interaction-static-pan-zoom
Jul 20, 2026
Merged

Add pan/zoom interaction switches for static-viewport charts#103
Alek99 merged 3 commits into
mainfrom
feat/interaction-static-pan-zoom

Conversation

@FarhanAliRaza

@FarhanAliRaza FarhanAliRaza commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Problem

XY charts are interactive by default, but interaction_config() only configured hover, click, selection, brush, crosshair, view-change events, and linked views — there was no supported way to make a chart's viewport static. Dashboard charts intended as fixed summaries could still be drag-panned or wheel-zoomed, leaving bars or axes partially out of view. Hiding the modebar does not disable the gestures, and disabling pointer events on a framework wrapper is brittle because the chart's canvas owns the interaction handlers.

API

chart = xy.bar_chart(
    xy.bar(["A", "B", "C"], [10, 20, 30]),
    xy.interaction_config(pan=False, zoom=False),
)
# or equivalently as chart-level kwargs: xy.bar_chart(..., pan=False, zoom=False)
  • pan=False — plain-drag pan gestures are ignored; the modebar Pan button is hidden. Shift-drag / box select stay governed by select/brush.
  • zoom=False — wheel zoom, box-zoom, and double-click reset are ignored; the modebar zoom controls (Zoom In/Out, Box Zoom, Reset View, percent label) are hidden. Double-click still clears selection.
  • Defaults remain enabled: the flags serialize only when explicitly set, and the client treats an absent key as enabled, so existing specs are byte-identical.
  • Hover, click, and selection remain independently configurable and functional on a static chart.

Behavior details

  • With zoom=False the wheel handler returns before preventDefault(), so the page scrolls naturally over a static chart embedded in a dashboard.
  • With pan disabled, the drag-tracking object is never created; a stationary press still fires click/pick events and hover is unaffected.
  • A chart with both flags disabled ignores inbound linked-view (link_group) updates; with only one disabled, linked updates still apply.
  • Non-bool values raise ValueError via the existing centralized interaction validation.
  • No Rust/ABI changes — interaction config never touches the core. python/xy/static/ regenerated via node js/build.mjs.

Testing

  • New tests: explicit pan/zoom serialization (True and False), chart-level kwargs, and ValueError on non-bool values (tests/test_components.py).
  • Existing exact-dict interaction spec tests pass unmodified aside from the deliberately extended declarative-contract test — confirming defaults serialize nothing.
  • Full suite: 2021 passed. ruff check, ruff format --check clean; ty check has one pre-existing diagnostic unrelated to this change (present on a clean tree).

Closes #80

interaction_config() covered hover, click, selection, brush, crosshair,
view-change events, and linked views, but there was no supported way to
freeze a chart's viewport: dashboard summary charts could still be
drag-panned or wheel-zoomed, leaving bars or axes out of view, and
hiding the modebar did not disable the gestures.

Add pan= and zoom= to interaction_config() and the chart-level kwargs.
pan=False ignores plain-drag pan; zoom=False ignores wheel zoom,
box-zoom, double-click reset, and hides the modebar zoom controls
(double-click still clears selection). Both flags serialize only when
explicitly set, so absent keys stay enabled and existing specs are
unchanged. With zoom disabled the wheel handler returns before
preventDefault so the page scrolls naturally over a static chart, and a
chart with both flags disabled ignores inbound linked-view updates.
@codspeed-hq

codspeed-hq Bot commented Jul 20, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 94 untouched benchmarks
⏩ 1 skipped benchmark1


Comparing feat/interaction-static-pan-zoom (58a60c6) with main (3c5054f)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

@FarhanAliRaza FarhanAliRaza added the valid Issue is validated. This pr actually fixes the issue. Needs Code Review. label Jul 20, 2026

@Alek99 Alek99 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review findings:

  • [P2] js/src/50_chartview.js:914: charts with pan and zoom disabled still set touch-action:none, so a touchscreen swipe over a static chart cannot scroll the containing dashboard. Make touch-action depend on enabled gestures/drag mode.
  • [P3] js/src/53_interaction.js:1205: pan-disabled charts still initialize dragMode="pan" and show a grab cursor even though plain dragging does nothing. Initialize/sanitize the mode from the enabled interactions.

@Alek99
Alek99 merged commit e3311dd into main Jul 20, 2026
21 checks passed
Alek99 added a commit that referenced this pull request Jul 21, 2026
The move in this PR promoted docs/engineering/ to spec/ and CLAUDE.md
declares that tree the source of truth. An audit of all 24 documents
against the code found the tree had drifted at PR #96: five behavior
PRs (#103, #105, #106, #108, #115) had landed with no spec update, and
~100 claims contradicted shipped code.

Accuracy: correct claims that no longer match the implementation,
including the Rust ABI version (v31 -> v36), the "zero-crate cdylib"
claim (png is a real dependency), the pyramid ABI function names, the
panic-shield status, the R2/VAO claim in the renderer doc, the
WASM/Web-Worker architecture in dossier §3/§8 (superseded by §32's
C-ABI cdylib), the count-only tier rule, the matplotlib shim overhead
figures (the cited guardrail says +60% at 10k, not +9%), and the
Plotly Scattergl 10M figure that appeared as two different numbers
from the same run.

Coverage: add spec/api/export.md, spec/api/interaction.md and
spec/design/wire-protocol.md for three surfaces that had no coverage
at all, plus the config.py threshold table, full JS and Rust module
inventories, and the hexbin centers-only wire contract that three
renderers must expand identically.

Structure: group 15 top-level files into api/, design/, matplotlib/,
benchmarks/ and process/, and rewrite spec/README.md as a real index
(it previously listed only the two SVG assets, leaving 21 of 22 docs
unreachable). All 150 consumer references repointed, including the CI
--emit-md target and the compat-matrix generator.

Guard: verify_sdist.py pinned only 5 top-level spec files, so the
sdist could silently lose spec/design/ or spec/assets/ and still
pass; it now asserts markdown under every subdirectory and both SVGs.
Alek99 added a commit that referenced this pull request Jul 21, 2026
…roup the tree (#122)

* Move engineering docs to root spec directory

* Resync spec/ with the implementation and group the tree

The move in this PR promoted docs/engineering/ to spec/ and CLAUDE.md
declares that tree the source of truth. An audit of all 24 documents
against the code found the tree had drifted at PR #96: five behavior
PRs (#103, #105, #106, #108, #115) had landed with no spec update, and
~100 claims contradicted shipped code.

Accuracy: correct claims that no longer match the implementation,
including the Rust ABI version (v31 -> v36), the "zero-crate cdylib"
claim (png is a real dependency), the pyramid ABI function names, the
panic-shield status, the R2/VAO claim in the renderer doc, the
WASM/Web-Worker architecture in dossier §3/§8 (superseded by §32's
C-ABI cdylib), the count-only tier rule, the matplotlib shim overhead
figures (the cited guardrail says +60% at 10k, not +9%), and the
Plotly Scattergl 10M figure that appeared as two different numbers
from the same run.

Coverage: add spec/api/export.md, spec/api/interaction.md and
spec/design/wire-protocol.md for three surfaces that had no coverage
at all, plus the config.py threshold table, full JS and Rust module
inventories, and the hexbin centers-only wire contract that three
renderers must expand identically.

Structure: group 15 top-level files into api/, design/, matplotlib/,
benchmarks/ and process/, and rewrite spec/README.md as a real index
(it previously listed only the two SVG assets, leaving 21 of 22 docs
unreachable). All 150 consumer references repointed, including the CI
--emit-md target and the compat-matrix generator.

Guard: verify_sdist.py pinned only 5 top-level spec files, so the
sdist could silently lose spec/design/ or spec/assets/ and still
pass; it now asserts markdown under every subdirectory and both SVGs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

valid Issue is validated. This pr actually fixes the issue. Needs Code Review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add first-class controls to disable pan and zoom

2 participants