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 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("15cm", "wide"))

tight_layout clipping labels

dm.simple_layout(fig) — deterministic, content-aware

Reaching for hex codes

color="dc.teal3" (the curated dc.* system), 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)

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

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.teal3", 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
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()
Scientific-style line chart created with dartwork-mpl

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:

Quickstart figure — dartwork vs vanilla — Dartwork
Quickstart figure — dartwork vs vanilla — Vanilla

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

dm.figsize("15cm", "wide")

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

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.

Choosing a purpose and canvas

Explicit width/height/style and journal/template constraints take priority, then prior project context, then purpose defaults. Start with these draft suggestions and adjust for panel/label complexity:

Context

Existing preset

Initial sizing decision

Paper / journal / 논문

scientific

dm.col1 (9 cm) or dm.col2 (17 cm) can be starting points; confirm the venue’s actual column width for final publication

Report / document / dashboard / 보고서

report

13–17 cm can be a starting range; use the available page/content width

Talk / slides / presentation / 발표

presentation

use the slide ratio and intended full-slide/inset placement

These are suggestions, not universal standards or size limits. If context is materially ambiguous, ask one short question such as “What page/content width or preferred figure size should I use?” For papers ask about single/double columns or venue width; for slides ask about 16:9/4:3 and full-slide/inset use. Reuse already supplied context. For a quick draft, state a provisional preset and size and invite correction. Use a -kr preset only for Korean plot text, not simply because the conversation is Korean. A geometry-only edit keeps the selected preset, data and other style choices. Presets supply defaults; explicit user overrides take priority. Use dm.fs(n), dm.fw(n) and dm.lw(n) for preset-relative changes; retain requested absolute sizes, widths and colors. Literal-size/width lint warnings are advisory: inspect and report them without replacing explicit values or hiding warnings.

Both width and height are freely chosen physical dimensions. For example, this complete script requests 24 × 12 cm using mixed units:

import matplotlib.pyplot as plt
import dartwork_mpl as dm

dm.style.use("report")  # illustrative choice for a report
fig, ax = plt.subplots(figsize=dm.figsize("24cm", "120mm"))
ax.plot([0, 1, 2], [1, 3, 2], linewidth=dm.lw(0))
ax.set_xlabel("Time")
ax.set_ylabel("Value", fontsize=dm.fs(0), fontweight=dm.fw(0))
dm.simple_layout(fig, margin="2%")
dm.save_formats(fig, "output/explicit-canvas", formats=("png", "pdf"),
                bbox_inches=None)
plt.close(fig)

The same canvas can use dm.figsize("240mm", "120mm") or dm.figsize(dm.inch(24 / 2.54), dm.inch(12 / 2.54)). Native plt.figure with GridSpec/nested GridSpec is also valid for custom panel allocations. dm.simple_layout adjusts content margins without resizing the canvas. bbox_inches=None is already the export default and retains that canvas; "tight" crops to content and can change its dimensions. DPI controls PNG pixel density, not physical size; PNG sizes quantize to whole pixels while PDF retains physical page size. The oversize-width lint warning above 17 cm asks for a page-fit review; it must not override an explicit size.

Prompt-driven native customization

Ask for the change in ordinary language, for example “Use report style and make only the axis labels one step larger.” The agent selects the native matplotlib label Text objects; the user need not supply plotting code. On an existing report figure, the corresponding edit is:

for label in (ax.xaxis.label, ax.yaxis.label):
    label.set_fontsize(label.get_fontsize() + 1)

This changes only axis-label size and retains tick labels, title, legend, data, colors, canvas and preset. Keep existing axes/layout when labels still fit. If a refit is necessary, reuse the original simple_layout kwargs, grid and margins instead of blindly rerunning margin=0 defaults. Disclose necessary axes or anchored-legend movement rather than promising fixed pixel positions. One current-relative step is +1 pt; repeating “one more” increments the retained current value. dm.fs(1) instead means global font.size + 1 pt; it does not inspect the current label or its role-specific default. dm.lw(n) adds n pt to global lines.linewidth, and dm.fw(n) adds 100*n to global font.weight. An absolute request such as “12 pt” retains that exact value.

Apply the preset before retained overrides and local artist edits. Use native setters for lines/custom dashes, spines, ticks, tick labels, axis labels, grids and other supported properties. Discover them through getters, artist.properties(), help(type(artist)) or official docs for the installed matplotlib version. Resolve the panel/series/artist before editing; ask one short question only for material ambiguity. The 18 templates are starting points, not chart limits. Choose unspecified defaults from data, purpose, panel density and categorical text; avoid unsolicited palette/legend changes. See native styling recipes. Agents can read the bundled native examples with dm.get_prompt("03-recipes") in installed Python, or the MCP dartwork-mpl://guide/recipes resource; dm.get_prompt("01-policy") gives the policy.

