Skip to content

refactor(glyphs)!: move render/prep logic onto the grouped parameter objects - #291

Open
MAfarrag wants to merge 15 commits into
mainfrom
refactor/group-object-methods
Open

refactor(glyphs)!: move render/prep logic onto the grouped parameter objects#291
MAfarrag wants to merge 15 commits into
mainfrom
refactor/group-object-methods

Conversation

@MAfarrag

@MAfarrag MAfarrag commented Aug 11, 2026

Copy link
Copy Markdown
Member

Description

Cohere the grouped parameter objects with the logic that consumes their fields. The model is the existing
ColorScaling.build_norm / every group's to_options: the object owns the transform from its own fields to
render output, and the glyph just calls it. This PR moves the remaining single-object logic out of the glyph
modules onto the objects it belongs to, and — as part of the same pass — groups ArrayGlyph's four loose RGB
band-prep constructor keywords into a new RgbBands object
(a breaking change; see below).

Render-cohesion moves (non-breaking)

  • ColorBarto_options(), resolve() (the None/False/True/instance dispatch), reset_options() (the
    colorbar=True reset dict), and specifies_placement() (the location/inside/orientation predicate). The
    free function _resolve_colorbar now just delegates; all colorbar option-building lives on the object.
  • PointOverlay.draw(ax) — the marker scatter + per-point value-label drawing, previously duplicated byte-for-byte
    across ArrayGlyph.plot and .animate; the base-glyph Glyph._plot_point_values helper is removed.
  • FrameLabel.resolve_location() + draw(ax, default_size) — the animate frame-label placement/transform/alignment.
  • PanelLabels.label_for(), panel_title(), validate() — the facet per-panel title/name_dict build and the
    axis-length checks (three duplicated raise blocks collapsed).
  • DataStyle.for_apply_style(style, hillshade=_UNSET) — the sentinel-gated apply_style construction that
    ArrayGlyph/MeshGlyph/KDEGlyph each hand-rolled; MeshGlyph and KDEGlyph drop their throwaway
    _UNSET_HILLSHADE sentinels.
  • Glyph._snapshot_group_options(*groups) — the pre-merge option-snapshot loop shared by ArrayGlyph.plot and
    KDEGlyph.plot (a base-class de-duplication; it spans multiple group objects so it stays on Glyph).

RGB band-prep grouping (BREAKING)

ArrayGlyph's constructor no longer takes the four loose RGB keywords (rgb, surface_reflectance, cutoff,
percentile); they are bundled into a new RgbBands object passed as rgb_bands=, which owns the
band-selection + stretch transform (validate(array) / prepare(array)). This also drops __init__ from 10
explicit params to 7. The lower-level prepare_array() and scale_percentile() utilities keep their loose public
signatures (and doctests) and now build an RgbBands internally. See the migration-guide entry.

No new dependencies.

Issues

This is an internal refactor and does not close an issue.

Type of change

Check relevant points.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update
  • Dev changes (CI/pyproject.toml/docs/examples/testing)

Breaking: ArrayGlyph(array, rgb=[...], surface_reflectance=..., cutoff=..., percentile=...)
ArrayGlyph(array, rgb_bands=RgbBands([...], surface_reflectance=..., cutoff=..., percentile=...)). The
RgbBands object is importable from cleopatra.glyphs.gridded.array_glyph; docs/migration.md documents the
move. The render-cohesion moves above are behaviour-preserving.

How Has This Been Tested?

Run in the worktree against the external uv environment.

  • Full non-e2e suite: pytest -m "not e2e"2232 passed.
  • Doctests: --doctest-modules across the edited modules (styling/colorbar.py, styling/params.py,
    glyphs/gridded/array_glyph.py) — incl. the new ColorBar.to_options/resolve, DataStyle.for_apply_style,
    and RgbBands examples, and the migrated ArrayGlyph(..., rgb_bands=...) constructor examples.
  • Affected test files (array/mesh/kde/glyph/colorbar/group-rollback + facet/RGB selections) all pass; the
    point-value-label tests were relocated onto PointOverlay.draw, and the 6 RGB constructor test sites migrated
    to RgbBands.
  • Ruff clean on all edited files under the repo's pinned v0.15.22.

Checklist:

  • updated version number in pyproject.toml
  • added changes to History.rst
  • updated the latest version in README file
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Cohere the grouped render-parameter objects with the logic that
consumes their fields, mirroring ColorScaling.build_norm/to_options: the
object owns the transform from its fields to render output and the glyph
just calls it.

- add ColorBar.to_options() and delegate _resolve_colorbar's instance
  branch to it, so ColorBar is symmetric with the other group objects
