Skip to main content

sprezzature-figures

🇫🇷 LISEZMOI.md · 🇬🇧 README.md

Python ≥3.10 License: BSD-3-Clause

logo

Publication-quality chart types, all hand-authored SVG callable as a Python library or a CLI command.

Part of the sprezzature suite.


Install

Requires Python 3.10–3.13. Tested on 🍎 macOS, 🐧 Ubuntu, and 🪟 Windows (CI runs the full suite plus a wheel-install check on all three).

pip install sprezzature-figures

Optional extras (combine as needed, e.g. "sprezzature-figures[cli,dataviz]"):

Extra Adds
[cli] the Click twin of the always-installed make-figure CLI
[dataviz] networkx / scikit-learn / pandas / shapely / pyproj / pyyaml — the data side (tables, geometry, models) behind the catalogue, causal inference and explainability; nothing here draws
[studio] Sprezzature Studio: the NiceGUI app + Ralph copilot (see below)
[api] FastAPI HTTP surface (see below)
[mcp] MCP (Model Context Protocol) tool surface on top of [api], for calling this library from an AI assistant (see below)

Use a virtual environment to keep things isolated:

🍎 macOS / 🐧 Ubuntu
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
pip install "sprezzature-figures[cli,dataviz]"
🪟 Windows (PowerShell)
py -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
pip install "sprezzature-figures[cli,dataviz]"

Verify the install:

make-figure --list --status stable
make-figure bar --out check.svg

Quick start

As a library

from sprezzature_figures import make_figure

data = [
    {"region": "North", "value": 42},
    {"region": "South", "value": 28},
    {"region": "East",  "value": 19},
    {"region": "West",  "value": 11},
]
path = make_figure("bar", data, out="revenue.png", title="Revenue by region")
print(path)  # PosixPath('revenue.png')

All 127 registered chart kinds are status="stable" (render-verified end to end); see docs/studio/GENERATOR_AUDIT.md for the per-chart audit detail, and make-figure --list --status stable to confirm the current set. Every stable kind tolerates optional roles being left unbound: it renders a sensible default rather than crashing.

As a CLI command

# List all available chart types (add --status stable to see only render-verified ones)
make-figure --list

# Render a chart using its built-in demo data
make-figure bar --out revenue.png --title "Revenue by region"
make-figure treemap --out budget.png --title "Budget breakdown"
make-figure funnel --out funnel.png

# Render your own data instead of the demo rows (.csv, .tsv, .json, .jsonl)
make-figure treemap --data budget.csv --out budget.png --title "Budget breakdown"

# When your columns don't match the figure's role names, bind them with --map
make-figure bar --data gdp.csv --map region=Country --map value=GDP --out gdp.png

# Pipe data in with --data - and upsample the raster with --scale
cat sales.jsonl | make-figure bar --data - --out sales@2x.png --scale 2

The output format follows the --out extension: .svg (default, fully self-contained with embedded fonts), .png, .pdf, .jpg, or .html. For raster and PDF output, --scale N upsamples N times for hi-DPI displays (--out chart.png --scale 3); it is ignored for the vector .svg/.html forms.

The --data file is read into one row dict per record: CSV/TSV cells are type-coerced (numbers stay numbers), and JSON accepts either a bare array of objects or an object wrapping a "data" array. Pass --data - to read the same formats from standard input (the shape is sniffed from the content). Column names should match the roles the chart expects (make-figure --list --status stable, then see FIGURES.md); when they don't, --map role=column binds them without touching the file.

Not sure which chart fits your file? Ask for a recommendation (needs the [cli] and [studio] extras). This runs the same deterministic compatibility + readability ranking the Studio GUI shows as cards, with no model involved:

sprezzature-figures recommend --data budget.csv
sprezzature-figures recommend --data budget.csv --render best.png  # + render the top pick
sprezzature-figures recommend --data budget.csv --intent hierarchy  # rank for a goal

Add --intent GOAL (comparison, trend, distribution, composition, relationship, flow, hierarchy, geography, model_evaluation) to rank the figures that serve that goal first. Without it, many kinds tie at the top because readability alone rarely separates them; the goal is what makes the ranking decisive.

From a picture of somebody else's chart

You are sent a screenshot of a chart that is hard to read, and asked to make it better. redraw does the reading: a vision model looks at the picture, says what kind of chart it is and what costs the reader effort, and the figure is drawn here.

sprezzature-figures redraw theirs.png --out ours.svg

# With your real numbers -- the mode whose output is worth publishing
sprezzature-figures redraw theirs.png --data sales.csv --out ours.svg

