Mirage 🪄
Ultra-lightweight, browser-less static image exporter for Plotly in Python.
Zero Chrome. Zero Chromium. Zero Playwright. 100% Vector & Raster Fidelity.
The Problem with Kaleido & Headless Browsers
Historically, exporting static images (.svg, .png, .jpg, .webp) from Plotly in Python relied on Kaleido:
- Kaleido v0.1/v0.2 bundled a custom, monolithic Chromium build that frequently froze, deadlocked, or failed to install across various Linux distributions and architectures (like ARM64 / Apple Silicon).
- Kaleido v1+ transitioned to requiring a full installation of Google Chrome or downloading ~150MB+ Playwright browser binaries.
- In Docker containers, AWS Lambda, minimal Linux (Alpine), and CI/CD pipelines, installing Chrome or WebKit pulls in hundreds of megabytes of system packages (
libglib,libgtk,libnss,Xvfb), making deployments slow, fragile, and bloated.
How Mirage Works
Plotly's Python figures are JSON specifications. The actual layout math, coordinate scaling, D3 curves, and SVG construction live inside plotly.js.
Mirage executes Plotly's official chart engine without a browser:
┌───────────────────────────────┐
│ fig = go.Figure(...) │
└──────────────┬────────────────┘
│ fig.to_dict()
▼
┌────────────────────────────────────────────────────────┐
│ Mirage Engine (Embedded QuickJS Runtime) │
│ │
│ 1. Micro-DOM (~400 lines JS): │
│ - Minimal SVG/HTML element tree │
│ - D3 selector & attribute managers │
│ 2. Pillow Font Engine: │
│ - Answers getBBox() text measurements via PIL │
│ 3. Plotly 2D Engine: │
│ - Computes layout & renders vector chart │
│ 4. Extracts clean, standalone <svg> │
└──────────────────────────────┬─────────────────────────┘
│ Pure SVG Vector Output
▼
┌───────────────────────────────┐
│ resvg-py (Rust Wheel) │
│ Instant SVG ➔ PNG / JPEG │
└───────────────────────────────┘
- Embedded QuickJS Engine: Runs the official
plotly.jsbundle inside an embedded C JavaScript runtime (~1.5 MB wheel). - Micro-DOM: Implements the precise subset of DOM and SVG APIs that D3 and Plotly require to build the SVG scene graph.
- Pillow Font Engine: When Plotly asks for text bounding boxes (
getBBox()), Mirage queries Python'sPIL.ImageFontto measure text dimensions, ensuring clean margins, titles, and tick placements without overlaps. - Rust Vector Rasterization (
resvg-py): Converts SVG directly into crisp PNG, JPEG, or WEBP bytes in milliseconds with zero C++ system dependencies.
Performance Benchmark
| Metric | Kaleido (Chrome / Playwright) | Mirage | Speedup |
|---|---|---|---|
| Download / Install Size | 150MB – 300MB | ~12MB | ~25x smaller |
| Cold Startup Time | 2.5s – 4.0s | ~130ms | ~20x faster |
| Per-Plot Render Time | 1,500ms – 3,000ms | ~28ms | ~50x–100x faster |
| System Dependencies | Chrome / GTK / X11 / Xvfb | Zero (Pure wheels) | Complete portability |
Installation
From GitHub (Pre-PyPI / Development)
pip install git+https://github.com/ProfLear/mirage.git
Or for local development:
pip install -e .
From PyPI (Upcoming)
pip install plotly-mirage
Works out-of-the-box on macOS (Intel & Apple Silicon), Linux (Ubuntu, Debian, Fedora, Alpine/musl), and Windows 10/11.
Quickstart
1. Direct API
import plotly.express as px
import mirage
fig = px.scatter(x=[1, 2, 3, 4], y=[10, 11, 12, 13], title="Sales Growth")
# Export to SVG string or file
svg_str = mirage.to_svg(fig)
mirage.write_image(fig, "sales.svg")
# Export to PNG / JPEG / WEBP
png_bytes = mirage.to_image(fig, format="png", scale=2.0)
mirage.write_image(fig, "sales.png", scale=2.0)
mirage.write_image(fig, "sales.jpg")
mirage.write_image(fig, "sales.webp")
2. Drop-in Replacement for Kaleido (mirage.register())
If you have existing code using Plotly's native fig.write_image() or fig.to_image():
import plotly.express as px
import mirage
# Patch Plotly to use Mirage
mirage.register()
fig = px.bar(x=["A", "B", "C"], y=[1, 3, 2])
# These now run instantly via Mirage without Chrome or Kaleido!
fig.write_image("chart.png")
fig.write_image("chart.svg")
png_data = fig.to_image(format="png")
Supported Chart Types
Mirage currently supports all standard 2D Cartesian and Polar Plotly charts:
- Scatter & Line plots (
px.scatter,px.line) - Bar charts (
px.bar, grouped & stacked) - Histograms (
px.histogram) - Box & Violin plots (
px.box,px.violin) - Pie & Donut charts (
px.pie) - Heatmaps (
px.density_heatmap,go.Heatmap) - Multi-series legends, titles, annotations, and axis styling
(Note: 3D/WebGL plots like scatter3d and surface require an OpenGL GPU context and are not currently supported).
License
MIT License. Copyright (c) 2026 Benjamin Lear.
Metadata
Release files for plotly-mirage 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| plotly_mirage-0.1.0.tar.gz | 370.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| plotly_mirage-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 743.0 kB
Release files / plotly_mirage-0.1.0.tar.gz
| Download URL | plotly_mirage-0.1.0.tar.gz |
|---|---|
| Size | 370.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
31e22ffb353d16fe2eba0c998b6e77a3aa094790c1787f0432493cd0bab13979
|
|
BLAKE2b-256 checksum How to use checksums |
d012c4dfe2556d921b11e6450403c3c7fb82fad32b673301783af38909c82d32
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|
Release files / plotly_mirage-0.1.0-py3-none-any.whl
| Download URL | plotly_mirage-0.1.0-py3-none-any.whl |
|---|---|
| Size | 372.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
64bf09121cf1249008784a6984d179c91d75f87536d40661678677de88cc2cce
|
|
BLAKE2b-256 checksum How to use checksums |
5ebf3a043c44d2bc071f792f91a93edfad5b4a808e4ad2a1b0ee61b5b203b5b9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|