Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
fad03ae
refactor(glyphs)!: remove legacy-kwarg shims and finish facet grouping
MAfarrag Aug 9, 2026
1a2eb77
test(array_glyph): hoist PanelLabels out of pytest.raises (Sonar S5778)
MAfarrag Aug 10, 2026
8c84634
test(array_glyph): hoist glyph construction out of pytest.raises (Son…
MAfarrag Aug 10, 2026
fef9d26
fix(array_glyph): reject stale figsize= on facet with a pointer to fi…
MAfarrag Aug 10, 2026
9e6b17c
docs(migration): correct exception type and note facet figsize now ra…
MAfarrag Aug 10, 2026
55d0e98
test(array_glyph): cover cell_value_text_colors threshold split
MAfarrag Aug 10, 2026
976ef38
docs(array-glyph): drop stale no_elem deprecated-alias note
MAfarrag Aug 10, 2026
e296409
docs(array_glyph): point _Unset/_UNSET docs at apply_style, not plot
MAfarrag Aug 10, 2026
80e7ca3
refactor(array_glyph): reuse label_location_is_default flag in animate
MAfarrag Aug 10, 2026
55fdc74
test(array_glyph): cover invalid loose cbar_orientation render valida…
MAfarrag Aug 10, 2026
8716af4
docs(array_glyph): document facet figsize rejection in Raises
MAfarrag Aug 10, 2026
9edf478
fix(array_glyph): reject removed facet col_coords/row_coords with a P…
MAfarrag Aug 10, 2026
7c6965b
fix(array_glyph): close facet figure when a panel kwarg is rejected
MAfarrag Aug 10, 2026
b3cb44c
docs(migration): disambiguate facet labels= from Contour labels
MAfarrag Aug 10, 2026
7596fd3
docs(migration): caveat bare-array points AttributeError to overlay k…
MAfarrag Aug 10, 2026
71fe8b3
test(array_glyph): assert bare-array points now raises AttributeError
MAfarrag Aug 10, 2026
751ed9d
docs(array_glyph): document facet col_coords/row_coords rejection in …
MAfarrag Aug 10, 2026
9fbdeeb
feat(styling): widen per-call preset overrides and vendor scientific/…
MAfarrag Aug 10, 2026
6238a41
feat(styling): widen per-call preset overrides and vendor scientific/…
MAfarrag Aug 10, 2026
6d4a94e
fix(glyphs): restore full option rollback on failed data_style (Array…
MAfarrag Aug 10, 2026
9a4c4f7
fix(mesh_glyph): restore Contour(levels=) support (option + node-cont…
MAfarrag Aug 10, 2026
9828a8c
fix(glyphs): restore animate(contour=...) on ArrayGlyph + MeshGlyph
MAfarrag Aug 10, 2026
c11fdfe
chore(glyphs): drop duplicate and unused imports (ruff F811/F401)
MAfarrag Aug 10, 2026
b4023db
fix(templates): restore publication_map style/data_style collision guard
MAfarrag Aug 10, 2026
5b8c4e4
style(imports): sort import blocks after the merge (ruff I001)
MAfarrag Aug 10, 2026
39e6897
docs(array_glyph): document restored animate(contour=) parameter
MAfarrag Aug 10, 2026
817f12f
fix(kde_glyph): restore _UNSET_HILLSHADE sentinel so apply_style(hill…
MAfarrag Aug 10, 2026
a66f451
docs(glyphs): correct construction-kwarg docstrings for the grouped-p…
MAfarrag Aug 10, 2026
c278da7
style(tests): sort import blocks introduced by the merge (ruff I001)
MAfarrag Aug 10, 2026
d875db8
style: clean up merge whitespace artifacts (blank line, plot( spacing)
MAfarrag Aug 10, 2026
672cf91
refactor(scaling): dispatch build_norm via _NORM_BUILDERS to cut cogn…
MAfarrag Aug 11, 2026
50ba192
test: satisfy Sonar S5778 (one raising call per pytest.raises) and S9…
MAfarrag Aug 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,7 @@ dmypy.json

# Local-only project guidance for Claude Code (not tracked)
CLAUDE.md

# Local-only maintainer colour-map download scripts (kept out of git)
tools/build_scientific_presets.py
tools/build_terrain_presets.py
108 changes: 107 additions & 1 deletion docs/migration.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,110 @@
# Migration guide — subpackage restructure
# Migration guide

This page collects cleopatra's breaking changes and how to update your code. Two migrations are covered:

1. **[Grouped render-parameter objects](#grouped-render-parameter-objects)** — the loose styling keywords on
`plot` / `animate` / `facet` were replaced by typed *group objects*, and the temporary deprecation shims that
kept the old keywords working have now been **removed**. Read this section if you hit a
`ValueError: The given keyword argument:... is not correct`, an `AttributeError` from passing a bare array
where an object is expected, or a `ValueError` from `facet` saying `figsize` was renamed to `figure_size`.
2. **[Subpackage restructure](#subpackage-restructure)** — an earlier release moved the flat `cleopatra.*`
modules into `glyphs/` / `styling/` / `basemap/` subpackages (import paths only).

---

## Grouped render-parameter objects

Related styling keywords that `plot` / `animate` (and the other glyphs) used to accept as long lists of loose
arguments are now bundled into small typed objects. During one release the old keywords kept working behind a
`DeprecationWarning`; **those shims are now gone**. Passing a removed keyword no longer warns — it funnels through
cleopatra's strict option validation and raises
`ValueError: The given keyword argument:<name> is not correct, possible parameters are, [...]`. Passing a bare
`(N, 3)` array as `points` raises `AttributeError` instead of being auto-wrapped on the overlay-drawing kinds
(`imshow` / `pcolormesh`); on `contour` / `contourf` the overlay is skipped, so a bare array is ignored rather
than raising — either way, pass a `PointOverlay`. The one exception is
`facet(figsize=...)`: because `figsize` is still a valid glyph option it would otherwise be absorbed silently, so
`facet` raises a targeted `ValueError` telling you to use `figure_size` (see the table below).

There is no automated rewrite for this one: the changes are semantic (loose keywords → object fields), so update
each call site by hand using the tables below.

### Point overlays → `PointOverlay`

`points` now takes a `PointOverlay` (or `None`); the marker/label styling lives on the object.

```python
from cleopatra.glyphs.gridded.array_glyph import ArrayGlyph, PointOverlay

# before
glyph.plot(points=arr, point_color="red", point_size=80,
point_label_color="blue", point_label_size=10)
# after
glyph.plot(points=PointOverlay(arr, color="red", size=80,
label_color="blue", label_size=10))
```

| Removed keyword | New `PointOverlay` field |
| --- | --- |
| `points=<array>` (bare) | `points=PointOverlay(<array>)` |
| `point_color` | `color` |
| `point_size` | `size` |
| `point_label_color` (or oldest `pid_color`) | `label_color` |
| `point_label_size` (or oldest `pid_size`) | `label_size` |

### Frame labels → `FrameLabel`

`animate`'s per-frame time-label styling now lives on a `FrameLabel` passed as `frame_label=` (a bare `[x, y]`
list passed positionally is no longer accepted).

```python
from cleopatra.glyphs.gridded.array_glyph import ArrayGlyph, FrameLabel

# before
glyph.animate(time, label_location=[0.1, 0.1], label_color="yellow")
# after
glyph.animate(time, frame_label=FrameLabel(location=[0.1, 0.1], color="yellow"))
```

| Removed keyword | New `FrameLabel` field |
| --- | --- |
| `label_location` (or oldest `text_loc`) | `location` |
| `label_color` | `color` |

### Renamed / restructured keywords

| Old | New |
| --- | --- |
| `animate(text_colors=...)` | `animate(cell_value_text_colors=...)` |
| `facet(col_coords=..., row_coords=...)` | `facet(labels=PanelLabels(col=..., row=...))` |
| `facet(figsize=...)` | `facet(figure_size=...)` |
| `ArrayGlyph.no_elem` | `ArrayGlyph.num_domain_cells` |

`PanelLabels` is importable from `cleopatra.glyphs.gridded.array_glyph`. Note that `facet`'s `labels=` names the
per-panel *title* labels (a `PanelLabels`); it is unrelated to the loose `labels` contour-line keyword that now
lives on `Contour` (see the colour/scale/cell groups table below).

### Colour / scale / cell-value groups

The colour-scale, discretisation, cell-value, and preset/relief keywords were already folded into typed group
objects in a prior release; passing them as loose keywords **raises** with a pointer to the object:

| Loose keywords | Group object |
| --- | --- |
| `color_scale`, `gamma`, `line_threshold`, `line_scale`, `bounds`, `midpoint` | `cleopatra.styling.scaling.ColorScaling` |
| `levels`, `labels`, `label_kw` | `cleopatra.styling.params.Contour` |
| `display_cell_value`, `num_size`, `background_color_threshold` | `cleopatra.styling.params.CellValues` |
| `style`, `hillshade` | `cleopatra.styling.params.DataStyle` |

### Colour bars — `cbar_*` still work

The loose `cbar_*` / `ticks_spacing` keywords are **not** removed — they remain valid options and keep working.
Only the `DeprecationWarning` that steered you toward `ColorBar` is gone. The typed
`colorbar=ColorBar(...)` form (`cleopatra.styling.colorbar.ColorBar`) is still preferred and wins when both are
given.

---

## Subpackage restructure

This release reorganises cleopatra's previously flat `cleopatra.*` module layout into three subpackages
(`glyphs/`, `styling/`, `basemap/`) and renames the histogram glyph. It is a **breaking change to import paths
Expand Down
699 changes: 594 additions & 105 deletions docs/notebooks/array_glyph/array_glyph_examples.ipynb

Large diffs are not rendered by default.

122 changes: 106 additions & 16 deletions docs/notebooks/array_glyph/reference_map.ipynb

Large diffs are not rendered by default.

87 changes: 74 additions & 13 deletions docs/notebooks/flow_glyph/flow_glyph_examples.ipynb

Large diffs are not rendered by default.

121 changes: 103 additions & 18 deletions docs/notebooks/kde_glyph/kde_glyph_examples.ipynb

Large diffs are not rendered by default.

441 changes: 356 additions & 85 deletions docs/notebooks/mesh_glyph/mesh_glyph_examples.ipynb

Large diffs are not rendered by default.

59 changes: 51 additions & 8 deletions docs/notebooks/polygon_glyph/polygon_glyph_examples.ipynb

Large diffs are not rendered by default.

164 changes: 164 additions & 0 deletions docs/notebooks/presets/cams_aerosols_and_particulates.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "72a1e7f9",
"metadata": {},
"source": [
"# CAMS aerosols & particulate matter\n",
"\n",
"cleopatra's weather preset library carries the full **CAMS atmospheric-composition** set of reference chart styles. This notebook shows the **aerosol optical depth** family (total, dust, sulphate at 550 nm) and the **particulate-matter** air-quality scales (PM10, PM2.5).\\n\\nEach preset is a complete look -- a discrete colour ramp banded over the field's real value range, an opacity policy, and a labelled legend -- applied with one `apply_data_style` call. The demo fields are synthetic (cleopatra takes NumPy arrays in and returns matplotlib artists out; it never reads files), scaled so every colour band shows."
]
},
{
"cell_type": "markdown",
"id": "365d3c09",
"metadata": {},
"source": [
"## Setup\n",
"\n",
"Import the one-call styler and the preset registry, and build a small synthetic-field helper."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f34615e5",
"metadata": {
"execution": {
"iopub.execute_input": "2026-08-09T20:52:57.106560Z",
"iopub.status.busy": "2026-08-09T20:52:57.106560Z",
"iopub.status.idle": "2026-08-09T20:52:57.663084Z",
"shell.execute_reply": "2026-08-09T20:52:57.661995Z"
}
},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"\n",
"from cleopatra.styling.colors import apply_data_style, DATA_STYLES\n",
"\n",
"\n",
"def demo_field(name, shape=(90, 140)):\n",
" \"\"\"A smooth synthetic field whose values span the preset's own colour bands.\n",
"\n",
" We build a couple of Gaussian bumps, normalise to 0..1, then map that\n",
" through the preset's `levels` so every discrete band actually shows.\n",
" \"\"\"\n",
" layer = DATA_STYLES[name][name]\n",
" levels = layer.get(\"levels\")\n",
" ny, nx = shape\n",
" yy, xx = np.mgrid[0:ny, 0:nx]\n",
"\n",
" def blob(cx, cy, sx, sy):\n",
" return np.exp(-(((xx - cx) / sx) ** 2 + ((yy - cy) / sy) ** 2))\n",
"\n",
" field = 0.25 * blob(40, 28, 30, 20) + 0.7 * blob(96, 58, 26, 30) + 0.5 * blob(70, 74, 42, 16)\n",
" field = (field - field.min()) / (field.max() - field.min())\n",
" if levels is None:\n",
" return field\n",
" return np.interp(field, np.linspace(0.0, 1.0, len(levels)), levels)\n",
"\n",
"\n",
"def gallery(names, ncols=3):\n",
" \"\"\"Render each named preset on its own demo field in a grid (no legend).\"\"\"\n",
" nrows = int(np.ceil(len(names) / ncols))\n",
" fig, axes = plt.subplots(nrows, ncols, figsize=(4.0 * ncols, 3.0 * nrows))\n",
" axes = np.atleast_1d(axes).ravel()\n",
" for ax, name in zip(axes, names):\n",
" apply_data_style(ax, {name: demo_field(name)}, style=name, legend=False)\n",
" label = DATA_STYLES[name][name][\"label\"]\n",
" ax.set_title(f\"{name}\\n{label}\", loc=\"left\", fontsize=8)\n",
" ax.set_xticks([])\n",
" ax.set_yticks([])\n",
" for ax in axes[len(names):]:\n",
" ax.set_visible(False)\n",
" fig.tight_layout()\n",
" plt.show()\n"
]
},
{
"cell_type": "markdown",
"id": "bb56c492",
"metadata": {},
"source": [
"## The preset gallery\n",
"\n",
"Every preset in this group on its own demo field."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0b27b848",
"metadata": {
"execution": {
"iopub.execute_input": "2026-08-09T20:52:57.666902Z",
"iopub.status.busy": "2026-08-09T20:52:57.666114Z",
"iopub.status.idle": "2026-08-09T20:52:58.100868Z",
"shell.execute_reply": "2026-08-09T20:52:58.100868Z"
}
},
"outputs": [],
"source": [
"gallery(['aerosol_optical_depth_550nm', 'dust_aerosol_optical_depth_550nm', 'sulphate_aerosol_optical_depth_550nm', 'particulate_matter_10um', 'particulate_matter_2p5um'])"
]
},
{
"cell_type": "markdown",
"id": "9ce84577",
"metadata": {},
"source": [
"## One preset in detail\n",
"\n",
"The full styled output, this time with the labelled band legend `apply_data_style` draws by default."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "114ed593",
"metadata": {
"execution": {
"iopub.execute_input": "2026-08-09T20:52:58.102877Z",
"iopub.status.busy": "2026-08-09T20:52:58.102877Z",
"iopub.status.idle": "2026-08-09T20:52:58.205693Z",
"shell.execute_reply": "2026-08-09T20:52:58.205146Z"
}
},
"outputs": [],
"source": [
"name = \"aerosol_optical_depth_550nm\"\n",
"label = DATA_STYLES[name][name][\"label\"]\n",
"fig, ax = plt.subplots(figsize=(7.5, 4.5))\n",
"apply_data_style(ax, {name: demo_field(name)}, style=name)\n",
"ax.set_title(f'style=\"{name}\" ({label})', loc=\"left\")\n",
"ax.set_xticks([])\n",
"ax.set_yticks([])\n",
"plt.show()\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading