Source code for dartwork_mpl.util

"""Miscellaneous utilities for Matplotlib figure management.

Collects small helper functions that do not warrant their own module.
Also re-exports core functions that were moved to dedicated modules
for backward compatibility.
"""

from __future__ import annotations

# Re-exports for backward compatibility - these were moved to
# dedicated modules but many consumers still import from util.
from .annotation import arrow_axis, label_axes
from .io import save_and_show, save_formats, show
from .layout import get_bounding_box, simple_layout
from .prompt import copy_prompt, get_prompt, list_prompts, prompt_path
from .scale import fs, fw, lw

__all__ = [
    "arrow_axis",
    "copy_prompt",
    "fs",
    "fw",
    "get_bounding_box",
    "get_prompt",
    "label_axes",
    "list_prompts",
    "lw",
    "make_offset",
    "mix_colors",
    "prompt_path",
    "pseudo_alpha",
    "save_and_show",
    "save_formats",
    "set_decimal",
    "show",
    "simple_layout",
]

import matplotlib.colors as mcolors
from matplotlib.axes import Axes
from matplotlib.figure import Figure
from matplotlib.transforms import ScaledTranslation

from .colors import Color


[docs] def set_decimal(ax: Axes, xn: int | None = None, yn: int | None = None) -> None: """Fix the number of decimal places displayed on tick labels. Parameters ---------- ax : matplotlib.axes.Axes The Axes to modify. xn : int | None, optional Number of decimal places for x-axis tick labels. If None, the x-axis is left unchanged. yn : int | None, optional Number of decimal places for y-axis tick labels. If None, the y-axis is left unchanged. """ if xn is not None: xticks = ax.get_xticks() ax.set_xticks(xticks) ax.set_xticklabels([f"{x:.{xn}f}" for x in xticks]) if yn is not None: yticks = ax.get_yticks() ax.set_yticks(yticks) ax.set_yticklabels([f"{y:.{yn}f}" for y in yticks])
[docs] def mix_colors( color1: str | tuple[float, float, float], color2: str | tuple[float, float, float], alpha: float = 0.5, ) -> tuple[float, float, float]: """Blend two colors in OKLab space (perceptually uniform blend). Blends in OKLab rather than gamma-encoded sRGB, so midpoints avoid the "muddy" saturation dip that naive RGB mixing produces for saturated hues (e.g. red + blue → purple, not a dark desaturated grey). Parameters ---------- color1 : str | tuple[float, float, float] First color to blend. Any format recognized by matplotlib. color2 : str | tuple[float, float, float] Second color to blend. Any format recognized by matplotlib. alpha : float, optional Weight of *color1* (0 → color2, 1 → color1). Default is 0.5. Returns ------- tuple[float, float, float] sRGB tuple of the blended result, compatible with any matplotlib ``color=`` argument. Notes ----- The result will differ subtly from previous versions for non-grayscale blends — gradients now look smoother and less desaturated through saturated midpoints. ``dm.pseudo_alpha`` (which delegates to this function) inherits the same improvement. """ r1, g1, b1 = mcolors.to_rgb(color1) r2, g2, b2 = mcolors.to_rgb(color2) c1 = Color.from_rgb(r1, g1, b1) c2 = Color.from_rgb(r2, g2, b2) L1, a1, b1_ok = c1.to_oklab() L2, a2, b2_ok = c2.to_oklab() L = alpha * L1 + (1.0 - alpha) * L2 a = alpha * a1 + (1.0 - alpha) * a2 b_blend = alpha * b1_ok + (1.0 - alpha) * b2_ok return Color.from_oklab(L, a, b_blend).to_rgb()
[docs] def pseudo_alpha( color: str | tuple[float, float, float], alpha: float = 1.0, background: str | tuple[float, float, float] = "white", ) -> tuple[float, float, float]: """Return an opaque RGB that simulates alpha transparency against a background. True alpha can cause darkening artifacts when lines overlap or cover images. This function blends the color with the background to produce a flat (opaque) color that visually mimics transparency. Parameters ---------- color : str | tuple[float, float, float] The target foreground color. alpha : float, optional Simulated opacity (0 to 1). Default is 1.0 (fully opaque). background : str | tuple[float, float, float], optional Background color to blend against. Default is "white". Returns ------- tuple[float, float, float] RGB tuple of the composited color. """ return mix_colors(color, background, alpha=alpha)
[docs] def make_offset(x: float, y: float, fig: Figure) -> ScaledTranslation: """Create a translation offset transform for positioning figure elements. Parameters ---------- x : float Horizontal offset in points. y : float Vertical offset in points. fig : matplotlib.figure.Figure The Figure whose DPI scale is used. Returns ------- matplotlib.transforms.ScaledTranslation A translation transform that can be added to other transforms. """ dx, dy = x / 72, y / 72 return ScaledTranslation(dx, dy, fig.dpi_scale_trans)