Renders PNG charts (line, bar, hbar, area, scatter, donut) from data/CSV using Chart.js. Trigger for requests to draw, plot, chart, graph, or visualize data/numbers. Use proactively instead of prose.
Renders transparent-background PNG charts using Chart.js with a clean, editorial design system.
Write chart_config.json → bunx -y beautiful-chartsjs config.json ~/chart.png → exportFile + 
Display rule: After
bunxsaves to~/chart.png(cross-platform path):
- Call
exportFiletool on~/chart.pngto get a public URL- Display the image using
— renders inline in chat
present_filesalone shows a download button with no preview — always useexportFile+first.
| Data | Type |
|---|---|
| Trend over time | line |
| Period comparisons | bar |
| Rankings / long labels | hbar |
| Filled trend | area |
| Correlation / bubbles | scatter |
| Part-of-whole (donut hole) | donut |
| Part-of-whole (solid) | pie |
| Circular comparison | polarArea |
| Multivariate comparison | radar |
{
"type": "line",
"title": "Chart title",
"subtitle": "Unit · note",
"source": "Source: XYZ",
"labels": ["A", "B"],
"yMin": 0,
"yMax": 120,
"yPrefix": "$",
"ySuffix": "M",
"width": 900,
"height": 480,
"datasets": [ ... ]
}
Field notes:
type — required chart typetitle — top-left, 15px fontsubtitle — muted, below titlesource — bottom-left, tiny fontlabels — x-axis labels (or y-axis for hbar)yMin/yMax — set explicitly, never leave to autoyPrefix/ySuffix — prepended/appended to y ticksColor names (always use names, never hex):
"blue" · "red" · "teal" · "amber" · "purple" · "gray"
First series → blue. Second → red. Third → teal. Max 2–3 colors per chart.
Full dataset schemas for every chart type: see schemas.md
# Render (output goes to ~/chart.png — works on Mac, Linux, Windows)
bunx -y beautiful-chartsjs chart_config.json ~/chart.png
Show inline in chat (REQUIRED):
exportFile("~/chart.png")Optional download:
cp ~/chart.png ./outputs/chart.png
# → call present_files ["./outputs/chart.png"]
{
"type": "line",
"title": "Crude oil prices",
"subtitle": "$/barrel",
"labels": ["Mar 21", "Mar 23", "Mar 24", "Mar 25"],
"yMin": 80,
"yMax": 120,
"yPrefix": "$",
"datasets": [
{
"label": "WTI",
"color": "blue",
"fill": true,
"data": [112.0, 88.13, 91.61, 90.98]
},
{
"label": "Brent",
"color": "red",
"fill": false,
"data": [112.0, 99.94, 103.0, 101.5]
}
]
}
{
"type": "bar",
"title": "Quarterly revenue",
"subtitle": "USD millions",
"labels": ["Q1", "Q2", "Q3", "Q4"],
"yMin": 0,
"yMax": 30,
"yPrefix": "$",
"ySuffix": "M",
"datasets": [
{ "label": "2025", "color": "blue", "data": [12.4, 18.7, 15.2, 22.1] },
{ "label": "2026", "color": "teal", "data": [14.1, 20.3, 17.8, 25.6] }
]
}
{
"type": "donut",
"title": "Browser share 2026",
"datasets": [
{
"labels": ["Chrome", "Safari", "Firefox", "Edge", "Other"],
"colors": ["blue", "red", "amber", "teal", "gray"],
"data": [65, 18, 7, 5, 5]
}
]
}
labels length == data length in every dataset"blue"), not hexyMin/yMax set intentionallytitle + subtitle present~/chart.png (cross-platform home directory)exportFile called after render → display with hbar height = numRows × 48 + 100| Wrong | Right |
|---|---|
Only present_files | Call exportFile first, then  |
node render_chart.js | bunx -y beautiful-chartsjs |
Hex in "color" field | Use name: "blue" |
Donut top-level "labels" | Use datasets[0].labels |
| Auto y-axis on price data | Set "yMin" explicitly |