Layout Utilities

Utilities for tightening layouts without juggling plt.subplots_adjust. simple_layout optimizes margins with L-BFGS-B so axes fit inside a bounding box; make_offset nudges text/legends in point units; label_axes adds standardized panel labels; arrow_axis draws annotated bidirectional arrows; and set_decimal/get_bounding_box provide quick helpers when formatting axes.

Example

import matplotlib.pyplot as plt
import numpy as np
import dartwork_mpl as dm

fig, axes = plt.subplots(1, 3, figsize=(dm.DW, dm.DW * 0.35))
for ax in axes:
    ax.plot(np.linspace(0, 1, 40), np.random.rand(40), color='oc.blue6')

# Panel labels
dm.label_axes(axes)  # adds a, b, c

# Layout optimization
dm.simple_layout(fig, margins=(0.08, 0.05, 0.1, 0.08))

# Decimal formatting
dm.set_decimal(axes[0], xn=2, yn=1)

# Arrow annotations
dm.arrow_axis(axes[1], 'x', 'Installation cost')
dm.arrow_axis(axes[2], 'y', 'Information richness')
3-panel layout with label_axes, arrow_axis, and set_decimal

API

Layout Functions

dartwork_mpl.cm2in(cm: float) float[source]

Convert centimeters to inches.

Parameters:

cm (float) – Value in centimeters.

Returns:

Equivalent value in inches.

Return type:

float

dartwork_mpl.simple_layout(fig: Figure, gs: GridSpec | SubplotSpec | None = None, margins: tuple[float, float, float, float] = (0.1, 0.1, 0.08, 0.05), bbox: tuple[float, float, float, float] = (0, 1, 0, 1), verbose: bool = False, gtol: float = 0.01, bound_margin: float = 0.2, use_all_axes: bool = True, importance_weights: tuple[float, float, float, float] = (1, 1, 1, 1)) OptimizeResult[source]

Apply an optimized layout to a GridSpec for fine-tuned subplot positioning.

Uses the L-BFGS-B optimization algorithm to compute GridSpec parameters that best fit subplots within the specified margins and bounding box. Provides more consistent and predictable margin control than the built-in tight_layout.

Parameters:
  • fig (Figure) – The Matplotlib Figure to apply the layout to.

  • gs (GridSpec | SubplotSpec | None, optional) – GridSpec or SubplotSpec to optimize. If None, defaults to the GridSpec of fig.axes[0]. If a SubplotSpec is provided, its parent GridSpec will be used.

  • margins (tuple[float, float, float, float], optional) – Margins in inches (left, right, bottom, top). Default is (0.15, 0.05, 0.05, 0.05).

  • bbox (tuple[float, float, float, float], optional) – Target region in figure-relative coordinates (left, right, bottom, top). Default (0, 1, 0, 1) covers the entire figure.

  • verbose (bool, optional) – Whether to print diagnostic logs during optimization. Default is False.

  • gtol (float, optional) – Gradient tolerance for L-BFGS-B optimization. Default is 1e-2.

  • bound_margin (float, optional) – Buffer margin for generating parameter bounds, controlling the optimization search space. Default is 0.2.

  • use_all_axes (bool, optional) – If True, uses all Axes in the Figure for bounding-box computation. If False, only Axes belonging to gs are considered. Default is True.

  • importance_weights (tuple[float, float, float, float], optional) – Weights (left, right, bottom, top) controlling the importance of matching each margin. Default is (1, 1, 1, 1).

Returns:

The scipy optimization result object.

Return type:

OptimizeResult

dartwork_mpl.auto_layout(fig: Figure, *, padding: float | tuple[float, float, float, float] = 0.08, max_iter: int = 5, tolerance: float = 2.0, verbose: bool = False) None[source]

Content-aware layout that auto-adjusts margins to eliminate overflow.

Wraps simple_layout with a Validate → Measure → Adjust → Retry loop. Starts with minimal margins, measures actual per-side overflow using text and tick-label bounding boxes, and increases margins only on overflowing sides. Converges in 1–3 iterations for typical charts; axes-relative annotations (which move with the subplot) may need more.

Parameters:
  • fig (Figure) – The Matplotlib Figure to lay out.

  • padding (float | tuple[float, float, float, float], optional) – Initial padding in inches for all four sides (left, right, bottom, top). If a single float, it is used for all sides. Default is 0.08.

  • max_iter (int, optional) – Maximum number of measure-and-adjust iterations. Default is 5.

  • tolerance (float, optional) – Overflow tolerance in pixels. Overflows below this threshold are ignored. Default is 2.0 px.

  • verbose (bool, optional) – If True, prints per-iteration diagnostics. Default is False.

Examples

>>> import dartwork_mpl as dm
>>> fig, ax = plt.subplots()
>>> ax.plot([1, 2, 3])
>>> ax.set_ylabel("Revenue ($M)")
>>> dm.auto_layout(fig)
dartwork_mpl.set_xmargin(ax: Axes, margin: float = 0.05, *, left: float | None = None, right: float | None = None) None[source]

