Skip to content

FIX: snap near-integer arc windings to a full circle on polar plots (#20388, #26972)#31844

Open
SharadhNaidu wants to merge 2 commits into
matplotlib:mainfrom
SharadhNaidu:fix-arc-winding-snap
Open

FIX: snap near-integer arc windings to a full circle on polar plots (#20388, #26972)#31844
SharadhNaidu wants to merge 2 commits into
matplotlib:mainfrom
SharadhNaidu:fix-arc-winding-snap

Conversation

@SharadhNaidu
Copy link
Copy Markdown

@SharadhNaidu SharadhNaidu commented Jun 5, 2026

PR summary

On polar plots, radial gridlines (#20388) and the outer spine (#26972) could collapse to a near-empty arc for certain set_theta_zero_location / set_theta_offset / set_theta_direction combinations.

The root cause is in Path.arc's angle-unwrap step. A span of 360*n plus a tiny floating-point rounding error was reduced modulo 360 to a near-zero arc instead of a full circle:

eta2 = theta2 - 360 * np.floor((theta2 - theta1) / 360)   # 360 + 1e-9 -> 1e-9

Fix

Detect spans that are within floating-point tolerance of a whole number of turns and draw a complete circle; otherwise unwrap theta2 to the shortest arc within 360 degrees exactly as before:

n_turns = (theta2 - theta1) / 360
if (theta2 != theta1 and round(n_turns) >= 1
        and abs(n_turns - round(n_turns)) < 1e-9):
    eta2 = theta1 + 360            # complete circle
else:
    eta2 = theta2 - 360 * np.floor(n_turns)   # shortest arc

The change is byte-identical for all non-degenerate inputs (e.g. 90°, 359.99°, 360°, 410°→50°, 540°→180°, 720°→full circle) and only affects the 360*n + ε collapse cases, so no call sites need to change.

Closes #20388
Closes #26972

Tests

  • test_arc_full_circle_snap / test_arc_unwrap_partial_turn in test_path.py cover the snap-vs-unwrap behaviour at the unit level.
  • test_path.py and end-to-end polar gridline / outer-spine regression tests in test_polar.py.

PR checklist

  • Code is PEP8 compliant.
  • New and changed code is tested.
  • Plot-related changes verified in CI (no image baselines change; behaviour is byte-identical outside the bug case).

AI Disclosure

AI was used to help proofread the grammar of this PR description and to understand some parts of the existing codebase during development. The code changes and design decisions were developed independently.

matplotlib#26972)

On polar plots, radial gridlines and the outer spine could collapse to a
near-empty arc. The root cause is Path.arc's angle-unwrap step: a span of
360*n plus a tiny floating-point rounding error was reduced modulo 360 to
a near-zero arc instead of a full circle.

Detect spans that are within floating-point tolerance of a whole number
of turns and draw a complete circle; otherwise unwrap theta2 to the
shortest arc within 360 degrees as before. The change is byte-identical
for all non-degenerate inputs, so no call sites need to change.

Adds unit coverage for the snap/unwrap behaviour and end-to-end polar
gridline and spine regression tests.
- Replace the 1e-9 magic tolerance with 1e-12 turns, justified by the
  observed error from the polar transform pipeline (machine-epsilon level,
  under 1e-15 turns), via np.rint(n_turns).
- Snap on any non-zero whole number of turns (abs winding), restoring the
  legacy full-circle behaviour for exact negative multiples such as
  Path.arc(0, -360); the previous rework collapsed those to an empty arc.
- Add regression tests for negative full circles and a tolerance-tightness
  guard (a span just past a full turn must not snap).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: set_theta_offset removes grid outline Radial grid missing in polar plots with ax.set_theta_direction(-1) and ax.set_theta_zero_location

1 participant