Skip to content

fix(imshow): make faceted slot assignments fail loudly and legibly - #95

Open
FBumann wants to merge 2 commits into
mainfrom
claude/imshow-facet-col-row-cgi703
Open

fix(imshow): make faceted slot assignments fail loudly and legibly#95
FBumann wants to merge 2 commits into
mainfrom
claude/imshow-facet-col-row-cgi703

Conversation

@FBumann

@FBumann FBumann commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Context

Started from "imshow with facet_col and facet_row doesn't work", then grew to cover a second report of IndexError: pop index out of range.

The feature itself is fine on plotly >= 6.7.0 — a 4D array rendered in a headless browser gives a correct facet grid, with both column and row titles, the right data in each panel, and shared color bounds. What was broken is everything around it: four separate ways to get a wrong figure or an error that named nothing the caller had written.

Silent wrong figures

1. plotly < 6.7.0 (px.imshow has no facet_row; the package floor is plotly>=5.0.0, so this is a valid install). Reproduced against 6.3.1:

  • A 4D array quietly dropped the facet_row slot and animated the fourth dimension instead — 2 traces + 3 frames rather than 6 subplots, with no warning at all.
  • A 5D array died with ValueError: Unassigned dimension(s): ['t'], which says nothing about plotly.
  • Only an explicitly named facet_row raised the informative error.

2. facet_col_wrap together with facet_row (every plotly version). px.imshow honours the wrap even when facet_row is set: it builds the right grid but silently drops the row titles. Straight from px.imshow, no wrapper involved:

no wrap: ['scen=a', 'scen=b', 'year=2022', 'year=2021', 'year=2020']
wrap=2 : ['scen=a', 'scen=b']

px.line and the other px functions correctly ignore the wrap here, so imshow was the odd one out — and the docstring claimed plotly ignored it, which it doesn't.

Crashes inside px

3. The same dimension in two slots popped the same index twice inside px.imshow, giving IndexError: pop index out of range. Duplicates that hit our transpose first surfaced as ValueError: axes don't match array.

4. Facet and animation slots consuming all but one dimension left nothing for the second heatmap axis, giving IndexError: list index out of range.

Changes

xarray_plotly/plotting.py:

  • _validate_imshow_slots runs before the transpose: every slot needs its own dimension, and both y and x must be filled. The error names the dimension and the two slots that want it, or the slots that consumed the axes.
  • _handle_unsupported_facet_row replaces the pre-assign_slots hack that blanked facet_row, so it runs after assignment and knows which dimension was intended. An auto-assigned facet_row on old plotly still falls back to animating, but warns: facet_row for imshow requires plotly>=6.7.0 (installed: 6.3.1). Dimension 'year' is animated instead of faceted across subplot rows; upgrade plotly to facet it. The 5D case raises an error naming the real cause. An explicitly named facet_row still raises, unchanged.
  • facet_col_wrap is dropped (with a warning) when facet_row is active, so the row titles survive.

Docstrings in plotting.py and accessor.py updated to describe the actual behaviour.

Verification

Swept all 3125 combinations of auto / None / each dimension across the five slots of a 4D array. Every one now either builds a figure or raises a ValueError that says what to change — no IndexError, no axes don't match array — with no change to the 1056 combinations that already worked.

Eleven tests added in tests/test_accessor.py, covering the warning on fallback, the 5D error, facet_col_wrap with and without facet_row, both duplicate-slot cases, both empty-axis cases, and one asserting that each (facet_col, facet_row) pair lands in its own subplot with the right data.

183 tests pass; ruff check, ruff format, and mypy are clean on Python 3.10, 3.12, and 3.13. Behaviour verified against both plotly 7.0.0 and plotly 6.3.1.

The CI failure, which was not from this branch

.venv/lib/python3.13/site-packages/numpy/__init__.pyi:737: error:
Type statement is only supported in Python 3.12 and greater  [syntax]

Reproduced on origin/main with nothing else changed, on the 3.12 and 3.13 matrix entries. numpy's stubs now use type statements, which mypy rejects while targeting python_version = "3.10". The pin is dropped here so the branch can go green: mypy then targets the interpreter it runs under, and each matrix entry checks its own version against the numpy stubs resolved for it — so the 3.10 job still type-checks 3.10 semantics. Say the word if you would rather have this as its own PR.

Not changed — your call

pyproject.toml still declares plotly>=5.0.0, and CI never tests against an old plotly (the existing tests monkeypatch the version check). That is why the plotly-version gap went unnoticed. If faceted imshow is meant to just work, the floor probably belongs at >=6.7.0; otherwise a min-deps CI job would catch this class of bug.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TKhPQzJgQr7khmzDPYAXA9

imshow with both facet_col and facet_row silently produced the wrong
figure in two cases.

On plotly < 6.7.0 (px.imshow has no facet_row), auto-assignment quietly
dropped the slot, so a 4D array animated its fourth dimension instead of
faceting subplot rows -- with no warning -- and a 5D array failed with an
unrelated "Unassigned dimension(s)" error. The fallback now warns, naming
the dimension and the plotly requirement, and the 5D case raises an error
that explains the real cause.

On any plotly version, px.imshow honours facet_col_wrap even when
facet_row is set: the grid is built but the facet_row subplot titles are
dropped. Every other px function ignores the wrap in that case, so drop
it here too and warn.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TKhPQzJgQr7khmzDPYAXA9
@coderabbitai

coderabbitai Bot commented Sep 10, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 6d841251-bdd6-496a-baa7-7215b1f8be64


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Two slot assignments reached px.imshow and crashed inside its own
slicing with an error that named nothing the caller had written:

- The same dimension in two slots popped the same index twice, giving
  "IndexError: pop index out of range".
- Facet and animation slots consuming all but one dimension left nothing
  for the second heatmap axis, giving "IndexError: list index out of
  range". Duplicates that reached the transpose first surfaced as
  "ValueError: axes don't match array" instead.

Validate the assignment before transposing: every slot needs its own
dimension, and both y and x must be filled. The error now names the
dimension and the two slots that want it, or the slots that consumed
the axes.

Sweeping all 3125 combinations of auto/None/each dimension across the
five slots of a 4D array: every one now either builds a figure or
raises a ValueError that says what to change, with no change to the
1056 combinations that already worked.

Also drop mypy's python_version = "3.10" pin, which failed CI on the
3.12 and 3.13 matrix entries -- numpy's stubs now use `type` statements
that mypy rejects when targeting 3.10. Reproduced on origin/main with
no other changes. Unpinned, mypy targets the interpreter it runs under,
so each matrix entry checks its own version against the numpy stubs
resolved for it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TKhPQzJgQr7khmzDPYAXA9
@FBumann FBumann changed the title fix(imshow): surface facet_row limitations instead of failing silently fix(imshow): make faceted slot assignments fail loudly and legibly Sep 10, 2026
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.

2 participants