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 .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
import threading
import textwrap
@ -16,6 +18,12 @@ if TYPE_CHECKING:
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)
intxt = text.replace('_', '-')
wrapped = textwrap.fill(intxt, **kwargs)
@ -35,27 +43,38 @@ def variability_plot(
boxprops: dict[str, Any] | bool = True,
meanprops: dict[str, Any] | bool = True,
) -> tuple[Figure, Axes, list[Axes], list[Axes]]:
"""
Create a variability plot (categorical box & scatter plot)
"""Create a hierarchical categorical variability 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:
data_table: Dataset to plot. Passed directly to `polars.DataFrame()`.
data_col: Column to use for box/scatterplot value.
groups: Columns to group by. Coarsest grouping should be first, and will appear
furthest from the scatterplot, at the bottom of the figure.
vert_groups: Labels for these column names will be rotated (text will run vertically).
wrap_fn: Function called to wrap label text (i.e. insert newlines).
data_table: Dataset to plot. Passed directly to
`polars.DataFrame`.
data_col: Numeric column to plot on the y-axis.
groups: Columns to group by. The coarsest grouping should be first and
appears furthest from the scatter plot, at the bottom of the figure.
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.
mainplot_ratios: Scale factors setting the size of the main axes, relative to the size
of other axes. Default is (10, 10).
ylim: Y-limits for the scatter/box plot. Points which fall outside the limits are drawn
as red triangles at the edges.
dotprops: Passed as kwargs to scatterplot.
boxprops: Passed as kwargs to boxplot.
meanprops: Passed as kwargs to lineplot of means.
mainplot_ratios: Width and height scale factors for the main data axes,
relative to the label/header axes.
ylim: Optional y-limits for the scatter and box plot. Points outside
the limits are drawn as red triangles at the corresponding edge.
dotprops: `False` disables jittered scatter points. A dictionary is
passed as keyword arguments to `matplotlib.axes.Axes.scatter`.
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:
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)
@ -261,6 +280,12 @@ def variability_plot(
label_stack_t = list[list[tuple[int, int, str]]]
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
def debounced_func(*args, **kwargs) -> None:
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:
"""
For each level, get (xmin_inclusive, xmax_inclusive, wrapped_text_for_label) for all the labels
"""Build label text and x-span metadata for each grouping level.
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 = []
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]]:
"""
Transform the label stack (see `get_label_stack` into a stack of (allocated x-span, unrotated x-size, unrotated y-size)
"""Measure unrotated label dimensions for layout calculations.
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()
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]:
"""
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.
Output order is reversed so that the bottom labels (most general) come last.
"""Calculate relative GridSpec heights for hierarchical label rows.
The calculation accounts for vertically rotated label groups, normalizes the
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)
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:
"""
Make some dummy data and write it to a csv file
"""Create synthetic variability-chart data for the module demo.
Args:
filename: Path where the generated CSV data should be written.
"""
rows = []
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 matplotlib import pyplot
@ -17,12 +19,30 @@ def wafermap(
xy2sn: Callable[[int, int], str] | None = None,
aspect_ratio: float = 1,
) -> tuple[Figure, Axes]:
"""
Wafer map plot
"""Plot sparse `(x, y, z)` samples as a tiled wafer-style heat map.
This is effectively a pcolor plot which does a clearer job showing that data may come
from anywhere within a tile, and correctly shows missing data.
The plot bins each sample into integer-spaced x/y tiles, draws one color
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()
@ -62,6 +82,7 @@ def wafermap(
def example() -> None:
"""Run an interactive wafer map example with synthetic radial data."""
rng = numpy.random.default_rng(0)
ar = 0.5
ny = 10