fix(pyplot): close the eight parity gaps from the PR #44 review - #50
Merged
Conversation
Independent adversarial testing against Matplotlib 3.11 found eight reproducible defects in the notebook-parity seams; every reference value below was re-verified against 3.11.0. - MaxNLocator: keep scale/offset math for narrow far-offset ranges instead of bailing to (1, 0), which left the axis tickless. - Absolute panels: axes titles widen panel chrome instead of eating 26-30px of the requested plot rect (matplotlib never moves the axes). - Dataless axes: the (0, 1) empty view applies to the render snapshot only; data plotted after a render autoscales as if never rendered. - GridSpec spans keep their SubplotSpec so subplots_adjust re-resolves their rects; cell_rect falls back per-key to the figure's adjusted SubplotParams (subplots_adjust also overrides set_position, as 3.11 does for subplotspec-backed axes). - Calendar locators: interval counting is phased at matplotlib's view-derived dtstart (vmin - relativedelta(vmax, vmin)) rather than the 1970 epoch; month occurrences stride all months so bymonth acts as an rrule filter. - Third-party locators are never mutated: the _nbins_hint density hint is only written to xy Locator instances. - annotate arrows survive an empty label or empty arrowprops dict, and offset placement is detected from the entry itself, not the defaulted copy. - Figure facecolor specs (RGBA tuples et al.) normalize through resolve_color before any HTML escaping.
Merging this PR will not alter performance
Comparing Footnotes
|
…horing
Two visual parity defects against matplotlib 3.11 (PDSH 04.09 scenes):
- Annotation arrows started exactly at the text anchor, striking through
their own label ("local minimum") and hiding inside bbox patches.
matplotlib clips every annotation arrow at the text patch (patchA +
shrink). The shared arrow geometry (python/xy/_arrowgeom.py +
js/src/51_annotations.js, kept in sync) now accepts a `label_clear`
"left,right,up,down" rectangle — the label's estimated extents around
the start point — and trims the start to where the departure tangent
exits it. annotate() emits it for data-coordinate arrows and offset
callouts, sized from text length, font, ha/va, and bbox padding, with
matplotlib's visible ~0.35em + 2pt gap.
- The DOM client top-anchored annotation labels while the SVG/raster
exporters (and matplotlib) anchor the text BASELINE at its position, so
client labels sat a line below the other renderers ("Halloween" under
its arrow instead of beside it). The client default is now baseline
(translate -100% + 0.35em), with va top/center/bottom unchanged.
- Bbox'd labels (annotate bbox=...) anchored by their box edge, eating
matplotlib's clearance and overlapping ("Thanksgiving"/"Christmas");
the padded box now grows outward around the anchored text edge, as a
matplotlib bbox patch does.
Contributor
Author
|
Added a second commit (187308a) fixing the annotation rendering defects found in the PDSH 04.09 notebook comparison:
Verified pixel-side against matplotlib 3.11 via headless-Chromium renders of the live client plus the SVG and native-PNG exporters. Full suite now 1,836 passing. |
…first
The baseline-anchored arrow start from the previous commit put the shaft
under the label: matplotlib attaches annotation arrows at the text patch
CENTER (relpos default (0.5, 0.5)), clipped at the patch edge — measured
on 3.11.0: for "local minimum" at 13.89px the patch spans
anchor+[0,106]x[-11.0,+3.3] (y-down) and the arrow starts at
(+111.6, -5.5), not at the baseline.
The shared geometry gains `start_offset` ("x,y" px, applied before
control points and tangents); the shim emits anchor->box-center offsets
with box-centered symmetric `label_clear` extents. Rendered pixel
relations are now pinned quantitatively: the tests run the real pipeline
(entries -> spec -> px transform -> shared arrow geometry) and assert
the recorded matplotlib 3.11.0 start positions within a few px — both
the rightward (patch-edge exit) and up-left (patch-TOP exit) cases, so
future attach regressions fail in pytest instead of surfacing in
notebook screenshots.
…fixes # Conflicts: # js/src/51_annotations.js # python/xy/static/index.js # python/xy/static/standalone.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #44, addressing all eight findings from its adversarial review. Every fix was verified against Matplotlib 3.11.0, and
tests/pyplot/test_adversarial_parity_fixes.pypins the review's reproductions (plus a few neighbors: adjust-before-create,bymonth+intervalstriding,set_positionvssubplots_adjustprecedence).Fixes
_ticker.py): removed thedv/maxabsv < 1e-12early return in_scale_range; only a true zero span bails now.MaxNLocator(4).tick_values(1e9+1e-4, 1e9+2e-4)returns matplotlib's exact five ticks instead of two out-of-view ones._mplfig.py):_chartsbuilds titled panels 26/30 px taller and_panel_positionsgrows the placement upward to match, so the title lives in panel chrome and the plot box stays on its requested GridSpec rect._axes.py): the(0, 1)empty view is applied to the render snapshot (x_props/y_props) only, never persisted into axis state; render-then-plot autoscales like matplotlib.subplots_adjust(_mplfig.py): span axes retain their originatingSubplotSpecand are re-resolved bysubplots_adjust;cell_rectfalls back per-key to the figure's adjusted SubplotParams. One deviation from the review's suggested fix: 3.11 overrides a priorset_positionon subplotspec-backed axes duringsubplots_adjust, so the spec is kept rather than cleared (test included).dates.py):intervalfiltering is anchored at matplotlib's view-deriveddtstart(vmin − relativedelta(vmax, vmin), reimplemented without dateutil); month occurrence numbers stride all months sobymonthacts as an rrule filter._axes.py):_nbins_hintis only written to xyLocatorinstances; slotted/frozen third-party locators render._axes.py): callout conversion gates onarrowprops is not Noneand detects offset placement from the entry itself, so empty labels andarrowprops={}keep their arrows (SVG exporter verified)._mplfig.py): figure facecolor normalizes throughresolve_colorat construction and inset_facecolorbefore any HTML escaping.Verification
pre-commit run --all-files,ruff check,ruff format --check,ty check python: all green.