|
from __future__ import annotations |
|
|
|
import io |
|
from typing import TYPE_CHECKING, Any, cast |
|
|
|
import matplotlib.collections as mcollections |
|
import matplotlib.pyplot as plt |
|
import numpy as np |
|
|
|
from contourpy import FillType, LineType |
|
from contourpy.util.mpl_util import filled_to_mpl_paths, lines_to_mpl_paths, mpl_codes_to_offsets |
|
from contourpy.util.renderer import Renderer |
|
|
|
if TYPE_CHECKING: |
|
from matplotlib.axes import Axes |
|
from matplotlib.figure import Figure |
|
from numpy.typing import ArrayLike |
|
|
|
import contourpy._contourpy as cpy |
|
|
|
|
|
class MplRenderer(Renderer): |
|
_axes: Axes |
|
_fig: Figure |
|
_want_tight: bool |
|
|
|
"""Utility renderer using Matplotlib to render a grid of plots over the same (x, y) range. |
|
|
|
Args: |
|
nrows (int, optional): Number of rows of plots, default ``1``. |
|
ncols (int, optional): Number of columns of plots, default ``1``. |
|
figsize (tuple(float, float), optional): Figure size in inches, default ``(9, 9)``. |
|
show_frame (bool, optional): Whether to show frame and axes ticks, default ``True``. |
|
backend (str, optional): Matplotlib backend to use or ``None`` for default backend. |
|
Default ``None``. |
|
gridspec_kw (dict, optional): Gridspec keyword arguments to pass to ``plt.subplots``, |
|
default None. |
|
""" |
|
def __init__( |
|
self, |
|
nrows: int = 1, |
|
ncols: int = 1, |
|
figsize: tuple[float, float] = (9, 9), |
|
show_frame: bool = True, |
|
backend: str | None = None, |
|
gridspec_kw: dict[str, Any] | None = None, |
|
) -> None: |
|
if backend is not None: |
|
import matplotlib |
|
matplotlib.use(backend) |
|
|
|
kwargs = dict(figsize=figsize, squeeze=False, sharex=True, sharey=True) |
|
if gridspec_kw is not None: |
|
kwargs["gridspec_kw"] = gridspec_kw |
|
else: |
|
kwargs["subplot_kw"] = dict(aspect="equal") |
|
|
|
self._fig, axes = plt.subplots(nrows, ncols, **kwargs) |
|
self._axes = axes.flatten() |
|
if not show_frame: |
|
for ax in self._axes: |
|
ax.axis("off") |
|
|
|
self._want_tight = True |
|
|
|
def __del__(self) -> None: |
|
if hasattr(self, "_fig"): |
|
plt.close(self._fig) |
|
|
|
def _autoscale(self) -> None: |
|
|
|
|
|
|
|
for ax in self._axes: |
|
if getattr(ax, "_need_autoscale", False): |
|
ax.autoscale_view(tight=True) |
|
ax._need_autoscale = False |
|
if self._want_tight and len(self._axes) > 1: |
|
self._fig.tight_layout() |
|
|
|
def _get_ax(self, ax: Axes | int) -> Axes: |
|
if isinstance(ax, int): |
|
ax = self._axes[ax] |
|
return ax |
|
|
|
def filled( |
|
self, |
|
filled: cpy.FillReturn, |
|
fill_type: FillType, |
|
ax: Axes | int = 0, |
|
color: str = "C0", |
|
alpha: float = 0.7, |
|
) -> None: |
|
"""Plot filled contours on a single Axes. |
|
|
|
Args: |
|
filled (sequence of arrays): Filled contour data as returned by |
|
:func:`~contourpy.ContourGenerator.filled`. |
|
fill_type (FillType): Type of ``filled`` data, as returned by |
|
:attr:`~contourpy.ContourGenerator.fill_type`. |
|
ax (int or Maplotlib Axes, optional): Which axes to plot on, default ``0``. |
|
color (str, optional): Color to plot with. May be a string color or the letter ``"C"`` |
|
followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the |
|
``tab10`` colormap. Default ``"C0"``. |
|
alpha (float, optional): Opacity to plot with, default ``0.7``. |
|
""" |
|
ax = self._get_ax(ax) |
|
paths = filled_to_mpl_paths(filled, fill_type) |
|
collection = mcollections.PathCollection( |
|
paths, facecolors=color, edgecolors="none", lw=0, alpha=alpha) |
|
ax.add_collection(collection) |
|
ax._need_autoscale = True |
|
|
|
def grid( |
|
self, |
|
x: ArrayLike, |
|
y: ArrayLike, |
|
ax: Axes | int = 0, |
|
color: str = "black", |
|
alpha: float = 0.1, |
|
point_color: str | None = None, |
|
quad_as_tri_alpha: float = 0, |
|
) -> None: |
|
"""Plot quad grid lines on a single Axes. |
|
|
|
Args: |
|
x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points. |
|
y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points. |
|
ax (int or Matplotlib Axes, optional): Which Axes to plot on, default ``0``. |
|
color (str, optional): Color to plot grid lines, default ``"black"``. |
|
alpha (float, optional): Opacity to plot lines with, default ``0.1``. |
|
point_color (str, optional): Color to plot grid points or ``None`` if grid points |
|
should not be plotted, default ``None``. |
|
quad_as_tri_alpha (float, optional): Opacity to plot ``quad_as_tri`` grid, default 0. |
|
|
|
Colors may be a string color or the letter ``"C"`` followed by an integer in the range |
|
``"C0"`` to ``"C9"`` to use a color from the ``tab10`` colormap. |
|
|
|
Warning: |
|
``quad_as_tri_alpha > 0`` plots all quads as though they are unmasked. |
|
""" |
|
ax = self._get_ax(ax) |
|
x, y = self._grid_as_2d(x, y) |
|
kwargs = dict(color=color, alpha=alpha) |
|
ax.plot(x, y, x.T, y.T, **kwargs) |
|
if quad_as_tri_alpha > 0: |
|
|
|
xmid = 0.25*(x[:-1, :-1] + x[1:, :-1] + x[:-1, 1:] + x[1:, 1:]) |
|
ymid = 0.25*(y[:-1, :-1] + y[1:, :-1] + y[:-1, 1:] + y[1:, 1:]) |
|
kwargs["alpha"] = quad_as_tri_alpha |
|
ax.plot( |
|
np.stack((x[:-1, :-1], xmid, x[1:, 1:])).reshape((3, -1)), |
|
np.stack((y[:-1, :-1], ymid, y[1:, 1:])).reshape((3, -1)), |
|
np.stack((x[1:, :-1], xmid, x[:-1, 1:])).reshape((3, -1)), |
|
np.stack((y[1:, :-1], ymid, y[:-1, 1:])).reshape((3, -1)), |
|
**kwargs) |
|
if point_color is not None: |
|
ax.plot(x, y, color=point_color, alpha=alpha, marker="o", lw=0) |
|
ax._need_autoscale = True |
|
|
|
def lines( |
|
self, |
|
lines: cpy.LineReturn, |
|
line_type: LineType, |
|
ax: Axes | int = 0, |
|
color: str = "C0", |
|
alpha: float = 1.0, |
|
linewidth: float = 1, |
|
) -> None: |
|
"""Plot contour lines on a single Axes. |
|
|
|
Args: |
|
lines (sequence of arrays): Contour line data as returned by |
|
:func:`~contourpy.ContourGenerator.lines`. |
|
line_type (LineType): Type of ``lines`` data, as returned by |
|
:attr:`~contourpy.ContourGenerator.line_type`. |
|
ax (int or Matplotlib Axes, optional): Which Axes to plot on, default ``0``. |
|
color (str, optional): Color to plot lines. May be a string color or the letter ``"C"`` |
|
followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the |
|
``tab10`` colormap. Default ``"C0"``. |
|
alpha (float, optional): Opacity to plot lines with, default ``1.0``. |
|
linewidth (float, optional): Width of lines, default ``1``. |
|
""" |
|
ax = self._get_ax(ax) |
|
paths = lines_to_mpl_paths(lines, line_type) |
|
collection = mcollections.PathCollection( |
|
paths, facecolors="none", edgecolors=color, lw=linewidth, alpha=alpha) |
|
ax.add_collection(collection) |
|
ax._need_autoscale = True |
|
|
|
def mask( |
|
self, |
|
x: ArrayLike, |
|
y: ArrayLike, |
|
z: ArrayLike | np.ma.MaskedArray[Any, Any], |
|
ax: Axes | int = 0, |
|
color: str = "black", |
|
) -> None: |
|
"""Plot masked out grid points as circles on a single Axes. |
|
|
|
Args: |
|
x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points. |
|
y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points. |
|
z (masked array of shape (ny, nx): z-values. |
|
ax (int or Matplotlib Axes, optional): Which Axes to plot on, default ``0``. |
|
color (str, optional): Circle color, default ``"black"``. |
|
""" |
|
mask = np.ma.getmask(z) |
|
if mask is np.ma.nomask: |
|
return |
|
ax = self._get_ax(ax) |
|
x, y = self._grid_as_2d(x, y) |
|
ax.plot(x[mask], y[mask], "o", c=color) |
|
|
|
def save(self, filename: str, transparent: bool = False) -> None: |
|
"""Save plots to SVG or PNG file. |
|
|
|
Args: |
|
filename (str): Filename to save to. |
|
transparent (bool, optional): Whether background should be transparent, default |
|
``False``. |
|
""" |
|
self._autoscale() |
|
self._fig.savefig(filename, transparent=transparent) |
|
|
|
def save_to_buffer(self) -> io.BytesIO: |
|
"""Save plots to an ``io.BytesIO`` buffer. |
|
|
|
Return: |
|
BytesIO: PNG image buffer. |
|
""" |
|
self._autoscale() |
|
buf = io.BytesIO() |
|
self._fig.savefig(buf, format="png") |
|
buf.seek(0) |
|
return buf |
|
|
|
def show(self) -> None: |
|
"""Show plots in an interactive window, in the usual Matplotlib manner. |
|
""" |
|
self._autoscale() |
|
plt.show() |
|
|
|
def title(self, title: str, ax: Axes | int = 0, color: str | None = None) -> None: |
|
"""Set the title of a single Axes. |
|
|
|
Args: |
|
title (str): Title text. |
|
ax (int or Matplotlib Axes, optional): Which Axes to set the title of, default ``0``. |
|
color (str, optional): Color to set title. May be a string color or the letter ``"C"`` |
|
followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the |
|
``tab10`` colormap. Default is ``None`` which uses Matplotlib's default title color |
|
that depends on the stylesheet in use. |
|
""" |
|
if color: |
|
self._get_ax(ax).set_title(title, color=color) |
|
else: |
|
self._get_ax(ax).set_title(title) |
|
|
|
def z_values( |
|
self, |
|
x: ArrayLike, |
|
y: ArrayLike, |
|
z: ArrayLike, |
|
ax: Axes | int = 0, |
|
color: str = "green", |
|
fmt: str = ".1f", |
|
quad_as_tri: bool = False, |
|
) -> None: |
|
"""Show ``z`` values on a single Axes. |
|
|
|
Args: |
|
x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points. |
|
y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points. |
|
z (array-like of shape (ny, nx): z-values. |
|
ax (int or Matplotlib Axes, optional): Which Axes to plot on, default ``0``. |
|
color (str, optional): Color of added text. May be a string color or the letter ``"C"`` |
|
followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the |
|
``tab10`` colormap. Default ``"green"``. |
|
fmt (str, optional): Format to display z-values, default ``".1f"``. |
|
quad_as_tri (bool, optional): Whether to show z-values at the ``quad_as_tri`` centers |
|
of quads. |
|
|
|
Warning: |
|
``quad_as_tri=True`` shows z-values for all quads, even if masked. |
|
""" |
|
ax = self._get_ax(ax) |
|
x, y = self._grid_as_2d(x, y) |
|
z = np.asarray(z) |
|
ny, nx = z.shape |
|
for j in range(ny): |
|
for i in range(nx): |
|
ax.text(x[j, i], y[j, i], f"{z[j, i]:{fmt}}", ha="center", va="center", |
|
color=color, clip_on=True) |
|
if quad_as_tri: |
|
for j in range(ny-1): |
|
for i in range(nx-1): |
|
xx = np.mean(x[j:j+2, i:i+2]) |
|
yy = np.mean(y[j:j+2, i:i+2]) |
|
zz = np.mean(z[j:j+2, i:i+2]) |
|
ax.text(xx, yy, f"{zz:{fmt}}", ha="center", va="center", color=color, |
|
clip_on=True) |
|
|
|
|
|
class MplTestRenderer(MplRenderer): |
|
"""Test renderer implemented using Matplotlib. |
|
|
|
No whitespace around plots and no spines/ticks displayed. |
|
Uses Agg backend, so can only save to file/buffer, cannot call ``show()``. |
|
""" |
|
def __init__( |
|
self, |
|
nrows: int = 1, |
|
ncols: int = 1, |
|
figsize: tuple[float, float] = (9, 9), |
|
) -> None: |
|
gridspec = { |
|
"left": 0.01, |
|
"right": 0.99, |
|
"top": 0.99, |
|
"bottom": 0.01, |
|
"wspace": 0.01, |
|
"hspace": 0.01, |
|
} |
|
super().__init__( |
|
nrows, ncols, figsize, show_frame=True, backend="Agg", gridspec_kw=gridspec, |
|
) |
|
|
|
for ax in self._axes: |
|
ax.set_xmargin(0.0) |
|
ax.set_ymargin(0.0) |
|
ax.set_xticks([]) |
|
ax.set_yticks([]) |
|
|
|
self._want_tight = False |
|
|
|
|
|
class MplDebugRenderer(MplRenderer): |
|
"""Debug renderer implemented using Matplotlib. |
|
|
|
Extends ``MplRenderer`` to add extra information to help in debugging such as markers, arrows, |
|
text, etc. |
|
""" |
|
def __init__( |
|
self, |
|
nrows: int = 1, |
|
ncols: int = 1, |
|
figsize: tuple[float, float] = (9, 9), |
|
show_frame: bool = True, |
|
) -> None: |
|
super().__init__(nrows, ncols, figsize, show_frame) |
|
|
|
def _arrow( |
|
self, |
|
ax: Axes, |
|
line_start: cpy.CoordinateArray, |
|
line_end: cpy.CoordinateArray, |
|
color: str, |
|
alpha: float, |
|
arrow_size: float, |
|
) -> None: |
|
mid = 0.5*(line_start + line_end) |
|
along = line_end - line_start |
|
along /= np.sqrt(np.dot(along, along)) |
|
right = np.asarray((along[1], -along[0])) |
|
arrow = np.stack(( |
|
mid - (along*0.5 - right)*arrow_size, |
|
mid + along*0.5*arrow_size, |
|
mid - (along*0.5 + right)*arrow_size, |
|
)) |
|
ax.plot(arrow[:, 0], arrow[:, 1], "-", c=color, alpha=alpha) |
|
|
|
def _filled_to_lists_of_points_and_offsets( |
|
self, |
|
filled: cpy.FillReturn, |
|
fill_type: FillType, |
|
) -> tuple[list[cpy.PointArray], list[cpy.OffsetArray]]: |
|
if fill_type == FillType.OuterCode: |
|
if TYPE_CHECKING: |
|
filled = cast(cpy.FillReturn_OuterCode, filled) |
|
all_points = filled[0] |
|
all_offsets = [mpl_codes_to_offsets(codes) for codes in filled[1]] |
|
elif fill_type == FillType.ChunkCombinedCode: |
|
if TYPE_CHECKING: |
|
filled = cast(cpy.FillReturn_ChunkCombinedCode, filled) |
|
all_points = [points for points in filled[0] if points is not None] |
|
all_offsets = [mpl_codes_to_offsets(codes) for codes in filled[1] if codes is not None] |
|
elif fill_type == FillType.OuterOffset: |
|
if TYPE_CHECKING: |
|
filled = cast(cpy.FillReturn_OuterOffset, filled) |
|
all_points = filled[0] |
|
all_offsets = filled[1] |
|
elif fill_type == FillType.ChunkCombinedOffset: |
|
if TYPE_CHECKING: |
|
filled = cast(cpy.FillReturn_ChunkCombinedOffset, filled) |
|
all_points = [points for points in filled[0] if points is not None] |
|
all_offsets = [offsets for offsets in filled[1] if offsets is not None] |
|
elif fill_type == FillType.ChunkCombinedCodeOffset: |
|
if TYPE_CHECKING: |
|
filled = cast(cpy.FillReturn_ChunkCombinedCodeOffset, filled) |
|
all_points = [] |
|
all_offsets = [] |
|
for points, codes, outer_offsets in zip(*filled): |
|
if points is None: |
|
continue |
|
if TYPE_CHECKING: |
|
assert codes is not None and outer_offsets is not None |
|
all_points += np.split(points, outer_offsets[1:-1]) |
|
all_codes = np.split(codes, outer_offsets[1:-1]) |
|
all_offsets += [mpl_codes_to_offsets(codes) for codes in all_codes] |
|
elif fill_type == FillType.ChunkCombinedOffsetOffset: |
|
if TYPE_CHECKING: |
|
filled = cast(cpy.FillReturn_ChunkCombinedOffsetOffset, filled) |
|
all_points = [] |
|
all_offsets = [] |
|
for points, offsets, outer_offsets in zip(*filled): |
|
if points is None: |
|
continue |
|
if TYPE_CHECKING: |
|
assert offsets is not None and outer_offsets is not None |
|
for i in range(len(outer_offsets)-1): |
|
offs = offsets[outer_offsets[i]:outer_offsets[i+1]+1] |
|
all_points.append(points[offs[0]:offs[-1]]) |
|
all_offsets.append(offs - offs[0]) |
|
else: |
|
raise RuntimeError(f"Rendering FillType {fill_type} not implemented") |
|
|
|
return all_points, all_offsets |
|
|
|
def _lines_to_list_of_points( |
|
self, lines: cpy.LineReturn, line_type: LineType, |
|
) -> list[cpy.PointArray]: |
|
if line_type == LineType.Separate: |
|
if TYPE_CHECKING: |
|
lines = cast(cpy.LineReturn_Separate, lines) |
|
all_lines = lines |
|
elif line_type == LineType.SeparateCode: |
|
if TYPE_CHECKING: |
|
lines = cast(cpy.LineReturn_SeparateCode, lines) |
|
all_lines = lines[0] |
|
elif line_type == LineType.ChunkCombinedCode: |
|
if TYPE_CHECKING: |
|
lines = cast(cpy.LineReturn_ChunkCombinedCode, lines) |
|
all_lines = [] |
|
for points, codes in zip(*lines): |
|
if points is not None: |
|
if TYPE_CHECKING: |
|
assert codes is not None |
|
offsets = mpl_codes_to_offsets(codes) |
|
for i in range(len(offsets)-1): |
|
all_lines.append(points[offsets[i]:offsets[i+1]]) |
|
elif line_type == LineType.ChunkCombinedOffset: |
|
if TYPE_CHECKING: |
|
lines = cast(cpy.LineReturn_ChunkCombinedOffset, lines) |
|
all_lines = [] |
|
for points, all_offsets in zip(*lines): |
|
if points is not None: |
|
if TYPE_CHECKING: |
|
assert all_offsets is not None |
|
for i in range(len(all_offsets)-1): |
|
all_lines.append(points[all_offsets[i]:all_offsets[i+1]]) |
|
else: |
|
raise RuntimeError(f"Rendering LineType {line_type} not implemented") |
|
|
|
return all_lines |
|
|
|
def filled( |
|
self, |
|
filled: cpy.FillReturn, |
|
fill_type: FillType, |
|
ax: Axes | int = 0, |
|
color: str = "C1", |
|
alpha: float = 0.7, |
|
line_color: str = "C0", |
|
line_alpha: float = 0.7, |
|
point_color: str = "C0", |
|
start_point_color: str = "red", |
|
arrow_size: float = 0.1, |
|
) -> None: |
|
super().filled(filled, fill_type, ax, color, alpha) |
|
|
|
if line_color is None and point_color is None: |
|
return |
|
|
|
ax = self._get_ax(ax) |
|
all_points, all_offsets = self._filled_to_lists_of_points_and_offsets(filled, fill_type) |
|
|
|
|
|
if line_color is not None: |
|
for points, offsets in zip(all_points, all_offsets): |
|
for start, end in zip(offsets[:-1], offsets[1:]): |
|
xys = points[start:end] |
|
ax.plot(xys[:, 0], xys[:, 1], c=line_color, alpha=line_alpha) |
|
|
|
if arrow_size > 0.0: |
|
n = len(xys) |
|
for i in range(n-1): |
|
self._arrow(ax, xys[i], xys[i+1], line_color, line_alpha, arrow_size) |
|
|
|
|
|
if point_color is not None: |
|
for points, offsets in zip(all_points, all_offsets): |
|
mask = np.ones(offsets[-1], dtype=bool) |
|
mask[offsets[1:]-1] = False |
|
if start_point_color is not None: |
|
start_indices = offsets[:-1] |
|
mask[start_indices] = False |
|
ax.plot( |
|
points[:, 0][mask], points[:, 1][mask], "o", c=point_color, alpha=line_alpha) |
|
|
|
if start_point_color is not None: |
|
ax.plot(points[:, 0][start_indices], points[:, 1][start_indices], "o", |
|
c=start_point_color, alpha=line_alpha) |
|
|
|
def lines( |
|
self, |
|
lines: cpy.LineReturn, |
|
line_type: LineType, |
|
ax: Axes | int = 0, |
|
color: str = "C0", |
|
alpha: float = 1.0, |
|
linewidth: float = 1, |
|
point_color: str = "C0", |
|
start_point_color: str = "red", |
|
arrow_size: float = 0.1, |
|
) -> None: |
|
super().lines(lines, line_type, ax, color, alpha, linewidth) |
|
|
|
if arrow_size == 0.0 and point_color is None: |
|
return |
|
|
|
ax = self._get_ax(ax) |
|
all_lines = self._lines_to_list_of_points(lines, line_type) |
|
|
|
if arrow_size > 0.0: |
|
for line in all_lines: |
|
for i in range(len(line)-1): |
|
self._arrow(ax, line[i], line[i+1], color, alpha, arrow_size) |
|
|
|
if point_color is not None: |
|
for line in all_lines: |
|
start_index = 0 |
|
end_index = len(line) |
|
if start_point_color is not None: |
|
ax.plot(line[0, 0], line[0, 1], "o", c=start_point_color, alpha=alpha) |
|
start_index = 1 |
|
if line[0][0] == line[-1][0] and line[0][1] == line[-1][1]: |
|
end_index -= 1 |
|
ax.plot(line[start_index:end_index, 0], line[start_index:end_index, 1], "o", |
|
c=color, alpha=alpha) |
|
|
|
def point_numbers( |
|
self, |
|
x: ArrayLike, |
|
y: ArrayLike, |
|
z: ArrayLike, |
|
ax: Axes | int = 0, |
|
color: str = "red", |
|
) -> None: |
|
ax = self._get_ax(ax) |
|
x, y = self._grid_as_2d(x, y) |
|
z = np.asarray(z) |
|
ny, nx = z.shape |
|
for j in range(ny): |
|
for i in range(nx): |
|
quad = i + j*nx |
|
ax.text(x[j, i], y[j, i], str(quad), ha="right", va="top", color=color, |
|
clip_on=True) |
|
|
|
def quad_numbers( |
|
self, |
|
x: ArrayLike, |
|
y: ArrayLike, |
|
z: ArrayLike, |
|
ax: Axes | int = 0, |
|
color: str = "blue", |
|
) -> None: |
|
ax = self._get_ax(ax) |
|
x, y = self._grid_as_2d(x, y) |
|
z = np.asarray(z) |
|
ny, nx = z.shape |
|
for j in range(1, ny): |
|
for i in range(1, nx): |
|
quad = i + j*nx |
|
xmid = x[j-1:j+1, i-1:i+1].mean() |
|
ymid = y[j-1:j+1, i-1:i+1].mean() |
|
ax.text(xmid, ymid, str(quad), ha="center", va="center", color=color, clip_on=True) |
|
|
|
def z_levels( |
|
self, |
|
x: ArrayLike, |
|
y: ArrayLike, |
|
z: ArrayLike, |
|
lower_level: float, |
|
upper_level: float | None = None, |
|
ax: Axes | int = 0, |
|
color: str = "green", |
|
) -> None: |
|
ax = self._get_ax(ax) |
|
x, y = self._grid_as_2d(x, y) |
|
z = np.asarray(z) |
|
ny, nx = z.shape |
|
for j in range(ny): |
|
for i in range(nx): |
|
zz = z[j, i] |
|
if upper_level is not None and zz > upper_level: |
|
z_level = 2 |
|
elif zz > lower_level: |
|
z_level = 1 |
|
else: |
|
z_level = 0 |
|
ax.text(x[j, i], y[j, i], z_level, ha="left", va="bottom", color=color, |
|
clip_on=True) |
|
|