pyplot: keep calendar coordinates for pandas datetime plots (#549)

Merged
FA
2d ago
+289-12

Fixes #549.

Problem

In native xy.pyplot, pandas datetime-indexed Series.plot() / DataFrame.plot() had two problems:

  1. Period numbers instead of dates. pandas 3.1 passes ax.plot() the raw integers behind its PeriodIndex (asi8) where 3.0 passed Period objects. The shim left those integers alone, so ax.get_xlim()[0] came out as 15340.0 instead of 1325376000000.0. Business-day (freq="B") series take a separate pandas path with their own day counts and had the same problem.
  2. PNG export crash. pandas' minor TimeSeries_Date* locator and formatter were stored and then called during export, raising NotImplementedError: Derived must override. Only the major setters were guarded before.

Fix (python/xy/pyplot/_axes.py)

  • _restore_pandas_period_timestamps converts the integers back to timestamps. It only does this when the array shares memory with, and equals, the index pandas just recorded in ax._plot_data. A later numeric overlay keeps its own units, even when its values match period numbers (e.g. [0, 1, 2] after a daily index that starts on 1970-01-01). Business-day numbers are converted with np.busday_offset from the epoch, which is pandas' own inverse. Checks are duck-typed, so the shim never imports pandas.
  • set_minor_locator / set_minor_formatter now ignore pandas' tickers, as the major setters already did.

Tests

  • tests/pyplot/test_pdsh_gap_features.py: daily, monthly, hourly and business-day indexes, each as a Series and a DataFrame. Each checks datetime xdata, millisecond get_xdata(orig=False) and limits, and PNG export. Also new: a numeric-overlay test and a pandas minor-ticker test. All new tests, plus the existing test_pandas_datetime_series_plot_completes, fail before the fix.
  • New corpus case tests/pyplot/corpus/59_pandas_datetime_plot.py reproduces the export failure before the fix. It now passes the native corpus run and the comparison against Matplotlib 3.11.
  • pandas' plotting backend imports Matplotlib itself. A REQUIRES table in test_corpus.py skips this case where pandas or Matplotlib is missing and keeps it out of the Matplotlib-blocked boundary test. xy.pyplot itself is still asserted Matplotlib-free.
  • pandas>=3 added to the dev group, so CI runs this path. pandas is not a runtime dependency. uv.lock and docs/app/uv.lock updated.
  • Focused tests also pass on pandas 3.0.3.

Spec / changelog

  • spec/matplotlib/compat.md: updated the datetime coordinates row and the corpus paragraph.
  • news/549.bugfix.md

Validation

  • uv run --with pre-commit pre-commit run --all-files, ruff check, ruff format --check, ty check: pass
  • uv run pytest tests/pyplot: 1444 passed

Generated by Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed pandas datetime-indexed plots so daily, monthly, hourly, and business-day data use correctly scaled date coordinates and limits.
    • Numeric overlays retain their original coordinate units when plotted alongside pandas date data.
    • Fixed PNG export failures caused by pandas minor date tickers.
  • Compatibility
    • Improved support for pandas 3.0 and 3.1 plotting in native mode, including calendar-based coordinates and limits.