Installed Python agents: read, execute, review, revise

No MCP or repository checkout is required. Read installed guidance and both template tiers with existing Python APIs:

from importlib.resources import files
from pathlib import Path
import dartwork_mpl as dm

print(dm.get_agent_doc("AGENTS"))
print(dm.get_prompt("01-policy"))
print(dm.get_prompt("03-recipes"))
print(dm.get_prompt("02-anti-patterns"))

# Ranking metadata is available without MCP; source_path is repo-relative.
root = files("dartwork_mpl").joinpath("asset", "prompt", "05-templates")
Path("output").mkdir(exist_ok=True)
for tier in ("basic", "advanced"):
    match = dm.find_template("line time series", top_k=1, tier=tier)[0]
    folder = root if tier == "basic" else root.joinpath("advanced")
    source = folder.joinpath(f"{match['template_id']}.py").read_text(encoding="utf-8")
    print(tier, match["template_id"], match["source_path"])
    Path(f"output/template-{tier}.py").write_text(source, encoding="utf-8")
    print(source)  # inspect/adapt; retrieving a template does not execute it

Use dm.lint_code(code) for source findings and dm.validate_figure(fig) for the full visual warning list. Keep all fields, including CHECK_FAILED; dm.save_formats returns None, not a validation report. Save PNG/PDF with bbox_inches=None, open the real images and inspect them before handoff. dm.validate_data filters NaN/Inf rows by default; to keep missing-reading line gaps use allow_nan=True with separate shape/alignment/finite-value checks. It is distinct from the MCP validate_plot_data tool.

Follow the complete executable workflow for a native Figure, full report, PNG/PDF review and a separate-stem revision. Use native multi-panel recipes or dm.get_prompt("03-recipes") for equal grids, global ratios, spans/nesting and whole-canvas 70:30 or independent 40:60 / 70:30 rows.

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.

# 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 / tall / standard / golden / wide / a4 / slide / cinema / panoramic.

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 for the full lint catalog.

Multi-panel figures:

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”), a 20-family v5 system with 10 perceptual steps per family — and registers six third-party design systems alongside it. Use any named color anywhere matplotlib accepts a color string:

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

# Third-party systems also available for cross-team consistency
ax.plot(x, y2, color="oc.blue5")                   # OpenColor
ax.plot(x, y1, color="tw.emerald500")              # Tailwind

Discover what’s available without leaving Python:

import dartwork_mpl as dm

dm.list_colors()[:2]        # → [{'name': 'amber', 'kind': 'sequential', ...}, ...]
dm.colors("teal", n=5)      # designed 5-color list
dm.show_colors(names=["teal"], n=5)  # preview swatches and ramps

See Colors and Colormaps for the full palette reference, or open the interactive palette explorer to click-and-copy color names from your browser.

Multi-panel layout

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.red1")
ax2.plot(x, np.cos(x), color="dc.teal3")

dm.label_axes([ax1, ax2])  # adds (a), (b) panel labels
dm.simple_layout(fig, gs=gs)
Two-panel layout with label_axes showing sin and cos

Saving in multiple formats

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:

warnings, fixes = dm.validate_with_fixes(fig)
for w in warnings:         # each is a VisualWarning with fix suggestions
    print(w)
# e.g. MARGIN_ASYMMETRY → fix: dm.simple_layout(fig)

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:

# 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 the viewer in your default browser
cd my-viewer && python viewer.py

→ Interactive UI guide

Next steps

🎨 Styles and Presets

Choose the right preset for your use case — papers, reports, slides, posters.

→ Browse presets

🌈 Colors and Colormaps

Explore 900+ named palettes and perceptual OKLCH interpolation.

→ See palettes

📐 Layout and Typography

Panel labels, arrows, font scaling, and margin optimization.

→ Learn layout

💾 Save and Validation

Multi-format export + automatic visual quality checks.

→ Export guide

🛠️ Interactive UI

Tune fonts, line weights, margins with sliders. Export the exact script that reproduces what you see.

→ Interactive UI

🔬 Diagnostics & Templates

dm.show_colors() / plot_fonts() for asset audit, plus ready-to-use plot templates like plot_diverging_bar.

→ Extras guide

Note

Upgrading from a previous PyPI release? The Migration Guide lists every renamed / removed name since v0.4.0 with a side-by-side Before → After table — most call sites are one-liners.