Update docs

This commit is contained in:
Jan Petykiewicz 2026-06-01 12:32:26 -07:00
commit e9421b60b0
3 changed files with 109 additions and 32 deletions

View file

@ -1,3 +1,5 @@
"""Miscellaneous matplotlib plots with small, focused public APIs."""
from .variability import variability_plot as variability_plot from .variability import variability_plot as variability_plot
from .wmap import wafermap as wafermap from .wmap import wafermap as wafermap

View file

@ -1,3 +1,5 @@
"""Hierarchical categorical variability charts for matplotlib."""
from typing import Sequence, Callable, Any, TYPE_CHECKING from typing import Sequence, Callable, Any, TYPE_CHECKING
import threading import threading
import textwrap import textwrap
@ -16,6 +18,12 @@ if TYPE_CHECKING:
def twrap(text: str, **kwargs) -> str: def twrap(text: str, **kwargs) -> str:
"""Wrap label text while preserving underscores in the displayed output.
Underscores are temporarily treated as wrap-friendly separators, then
restored after wrapping. Keyword arguments are forwarded to
`textwrap.fill`; `width` defaults to 15 characters.
"""
kwargs.setdefault('width', 15) kwargs.setdefault('width', 15)
intxt = text.replace('_', '-') intxt = text.replace('_', '-')
wrapped = textwrap.fill(intxt, **kwargs) wrapped = textwrap.fill(intxt, **kwargs)
@ -35,27 +43,38 @@ def variability_plot(
boxprops: dict[str, Any] | bool = True, boxprops: dict[str, Any] | bool = True,
meanprops: dict[str, Any] | bool = True, meanprops: dict[str, Any] | bool = True,
) -> tuple[Figure, Axes, list[Axes], list[Axes]]: ) -> tuple[Figure, Axes, list[Axes], list[Axes]]:
""" """Create a hierarchical categorical variability plot.
Create a variability plot (categorical box & scatter plot)
The main axes show jittered scatter points, optional box plots, and optional
per-category means for `data_col`. Additional axes beneath the main plot
show the category labels for each grouping level, with coarser groups
spanning multiple finer groups.
Args: Args:
data_table: Dataset to plot. Passed directly to `polars.DataFrame()`. data_table: Dataset to plot. Passed directly to
data_col: Column to use for box/scatterplot value. `polars.DataFrame`.
groups: Columns to group by. Coarsest grouping should be first, and will appear data_col: Numeric column to plot on the y-axis.
furthest from the scatterplot, at the bottom of the figure. groups: Columns to group by. The coarsest grouping should be first and
vert_groups: Labels for these column names will be rotated (text will run vertically). appears furthest from the scatter plot, at the bottom of the figure.
wrap_fn: Function called to wrap label text (i.e. insert newlines). The finest grouping should be last and appears closest to the data.
vert_groups: Group names whose labels should be rotated vertically.
wrap_fn: Function called to wrap label text by inserting newlines.
Default wraps at 15 characters, preferentially on underscores or whitespace. Default wraps at 15 characters, preferentially on underscores or whitespace.
mainplot_ratios: Scale factors setting the size of the main axes, relative to the size mainplot_ratios: Width and height scale factors for the main data axes,
of other axes. Default is (10, 10). relative to the label/header axes.
ylim: Y-limits for the scatter/box plot. Points which fall outside the limits are drawn ylim: Optional y-limits for the scatter and box plot. Points outside
as red triangles at the edges. the limits are drawn as red triangles at the corresponding edge.
dotprops: Passed as kwargs to scatterplot. dotprops: `False` disables jittered scatter points. A dictionary is
boxprops: Passed as kwargs to boxplot. passed as keyword arguments to `matplotlib.axes.Axes.scatter`.
meanprops: Passed as kwargs to lineplot of means. boxprops: `False` disables box plots. A dictionary is passed as
keyword arguments to `matplotlib.axes.Axes.boxplot`.
meanprops: `False` disables the mean trace. A dictionary is passed as
keyword arguments to `matplotlib.axes.Axes.plot`.
Returns: Returns:
figure, data axes, label axes, header axes Tuple of `(figure, data_axes, label_axes, header_axes)`. The label and
header axes are ordered from the finest displayed row nearest the data
to the coarsest displayed row at the bottom.
""" """
vert_groups = set(vert_groups) vert_groups = set(vert_groups)
@ -261,6 +280,12 @@ def variability_plot(
label_stack_t = list[list[tuple[int, int, str]]] label_stack_t = list[list[tuple[int, int, str]]]
def debounce(func: Callable, delay_s: float = 0.05) -> Callable: def debounce(func: Callable, delay_s: float = 0.05) -> Callable:
"""Return a wrapper that calls `func` after resize events settle.
Repeated calls within `delay_s` cancel the previous pending call. This
keeps expensive text-size recalculation from running continuously while a
matplotlib window is being resized.
"""
timer = None timer = None
def debounced_func(*args, **kwargs) -> None: def debounced_func(*args, **kwargs) -> None:
nonlocal timer nonlocal timer
@ -272,8 +297,18 @@ def debounce(func: Callable, delay_s: float = 0.05) -> Callable:
def get_label_stack(df_groups: polars.DataFrame, groups: Sequence[str], wrap_fn: Callable) -> label_stack_t: def get_label_stack(df_groups: polars.DataFrame, groups: Sequence[str], wrap_fn: Callable) -> label_stack_t:
""" """Build label text and x-span metadata for each grouping level.
For each level, get (xmin_inclusive, xmax_inclusive, wrapped_text_for_label) for all the labels
Args:
df_groups: DataFrame containing one row per plotted category
combination and an `x_pos` column with integer plot positions.
groups: Grouping columns ordered from coarsest to finest.
wrap_fn: Function used to convert each label value to display text.
Returns:
Nested list in grouping order. Each row contains
`(xmin_inclusive, xmax_inclusive, wrapped_label_text)` tuples for the
contiguous spans at that grouping level.
""" """
label_stack = [] label_stack = []
for ll, level in enumerate(groups): for ll, level in enumerate(groups):
@ -295,8 +330,14 @@ def get_label_stack(df_groups: polars.DataFrame, groups: Sequence[str], wrap_fn:
def get_text_sizes(label_stack: label_stack_t) -> list[NDArray[numpy.float64]]: def get_text_sizes(label_stack: label_stack_t) -> list[NDArray[numpy.float64]]:
""" """Measure unrotated label dimensions for layout calculations.
Transform the label stack (see `get_label_stack` into a stack of (allocated x-span, unrotated x-size, unrotated y-size)
Args:
label_stack: Label span metadata returned by `get_label_stack`.
Returns:
One array per label level. Each row contains
`(allocated_x_span, unrotated_width_px, unrotated_height_px)`.
""" """
fig, ax = pyplot.subplots() fig, ax = pyplot.subplots()
text_obj = ax.text(0, 0, 'placeholder') text_obj = ax.text(0, 0, 'placeholder')
@ -315,10 +356,21 @@ def get_text_sizes(label_stack: label_stack_t) -> list[NDArray[numpy.float64]]:
def get_label_y_ratios(groups: Sequence[str], vert_groups: set[str], size_lists: list[NDArray[numpy.float64]]) -> list[float]: def get_label_y_ratios(groups: Sequence[str], vert_groups: set[str], size_lists: list[NDArray[numpy.float64]]) -> list[float]:
""" """Calculate relative GridSpec heights for hierarchical label rows.
For each level, figure out max(rotated_x_size / x_span) and max(rotated_y_size).
Normalize so that the sum of y-values is equal to the number of levels. The calculation accounts for vertically rotated label groups, normalizes the
Output order is reversed so that the bottom labels (most general) come last. label rows so their total height equals the number of grouping levels, and
reverses the output for the figure layout so the coarsest labels appear at
the bottom.
Args:
groups: Grouping columns ordered from coarsest to finest.
vert_groups: Set of group names whose labels are rotated vertically.
size_lists: Text measurement arrays returned by
`get_text_sizes`.
Returns:
Height ratios ordered for the label rows in the matplotlib GridSpec.
""" """
grouping_rotated = numpy.array([grouping in vert_groups for grouping in groups], dtype=bool) grouping_rotated = numpy.array([grouping in vert_groups for grouping in groups], dtype=bool)
level_dims = [] level_dims = []
@ -335,8 +387,10 @@ def get_label_y_ratios(groups: Sequence[str], vert_groups: set[str], size_lists:
def _mk_data(filename: str) -> None: def _mk_data(filename: str) -> None:
""" """Create synthetic variability-chart data for the module demo.
Make some dummy data and write it to a csv file
Args:
filename: Path where the generated CSV data should be written.
""" """
rows = [] rows = []
rng = numpy.random.default_rng(seed=0) rng = numpy.random.default_rng(seed=0)

View file

@ -1,3 +1,5 @@
"""Wafer-style tiled heat maps for matplotlib."""
from typing import Any, Callable from typing import Any, Callable
from matplotlib import pyplot from matplotlib import pyplot
@ -17,12 +19,30 @@ def wafermap(
xy2sn: Callable[[int, int], str] | None = None, xy2sn: Callable[[int, int], str] | None = None,
aspect_ratio: float = 1, aspect_ratio: float = 1,
) -> tuple[Figure, Axes]: ) -> tuple[Figure, Axes]:
""" """Plot sparse `(x, y, z)` samples as a tiled wafer-style heat map.
Wafer map plot
This is effectively a pcolor plot which does a clearer job showing that data may come The plot bins each sample into integer-spaced x/y tiles, draws one color
from anywhere within a tile, and correctly shows missing data. per populated tile, and leaves unpopulated tiles transparent. This is useful
for spatial measurements where each value belongs to a die, cell, or other
tile rather than to an exact point location.
Args:
xyz: Array-like object with three columns: x coordinate, y coordinate,
and z value to color. Coordinates are binned with unit spacing based
on their floored values.
pcoloropts: Optional keyword arguments passed to
`matplotlib.axes.Axes.pcolor`. Defaults are added for
`shading`, `edgecolors`, and `cmap` when they are not already
present.
zlabel: Label for the colorbar.
xy2sn: Optional callback that receives the binned x/y indices and
returns text to draw over populated tiles.
aspect_ratio: Scale factor applied to x coordinates before plotting.
Use values below 1 for horizontally compressed layouts and values
above 1 for horizontally stretched layouts.
Returns:
The created matplotlib `(figure, axes)` pair.
""" """
fig, ax = pyplot.subplots() fig, ax = pyplot.subplots()
@ -62,6 +82,7 @@ def wafermap(
def example() -> None: def example() -> None:
"""Run an interactive wafer map example with synthetic radial data."""
rng = numpy.random.default_rng(0) rng = numpy.random.default_rng(0)
ar = 0.5 ar = 0.5
ny = 10 ny = 10