Source code for anyplotlib.plot2d._plotmesh

"""
plot2d/_plotmesh.py
===================
pcolormesh panel (non-uniform grid).
"""

from __future__ import annotations

import numpy as np

from anyplotlib.markers import MarkerRegistry
from anyplotlib.plot2d._plot2d import Plot2D
from anyplotlib._utils import (_normalize_image, _build_colormap_lut,
                               _resample_mesh, _codes_are_int)


[docs] class PlotMesh(Plot2D): """2-D mesh plot panel created by :meth:`Axes.pcolormesh`. Accepts cell *edge* arrays (length N+1 / M+1) rather than centre arrays, matches matplotlib's ``pcolormesh`` convention. Only ``'circles'`` and ``'lines'`` markers are supported. """ def __init__(self, data: np.ndarray, x_edges=None, y_edges=None, units: str = ""): data = np.asarray(data) if data.ndim != 2: raise ValueError(f"data must be 2-D (M x N), got {data.shape}") rows, cols = data.shape if x_edges is None: x_edges = np.arange(cols + 1, dtype=float) if y_edges is None: y_edges = np.arange(rows + 1, dtype=float) x_edges = np.asarray(x_edges, dtype=float) y_edges = np.asarray(y_edges, dtype=float) if len(x_edges) != cols + 1: raise ValueError( f"x_edges must have length {cols + 1} for {cols} columns, " f"got {len(x_edges)}") if len(y_edges) != rows + 1: raise ValueError( f"y_edges must have length {rows + 1} for {rows} rows, " f"got {len(y_edges)}") # Resample to a regular pixel grid for display resampled = _resample_mesh(data, x_edges, y_edges) # Use cell centres to initialise the parent (axes will be replaced) x_c = (x_edges[:-1] + x_edges[1:]) / 2.0 y_c = (y_edges[:-1] + y_edges[1:]) / 2.0 super().__init__(resampled, x_axis=x_c, y_axis=y_c, units=units) # Override mesh-specific state self._state["is_mesh"] = True self._state["has_axes"] = True # Store edges (not centres) so the JS renderer can place grid lines self._state["x_axis"] = x_edges.tolist() self._state["y_axis"] = y_edges.tolist() # Mesh panels have no fixed pixel scale self._state.pop("scale_x", None) self._state.pop("scale_y", None) # Restrict markers to circles + lines only self.markers = MarkerRegistry(self._push_markers, allowed=MarkerRegistry._KNOWN_MESH, transform_fn=lambda: self.transform) # ------------------------------------------------------------------ # Data # ------------------------------------------------------------------
[docs] def set_data(self, data: np.ndarray, x_edges=None, y_edges=None, units: str | None = None) -> None: """Replace the mesh data (and optionally the edge arrays).""" data = np.asarray(data) if data.ndim != 2: raise ValueError(f"data must be 2-D, got {data.shape}") rows, cols = data.shape cur_xe = np.asarray(self._state["x_axis"], dtype=float) cur_ye = np.asarray(self._state["y_axis"], dtype=float) xe = np.asarray(x_edges, dtype=float) if x_edges is not None else cur_xe ye = np.asarray(y_edges, dtype=float) if y_edges is not None else cur_ye if len(xe) != cols + 1: raise ValueError(f"x_edges must have length {cols + 1}") if len(ye) != rows + 1: raise ValueError(f"y_edges must have length {rows + 1}") resampled = _resample_mesh(data, xe, ye) img_u8, vmin, vmax = _normalize_image(resampled) self._raw_u8, self._raw_vmin, self._raw_vmax = img_u8, vmin, vmax # The resampled grid IS the displayed frame (one texel per cell), so it is # what `.data` reads back and what the hover probe looks values up in — # leaving the previous frame here would answer both with stale values. self._data = resampled self._state.update({ "image_b64": self._encode_pixels("image_b64", img_u8), "image_width": cols, "image_height": rows, "x_axis": xe.tolist(), "y_axis": ye.tolist(), "display_min": vmin, "display_max": vmax, "raw_min": vmin, "raw_max": vmax, "raw_is_int": _codes_are_int(resampled), "colormap_data": _build_colormap_lut(self._state["colormap_name"]), # A new frame invalidates any exact value answered for the old one. "probe_x": None, "probe_y": None, "probe_value": None, }) if units is not None: self._state["units"] = units self._push()
def __repr__(self) -> str: xe = self._state.get("x_axis", []) ye = self._state.get("y_axis", []) cols = max(0, len(xe) - 1) rows = max(0, len(ye) - 1) cmap = self._state.get("colormap_name", "?") return f"PlotMesh({rows}×{cols}, cmap={cmap!r})"