# Override what the model chose
sprezzature-figures redraw theirs.png --kind bar --title "Exports carry the quarter"
from sprezzature_figures import redraw

result = redraw("theirs.png", out="ours.svg", data=rows)
result.kind          # 'bar'
result.data_origin   # 'your-data'
result.changes       # what it does differently, costliest first

A picture of a chart carries two things, and carries them with very different confidence. The design — what kind of chart it is, what it is about, what it costs the reader — is legible from pixels. The data usually is not: a chart drawn without data labels does not contain its own numbers, and a model asked for them anyway will produce some, because that is what models do.

So redraw never guesses, and every result says which one it got:

data_origin What you have
your-data You passed rows. The image supplied only the design. The real figure.
read-from-image The numbers were printed on the original and read back. Approximate, and the figure says so on its face.
demo Nothing readable. The redesign on sample data — the right chart type and the house typography, captioned as such. Look at it; do not publish it.

That caption is written onto the figure itself, in a strip grown below the drawing, not just returned to the caller: the SVG outlives the function call and will be looked at by someone who never saw it.

Input can be PNG, JPEG, GIF, WebP or SVG — a screenshot is the usual case. A PDF is refused by name, with what to do instead. Needs a vision model: the [local] extra plus a running Ollama, same as every other model call in the suite.

What to expect from the model you run it on: a 7B vision model on a laptop takes about 90 seconds, reliably gets the chart kind and the text printed on the image, and reports one or two of the reading problems. The judgement fields (what_it_shows, a title that states the result) are where a bigger model earns its keep — when they come back empty, the redraw falls back to the original's own title rather than inventing one.



Chart catalogue

127 chart types across 21 categories. See FIGURES.md for the full table with per-chart guidance on when to use each type.

Quick overview:

Category Charts
Comparison bar, bar-grouped, bar3d, bubble, columnrange, difference-chart, dotplot, dumbbell, lollipop, packed-bubble, pareto, radial-bar, variwide, waterfall
Composition area, donut, parliament, pictorial, stacked-area, stacked-bar, ternary, waffle
Distribution beeswarm, bellcurve, blandaltman, boxen, boxplot, corr-matrix, ecdf, errorbar, hexbin, histogram, kde1d, kde2d-contour, mosaic, population-pyramid, ridgeline, rug, strip, violin
Flow alluvial, chord, funnel, parallel-sets, sankey
Geospatial binned-grid-map, choropleth, dotdensity, hexbin-map, hexmap, situation_map, spike-map, voronoi
Hierarchy circle-packing, convex-hull, dendrogram, icicle, org-chart, radial-tree, sunburst, tree, treemap
KPI bullet, gauge, liquid-gauge
Matrix / Image clustermap, heatmap, imshow-interpolated
Meteorology windbarb, windrose
Model evaluation calibration, confusion-matrix, elbow, gaussian-process, liftgain, manhattan, ppplot, prcurve, qqplot, roc-curve, survival-km
Network arcdiagram, dependency-wheel, edge-bundling, network, sfdp-largegraph
Regression regression-ci-band, residual
Relationship parcoords, scatter, volcano
Signal quiver, spectrogram, streamplot
Text wordcloud
3-D scatter3d, surface3d, wireframe3d
Time series bollinger, calendar-heatmap, candlestick, connected-scatter, horizon, line, line-multi, slope, step, streamgraph, timeline
Multivariate andrews, embedding_projector, jointplot, pairplot, radar, radviz, upset, venn
Meta-analysis forest
Animated gapminder, gapminder_variants
Other cycle, gantt, interruption-matrix, polar, rose, speaking_time

Visual themes

Every chart supports a theme parameter for how it looks (fonts, colors), kept separate from accessibility, which controls whether the palette stays readable for colour-vision deficiency (CVD, the general term for what is commonly called colour blindness). The two settings compose freely because they solve different problems: one is taste, the other is who can actually read the chart.

  • "corporate" (default): Roboto for the chrome text (title, subtitle, axis labels), Roboto Mono for tick and numeric labels, and a categorical palette derived from Apple's system colours. Renders byte-identical to every chart made before theme existed, so adopting the parameter changes nothing for existing callers.
  • "academic": Latin Modern Roman and Mono, the free, LaTeX-native extension of Computer Modern, for a journal-figure look, paired with the Okabe-Ito categorical palette (Okabe and Ito, 2002). That palette is CVD-safe by construction: it was designed so its colours stay distinguishable under the common forms of colour-vision deficiency, which is why it has been the standard recommendation for scientific figures since Wong's 2011 editorial in Nature Methods.
