Charts CLI
Generate charts from the command line using ECharts. No browser needed.
Setup (one-time in container)
npm install -g charts-cli
Usage
List available chart types
charts schema --list
Get schema for a chart type
charts schema bar
charts schema pie
charts schema xAxis
Render a chart
# Pipe JSON config
echo '<json>' | charts render -o /workspace/scratch/chart.png
# From file
charts render --config option.json -o /workspace/scratch/chart.png
Options
| Option |
Description |
-o, --output <file> |
Output path (.svg or .png) |
-W, --width <n> |
Width in pixels (default: 800) |
-H, --height <n> |
Height in pixels (default: 400) |
--theme <name> |
dark, vintage, or path to JSON |
--format <type> |
svg or png (auto-detected from extension) |
Supported chart types
bar, line, pie, scatter, radar, funnel, gauge, treemap, boxplot, heatmap, candlestick, sankey
Visual Style Rules
Always apply these defaults to every chart config for clean, modern output.
Global defaults
backgroundColor: "#ffffff" — mandatory; without it PNGs are transparent and unreadable on dark viewers
- Title:
left: "center", top: 14, fontSize: 16, fontWeight: "bold", color: "#111827"
- Grid:
{ left: 60, right: 32, top: 60, bottom: 48 } — increase top to ~80 when using both title and legend
- Color palette (use in order):
#4f46e5, #0d9488, #d97706, #dc2626, #7c3aed, #0891b2
- Area fill RGBA (0.10 opacity):
rgba(79,70,229,0.10), rgba(13,148,136,0.10), rgba(217,119,6,0.10), rgba(220,38,38,0.10), rgba(124,58,237,0.10), rgba(8,145,178,0.10)
Axis styling (for charts that use xAxis/yAxis)
- xAxis (category):
axisTick.show: false, axisLine color #d1d5db, axisLabel color #4b5563 fontSize 13
- yAxis (value):
axisLine.show: false, axisTick.show: false, axisLabel color #9ca3af fontSize 12, splitLine dashed #e5e7eb
Bar
barWidth: "50%", rounded top corners borderRadius: [5,5,0,0]
- Value labels on top:
label.show: true, position: "top", color #1f2937, fontSize 13, fontWeight bold
Line
smooth: true for modern curves; omit for angular data
symbol: "circle", symbolSize: 7, lineStyle width 3
- White-bordered data points:
itemStyle.borderColor: "#ffffff", borderWidth: 2
- Single series: area fill at ~0.15 opacity;
boundaryGap: false on xAxis
- Multi-series: no area fill (overlaps too much), explicit color per series, add legend at
top: 42
- No value labels — they clutter curves
Pie / donut
- No
grid, xAxis, yAxis. Use center: ["50%","55%"], render with -W 800 -H 500
- White borders between segments:
borderColor: "#ffffff", borderWidth: 2, borderRadius: 4
- Labels:
formatter: "{b} {d}%", color #374151, fontSize 13; labelLine color #d1d5db, length 16/20
emphasis.itemStyle.shadowBlur: 0 — flat look, no hover shadow
- Set
color array on the series directly (no axis to carry it)
- Donut:
radius: ["40%","65%"], bump borderWidth: 3, borderRadius: 6
- Donut center label:
title as array; second element with left/top: "center", large fontSize, subtext for descriptor
- Legend layout (many slices):
orient: "vertical", right: 24, top: "middle", hide labels, shift center to ["38%","55%"]
Scatter
- Both axes
type: "value" (not category)
symbolSize: 8, itemStyle.opacity: 0.7 to handle overlap
- Use
xAxis.name / yAxis.name to label axes
Radar
- Uses
radar component (not xAxis/yAxis) with indicator array: [{name, max}, ...]
shape: "polygon", center: ["50%","55%"], radius: "60%"
areaStyle.opacity: 0.3 for overlapping multi-series comparison
- Render with
-W 800 -H 500
Funnel
- No axes.
sort: "descending", gap: 2, width: "70%", height: "75%"
- White borders:
borderColor: "#ffffff", borderWidth: 3
- Labels inside:
position: "inside", white text, formatter: "{b}: {c}"
- Assign color per data item via
itemStyle.color
- Render with
-W 800 -H 500
Gauge
- No axes. Modern arc style:
progress.show: true, pointer.show: false
- Track:
axisLine.lineStyle.width: 24, color [[1, "#e5e7eb"]]; progress width matches
- Hide ticks, splitLines, axisLabels for clean look
- Center value:
detail.formatter: "{value}%", fontSize 48, fontWeight bold, color #111827
- Subtitle via
title.offsetCenter: [0, "50%"], fontSize 14, color #6b7280
- Multi-range:
axisLine.lineStyle.color: [[0.3, "#22c55e"], [0.7, "#d97706"], [1, "#dc2626"]]
- Render with
-W 800 -H 500
Heatmap
- Both axes
type: "category". Data as [x, y, value] triples (indices into axis arrays)
- Requires
visualMap: type: "continuous", inRange.color array of 3–5 colors for gradient (e.g. ["#e0f2fe","#7dd3fc","#0ea5e9","#0369a1","#1e3a8a"])
- Cell labels:
label.show: true, fontSize 11, fontWeight bold
- White borders:
borderColor: "#ffffff", borderWidth: 2
- Increase
grid.right: 120 to make room for visualMap legend
Sankey
- No axes. Position with
left/right/top/bottom on the series
- Data:
data array (nodes with name + itemStyle.color) and links array (source, target, value)
lineStyle.opacity: 0.25 — semi-transparent flows prevent clutter
nodeWidth: 20, nodeGap: 14
- Labels:
position: "right", color #374151, fontSize 13
- Render with
-W 800 -H 500
Treemap
- No axes. Position with
width/height/left/top on the series. roam: false, breadcrumb.show: false
- Hierarchical data:
[{name, itemStyle.color, children: [{name, value}, ...]}, ...]
- White borders:
borderWidth: 3, gapWidth: 3
- Use
levels array for depth-specific styling — wider gaps at top level, smaller borders for leaves
- Labels: white text over colored backgrounds,
formatter: "{b}"
- Assign color per top-level category; children inherit
- Render with
-W 800 -H 500
Boxplot
- Standard category xAxis (group names) + value yAxis
- Data:
[min, Q1, median, Q3, max] per group
boxWidth: ["40%","70%"], fill color #4f46e5
- Dark borders:
borderColor: "#111827", borderWidth: 1.5
Candlestick
- Standard category xAxis (dates) + value yAxis
- Data:
[open, close, low, high] per candle
- Green up / red down:
color: "#10b981", color0: "#ef4444", darker borders #059669 / #dc2626
yAxis.scale: true — essential; without it axis starts at 0 and candles become slivers
Reference example
Shows how the global defaults, axis styling, and bar series rules compose into a complete config:
{
"backgroundColor": "#ffffff",
"title": {
"text": "Monthly Revenue (k$)",
"textStyle": { "fontSize": 16, "fontWeight": "bold", "color": "#111827" },
"left": "center",
"top": 14
},
"grid": { "left": 60, "right": 32, "top": 60, "bottom": 48 },
"xAxis": {
"type": "category",
"data": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
"axisTick": { "show": false },
"axisLine": { "lineStyle": { "color": "#d1d5db" } },
"axisLabel": { "color": "#4b5563", "fontSize": 13 }
},
"yAxis": {
"type": "value",
"axisLine": { "show": false },
"axisTick": { "show": false },
"axisLabel": { "color": "#9ca3af", "fontSize": 12 },
"splitLine": { "lineStyle": { "type": "dashed", "color": "#e5e7eb" } }
},
"series": [{
"type": "bar",
"data": [10, 20, 35, 28, 42, 38],
"barWidth": "50%",
"itemStyle": { "borderRadius": [5, 5, 0, 0], "color": "#4f46e5" },
"label": { "show": true, "position": "top", "color": "#1f2937", "fontSize": 13, "fontWeight": "bold" }
}]
}
Workflow
- Use
charts schema <type> to check the config format
- Build the ECharts JSON option, applying the visual style rules above
- Pipe it to
charts render -o /workspace/scratch/chart.png
- Use the
attach tool to send the image to Slack
1---2name: charts3description: Generate SVG/PNG charts (bar, line, pie, scatter, radar, heatmap, funnel, gauge, sankey, etc.) from ECharts JSON configs4---56# Charts CLI78Generate charts from the command line using ECharts. No browser needed.910## Setup (one-time in container)1112```bash13npm install -g charts-cli14```1516## Usage1718### List available chart types1920```bash21charts schema --list22```2324### Get schema for a chart type2526```bash27charts schema bar28charts schema pie29charts schema xAxis30```3132### Render a chart3334```bash35# Pipe JSON config36echo '<json>' | charts render -o /workspace/scratch/chart.png3738# From file39charts render --config option.json -o /workspace/scratch/chart.png40```4142### Options4344| Option | Description |45|--------|-------------|46| `-o, --output <file>` | Output path (.svg or .png) |47| `-W, --width <n>` | Width in pixels (default: 800) |48| `-H, --height <n>` | Height in pixels (default: 400) |49| `--theme <name>` | `dark`, `vintage`, or path to JSON |50| `--format <type>` | `svg` or `png` (auto-detected from extension) |5152### Supported chart types5354bar, line, pie, scatter, radar, funnel, gauge, treemap, boxplot, heatmap, candlestick, sankey5556## Visual Style Rules5758**Always apply these defaults** to every chart config for clean, modern output.5960### Global defaults6162- **`backgroundColor: "#ffffff"`** — mandatory; without it PNGs are transparent and unreadable on dark viewers63- **Title**: `left: "center"`, `top: 14`, `fontSize: 16`, `fontWeight: "bold"`, `color: "#111827"`64- **Grid**: `{ left: 60, right: 32, top: 60, bottom: 48 }` — increase `top` to ~80 when using both title and legend65- **Color palette** (use in order): `#4f46e5`, `#0d9488`, `#d97706`, `#dc2626`, `#7c3aed`, `#0891b2`66- **Area fill RGBA** (0.10 opacity): `rgba(79,70,229,0.10)`, `rgba(13,148,136,0.10)`, `rgba(217,119,6,0.10)`, `rgba(220,38,38,0.10)`, `rgba(124,58,237,0.10)`, `rgba(8,145,178,0.10)`6768### Axis styling (for charts that use xAxis/yAxis)6970- **xAxis (category)**: `axisTick.show: false`, axisLine color `#d1d5db`, axisLabel color `#4b5563` fontSize 1371- **yAxis (value)**: `axisLine.show: false`, `axisTick.show: false`, axisLabel color `#9ca3af` fontSize 12, splitLine dashed `#e5e7eb`7273### Bar7475- `barWidth: "50%"`, rounded top corners `borderRadius: [5,5,0,0]`76- Value labels on top: `label.show: true`, `position: "top"`, color `#1f2937`, fontSize 13, fontWeight bold7778### Line7980- `smooth: true` for modern curves; omit for angular data81- `symbol: "circle"`, `symbolSize: 7`, lineStyle width 382- White-bordered data points: `itemStyle.borderColor: "#ffffff"`, `borderWidth: 2`83- **Single series**: area fill at ~0.15 opacity; `boundaryGap: false` on xAxis84- **Multi-series**: no area fill (overlaps too much), explicit color per series, add legend at `top: 42`85- No value labels — they clutter curves8687### Pie / donut8889- No `grid`, `xAxis`, `yAxis`. Use `center: ["50%","55%"]`, render with `-W 800 -H 500`90- White borders between segments: `borderColor: "#ffffff"`, `borderWidth: 2`, `borderRadius: 4`91- Labels: `formatter: "{b} {d}%"`, color `#374151`, fontSize 13; labelLine color `#d1d5db`, length 16/2092- `emphasis.itemStyle.shadowBlur: 0` — flat look, no hover shadow93- Set `color` array on the series directly (no axis to carry it)94- **Donut**: `radius: ["40%","65%"]`, bump `borderWidth: 3`, `borderRadius: 6`95- **Donut center label**: `title` as array; second element with `left/top: "center"`, large fontSize, `subtext` for descriptor96- **Legend layout** (many slices): `orient: "vertical"`, `right: 24`, `top: "middle"`, hide labels, shift `center` to `["38%","55%"]`9798### Scatter99100- Both axes `type: "value"` (not category)101- `symbolSize: 8`, `itemStyle.opacity: 0.7` to handle overlap102- Use `xAxis.name` / `yAxis.name` to label axes103104### Radar105106- Uses `radar` component (not xAxis/yAxis) with `indicator` array: `[{name, max}, ...]`107- `shape: "polygon"`, `center: ["50%","55%"]`, `radius: "60%"`108- `areaStyle.opacity: 0.3` for overlapping multi-series comparison109- Render with `-W 800 -H 500`110111### Funnel112113- No axes. `sort: "descending"`, `gap: 2`, `width: "70%"`, `height: "75%"`114- White borders: `borderColor: "#ffffff"`, `borderWidth: 3`115- Labels inside: `position: "inside"`, white text, `formatter: "{b}: {c}"`116- Assign color per data item via `itemStyle.color`117- Render with `-W 800 -H 500`118119### Gauge120121- No axes. Modern arc style: `progress.show: true`, `pointer.show: false`122- Track: `axisLine.lineStyle.width: 24`, color `[[1, "#e5e7eb"]]`; progress width matches123- Hide ticks, splitLines, axisLabels for clean look124- Center value: `detail.formatter: "{value}%"`, fontSize 48, fontWeight bold, color `#111827`125- Subtitle via `title.offsetCenter: [0, "50%"]`, fontSize 14, color `#6b7280`126- **Multi-range**: `axisLine.lineStyle.color: [[0.3, "#22c55e"], [0.7, "#d97706"], [1, "#dc2626"]]`127- Render with `-W 800 -H 500`128129### Heatmap130131- Both axes `type: "category"`. Data as `[x, y, value]` triples (indices into axis arrays)132- Requires `visualMap`: `type: "continuous"`, `inRange.color` array of 3–5 colors for gradient (e.g. `["#e0f2fe","#7dd3fc","#0ea5e9","#0369a1","#1e3a8a"]`)133- Cell labels: `label.show: true`, fontSize 11, fontWeight bold134- White borders: `borderColor: "#ffffff"`, `borderWidth: 2`135- Increase `grid.right: 120` to make room for visualMap legend136137### Sankey138139- No axes. Position with `left/right/top/bottom` on the series140- Data: `data` array (nodes with `name` + `itemStyle.color`) and `links` array (`source`, `target`, `value`)141- `lineStyle.opacity: 0.25` — semi-transparent flows prevent clutter142- `nodeWidth: 20`, `nodeGap: 14`143- Labels: `position: "right"`, color `#374151`, fontSize 13144- Render with `-W 800 -H 500`145146### Treemap147148- No axes. Position with `width/height/left/top` on the series. `roam: false`, `breadcrumb.show: false`149- Hierarchical data: `[{name, itemStyle.color, children: [{name, value}, ...]}, ...]`150- White borders: `borderWidth: 3`, `gapWidth: 3`151- Use `levels` array for depth-specific styling — wider gaps at top level, smaller borders for leaves152- Labels: white text over colored backgrounds, `formatter: "{b}"`153- Assign color per top-level category; children inherit154- Render with `-W 800 -H 500`155156### Boxplot157158- Standard category xAxis (group names) + value yAxis159- Data: `[min, Q1, median, Q3, max]` per group160- `boxWidth: ["40%","70%"]`, fill color `#4f46e5`161- Dark borders: `borderColor: "#111827"`, `borderWidth: 1.5`162163### Candlestick164165- Standard category xAxis (dates) + value yAxis166- Data: `[open, close, low, high]` per candle167- Green up / red down: `color: "#10b981"`, `color0: "#ef4444"`, darker borders `#059669` / `#dc2626`168- **`yAxis.scale: true`** — essential; without it axis starts at 0 and candles become slivers169170## Reference example171172Shows how the global defaults, axis styling, and bar series rules compose into a complete config:173174```json175{176 "backgroundColor": "#ffffff",177 "title": {178 "text": "Monthly Revenue (k$)",179 "textStyle": { "fontSize": 16, "fontWeight": "bold", "color": "#111827" },180 "left": "center",181 "top": 14182 },183 "grid": { "left": 60, "right": 32, "top": 60, "bottom": 48 },184 "xAxis": {185 "type": "category",186 "data": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],187 "axisTick": { "show": false },188 "axisLine": { "lineStyle": { "color": "#d1d5db" } },189 "axisLabel": { "color": "#4b5563", "fontSize": 13 }190 },191 "yAxis": {192 "type": "value",193 "axisLine": { "show": false },194 "axisTick": { "show": false },195 "axisLabel": { "color": "#9ca3af", "fontSize": 12 },196 "splitLine": { "lineStyle": { "type": "dashed", "color": "#e5e7eb" } }197 },198 "series": [{199 "type": "bar",200 "data": [10, 20, 35, 28, 42, 38],201 "barWidth": "50%",202 "itemStyle": { "borderRadius": [5, 5, 0, 0], "color": "#4f46e5" },203 "label": { "show": true, "position": "top", "color": "#1f2937", "fontSize": 13, "fontWeight": "bold" }204 }]205}206```207208## Workflow2092101. Use `charts schema <type>` to check the config format2112. Build the ECharts JSON option, applying the visual style rules above2123. Pipe it to `charts render -o /workspace/scratch/chart.png`2134. Use the `attach` tool to send the image to Slack