Skip to content

Releases: serapeum-org/cleopatra

0.34.0

Choose a tag to compare

@github-actions github-actions released this 25 Aug 23:09
  • 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].
  • 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

Choose a tag to compare

@github-actions github-actions released this 25 Aug 13:15
  • 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

    Closes #315, #308

  • 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). frac sizes the
      mark's longer side, so it is never distorted and always fits, in any
      of the four corners; margin is a scalar or an (x, y) pair.
  • 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; blur is a fraction of the mark's unpadded width, and the
    axes is grown so the mark's own painted extent still equals frac.

  • 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

  • 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.
  • 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.

  • Closes #309, #310

0.32.0

Choose a tag to compare

@github-actions github-actions released this 17 Aug 21:44
  • 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 old len(shape) == 3 frame 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
  • count a stack on frame 0 and a single frame (2-D, or an (h, w, 3) RGB
    image from rgb_bands) whole, using self.rgb to 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 old get_indices2 (a plain ~np.isnan would
    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_cells docstring

  • 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

0.31.0

Choose a tag to compare

@github-actions github-actions released this 12 Aug 20:40
  • 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
  • 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
  • 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

Choose a tag to compare

@github-actions github-actions released this 11 Aug 17:22
  • 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.

  • Closes #288, #289

  • 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.

  • Closes #281, #283, #285

  • 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 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.

  • Closes #277, #278, #279, #280, #281, #282, #283, #284, #285

  • 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
  • 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...

Read more

0.29.0

Choose a tag to compare

@github-actions github-actions released this 03 Aug 17:47
  • 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.

    Closes #239, #241, #242

0.28.0

Choose a tag to compare

@github-actions github-actions released this 02 Aug 21:14
  • 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).
  • 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.
  • 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

  • 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
  • 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

Choose a tag to compare

@github-actions github-actions released this 01 Aug 22:07
  • 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
  • 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
  • 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).
  • 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.
  • 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

Choose a tag to compare

@github-actions github-actions released this 18 Jul 20:47
  • 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 via ax=/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

0.26.0

Choose a tag to compare

@github-actions github-actions released this 16 Jul 18:08
  • 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

  • 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