Set responsive margins or fixed bounds on the x-axis limits.

Wraps set_xlim to allow specifying a global margin ratio while optionally pinning one or both edges to fixed values.

Parameters:
  • ax (Axes) – The matplotlib Axes to modify.

  • margin (float, optional) – Fractional margin applied to both sides. Default is 0.05.

  • left (float | None, optional) – Fixed left bound for the x-axis. Overrides the margin on that side.

  • right (float | None, optional) – Fixed right bound for the x-axis. Overrides the margin on that side.

dartwork_mpl.set_ymargin(ax: Axes, margin: float = 0.05, *, bottom: float | None = None, top: float | None = None) None[source]

Set responsive margins or fixed bounds on the y-axis limits.

Wraps set_ylim to allow specifying a global margin ratio while optionally pinning one or both edges to fixed values.

Parameters:
  • ax (Axes) – The matplotlib Axes to modify.

  • margin (float, optional) – Fractional margin applied to both sides. Default is 0.05.

  • bottom (float | None, optional) – Fixed bottom bound for the y-axis. Overrides the margin on that side.

  • top (float | None, optional) – Fixed top bound for the y-axis. Overrides the margin on that side.

Annotation Functions

dartwork_mpl.make_offset(x: float, y: float, fig: Figure) ScaledTranslation[source]

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:

A translation transform that can be added to other transforms.

Return type:

matplotlib.transforms.ScaledTranslation

dartwork_mpl.label_axes(axes: list[Axes] | ndarray, labels: list[str] | None = None, fontsize: float = 10, fontweight: str = 'bold', x: float | str = 'auto', y: float = 1.05, **kwargs) list[source]

Add standardized identification labels (a, b, c, …) to subplot panels.

Commonly used in academic papers and reports to annotate multiple panels of a figure, placing labels at the left edge or top corner of each Axes.

Parameters:
  • axes (list[Axes] | np.ndarray) – List or array of Axes objects to label.

  • labels (list[str] | None, optional) – Custom text labels. If None, lowercase letters (a, b, c, …) are assigned automatically.

  • fontsize (float, optional) – Font size for the labels. Default is 10 points.

  • fontweight (str, optional) – Font weight for the labels. Default is “bold”.

  • x (float | str, optional) – Horizontal position in Axes-relative coordinates (may exceed 0.0–1.0). If “auto”, the optimal x position is determined based on whether a y-axis label is present (-0.18 or -0.02).

  • y (float, optional) – Vertical position in Axes-relative coordinates. Default is 1.05.

  • **kwargs – Additional text properties passed to ax.text().

Returns:

List of created Text objects.

Return type:

list

dartwork_mpl.arrow_axis(ax: Axes, direction: str, label: str, *, offset: float = -0.1, low: str = 'Low', high: str = 'High', fontsize: float | None = None, fontsize_label: float | None = None, pad: float = -0.005, weight: str = 'normal', color: str = 'black', arrow_kw: dict | None = None) None[source]

Draw a bidirectional Low–High arrow axis along the edge of a plot.

Produces a visual like Low ◄── label ──► High near the spine exterior.

Parameters:
  • ax (Axes) – Target Axes object for the annotation.

  • direction ({'x', 'y'}) – “x”: insert a horizontal arrow axis below the x-axis spine. “y”: insert a vertical arrow axis to the left of the y-axis spine.

  • label (str) – Center label text placed at the midpoint of the axis.

  • offset (float, optional) – Offset from the spine in Axes-fraction units. Default is -0.10 (sufficiently outside to avoid overlap with tick labels).

  • low (str, optional) – Text for the low end (bottom/left) of the axis. Default is “Low”.

  • high (str, optional) – Text for the high end (top/right) of the axis. Default is “High”.

  • fontsize (float | None, optional) – Font size for the Low/High endpoint labels. Default is fs(-1).

  • fontsize_label (float | None, optional) – Font size for the center label. Default is fs(0).

  • pad (float, optional) – Fractional gap between text and arrowheads. Default is -0.005.

  • weight (str, optional) – Font weight applied to all text elements.

  • color (str, optional) – Color for both text and arrows. Default is “black”.

  • arrow_kw (dict | None, optional) – Override the arrowprops passed to the internal ax.annotate calls.

Utility Functions

dartwork_mpl.layout.get_bounding_box(boxes: list) tuple[float, float, float, float][source]

Compute the minimum bounding box that encloses all given box regions.

Parameters:

boxes (list) – List of box objects, each having at minimum p0 (bottom-left coordinate), width, and height attributes.

Returns:

Overall bounding box as (min_x, min_y, bbox_width, bbox_height).

Return type:

tuple[float, float, float, float]

dartwork_mpl.set_decimal(ax: Axes, xn: int | None = None, yn: int | None = None) None[source]

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.