make_figure("bar", data, out="revenue.svg", theme="academic")
make-figure bar --out revenue.svg --theme academic

Both fonts are embedded (self-contained SVGs need no external font loading); licenses are vendored in assets/fonts/ (Roboto: OFL, Latin Modern: GUST Font License, both permit bundling).

theme="academic" also switches sequential colour ramps (heatmaps, hexbin density, cluster maps, and similar) to viridis, the perceptually-uniform, colour-vision-deficiency-safe colormap recommended for scientific figures; "corporate" keeps each chart's own tuned single-hue ramp unchanged.


Architecture

sprezzature-figures/
├── sprezzature_figures/
│   ├── __init__.py        # exports make_figure, list_kinds, get_figure_definition
│   ├── make_figure.py     # registry-backed dispatcher + argparse CLI
│   ├── cli.py             # Click entry point (optional, needs [cli] extra)
│   └── catalog/           # figure registry: FigureDefinition + figures.json
├── scripts/
│   ├── make_treemap.py            # self-contained chart script
│   ├── make_connected-scatter.py  # hyphenated kinds are supported
│   └── ...                        # 127 make_*.py scripts total
├── assets/
│   └── svg-examples/      # SVG template examples
└── tests/

Each make_<kind>.py script is self-contained: it imports what it needs, defines make_<kind>(data, *, out=None, title="", ...) -> Path and exposes a DEMO_DATA list for CLI and test use. make_figure() resolves the kind through sprezzature_figures/catalog/figures.json rather than guessing the filename; see docs/studio/GENERATOR_AUDIT.md for which of the 127 scripts currently satisfy this contract.


Adding a chart type

  1. Create scripts/make_<kind>.py following the pattern of any existing script.
  2. Expose DEMO_DATA: list[dict] and a function make_<kind>(data, *, out=None, title="", ...) -> Path.
  3. Add a row to FIGURES.md.
  4. Run python tools/audit_generators.py --render then python tools/build_figures_catalog.py to register it in sprezzature_figures/catalog/figures.json (without this, make_figure() only reaches it through a deprecated fallback and prints a warning).
  5. Run make-figure <kind> to verify the output.

Sprezzature Studio

This repository is two things:

  • The library (sprezzature_figures.make_figure, make-figure, sprezzature-figures CLI): everything above, with no extra dependencies beyond [cli]/[dataviz].

  • Sprezzature Studio (sprezzature_figures.studio, sprezzature-studio CLI): a local NiceGUI app to import a CSV/TSV/XLSX/JSON file, pick a chart type, bind columns, and refine the figure by chatting with Ralph, a copilot built on a language model that reads your text (an LLM) paired with a vision model that looks at the rendered image (a VLM). Ralph edits a structured plan and actually looks at the rendered PNG before deciding it's done. Needs the studio extra:

    pip install "sprezzature-figures[studio]"
    sprezzature-studio
    

    Ralph's LLM/VLM is resolved by best-engine-ai-helper, which talks to a local Ollama by default (text model qwen3:8b, vision model gemma3:12b; override with BEST_LLM_TEXT / BEST_LLM_VISION). The app starts and stays fully usable with no model at all: import, profiling, manual chart choice, property tweaks, history, and export all work in degraded mode; only the chat/critique features need a reachable model. Nothing leaves your machine unless you point it at a remote backend (DATA_PRIVACY.md).

    Full documentation: docs/studio/README.md.

There is no separate "Ralph CLI" in this repository. scripts/ ralph_eyeball_loop.py is a standalone, repo-internal visual-QA tool used while developing the chart generators themselves (see its own docstring); it predates and is unrelated to the Studio's Ralph engine (sprezzature_figures.studio.ralph), which is a from-scratch, plan-driven, testable implementation.


HTTP API & MCP

Beyond the command line, the same make_figure() function is reachable over the network in two ways: as a plain HTTP API, and as an MCP tool. MCP (the Model Context Protocol) is a standard that lets an AI assistant call a program's functions directly, the same way a human would call them from a script, instead of having to read documentation and guess. Three interfaces expose the same make_figure() dispatcher in total:

Interface Always installed? Entry point
CLI (argparse) Yes make-figure
CLI (Click) [cli] extra sprezzature-figures
HTTP API (FastAPI) [api] extra uvicorn sprezzature_figures.api:app
MCP tools (fastapi-mcp) [api,mcp] extras sprezzature-figures-mcp
pip install "sprezzature-figures[api]"
uvicorn sprezzature_figures.api:app --host 0.0.0.0 --port 8000

