xviz-cli
v1.0.0
Published
Headless chart renderer — turn JSON, CSV, or SQL query results into PNG / PDF / HTML. Ships render / query / serve / mcp commands. Stable 1.x — see VERSIONING.md.
Maintainers
Readme
xviz
Headless chart renderer. Takes JSON or CSV data in, emits PNG / PDF / HTML out.
Built on @minimal-viz/core + Puppeteer.
Stable 1.0 — see VERSIONING.md for the SemVer commitment.
Three modes:
xviz render— one-shot CLI renderingxviz serve— HTTP server withPOST /renderxviz mcp— MCP server for LLM tool-use (Claude, etc.)
Supports 39 chart types in the default build: pie bar line table big-number scatter heatmap sankey funnel gauge boxplot histogram treemap sunburst radar waterfall step tree graph timeseries-bar timeseries-line mixed-timeseries gantt big-number-total big-number-pop time-table pivot-table calendar rose parallel bullet compare partition time-pivot chord horizon paired-ttest world-map country-map.
For 13 additional deck.gl-powered map types (deck-scatter, deck-path,
deck-polygon, deck-arc, deck-geojson, deck-grid, deck-hex,
deck-heatmap, deck-screengrid, deck-contour, deck-multi,
point-cluster-map, cartodiagram), build the renderer with the
maps satellite enabled — see § Maps satellite below.
Setup
npm i -g xviz-cliChrome or Chromium is required at runtime — xviz uses puppeteer-core
and does not bundle a browser. The CLI auto-detects these paths:
- macOS:
/Applications/Google Chrome.app/Contents/MacOS/Google Chrome - Linux:
/usr/bin/google-chrome,/usr/bin/chromium,/usr/bin/chromium-browser
Override the path with XVIZ_CHROME=/path/to/chrome.
Or use the Docker image
docker run --rm -v "$PWD:/data" ghcr.io/caiyin-bit/xviz/xviz-cli:latest \
render -d /data/sales.json -f /data/pie.json -o /data/out.png
# Pin to a major.minor for production
docker run --rm ghcr.io/caiyin-bit/xviz/xviz-cli:1.0 --helpThe image bundles chromium (XVIZ_CHROME is pre-wired). It does not
include the maps satellite — fork the Dockerfile and
build with XVIZ_ENABLE_MAPS=1 to include it.
From source (contributors only)
git clone https://github.com/caiyin-bit/xviz.git
cd xviz/xviz-cli
npm ci --include=optional
npm run build # builds the renderer bundle into dist/
node bin/xviz.mjs --helpxviz render
Render a single chart to a file.
node bin/xviz.mjs render \
--data examples/pie-data.json \
--form examples/pie-form.json \
--out chart.png \
--width 800 --height 500 \
--theme darkFlags:
| Flag | Default | Notes |
|---|---|---|
| -d, --data <file> | — | JSON array, JSON QueryData[], or .csv |
| -f, --form <file> | — | JSON with chart formData (includes vizType) |
| -c, --config <file> | — | Single JSON with {type, width, height, formData, data} |
| -o, --out <file> | chart.png | Extension selects format: .png .jpg .pdf .html |
| -w, --width <px> | 800 | |
| -h, --height <px> | 500 | |
| --scale <n> | 2 | Device scale factor (2 = retina) |
| --theme <name> | light | light | dark |
| --delay <ms> | 400 | Extra wait after render (for ECharts' canvas draw) |
| --verbose | | Stream browser console to stderr |
Input data accepts three shapes:
// 1. Array of rows (most common)
[{ "region": "NA", "sales": 1200 }, ...]
// 2. Superset's QueryData
[{ "data": [{...}], "colnames": [...] }]
// 3. Single QueryData
{ "data": [...], "colnames": [...] }xviz query
Query a database and render the result in one step. Combines SQL execution with chart rendering — no JSON / CSV intermediate needed.
node bin/xviz.mjs query \
--db sqlite:examples/sql/sample.db \
--sql "SELECT region, SUM(revenue) AS revenue FROM orders GROUP BY 1" \
--form examples/sql/region-pie-form.json \
--out /tmp/regions.pngFlags specific to query:
| Flag | Notes |
|---|---|
| --db <url> | Connection URL (see below). Required. |
| --sql <sql> | Inline SQL query. |
| --sql-file <file> | Path to a .sql file (alternative to --sql). |
| --limit <n> | Max rows accepted from SQL (default 10000). |
All other flags from render apply (--form, --out, --width, --theme, …).
Supported connection URLs
| Driver | URL shape | Install |
|---|---|---|
| SQLite | sqlite:/abs/path.db or ./app.db | npm install better-sqlite3 |
| PostgreSQL | postgres://user:pass@host:5432/db | npm install pg |
| MySQL | mysql://user:pass@host:3306/db | npm install mysql2 |
Drivers are optional — xviz imports them dynamically and errors with a
helpful message if the one you need isn't installed.
Example
# Build the sample SQLite database (96 rows of orders across 6 months)
node examples/sql/build-sample.mjs
# Monthly revenue stacked by category
node bin/xviz.mjs query \
--db sqlite:examples/sql/sample.db \
--sql "SELECT strftime('%Y-%m', placed_at) AS month,
product_category AS category,
SUM(revenue) AS revenue
FROM orders GROUP BY 1, 2 ORDER BY 1, 2" \
--form examples/sql/monthly-revenue-form.json \
--out /tmp/monthly.png --width 800 --height 450xviz serve
Long-lived HTTP server. Puppeteer browser stays warm between requests (~1.2s / render on an M-series Mac).
node bin/xviz.mjs serve --port 3737 --host 127.0.0.1curl http://localhost:3737/health
# → {"status":"ok","service":"xviz","version":"0.1.0",
# "endpoints":["POST /render"],
# "supported":["pie","bar","line",...]}
curl -X POST http://localhost:3737/render \
-H 'Content-Type: application/json' \
-d '{
"type": "pie",
"width": 600, "height": 400,
"data": [
{"region": "NA", "sales": 1200},
{"region": "EU", "sales": 900},
{"region": "AS", "sales": 1500}
],
"formData": {
"vizType": "pie",
"groupby": ["region"],
"metric": "sales",
"donut": true, "showTotal": true
}
}' -o chart.pngRequest body:
| Field | Required | Default |
|---|---|---|
| type | yes¹ | — |
| formData | yes | — |
| data or queriesData | yes | — |
| width / height | no | 800 / 500 |
| format | no | png (or jpg, jpeg, pdf, html) |
| theme | no | light, dark, or partial Theme object |
| scale, delay | no | 2, 400 |
¹ Derived from formData.vizType if omitted.
xviz mcp
Exposes rendering as MCP tools so Claude and other LLMs can generate charts through natural language. Communicates over stdio.
node bin/xviz.mjs mcpTools advertised:
render_chart—{type, data, formData, width?, height?}→ returns base64 PNG + metadatalist_chart_types— lists supported chart types
Add to Claude Desktop config:
{
"mcpServers": {
"xviz": {
"command": "node",
"args": ["/absolute/path/to/xviz-cli/bin/xviz.mjs", "mcp"]
}
}
}Then ask Claude: "Chart these numbers as a donut: {NA: 1200, EU: 900, AS: 1500}".
Superset compatibility
The CSV parser handles Apache Superset's Export to CSV output verbatim.
Tested with 16 real-world edge cases at test/csv-compat.test.mjs:
- UTF-8 BOM (
encoding="utf-8-sig") - Thousands-separated numbers:
"9,823,456","24,800.00" - CSV-injection guard:
"'+12V"→"+12V","'@formula"→"@formula" - Nested double quotes:
"""Pro"" Kit"→"Pro" Kit - Aggregate column names:
SUM(confirmed),COUNT(*) __timestampcolumns preserved as strings- Scientific notation, leading-zero strings, etc.
node test/csv-compat.test.mjs # 16 passed, 0 failedSee examples/superset-exports/ for real
Superset-style CSV files you can pipe through the CLI.
Examples
The examples/ directory contains ready-to-run configs:
# JSON data
node bin/xviz.mjs render -d examples/pie-data.json -f examples/pie-form.json -o /tmp/pie.png
node bin/xviz.mjs render -d examples/sales-data.json -f examples/bar-form.json -o /tmp/bar.png
node bin/xviz.mjs render -d examples/sales-data.json -f examples/line-form.json -o /tmp/line.png
node bin/xviz.mjs render -c examples/scatter.json -o /tmp/scatter.png
node bin/xviz.mjs render -c examples/heatmap.json -o /tmp/heatmap.png
node bin/xviz.mjs render -c examples/sankey.json -o /tmp/sankey.png
node bin/xviz.mjs render -c examples/funnel.json -o /tmp/funnel.png
node bin/xviz.mjs render -c examples/gauge.json -o /tmp/gauge.png
# Real Superset CSV exports
node bin/xviz.mjs render \
-d examples/superset-exports/covid_states.csv \
-f examples/superset-exports/covid-form.json \
-o /tmp/covid.png --width 900Maps satellite (XVIZ_ENABLE_MAPS)
The 13 deck.gl-powered map charts live in the optional
@minimal-viz/maps satellite package. They are
not in the default xviz-cli renderer — adding deck.gl + maplibre-gl
to every CLI install would 3× the bundle. Opt in at build time:
git clone https://github.com/caiyin-bit/xviz.git
cd xviz/xviz-cli
npm ci --include=optional
npm run build:maps # XVIZ_ENABLE_MAPS=1 vite build
node bin/xviz.mjs render \
-d examples/deck-scatter-cities/data.json \
-f examples/deck-scatter-cities/form.json \
-o cities.png --width 900 --height 540xviz serve /health reports mapsEnabled: true and includes the 13 map
types alongside the 39 core types when the env var is set at CLI launch:
XVIZ_ENABLE_MAPS=1 node bin/xviz.mjs serve --port 3737
curl -s http://localhost:3737/health | jq .mapsEnabled # → trueBundle sizes (CI-enforced budgets in .github/workflows/ci.yml):
| Build | Raw | Gzip | |---------------|---------:|-------:| | Default | ~1.15 MB | ~374 KB | | Maps-enabled | ~3.02 MB | ~884 KB |
Performance baseline
A repeatable benchmark lives in bench/. It exercises the
shared Engine over 5 representative fixtures × N runs, separating
cold-start (puppeteer launch) from warm renders:
npm run build
node bench/render-bench.mjs # default: 3 runs each, markdown out
node bench/render-bench.mjs --runs 10 # tighter signal
node bench/render-bench.mjs --json # machine-readableRun it locally before any change you suspect could move the perf needle.
Numbers are hardware-dependent (see bench/README.md).
License
Apache 2.0
