Generate publication-quality PNG chart images from data, supporting line, bar, area, candlestick, pie, and heatmap charts. Triggers when the user asks to visualize data, create a graph, plot a time series, or generate a chart for a report, alert, or dashboard. Runs as a lightweight, headless Node.js process without a browser.
68
82%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Critical
Do not install without reviewing
Generate PNG chart images from data using Vega-Lite. Perfect for headless server environments.
Built for Fly.io / VPS / Docker deployments:
canvas which requires build tools)cd /data/clawd/skills/chart-image/scripts && npm installnode /data/clawd/skills/chart-image/scripts/chart.mjs \
--type line \
--data '[{"x":"10:00","y":25},{"x":"10:30","y":27},{"x":"11:00","y":31}]' \
--title "Price Over Time" \
--output chart.pngnode chart.mjs --type line --data '[{"x":"A","y":10},{"x":"B","y":15}]' --output line.pngnode chart.mjs --type bar --data '[{"x":"A","y":10},{"x":"B","y":15}]' --output bar.pngnode chart.mjs --type area --data '[{"x":"A","y":10},{"x":"B","y":15}]' --output area.png# Pie
node chart.mjs --type pie --data '[{"category":"A","value":30},{"category":"B","value":70}]' \
--category-field category --y-field value --output pie.png
# Donut (with hole)
node chart.mjs --type donut --data '[{"category":"A","value":30},{"category":"B","value":70}]' \
--category-field category --y-field value --output donut.pngnode chart.mjs --type candlestick \
--data '[{"x":"Mon","open":100,"high":110,"low":95,"close":105}]' \
--open-field open --high-field high --low-field low --close-field close \
--title "Stock Price" --output candle.pngnode chart.mjs --type heatmap \
--data '[{"x":"Mon","y":"Week1","value":5},{"x":"Tue","y":"Week1","value":8}]' \
--color-value-field value --color-scheme viridis \
--title "Activity Heatmap" --output heatmap.pngCompare multiple trends on one chart:
node chart.mjs --type line --series-field "market" \
--data '[{"x":"Jan","y":10,"market":"A"},{"x":"Jan","y":15,"market":"B"}]' \
--title "Comparison" --output multi.pngnode chart.mjs --type bar --stacked --color-field "category" \
--data '[{"x":"Mon","y":10,"category":"Work"},{"x":"Mon","y":5,"category":"Personal"}]' \
--title "Hours by Category" --output stacked.pngPrice line with volume bars:
node chart.mjs --type line --volume-field volume \
--data '[{"x":"10:00","y":100,"volume":5000},{"x":"11:00","y":105,"volume":3000}]' \
--title "Price + Volume" --output volume.pngnode chart.mjs --sparkline --data '[{"x":"1","y":10},{"x":"2","y":15}]' --output spark.pngSparklines are 80x20 by default, transparent, no axes.
| Option | Description | Default |
|---|---|---|
--type | Chart type: line, bar, area, point, pie, donut, candlestick, heatmap | line |
--data | JSON array of data points | - |
--output | Output file path | chart.png |
--title | Chart title | - |
--width | Width in pixels | 600 |
--height | Height in pixels | 300 |
| Option | Description | Default |
|---|---|---|
--x-field | Field name for X axis | x |
--y-field | Field name for Y axis | y |
--x-title | X axis label | field name |
--y-title | Y axis label | field name |
--x-type | X axis type: ordinal, temporal, quantitative | ordinal |
--y-domain | Y scale as "min,max" | auto |
| Option | Description | Default |
|---|---|---|
--color | Line/bar color | #e63946 |
--dark | Dark mode theme | false |
--svg | Output SVG instead of PNG | false |
--color-scheme | Vega color scheme (category10, viridis, etc.) | - |
| Option | Description | Default |
|---|---|---|
--show-change | Show +/-% change annotation at last point | false |
--focus-change | Zoom Y-axis to 2x data range | false |
--focus-recent N | Show only last N data points | all |
--show-values | Label min/max peak points | false |
| Option | Description | Default |
|---|---|---|
--series-field | Field for multi-series line charts | - |
--stacked | Enable stacked bar mode | false |
--color-field | Field for stack/color categories | - |
| Option | Description | Default |
|---|---|---|
--open-field | OHLC open field | open |
--high-field | OHLC high field | high |
--low-field | OHLC low field | low |
--close-field | OHLC close field | close |
| Option | Description | Default |
|---|---|---|
--category-field | Field for pie slice categories | x |
--donut | Render as donut (with center hole) | false |
| Option | Description | Default |
|---|---|---|
--color-value-field | Field for heatmap intensity | value |
--y-category-field | Y axis category field | y |
| Option | Description | Default |
|---|---|---|
--y2-field | Second Y axis field (independent right axis) | - |
--y2-title | Title for second Y axis | field name |
--y2-color | Color for second series | #60a5fa (dark) / #2563eb (light) |
--y2-type | Chart type for second axis: line, bar, area | line |
Example: Revenue bars (left) + Churn area (right):
node chart.mjs \
--data '[{"month":"Jan","revenue":12000,"churn":4.2},...]' \
--x-field month --y-field revenue --type bar \
--y2-field churn --y2-type area --y2-color "#60a5fa" \
--y-title "Revenue ($)" --y2-title "Churn (%)" \
--x-sort none --dark --title "Revenue vs Churn"| Option | Description | Default |
|---|---|---|
--volume-field | Field for volume bars (enables dual-axis) | - |
--volume-color | Color for volume bars | #4a5568 |
| Option | Description | Default |
|---|---|---|
--y-format | Y axis format: percent, dollar, compact, decimal4, integer, scientific, or d3-format string | auto |
--subtitle | Subtitle text below chart title | - |
--hline | Horizontal reference line: "value" or "value,color" or "value,color,label" (repeatable) | - |
| Option | Description | Default |
|---|---|---|
--annotation | Static text annotation | - |
--annotations | JSON array of event markers | - |
node chart.mjs --type line --data '[...]' \
--title "Iran Strike Odds (48h)" \
--show-change --focus-change --show-values --dark \
--output alert.pngFor recent action only:
node chart.mjs --type line --data '[hourly data...]' \
--focus-recent 4 --show-change --focus-change --dark \
--output recent.pngMark events on the chart:
node chart.mjs --type line --data '[...]' \
--annotations '[{"x":"14:00","label":"News broke"},{"x":"16:30","label":"Press conf"}]' \
--output annotated.pngFor proper time series with date gaps:
node chart.mjs --type line --x-type temporal \
--data '[{"x":"2026-01-01","y":10},{"x":"2026-01-15","y":20}]' \
--output temporal.pngUse --x-type temporal when X values are ISO dates and you want spacing to reflect actual time gaps (not evenly spaced).
Format axis values for readability:
# Dollar amounts
node chart.mjs --data '[...]' --y-format dollar --output revenue.png
# → $1,234.56
# Percentages (values as decimals 0-1)
node chart.mjs --data '[...]' --y-format percent --output rates.png
# → 45.2%
# Compact large numbers
node chart.mjs --data '[...]' --y-format compact --output users.png
# → 1.2K, 3.4M
# Crypto prices (4 decimal places)
node chart.mjs --data '[...]' --y-format decimal4 --output molt.png
# → 0.0004
# Custom d3-format string
node chart.mjs --data '[...]' --y-format ',.3f' --output custom.pngAvailable shortcuts: percent, dollar/usd, compact, integer, decimal2, decimal4, scientific
Add context below the title:
node chart.mjs --title "MOLT Price" --subtitle "20,668 MOLT held" --data '[...]' --output molt.pngUse --dark for dark mode. Auto-select based on time:
--darkecho '[{"x":"A","y":1},{"x":"B","y":2}]' | node chart.mjs --output out.pngFor advanced charts:
node chart.mjs --spec my-spec.json --output custom.pngAfter generating a chart, always send it back to the user's channel. Don't just save to a file and describe it — the whole point is the visual.
# 1. Generate the chart
node chart.mjs --type line --data '...' --output /data/clawd/tmp/my-chart.png
# 2. Send it! Use message tool with filePath:
# action=send, target=<channel_id>, filePath=/data/clawd/tmp/my-chart.pngTips:
/data/clawd/tmp/ (persistent) not /tmp/ (may get cleaned)action=send with filePath — thread-reply does NOT support file attachments--dark between 20:00-07:00 Israel timeUpdated: 2026-02-04 - Added --y-format (percent/dollar/compact/decimal4) and --subtitle
af7d491
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.