dartwork-mpl
Publication-quality matplotlib — a thin utility layer, not a wrapper.
dartwork-mpl keeps Figure / Axes 100% native and adds the parts matplotlib
makes tedious: a physical-width geometry API, curated style presets, an OKLCH-aware
color system, deterministic content-aware layout, visual validation, and a
first-class integration for AI coding assistants (an MCP server + a bundled prompt
corpus). You never learn a new plotting API — you keep writing matplotlib, just
without the friction.
import matplotlib.pyplot as plt
import dartwork_mpl as dm
dm.style.use("scientific") # 1. curated preset
fig, ax = plt.subplots(figsize=dm.figsize("13cm", "standard")) # 2. physical width × aspect
ax.plot(x, y, color="oc.blue5", lw=dm.lw(0))
ax.set_xlabel("Time [s]")
dm.simple_layout(fig) # 3. content-aware margins
dm.save_formats(fig, "figure", formats=("svg", "png", "pdf")) # 4. multi-format save
That four-step pattern — preset → figsize(width, aspect) → simple_layout →
save_formats — is the whole workflow. No tight_layout(), no hand-tuned
figsize=(w, h) arithmetic, no dpi= guesswork.
Installation
pip install dartwork-mpl # or: uv add dartwork-mpl
Requires Python 3.10+. The core install is intentionally lean; add an extra only when you need it:
| Extra | Enables | Pulls in |
|---|---|---|
[notebook] |
dm.show() inline SVG display in Jupyter |
ipython |
[mcp] |
the MCP server for AI assistants | fastmcp, httpx |
[ui] |
the interactive parameter viewer | fastapi, uvicorn |
pip install "dartwork-mpl[notebook]" # or [mcp], [ui]
Highlights
- Geometry, decoupled from inches.
dm.figsize("13cm", "standard")takes a physical width (cm / in / mm / pt, ordm.col1= 9 cm /dm.col2= 17 cm) and one of ten aspect tokens (square / portrait / tall / standard / golden / wide / a4 / slide / cinema / panoramic); the height follows. Bare numbers are rejected so the unit is always explicit. - Deterministic layout.
simple_layout(fig)measures every visible artist and places the GridSpec arithmetically — reproducible across machines, unliketight_layout()'s heuristics.margin="2%"(ordm.mm(2)) adds a buffer. - OKLCH-aware color. Named palettes (
oc.*Open Color,tw.*Tailwind,md.*,ad.*,cu.*,pr.*) plus aColorclass spanning OKLab / OKLCH / RGB / hex with perceptual interpolation (cspace) and gamut-correct mapping. - Curated styling. Seven presets (
scientific,report,presentation, …), each with a Korean-krvariant, and preset-relative scaling helpersfs/fw/lwso literals never drift when you switch themes. - Validation & export.
validate_figure(fig)flags overflow, text/legend overlap, tick crowding, and empty axes — invisible failures in headless agent pipelines.save_formats(fig, ...)writes SVG / PNG / PDF / EPS at once, with deterministic SVG/PDF/SVGZ output for unchanged figures. - AI-native. A bundled MCP server exposes lint + auto-fix, figure validation, color lookup, and the live policy corpus to Claude Code / Cursor / Windsurf. No-MCP agents read the same corpus from disk.
- Batteries included. Material Design Icons + Font Awesome 6 fonts, ready-made
plot templates (
plot_diverging_bar, …), and a FastAPI viewer for live tuning.
Core API at a glance
import dartwork_mpl as dm
# ── Geometry ──────────────────────────────────────────────────────────
dm.figsize("13cm", "wide") # width × aspect token → inch tuple
dm.figsize("13cm", 0.6) # ...or a numeric ratio / "8cm" / dm.cm(8)
dm.cm(13); dm.mm(170); dm.inch(4.6); dm.pt(24) # Length values
dm.col1; dm.col2 # 9 cm / 17 cm academic-column sugar
# ── Styling & scaling ─────────────────────────────────────────────────
dm.style.use("scientific") # apply a preset
dm.style.stack(["base", "font-scientific", "lang-kr"]) # compose
dm.fs(2); dm.fw(1); dm.lw(-0.3) # preset-relative font size / weight / line width
# ── Color ─────────────────────────────────────────────────────────────
ax.plot(x, y, color="oc.blue5") # named palettes register with matplotlib
dm.color("oc.blue5") # parse name / "#4285F4" / "rgb(...)" / "oklch(...)"
dm.oklch(0.7, 0.15, 150); dm.rgb(66, 133, 244); dm.hex("#4285F4")
dm.cspace("#FF0000", "#0000FF", n=5, space="oklch") # perceptual interpolation
dm.mix_colors("oc.blue5", "white", alpha=0.35)
# ── Layout & annotation ───────────────────────────────────────────────
dm.simple_layout(fig) # deterministic content-aware margins
dm.simple_layout(fig, margin="2%", gs=gs) # buffer + target a GridSpec
dm.label_axes(axes) # (a) (b) (c) panel labels
dm.annotate_value(ax, x[-1], y[-1], "peak") # compact value label
dm.annotate_corner(ax, "note") # tight corner annotation
dm.label_hline(ax, 0.8, "Reference", x="auto") # endpoint reference label
dm.place_legend(ax) # data/text-aware axes legend
dm.wrap_axis_label(ax, "y") # two-line overlong axis label
dm.arrow_axis(ax, "x", "Cost") # Low ◄── Cost ──► High
# ── Validate, export, icons ───────────────────────────────────────────
dm.validate_figure(fig) # overflow / overlap / tick-crowding / empty
dm.save_formats(fig, "fig", formats=("png", "svg", "pdf"), dpi=300)
mdi = dm.icon_font("mdi") # also "fa-solid" / "fa-regular" / "fa-brands"
# ── Plot templates ────────────────────────────────────────────────────
from dartwork_mpl.templates import plot_diverging_bar
fig, ax = plot_diverging_bar(labels=["A", "B"], neg_values=[-30, -15], pos_values=[40, 55])
See the usage guide and API reference for the full surface.
Common pitfalls
Three patterns trip up new users (and AI assistants) more than any others. The
built-in lint engine flags all three; dm.migrate_legacy_code rewrites them in
place.
| Pitfall | Why it's wrong | Use instead |
|---|---|---|
plt.subplots(figsize=(8, 5)) (raw inch tuple) |
dartwork-mpl's geometry is physical (cm/mm) and aspect-driven; raw tuples bypass the preset's typography pairing | plt.subplots(figsize=dm.figsize("13cm", "standard")) |
plt.tight_layout() / fig.tight_layout() |
Non-deterministic outer-margin solver; fights with simple_layout's GridSpec arithmetic | dm.simple_layout(fig) |
ax.set_title("…", fontsize=14) (raw font literal) |
Becomes wrong the moment you switch from scientific to presentation or a *-kr preset |
ax.set_title("…", fontsize=dm.fs(0)) (same for dm.lw(n), dm.fw(n)) |
Full catalog: 02-anti-patterns.yaml.
Reachable at runtime via dm.get_prompt("02-anti-patterns") or
lint_dartwork_mpl_code(code) over MCP.
Upgrading from v4? See the Migration Guide for the palette codemod and removed-name map.
Style presets
| Preset | Use case |
|---|---|
scientific |
Compact fonts for academic papers and journals |
report |
Reports and dashboards, cleaner spines |
minimal |
Tufte-style, data-ink focus — no spines or ticks |
presentation |
Large fonts for projected slides |
poster |
Extra-large fonts and thick lines for posters |
web |
On-screen readability for docs and notebooks |
dark |
Dark backgrounds for Jupyter and dark-mode slides |
Each has a Korean -kr variant (scientific-kr, report-kr, …) with Korean-aware
fonts. List them with dm.list_styles().
AI-assisted development (MCP)
dartwork-mpl ships a built-in Model Context Protocol server so AI coding assistants pull the current policy guides, color palettes, lint catalog, and helper tools straight into the chat — no copy-pasting docs. It exposes 16 tools (lint + auto-fix, figure validation, render, color lookup, info, chart-type recommender, layered-plot composer, advanced-tier render), 10 resources + 4 resource templates (the prompt corpus + 18 basic + 18 tier-2 advanced plot templates), and 2 prompts.
pip install "dartwork-mpl[mcp]" # installs fastmcp + httpx; adds the dartwork-mpl-mcp script
Point your client at the dartwork-mpl-mcp console script — e.g. Claude Code
(~/.claude.json) or Cursor (~/.cursor/mcp.json):
{
"mcpServers": {
"dartwork-mpl": { "command": "dartwork-mpl-mcp" }
}
}
Restart the client and ask it to list its MCP resources to confirm. Windsurf,
Antigravity, generic stdio setups, the full tool/resource catalog, and the
local-clone variant are covered in
docs/integrations/mcp_server.md.
No MCP? The same corpus is bundled in the wheel and reachable from Python —
dm.get_agent_doc("llms-full")(also"AGENTS","CLAUDE","llms") returns the text,dm.agent_doc_path(name)its path. The repo-rootCLAUDE.md/AGENTS.md/llms.txt(per the llmstxt.org spec) are the 30-second onboarding.
Documentation
- Quickstart — install, first figure, save
- Usage guide — width/aspect, layout, color, patterns
- Color system — palettes, OKLCH, colormaps
- Example gallery — rendered, copy-pasteable
- API reference — every public function and class
- Design philosophy — why thin utilities, not a wrapper
Project layout
src/dartwork_mpl/
├── units.py / scale.py # figsize, cm/mm/inch/pt, col1/col2 · fs/fw/lw
├── style.py # Style class + preset management
├── colors/ # Color (OKLab/OKLCH/RGB/hex) + named palettes
├── layout.py / annotation.py # simple_layout, label_axes, arrow_axis
├── validate.py / lint.py # validate_figure · lint + migrate_legacy_code
├── io.py / formatting.py # save_formats, show · format_axis_*
├── icon.py / font.py / cmap.py / diagnostics/ # fonts, colormaps, viz helpers
├── templates/ / helpers/ # plot templates · high-level composition helpers
├── agent.py / prompt.py # bundled LLM corpus · prompt guides
├── mcp/ # MCP server (server / resources / tools / prompts)
├── ui/ # interactive FastAPI viewer
└── asset/ # bundled styles, colors, fonts, icons, prompts
Contributing & issues
Bug reports and feature requests go to the GitHub issue tracker. Released under the MIT License.
No comments yet
Be the first to share your take.