# dartwork-mpl — full agent reference (llms-full.txt)

This file is auto-generated at build time from the canonical sources
listed below. Do **not** hand-edit; edit the upstream files and rebuild
the docs (`uv run sphinx-build -b html docs docs/_build/html`).

Companions:

- `CLAUDE.md` / `AGENTS.md` — 30-second onboarding for agents.
- `llms.txt` — link index following the llmstxt.org spec.


---
## Quickstart
# Quick Start

A minimal end-to-end workflow: apply a style, create a figure, and
export it. Skim this in five minutes — you'll already know enough to
ship a publication-grade plot.

:::{tip}
A **live ruler** below this section maps `dm.fs(n)` / `dm.fw(n)` / `dm.lw(n)`
to actual point sizes and stroke widths under each preset — drag the sliders
to read off the resolved values without leaving the docs.

If the slider doesn't load (JavaScript disabled, terminal browsers,
or offline LLM contexts), the [static reference table](#fs-fallback-table)
further down lists the same base values per preset.
:::

## At-a-glance ROI

| What used to hurt                   | dartwork-mpl                                    |
| ----------------------------------- | ----------------------------------------------- |
| Hand-tuning `figsize` and `dpi`     | `plt.subplots(figsize=dm.figsize("13cm", "standard"))`  |
| `tight_layout` clipping labels      | `dm.simple_layout(fig)` — deterministic, content-aware |
| Reaching for hex codes              | `color="dc.ocean3"` (curated 8-mood palette), plus `"oc.*"`, `"tw.*"`, `"md.*"`, `"ad.*"`, `"cu.*"`, `"pr.*"` for third-party systems |
| Saving in 3 formats                 | `dm.save_formats(fig, "out", formats=("png", "svg", "pdf"))` |
| Catching margin / overflow problems | `dm.validate_with_fixes(fig)`                   |

:::{note}
**Upgrading from a previous PyPI release?** The
[Migration Guide](../migration.md) lists every renamed / removed name
since v0.4.0 with a side-by-side `Before → After` table — most call
sites are one-liners.
:::

Here's a typical matplotlib figure, then the same figure with dartwork-mpl:

::::{tab-set}

:::{tab-item} ✨ With dartwork-mpl

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

dm.style.use("scientific")  # curated fonts, colors, line weights

# width = physical figure width, aspect = height/width ratio
fig, ax = plt.subplots(figsize=dm.figsize("15cm", "wide"))

x = np.linspace(0, 10, 200)
ax.plot(x, np.sin(x), color="dc.ocean3", label="signal", lw=dm.lw(1.5))
ax.set_xticks(np.arange(0, 11, 2))
ax.set_xlabel("Time [s]")
ax.set_ylabel("Amplitude")
ax.legend()

dm.simple_layout(fig)           # content-aware margins
dm.save_and_show(fig, "first")  # save + inline preview
```

:::

:::{tab-item} 🔧 Vanilla matplotlib

```python
import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots(figsize=(5.91, 3.94), dpi=300)

x = np.linspace(0, 10, 200)
ax.plot(x, np.sin(x), color="#1c7ed6", label="signal", lw=1.5)
ax.set_xticks(np.arange(0, 11, 2))
ax.set_xlabel("Time [s]", fontsize=7.5)
ax.set_ylabel("Amplitude", fontsize=7.5)
ax.legend(fontsize=6.5)

fig.tight_layout()
fig.savefig("output.png", dpi=300, bbox_inches="tight")
plt.show()
```

:::

::::

:::{figure} images/quickstart_first_figure.svg
:alt: Scientific-style line chart created with dartwork-mpl
:width: 100%

The same chart rendered with `dm.style.use("scientific")` and
`plt.subplots(figsize=dm.figsize(…, …))` — professional typography,
optimized margins, and named colors.
:::

**Drag the slider to compare — same data, different styling:**

```{raw} html
:file: images/compare_slider.html
```

Same data, same plotting logic — the difference is one `dm.style.use()`
call, the `width` / `aspect` arguments on `dm.figsize`, named colors,
and `simple_layout`.

**What each dartwork-mpl call does:**

| Call                                          | Purpose                                                                            |
| --------------------------------------------- | ---------------------------------------------------------------------------------- |
| `dm.style.use("scientific")`                  | Sets palette, fonts, line weights — see [Styles](styles.md)                        |
| `dm.figsize("13cm", "standard")`              | Physical width plus an aspect token, returned as the inches tuple `figsize=` expects. |
| `dm.fs(0)`                                    | Returns the base font size of the active preset (`fs(2)` = base + 2 pt, and so on) |
| `dm.simple_layout(fig)`                       | Deterministic content-aware margins (replaces `tight_layout`)                      |
| `dm.save_and_show(fig, "first")`              | Saves multi-format and previews inline in the notebook                             |

(fs-fallback-table)=
### Static reference: `dm.fs(n)` resolved per preset

This is the **JS-free equivalent of the live ruler at the top of the
page** — same numbers, same purpose. Useful when JavaScript is
disabled (AI agents, terminal browsers) or when copying numbers into a
spreadsheet. Each row is the **base font size** that ships with the
preset's `font-*.mplstyle`; `dm.fs(n)` returns ``base + n`` (in
points), `dm.fw(n)` and `dm.lw(n)` apply analogous offsets to font
weight and stroke width.

| Preset         | Base `font.size` (pt) | Typical `dm.fs(2)` (pt) |
| -------------- | --------------------: | ----------------------: |
| `scientific`   | 7.5                   | 9.5                     |
| `report`       | 8.0                   | 10.0                    |
| `web`          | 11.0                  | 13.0                    |
| `presentation` | 10.5                  | 12.5                    |
| `poster`       | 12.0                  | 14.0                    |
| `minimal`      | 7.5                   | 9.5                     |

Source of truth: `src/dartwork_mpl/asset/mplstyle/font-*.mplstyle`. If
those values change, regenerate this table.

## Creating Figures with `dm.figsize`

`dm.figsize(width, aspect)` is the sanctioned way to size a figure. It
returns the inch tuple matplotlib's `figsize=` argument expects, so you
keep the native `plt.subplots` / `plt.figure` constructors and never
hand-roll inches yourself.

```python
# Physical width plus an aspect token (default aspect="standard" = 3/4)
fig, ax = plt.subplots(figsize=dm.figsize("13cm"))
fig, ax = plt.subplots(figsize=dm.figsize("15cm", "wide"))
fig, ax = plt.subplots(figsize=dm.figsize(dm.cm(11.3), "square"))

# Apply a style preset separately
dm.style.use("scientific")
fig, ax = plt.subplots(figsize=dm.figsize("13cm", "wide"))

# Stacked styles
dm.style.stack(["font-report", "theme-dark"])
fig, axes = plt.subplots(2, 2, figsize=dm.figsize("17cm", "standard"))

# Academic-column shortcuts
fig, ax = plt.subplots(figsize=dm.figsize(dm.col1, "golden"))  # 9 cm
fig, ax = plt.subplots(figsize=dm.figsize(dm.col2, "cinema"))  # 17 cm
```

**Width** accepts:

- A unit-suffixed string: `"13cm"`, `"9.5cm"`, `"6.7in"`, `"170mm"`,
  `"24pt"`
- A `Length` value: `dm.cm(11.3)`, `dm.inch(4.6)`, `dm.mm(170)`,
  `dm.pt(24)`, or `dm.length("13cm")` (string parser)
- The sugar constants `dm.col1` (9 cm) and `dm.col2` (17 cm)

Bare `int` / `float` are rejected — the unit must always be explicit.

**The second argument** picks the figure's height in one of four
equivalent forms; use whichever reads naturally for the call site:

| Form                      | Example                              | Notes                                            |
| :------------------------ | :----------------------------------- | :----------------------------------------------- |
| Aspect token              | `dm.figsize("13cm", "wide")`         | One of `square / portrait / standard / golden / wide / cinema`. |
| Numeric ratio             | `dm.figsize("13cm", 0.6)`            | Positive `int` / `float` interpreted as `height / width`. |
| Unit-suffix string height | `dm.figsize("13cm", "8cm")`          | Width and height units may differ.               |
| `Length` value            | `dm.figsize("13cm", dm.cm(8))`       | Useful for `dm.col1` / `dm.col2` heights.        |

Bare numeric strings (`"0.5"`) and unknown aspect tokens raise
`ValueError` with a "did-you-mean" hint, so ambiguous inputs fail
loudly.

:::{note}
The 0.4-era constructors `dm.subplots` and `dm.figure` (and their
`figsize=` / `dpi=` arguments) were removed. Calls now raise
`AttributeError` / `TypeError` with a message naming the modern
`plt.subplots(figsize=dm.figsize(...))` form. See
[`asset/prompt/01-policy.md`](https://github.com/dartworklabs/dartwork-mpl/blob/main/src/dartwork_mpl/asset/prompt/01-policy.md)
for the full lint catalog.
:::

**Multi-panel figures:**

```python
fig, axes = plt.subplots(
    2, 2,
    figsize=dm.figsize("17cm", "standard"),
    width_ratios=[2, 1],
    height_ratios=[1, 2],
)

for ax in axes.flat:
    ax.plot(np.random.randn(100))

dm.simple_layout(fig)
```

## Adding color

dartwork-mpl ships its own curated palette — `dc.*` ("dartwork color"),
8 mood families × 6 shades each — and registers six third-party design
systems alongside it. Use any of them anywhere matplotlib accepts a
color string:

```python
# Curated dartwork palette (recommended starting point)
ax.plot(x, y, color="dc.ocean3")                   # cool blue
ax.fill_between(x, y1, y2, color="dc.sunset1")     # warm accent
ax.bar(categories, values, color="dc.vivid1")      # bold call-out

# Third-party systems also available for cross-team consistency
ax.plot(x, y2, color="dc.ocean2")                   # OpenColor
ax.plot(x, y3, color="tw.emerald500")              # Tailwind
```

**Discover what's available without leaving Python:**

```python
import dartwork_mpl as dm

dm.list_palettes()[:5]      # → ['dc.autumn', 'dc.cyber', 'dc.forest', ...]
dm.show_palette("dc.ocean") # renders the 6-shade swatch row in Jupyter
dm.plot_colors(ncols=4)     # full library overview, one figure per system
```

See [Colors and Colormaps](colors.md) for the full palette reference,
or open the [interactive palette explorer](../color_system/colors.md)
to click-and-copy color names from your browser.

## Multi-panel layout

```python
import dartwork_mpl as dm
import numpy as np

dm.style.use("presentation")

x = np.linspace(0, 10, 100)
fig = plt.figure(figsize=dm.figsize("15cm", "wide"))
gs = fig.add_gridspec(1, 2, wspace=0.3)
ax1 = fig.add_subplot(gs[0])
ax2 = fig.add_subplot(gs[1])

ax1.plot(x, np.sin(x), color="dc.vivid1")
ax2.plot(x, np.cos(x), color="dc.ocean3")

dm.label_axes([ax1, ax2])  # adds (a), (b) panel labels
dm.simple_layout(fig, gs=gs)
```

:::{figure} images/quickstart_multi_panel.svg
:alt: Two-panel layout with label_axes showing sin and cos
:width: 100%
:::

## Saving in multiple formats

```python
dm.save_formats(
    fig,
    "output/my_figure",
    formats=("png", "svg", "pdf"),
    dpi=300,
    validate=True,  # auto-check for overflow, overlap, etc.
)
```

## Catch problems before you export

`validate=True` above runs the same checks as the standalone
`validate_with_fixes` helper, which can also patch the easy issues
in-place:

```python
result = dm.validate_with_fixes(fig)
print(result.report())     # human-readable summary of warnings
# margin_asymmetry → auto-fixed via dm.simple_layout()
# pie_label_offset → auto-adjusted pctdistance
```

Use it in CI to fail a build when a figure breaks; use it locally to
get a one-line health check before you `save_formats`.

## Try the interactive UI

If you'd rather see the effect of every parameter before committing
it to code, dartwork-mpl ships a local web app that wires sliders to
your render function and exports the resulting Python script:

```bash
# 1. scaffold a starter viewer (first time only)
pip install "dartwork-mpl[ui]"
dartwork-mpl-ui init ./my-viewer --example simple

# 2. run it — opens http://127.0.0.1:8501
cd my-viewer && python viewer.py
```

→ [Interactive UI guide](interactive.md)

## Next steps

::::{grid} 1 1 2 3
:gutter: 2

:::{grid-item-card} 🎨 Styles and Presets
Choose the right preset for your use case — papers, reports, slides, posters.

→ [Browse presets](styles.md)
:::

:::{grid-item-card} 🌈 Colors and Colormaps
Explore 900+ named palettes and perceptual OKLCH interpolation.

→ [See palettes](colors.md)
:::

:::{grid-item-card} 📐 Layout and Typography
Panel labels, arrows, font scaling, and margin optimization.

→ [Learn layout](layout.md)
:::

:::{grid-item-card} 💾 Save and Validation
Multi-format export + automatic visual quality checks.

→ [Export guide](save_export.md)
:::

:::{grid-item-card} 🛠️ Interactive UI
Tune fonts, line weights, margins with sliders. Export the exact
script that reproduces what you see.

→ [Interactive UI](interactive.md)
:::

:::{grid-item-card} 🔬 Diagnostics & Templates
`dm.plot_colors()` / `plot_colormaps()` / `plot_fonts()` for asset
audit, plus ready-to-use plot templates like `plot_diverging_bar`.

→ [Extras guide](extras.md)
:::

::::


---
## Migration (0.3 → 0.4)
---
orphan: true
---

# Migration Guide

This guide collects every rename / deprecation that has shipped on PyPI
(v0.4.0 onwards), ordered newest first. Each removed symbol now raises
`AttributeError` / `ModuleNotFoundError` / `TypeError` with a message
naming its replacement. The `dartwork_mpl.asset_viz` and
`dartwork_mpl.helpers.formatting` submodule aliases still import with a
`DeprecationWarning` and are scheduled for removal in **v1.0**.

> **New to dartwork-mpl?** You don't need this page. Head to the
> [Quick Start](usage_guide/quickstart.md) — it uses the current API
> end-to-end.

## At a glance

| Old surface                           | New surface                                          |
| ------------------------------------- | ---------------------------------------------------- |
| `dm.install_llm_txt()` / `uninstall_llm_txt()` | `dm.get_agent_doc(name)` / `dm.agent_doc_path(name)` (or MCP `dartwork-mpl://guide/*`) |
| `dm.subplots(width=..., aspect=...)`  | `plt.subplots(figsize=dm.figsize(...))`              |
| `dm.figure(width=..., aspect=...)`    | `plt.figure(figsize=dm.figsize(...))`                |
| `dm.subplots(..., style=...)`         | call `dm.style.use(...)` first, then `plt.subplots`  |
| `plt.tight_layout()`                  | `dm.simple_layout(fig)`                              |
| `dm.auto_layout(fig)`                 | `dm.simple_layout(fig)` (auto_layout is a deprecation alias) |
| `dm.simple_layout(fig, margins=(...), bbox=..., bound_margin=..., gtol=..., importance_weights=...)` | `dm.simple_layout(fig, margin="2%", ml=..., mr=..., mt=..., mb=...)` (new keyword API) |
| `dartwork_mpl.helpers.formatting`     | `dartwork_mpl.helpers.labels`                        |
| `dartwork_mpl.asset_viz`              | `dartwork_mpl.diagnostics`                           |
| `dm.style_spines(ax, color=c, linewidth=w, which=ws)` | `for s in ws: ax.spines[s].set_color(c); ax.spines[s].set_linewidth(w)` |
| `dm.add_grid(ax)`                     | `ax.grid(True, color="oc.gray3", alpha=0.3, linewidth=0.5); ax.set_axisbelow(True)` |
| `dm.minimal_axes(ax)`                 | see [Minimal axes recipe](usage_guide/recipes.md#minimal-axes-tufte-style) |
| `dm.auto_select_colors(n_series=N, color_type=K, highlight_index=I)` | `dm.make_palette(N, kind=K, highlight=I)` |
| `dm.named("oc.red5")`                 | `dm.color("oc.red5")` (also accepts hex / `rgb(...)` / `oklch(...)` / `oklab(...)`) |
| `from dartwork_mpl.color import ...`  | `from dartwork_mpl.colors import ...` (submodule renamed) |

## `dm.named` removal & `color` → `colors` submodule rename

`dm.named` is gone. The replacement is `dm.color`, a single
string-parser entry point that mirrors `dm.length`:

```python
# Before
red = dm.named("oc.red5")

# Now
red = dm.color("oc.red5")               # palette token
red = dm.color("#ff0000")               # hex
red = dm.color("rgb(255, 0, 0)")        # rgb function string
red = dm.color("oklch(0.7 0.2 30)")     # oklch function string
red = dm.color("oklab(0.7 0.1 0.1)")    # oklab function string
```

The submodule was also renamed `color` → `colors`. Anywhere you used
`from dartwork_mpl.color import ...` swap to:

```python
# Before
from dartwork_mpl.color import Color

# Now
from dartwork_mpl.colors import Color
```

## `dm.subplots` / `dm.figure` removal

The `dm.subplots` / `dm.figure` wrappers around the matplotlib figure
constructors are gone. Use `plt.subplots` / `plt.figure` directly and
pass `figsize=dm.figsize(width, aspect)`:

```python
# Before
fig, ax = dm.subplots(width="13cm", aspect="standard")

# Now
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=dm.figsize("13cm", "standard"))
```

If you also passed `style=`, call `dm.style.use(...)` first:

```python
# Before
fig, ax = dm.subplots(width="13cm", aspect="standard", style="scientific")

# Now
dm.style.use("scientific")
fig, ax = plt.subplots(figsize=dm.figsize("13cm", "standard"))
```

`dm.figsize(width, aspect)` accepts a unit-string width (`"13cm"`,
`"5in"`, `"170mm"`, `"24pt"`) or a `Length` value (`dm.cm(13)`,
`dm.col1`, `dm.col2`), and four equivalent aspect forms — token
(`"wide"`), numeric ratio (`0.6`), unit-string height (`"12cm"`), or
`Length` height (`dm.cm(12)`). Bare `int` / `float` widths are
rejected.

## v0.4.x → v0.5.0

**The `install_llm_txt` installer was removed (#170).** It copied the
bundled prompt corpus into IDE-specific folders; the corpus is now read
at runtime instead, so there is nothing to install. `dm.install_llm_txt`
/ `dm.uninstall_llm_txt` / `dm.INSTALL_TARGETS` raise `AttributeError`.

| Removed | Replacement |
|---|---|
| `dm.install_llm_txt(...)` | `dm.get_agent_doc(name)` / `dm.agent_doc_path(name)` — `name` ∈ `AGENTS`, `CLAUDE`, `llms`, `llms-full` — or the MCP `dartwork-mpl://guide/*` resources |
| `dm.uninstall_llm_txt(...)` | nothing — the corpus is read at runtime, so there is no install to undo |
| `dm.INSTALL_TARGETS` | nothing — install targets no longer exist |

**`ipython` is no longer a core dependency (#248).** Only `dm.show()`
(inline SVG display in Jupyter) needs it, so it moved to the `notebook`
optional extra. `dm.show()` raises a clear `ImportError` naming the
extra when IPython is absent; every other entry point is unaffected.

| Old install | New install |
|---|---|
| `pip install dartwork-mpl` (pulled in ~30 MB of IPython deps) | `pip install "dartwork-mpl[notebook]"` if you use `dm.show()` |

## v0.4.0 → v0.4.1 — API audit round 3 (#141)

Five thin wrappers around 1–3 line matplotlib calls were retired. The
curated default kwargs they encoded now live as snippets in
[`usage_guide/recipes.md`](usage_guide/recipes.md):

```python
# Before
dm.format_axis_percent(ax)
dm.format_axis_labels(ax, fmt="{:,.0f}")
dm.add_frame(fig)
dm.add_value_labels(ax, bars)
dm.set_xmargin(ax, 0.05); dm.set_ymargin(ax, 0.05)

# Now — direct matplotlib calls, sometimes one-liners
from matplotlib import ticker
ax.yaxis.set_major_formatter(ticker.PercentFormatter(xmax=1.0))
ax.yaxis.set_major_formatter(ticker.StrMethodFormatter("{x:,.0f}"))
fig.patches.append(plt.Rectangle((0, 0), 1, 1, fill=False, transform=fig.transFigure))
for bar in bars: ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height(),
                          f"{bar.get_height():.0f}", ha="center", va="bottom")
ax.set_xmargin(0.05); ax.set_ymargin(0.05)
```

## v0.4.0 → v0.4.1 — API audit round 2 (#141)

Eight more wrappers retired; each is a single matplotlib call now:

```python
# Before / Now
dm.hide_spines(ax, ["top", "right"])            # → for s in ("top", "right"): ax.spines[s].set_visible(False)
dm.hide_all_spines(ax)                           # → for s in ax.spines: ax.spines[s].set_visible(False)
dm.show_only_spines(ax, ["left", "bottom"])      # → for s in ax.spines: ax.spines[s].set_visible(s in ("left", "bottom"))
dm.remove_grid(ax)                               # → ax.grid(False)
dm.format_axis_thousands(ax, axis="y")           # → see snippet below
dm.save_figure(fig, "out.png")                   # → fig.savefig("out.png")
dm.create_figure_with_style(width=..., style=...)  # → dm.style.use(style); plt.subplots(figsize=dm.figsize(...))
dm.templates.diverging_bar.get_source_code()     # → inspect.getsource(dm.templates.diverging_bar)
```

```python
# Thousand separator on y-axis:
from matplotlib import ticker
formatter = ticker.FuncFormatter(lambda x, p: f"{x:,.0f}")   # sep=","
# Non-comma separator:
# formatter = ticker.FuncFormatter(lambda x, p: f"{x:,.0f}".replace(",", sep))
ax.yaxis.set_major_formatter(formatter)   # or ax.xaxis for axis="x"
```


---
## Anti-pattern catalog (SSOT, YAML)
# dartwork-mpl anti-pattern catalog (SSOT for lint engine).
#
# Each rule has:
#   id          unique kebab-case identifier
#   severity    critical | warning | info
#   detector
#     kind      regex | substring
#     pattern   regex pattern (Python re, MULTILINE) for kind=regex
#     literal   literal substring (case-sensitive) for kind=substring
#   message     short user-facing text shown in the lint report
#   why         (optional) longer rationale; shown only on demand
#   fix_suggestion  (optional) one-line replacement or pointer
#
# Rule IDs are part of the public API: tools, docs, and tests refer
# to them. Do not rename without bumping the dartwork-mpl version.

version: 1
rules:
  - id: figsize-direct
    severity: critical
    detector:
      kind: regex
      pattern: '\bfigsize\s*=\s*\('
    message: |
      Raw `figsize=(w, h)` tuples bypass dartwork-mpl's physical-width
      contract. Use `figsize=dm.figsize("13cm", "wide")` so the width
      stays in cm/in/mm and the aspect intent is named.
    why: |
      Width and aspect are decided separately so chart-wide width
      consistency can be enforced and so the height follows from the
      content's aspect intent.
    fix_suggestion: 'figsize=dm.figsize("13cm", "standard")'

  - id: tight-layout
    severity: critical
    detector:
      kind: regex
      pattern: '\btight_layout\s*\('
    message: |
      `tight_layout()` collides with dartwork-mpl's spine and legend
      handling. Use `dm.simple_layout(fig)` instead.
    fix_suggestion: 'dm.simple_layout(fig)'

  - id: multirow-subplots-no-gridspec-kw
    severity: info
    detector:
      kind: regex
      pattern: '\bplt\.subplots\(\s*[2-9]\b(?![\s\S]{0,200}?(?:gridspec_kw|sharex|sharey))'
    message: |
      Vertically stacked `plt.subplots(>=2, ...)` was created without
      `gridspec_kw={"hspace": ..., "wspace": ...}`. The matplotlib
      default `hspace` (0.2) frequently causes the upper subplot's
      xlabel / xtick labels to overlap the lower subplot's title once
      `dm.simple_layout(fig)` snaps the outer margins flush. Pass an
      explicit `gridspec_kw` (e.g. `hspace=0.55`, `wspace=0.3`) when
      every subplot has both a title and an xlabel, then verify with
      `dm.validate_figure(fig)` — the `CROSS_AXES_OVERLAP` check will
      flag any residual collisions.
    why: |
      `dm.simple_layout` only adjusts the figure's outer margins; it
      intentionally preserves the GridSpec's `hspace` / `wspace` so
      authors can keep their own intent. That makes the default
      `hspace=0.2` the hidden source of title↔xlabel collisions in
      multi-panel gallery and report figures. The detector scans the
      200 characters following `plt.subplots(<n>` for `gridspec_kw` /
      `sharex` / `sharey` so it catches line-wrapped calls (where the
      kwargs sit on a later line), not just single-line ones. This is
      a best-effort static hint — the authoritative collision check is
      the runtime `CROSS_AXES_OVERLAP` validator.
    fix_suggestion: 'gridspec_kw={"hspace": 0.55, "wspace": 0.3}'

  - id: zero-resize-mention
    severity: warning
    detector:
      kind: substring
      literal: 'Zero-Resize'
    message: |
      "Zero-Resize Policy" was retired in 0.4.0. dartwork-mpl now uses
      free-form width input plus a lint consistency guard.

  - id: plt-style-use
    severity: warning
    detector:
      kind: regex
      pattern: '\bplt\.style\.use\s*\('
    message: |
      Use `dm.style.use(...)` (or `dm.style.stack([...])` for a stack)
      instead of `plt.style.use(...)`.
    fix_suggestion: 'dm.style.use("scientific")'

  - id: plt-show-only
    severity: info
    detector:
      kind: regex
      pattern: '\bplt\.show\s*\(\)'
    message: |
      Prefer `dm.save_and_show(fig, "name")` or `dm.save_formats(fig,
      "name")` so the figure ships with its rendered artifact.

  - id: dm-subplots-removed
    severity: critical
    detector:
      kind: regex
      pattern: '\bdm\.(?:subplots|figure)\s*\('
    message: |
      `dm.subplots` and `dm.figure` were REMOVED — accessing them now
      raises `AttributeError`. Use `plt.subplots(figsize=dm.figsize(...))`
      (or `plt.figure(figsize=dm.figsize(...))` for custom GridSpec).
      Apply style separately via `dm.style.use(...)`.
    why: |
      Both functions were thin wrappers around `plt.subplots` / `plt.figure`
      that bundled style mutation, polymorphic `width=`, and a deny-list
      of legacy kwargs. Removing them keeps the matplotlib API native and
      reduces dartwork-mpl's surface to a single sizing helper.
    fix_suggestion: 'plt.subplots(figsize=dm.figsize("13cm", "standard"))'

  - id: cm2in-figsize
    severity: critical
    detector:
      kind: regex
      pattern: 'figsize\s*=\s*\([^)]*cm2in'
    message: |
      `figsize=(dm.cm2in(...), dm.cm2in(...))` is the legacy 0.3
      pattern: `dm.cm2in` itself raises `AttributeError`. Use
      `figsize=dm.figsize("<n>cm", "<aspect>")` instead.
    fix_suggestion: 'figsize=dm.figsize("9cm", "standard")'

  - id: dm-spines-removed
    severity: critical
    detector:
      kind: regex
      pattern: '\bdm\.(?:style_spines|add_grid|minimal_axes)\s*\('
    message: |
      `dm.style_spines`, `dm.add_grid`, and `dm.minimal_axes` were
      REMOVED in 0.4.1 (#156). They were thin matplotlib wrappers
      whose only contribution was curated default kwargs. Inline the
      raw matplotlib calls instead, or copy a snippet from
      `docs/usage_guide/recipes.md` (publication grid, minimal axes,
      thin gray spines).
    fix_suggestion: 'see docs/usage_guide/recipes.md'

  - id: dm-auto-select-colors-renamed
    severity: critical
    detector:
      kind: regex
      pattern: '\bdm\.(?:helpers\.colors\.)?auto_select_colors\s*\('
    message: |
      `auto_select_colors` was renamed to `make_palette` in 0.4.1
      (#156) for vocabulary alignment with `make_offset` /
      `list_palettes`. Argument cleanup at the same time:
      `n_series` → `n`, `color_type` → `kind`,
      `highlight_index` → `highlight`.
    fix_suggestion: 'dm.make_palette(n, kind="categorical")'

  - id: deprecated-width-token
    severity: critical
    detector:
      kind: regex
      pattern: '\bdm\.(SW|MW|TW|DW|FS_[A-Z_]+|WIDTHS)\b'
    message: |
      `dm.SW/MW/TW/DW/FS_*/WIDTHS` were REMOVED in 0.4.0 — accessing
      them now raises `AttributeError`. Use
      `figsize=dm.figsize("<n>cm", "<aspect>")`, or `dm.col1`/`dm.col2`
      for academic columns.

  - id: raw-width-number
    severity: critical
    detector:
      kind: regex
      # `dm.figsize(13, ...)` or `dm.figsize(13.5)` — bare numbers carry
      # no unit and dartwork-mpl rejects them at runtime. Catch the
      # call site with a clearer message than the raised TypeError.
      pattern: '\bdm\.figsize\s*\(\s*-?\d+(?:\.\d+)?\s*[,)]'
    message: |
      `dm.figsize(<bare number>, ...)` is rejected — bare numbers carry
      no unit. Pass a unit string (`"13cm"`, `"5in"`, `"170mm"`,
      `"24pt"`) or a Length value (`dm.cm(13)`, `dm.col1`, `dm.col2`).
    fix_suggestion: 'dm.figsize("13cm", "standard")'

  - id: dpi-arg
    severity: critical
    detector:
      kind: regex
      # Allow one level of nested parens (e.g. ``figsize=(8, 6)``)
      # before the ``dpi=`` token so that
      # ``plt.figure(figsize=(8,6), dpi=200)`` is caught. The previous
      # ``[^)]*`` form bailed at the first ``)`` and silently missed
      # this very common spelling. ``savefig(dpi=...)`` is intentionally
      # outside this rule's scope — see ``savefig-direct``.
      pattern: '\bplt\.(?:subplots|figure)\s*\((?:[^()]|\([^()]*\))*\bdpi\s*='
    message: |
      `dpi=` should not be set per-figure. The active dartwork-mpl
      style controls dpi (display vs savefig). Direct
      ``savefig(dpi=...)`` is flagged separately by ``savefig-direct``.

  - id: raw-hex-color
    severity: info
    detector:
      kind: regex
      pattern: '\b(?:color|c)\s*=\s*["'']#[0-9a-fA-F]{3,8}["'']'
    message: |
      Raw hex colors bypass dartwork-mpl's color system. Prefer named
      tokens passed as strings, e.g. `color="oc.blue6"` (Open Color),
      `color="tw.sky500"` (Tailwind), or `color="dc.0"` (dartwork core
      categorical). These register as matplotlib named colors; wrap in
      `dm.color("oc.blue6")` when you need a Color object.
    why: |
      Named palettes give consistent perception across reports and
      let style presets remap colors centrally.
    fix_suggestion: 'color="oc.blue6"'

  - id: fontsize-literal
    severity: warning
    detector:
      kind: regex
      pattern: '\bfontsize\s*=\s*\d+(?:\.\d+)?'
    message: |
      `fontsize=<n>` hardcodes a pt size and bypasses the active
      style preset. Use `dm.fs(n)` for an offset from the style's
      base size, or rely on the style defaults.
    fix_suggestion: 'fontsize=dm.fs(0)'

  - id: linewidth-literal
    severity: warning
    detector:
      kind: regex
      # Match only literals whose integer part is >= 1. Sub-1 values
      # (e.g. ``linewidth=0.3`` for hairline edges) and the no-border
      # idiom ``linewidth=0`` are common, intentional decoration and
      # don't fight the active style — bundled templates rely on them.
      pattern: '\b(?:linewidth|lw)\s*=\s*[1-9]\d*(?:\.\d+)?'
    message: |
      `linewidth=<n>` (>= 1) hardcodes a line width. Use `dm.lw(n)`
      for a relative offset from the active style. (`linewidth=0`,
      used to disable a border, and sub-1 hairline widths like
      `linewidth=0.3` are allowed.)
    fix_suggestion: 'linewidth=dm.lw(0)'

  - id: savefig-direct
    severity: warning
    detector:
      kind: regex
      pattern: '\b(?:fig|plt)\.savefig\s*\('
    message: |
      Direct `savefig(...)` bypasses the dartwork-mpl save preset
      (multi-format, dpi, metadata). Use
      `dm.save_formats(fig, "name")` for scripts or
      `dm.save_and_show(fig, "name")` for notebooks.
    fix_suggestion: 'dm.save_formats(fig, "name")'

  - id: jet-cmap
    severity: warning
    detector:
      kind: regex
      pattern: 'cmap\s*=\s*["''](?:jet|hsv|gist_rainbow|gist_ncar|nipy_spectral|rainbow)["'']'
    message: |
      Rainbow colormaps (`jet`, `hsv`, `gist_rainbow`, `gist_ncar`,
      `nipy_spectral`, `rainbow`) misrepresent ordinal data. Use a
      perceptually uniform colormap (`viridis`, `cividis`, `inferno`,
      `magma`, `plasma`) or one of the dartwork-registered colormaps
      (`dm.list_colormaps()`).
    fix_suggestion: 'cmap="viridis"'

  - id: oversize-width
    severity: warning
    detector:
      kind: regex
      # Match a > 17 cm literal in either of the two canonical width
      # contexts: a `width=` kwarg (legacy / non-figsize callers) or
      # the first positional of `dm.figsize("...cm", ...)`. The aspect
      # argument (second positional) is filtered out because it sits
      # behind a `,` rather than directly after `(`.
      pattern: '(?:width\s*=\s*|\bdm\.figsize\s*\(\s*)["''](?:1[89]|[2-9]\d|\d{3,}|17\.\d*[1-9]\d*)(?:\.\d+)?cm["'']'
    message: |
      Width exceeds 17 cm (`dm.col2`). Most page layouts break above
      this threshold and produce ragged multi-figure reports. Reduce
      to 17 cm or split into a multi-row layout.
    why: |
      Snap widths to the 0.5 cm grid and keep ≤ 5 distinct widths
      per project for cross-figure consistency.
    fix_suggestion: 'dm.figsize("17cm", "standard")'


---
## AI plot templates index
AI Plot Templates
-----------------

Eighteen ready-to-use plot templates curated for AI coding assistants. Each
template is a small, self-contained script written with the 0.4
``width=...``/``aspect=...`` API and Open Color palette. They are bundled in
the wheel under ``dartwork_mpl/asset/prompt/05-templates/`` and exposed
through both the prompt utilities and the
:doc:`MCP server </integrations/mcp_server>`.

How to access these templates
=============================

.. code-block:: python

   import dartwork_mpl as dm

   # Read a template script as text
   bar_src = dm.get_prompt("05-templates/bar")

   # Or list everything available under prompts/
   print(dm.list_prompts())

Or via MCP from Claude / Cursor / Continue:

.. code-block:: text

   dartwork-mpl://templates/{plot_type}

where ``{plot_type}`` is one of ``bar``, ``bar_horizontal``,
``bar_grouped``, ``boxplot``, ``contour``, ``heatmap``, ``histogram``,
``line``, ``pie``, ``plot_3d``, ``polar``, ``scatter``, ``small_multiples``,
``stacked_bar``, ``tornado``, ``twin_axis``, ``violin``, or ``waterfall``.

The gallery entries below render each template so you can pick the right
shape at a glance, then copy the source from the page (or fetch it via
``dm.get_prompt``) into your project.
