Fix log scale ticks, grids, and nonpositive handling [mpl compatibility] - #336
Conversation
📝 WalkthroughWalkthroughProtocol v9 adds explicit minor tick values/styles and configurable log nonpositive handling. Pyplot gains nonlinear scales, tick expansion, log-aware aspect behavior, and new formatters. Browser, SVG, and raster renderers draw minor gridlines and ticks, while line markers, errorbar caps, layout, and compatibility tests are extended. ChangesAxis scale, ticker, grid, and layout behavior
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant Pyplot
participant Figure
participant AxisSpec
participant Renderer
Pyplot->>Figure: configure scales, ticks, styles, and nonpositive policy
Figure->>AxisSpec: normalize and serialize axis options
AxisSpec->>Renderer: send protocol v9 fields
Renderer->>Renderer: map values and draw major/minor axis elements
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
python/xy/pyplot/_axes.py (1)
5787-5799: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winDocstring is stale for the new
minortier. The body now accepts"minor", but the docstring still sayswhichaccepts"major"/"both".📝 Proposed fix
- ``visible=None`` toggles the current state; ``which`` accepts - ``"major"``/``"both"`` and ``axis`` restricts to ``"x"`` or ``"y"``. + ``visible=None`` toggles the current state; ``which`` accepts + ``"major"``/``"minor"``/``"both"`` and ``axis`` restricts to ``"x"`` + or ``"y"``.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@python/xy/pyplot/_axes.py` around lines 5787 - 5799, Update the grid method docstring to document that the which parameter accepts "major", "minor", or "both", matching the validation in the grid implementation; leave the behavior unchanged.
🧹 Nitpick comments (6)
python/xy/pyplot/_axes.py (2)
653-660: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider mirroring
set_major_locator's validation.set_minor_locatoraccepts any object;_apply_tickerssilently skips it when it lackstick_values(), so a typo'd argument disappears instead of raising.♻️ Optional parity fix
def set_minor_locator(self, locator: Any) -> None: + if not hasattr(locator, "tick_values"): + raise TypeError("set_minor_locator() requires a Locator with tick_values()") host, key = self._ticker_slot()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@python/xy/pyplot/_axes.py` around lines 653 - 660, Update set_minor_locator to validate the supplied locator with the same validation used by set_major_locator before storing it in host._tickers. Reject objects that do not provide the required tick_values() behavior so invalid arguments raise immediately rather than being skipped by _apply_tickers; preserve the existing storage and invalidation flow for valid locators.
1242-1245: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoffPer-
_adddomain recomputation can get quadratic._expand_domain_to_tickscalls_auto_domain, which rescans every entry array; running it on each_addmakes plotting N series O(N·data) once ticks were set before plotting. Deferring the expansion to_build_chart(where the other domain materialization happens) would avoid the repeated scan.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@python/xy/pyplot/_axes.py` around lines 1242 - 1245, Remove the per-series `_expand_domain_to_ticks` invocation from the `_add` path’s axis loop. Defer tick-based domain expansion until `_build_chart`, alongside the existing domain materialization, ensuring each applicable axis is expanded once after all series are added and preserving the `_tick_expanded_domains` checks.tests/pyplot/test_axis_tick_gallery_compat.py (1)
61-63: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider asserting the y minor tier stays untouched. The test name claims dimension isolation, but only the x minor style and the y major style are checked.
💚 Suggested addition
assert ax._axis_props("x")["minor_style"]["grid_color"] == "rgb(230,230,230)" + assert ax._axis_props("y")["minor_style"]["grid_color"] == "transparent" assert ax._axis_props("y")["style"]["grid_color"] == "`#1f77b4`"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pyplot/test_axis_tick_gallery_compat.py` around lines 61 - 63, Extend the dimension-isolation assertions in the test to verify that the y-axis minor tier remains unchanged after configuring the x-axis minor grid. Use the existing ax._axis_props("y") structure and its minor_style grid_color value, while preserving the current x-minor and y-major assertions.python/xy/pyplot/_ticker.py (1)
513-516: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
_locsis stored but never read.set_locsexists so_apply_tickerscan call it, but__call__ignores the locations entirely — either use it (Matplotlib'sLogitFormatterdecides minor labeling from_locs) or drop the field and keepset_locsa no-op.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@python/xy/pyplot/_ticker.py` around lines 513 - 516, Update the formatter implementation around set_locs and __call__ so the stored _locs value affects minor-label decisions consistently with Matplotlib’s LogitFormatter; otherwise remove the unused _locs storage while preserving set_locs as a no-op for _apply_tickers compatibility. Choose one approach and ensure the resulting behavior no longer stores locations that are never read.python/xy/pyplot/_rc.py (1)
181-190: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value
grid.alphaonly gets a non-negative check. Values above 1 flow intogrid_opacityand out to the renderers as an out-of-range opacity. Matplotlib bounds this to[0, 1].🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@python/xy/pyplot/_rc.py` around lines 181 - 190, Update the validation branch in the rc parameter handling around the “grid.alpha” key so alpha values must remain within the inclusive [0, 1] range. Preserve the existing non-negative validation for the other size, width, and grid parameters, and raise the same ValueError style when grid.alpha exceeds 1.python/xy/_figure.py (1)
1316-1320: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
minor_styleskips the same normalization passstylegets.
styleis recompiled viastyles.compile_axis_style(...)right before shipping (Line 1320), butminor_styleis shipped as a rawdict(opts["minor_style"])copy (Lines 1316-1317) without going through the same normalizer. Both fields are populated identically atset_axis()time, so this is an inconsistency in the defensive-revalidation pattern this function already applies tostyle.♻️ Proposed fix
- if opts.get("minor_style"): - spec["minor_style"] = dict(opts["minor_style"]) - if opts.get("format") is not None: + if opts.get("format") is not None: spec["format"] = opts["format"] style = styles.compile_axis_style(opts.get("style"), f"{axis_id} axis style") if style: spec["style"] = style + minor_style = styles.compile_axis_style(opts.get("minor_style"), f"{axis_id} minor axis style") + if minor_style: + spec["minor_style"] = minor_style🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@python/xy/_figure.py` around lines 1316 - 1320, Update the minor_style handling in the axis-specification flow to pass opts["minor_style"] through styles.compile_axis_style(...) using the same normalization pattern and axis context as style, rather than copying it directly; preserve the existing format handling and style compilation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@python/xy/pyplot/_axes.py`:
- Around line 3439-3450: Update the log-limit transformation in the bounds
conversion logic used by _aspect_coordinates so non-positive or non-finite
transformed values do not raise during read-only geometry queries. Clamp invalid
non-positive log limits to the smallest positive representable bound, or use the
unadjusted rectangle as the fallback, while preserving normal transformation for
valid limits and inverse-log behavior.
- Around line 5003-5014: Update the log-scale validation in the scale
configuration block to reject every base value less than or equal to 1, matching
LogLocator’s requirement that the base be greater than 1. Preserve the existing
finite-value validation and raise the current ValueError at set_xscale call
time.
In `@python/xy/pyplot/_plot_types.py`:
- Around line 2540-2559: Update the capsize conversion around resolved_capsize
to handle both yerr and xerr orientations, rather than gating conversion on yerr
alone. Use the active axis transform and current limits to convert the
point-based half-width into data units for each orientation, preserving
half-width semantics and supporting log or explicitly limited axes instead of
deriving scale from x_values and figure width.
In `@python/xy/pyplot/_rc.py`:
- Line 80: Resolve the unused grid.linestyle configuration by either wiring the
_DEFAULTS entry into the grid style builder used by grid rendering, or removing
grid.linestyle from _DEFAULTS; ensure the chosen approach keeps rcParams
behavior consistent with the supported grid(linestyle=...) path.
In `@spec/design/renderer-architecture.md`:
- Line 331: Update the wording in the renderer architecture documentation so the
`tick_values` description uses “labelled,” matching the surrounding section’s
spelling convention and existing “labels” terminology.
---
Outside diff comments:
In `@python/xy/pyplot/_axes.py`:
- Around line 5787-5799: Update the grid method docstring to document that the
which parameter accepts "major", "minor", or "both", matching the validation in
the grid implementation; leave the behavior unchanged.
---
Nitpick comments:
In `@python/xy/_figure.py`:
- Around line 1316-1320: Update the minor_style handling in the
axis-specification flow to pass opts["minor_style"] through
styles.compile_axis_style(...) using the same normalization pattern and axis
context as style, rather than copying it directly; preserve the existing format
handling and style compilation.
In `@python/xy/pyplot/_axes.py`:
- Around line 653-660: Update set_minor_locator to validate the supplied locator
with the same validation used by set_major_locator before storing it in
host._tickers. Reject objects that do not provide the required tick_values()
behavior so invalid arguments raise immediately rather than being skipped by
_apply_tickers; preserve the existing storage and invalidation flow for valid
locators.
- Around line 1242-1245: Remove the per-series `_expand_domain_to_ticks`
invocation from the `_add` path’s axis loop. Defer tick-based domain expansion
until `_build_chart`, alongside the existing domain materialization, ensuring
each applicable axis is expanded once after all series are added and preserving
the `_tick_expanded_domains` checks.
In `@python/xy/pyplot/_rc.py`:
- Around line 181-190: Update the validation branch in the rc parameter handling
around the “grid.alpha” key so alpha values must remain within the inclusive [0,
1] range. Preserve the existing non-negative validation for the other size,
width, and grid parameters, and raise the same ValueError style when grid.alpha
exceeds 1.
In `@python/xy/pyplot/_ticker.py`:
- Around line 513-516: Update the formatter implementation around set_locs and
__call__ so the stored _locs value affects minor-label decisions consistently
with Matplotlib’s LogitFormatter; otherwise remove the unused _locs storage
while preserving set_locs as a no-op for _apply_tickers compatibility. Choose
one approach and ensure the resulting behavior no longer stores locations that
are never read.
In `@tests/pyplot/test_axis_tick_gallery_compat.py`:
- Around line 61-63: Extend the dimension-isolation assertions in the test to
verify that the y-axis minor tier remains unchanged after configuring the x-axis
minor grid. Use the existing ax._axis_props("y") structure and its minor_style
grid_color value, while preserving the current x-minor and y-major assertions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 127e8dc9-9a0c-4906-960a-b4802de73a69
📒 Files selected for processing (22)
js/src/00_header.tsjs/src/40_gl.tsjs/src/50_chartview.tspython/xy/_figure.pypython/xy/_raster.pypython/xy/_svg.pypython/xy/components.pypython/xy/config.pypython/xy/pyplot/_axes.pypython/xy/pyplot/_mplfig.pypython/xy/pyplot/_plot_types.pypython/xy/pyplot/_rc.pypython/xy/pyplot/_ticker.pyspec/api/styling.mdspec/design/renderer-architecture.mdspec/design/wire-protocol.mdtests/pyplot/test_axis_tick_gallery_compat.pytests/pyplot/test_grid_legend_contracts.pytests/pyplot/test_log_scale_gallery_compat.pytests/pyplot/test_nonlinear_scale_gallery_compat.pytests/pyplot/test_p3_option_contracts.pytests/test_ui_issue_regressions.py
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
js/src/40_gl.ts (1)
84-104: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winUse a NaN-safe mask sentinel here
mode == 3now returnsNaNfor nonpositive values, butHEATMAP_FSdiscards with ordered comparisons (uv.x < 0.0 || uv.x > 1.0 || ...), so masked coordinates can slip through and sample with NaN UVs instead of being dropped. The existingmode == 1path already uses a finite sentinel (-1e30); keep the mask path finite too, or switch the fragment discard to a NaN-safe range check.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@js/src/40_gl.ts` around lines 84 - 104, Update the mode == 3 branches in xyAxisCoord and xyViewCoord to return a finite sentinel for nonpositive values, matching the existing mode == 1 behavior so HEATMAP_FS ordered comparisons discard masked coordinates. Keep the positive-value logarithmic conversion and other mode behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@js/src/40_gl.ts`:
- Around line 84-104: Update the mode == 3 branches in xyAxisCoord and
xyViewCoord to return a finite sentinel for nonpositive values, matching the
existing mode == 1 behavior so HEATMAP_FS ordered comparisons discard masked
coordinates. Keep the positive-value logarithmic conversion and other mode
behavior unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 3db1fc2c-6e4e-4a68-b4a4-c9d4d74065b2
📒 Files selected for processing (22)
docs/charts/scatter.mddocs/styling/mark-styles.mdjs/src/40_gl.tsjs/src/50_chartview.tspython/xy/_raster.pypython/xy/_svg.pypython/xy/_validate.pypython/xy/interaction.pypython/xy/marks.pypython/xy/pyplot/_artists.pypython/xy/pyplot/_axes.pypython/xy/pyplot/_plot_types.pypython/xy/pyplot/_rc.pypython/xy/pyplot/_translate.pyspec/api/styling.mdspec/design/renderer-architecture.mdsrc/raster.rstests/pyplot/test_axes_layout.pytests/pyplot/test_gallery_hist_errorbar_compat.pytests/pyplot/test_grid_legend_contracts.pytests/pyplot/test_marker_fidelity.pytests/pyplot/test_nonlinear_scale_gallery_compat.py
🚧 Files skipped from review as they are similar to previous changes (6)
- spec/design/renderer-architecture.md
- spec/api/styling.md
- python/xy/pyplot/_rc.py
- js/src/50_chartview.ts
- python/xy/_svg.py
- python/xy/pyplot/_axes.py
…le-compat # Conflicts: # js/src/00_header.ts # js/src/50_chartview.ts # python/xy/_raster.py # python/xy/_svg.py # python/xy/config.py # python/xy/pyplot/_axes.py # spec/design/wire-protocol.md # tests/pyplot/test_marker_fidelity.py
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
python/xy/pyplot/_plot_types.py (1)
3581-3593: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy liftUse the active display transform for contour-label geometry.
Lines 3581–3593 map raw data with a positive linear scale, skipping log/symlog transforms and screen-Y inversion. Consequently rotation, collision avoidance, inline gaps, and manual snapping at Lines 3645–3648 are incorrect on nonlinear or inverted axes. Transform paths and manual queries through the active axis/display transform first.
Also applies to: 3645-3648
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@python/xy/pyplot/_plot_types.py` around lines 3581 - 3593, Update contour-label geometry in the surrounding plotting method, including the manual query logic near the referenced snapping code, to use the active axis/display transform rather than the raw positive linear scale. Transform connected contour paths and manual label positions through that transform so rotation, collision avoidance, inline gaps, and snapping remain correct for log, symlog, and inverted axes; preserve the existing level selection and path-connection behavior.python/xy/pyplot/_axes.py (1)
3110-3142: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
_set_axes_image_dataleaks axes-level aspect state onset_data().This rebuilds the array via a temporary
self.imshow(...)call and only cleans up the temp entry/artist afterward. Butimshow()also mutates axes-wide state (self._aspect_equal,self._aspect_value, and especiallyself._aspect_bounds), and none of that is restored._aspect_boundsin particular is unioned with the previous value rather than replaced:if self._aspect_bounds is None: self._aspect_bounds = bounds else: old = self._aspect_bounds self._aspect_bounds = (min(old[0], bounds[0]), max(old[1], bounds[1]), ...)Since the persistent entry already contributed its old extent to
_aspect_boundswhen it was first created, callingimage.set_data(new_z)with a differently shaped/extent array will accumulate the union of old and new extents into_aspect_boundsinstead of replacing it — corrupting equal-aspect layout (get_position()/_build_chart()both consume_aspect_bounds) after a data update.Save and restore
_aspect_equal/_aspect_value/_aspect_bounds(and_aspect_adjustable) around the temporaryimshow()call, then apply only the freshly computed bounds for this image.🐛 Proposed fix
replacement = self.imshow(z, state["cmap"], **state["kwargs"]) prepared = replacement._entry self._remove_entry(prepared) self._unregister_artist(replacement)+ replacement = self.imshow(z, state["cmap"], **state["kwargs"]) + prepared = replacement._entry + self._remove_entry(prepared) + self._unregister_artist(replacement) + # imshow() above mutated axes-wide aspect tracking as a side effect; + # restore it and apply only this image's fresh extent. + self._aspect_equal = prior_aspect_equal + self._aspect_value = prior_aspect_value + self._aspect_bounds = prepared["extent"](with
prior_aspect_equal, prior_aspect_value, prior_aspect_bounds = self._aspect_equal, self._aspect_value, self._aspect_boundscaptured before the call)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@python/xy/pyplot/_axes.py` around lines 3110 - 3142, Update _set_axes_image_data to capture _aspect_equal, _aspect_value, _aspect_bounds, and _aspect_adjustable before the temporary imshow call, then restore the aspect flags and replace _aspect_bounds with the bounds freshly computed for the updated image after cleanup. Ensure the prior axes-wide aspect state is not accumulated or leaked while retaining the new image’s bounds.
🧹 Nitpick comments (1)
python/xy/pyplot/_axes.py (1)
5601-5682: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
minor=Trueinset_xticks/set_yticksstays a silent no-op even though this PR adds native minor-tick support.The docstring still says "minor ticks are outside the native axis contract," but this PR introduces
minor_tick_values,minor_locator/minor_formatter, andminor_styleas first-class axis fields. A matplotlib script callingax.set_xticks(positions, minor=True)to pin custom minor tick locations will now silently do nothing, even though the wire protocol can represent exactly that. Consider wiringminor=Truethrough tominor_tick_values/aFixedLocatoron the minor slot (or updating the docstring/no-op comment to make the limitation explicit and intentional).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@python/xy/pyplot/_axes.py` around lines 5601 - 5682, Update set_xticks and set_yticks so minor=True applies the supplied ticks to the axis’s native minor-tick configuration, including minor_tick_values and the minor locator slot, while preserving label and rotation handling as appropriate; remove the obsolete silent no-op behavior and update both docstrings to describe the supported minor-tick behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@python/xy/pyplot/_axes.py`:
- Around line 3110-3142: Update _set_axes_image_data to capture _aspect_equal,
_aspect_value, _aspect_bounds, and _aspect_adjustable before the temporary
imshow call, then restore the aspect flags and replace _aspect_bounds with the
bounds freshly computed for the updated image after cleanup. Ensure the prior
axes-wide aspect state is not accumulated or leaked while retaining the new
image’s bounds.
In `@python/xy/pyplot/_plot_types.py`:
- Around line 3581-3593: Update contour-label geometry in the surrounding
plotting method, including the manual query logic near the referenced snapping
code, to use the active axis/display transform rather than the raw positive
linear scale. Transform connected contour paths and manual label positions
through that transform so rotation, collision avoidance, inline gaps, and
snapping remain correct for log, symlog, and inverted axes; preserve the
existing level selection and path-connection behavior.
---
Nitpick comments:
In `@python/xy/pyplot/_axes.py`:
- Around line 5601-5682: Update set_xticks and set_yticks so minor=True applies
the supplied ticks to the axis’s native minor-tick configuration, including
minor_tick_values and the minor locator slot, while preserving label and
rotation handling as appropriate; remove the obsolete silent no-op behavior and
update both docstrings to describe the supported minor-tick behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: cd931525-7d7f-49fa-8ed6-78adedce4ab4
📒 Files selected for processing (17)
js/src/00_header.tsjs/src/50_chartview.tspython/xy/_raster.pypython/xy/_svg.pypython/xy/components.pypython/xy/config.pypython/xy/marks.pypython/xy/pyplot/_artists.pypython/xy/pyplot/_axes.pypython/xy/pyplot/_mplfig.pypython/xy/pyplot/_plot_types.pypython/xy/pyplot/_rc.pyspec/api/styling.mdspec/design/wire-protocol.mdtests/pyplot/test_axes_layout.pytests/pyplot/test_marker_fidelity.pytests/pyplot/test_p3_option_contracts.py
🚧 Files skipped from review as they are similar to previous changes (12)
- python/xy/marks.py
- tests/pyplot/test_axes_layout.py
- tests/pyplot/test_p3_option_contracts.py
- js/src/00_header.ts
- python/xy/pyplot/_rc.py
- python/xy/pyplot/_artists.py
- tests/pyplot/test_marker_fidelity.py
- spec/api/styling.md
- python/xy/_raster.py
- js/src/50_chartview.ts
- python/xy/components.py
- python/xy/_svg.py
Summary
This makes the selected Matplotlib log-scale gallery path technically correct across browser, SVG, and raster output:
nonpositive="mask"fromnonpositive="clip", including WebGL;The renderer protocol advances to v9 because minor tick values/styles are now explicit wire data.
Source behavior
The shim now follows Matplotlib's
LogLocator/formatter separation: major ticks own labels, minor ticks own subdivisions and optional grid lines, and nonpositive masking is not silently converted into clipping.Visual comparison
The image is stored on immutable review-only evidence commit
3677799, not in this PR's diff orpr-assets.Before, XY emitted only major grids for the semilogy/loglog panels. After, the exact gallery output carries the same minor subdivisions and hierarchy as Matplotlib.
Exact gallery verification
The rebased branch reran the exact adapted
scales/log_demo.pyand produced all three figures:maskversusclipnonpositive behavior.The rebased outputs are byte-identical to the reviewed post-fix artifacts.
Verification
Current integration state:
9fdc236; headd4aa8db;The exact
scales/log_demo.pyevidence above and the earlier 799-test pyplot run predate the final current-main merge; they are retained as review evidence rather than presented as fresh post-merge runs. No local Chrome/Playwright capture, full suite, or native build was rerun under the low-resource policy. Fresh GitHub Actions are the final integrated gate.Summary by CodeRabbit