Skip to content

fix(zoom): split ZoomAndPanMode into per-gesture flags + honor NoFit in manual Zoom - #2177

Merged
beto-rodriguez merged 6 commits into
masterfrom
fix/issue-2175-granular-zoom-pan-flags
Apr 27, 2026
Merged

fix(zoom): split ZoomAndPanMode into per-gesture flags + honor NoFit in manual Zoom#2177
beto-rodriguez merged 6 commits into
masterfrom
fix/issue-2175-granular-zoom-pan-flags

Conversation

@beto-rodriguez

@beto-rodriguez beto-rodriguez commented Apr 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds PanX, ZoomX, PanY, ZoomY to ZoomAndPanMode so each gesture can be enabled independently (e.g. ZoomMode = ZoomAndPanMode.ZoomX to allow wheel/pinch zoom on the X axis with no panning).
  • Keeps X, Y, Both as composites (X = PanX | ZoomX, Y = PanY | ZoomY, Both = X | Y) — code or XAML that references them by name keeps the existing pan+zoom behavior.
  • Updates CartesianChartEngine so each gesture consults the right flag: Zoom and zoom-by-section check ZoomX/ZoomY, Pan and the pointer-up bounce-back check PanX/PanY, and the post-zoom fit checks ZoomX/ZoomY.
  • Fixes FitAllOnZoom to honor the NoFit flag passed to the public Zoom(...) call. It used to read the chart view's ZoomMode for NoFit, which silently ignored the argument when callers had set ZoomMode = None and were driving zoom manually.
  • Updates the axes guide, the lines/zoom sample doc, and llms-full.txt to list the new per-gesture flags and show the "zoom on X, no pan" combination.

Closes #2175 (originally reported in discussion #2167 — "How to disable pan, but keep the zoom on axis?").
Closes #2119 — the "decouple Zoom and Pan" and "manual Zoom must honor NoFit" parts. The third ask in #2119 (custom input mapping for keys/pointer events) is intentionally out of scope; the existing API surface is enough to build that on the consumer side.

Notes

  • The integer bit positions of the flags shifted (NoFit is now 16, etc.). Code that references the enum by name is unaffected; anyone hardcoding integer values for the enum would see a shift.
  • The composite X (now PanX | ZoomX = 3) still preserves the old pan+zoom-on-X behavior.

Test plan

  • dotnet test --project tests/CoreTests/CoreTests.csproj --framework net8.0 — 461/461 passing (453 prior + 8 new).
  • Granular flag tests: ZoomX/PanX isolation, ZoomY/PanY isolation, Pan no-op under ZoomX-only, Zoom no-op under PanX-only, composite X still pans+zooms.
  • NoFit-honoring regression test verified to fail without the engine fix (MaxLimit snapped from 60 → ~9 with the bug, stays ~59 with the fix).

🤖 Generated with Claude Code

beto-rodriguez and others added 2 commits April 26, 2026 11:38
Adds PanX, ZoomX, PanY, ZoomY so users can enable zoom while disabling
pan (or vice versa). The existing X, Y and Both names are preserved as
composites (X = PanX | ZoomX, Y = PanY | ZoomY), so code or XAML that
references them by name keeps the same pan+zoom semantics.

The CartesianChartEngine now checks the granular flag for each gesture:
Zoom and zoom-by-section consult ZoomX/ZoomY, Pan and the pointer-up
bounce-back consult PanX/PanY, and the post-zoom fit consults ZoomX/ZoomY.

Closes #2175

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verifies that ZoomX/PanX (and Y counterparts) only act on their gesture,
that mismatched calls are no-ops (Pan with ZoomX-only does nothing, Zoom
with PanX-only does nothing), and that the composite X flag still pans
and zooms — covering the back-compat guarantee.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 26, 2026 17:38
Updates the axes guide, the lines/zoom sample template and llms-full.txt
to list the new per-gesture flags alongside the existing X/Y/Both
composites, and shows the "zoom on X, no pan" combination from the
discussion that motivated #2175.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR refines the chart interaction API by splitting ZoomAndPanMode into per-gesture, per-axis flags so consumers can enable/disable pan vs. zoom independently (e.g., allow wheel/pinch zoom on X while disallowing panning).

Changes:

  • Extended ZoomAndPanMode with PanX, ZoomX, PanY, ZoomY, while keeping X, Y, and Both as backward-compatible composites.
  • Updated CartesianChartEngine to use the new flags so zoom-related and pan-related gestures consult the appropriate bits.
  • Added unit tests validating zoom/pan isolation per axis and confirming composite X preserves existing behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

