Releases: serapeum-org/cleopatra
Release list
0.34.0
- feat(glyphs): add directional lighting to TexturedGlobeGlyph (#320)
- Add a sun unit vector and an ambient floor so the globe can be lit from a
direction, shading a lambertian day/night terminator instead of reading as
evenly illuminated. sun=None (the default) renders byte-identical to 0.33.0. -
- Accept sun/ambient on init and override per call on draw/animate
(mirroring spin, via an inherit sentinel so sun=None can disable lighting
for one call); sun is world-space (+z up/north, +x toward the viewer at
spin=0), auto-normalized to unit length; ambient must be in [0, 1].
- Accept sun/ambient on init and override per call on draw/animate
- Apply lighting per frame from the already-rotated vertices (one dot product
over the mesh, whose unit-sphere positions are the surface normals), scaling
a copy of the cached facecolors by
ambient + (1 - ambient) * clip(dot(normal, sun), 0, 1). The facecolors cache
is never mutated and the texture is never re-sampled, so the
sample-once/rotate-per-frame contract holds and a fixed sun sweeps the
terminator as the globe spins; alpha is preserved. - Validate sun/ambient eagerly on both draw and animate; reject non-1-D or
zero sun vectors and out-of-range ambient. - Add lighting tests (byte-identical sun=None, terminator + ambient floor,
lit fraction tracks spin, cache untouched, world-space sun under tilt,
validation) and a reference-doc example. - Closes #319
0.33.0
-
feat(animation): derive a GIF from an existing video and fix the clip palette (#317)
-
The shared GIF palette was chosen by pixel population, so on a clip with a
large textured area the background claimed nearly the whole table and small
saturated marks collapsed to the nearest muddy neighbour. It is now chosen
for colour coverage over the set of colours the clip contains, collected at
full 8-bit precision, so a mark survives however few pixels it covers.- Add gif_from_video, deriving a GIF from a video already on disk so the
frames are rendered once and every other format read back off that file - Build the palette from a colour census rather than a spatial downsample,
which blended one-pixel marks away before the quantiser could see them - Share build_clip_palette and quantize_to_palette between the rendered and
derived paths so both quantise identically - Stream the video in two passes instead of buffering it, keeping the
decoded RGB frames from ever being resident together - Warn when the source is chroma-subsampled, since that loss precedes the
palette and no quantiser can undo it - Add quantize_method for clips better served by a population-weighted split
- Validate writer inputs, close the decoder, resolve ffmpeg the same way for
reading as for writing, and classify pixel formats by family - Document the real memory cost: Pillow accumulates the quantised frames, so
peak stays proportional to the clip's length - Record in SCOPE.md why re-encoding cleopatra's own animation output is in
scope, and what still is not
- Add gif_from_video, deriving a GIF from a video already on disk so the
-
feat(styling): add stamp_mark figure watermark / brand-mark helper (#314)
-
Stamp a logo/watermark image onto a matplotlib Figure with one call,
sized as a fraction of the figure so it stays proportional across the
dpis a figure is exported at (MP4 master, web copy, GIF). -
- Draw on a frameless inset axes in figure-fraction coordinates (the
dpi-independent counterpart of Figure.figimage).fracsizes the
mark's longer side, so it is never distorted and always fits, in any
of the four corners;marginis a scalar or an (x, y) pair.
- Draw on a frameless inset axes in figure-fraction coordinates (the
-
Optionally composite a centred, gaussian-blurred halo behind the mark
(alpha-over into a single axes) so it separates from a busy or dark
canvas;bluris a fraction of the mark's unpadded width, and the
axes is grown so the mark's own painted extent still equalsfrac. -
Accept a file path (via Pillow) or an in-memory RGB/RGBA array (uint8
0-255 or float 0-1); reject out-of-contract inputs -- bad shape, a
non-uint8 non-float dtype, a float outside [0, 1] or with NaN/inf, a
zero-size image, or a margin that pushes the mark off-canvas -- with
clear ValueErrors. -
No new dependency (Pillow is already a base dependency; no SciPy).
-
Add a docs reference page and a SCOPE.md note that reading a
presentation asset (a logo, not user data) is an allowed exception. -
Closes #312
-
feat(glyphs): add TexturedGlobeGlyph for 3-D textured globes (#316)
-
Add cleopatra's first 3-D glyph: wrap an equirectangular (lon/lat)
RGB(A) texture onto a tilted, spinnable sphere on a matplotlib Axes3D.- Take an (H, W, 3)/(H, W, 4) equirectangular array (the north-up
layout of basemap.reference.relief()) and return (fig, Axes3D); a
standalone class like HistogramGlyph, not a Glyph subclass. - draw(spin=...) rotates the globe about its fixed tilted polar axis by
rotating only the once-sampled mesh; animate() returns a FuncAnimation
of a full rotation. - Normalize textures by dtype: integer by dtype max, float by their own
peak only when a channel exceeds 1 (RGB only, so alpha is preserved);
NaN and negative cells render black; unknown render options raise
ValueError. - Add no dependency (mpl_toolkits.mplot3d ships with matplotlib);
default mesh 180x90, with the quadratic render cost documented. - Add a full test suite (100% line + branch) and a reference doc page.
Closes #311
- Take an (H, W, 3)/(H, W, 4) equirectangular array (the north-up
-
feat(basemap): add world_texture() and mercator_to_equirectangular (#313)
-
Port the two generic basemap helpers from the earthlens satellite
showcase notebook's inline basemap_texture into cleopatra.basemap.tiles. -
- mercator_to_equirectangular(mosaic, bounds, n_lon, n_lat): a
pure-NumPy area-averaging resample of a Web Mercator (EPSG:3857) tile
mosaic onto an equirectangular lon/lat grid via np.add.reduceat. It
reads the mosaic's own 3857 bounds and sizes each cell's divisor from
the actual reduceat block widths so the poles do not seam. Returns
float32 in the input value scale; the output always spans the globe,
clamping out-of-coverage edges. No network, Pillow, or pyproj needed.
- mercator_to_equirectangular(mosaic, bounds, n_lon, n_lat): a
-
world_texture(provider, *, zoom, n_lon, n_lat, cache, ...): the XYZ
analogue of reference.relief -- fetches the whole 2**zoom world tile
grid (zoom capped at 6), stitches, reprojects, and returns an
(n_lat, n_lon, 3) float32 texture in [0, 1]. Accepts a provider name
or a resolved TileProvider. Caches the texture under
Config.get_cache_dir() with a guarded read that rebuilds a corrupt
file and an atomic mkstemp write. Requires the [tiles] extra. -
The earthlens-specific two-tone / ocean-land recolour stays in the
notebook.
0.32.0
-
perf(array_glyph): count domain cells without materialising a per-cell index list (#305)
-
Replace
len(get_indices2(frame, [np.nan])), which built one Python tuple
per cell, with a pure-numpy, mask-aware reduction extracted into
ArrayGlyph._count_domain_cells. -
- fixes the MemoryError when building a 4-D
(n, h, w, 3)RGB animation
stack: the oldlen(shape) == 3frame pick passed the whole stack to
get_indices2(~15 GB tuple list); the reduction is O(1) in Python
objects and ~3000x faster on large frames
- fixes the MemoryError when building a 4-D
-
count a stack on frame 0 and a single frame (2-D, or an
(h, w, 3)RGB
image fromrgb_bands) whole, usingself.rgbto disambiguate the two
3-D shapes and fix a lone RGB image counting only its first row -
keep the mask term so
exclude_value-masked cells stay excluded,
byte-equivalent to the oldget_indices2(a plain~np.isnanwould
over-count masked cells) -
add regression tests (4-D, 3-D multi-frame, zero-domain, masked 2-D and
masked stack, single RGB image, integer dtype) and clarify the
num_domain_cellsdocstring -
Closes #304
-
fix(styling): drop pandas & numpy null sentinels in categorize (#303)
-
categorize's null filter recognised only None and np.nan, so pandas'
pd.NA / pd.NaT (and, on some numpy builds, datetime64('NaT')) survived
and became their own colour category -- making categorisation depend on
the column dtype. Treat every null flavour uniformly so the category set
depends only on the distinct real values (the in-glyph categorical path
delegates to categorize and inherits the fix).- Drop pd.NA / pd.NaT via pandas' scalar pd.isna, and datetime64('NaT')
via np.isnan or np.isnat. - Keep pandas an undeclared soft dependency: import pd.isna lazily and
once (not per element), degrading to the numpy-only path (np.isnat for
datetime NaT) when pandas is absent -- no new runtime dependency. - Short-circuit str/bytes and guard np.isnan / pd.isna against array-like
object-array elements (e.g. range) so odd contents are carried through
as non-null rather than crashing, matching base behaviour. - Extract the check to a module-level _categorical_is_null helper to keep
categorize's cognitive complexity within bounds. - Add tests (pd.NA/pd.NaT, no-pandas fallback, all-null error, mixed null
kinds, numpy datetime NaT incl. the forced np.isnat path, genuine
pandas nullable dtypes, array-like element) and refresh the categorize
/ _is_null docstrings.
Closes #302
- Drop pd.NA / pd.NaT via pandas' scalar pd.isna, and datetime64('NaT')
0.31.0
- chore: repo housekeeping and docs refresh for the grouped-parameter API (#298)
-
- untrack the maintainer-only tools/build_.py scripts (kept on disk)
and broaden the ignore rule to tools/.py
- untrack the maintainer-only tools/build_.py scripts (kept on disk)
- remove nbqa's leaked *_nbqa_ipynb.py temp files and ignore the pattern
- refresh the docs and README for the grouped render-parameter API: fix
examples that used removed loose keywords, add a Render-options
reference page, complete the migration guide, and fix a broken link - rephrase historical change-log entries to drop vendored-source and
library names - Closes #299, #300
- refactor(glyphs)!: move render/prep logic onto the grouped parameter objects (#291)
- Cohere the grouped rendering/prep objects with the logic that consumes
their fields (the ColorScaling.build_norm / to_options model): the object
owns the transform, the glyph just calls it. Also groups ArrayGlyph's
loose RGB band-prep keywords into a new RgbBands object. -
- ColorBar owns to_options(), resolve(), reset_options(), and
specifies_placement(); _resolve_colorbar just delegates
- ColorBar owns to_options(), resolve(), reset_options(), and
- PointOverlay.draw(), FrameLabel.resolve_location()/draw(), and
PanelLabels.label_for()/panel_title()/validate() replace the inline
plot/animate/facet logic; base Glyph._plot_point_values is removed - add DataStyle.for_apply_style() and unify the hillshade "unset"
sentinel, deleting three per-glyph duplicates - add Glyph._snapshot_group_options() for the shared pre-merge
option-snapshot (ArrayGlyph.plot and KDEGlyph.plot) - group ArrayGlyph's rgb / surface_reflectance / cutoff / percentile
constructor keywords into an RgbBands object owning validate() /
prepare(); init drops from 10 to 7 explicit params - fix the RGB surface-reflectance cutoff to clip each band's data, not
the integer band index - add unit tests + doctest examples for the new methods; migrate the
examples, the array_glyph notebook, 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. prepare_array() and scale_percentile() keep
their loose keyword signatures. - Closes #292, #293, #294, #295, #296, #297
0.30.0
-
feat(styling)!: grouped render parameters and a large vendored preset-library expansion (#275)
-
Complete the migration of ArrayGlyph/MeshGlyph plot/animate/facet from
flat keyword arguments to typed group objects, and substantially expand
the vendored preset library.- Finish the grouped-parameter render API: data_style=DataStyle(...),
color=ColorScaling(...), contour=Contour(...), cells=CellValues(...),
classify=Classify(...); remove the legacy-keyword shims so a moved
keyword now raises with a pointer to its group object. - Widen per-call styled-preset overrides (bands/alpha/alpha_range) onto
DataStyle: sticky across calls on a reused glyph, with correct opacity
mode-switch and clearing. - Vendor ~200 presets across seven libraries: 39 perceptually-uniform
scientific colour maps, 11 radar/satellite tables, 3 hypsometric
terrain ramps, and the weather library grown to 112 (+28 CAMS
atmospheric-composition and operational fields). - Add an optional [science-colors] extra for namespaced colour maps
(cmocean:thermal, ...) via the numpy-only cmap aggregator; no new
runtime dependency. - Add gallery notebooks for the new preset sets, committed output-free.
- Treat vendored colour data as derived under neutral-source naming; keep
the maintainer download scripts local-only.
BREAKING CHANGE: the legacy flat render keywords on ArrayGlyph/MeshGlyph
plot/animate/facet are removed. Pass them through the group objects
(DataStyle, ColorScaling, Contour, CellValues, Classify) instead; a
removed keyword now raises with a pointer to its group. - Finish the grouped-parameter render API: data_style=DataStyle(...),
-
refactor(glyphs)!: group plot/animate parameters into typed objects (#287)
-
Replace the loose **kwargs surface on every glyph's plot() / animate()
with a small set of discoverable, typed parameter objects, and remove the
backward-compatibility shims that kept the old loose keywords working.- bundle the loose plot() / animate() keywords into grouped objects:
color=ColorScaling, contour=Contour, cells=CellValues,
classify=Classify, data_style=DataStyle, points=PointOverlay,
frame_label=FrameLabel, colorbar=ColorBar - group facet's per-panel coordinate labels into PanelLabels and rename
facet's figsize to figure_size - remove the legacy loose-kwarg shims; a removed keyword now raises with a
pointer to its group object - rename text_colors to cell_value_text_colors and no_elem to
num_domain_cells
BREAKING CHANGE: the loose plot / animate styling keywords (point_color /
point_size / point_label_color / point_label_size / pid_color / pid_size,
label_location / label_color / text_loc, text_colors, col_coords /
row_coords, and facet's figsize) are removed and now raise; pass the
matching group object instead (PointOverlay, FrameLabel,
cell_value_text_colors, labels=PanelLabels, figure_size). ArrayGlyph.no_elem
is renamed to num_domain_cells, and a bare array for points must be wrapped
in a PointOverlay. - bundle the loose plot() / animate() keywords into grouped objects:
-
refactor(glyphs)!: group plot/animate parameters into typed objects (#274)
-
Replace the flat ~30 keyword surface on every glyph's plot()/animate()
with a small set of discoverable, typed parameter objects, and remove
the backward-compatible shims that kept the old keywords working. -
- add ColorScaling owning norm/colorbar construction, with variants
linear/power/sym_log/boundary/midpoint
- add ColorScaling owning norm/colorbar construction, with variants
-
add Contour, CellValues, DataStyle, and Classify group objects in
styling/params.py, each emitting only set fields via to_options() -
add PointOverlay and FrameLabel for point overlays and animation
frame labels -
group facet parameters into PanelLabels; rename facet figsize to
figure_size -
add base group-merge infrastructure (_merge_group_params,
_reject_grouped_kwargs, _rollback_options_on_error): loose grouped
kwargs raise with a pointer, failed styled plots roll back -
remove all deprecation shims; rename text_colors to
cell_value_text_colors and no_elem to num_domain_cells -
reject conflicting style and data_style in templates.publication_map
-
add a migration guide and wire it into the mkdocs nav
-
BREAKING CHANGE: the flat styling keywords (color_scale, gamma, bounds,
midpoint, line_threshold, line_scale, levels, labels, display_cell_value,
num_size, background_color_threshold, style, hillshade, scheme, k,
point_color, point_size, point_label_color, point_label_size,
label_location, label_color, text_loc) are removed; pass the matching
group object instead. text_colors is renamed to cell_value_text_colors,
no_elem to num_domain_cells, and facet's figsize to figure_size. Passing
a bare array as points no longer works; wrap it in a PointOverlay. -
feat(geo): add a relief backdrop to the ecmwf-dark reference map (#273)
-
add_reference_map now draws a dimmed hypsometric relief backdrop beneath
the data when the resolved preset carries a "relief" entry -- present on
ecmwf-dark, absent on the chrome-only ecmwf. The relief config accepts a
resolution string, an add_relief kwargs dict, or True (mirroring the
sibling _draw_basemap), and defaults crs to self.crs via _basemap_kwargs
so it warps to match non-EPSG:4326 data. -
The backdrop is skipped when the axes are not georeferenced, and an
environmental relief failure -- missing Pillow (the [tiles] extra), an
offline/uncached fetch, or a corrupt cache -- degrades with a warning
while the coastline/border chrome still draws, so the chrome never
hard-depends on the relief. A bad relief resolution in a custom preset
still raises loudly. The per-call resolution= knob affects only the
features, not the relief. -
Closes #216
-
feat(config): add Config.get_cache_dir for the basemap cache directory (#272)
-
Surface CLEOPATRA_CACHE_DIR — previously a bare os.environ read buried in
basemap/reference._cache_dir — as a single, discoverable Config method,
so it lives alongside set_matplotlib_backend where users look for
configuration. -
- resolve in order: a non-empty explicit path argument, then the
CLEOPATRA_CACHE_DIR environment variable, then the default
~/.cleopatra/naturalearth; a leading ~ is expanded
- resolve in order: a non-empty explicit path argument, then the
-
treat any value that is None, empty, or whitespace-only (for both the
argument and the env var) as not provided, so get_cache_dir("") and
get_cache_dir(" ") behave like get_cache_dir(); Path("") is Path(".")
under pathlib and resolves to the current directory, as documented -
keep it a pure getter that only resolves the path; reference._cache_dir
delegates to it and retains the create-on-use mkdir, so config stays the
leaf owner of the setting and reference remains its sole consumer -
expand ~ in the env var, fixing a latent bug where CLEOPATRA_CACHE_DIR=
/foo created a literal .//foo directory -
add unit tests (14 get_cache_dir scenarios, including the Path("") and
whitespace edges), a reference delegation/creation test, and a hermetic
doctest runner; document get_cache_dir on the config reference page -
Closes #253
-
feat(array_glyp...
0.29.0
-
feat(glyphs): accept colorbar=ColorBar on every glyph, plus label_location validation and colorbar=True reset (#244)
-
Follow-ups to the ColorBar spec (#234/#235) that make it work package-wide
and harden two edges.- Move ColorBar, _resolve_colorbar, _swatch_text_default,
_warn_deprecated_cbar_kwargs, and _DEPRECATED_CBAR_KWARGS into a lean,
glyph-independent cleopatra.colorbar module, re-exported from array_glyph
for back-compat. - Wire a colorbar: bool | ColorBar | None parameter into MeshGlyph (plot and
animate), FlowGlyph, KDEGlyph, ScatterGlyph, PolygonGlyph, and VectorGlyph;
each merges the resolved spec into its options and deprecates the loose
cbar_* kwargs. Non-breaking: existing add_colorbar params and MeshGlyph's
colorbar bool toggle keep working, and the merged spec is sticky like
ArrayGlyph (colorbar=True resets, False suppresses). - Reject an orientation-incompatible label_location up front (and at render,
against the resolved orientation, for the unpinned case) instead of crashing
in matplotlib; drop the invalid baseline/center_baseline label positions. - Make colorbar=True reset the whole caption/sizing cbar_* family to defaults
on a reused glyph, and honour a ColorBar(ticks_spacing=...) spec on
MeshGlyph rather than auto-overwriting it.
- Move ColorBar, _resolve_colorbar, _swatch_text_default,
0.28.0
-
feat(array_glyph): add ColorBar.orientation and deprecate the loose cbar_orientation kwarg (#240)
-
Give ColorBar a typed orientation field so the colorbar orientation can be
set through colorbar=ColorBar(...) instead of the loose cbar_orientation
kwarg, and resolve the silent-override footgun behind #235. -
- Map ColorBar.orientation onto cbar_orientation only when set; deprecate
the loose cbar_orientation kwarg (it still works but warns).
- Map ColorBar.orientation onto cbar_orientation only when set; deprecate
-
Warn at construction when an explicit orientation disagrees with a set
location (location wins at render); validate orientation up front and at
render so a typo raises an actionable error, not an opaque matplotlib one. -
Fix a crash: a horizontal inset colorbar with no location now derives its
inset edge from the resolved orientation instead of the vertical layout. -
Reset a sticky orientation on colorbar=True, and draw a real colorbar for
an orientation-only spec over a style preset instead of dropping it. -
Migrate the docstrings, numpydoc blocks, a plot example, and the tutorial
notebook to the typed form, and extract the shared plot/animate
kwargs+colorbar setup to remove duplication. -
Closes #235
-
feat(array_glyph): complete the ColorBar spec with caption and sizing fields (#237)
-
Add six caption/sizing fields to ColorBar (label, length, label_size,
label_rotation, label_location, ticks_spacing) and map them through
resolve_colorbar onto the internal cbar* keys, but only when set, so a
loose cbar_* value survives during the transition. -
- Wire cbar_label_rotation into the colorbar label and honour
ColorBar.length for inside (inset) colorbars; both were silent no-ops.
- Wire cbar_label_rotation into the colorbar label and honour
-
Stop the auto-computed tick spacing from clobbering a
ColorBar(ticks_spacing=...) value in plot() and animate(). -
Deprecate the loose cbar_* and ticks_spacing kwargs in favour of
colorbar=ColorBar(...) via a DeprecationWarning; they still take
effect during the deprecation window. -
Migrate the plot/animate docstrings, the ArrayGlyph tutorial
notebooks, and the tests to the typed spec; add render-level tests. -
Use numpy.random.Generator in the migrated notebook example
(SonarCloud S6711). -
Closes #234
-
refactor(tiles): replace the mercantile dependency with built-in tile math (#236)
-
- Add a Tile NamedTuple plus _tiles_for_bbox()/_tile_xy_bounds()/
_lonlat_to_tile_xy(), a direct port of mercantile's tile(),
tiles(), and xy_bounds() (the only three things cleopatra.tiles
used from it), verified formula-for-formula against mercantile's
own installed source. - Drop mercantile from the [tiles] extra, uv.lock, and docs/README;
update tests/test_tiles.py to mock the new internal function
instead of patching the mercantile module. - Clamp both latitude bounds symmetrically (north was capped but
south only floored, and vice versa) and nudge sin(lat) away from
the exact +/-90 singularity, closing a crash on any bbox whose
north or south sits within ~1e-7 degrees of a pole. - Split an antimeridian-crossing bbox (west > east) into its two
dateline-side sub-boxes instead of raising, matching
mercantile.tiles()'s own behavior -- needed because reprojecting a
near-global Web Mercator extent to EPSG:4326 wraps longitude at
the +/-180 seam, so this is a real, reachable input through
add_tiles(), not just a hand-crafted edge case. - Add 30+ direct unit tests for the tile-math functions and Tile's
NamedTuple behavior, with expected values cross-checked against
the real mercantile package, plus an end-to-end add_tiles() test
exercising the reprojection-driven antimeridian wraparound. - Fix a tautological-looking test assertion (same expression on
both sides of ==) flagged by SonarCloud.
No public API changes; add_tiles/fetch_tiles/stitch_tiles/get_provider
keep their existing signatures and behavior.
Closes #238 - Add a Tile NamedTuple plus _tiles_for_bbox()/_tile_xy_bounds()/
-
ci(docs): resolve release tag for workflow_run-triggered mkdocs deploys (#233)
-
deploy-release hardcoded trigger: 'release' but never supplied a
release-tag, so mkdocs-deploy could only resolve the version from
github.event.release.tag_name -- populated only on a real release
event. This job normally runs via workflow_run instead, so that
payload is absent and the deploy fails outright, as it did for the
0.27.0 release. -
- Check out the branch the release actually ran on
(workflow_run.head_branch) instead of the default trigger ref
- Check out the branch the release actually ran on
-
Extract the version from the just-bumped pyproject.toml as a
fallback source for the release tag -
Pass release-tag with a fallback: release event payload first,
extracted version otherwise -
Guard the workflow_run branch of the job condition against forks
-
Mirrors the existing working implementation in pyramids.
-
ci(docs): resolve the release tag for workflow_run-triggered deploys
-
deploy-release hardcoded trigger: 'release' but never supplied a
release-tag, so mkdocs-deploy could only resolve the version from
github.event.release.tag_name -- which is only populated on a real
release event. Since this job is normally reached via workflow_run
(github-release completing), that payload is absent and the job
fails outright, as it did for the 0.27.0 release. -
Mirror the fix already used in pyramids: check out the branch the
release actually ran on instead of defaulting to the trigger ref,
extract the version straight from the just-bumped pyproject.toml,
and fall back to it whenever the release event payload isn't
available. Also guard the workflow_run branch of the job condition
against forks, matching pyramids.
0.27.0
- ci: pin workflow action versions and tighten CI concurrency (#231)
- Harden the GitHub Actions workflows by resolving every action
reference to an explicit, verified version instead of a moving
target, and stop wasted CI time on superseded pull request pushes. -
- Bump actions/checkout (v5 -> v7.0.1) and codecov/codecov-action
(v5 -> v7.0.0), both two majors behind
- Bump actions/checkout (v5 -> v7.0.1) and codecov/codecov-action
- Pin every serapeum-org/github-actions composite action to a
specific released version instead of the floating v1 major tag - Replace every tag reference with the full commit SHA it resolves
to, keeping the version as a trailing comment - Fix mkdocs-deploy using an unpinned @main ref in two of its three
jobs while the third used a pinned tag - Pin the uv version installed in tests.yml and github-release.yml
to 0.12.1 to match the local dev environment - Group tests.yml by PR number with cancel-in-progress so a new
push cancels its own stale run, while push events to main are
grouped by commit SHA so they always run to completion - feat!: add a perceptual palette system and richer plotting controls (#220)
-
- perceptual: a numpy-only sRGB<->CIELAB toolkit -- interp_perceptual /
perceptual_colormap (exact endpoints preserved), make_diverging
(two lightness-balanced Lab arms), make_categorical (the glasbey
max-min method) and a perceptual_uniformity diagnostic
- perceptual: a numpy-only sRGB<->CIELAB toolkit -- interp_perceptual /
- palettes: one Palette record + PaletteKind registry with a
kind-driven default_norm and preview_palettes(); the haze / CAMS-AOD
/ flame families now build in CIELAB and register at import - colors: ECMWF/cmocean ocean & weather data-style presets, continuous
ramps re-interpolated in CIELAB, and a defaults < preset < explicit
style precedence - array_glyph/glyph: a ColorBar spec (location / inside / box /
label_color / tick_color) replacing the separate cbar_* kwargs, a
FrameLabel with its own size, and a full_bleed chrome-free layout - geo: a basemap= parameter with typed Basemap / Feature specs,
CRS-aware relief warping, and an opt-in mis-georeference check - animation: one shared GIF palette across frames with pure black and
white reserved, so date labels and colorbar ticks stay crisp - tooling: a ruff / mypy / bandit lint stack, http(s)-only urlopen and
a path-traversal guard in the asset builders, and NumPy-only example
notebooks - Closes #218, #219, #225, #226, #227, #228, #229, #230
Refs #216
BREAKING CHANGE: the weather/ocean data-style preset keys were renamed
from GRIB shortNames to descriptive names; update any code that looks
up presets by the old keys. - refactor(data)!: restructure the ECMWF weather/ocean preset system and rename its keys (#217)
-
- Merge the Magics and earthkit-plots preset libraries into one
weather_presets.json with a single loader, and rename every weather
preset key from a raw GRIB shortName to a descriptive slug (e.g.
2t -> temperature_2m).
- Merge the Magics and earthkit-plots preset libraries into one
- Drop dead per-vendor metadata (_meta, source_style,
continuous_colormap) never read by any loader, and fix a silent bug
where colors.py pointed at the pre-rename ocean presets file. - Migrate lint/type tooling to Ruff, add a mypy hook, and fix the 194
pre-existing type errors it surfaced. - Close bandit and SonarCloud security findings (restrict urlopen to
http(s), guard build-script output paths against path traversal,
parse URL schemes properly) without suppressing any of them. - Serialize the mkdocs deploy-pr/deploy-main jobs to stop them racing
gh-pages pushes. - BREAKING CHANGE: DATA_STYLES weather presets are keyed by descriptive
names now, not GRIB shortNames (e.g. "temperature_2m" instead of "2t").
Closes #221, #222, #223, #224 - feat(reference): make add_relief honor the axis CRS (#214)
- add_relief now respects the CRS of the data on the axis, so the relief
lines up under the plot instead of being stretched or misprojected. -
- Crop a lon/lat extent that lies within the global bounds out of the
relief array and draw it at that box, instead of stretching the whole
globe into it (the issue #177 footgun). extent=None and out-of-bounds
extents keep the previous whole-image placement.
- Crop a lon/lat extent that lies within the global bounds out of the
- Add a keyword-only crs= parameter. A None or EPSG:4326 axis runs the
lon/lat path unchanged, with no pyproj import; any other CRS warps the
global relief into the axis CRS -- an output grid over the axis view is
inverse-transformed to lon/lat via pyproj, sampled per pixel, and cells
outside the CRS domain are left transparent. - GeoMixin.add_relief defaults crs to self.crs (via _basemap_kwargs),
matching add_features and add_tiles; an explicit crs= still wins and an
unset self.crs preserves the prior behaviour. - No new dependencies (pyproj already ships in the [tiles] extra) and no
GDAL. - Closes #177
0.26.1
-
fix(glyphs): remove orphaned render artists on repeated plot()/animate() calls (#211)
-
- Track each Axes' prior render artists via a shared marker so a second
plot()/animate() call — same glyph instance, or a different glyph
sharing the Axes viaax=/fig=— removes them instead of leaving
them attached and undriven. - Extend cleanup to ArrayGlyph, MeshGlyph, StatisticalGlyph, and
VectorGlyph: colorbars, frame-label text, point/cell-value overlays,
and streamplot arrowheads (never actually attached via the returned
collection, so removed by diffing ax.patches instead). - Defer cleanup until after each call's own input validation succeeds,
so a failed call (e.g. an invalid color_scale, or a mismatched color
list) no longer destroys a valid prior render before propagating its
exception. - Tolerate an artist already partially removed by ax.clear() or a
prior apply_style() call instead of crashing on the second removal
attempt. - Add direct unit tests for the shared cleanup helpers and regression
tests covering same-instance repeats, cross-glyph shared axes, and
validation-failure paths across all four glyph classes.
Closes #210
- Track each Axes' prior render artists via a shared marker so a second
0.26.0
-
refactor(array_glyph)!: clean up plot()/animate()'s kwarg API (#207)
-
- Rename pid_color/pid_size to point_label_color/point_label_size,
animate()'s text_colors to cell_value_text_colors, and text_loc to
label_location - Bundle the five point-overlay parameters into a new PointOverlay
class and animate()'s two frame-label parameters into a new
FrameLabel class - Type the remaining **kwargs on both methods via TypedDict + Unpack
(PEP 692), inert at runtime - Keep every removed name/shape working via **kwargs behind a
DeprecationWarning, resolved before the strict kwargs validation - Fix bugs found during two rounds of adversarial review: a silent
positional-arg drop, wrong warning stacklevels, a both-given
conflict false-negative, a missing precision field, and a crash on
deprecated point kwargs passed without points - Update the example notebooks to the new PointOverlay/FrameLabel API
BREAKING CHANGE: point_color, point_size, pid_color, pid_size, and
animate()'s label_color are no longer explicit parameters. Keyword
calls still work via a deprecated alias with a warning; positional
calls to these slots now bind to the wrong parameter or raise
TypeError.
Closes #208 - Rename pid_color/pid_size to point_label_color/point_label_size,
-
feat(styles,glyphs): distinct-value categorical colouring for PolygonGlyph/ScatterGlyph (#206)
-
Add styles.categorize(values, cmap="tab10") -> (categories, colors), the
distinct-value counterpart to classify(): one colour per unique value,
sorted when sortable, cycling past the cmap's size, nulls dropped.Wire a "categorical" scheme into the shared Glyph scalar-mapping
pipeline: _prepare_categorical_mapping builds a ListedColormap +
BoundaryNorm over per-element integer class codes, and
create_categorical_legend draws a disjoint_legend in place of a
colorbar. PolygonGlyph and ScatterGlyph opt in via
_SUPPORTS_CATEGORICAL_SCHEME; VectorGlyph/FlowGlyph still accept scheme
for continuous classification but reject "categorical" with a clear
error.- Validate the full values shape (not just its first dimension) in
PolygonGlyph, closing a silent colour-array mis-sizing bug - Reset cbar/category_legend unconditionally on every plot() call so a
scheme-switching re-plot never leaves a stale reference - Fall back to a qualitative cmap when the caller left cmap at the
glyph's own continuous default, matched by resolved name so a
Colormap object is caught the same as the equivalent string - Re-attach the categorical legend via ax.add_artist() before drawing
ScatterGlyph's size legend, since Axes.legend() is single-slot per
axes and would otherwise silently evict it - Add category_legend_kwargs (mirroring size_legend_kwargs) to
reposition/restyle the disjoint legend - categorize() raises the documented TypeError for non-hashable
entries and documents the int/bool/float dedup collision
Closes #204
- Validate the full values shape (not just its first dimension) in