- add PointOverlay.draw(ax) owning the marker scatter and per-point
  value labels, replacing the block duplicated across plot() and
  animate() and the base-glyph Glyph._plot_point_values helper (removed)
- add FrameLabel.resolve_location() and FrameLabel.draw(ax, default_size)
  owning the animate frame-label placement/transform/alignment
- relocate the point-value-label tests onto PointOverlay.draw
…he objects

Second tier of the same cohesion pass: relocate the remaining
single-object transforms off the free function / glyph code onto the
group objects that own their fields.

- add ColorBar.resolve() owning the None/False/True/instance dispatch and
  ColorBar.reset_options() owning the colorbar=True reset-to-defaults dict;
  _resolve_colorbar now just delegates to ColorBar.resolve, so all of the
  colorbar option-building lives on the object
- add PanelLabels.label_for(), PanelLabels.panel_title() and
  PanelLabels.validate(); facet drops its col_coords/row_coords locals and
  calls these for per-panel titles/name_dicts and the axis-length checks
Bundle ArrayGlyph's four RGB data-preparation keywords (rgb,
surface_reflectance, cutoff, percentile) into a single RgbBands object
passed as rgb_bands=, mirroring the render-parameter grouping. The
band-selection and stretch transform moves onto the object, and __init__
drops from 10 explicit params to 7 (under Sonar S107).

- add RgbBands (indices + surface_reflectance/cutoff/percentile) owning
  validate(array) and prepare(array); the percentile / reflectance /
  cutoff stretch lives on the object
- ArrayGlyph.__init__ takes rgb_bands instead of the four loose kwargs
- prepare_array() and scale_percentile() keep their loose public
  signatures (and doctests) and now delegate to RgbBands internally;
  _prepare_sentinel_rgb folds into RgbBands._apply_surface_reflectance
  (existing cutoff behavior preserved verbatim)
- migrate examples and add a migration-guide entry

BREAKING CHANGE: ArrayGlyph no longer accepts the loose rgb /
surface_reflectance / cutoff / percentile constructor keywords; pass
rgb_bands=RgbBands([r, g, b], surface_reflectance=..., cutoff=...,
percentile=...) instead. The prepare_array() and scale_percentile()
utilities still accept the loose keywords.
Fourth cohesion pass, closing out the single-object logic still living
in glyph code:

- add DataStyle.for_apply_style(style, hillshade=_UNSET) owning the
  sentinel-gated apply_style construction the three glyphs each
  hand-rolled; ArrayGlyph/MeshGlyph/KDEGlyph now call it, and MeshGlyph
  and KDEGlyph delete their throwaway _UNSET_HILLSHADE sentinels
  (standardising on DataStyle's own _UNSET)
- add ColorBar.specifies_placement() for the location/inside/orientation
  predicate, removing the last direct ColorBar field reads from glyph
  code
- add Glyph._snapshot_group_options(*groups) for the pre-merge
  option-snapshot loop duplicated in ArrayGlyph.plot and KDEGlyph.plot
  (a base-class de-duplication; spans multiple groups so it stays on
  Glyph, not on any one object)
@MAfarrag MAfarrag changed the title refactor(glyphs): move group-object render logic onto the objects refactor(glyphs)!: move render/prep logic onto the grouped parameter objects Aug 11, 2026
…index

The surface-reflectance cutoff branch indexed `array[0]` (the first row
of the band-last array) and clipped the integer band *index* rather than
the band's pixel data, so `cutoff` never actually stretched the bands.
Clip each band's normalised data to [0, cutoff[band]] and rescale to
[0, 1] (a per-band contrast stretch), iterating over the cutoff list.

Add a regression test pinning the corrected per-band output.
…methods

Cover the group-object methods added on this branch that previously had
only indirect (integration/doctest) coverage:

- new tests/test_params.py for DataStyle.for_apply_style
- TestColorBarMethods (to_options / resolve / reset_options /
  specifies_placement) in test_colorbar_glyphs
- TestSnapshotGroupOptions for Glyph._snapshot_group_options in test_glyph
- TestRgbBands, TestFrameLabelMethods, TestPanelLabelsMethods in
  test_array_glyph
- add executable Examples to the docstrings of ColorBar.reset_options and
  specifies_placement, RgbBands.validate/prepare, PointOverlay.draw,
  FrameLabel.resolve_location/draw, and PanelLabels.label_for /
  panel_title / validate
The apply_style refactor to DataStyle.for_apply_style removed the only
use of ArrayGlyph's private _Unset/_UNSET, leaving a second sentinel type
duplicating the canonical one in styling.params. Delete the dead
definition and relocate its repr/identity test onto params._Unset,
removing the latent cross-module footgun (passing one module's sentinel
into the other's isinstance gate).
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant