◉ Pulse
jaychouchannel avatar
#43 today

jaychouchannel/PlotCraft

TypeScript AI

AI-powered scientific figure generator: natural language → matplotlib code → sandboxed SVG rendering. Local-first, Fernet-encrypted API keys, model-agnostic (OpenAI-compat/Gemini), 8 built-in Nature-style templates, editable & re-renderable code, full SQLite history. FastAPI · React 19 · matplotlib.

Created Jul 21, 2026
Stars
7
Today
+7
Forks
1
Quality
80/100
Momentum
17

README

Fetched from GitHub on Oct 30, 2026, 9:23 PM UTC · README.md

View original on GitHub

Third-party content — Open Source Pulse is not the owner of this repository.

This README belongs to jaychouchannel/PlotCraft and its contributors. We fetch it directly from the project’s public GitHub repository and display it for reference only. All text, code, images and trademarks remain the property of their respective authors. See the repository for its license terms. Copyright / takedown policy.

PlotCraft — AI-powered vector graphics generator for scientific papers

PlotCraft

AI-powered vector graphics generator for scientific papers · Sandboxed rendering · Publication-ready

Describe your figure in natural language. PlotCraft calls an LLM to generate matplotlib code, executes it in an isolated sandbox on the backend, and produces SVG vector graphics at Nature / Cell / Science quality — all API keys encrypted locally, all code running locally, all data staying on your machine.


What is this?

demo figure 1

Scientific plotting has long been the most tedious part of paper writing:

  • GraphPad / Origin are maze-like, requiring dozens of clicks for one figure;
  • Hand-writing matplotlib means juggling color schemes, fonts, statistical annotations, and vector export — endlessly looking up docs;
  • AI tools (GPT-4 / Gemini) can write plotting code but can't actually run it — the loop stays inside the chat.

PlotCraft closes the entire pipeline in one go: natural language → code → render → vector output.

  1. You describe your figure in one sentence ("5 curves, x=0~10, y=sin(x), label Time (s) / Amplitude");
  2. The backend calls your configured LLM to generate matplotlib Python code;
  3. The code runs in an isolated subprocess sandbox — forced Agg backend, no network, 60s timeout, import blacklist (os, sys, socket, subprocess, requests, pathlib, etc.);
  4. The rendered output.svg streams back to the frontend for inline preview — downloadable, editable, re-renderable;
  5. Every session (prompt + code + SVG) is persisted to local SQLite history — replayable, searchable, cleanable.

This is not another ChatGPT wrapper. It's a local-first tool that closes the gap between LLM-generated code and actually running it to produce a figure.


Key Features