# List stable chart kinds
curl http://localhost:8000/kinds?status=stable

# Render the demo treemap as SVG
curl -X POST http://localhost:8000/render/treemap -o treemap.svg

# Render your own data
curl -X POST http://localhost:8000/render/bar -H 'Content-Type: application/json' \
     -d '{"data": [{"region": "North", "value": 42}], "title": "My chart"}' -o bar.svg

# Redraw somebody else's chart from a picture of it
curl -X POST http://localhost:8000/redraw -H 'Content-Type: application/json' \
     -d "{\"image_base64\": \"$(base64 < theirs.png)\"}" | jq -r .data_origin

# Full OpenAPI docs
open http://localhost:8000/docs

POST /redraw answers JSON rather than file bytes: the figure arrives base64-encoded in figure_base64, next to the diagnosis that justified it (kind, data_origin, changes, reading). Reading data_origin before using the figure is the point — see From a picture of somebody else's chart.

POST /recommend ranks which chart kinds your rows can fill, each with its role bindings already worked out — the same deterministic ranking the CLI's recommend command runs, over HTTP. It is the route to call before /render/{kind} whenever nobody has named a chart type.

The MCP surface (sprezzature-figures[api,mcp]) exposes the exact same routes as MCP tools (list_kinds, get_kind, recommend_figures, render_figure, redraw_figure) at /mcp on the same FastAPI app. Each carries a written summary and a description saying when to call it — see TRIGGERS.md for the routing rules an agent should follow. fastapi-mcp wraps the whole HTTP surface in one line, so the route definitions are never duplicated:

pip install "sprezzature-figures[api,mcp]"
sprezzature-figures-mcp

Development

git clone https://github.com/warith-harchaoui/sprezzature-figures.git
cd sprezzature-figures
pip install -e ".[dev,cli]"
ruff check sprezzature_figures/
python -m pytest tests/ -q

The default pytest run excludes four marked groups (see docs/studio/TESTING.md):

python -m pytest -m slow        # actually render figures (seconds each)
python -m pytest -m packaging   # build a wheel, install it in a fresh venv
python -m pytest -m llm         # hit a live text model via best-engine-ai-helper
python -m pytest -m vision      # hit a live vision model / VLM

The llm / vision tests skip (never fail) when no model backend is reachable, so they are safe to run without Ollama up. CI runs everything except llm / vision.

Alternative: conda (local) / Docker (server)

requirements.txt (-e .[cli,dataviz,studio]) is the shared dependency list behind both; pyproject.toml's extras stay the actual source of truth, and this file just selects which of them to install, so there is nothing to fall out of sync.

# local dev
conda env create -f environment.yaml
conda activate sprezzature-figures
pip install -r requirements-dev.txt   # pytest, ruff, playwright
playwright install chromium           # one-time, for headless-Chromium checks

# server (Studio app + CLI, same dependency set as above)
docker build -t sprezzature-figures .
docker run --rm sprezzature-figures --list

License

BSD 3-Clause. See LICENSE.

Author

Warith HARCHAOUI · warith.harchaoui@gmail.com · harchaoui.org/warith/sprezzature

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sprezzature_figures-2.3.0.tar.gz (11.3 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sprezzature_figures-2.3.0-py3-none-any.whl (3.2 MB view details)

Uploaded Python 3

File details

Details for the file sprezzature_figures-2.3.0.tar.gz.

File metadata

  • Download URL: sprezzature_figures-2.3.0.tar.gz
  • Upload date:
  • Size: 11.3 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.13

File hashes

Hashes for sprezzature_figures-2.3.0.tar.gz
Algorithm Hash digest
SHA256 d93713a7b0ba9e0c536e77b117845c57b6a4978b8572bb03a1c848625182f1e3
MD5 d744d3dab4b60e3204b692b9065493c0
BLAKE2b-256 17732ac3998b10fe32571a58602601617d68dd2b6c8e2f647447517a621961a9

See more details on using hashes here.

File details

Details for the file sprezzature_figures-2.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for sprezzature_figures-2.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5d46813269b730efbd6d8fcedbdea8170ead1d7fd833a2ef543b5eabf2d3bcdc
MD5 2f92e139c6ff963680bc99fc4a45cdf9
BLAKE2b-256 0fd30e8820ad4af45cc2004bd462afcc4676a32dea552817a13fa564c921999d

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

2.3.0 This release

2 files

2.2.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page