sprezzature-figures
🇫🇷 LISEZMOI.md · 🇬🇧 README.md
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 beforethemeexisted, 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
- Create
scripts/make_<kind>.pyfollowing the pattern of any existing script. - Expose
DEMO_DATA: list[dict]and a functionmake_<kind>(data, *, out=None, title="", ...) -> Path. - Add a row to FIGURES.md.
- Run
python tools/audit_generators.py --renderthenpython tools/build_figures_catalog.pyto register it insprezzature_figures/catalog/figures.json(without this,make_figure()only reaches it through a deprecated fallback and prints a warning). - Run
make-figure <kind>to verify the output.
Sprezzature Studio
This repository is two things:
-
The library (
sprezzature_figures.make_figure,make-figure,sprezzature-figuresCLI): everything above, with no extra dependencies beyond[cli]/[dataviz]. -
Sprezzature Studio (
sprezzature_figures.studio,sprezzature-studioCLI): 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 thestudioextra: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 modelgemma3:12b; override withBEST_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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d93713a7b0ba9e0c536e77b117845c57b6a4978b8572bb03a1c848625182f1e3
|
|
| MD5 |
d744d3dab4b60e3204b692b9065493c0
|
|
| BLAKE2b-256 |
17732ac3998b10fe32571a58602601617d68dd2b6c8e2f647447517a621961a9
|
File details
Details for the file sprezzature_figures-2.3.0-py3-none-any.whl.
File metadata
- Download URL: sprezzature_figures-2.3.0-py3-none-any.whl
- Upload date:
- Size: 3.2 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5d46813269b730efbd6d8fcedbdea8170ead1d7fd833a2ef543b5eabf2d3bcdc
|
|
| MD5 |
2f92e139c6ff963680bc99fc4a45cdf9
|
|
| BLAKE2b-256 |
0fd30e8820ad4af45cc2004bd462afcc4676a32dea552817a13fa564c921999d
|