File Description
src/LiveChartsCore/Measure/ZoomAndPanMode.cs Introduces new per-gesture flags and redefines X/Y/Both as composites; updates docs.
src/LiveChartsCore/CartesianChartEngine.cs Updates zoom/pan/zoom-by-section and related fit/bounce logic to check ZoomX/ZoomY vs PanX/PanY.
tests/CoreTests/OtherTests/ChartInteractiveApiTests.cs Adds test coverage for the new flags and for backward compatibility of X.

Comment thread src/LiveChartsCore/Measure/ZoomAndPanMode.cs
Comment thread src/LiveChartsCore/CartesianChartEngine.cs Outdated
Comment thread src/LiveChartsCore/Measure/ZoomAndPanMode.cs Outdated
@livecharts

livecharts Bot commented Apr 26, 2026

Copy link
Copy Markdown

Thanks for your contribution!

Benchmark delta — ✅ 1 improvement, no regressions

Show details

Base: master — Head: ec7217a (run)
Thresholds: 🔴 > +10% slower, 🟢 > -10% faster. Numbers on GitHub-hosted runners are noisy — treat double-digit deltas on non-trivial benchmarks as signal, single-digit as noise.

Benchmark Base (ms) Head (ms) Δ Alloc base Alloc head
BoxSeriesBench.Reinvalidate 11.885 12.431 +4.6% 631.4 KB 631.4 KB
BoxSeriesBench.UpdateOnePoint 12.045 12.253 +1.7% 631.5 KB 631.5 KB
CandlesticksSeriesBench.Reinvalidate 10.726 10.717 -0.1% 657.3 KB 657.3 KB
CandlesticksSeriesBench.UpdateOnePoint 11.207 10.739 -4.2% 657.5 KB 657.5 KB
ColumnSeriesBench.Reinvalidate 5.908 6.026 +2.0% 524.5 KB 524.5 KB
ColumnSeriesBench.UpdateOnePoint 5.899 5.428 -8.0% 518.3 KB 518.3 KB
HeatSeriesBench.Reinvalidate 2.929 2.786 -4.9% 502.4 KB 502.4 KB
HeatSeriesBench.UpdateOnePoint 3.050 2.768 -9.2% 502.4 KB 502.4 KB
LineSeriesBench.FirstRender(PointCount: 1000) 19.669 19.781 +0.6% 3.9 MB 3.9 MB
LineSeriesBench.FirstRender(PointCount: 10000) 182.872 188.769 +3.2% 27.5 MB 27.5 MB
LineSeriesBench.Reinvalidate(PointCount: 1000) 16.394 16.525 +0.8% 801.2 KB 801.2 KB
LineSeriesBench.Reinvalidate(PointCount: 10000) 155.672 153.864 -1.2% 4.0 MB 4.0 MB
LineSeriesBench.ToggleNullGap(PointCount: 1000) 16.558 16.655 +0.6% 849.7 KB 849.7 KB
LineSeriesBench.ToggleNullGap(PointCount: 10000) 210.064 189.144 -10.0% 4.4 MB 4.4 MB
LineSeriesBench.UpdateOnePoint(PointCount: 1000) 16.441 17.027 +3.6% 801.2 KB 801.2 KB
LineSeriesBench.UpdateOnePoint(PointCount: 10000) 155.274 152.485 -1.8% 4.0 MB 4.0 MB
PieSeriesBench.Reinvalidate 1.322 1.058 🟢 -20.0% 97.2 KB 97.2 KB
PieSeriesBench.UpdateOnePoint 1.066 1.042 -2.3% 97.3 KB 97.3 KB
PolarLineSeriesBench.Reinvalidate 14.695 15.280 +4.0% 637.6 KB 637.6 KB
PolarLineSeriesBench.UpdateOnePoint 14.602 15.068 +3.2% 637.6 KB 637.7 KB
ScatterSeriesBench.Reinvalidate 5.841 5.849 +0.1% 528.0 KB 528.0 KB
ScatterSeriesBench.UpdateOnePoint 5.806 5.846 +0.7% 521.8 KB 521.8 KB
StackedAreaSeriesBench.Reinvalidate 5.677 5.359 -5.6% 1.9 MB 1.9 MB
StackedAreaSeriesBench.UpdateOnePoint 5.703 5.335 -6.5% 1.9 MB 1.9 MB
StepLineSeriesBench.Reinvalidate 16.513 15.697 -4.9% 722.8 KB 722.8 KB
StepLineSeriesBench.ToggleNullGap 16.140 15.597 -3.4% 771.3 KB 771.2 KB
StepLineSeriesBench.UpdateOnePoint 15.948 16.101 +1.0% 722.8 KB 722.8 KB

