URL-based chart image service. Construct a URL, get back an SVG or PNG chart. Embed anywhere Markdown or HTML is supported.
npm install
npm run dev
# http://localhost:3000GET /chart Smart route — auto-detects context (see below)
GET /chart.svg Returns SVG image
GET /chart.png Returns PNG image
GET /chart.html Returns interactive HTML (ECharts client-side, with tooltip)
GET /share.html Share page with chart preview + embed code bar
GET /editor.html Full chart editor with controls, preview, and export
One URL, multiple behaviors — automatically adapts based on how it's used:
| Context | Header | Returns |
|---|---|---|
| Browser address bar | Sec-Fetch-Dest: document |
Share page |
<img src="..."> |
Sec-Fetch-Dest: image |
SVG image |
<iframe src="..."> |
Sec-Fetch-Dest: iframe |
Interactive HTML |
| curl / API | (none) | SVG image |
<!-- Same URL, different results -->
<img src="https://domain.com/chart?data=...&type=line" />
<iframe src="https://domain.com/chart?data=...&type=line" width="800" height="400"></iframe>
<!-- Browser: opens share page with embed codes -->| Parameter | Required | Default | Description |
|---|---|---|---|
data |
* | - | Inline CSV or Markdown table (URL encoded) |
source |
* | - | URL pointing to a CSV or Markdown table |
type |
No | line |
Chart type (see below) |
renderer |
No | auto |
d3 or echarts |
title |
No | - | Chart title |
width |
No | 800 |
Image width (100-2000) |
height |
No | 400 |
Image height (100-2000) |
theme |
No | light |
light or dark |
colors |
No | - | Comma-separated hex colors, e.g. #ff6600,#3366cc |
xlabel |
No | - | X-axis label |
ylabel |
No | - | Y-axis label |
Either
dataorsourceis required.datatakes inline text,sourcefetches from a remote URL.
| Type | Renderer | Description |
|---|---|---|
line |
D3 | Line chart |
bar |
D3 | Bar chart (grouped for multi-series) |
area |
D3 | Area chart |
scatter |
D3 | Scatter plot |
pie |
D3 | Pie / donut chart |
radar |
ECharts | Radar / spider chart |
heatmap |
ECharts | Heatmap matrix |
candlestick |
ECharts | Candlestick (OHLC) |
funnel |
ECharts | Funnel chart |
treemap |
ECharts | Treemap |
gauge |
ECharts | Gauge meter |
D3 is the default renderer for basic types (lighter, faster SVG output). ECharts handles complex types via SSR. Override with &renderer=echarts to force ECharts for any type.
/chart.svg?data=Month,Revenue,Cost%0AJan,12000,8000%0AFeb,15000,9000%0AMar,18000,10000&type=bar
/chart.svg?data=%7C+Month+%7C+Sales+%7C%0A%7C-------%7C-------%7C%0A%7C+Q1+%7C+120+%7C%0A%7C+Q2+%7C+150+%7C&type=line
/chart.svg?source=https://raw.githubusercontent.com/user/repo/main/data.csv&type=line
The parser auto-detects CSV vs Markdown table format.
/chart.html returns a self-contained HTML page with ECharts client-side rendering. Supports tooltip on hover, legend interaction, and auto-resize. Perfect for embedding via iframe.
URL format - same parameters as /chart.svg, just change the extension:
/chart.html?data=Month,Sales,Profit%0AJan,100,60%0AFeb,200,120%0AMar,300,180&type=line&title=Revenue&theme=dark
Embed via iframe:
<iframe
src="https://your-domain.com/chart.html?data=Month,Sales%0AJan,100%0AFeb,200%0AMar,300&type=line&title=Traffic"
width="800" height="400" frameborder="0">
</iframe>Features:
- Tooltip with crosshair on hover (axis-type for cartesian charts, item-type for pie/funnel/radar)
- Auto-resize to fit iframe container
- All 11 chart types supported
- Light and dark themes
/share.html displays the chart full-screen with a compact bottom bar for sharing embed codes.
/share.html?data=Month,Sales,Profit%0AJan,100,60%0AFeb,200,120%0AMar,300,180&type=line&title=Revenue
Bottom bar tabs:
- Link — Smart URL (
/chart?...) - Markdown —
embed syntax - HTML —
<img src="url" />tag - iframe —
<iframe src="url" ...></iframe>embed
Each tab shows the code in a monospace box with a Copy button. An Edit button navigates to the full editor (/editor.html) with the same parameters.
This is the default page when opening /chart in a browser.
/editor.html provides a full visual editor for designing charts interactively.
/editor.html?data=Month,Sales,Profit%0AJan,100,60%0AFeb,200,120%0AMar,300,180&type=line&title=Revenue
Editor features:
- Switch between all 11 chart types with icon buttons, real-time preview
- 6 preset color schemes (Default, Warm, Cool, Pastel, Vivid, Monochrome) + custom hex colors
- Light / Dark theme toggle
- Edit title, axis labels, dimensions, smooth curves
- Export panel with one-click copy for 6 formats:
- SVG URL, PNG URL, Markdown embed, HTML
<img>tag,<iframe>embed, raw CSV
- SVG URL, PNG URL, Markdown embed, HTML
- Direct download as SVG or PNG
Line chart, dark theme:
Bar chart with custom colors:
Radar chart (ECharts):
Funnel chart, dark theme:
src/
├── app.ts Pure Hono app (platform-agnostic entry)
├── index.ts Node.js dev server (localhost + fixture serving)
├── routes/chart.ts GET /chart.svg, /chart.png, /chart.html, /editor.html
├── parsers/ CSV (PapaParse) + Markdown table parser
├── renderers/
│ ├── types.ts ChartConfig, ChartRenderer interface
│ ├── d3.ts D3 + linkedom (line/bar/pie/area/scatter)
│ ├── echarts.ts ECharts SSR (radar/heatmap/funnel/...)
│ └── index.ts Renderer factory
├── converter/png.ts SVG -> PNG via resvg-wasm
├── fetcher/ Remote URL fetcher (timeout, size limit, SSRF protection)
├── cache/ HTTP cache headers
├── config/ Themes (light/dark), defaults
├── utils/validate.ts Query parameter validation
└── errors/ Typed error classes
api/index.ts Vercel Edge entry point
bun/serve.ts Bun entry point
deno/serve.ts Deno Deploy entry point
Dual renderer engine:
- D3 + linkedom for basic chart types - pure SVG string generation, small output, fast
- ECharts SSR for complex chart types - built-in layout algorithms, rendered server-side to SVG
Both renderers work in Edge runtime (no native Node.js modules required).
SmartBI runs on any edge runtime. The core app (src/app.ts) is platform-agnostic — each platform has its own thin entry point.
src/app.ts <- Pure Hono app (shared by all platforms)
src/index.ts <- Node.js dev server (localhost)
api/index.ts <- Vercel Edge entry
bun/serve.ts <- Bun entry
wrangler.toml <- Cloudflare Workers config
deno/serve.ts <- Deno Deploy entry
- Install Wrangler CLI:
npm install -g wrangler- Login to Cloudflare:
wrangler login- Deploy:
npx wrangler deployThe wrangler.toml config points to src/app.ts as the entry. Workers use the default export of the Hono app directly.
To preview before deploying:
npx wrangler dev- Install Vercel CLI:
npm install -g vercel- Deploy:
vercel deployOn first run, Vercel CLI will prompt you to link or create a project. The vercel.json rewrites all requests to the api/index.ts Edge Function entry point.
For production deployment:
vercel deploy --prodYou can also connect your GitHub repo in the Vercel Dashboard for automatic deployments on push.
- Install deployctl:
deno install -gArf jsr:@deno/deployctl- Deploy:
deployctl deploy --project=smartbi --entrypoint=deno/serve.tsOr, if you have deno.json configured:
deno task deploy- Go to Deno Deploy Dashboard and create a new project
- Connect your GitHub repository
- Set the entrypoint to
deno/serve.ts - Every push to
mainwill auto-deploy
deno task start- SSRF protection: In production (
NODE_ENV=production), requests to private/local IPs (127.x, 10.x, 172.16-31.x, 192.168.x) via?source=are blocked - Dev mode: When
NODE_ENVis notproduction, localhost URLs are allowed for testing with?source= - PNG support: SVG-to-PNG conversion via
resvg-wasm— works on all platforms that support WebAssembly
Three Dockerfiles are provided for Node.js, Bun, and Deno runtimes:
Node.js (default):
docker build -t smartbi .
docker run -p 3000:3000 smartbiBun:
docker build -f Dockerfile.bun -t smartbi-bun .
docker run -p 3000:3000 smartbi-bunDeno:
docker build -f Dockerfile.deno -t smartbi-deno .
docker run -p 8000:8000 smartbi-deno| Runtime | Dockerfile | Port | Entry point |
|---|---|---|---|
| Node.js | Dockerfile |
3000 | src/index.ts |
| Bun | Dockerfile.bun |
3000 | bun/serve.ts |
| Deno | Dockerfile.deno |
8000 | deno/serve.ts |
Two workflows are included in .github/workflows/:
deploy.yml — Push to main auto-deploys to all three platforms:
| Platform | Runs on | Required Secrets |
|---|---|---|
| Cloudflare Workers | push: main |
CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID |
| Vercel | push: main |
VERCEL_TOKEN, VERCEL_ORG_ID, VERCEL_PROJECT_ID |
| Deno Deploy | push: main |
(uses GitHub OIDC — link repo in Deno Deploy Dashboard) |
docker.yml — Push a version tag to build & publish Docker images:
git tag v1.0.0 && git push origin v1.0.0Publishes to ghcr.io (GitHub Container Registry):
| Tag | Image |
|---|---|
latest / v1.0.0 |
Node.js (default) |
bun / v1.0.0-bun |
Bun |
deno / v1.0.0-deno |
Deno |
docker pull ghcr.io/zcpua/smartbi:latest # Node.js
docker pull ghcr.io/zcpua/smartbi:bun # Bun
docker pull ghcr.io/zcpua/smartbi:deno # Deno- Cloudflare: Create API Token with
Workerspermission → addCLOUDFLARE_API_TOKENandCLOUDFLARE_ACCOUNT_IDto repo secrets - Vercel: Run
vercel linklocally, then copy values from.vercel/project.json→ addVERCEL_TOKEN(from Vercel Tokens),VERCEL_ORG_ID,VERCEL_PROJECT_ID - Deno Deploy: Link your GitHub repo in Deno Deploy Dashboard — the workflow uses GitHub OIDC, no token needed
Both workflows also support workflow_dispatch for manual triggers from the Actions tab.
npm run dev # Start with hot reload (tsx --watch)Test locally:
# SVG output
curl "http://localhost:3000/chart.svg?data=Month,Sales%0AJan,100%0AFeb,200&type=line" -o chart.svg
# PNG output
curl "http://localhost:3000/chart.png?data=Month,Sales%0AJan,100%0AFeb,200&type=bar&theme=dark" -o chart.png
# Remote data source
curl "http://localhost:3000/chart.svg?source=http://localhost:3000/fixtures/demo.csv&type=line&renderer=echarts" -o chart.svg
# Interactive HTML (open in browser)
open "http://localhost:3000/chart.html?data=Month,Sales,Profit%0AJan,100,60%0AFeb,200,120%0AMar,300,180&type=line&title=Revenue&theme=dark"- Hono - Edge-first HTTP framework
- D3 (selective imports) + linkedom - SVG chart rendering
- ECharts (SSR mode) - Complex chart rendering
- @resvg/resvg-wasm - SVG to PNG conversion
- PapaParse - CSV parsing
Apache-2.0