🎯 Vector-first, publication-ready

  • Renders SVG directly (plt.savefig(format="svg", dpi=300, bbox_inches="tight")), lossless at any zoom, ready for LaTeX / Word / InDesign;
  • System prompt enforces Nature-style color palette (#E64B35 #4DBBD5 #00A087 #3C5488 #F39B7F #8491B4) and typography conventions: axis labels in 12pt, tick labels 10pt, legends 10pt, titles 14pt; semantic significance markers (*p<0.05, **p<0.01, ***p<0.001);
  • 8 built-in templates: line (with error bars), grouped bar (significance), scatter + regression, box + violin, heatmap, radar, dual-axis line, Kaplan-Meier survival — covering life sciences, materials science, and clinical medicine.

🔒 Local encryption · Keys never leave your machine

  • API keys are Fernet-encrypted (cryptography lib) before storage in local SQLite, key held by ONE_ENCRYPT_KEY env var;
  • Frontend only receives masked api_key_masked — plaintext never leaves the server;
  • No third-party cloud dependency; data stays fully on your machine in self-hosted deployment.

🏝 Sandboxed execution · LLM code stays constrained

  • Each render gets a fresh temporary directory (tempfile.mkdtemp), cleaned up on exit;
  • Environment-level safeguards: NO_PROXY=*, forced MPLBACKEND=Agg (no GUI popups);
  • System prompt prohibits os / sys / socket / requests / httpx / subprocess / shutil / pathlib / importlib imports and any file I/O beyond output.svg;
  • Subprocess killed after 60s timeout to prevent infinite loops;
  • On failure, last 500 characters of stderr are returned to the frontend for rapid iteration.

🔌 Model-agnostic · Multi-provider unified interface

  • Two built-in providers:
    • openai_compat: any OpenAI Chat Completions-compatible API (DeepSeek, Qwen, Moonshot, Zhipu, Together, OpenRouter, local vLLM, etc.);
    • gemini: Google Gemini (google-genai SDK);
  • replicate interface reserved for future photorealistic bitmap model integration;
  • Models can be added, updated, and deleted from the UI at any time — no code changes needed.

📝 Template system · Inheritable presets, custom overrides

  • 8 built-in templates with system_prompt + user_template placeholders (e.g., {xlabel}, {series}), auto-seeded into SQLite at startup;
  • Built-in templates are read-only (to prevent accidental edits); clone them for customization;
  • Templates are categorized, filterable by category in the frontend dropdown;
  • The prompt editor pre-fills from the template but remains freely editable — templates are a starting point, not a ceiling.

🖥 Modern frontend · Editable code, re-render at zero LLM cost

  • React 19 + TypeScript 7 + Vite 8 + Tailwind 4;
  • CodeMirror editor (VSCode Dark theme + Python syntax highlighting) for online code editing;
  • "Re-render" button POSTs modified code to /api/generate/render — no LLM quota consumed;
  • SVG preview via dangerouslySetInnerHTML (sandbox already air-gaps networks; SVG is local-only), right-click to save;
  • One-click downloads: .svg vector and .py source code.

🗂 Full history · Never lose an experiment

  • Every generation (success, failure, or code-only) lands in the generations table with model_id, template_id, user_input, generated_code, status, error, and created_at; SVG content is stored in a separate generation_svgs table;
  • History page shows last 50 records, supports single delete and full clear;
  • Failed runs are preserved for side-by-side debugging.

Tech Stack

Layer Technology
Backend FastAPI · Pydantic · aiosqlite · aiofiles · cryptography · openai · google-genai
Rendering matplotlib (Agg backend) · numpy · pandas · scipy (as needed)
Frontend React 19 · TypeScript 7 · Vite 8 · Tailwind 4 · @uiw/react-codemirror · react-router-dom 7
Storage SQLite (model configs / templates / history)
Sandbox tempfile + subprocess + Agg + no-network + timeout kill

Project Structure

PlotCraft/
├── backend/
│   ├── app/
│   │   ├── main.py              FastAPI entry, CORS + lifespan + template seeding
│   │   ├── config.py            Environment variable loader
│   │   ├── crypto.py            Fernet encryption/decryption
│   │   ├── db.py                aiosqlite connection + table init
│   │   ├── executor.py          Sandbox execution (core security boundary)
│   │   ├── prompts.py           Scientific plotting system prompt template
│   │   ├── models.py            Pydantic data models
│   │   ├── providers/
│   │   │   ├── base.py          Provider abstract base class
│   │   │   ├── factory.py       Provider factory
│   │   │   ├── openai_compat.py OpenAI-compatible protocol
│   │   │   └── gemini.py        Google Gemini
│   │   └── routes/
│   │       ├── generate.py       /api/generate + /api/generate/render
│   │       ├── history.py        History CRUD
│   │       ├── models.py         Model config CRUD
│   │       └── templates.py      Template CRUD + startup seeding
│   ├── templates_seed/           8 built-in figure JSON templates
│   │   ├── 01_line_with_errorbar.json
│   │   ├── 02_grouped_bar_significance.json
│   │   ├── 03_scatter_regression.json
│   │   ├── 04_box_violin.json
│   │   ├── 05_heatmap.json
│   │   ├── 06_radar.json
│   │   ├── 07_dual_axis_line.json
│   │   └── 08_kaplan_meier.json
│   ├── requirements.txt
│   └── .env.example
├── frontend/
│   ├── src/
│   │   ├── App.tsx              Routes + navigation
│   │   ├── main.tsx             Entry point
│   │   ├── index.css            Tailwind
│   │   ├── lib/api.ts           Frontend API client
│   │   ├── components/
│   │   │   └── SetupBanner.tsx  Setup prompt when no key is configured
│   │   └── pages/
│   │       ├── Generate.tsx     Main generation page
│   │       ├── History.tsx      History page
│   │       ├── ModelConfig.tsx  Model configuration page
│   │       └── Templates.tsx    Template management page
│   ├── index.html
│   ├── package.json
│   ├── tsconfig.json
│   └── vite.config.ts
└── .gitignore

Quick Start

1. Backend

cd backend
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate

pip install -r requirements.txt

# Generate a Fernet key
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

# Write the key to .env
cp .env.example .env
# Edit .env, paste the key into ONE_ENCRYPT_KEY=
# You can also change HOST / PORT / DB_PATH

# Start the server
uvicorn app.main:app --reload --port 8000

Open http://127.0.0.1:8000/docs to see the OpenAPI docs.

2. Frontend

cd frontend
npm install
npm run dev

Open the Vite URL shown in the terminal (default http://localhost:5173).

3. Configure a Model

Go to the "Models" page and add a model:

  • OpenAI-compatible (DeepSeek example)
    • provider: openai_compat
    • base_url: https://api.deepseek.com/v1
    • model_name: deepseek-chat
    • api_key: your DeepSeek key
  • Gemini
    • provider: gemini
    • model_name: gemini-2.5-flash
    • api_key: Google AI Studio key
  • Local vLLM / Ollama (OpenAI-compatible)
    • base_url: http://localhost:8000/v1 (vLLM) or http://localhost:11434/v1 (Ollama)
    • api_key: any non-empty string for local services

4. Generate Your First Figure

  1. Go to the "Generate" page, select a model + a template (e.g., "Scatter + Linear Regression");
  2. The template prompt auto-fills into the editor; adapt it to your data:
    Draw a scatter plot with linear regression:
    - x: cell diameter (μm), y: protein expression (RFU)
    - 30 samples, scatter with noise
    - fit line + 95% confidence interval shading
    - show R² and p-value on the chart
    - Nature-style color palette
    
  3. Click "Generate" — the SVG preview appears on the right in seconds;
  4. Happy → download .svg / .py; not happy → edit the code in CodeMirror → click "Re-render" (zero LLM cost).

Demo

The figures below were generated entirely by PlotCraft — natural language in, matplotlib code generated by an LLM, rendered in the backend sandbox, exported as SVG (re-rasterized here for GitHub preview).

demo figure 2 demo figure 3

Left: multi-series line with error bars · Middle: grouped bar with significance markers · Right: scatter with linear regression + 95% CI


Built-in Template List

# Category Template Name Typical Use Case
01 Line Multi-series line with error bars Time course, dose-response curves
02 Bar Grouped bar with significance Multi-group comparisons + ANOVA markers
03 Scatter Scatter + linear regression Correlation analysis, R² / p-value
04 Distribution Box + violin Inter-group distribution comparison
05 Heatmap Heatmap Gene expression, confusion matrix
06 Radar Radar chart Multi-dimensional score comparison
07 Dual-axis Dual-axis line Different units on one chart (temp vs yield)
08 Survival Kaplan-Meier Survival analysis + log-rank test

Security Model

Risk Mitigation
LLM-generated malicious code reads local files AST static analysis blocks os / sys / shutil / pathlib / subprocess / open(w+) / __import__ / eval / exec etc. before subprocess exec (code_path does not accept route input); subprocess runs in an isolated temp directory
LLM sends data to public networks System prompt bans socket / requests / httpx; env sets NO_PROXY=*; downstream network namespaces can be added
Infinite loop / deadlock exhausts CPU Subprocess killed after 60s timeout
API key leakage Fernet symmetric encryption to database; frontend only sees masked strings
SVG XSS SVG rendered inline in frontend DOM; sandbox has no network; no external resource loading

⚠️ Production note: CORS defaults to allow_origins=["*"] with allow_credentials=False, suitable for local single-user use. For multi-user or public deployments requiring auth, set CORS_ORIGINS env to a comma-separated whitelist (e.g., https://your.domain.com) — allow_credentials enables automatically when the whitelist is non-empty. The whitelist must not contain wildcards when credentials are enabled.


When to Use

Good for

  • Individual researchers / graduate students / postdocs producing paper figures quickly
  • Lab-internal shared plotting tool (self-hosted on a group server)
  • Teaching: showing students how an LLM maps natural language to executable matplotlib code

Not suitable for

  • Enterprise multi-user scenarios requiring compliance audit trails (no authentication / audit log)
  • Replacing professional publishing tools (Adobe Illustrator can still polish SVGs)
  • Interactive 3D or large-scale WebGL rendering (sandbox only runs matplotlib)

Roadmap

  • PDF / EPS export (currently SVG only)
  • Batch generation (multi-panel figures from one prompt)
  • Figure parameter cloning ("same style, different data")
  • Replicate / FAL photorealistic provider
  • Client-side sandpack-js pre-rendering to reduce Python subprocess overhead

License

This project is licensed under the MIT License — see the LICENSE file for details. You are free to use, modify, and distribute this software, provided the copyright notice and license text are included.


Acknowledgements

  • Color inspiration: ggsci — Nature journal palettes
  • Template design: matplotlib gallery + common paper figure conventions
  • Sandbox inspiration: Jupyter %%script / nbconvert isolate mode
Rendered by GitHub from the repository’s README. Scroll to read the full file.
First trending

Jul 21, 2026

first spotted on the list

Best rank

#43

highest position reached

Appearances

1

times on the trending list

Quality score

80

out of 100

Momentum

17

trend strength indicator

Star velocity

0.07

stars per day since creation

Activity — last 60 days

daily counts, collected every 6 hours
  • Stars
  • Forks
  • Merged PRs
  • Issues
  • Closed issues

Collecting activity data since launch — check back in a few days.

Star history

total stars per snapshot

Not enough snapshots yet — check back soon.

Rank history

position on trending · lower is better

Not enough snapshots yet — check back soon.

3D card for your website

Live HTML card with a glass surface and pointer tilt. Paste the iframe on any website.

<iframe src="https://www.notionpulse.cc/embed/jaychouchannel/PlotCraft" width="420" height="240" style="border:0;max-width:100%;background:transparent" loading="lazy" title="jaychouchannel/PlotCraft — trending on Open Source Pulse"></iframe>

Stars badge

Show current stars in your README or profile. Auto-updates every 6 hours.

jaychouchannel/PlotCraft stars badge
Markdown
[![jaychouchannel/PlotCraft](https://www.notionpulse.cc/api/badge/jaychouchannel/PlotCraft.svg)](https://www.notionpulse.cc/repo/jaychouchannel/PlotCraft)
HTML
<a href="https://www.notionpulse.cc/repo/jaychouchannel/PlotCraft"><img src="https://www.notionpulse.cc/api/badge/jaychouchannel/PlotCraft.svg" alt="jaychouchannel/PlotCraft stars badge" /></a>

Repository of the Day

#43 Repository of the Day

Repository of the Week

#43 Repository of the Week

Repository of the Month

#43 Repository of the Month