Build summary for run #353.

Packing ✅

Download the NuGet packages for this build here (dev-353). Available for 30 days — you can either use them directly or wait for this PR to be merged to have them published to NuGet.org.

Tests ✅

Show details
  • Avalonia
    • desktop (windows) ✅ — trx
    • desktop (linux) ✅ — trx
    • desktop (mac) ✅ — trx
    • android ✅ — trx
    • ios ✅ — trx
    • browser ✅ — trx
  • Uno
    • desktop (windows) ✅ — trx
    • win10.0.19041 (windows) ✅ — trx
    • desktop (linux) ✅ — trx
    • desktop (mac) ✅ — trx
  • MAUI
    • windows ✅ — trx
    • maccatalyst ✅ — trx
    • android ✅ — trx
    • ios ✅ — trx
  • WinUI
    • windows ✅ — trx
  • WinForms
    • net10 (windows) ✅ — trx
    • net10-19041 (windows) ✅ — trx
    • net462 (windows) ✅ — trx
  • WPF
    • net10 (windows) ✅ — trx
    • net10-19041 (windows) ✅ — trx
    • net462 (windows) ✅ — trx
  • Eto
    • windows ✅ — trx
    • mac ✅ — trx
  • Core
    • net8.0 ✅ — trx
    • net462 ✅ — trx
  • Snapshot
    • snapshot ✅ — trx

FitAllOnZoom checked the chart view's ZoomMode for the NoFit flag
instead of the flags passed to the public Zoom(...) call. Manual
callers that built their own input scheme (view ZoomMode = None) had
their NoFit ignored, so the post-zoom debounced fit snapped axis
limits back to the data bounds.

Now the fit consults the flags argument, matching the other NoFit
checks in ZoomAxis / PanAxis / InvokePointerUp.

Closes #2119 (zoom decoupling part is in the prior commit; this
covers the "manual Zoom must honor NoFit" part).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@beto-rodriguez beto-rodriguez changed the title fix(zoom): split ZoomAndPanMode into per-gesture PanX/ZoomX/PanY/ZoomY flags fix(zoom): split ZoomAndPanMode into per-gesture flags + honor NoFit in manual Zoom Apr 26, 2026
Copilot AI review requested due to automatic review settings April 26, 2026 18:50
Avalonia and Eto cartesian views were marking PointerWheel events as
Handled whenever ZoomMode was non-None. With pan-only modes (PanX/PanY)
now possible, that swallowed the wheel even though the underlying
Zoom() call is a no-op — blocking parent ScrollViewers in the same way
issue #1864 originally fixed for ZoomMode = None. The guard now checks
for any zoom flag (ZoomX/ZoomY) before suppressing the event.

Also fixes two pre-existing doc issues touched by this PR:
- StartZoomingSection's `point` param doc said "panning operation"
  instead of describing the zoom-by-section start point.
- InvertPanningPointerTrigger doc said "triggered to the left click"
  (should read "triggered by the left click").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Comment thread docs/samples/lines/zoom/template.md Outdated
Comment thread docs/samples/lines/zoom/template.md Outdated
Comment thread src/LiveChartsCore/Measure/ZoomAndPanMode.cs Outdated
- "forces the the chart" → "forces the chart" (lines/zoom template).
- "Fit top Bounds" → "Fit to Bounds" (lines/zoom template).
- InvertPanningPointerTrigger doc claimed both pan and zoom-by-section
  were triggered by "touch-and-hold" on touch devices, which can't be
  true since they're different actions. Aligned with the secondary-
  action terminology used elsewhere in the codebase: pan via right
  click / double tap, zoom-by-section via left click / single tap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@beto-rodriguez
beto-rodriguez merged commit 757b24b into master Apr 27, 2026
52 checks passed
beto-rodriguez added a commit that referenced this pull request Apr 28, 2026
@beto-rodriguez
beto-rodriguez deleted the fix/issue-2175-granular-zoom-pan-flags branch April 28, 2026 21:00
pm-kuerten pushed a commit to pm-kuerten/LiveCharts2 that referenced this pull request Jul 6, 2026
…ranular-zoom-pan-flags

fix(zoom): split ZoomAndPanMode into per-gesture flags + honor NoFit in manual Zoom
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

How to disable pan, but keep the zoom on axis? Decouple Zoom and Pan triggers and improve manual cartesianChartEngine.Zoom control with NoFit

3 participants