refactor: decompose show() into named stages - #714

Merged
timtreis merged 7 commits into
mainfrom
claude/open-issues-review-j70n0h
Jun 14, 2026
Merged

refactor: decompose show() into named stages#714
timtreis merged 7 commits into
mainfrom
claude/open-issues-review-j70n0h

Conversation

@timtreis

@timtreistimtreis commented Jun 13, 2026

Copy link
Copy Markdown
Member

Closes#697.

Decomposes the 644-line show() god-function into named, single-purpose helpers. Behavior-preserving — no public API change.

Extracted helpers

_collect_render_commands · _normalize_title · _resolve_coordinate_systems · _plan_panels · _build_legend_params · _draw_colorbar (promoted from closure) · _layout_pending_colorbars · _render_panel · _finalize_panel · _should_rasterize · _maybe_set_label_colors

Also: _validate_show_parameters now called by keyword; per-render dispatch is a table; has_*/wants_* boolean sprawl replaced by cs_row + a wants dict.

Metrics (lizard, basic.py)

mainthis PR
show() cyclomatic complexity10930
show() NLOC390195
avg complexity / function15.88.2

claude added 4 commits June 13, 2026 00:07
…tion
Decompose the show() god-function (#697). First, behavior-preserving steps:
- pass _validate_show_parameters args by keyword so a signature reorder
can no longer silently misvalidate one parameter as another
- extract _collect_render_commands() and _normalize_title() module helpers
No behavior change.
…bar stages
Continue decomposing show() (#697), behavior-preserving:
- _resolve_coordinate_systems(): CS auto-detection, validation and filtering
- _plan_panels(): panel layout (one-per-CS vs one-per-color-key) + ax-count check
- _build_legend_params(): LegendParams construction with legend_params overrides
- promote the _draw_colorbar closure to a module function taking colorbar_params
explicitly (was a ~90-line closure capturing it implicitly)
- _layout_pending_colorbars(): the deferred second-pass colorbar layout
No behavior change.
…_finalize_panel
Extract the inner render-command dispatch loop (#697), behavior-preserving:
- _render_panel(): dispatches each queued render command into one panel's axes,
returning the wanted elements and per-type wants_* flags
- _finalize_panel(): per-panel title / equal-aspect / frame visibility
show() is now a compact orchestrator calling named stages. Full test suite
passes unchanged (719 passed, 1 skipped), image baselines included.
No behavior change.
Post-review cleanups (all behavior-preserving, full suite green 719 passed):
- drop dead _draw_colorbar param base_offsets_axes (never read)
- collapse the two parallel 4-branch location chains in _draw_colorbar into
data-driven lookups (vertical flag + opposite map + getattr on the axis)
- extract _should_rasterize() to dedup the images/labels rasterize heuristic
- extract _maybe_set_label_colors() for the categorical-color prestep
- replace the 5-branch render dispatch in _render_panel with a renderer table
keyed by command; graph stays a small special case
- pass cs_row instead of four has_* booleans; return a wants dict instead of a
four-boolean tuple (removes the parallel-variable sprawl)
@codecov-commenter

codecov-commenter commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.23529% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.13%. Comparing base (8730ff4) to head (3f26158).

Files with missing linesPatch %Lines
src/spatialdata_plot/pl/basic.py89.07%8 Missing and 12 partials ⚠️
src/spatialdata_plot/pl/utils.py50.00%2 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #714 +/- ##
==========================================
+ Coverage 77.00% 77.13% +0.13% 
==========================================
Files 14 14 Lines 4457 4457 Branches 1036 1023 -13 ==========================================
+ Hits 3432 3438 +6 - Misses 660 661 +1 + Partials 365 358 -7 
Files with missing linesCoverage Δ
src/spatialdata_plot/pl/utils.py69.38% <50.00%> (-0.02%)⬇️
src/spatialdata_plot/pl/basic.py82.36% <89.07%> (+1.35%)⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@timtreistimtreis changed the title refactor: decompose show() into named stages (#697)refactor: decompose show() into named stagesJun 14, 2026
Follow-up cleanup on the show() decomposition:
- Hoist the render-command dispatch dict to a module-level _RENDERERS
constant instead of rebuilding it on every _render_panel call.
- Derive _VALID_RENDER_CMDS from _RENDER_CMD_TO_CS_FLAG (+ render_graph)
so the valid-command set isn't a second hand-maintained list.
- Drop the always-true `if "render" in cmd` branch in
_collect_render_commands (every valid command starts with "render",
and unknown commands already raise above it).
- Hoist _finalize_panel out of the per-command loop in _render_panel; it
depends only on per-panel values, so it now runs once per panel.
Behavior-preserving; no public API change. (Pre-existing mypy errors on
this branch are unrelated and unchanged by this commit.)
`_render_panel` iterates render commands whose params are a 5-way
RenderParams union. The command string discriminates the concrete type
but mypy can't infer that, so narrow with explicit casts at each typed
call (_render_graph, _get_wanted_render_elements, _should_rasterize,
_maybe_set_label_colors) and type _RENDERERS as
`dict[str, Callable[..., None]]` so the dynamic dispatch type-checks.
Also fixes an unrelated pre-existing mypy error in utils.py
(`_resolve_measure_table` returning Any) by coercing the table name to
str; ruff additionally normalized the adjacent spatialdata import block.
Both are required for this branch to pass the mypy/ruff pre-commit hooks
(mypy follows imports, so the utils.py error blocked any basic.py commit).
No runtime behavior change: casts are no-ops and table names are strings.
@timtreis
timtreis marked this pull request as ready for review June 14, 2026 16:23
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
Integrate #702 (per-panel title fix) and #703 (as_points + fast extent)
into the show() decomposition.
Conflict resolution in src/spatialdata_plot/pl/basic.py:
- #702 up-front title-count validation: kept (after num_panels). The
adjacent axes/panel-count check was dropped here because the
decomposition already relocated it into _plan_panels.
- #702 simplified title selection (dropped the per-panel try/except):
applied to the extracted _finalize_panel helper.
- #703 had no show()-level render-dispatch changes (as_points is
param-driven in render.py, handled inside _render_panel already); its
only show()-level change, get_extent -> _get_extent_fast, auto-merged
into the extent block and consumes _render_panel's `wants` dict.
Also sweeps up two pre-existing #703 lint/type nits in utils.py surfaced
by the merge: _fast_extent docstring (ruff D205) and _get_extent_fast
Any-return (mypy).
Verified: ruff + mypy clean; 109 non-visual show/shapes/labels tests pass.
@timtreis
timtreis merged commit 4ba13a3 into mainJun 14, 2026
7 of 8 checks passed
timtreis added a commit that referenced this pull request Jun 14, 2026
Integrate #714 (show() decomposition) into the utils.py split. Only utils.py
conflicted:
- import block: dropped the now-unused `_locate_value` import (it moved to
_color.py with the color code that uses it); kept main's `_locate_value`
out of utils.
- `_fast_extent` docstring: took main's #714 version (D205 fix).
basic.py auto-merged: #714's decomposed show()/helpers now import color and
validation symbols from _color/_validate (the split's repoints), not utils.
Bonus: merging #714 brings its fixes for the pre-existing #703/#705 debt
(_resolve_measure_table str-return, _get_extent_fast Any-return, _fast_extent
D205), so the branch is now fully ruff + mypy clean (no --no-verify).
Verified: no import cycle; ruff + ruff-format + mypy all pass; 410 non-visual
tests pass.
@timtreis
timtreis deleted the claude/open-issues-review-j70n0h branch July 10, 2026 11:11
Sign up for freeto 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.

Refactor: decompose the 644-line show() god-function into named stages

3 participants

@timtreis@codecov-commenter@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

refactor: decompose show() into named stages - #714

Merged
timtreis merged 7 commits into
mainfrom
claude/open-issues-review-j70n0h
Jun 14, 2026
Merged

refactor: decompose show() into named stages#714
timtreis merged 7 commits into
mainfrom
claude/open-issues-review-j70n0h

Conversation

@timtreis

@timtreistimtreis commented Jun 13, 2026

Copy link
Copy Markdown
Member

Closes#697.

Decomposes the 644-line show() god-function into named, single-purpose helpers. Behavior-preserving — no public API change.

Extracted helpers

_collect_render_commands · _normalize_title · _resolve_coordinate_systems · _plan_panels · _build_legend_params · _draw_colorbar (promoted from closure) · _layout_pending_colorbars · _render_panel · _finalize_panel · _should_rasterize · _maybe_set_label_colors

Also: _validate_show_parameters now called by keyword; per-render dispatch is a table; has_*/wants_* boolean sprawl replaced by cs_row + a wants dict.

Metrics (lizard, basic.py)

mainthis PR
show() cyclomatic complexity10930
show() NLOC390195
avg complexity / function15.88.2

claude added 4 commits June 13, 2026 00:07
…tion
Decompose the show() god-function (#697). First, behavior-preserving steps:
- pass _validate_show_parameters args by keyword so a signature reorder
can no longer silently misvalidate one parameter as another
- extract _collect_render_commands() and _normalize_title() module helpers
No behavior change.
…bar stages
Continue decomposing show() (#697), behavior-preserving:
- _resolve_coordinate_systems(): CS auto-detection, validation and filtering
- _plan_panels(): panel layout (one-per-CS vs one-per-color-key) + ax-count check
- _build_legend_params(): LegendParams construction with legend_params overrides
- promote the _draw_colorbar closure to a module function taking colorbar_params
explicitly (was a ~90-line closure capturing it implicitly)
- _layout_pending_colorbars(): the deferred second-pass colorbar layout
No behavior change.
…_finalize_panel
Extract the inner render-command dispatch loop (#697), behavior-preserving:
- _render_panel(): dispatches each queued render command into one panel's axes,
returning the wanted elements and per-type wants_* flags
- _finalize_panel(): per-panel title / equal-aspect / frame visibility
show() is now a compact orchestrator calling named stages. Full test suite
passes unchanged (719 passed, 1 skipped), image baselines included.
No behavior change.
Post-review cleanups (all behavior-preserving, full suite green 719 passed):
- drop dead _draw_colorbar param base_offsets_axes (never read)
- collapse the two parallel 4-branch location chains in _draw_colorbar into
data-driven lookups (vertical flag + opposite map + getattr on the axis)
- extract _should_rasterize() to dedup the images/labels rasterize heuristic
- extract _maybe_set_label_colors() for the categorical-color prestep
- replace the 5-branch render dispatch in _render_panel with a renderer table
keyed by command; graph stays a small special case
- pass cs_row instead of four has_* booleans; return a wants dict instead of a
four-boolean tuple (removes the parallel-variable sprawl)
@codecov-commenter

codecov-commenter commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.23529% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.13%. Comparing base (8730ff4) to head (3f26158).

Files with missing linesPatch %Lines
src/spatialdata_plot/pl/basic.py89.07%8 Missing and 12 partials ⚠️
src/spatialdata_plot/pl/utils.py50.00%2 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #714 +/- ##
==========================================
+ Coverage 77.00% 77.13% +0.13% 
==========================================
Files 14 14 Lines 4457 4457 Branches 1036 1023 -13 ==========================================
+ Hits 3432 3438 +6 - Misses 660 661 +1 + Partials 365 358 -7 
Files with missing linesCoverage Δ
src/spatialdata_plot/pl/utils.py69.38% <50.00%> (-0.02%)⬇️
src/spatialdata_plot/pl/basic.py82.36% <89.07%> (+1.35%)⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@timtreistimtreis changed the title refactor: decompose show() into named stages (#697)refactor: decompose show() into named stagesJun 14, 2026
Follow-up cleanup on the show() decomposition:
- Hoist the render-command dispatch dict to a module-level _RENDERERS
constant instead of rebuilding it on every _render_panel call.
- Derive _VALID_RENDER_CMDS from _RENDER_CMD_TO_CS_FLAG (+ render_graph)
so the valid-command set isn't a second hand-maintained list.
- Drop the always-true `if "render" in cmd` branch in
_collect_render_commands (every valid command starts with "render",
and unknown commands already raise above it).
- Hoist _finalize_panel out of the per-command loop in _render_panel; it
depends only on per-panel values, so it now runs once per panel.
Behavior-preserving; no public API change. (Pre-existing mypy errors on
this branch are unrelated and unchanged by this commit.)
`_render_panel` iterates render commands whose params are a 5-way
RenderParams union. The command string discriminates the concrete type
but mypy can't infer that, so narrow with explicit casts at each typed
call (_render_graph, _get_wanted_render_elements, _should_rasterize,
_maybe_set_label_colors) and type _RENDERERS as
`dict[str, Callable[..., None]]` so the dynamic dispatch type-checks.
Also fixes an unrelated pre-existing mypy error in utils.py
(`_resolve_measure_table` returning Any) by coercing the table name to
str; ruff additionally normalized the adjacent spatialdata import block.
Both are required for this branch to pass the mypy/ruff pre-commit hooks
(mypy follows imports, so the utils.py error blocked any basic.py commit).
No runtime behavior change: casts are no-ops and table names are strings.
@timtreis
timtreis marked this pull request as ready for review June 14, 2026 16:23
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
Integrate #702 (per-panel title fix) and #703 (as_points + fast extent)
into the show() decomposition.
Conflict resolution in src/spatialdata_plot/pl/basic.py:
- #702 up-front title-count validation: kept (after num_panels). The
adjacent axes/panel-count check was dropped here because the
decomposition already relocated it into _plan_panels.
- #702 simplified title selection (dropped the per-panel try/except):
applied to the extracted _finalize_panel helper.
- #703 had no show()-level render-dispatch changes (as_points is
param-driven in render.py, handled inside _render_panel already); its
only show()-level change, get_extent -> _get_extent_fast, auto-merged
into the extent block and consumes _render_panel's `wants` dict.
Also sweeps up two pre-existing #703 lint/type nits in utils.py surfaced
by the merge: _fast_extent docstring (ruff D205) and _get_extent_fast
Any-return (mypy).
Verified: ruff + mypy clean; 109 non-visual show/shapes/labels tests pass.
@timtreis
timtreis merged commit 4ba13a3 into mainJun 14, 2026
7 of 8 checks passed
timtreis added a commit that referenced this pull request Jun 14, 2026
Integrate #714 (show() decomposition) into the utils.py split. Only utils.py
conflicted:
- import block: dropped the now-unused `_locate_value` import (it moved to
_color.py with the color code that uses it); kept main's `_locate_value`
out of utils.
- `_fast_extent` docstring: took main's #714 version (D205 fix).
basic.py auto-merged: #714's decomposed show()/helpers now import color and
validation symbols from _color/_validate (the split's repoints), not utils.
Bonus: merging #714 brings its fixes for the pre-existing #703/#705 debt
(_resolve_measure_table str-return, _get_extent_fast Any-return, _fast_extent
D205), so the branch is now fully ruff + mypy clean (no --no-verify).
Verified: no import cycle; ruff + ruff-format + mypy all pass; 410 non-visual
tests pass.
@timtreis
timtreis deleted the claude/open-issues-review-j70n0h branch July 10, 2026 11:11
Sign up for freeto 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.

Refactor: decompose the 644-line show() god-function into named stages

3 participants

@timtreis@codecov-commenter@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor: decompose show() into named stages - #714

Merged
timtreis merged 7 commits into
mainfrom
claude/open-issues-review-j70n0h
Jun 14, 2026
Merged

refactor: decompose show() into named stages#714
timtreis merged 7 commits into
mainfrom
claude/open-issues-review-j70n0h

Conversation

@timtreis

@timtreistimtreis commented Jun 13, 2026

Copy link
Copy Markdown
Member

Closes#697.

Decomposes the 644-line show() god-function into named, single-purpose helpers. Behavior-preserving — no public API change.

Extracted helpers

_collect_render_commands · _normalize_title · _resolve_coordinate_systems · _plan_panels · _build_legend_params · _draw_colorbar (promoted from closure) · _layout_pending_colorbars · _render_panel · _finalize_panel · _should_rasterize · _maybe_set_label_colors

Also: _validate_show_parameters now called by keyword; per-render dispatch is a table; has_*/wants_* boolean sprawl replaced by cs_row + a wants dict.

Metrics (lizard, basic.py)

mainthis PR
show() cyclomatic complexity10930
show() NLOC390195
avg complexity / function15.88.2

claude added 4 commits June 13, 2026 00:07
…tion
Decompose the show() god-function (#697). First, behavior-preserving steps:
- pass _validate_show_parameters args by keyword so a signature reorder
can no longer silently misvalidate one parameter as another
- extract _collect_render_commands() and _normalize_title() module helpers
No behavior change.
…bar stages
Continue decomposing show() (#697), behavior-preserving:
- _resolve_coordinate_systems(): CS auto-detection, validation and filtering
- _plan_panels(): panel layout (one-per-CS vs one-per-color-key) + ax-count check
- _build_legend_params(): LegendParams construction with legend_params overrides
- promote the _draw_colorbar closure to a module function taking colorbar_params
explicitly (was a ~90-line closure capturing it implicitly)
- _layout_pending_colorbars(): the deferred second-pass colorbar layout
No behavior change.
…_finalize_panel
Extract the inner render-command dispatch loop (#697), behavior-preserving:
- _render_panel(): dispatches each queued render command into one panel's axes,
returning the wanted elements and per-type wants_* flags
- _finalize_panel(): per-panel title / equal-aspect / frame visibility
show() is now a compact orchestrator calling named stages. Full test suite
passes unchanged (719 passed, 1 skipped), image baselines included.
No behavior change.
Post-review cleanups (all behavior-preserving, full suite green 719 passed):
- drop dead _draw_colorbar param base_offsets_axes (never read)
- collapse the two parallel 4-branch location chains in _draw_colorbar into
data-driven lookups (vertical flag + opposite map + getattr on the axis)
- extract _should_rasterize() to dedup the images/labels rasterize heuristic
- extract _maybe_set_label_colors() for the categorical-color prestep
- replace the 5-branch render dispatch in _render_panel with a renderer table
keyed by command; graph stays a small special case
- pass cs_row instead of four has_* booleans; return a wants dict instead of a
four-boolean tuple (removes the parallel-variable sprawl)
@codecov-commenter

codecov-commenter commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.23529% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.13%. Comparing base (8730ff4) to head (3f26158).

Files with missing linesPatch %Lines
src/spatialdata_plot/pl/basic.py89.07%8 Missing and 12 partials ⚠️
src/spatialdata_plot/pl/utils.py50.00%2 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #714 +/- ##
==========================================
+ Coverage 77.00% 77.13% +0.13% 
==========================================
Files 14 14 Lines 4457 4457 Branches 1036 1023 -13 ==========================================
+ Hits 3432 3438 +6 - Misses 660 661 +1 + Partials 365 358 -7 
Files with missing linesCoverage Δ
src/spatialdata_plot/pl/utils.py69.38% <50.00%> (-0.02%)⬇️
src/spatialdata_plot/pl/basic.py82.36% <89.07%> (+1.35%)⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@timtreistimtreis changed the title refactor: decompose show() into named stages (#697)refactor: decompose show() into named stagesJun 14, 2026
Follow-up cleanup on the show() decomposition:
- Hoist the render-command dispatch dict to a module-level _RENDERERS
constant instead of rebuilding it on every _render_panel call.
- Derive _VALID_RENDER_CMDS from _RENDER_CMD_TO_CS_FLAG (+ render_graph)
so the valid-command set isn't a second hand-maintained list.
- Drop the always-true `if "render" in cmd` branch in
_collect_render_commands (every valid command starts with "render",
and unknown commands already raise above it).
- Hoist _finalize_panel out of the per-command loop in _render_panel; it
depends only on per-panel values, so it now runs once per panel.
Behavior-preserving; no public API change. (Pre-existing mypy errors on
this branch are unrelated and unchanged by this commit.)
`_render_panel` iterates render commands whose params are a 5-way
RenderParams union. The command string discriminates the concrete type
but mypy can't infer that, so narrow with explicit casts at each typed
call (_render_graph, _get_wanted_render_elements, _should_rasterize,
_maybe_set_label_colors) and type _RENDERERS as
`dict[str, Callable[..., None]]` so the dynamic dispatch type-checks.
Also fixes an unrelated pre-existing mypy error in utils.py
(`_resolve_measure_table` returning Any) by coercing the table name to
str; ruff additionally normalized the adjacent spatialdata import block.
Both are required for this branch to pass the mypy/ruff pre-commit hooks
(mypy follows imports, so the utils.py error blocked any basic.py commit).
No runtime behavior change: casts are no-ops and table names are strings.
@timtreis
timtreis marked this pull request as ready for review June 14, 2026 16:23
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
Integrate #702 (per-panel title fix) and #703 (as_points + fast extent)
into the show() decomposition.
Conflict resolution in src/spatialdata_plot/pl/basic.py:
- #702 up-front title-count validation: kept (after num_panels). The
adjacent axes/panel-count check was dropped here because the
decomposition already relocated it into _plan_panels.
- #702 simplified title selection (dropped the per-panel try/except):
applied to the extracted _finalize_panel helper.
- #703 had no show()-level render-dispatch changes (as_points is
param-driven in render.py, handled inside _render_panel already); its
only show()-level change, get_extent -> _get_extent_fast, auto-merged
into the extent block and consumes _render_panel's `wants` dict.
Also sweeps up two pre-existing #703 lint/type nits in utils.py surfaced
by the merge: _fast_extent docstring (ruff D205) and _get_extent_fast
Any-return (mypy).
Verified: ruff + mypy clean; 109 non-visual show/shapes/labels tests pass.
@timtreis
timtreis merged commit 4ba13a3 into mainJun 14, 2026
7 of 8 checks passed
timtreis added a commit that referenced this pull request Jun 14, 2026
Integrate #714 (show() decomposition) into the utils.py split. Only utils.py
conflicted:
- import block: dropped the now-unused `_locate_value` import (it moved to
_color.py with the color code that uses it); kept main's `_locate_value`
out of utils.
- `_fast_extent` docstring: took main's #714 version (D205 fix).
basic.py auto-merged: #714's decomposed show()/helpers now import color and
validation symbols from _color/_validate (the split's repoints), not utils.
Bonus: merging #714 brings its fixes for the pre-existing #703/#705 debt
(_resolve_measure_table str-return, _get_extent_fast Any-return, _fast_extent
D205), so the branch is now fully ruff + mypy clean (no --no-verify).
Verified: no import cycle; ruff + ruff-format + mypy all pass; 410 non-visual
tests pass.
@timtreis
timtreis deleted the claude/open-issues-review-j70n0h branch July 10, 2026 11:11
Sign up for freeto 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.

Refactor: decompose the 644-line show() god-function into named stages

3 participants

@timtreis@codecov-commenter@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor: decompose show() into named stages - #714

Merged
timtreis merged 7 commits into
mainfrom
claude/open-issues-review-j70n0h
Jun 14, 2026
Merged

refactor: decompose show() into named stages#714
timtreis merged 7 commits into
mainfrom
claude/open-issues-review-j70n0h

Conversation

@timtreis

@timtreistimtreis commented Jun 13, 2026

Copy link
Copy Markdown
Member

Closes#697.

Decomposes the 644-line show() god-function into named, single-purpose helpers. Behavior-preserving — no public API change.

Extracted helpers

_collect_render_commands · _normalize_title · _resolve_coordinate_systems · _plan_panels · _build_legend_params · _draw_colorbar (promoted from closure) · _layout_pending_colorbars · _render_panel · _finalize_panel · _should_rasterize · _maybe_set_label_colors

Also: _validate_show_parameters now called by keyword; per-render dispatch is a table; has_*/wants_* boolean sprawl replaced by cs_row + a wants dict.

Metrics (lizard, basic.py)

mainthis PR
show() cyclomatic complexity10930
show() NLOC390195
avg complexity / function15.88.2

claude added 4 commits June 13, 2026 00:07
…tion
Decompose the show() god-function (#697). First, behavior-preserving steps:
- pass _validate_show_parameters args by keyword so a signature reorder
can no longer silently misvalidate one parameter as another
- extract _collect_render_commands() and _normalize_title() module helpers
No behavior change.
…bar stages
Continue decomposing show() (#697), behavior-preserving:
- _resolve_coordinate_systems(): CS auto-detection, validation and filtering
- _plan_panels(): panel layout (one-per-CS vs one-per-color-key) + ax-count check
- _build_legend_params(): LegendParams construction with legend_params overrides
- promote the _draw_colorbar closure to a module function taking colorbar_params
explicitly (was a ~90-line closure capturing it implicitly)
- _layout_pending_colorbars(): the deferred second-pass colorbar layout
No behavior change.
…_finalize_panel
Extract the inner render-command dispatch loop (#697), behavior-preserving:
- _render_panel(): dispatches each queued render command into one panel's axes,
returning the wanted elements and per-type wants_* flags
- _finalize_panel(): per-panel title / equal-aspect / frame visibility
show() is now a compact orchestrator calling named stages. Full test suite
passes unchanged (719 passed, 1 skipped), image baselines included.
No behavior change.
Post-review cleanups (all behavior-preserving, full suite green 719 passed):
- drop dead _draw_colorbar param base_offsets_axes (never read)
- collapse the two parallel 4-branch location chains in _draw_colorbar into
data-driven lookups (vertical flag + opposite map + getattr on the axis)
- extract _should_rasterize() to dedup the images/labels rasterize heuristic
- extract _maybe_set_label_colors() for the categorical-color prestep
- replace the 5-branch render dispatch in _render_panel with a renderer table
keyed by command; graph stays a small special case
- pass cs_row instead of four has_* booleans; return a wants dict instead of a
four-boolean tuple (removes the parallel-variable sprawl)
@codecov-commenter

codecov-commenter commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.23529% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.13%. Comparing base (8730ff4) to head (3f26158).

Files with missing linesPatch %Lines
src/spatialdata_plot/pl/basic.py89.07%8 Missing and 12 partials ⚠️
src/spatialdata_plot/pl/utils.py50.00%2 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #714 +/- ##
==========================================
+ Coverage 77.00% 77.13% +0.13% 
==========================================
Files 14 14 Lines 4457 4457 Branches 1036 1023 -13 ==========================================
+ Hits 3432 3438 +6 - Misses 660 661 +1 + Partials 365 358 -7 
Files with missing linesCoverage Δ
src/spatialdata_plot/pl/utils.py69.38% <50.00%> (-0.02%)⬇️
src/spatialdata_plot/pl/basic.py82.36% <89.07%> (+1.35%)⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@timtreistimtreis changed the title refactor: decompose show() into named stages (#697)refactor: decompose show() into named stagesJun 14, 2026
Follow-up cleanup on the show() decomposition:
- Hoist the render-command dispatch dict to a module-level _RENDERERS
constant instead of rebuilding it on every _render_panel call.
- Derive _VALID_RENDER_CMDS from _RENDER_CMD_TO_CS_FLAG (+ render_graph)
so the valid-command set isn't a second hand-maintained list.
- Drop the always-true `if "render" in cmd` branch in
_collect_render_commands (every valid command starts with "render",
and unknown commands already raise above it).
- Hoist _finalize_panel out of the per-command loop in _render_panel; it
depends only on per-panel values, so it now runs once per panel.
Behavior-preserving; no public API change. (Pre-existing mypy errors on
this branch are unrelated and unchanged by this commit.)
`_render_panel` iterates render commands whose params are a 5-way
RenderParams union. The command string discriminates the concrete type
but mypy can't infer that, so narrow with explicit casts at each typed
call (_render_graph, _get_wanted_render_elements, _should_rasterize,
_maybe_set_label_colors) and type _RENDERERS as
`dict[str, Callable[..., None]]` so the dynamic dispatch type-checks.
Also fixes an unrelated pre-existing mypy error in utils.py
(`_resolve_measure_table` returning Any) by coercing the table name to
str; ruff additionally normalized the adjacent spatialdata import block.
Both are required for this branch to pass the mypy/ruff pre-commit hooks
(mypy follows imports, so the utils.py error blocked any basic.py commit).
No runtime behavior change: casts are no-ops and table names are strings.
@timtreis
timtreis marked this pull request as ready for review June 14, 2026 16:23
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
Integrate #702 (per-panel title fix) and #703 (as_points + fast extent)
into the show() decomposition.
Conflict resolution in src/spatialdata_plot/pl/basic.py:
- #702 up-front title-count validation: kept (after num_panels). The
adjacent axes/panel-count check was dropped here because the
decomposition already relocated it into _plan_panels.
- #702 simplified title selection (dropped the per-panel try/except):
applied to the extracted _finalize_panel helper.
- #703 had no show()-level render-dispatch changes (as_points is
param-driven in render.py, handled inside _render_panel already); its
only show()-level change, get_extent -> _get_extent_fast, auto-merged
into the extent block and consumes _render_panel's `wants` dict.
Also sweeps up two pre-existing #703 lint/type nits in utils.py surfaced
by the merge: _fast_extent docstring (ruff D205) and _get_extent_fast
Any-return (mypy).
Verified: ruff + mypy clean; 109 non-visual show/shapes/labels tests pass.
@timtreis
timtreis merged commit 4ba13a3 into mainJun 14, 2026
7 of 8 checks passed
timtreis added a commit that referenced this pull request Jun 14, 2026
Integrate #714 (show() decomposition) into the utils.py split. Only utils.py
conflicted:
- import block: dropped the now-unused `_locate_value` import (it moved to
_color.py with the color code that uses it); kept main's `_locate_value`
out of utils.
- `_fast_extent` docstring: took main's #714 version (D205 fix).
basic.py auto-merged: #714's decomposed show()/helpers now import color and
validation symbols from _color/_validate (the split's repoints), not utils.
Bonus: merging #714 brings its fixes for the pre-existing #703/#705 debt
(_resolve_measure_table str-return, _get_extent_fast Any-return, _fast_extent
D205), so the branch is now fully ruff + mypy clean (no --no-verify).
Verified: no import cycle; ruff + ruff-format + mypy all pass; 410 non-visual
tests pass.
@timtreis
timtreis deleted the claude/open-issues-review-j70n0h branch July 10, 2026 11:11
Sign up for freeto 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.

Refactor: decompose the 644-line show() god-function into named stages

3 participants

@timtreis@codecov-commenter@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

refactor: decompose show() into named stages - #714

Merged
timtreis merged 7 commits into
mainfrom
claude/open-issues-review-j70n0h
Jun 14, 2026
Merged

refactor: decompose show() into named stages#714
timtreis merged 7 commits into
mainfrom
claude/open-issues-review-j70n0h

Conversation

@timtreis

@timtreistimtreis commented Jun 13, 2026

Copy link
Copy Markdown
Member

Closes#697.

Decomposes the 644-line show() god-function into named, single-purpose helpers. Behavior-preserving — no public API change.

Extracted helpers

_collect_render_commands · _normalize_title · _resolve_coordinate_systems · _plan_panels · _build_legend_params · _draw_colorbar (promoted from closure) · _layout_pending_colorbars · _render_panel · _finalize_panel · _should_rasterize · _maybe_set_label_colors

Also: _validate_show_parameters now called by keyword; per-render dispatch is a table; has_*/wants_* boolean sprawl replaced by cs_row + a wants dict.

Metrics (lizard, basic.py)

mainthis PR
show() cyclomatic complexity10930
show() NLOC390195
avg complexity / function15.88.2

claude added 4 commits June 13, 2026 00:07
…tion
Decompose the show() god-function (#697). First, behavior-preserving steps:
- pass _validate_show_parameters args by keyword so a signature reorder
can no longer silently misvalidate one parameter as another
- extract _collect_render_commands() and _normalize_title() module helpers
No behavior change.
…bar stages
Continue decomposing show() (#697), behavior-preserving:
- _resolve_coordinate_systems(): CS auto-detection, validation and filtering
- _plan_panels(): panel layout (one-per-CS vs one-per-color-key) + ax-count check
- _build_legend_params(): LegendParams construction with legend_params overrides
- promote the _draw_colorbar closure to a module function taking colorbar_params
explicitly (was a ~90-line closure capturing it implicitly)
- _layout_pending_colorbars(): the deferred second-pass colorbar layout
No behavior change.
…_finalize_panel
Extract the inner render-command dispatch loop (#697), behavior-preserving:
- _render_panel(): dispatches each queued render command into one panel's axes,
returning the wanted elements and per-type wants_* flags
- _finalize_panel(): per-panel title / equal-aspect / frame visibility
show() is now a compact orchestrator calling named stages. Full test suite
passes unchanged (719 passed, 1 skipped), image baselines included.
No behavior change.
Post-review cleanups (all behavior-preserving, full suite green 719 passed):
- drop dead _draw_colorbar param base_offsets_axes (never read)
- collapse the two parallel 4-branch location chains in _draw_colorbar into
data-driven lookups (vertical flag + opposite map + getattr on the axis)
- extract _should_rasterize() to dedup the images/labels rasterize heuristic
- extract _maybe_set_label_colors() for the categorical-color prestep
- replace the 5-branch render dispatch in _render_panel with a renderer table
keyed by command; graph stays a small special case
- pass cs_row instead of four has_* booleans; return a wants dict instead of a
four-boolean tuple (removes the parallel-variable sprawl)
@codecov-commenter

codecov-commenter commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.23529% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.13%. Comparing base (8730ff4) to head (3f26158).

Files with missing linesPatch %Lines
src/spatialdata_plot/pl/basic.py89.07%8 Missing and 12 partials ⚠️
src/spatialdata_plot/pl/utils.py50.00%2 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #714 +/- ##
==========================================
+ Coverage 77.00% 77.13% +0.13% 
==========================================
Files 14 14 Lines 4457 4457 Branches 1036 1023 -13 ==========================================
+ Hits 3432 3438 +6 - Misses 660 661 +1 + Partials 365 358 -7 
Files with missing linesCoverage Δ
src/spatialdata_plot/pl/utils.py69.38% <50.00%> (-0.02%)⬇️
src/spatialdata_plot/pl/basic.py82.36% <89.07%> (+1.35%)⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@timtreistimtreis changed the title refactor: decompose show() into named stages (#697)refactor: decompose show() into named stagesJun 14, 2026
Follow-up cleanup on the show() decomposition:
- Hoist the render-command dispatch dict to a module-level _RENDERERS
constant instead of rebuilding it on every _render_panel call.
- Derive _VALID_RENDER_CMDS from _RENDER_CMD_TO_CS_FLAG (+ render_graph)
so the valid-command set isn't a second hand-maintained list.
- Drop the always-true `if "render" in cmd` branch in
_collect_render_commands (every valid command starts with "render",
and unknown commands already raise above it).
- Hoist _finalize_panel out of the per-command loop in _render_panel; it
depends only on per-panel values, so it now runs once per panel.
Behavior-preserving; no public API change. (Pre-existing mypy errors on
this branch are unrelated and unchanged by this commit.)
`_render_panel` iterates render commands whose params are a 5-way
RenderParams union. The command string discriminates the concrete type
but mypy can't infer that, so narrow with explicit casts at each typed
call (_render_graph, _get_wanted_render_elements, _should_rasterize,
_maybe_set_label_colors) and type _RENDERERS as
`dict[str, Callable[..., None]]` so the dynamic dispatch type-checks.
Also fixes an unrelated pre-existing mypy error in utils.py
(`_resolve_measure_table` returning Any) by coercing the table name to
str; ruff additionally normalized the adjacent spatialdata import block.
Both are required for this branch to pass the mypy/ruff pre-commit hooks
(mypy follows imports, so the utils.py error blocked any basic.py commit).
No runtime behavior change: casts are no-ops and table names are strings.
@timtreis
timtreis marked this pull request as ready for review June 14, 2026 16:23
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
Integrate #702 (per-panel title fix) and #703 (as_points + fast extent)
into the show() decomposition.
Conflict resolution in src/spatialdata_plot/pl/basic.py:
- #702 up-front title-count validation: kept (after num_panels). The
adjacent axes/panel-count check was dropped here because the
decomposition already relocated it into _plan_panels.
- #702 simplified title selection (dropped the per-panel try/except):
applied to the extracted _finalize_panel helper.
- #703 had no show()-level render-dispatch changes (as_points is
param-driven in render.py, handled inside _render_panel already); its
only show()-level change, get_extent -> _get_extent_fast, auto-merged
into the extent block and consumes _render_panel's `wants` dict.
Also sweeps up two pre-existing #703 lint/type nits in utils.py surfaced
by the merge: _fast_extent docstring (ruff D205) and _get_extent_fast
Any-return (mypy).
Verified: ruff + mypy clean; 109 non-visual show/shapes/labels tests pass.
@timtreis
timtreis merged commit 4ba13a3 into mainJun 14, 2026
7 of 8 checks passed
timtreis added a commit that referenced this pull request Jun 14, 2026
Integrate #714 (show() decomposition) into the utils.py split. Only utils.py
conflicted:
- import block: dropped the now-unused `_locate_value` import (it moved to
_color.py with the color code that uses it); kept main's `_locate_value`
out of utils.
- `_fast_extent` docstring: took main's #714 version (D205 fix).
basic.py auto-merged: #714's decomposed show()/helpers now import color and
validation symbols from _color/_validate (the split's repoints), not utils.
Bonus: merging #714 brings its fixes for the pre-existing #703/#705 debt
(_resolve_measure_table str-return, _get_extent_fast Any-return, _fast_extent
D205), so the branch is now fully ruff + mypy clean (no --no-verify).
Verified: no import cycle; ruff + ruff-format + mypy all pass; 410 non-visual
tests pass.
@timtreis
timtreis deleted the claude/open-issues-review-j70n0h branch July 10, 2026 11:11
Sign up for freeto 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.

Refactor: decompose the 644-line show() god-function into named stages

3 participants

@timtreis@codecov-commenter@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor: decompose show() into named stages - #714

Merged
timtreis merged 7 commits into
mainfrom
claude/open-issues-review-j70n0h
Jun 14, 2026
Merged

refactor: decompose show() into named stages#714
timtreis merged 7 commits into
mainfrom
claude/open-issues-review-j70n0h

Conversation

@timtreis

@timtreistimtreis commented Jun 13, 2026

Copy link
Copy Markdown
Member

Closes#697.

Decomposes the 644-line show() god-function into named, single-purpose helpers. Behavior-preserving — no public API change.

Extracted helpers

_collect_render_commands · _normalize_title · _resolve_coordinate_systems · _plan_panels · _build_legend_params · _draw_colorbar (promoted from closure) · _layout_pending_colorbars · _render_panel · _finalize_panel · _should_rasterize · _maybe_set_label_colors

Also: _validate_show_parameters now called by keyword; per-render dispatch is a table; has_*/wants_* boolean sprawl replaced by cs_row + a wants dict.

Metrics (lizard, basic.py)

mainthis PR
show() cyclomatic complexity10930
show() NLOC390195
avg complexity / function15.88.2

claude added 4 commits June 13, 2026 00:07
…tion
Decompose the show() god-function (#697). First, behavior-preserving steps:
- pass _validate_show_parameters args by keyword so a signature reorder
can no longer silently misvalidate one parameter as another
- extract _collect_render_commands() and _normalize_title() module helpers
No behavior change.
…bar stages
Continue decomposing show() (#697), behavior-preserving:
- _resolve_coordinate_systems(): CS auto-detection, validation and filtering
- _plan_panels(): panel layout (one-per-CS vs one-per-color-key) + ax-count check
- _build_legend_params(): LegendParams construction with legend_params overrides
- promote the _draw_colorbar closure to a module function taking colorbar_params
explicitly (was a ~90-line closure capturing it implicitly)
- _layout_pending_colorbars(): the deferred second-pass colorbar layout
No behavior change.
…_finalize_panel
Extract the inner render-command dispatch loop (#697), behavior-preserving:
- _render_panel(): dispatches each queued render command into one panel's axes,
returning the wanted elements and per-type wants_* flags
- _finalize_panel(): per-panel title / equal-aspect / frame visibility
show() is now a compact orchestrator calling named stages. Full test suite
passes unchanged (719 passed, 1 skipped), image baselines included.
No behavior change.
Post-review cleanups (all behavior-preserving, full suite green 719 passed):
- drop dead _draw_colorbar param base_offsets_axes (never read)
- collapse the two parallel 4-branch location chains in _draw_colorbar into
data-driven lookups (vertical flag + opposite map + getattr on the axis)
- extract _should_rasterize() to dedup the images/labels rasterize heuristic
- extract _maybe_set_label_colors() for the categorical-color prestep
- replace the 5-branch render dispatch in _render_panel with a renderer table
keyed by command; graph stays a small special case
- pass cs_row instead of four has_* booleans; return a wants dict instead of a
four-boolean tuple (removes the parallel-variable sprawl)
@codecov-commenter

codecov-commenter commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.23529% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.13%. Comparing base (8730ff4) to head (3f26158).

Files with missing linesPatch %Lines
src/spatialdata_plot/pl/basic.py89.07%8 Missing and 12 partials ⚠️
src/spatialdata_plot/pl/utils.py50.00%2 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #714 +/- ##
==========================================
+ Coverage 77.00% 77.13% +0.13% 
==========================================
Files 14 14 Lines 4457 4457 Branches 1036 1023 -13 ==========================================
+ Hits 3432 3438 +6 - Misses 660 661 +1 + Partials 365 358 -7 
Files with missing linesCoverage Δ
src/spatialdata_plot/pl/utils.py69.38% <50.00%> (-0.02%)⬇️
src/spatialdata_plot/pl/basic.py82.36% <89.07%> (+1.35%)⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@timtreistimtreis changed the title refactor: decompose show() into named stages (#697)refactor: decompose show() into named stagesJun 14, 2026
Follow-up cleanup on the show() decomposition:
- Hoist the render-command dispatch dict to a module-level _RENDERERS
constant instead of rebuilding it on every _render_panel call.
- Derive _VALID_RENDER_CMDS from _RENDER_CMD_TO_CS_FLAG (+ render_graph)
so the valid-command set isn't a second hand-maintained list.
- Drop the always-true `if "render" in cmd` branch in
_collect_render_commands (every valid command starts with "render",
and unknown commands already raise above it).
- Hoist _finalize_panel out of the per-command loop in _render_panel; it
depends only on per-panel values, so it now runs once per panel.
Behavior-preserving; no public API change. (Pre-existing mypy errors on
this branch are unrelated and unchanged by this commit.)
`_render_panel` iterates render commands whose params are a 5-way
RenderParams union. The command string discriminates the concrete type
but mypy can't infer that, so narrow with explicit casts at each typed
call (_render_graph, _get_wanted_render_elements, _should_rasterize,
_maybe_set_label_colors) and type _RENDERERS as
`dict[str, Callable[..., None]]` so the dynamic dispatch type-checks.
Also fixes an unrelated pre-existing mypy error in utils.py
(`_resolve_measure_table` returning Any) by coercing the table name to
str; ruff additionally normalized the adjacent spatialdata import block.
Both are required for this branch to pass the mypy/ruff pre-commit hooks
(mypy follows imports, so the utils.py error blocked any basic.py commit).
No runtime behavior change: casts are no-ops and table names are strings.
@timtreis
timtreis marked this pull request as ready for review June 14, 2026 16:23
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
Integrate #702 (per-panel title fix) and #703 (as_points + fast extent)
into the show() decomposition.
Conflict resolution in src/spatialdata_plot/pl/basic.py:
- #702 up-front title-count validation: kept (after num_panels). The
adjacent axes/panel-count check was dropped here because the
decomposition already relocated it into _plan_panels.
- #702 simplified title selection (dropped the per-panel try/except):
applied to the extracted _finalize_panel helper.
- #703 had no show()-level render-dispatch changes (as_points is
param-driven in render.py, handled inside _render_panel already); its
only show()-level change, get_extent -> _get_extent_fast, auto-merged
into the extent block and consumes _render_panel's `wants` dict.
Also sweeps up two pre-existing #703 lint/type nits in utils.py surfaced
by the merge: _fast_extent docstring (ruff D205) and _get_extent_fast
Any-return (mypy).
Verified: ruff + mypy clean; 109 non-visual show/shapes/labels tests pass.
@timtreis
timtreis merged commit 4ba13a3 into mainJun 14, 2026
7 of 8 checks passed
timtreis added a commit that referenced this pull request Jun 14, 2026
Integrate #714 (show() decomposition) into the utils.py split. Only utils.py
conflicted:
- import block: dropped the now-unused `_locate_value` import (it moved to
_color.py with the color code that uses it); kept main's `_locate_value`
out of utils.
- `_fast_extent` docstring: took main's #714 version (D205 fix).
basic.py auto-merged: #714's decomposed show()/helpers now import color and
validation symbols from _color/_validate (the split's repoints), not utils.
Bonus: merging #714 brings its fixes for the pre-existing #703/#705 debt
(_resolve_measure_table str-return, _get_extent_fast Any-return, _fast_extent
D205), so the branch is now fully ruff + mypy clean (no --no-verify).
Verified: no import cycle; ruff + ruff-format + mypy all pass; 410 non-visual
tests pass.
@timtreis
timtreis deleted the claude/open-issues-review-j70n0h branch July 10, 2026 11:11
Sign up for freeto 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.

Refactor: decompose the 644-line show() god-function into named stages

3 participants

@timtreis@codecov-commenter@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

refactor: decompose show() into named stages - #714

Merged
timtreis merged 7 commits into
mainfrom
claude/open-issues-review-j70n0h
Jun 14, 2026
Merged

refactor: decompose show() into named stages#714
timtreis merged 7 commits into
mainfrom
claude/open-issues-review-j70n0h

Conversation

@timtreis

@timtreistimtreis commented Jun 13, 2026

Copy link
Copy Markdown
Member

Closes#697.

Decomposes the 644-line show() god-function into named, single-purpose helpers. Behavior-preserving — no public API change.

Extracted helpers

_collect_render_commands · _normalize_title · _resolve_coordinate_systems · _plan_panels · _build_legend_params · _draw_colorbar (promoted from closure) · _layout_pending_colorbars · _render_panel · _finalize_panel · _should_rasterize · _maybe_set_label_colors

Also: _validate_show_parameters now called by keyword; per-render dispatch is a table; has_*/wants_* boolean sprawl replaced by cs_row + a wants dict.

Metrics (lizard, basic.py)

mainthis PR
show() cyclomatic complexity10930
show() NLOC390195
avg complexity / function15.88.2

claude added 4 commits June 13, 2026 00:07
…tion
Decompose the show() god-function (#697). First, behavior-preserving steps:
- pass _validate_show_parameters args by keyword so a signature reorder
can no longer silently misvalidate one parameter as another
- extract _collect_render_commands() and _normalize_title() module helpers
No behavior change.
…bar stages
Continue decomposing show() (#697), behavior-preserving:
- _resolve_coordinate_systems(): CS auto-detection, validation and filtering
- _plan_panels(): panel layout (one-per-CS vs one-per-color-key) + ax-count check
- _build_legend_params(): LegendParams construction with legend_params overrides
- promote the _draw_colorbar closure to a module function taking colorbar_params
explicitly (was a ~90-line closure capturing it implicitly)
- _layout_pending_colorbars(): the deferred second-pass colorbar layout
No behavior change.
…_finalize_panel
Extract the inner render-command dispatch loop (#697), behavior-preserving:
- _render_panel(): dispatches each queued render command into one panel's axes,
returning the wanted elements and per-type wants_* flags
- _finalize_panel(): per-panel title / equal-aspect / frame visibility
show() is now a compact orchestrator calling named stages. Full test suite
passes unchanged (719 passed, 1 skipped), image baselines included.
No behavior change.
Post-review cleanups (all behavior-preserving, full suite green 719 passed):
- drop dead _draw_colorbar param base_offsets_axes (never read)
- collapse the two parallel 4-branch location chains in _draw_colorbar into
data-driven lookups (vertical flag + opposite map + getattr on the axis)
- extract _should_rasterize() to dedup the images/labels rasterize heuristic
- extract _maybe_set_label_colors() for the categorical-color prestep
- replace the 5-branch render dispatch in _render_panel with a renderer table
keyed by command; graph stays a small special case
- pass cs_row instead of four has_* booleans; return a wants dict instead of a
four-boolean tuple (removes the parallel-variable sprawl)
@codecov-commenter

codecov-commenter commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.23529% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.13%. Comparing base (8730ff4) to head (3f26158).

Files with missing linesPatch %Lines
src/spatialdata_plot/pl/basic.py89.07%8 Missing and 12 partials ⚠️
src/spatialdata_plot/pl/utils.py50.00%2 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #714 +/- ##
==========================================
+ Coverage 77.00% 77.13% +0.13% 
==========================================
Files 14 14 Lines 4457 4457 Branches 1036 1023 -13 ==========================================
+ Hits 3432 3438 +6 - Misses 660 661 +1 + Partials 365 358 -7 
Files with missing linesCoverage Δ
src/spatialdata_plot/pl/utils.py69.38% <50.00%> (-0.02%)⬇️
src/spatialdata_plot/pl/basic.py82.36% <89.07%> (+1.35%)⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@timtreistimtreis changed the title refactor: decompose show() into named stages (#697)refactor: decompose show() into named stagesJun 14, 2026
Follow-up cleanup on the show() decomposition:
- Hoist the render-command dispatch dict to a module-level _RENDERERS
constant instead of rebuilding it on every _render_panel call.
- Derive _VALID_RENDER_CMDS from _RENDER_CMD_TO_CS_FLAG (+ render_graph)
so the valid-command set isn't a second hand-maintained list.
- Drop the always-true `if "render" in cmd` branch in
_collect_render_commands (every valid command starts with "render",
and unknown commands already raise above it).
- Hoist _finalize_panel out of the per-command loop in _render_panel; it
depends only on per-panel values, so it now runs once per panel.
Behavior-preserving; no public API change. (Pre-existing mypy errors on
this branch are unrelated and unchanged by this commit.)
`_render_panel` iterates render commands whose params are a 5-way
RenderParams union. The command string discriminates the concrete type
but mypy can't infer that, so narrow with explicit casts at each typed
call (_render_graph, _get_wanted_render_elements, _should_rasterize,
_maybe_set_label_colors) and type _RENDERERS as
`dict[str, Callable[..., None]]` so the dynamic dispatch type-checks.
Also fixes an unrelated pre-existing mypy error in utils.py
(`_resolve_measure_table` returning Any) by coercing the table name to
str; ruff additionally normalized the adjacent spatialdata import block.
Both are required for this branch to pass the mypy/ruff pre-commit hooks
(mypy follows imports, so the utils.py error blocked any basic.py commit).
No runtime behavior change: casts are no-ops and table names are strings.
@timtreis
timtreis marked this pull request as ready for review June 14, 2026 16:23
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
Integrate #702 (per-panel title fix) and #703 (as_points + fast extent)
into the show() decomposition.
Conflict resolution in src/spatialdata_plot/pl/basic.py:
- #702 up-front title-count validation: kept (after num_panels). The
adjacent axes/panel-count check was dropped here because the
decomposition already relocated it into _plan_panels.
- #702 simplified title selection (dropped the per-panel try/except):
applied to the extracted _finalize_panel helper.
- #703 had no show()-level render-dispatch changes (as_points is
param-driven in render.py, handled inside _render_panel already); its
only show()-level change, get_extent -> _get_extent_fast, auto-merged
into the extent block and consumes _render_panel's `wants` dict.
Also sweeps up two pre-existing #703 lint/type nits in utils.py surfaced
by the merge: _fast_extent docstring (ruff D205) and _get_extent_fast
Any-return (mypy).
Verified: ruff + mypy clean; 109 non-visual show/shapes/labels tests pass.
@timtreis
timtreis merged commit 4ba13a3 into mainJun 14, 2026
7 of 8 checks passed
timtreis added a commit that referenced this pull request Jun 14, 2026
Integrate #714 (show() decomposition) into the utils.py split. Only utils.py
conflicted:
- import block: dropped the now-unused `_locate_value` import (it moved to
_color.py with the color code that uses it); kept main's `_locate_value`
out of utils.
- `_fast_extent` docstring: took main's #714 version (D205 fix).
basic.py auto-merged: #714's decomposed show()/helpers now import color and
validation symbols from _color/_validate (the split's repoints), not utils.
Bonus: merging #714 brings its fixes for the pre-existing #703/#705 debt
(_resolve_measure_table str-return, _get_extent_fast Any-return, _fast_extent
D205), so the branch is now fully ruff + mypy clean (no --no-verify).
Verified: no import cycle; ruff + ruff-format + mypy all pass; 410 non-visual
tests pass.
@timtreis
timtreis deleted the claude/open-issues-review-j70n0h branch July 10, 2026 11:11
Sign up for freeto 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.

Refactor: decompose the 644-line show() god-function into named stages

3 participants

@timtreis@codecov-commenter@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

refactor: decompose show() into named stages - #714

Merged
timtreis merged 7 commits into
mainfrom
claude/open-issues-review-j70n0h
Jun 14, 2026
Merged

refactor: decompose show() into named stages#714
timtreis merged 7 commits into
mainfrom
claude/open-issues-review-j70n0h

Conversation

@timtreis

@timtreistimtreis commented Jun 13, 2026

Copy link
Copy Markdown
Member

Closes#697.

Decomposes the 644-line show() god-function into named, single-purpose helpers. Behavior-preserving — no public API change.

Extracted helpers

_collect_render_commands · _normalize_title · _resolve_coordinate_systems · _plan_panels · _build_legend_params · _draw_colorbar (promoted from closure) · _layout_pending_colorbars · _render_panel · _finalize_panel · _should_rasterize · _maybe_set_label_colors

Also: _validate_show_parameters now called by keyword; per-render dispatch is a table; has_*/wants_* boolean sprawl replaced by cs_row + a wants dict.

Metrics (lizard, basic.py)

mainthis PR
show() cyclomatic complexity10930
show() NLOC390195
avg complexity / function15.88.2

claude added 4 commits June 13, 2026 00:07
…tion
Decompose the show() god-function (#697). First, behavior-preserving steps:
- pass _validate_show_parameters args by keyword so a signature reorder
can no longer silently misvalidate one parameter as another
- extract _collect_render_commands() and _normalize_title() module helpers
No behavior change.
…bar stages
Continue decomposing show() (#697), behavior-preserving:
- _resolve_coordinate_systems(): CS auto-detection, validation and filtering
- _plan_panels(): panel layout (one-per-CS vs one-per-color-key) + ax-count check
- _build_legend_params(): LegendParams construction with legend_params overrides
- promote the _draw_colorbar closure to a module function taking colorbar_params
explicitly (was a ~90-line closure capturing it implicitly)
- _layout_pending_colorbars(): the deferred second-pass colorbar layout
No behavior change.
…_finalize_panel
Extract the inner render-command dispatch loop (#697), behavior-preserving:
- _render_panel(): dispatches each queued render command into one panel's axes,
returning the wanted elements and per-type wants_* flags
- _finalize_panel(): per-panel title / equal-aspect / frame visibility
show() is now a compact orchestrator calling named stages. Full test suite
passes unchanged (719 passed, 1 skipped), image baselines included.
No behavior change.
Post-review cleanups (all behavior-preserving, full suite green 719 passed):
- drop dead _draw_colorbar param base_offsets_axes (never read)
- collapse the two parallel 4-branch location chains in _draw_colorbar into
data-driven lookups (vertical flag + opposite map + getattr on the axis)
- extract _should_rasterize() to dedup the images/labels rasterize heuristic
- extract _maybe_set_label_colors() for the categorical-color prestep
- replace the 5-branch render dispatch in _render_panel with a renderer table
keyed by command; graph stays a small special case
- pass cs_row instead of four has_* booleans; return a wants dict instead of a
four-boolean tuple (removes the parallel-variable sprawl)
@codecov-commenter

codecov-commenter commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.23529% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.13%. Comparing base (8730ff4) to head (3f26158).

Files with missing linesPatch %Lines
src/spatialdata_plot/pl/basic.py89.07%8 Missing and 12 partials ⚠️
src/spatialdata_plot/pl/utils.py50.00%2 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #714 +/- ##
==========================================
+ Coverage 77.00% 77.13% +0.13% 
==========================================
Files 14 14 Lines 4457 4457 Branches 1036 1023 -13 ==========================================
+ Hits 3432 3438 +6 - Misses 660 661 +1 + Partials 365 358 -7 
Files with missing linesCoverage Δ
src/spatialdata_plot/pl/utils.py69.38% <50.00%> (-0.02%)⬇️
src/spatialdata_plot/pl/basic.py82.36% <89.07%> (+1.35%)⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@timtreistimtreis changed the title refactor: decompose show() into named stages (#697)refactor: decompose show() into named stagesJun 14, 2026
Follow-up cleanup on the show() decomposition:
- Hoist the render-command dispatch dict to a module-level _RENDERERS
constant instead of rebuilding it on every _render_panel call.
- Derive _VALID_RENDER_CMDS from _RENDER_CMD_TO_CS_FLAG (+ render_graph)
so the valid-command set isn't a second hand-maintained list.
- Drop the always-true `if "render" in cmd` branch in
_collect_render_commands (every valid command starts with "render",
and unknown commands already raise above it).
- Hoist _finalize_panel out of the per-command loop in _render_panel; it
depends only on per-panel values, so it now runs once per panel.
Behavior-preserving; no public API change. (Pre-existing mypy errors on
this branch are unrelated and unchanged by this commit.)
`_render_panel` iterates render commands whose params are a 5-way
RenderParams union. The command string discriminates the concrete type
but mypy can't infer that, so narrow with explicit casts at each typed
call (_render_graph, _get_wanted_render_elements, _should_rasterize,
_maybe_set_label_colors) and type _RENDERERS as
`dict[str, Callable[..., None]]` so the dynamic dispatch type-checks.
Also fixes an unrelated pre-existing mypy error in utils.py
(`_resolve_measure_table` returning Any) by coercing the table name to
str; ruff additionally normalized the adjacent spatialdata import block.
Both are required for this branch to pass the mypy/ruff pre-commit hooks
(mypy follows imports, so the utils.py error blocked any basic.py commit).
No runtime behavior change: casts are no-ops and table names are strings.
@timtreis
timtreis marked this pull request as ready for review June 14, 2026 16:23
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:
- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.
The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.
Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
Integrate #702 (per-panel title fix) and #703 (as_points + fast extent)
into the show() decomposition.
Conflict resolution in src/spatialdata_plot/pl/basic.py:
- #702 up-front title-count validation: kept (after num_panels). The
adjacent axes/panel-count check was dropped here because the
decomposition already relocated it into _plan_panels.
- #702 simplified title selection (dropped the per-panel try/except):
applied to the extracted _finalize_panel helper.
- #703 had no show()-level render-dispatch changes (as_points is
param-driven in render.py, handled inside _render_panel already); its
only show()-level change, get_extent -> _get_extent_fast, auto-merged
into the extent block and consumes _render_panel's `wants` dict.
Also sweeps up two pre-existing #703 lint/type nits in utils.py surfaced
by the merge: _fast_extent docstring (ruff D205) and _get_extent_fast
Any-return (mypy).
Verified: ruff + mypy clean; 109 non-visual show/shapes/labels tests pass.
@timtreis
timtreis merged commit 4ba13a3 into mainJun 14, 2026
7 of 8 checks passed
timtreis added a commit that referenced this pull request Jun 14, 2026
Integrate #714 (show() decomposition) into the utils.py split. Only utils.py
conflicted:
- import block: dropped the now-unused `_locate_value` import (it moved to
_color.py with the color code that uses it); kept main's `_locate_value`
out of utils.
- `_fast_extent` docstring: took main's #714 version (D205 fix).
basic.py auto-merged: #714's decomposed show()/helpers now import color and
validation symbols from _color/_validate (the split's repoints), not utils.
Bonus: merging #714 brings its fixes for the pre-existing #703/#705 debt
(_resolve_measure_table str-return, _get_extent_fast Any-return, _fast_extent
D205), so the branch is now fully ruff + mypy clean (no --no-verify).
Verified: no import cycle; ruff + ruff-format + mypy all pass; 410 non-visual
tests pass.
@timtreis
timtreis deleted the claude/open-issues-review-j70n0h branch July 10, 2026 11:11
Sign up for freeto 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.

Refactor: decompose the 644-line show() god-function into named stages

3 participants

@timtreis@codecov-commenter@claude