anyplot is an open catalogue of plotting examples. Each plot type starts as one library-agnostic specification, and an AI pipeline writes, renders, reviews, and maintains its implementations in 15 plotting libraries across Python, R, Julia, and JavaScript. On anyplot.ai, you compare the libraries side by side and copy working code.
I'm Markus Neusinger, a physicist working in data and AI: since 2013 I've built data pipelines, analyses and machine learning, and the apps people use them through. Models write anyplot's code; I lead them, with that experience across data, backends, frontends and hosting.
- I set the course. The vision, the decisions, and the structure that keeps a codebase written by agents from turning into a mess. Seven of those decisions are below.
- I know when to push back. The models know a lot. The job is knowing which question to ask, when to make a model research a point properly, and when a confident answer is wrong. Security is a good example: I know what has to hold, private data and no way to abuse the service, and asking the right question is often enough for a model to find what still needs hardening.
- I split, assign, test, and send back. Work goes to Claude Code, Claude in GitHub Actions, Gemini or Copilot by what it needs and what it costs. I test what comes back and return it until it's right.
This isn't one prompt and a wait. Since November 2025, more than 850 pull requests from my account have merged, and what I correct becomes a rule the agents follow. How anyplot is built with AI agents tells the whole story.
One spec in each library, drawn from the live catalogue by
scripts/readme_showcase.py. Browse all plots.
Seven decisions I made and hold the agents to, each with its reason and the code that carries it out:
- A review loop instead of hand-edits. No generated plot is patched by hand. An AI reviewer scores every render on a 24-criterion rubric, blind to any earlier score, and a repair loop fixes the defects it names, against a bar that starts at 90 of 100 and drops 10 points per repair. A fix to a prompt or a spec fixes every plot it governs. (rubric, review workflow)
- A gate against silent regressions. Every night the oldest implementations are regenerated, and a new
version replaces the live one only when a pairwise review of both finds a visible fix and the new score
drops by no more than a point. A newer model can't quietly make the catalogue worse.
(
regen_gate.py,daily-regen.yml) - One agent talks, code decides. In "Use with my data", one Google ADK agent talks to the user. The
per-library adapters and the reviewer run inside a pipeline whose loop is plain code: at most one repair
round, at most two reviews, and the spec, library, and data come from server state, so neither the model
nor an injected prompt can redirect a run. (
pipeline.py, design) - Model-written code runs where it can't do harm. No generated code runs in the agent service. A
validator (Python's
ast, tree-sitter for R, Julia, and JavaScript) refuses disallowed imports and calls first; a separate renderer then runs the code in Cloud Run sandboxes (gVisor) with no network egress, no metadata server, and a read-only root filesystem, under a service account that holds no IAM role. (renderer, validator) - Models choose, the server acts. The assistant never writes data rows: it picks from a closed set of typed operations, which the server applies with pandas and reports with counts. Guardrail plugins check every turn: a scope judge that fails closed, token and run budgets per request, user, network, and day, and a tool allowlist per agent. (operations, plugins)
- Private by construction. Chats and data live only in server memory and are dropped after an hour idle, and the session id never appears in a URL. Usage telemetry holds only enums, buckets, and counts, keyed by hashes under a per-process random salt, and a canary test fails if any message or data value reaches a log line. (telemetry, canary test)
- Rules that turn a repeated mistake into a check. A mistake that comes back becomes a test or a written rule, with the incident beside it. One test keeps the assistant's prompt in step with the buttons the chat page renders; another asserts that no agent has a code executor or a search tool. After a green deploy left anyplot.ai blank for 45 minutes, every merge ends with a smoke test of the live site, and CI runs the same check on the built image. Around them: a changelog fragment per pull request checked in CI, every migration run against a throwaway PostgreSQL, CodeQL, and Dependabot. (prompt-control test, site smoke test, guardrails)
The site and "Use with my data":
flowchart TB
browser(["Browser"]) --> cf["Cloudflare"]
cf --> app["anyplot-app<br>React SPA"]
cf --> api["anyplot-api<br>FastAPI, MCP server<br>agent BFF, Turnstile<br>per-network caps"]
browser -. "plot images" .-> gcs[("Cloud Storage")]
api --> sql[("Cloud SQL<br>PostgreSQL")]
api -- "ID token,<br>catalogue snapshot" --> chat
subgraph chat ["Use with my data"]
direction LR
agents["anyplot-agents<br>Google ADK<br>Claude Haiku 5.5<br>Vertex AI, EU"] -- "ID token" --> renderer["anyplot-renderer<br>gVisor sandboxes<br>no egress<br>zero-role account"]
end
The catalogue pipeline, in GitHub Actions, writing to the same storage:
flowchart TB
spec["spec-create"] --> gen["impl-generate"]
regen["daily-regen"] --> gen
gen --> review["impl-review<br>24-criterion rubric"]
review -- "below the bar" --> repair["impl-repair"] --> review
review -- "approved" --> merge["impl-merge"]
merge -- "promote images" --> gcs[("Cloud Storage")]
merge -- "sync" --> sql[("Cloud SQL")]
All of it runs live at anyplot.ai.
| Specifications | Implementations | Libraries | Languages |
|---|---|---|---|
| 325 | 4,863 | 15 | 4 |
Counted on 2026-11-02 from the public API (https://api.anyplot.ai/stats); the stats page
shows the current numbers. 316 of the 325 specs exist in all 15 libraries; the other nine, mostly 3D and map plots,
lack one or two.
| Language | Libraries |
|---|---|
| Python | matplotlib, seaborn, plotly, bokeh, altair, plotnine, pygal, lets-plot |
| R | ggplot2 |
| Julia | Makie.jl |
| JavaScript | Chart.js, D3.js, ECharts, Highcharts, MUI X Charts (React) |
- Compare libraries. Each spec page shows the same plot in every library, usually all 15, in a light and a dark theme, each with its code and its review score.
- Search and filter. A typo-tolerant search matches spec titles and tag values, and the matches become filters: language, library, plot type, data structure, domain, and features, plus implementation tags such as techniques, dependencies, and styling.
- Explore by similarity. The map draws the catalogue as a graph of specs linked by tag similarity, and every spec page lists related specs.
- Use it from an AI assistant. The MCP server serves the same catalogue to coding agents.
AI writes every implementation, most of them Claude running in GitHub Actions. GitHub issues hold the state of each plot.
- Request. Someone opens an issue, from GitHub or from a link on anyplot.ai.
- Specification.
spec-create.ymlchecks for duplicates, assigns a spec ID and tags, and opens a pull request with the spec. The maintainer approves it on the issue. - Generation.
impl-generate.ymlwrites one implementation per library and renders it in both themes. - Review.
impl-review.ymlfirst runs automated checks: the code parses and runs, the image exists and isn't blank, and the canvas has its target size. An AI reviewer then scores the render, blind to any earlier score, on a 100-point rubric of 24 criteria (prompts/quality-criteria.md): visual quality 30, design 20, spec compliance 15, data quality 15, code quality 10, and library mastery 10. - Repair. Below the bar,
impl-repair.ymlfixes the defects the review named, and the plot is reviewed again. - Merge.
impl-merge.ymlmerges the pull request, promotes the images from staging to production in Cloud Storage, and syncs PostgreSQL.
- A first review needs 90 of 100. Each repair lowers the bar by 10 points: 80, 70, 60, and 50 after the fourth and last repair. An implementation still below 50 is closed and marked failed.
- The rubric reserves 90 and more for a plot that "should genuinely impress a data visualization professional". The reviewer never sees the thresholds.
- Technical criteria start at full points and lose them only through a written defect line that names the criterion, the observed value, and the fix. Design and library criteria start at a default and rise only with named evidence.
- Code that fails to run, a blank image, clipped labels, or a static library faking interactivity scores 0.
- One exception to the bar: a review of 80 or more that names no defect and gives every technical criterion its full points is approved, because a repair would have nothing to fix.
daily-regen.yml regenerates the oldest part of the catalogue every night: by default the oldest spec in every
library, and since 2026-10-31 the two oldest single implementations instead. A regeneration replaces the live
implementation only when its review, which scores both versions, finds a fixed defect and no regression (the regen
gate).
For the labels, retries, and every workflow, see Workflows.
Status: experimental, open to every visitor since 2 November 2026.
On a plot page, choose use with my data and paste your own data. An agent adapts that plot's catalogue code to your data, renders it in a sandbox, reviews the render against the catalogue's rubric, repairs it once if needed, and returns the image and the code.
- Agents. A Google ADK service on Cloud Run. The model is Claude Haiku 5.5 through Vertex AI in the EU; Gemini 3.8 Flash is a configurable second arm. Runtimes exist for all 15 libraries.
- Sandboxed rendering. No model-written code runs in the agent service. A separate renderer runs it in Cloud Run sandboxes (gVisor): no network egress, no metadata server, and a read-only root filesystem. The renderer's service account holds no IAM role.
- Static validation. Before anything runs, a validator reads the code (Python's
ast, tree-sitter for R, Julia, and JavaScript) and refuses disallowed imports and calls. - Guard judges. A scope judge reads every typed message before any agent sees it and fails closed; a dataset judge checks pasted data and notes. Spend limits apply per user and per day, and a budget guard switches Vertex AI off when its monthly budget is reached.
- Evals. A model-regression harness runs synthetic, stress, and promoted real cases; separate evals score the scope judge and the data preparation.
- Content-free telemetry. Usage events hold only enums, buckets, counts, and IDs from closed sets. No message, data value, or column name reaches a line, and a test enforces it.
Read the agent network design, the agents README, and the telemetry reference.
anyplot runs an MCP (Model Context Protocol) server, so AI assistants can search the catalogue and fetch implementation code.
| Tool | What it does |
|---|---|
list_specs |
Lists all plot specifications |
search_specs_by_tags |
Finds specs by tags: plot type, data type, domain, features, library, and implementation tags |
get_spec_detail |
Returns a full specification with its implementations |
get_implementation |
Returns the code for one library |
list_libraries |
Lists the supported libraries |
get_tag_values |
Lists the values of a tag category |
Add it to your MCP client configuration, for example Claude Code's .mcp.json:
{
"mcpServers": {
"anyplot": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://api.anyplot.ai/mcp/"]
}
}
}The server speaks Streamable HTTP only. Clients with native support connect to https://api.anyplot.ai/mcp/ without
the mcp-remote bridge. The legacy SSE endpoint (https://api.anyplot.ai/sse/) has been removed. For details, see
MCP server.
| Area | Stack |
|---|---|
| Backend | Python 3.14, FastAPI, SQLAlchemy (async), PostgreSQL, Alembic |
| Frontend | React 19, TypeScript, Vite, MUI |
| Agents | Google ADK, Claude Haiku 5.5 on Vertex AI, Cloud Run sandboxes |
| Pipeline | GitHub Actions with Claude Code |
| Infrastructure | Cloud Run, Cloud SQL, Cloud Storage, Cloud Build, BigQuery, Cloudflare |
Each render runtime is pinned: Python by pyproject.toml and uv.lock, the JavaScript libraries by package.json and
package-lock.json, Julia by Project.toml and Manifest.toml, and R by a dated Posit Package Manager snapshot.
anyplot/
├── plots/ # One directory per plot type: spec, metadata, implementations
├── prompts/ # Generation and review prompts, the quality rubric
├── .github/workflows/ # The pipeline (spec-create, impl-*, daily-regen) and CI
├── api/ # FastAPI backend and the MCP server
├── app/ # React frontend
├── agents/ # "Use with my data": ADK agent service, sandbox renderer, evals
├── core/ # Shared Python logic: database, palette, checks
├── automation/ # Workflow scripts: sync, labels, the regen gate, the JS render harness
├── alembic/ # Database migrations
├── tests/ # Unit, integration, and end-to-end tests
├── infra/ # Cloudflare configuration
├── scripts/ # Maintenance scripts
├── tools/, changelog.d/ # Changelog tooling and one changelog fragment per pull request
├── agentic/ # Commands and docs for the coding agents that work on this repository
└── docs/ # Documentation
The root package.json and package-lock.json pin the JavaScript render runtime, and Project.toml and
Manifest.toml pin the Julia one. A plot directory looks like this:
plots/scatter-basic/
├── specification.md # Library-agnostic specification
├── specification.yaml # Tags, creation date, issue
├── metadata/ # Per-library quality score, review, preview URLs
└── implementations/
├── python/ # matplotlib.py, seaborn.py, … (8 files)
├── r/ggplot2.R
├── julia/makie.jl
└── javascript/ # chartjs.js, d3.js, echarts.js, highcharts.js, muix.tsx
For every directory, see Repository structure.
You contribute ideas, not code: the pipeline writes every implementation.
| Action | When to use | On anyplot.ai |
|---|---|---|
| Suggest a spec | Propose a new plot type | spec.suggest() on the home page and the specs list |
| Report a spec issue | A specification has a problem | "report issue" on the spec page |
| Report an implementation issue | One library's implementation has a problem | "report issue" on the spec page |
- Open an issue, on GitHub or through a link on anyplot.ai.
- The AI validates and processes it.
- The maintainer reviews and approves it.
- The pipeline generates or fixes the code, and the review gates it.
Don't open pull requests with plot code. A plot with quality problems usually needs a better spec, not hand-edited code. For details, see Contributing. To run the project locally, see the development guide.
- Vision: the product vision
- Workflows: the automation and its labels
- Agent network: the design of "Use with my data"
- How anyplot is built with AI agents: who does what, the path of a change, and how corrections become rules
- Reference: API, MCP, database, repository structure, and style guide
- Documentation index: everything else
Most plotting libraries are fully open source. Highcharts is free for non-commercial use; commercial use requires a license from highcharts.com.
The project is under the MIT License; see LICENSE. The JetBrains Mono fonts bundled in
agents/anyplot/render/fonts/ for the footer strip of user plots are third-party files under the SIL Open Font License
1.1; see their notice.
- Website: anyplot.ai
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Analytics: public Plausible dashboard
Built by Markus Neusinger
