Glyph Arts Skill
Vision
When AI answers in chat, useful visuals should be able to live there too.
Chat output comes first. Terminal rendering, files, and host previews are exits
for cases where the drawing needs more room, persistence, or human review.
glyph-arts gives the AI a native visual language without forcing every drawing
through a GUI.
Invocation
SKILL=~/.claude/skills/glyph-arts
python $SKILL/scripts/chart.py <type> [--json '<data>'] [--file path.json] \
[--duckdb 'SQL'] [--db PATH] \
[--title 'T'] [--width N] [--height 20] [--theme pro] \
[--sample N] [--xlabel X] [--ylabel Y] \
[--xlim MIN MAX] [--ylim MIN MAX] \
[--xscale linear|log] [--yscale linear|log] \
[--orientation vertical|horizontal] [--output FILE] [--no-color] \
[--link-data URL] [--link-title URL] [--statusline]
python $SKILL/scripts/chart.py animate <line|bar|scatter|sparkline> \
--duration SEC --frames N --json '<data>'
python $SKILL/scripts/chart.py record demo.cast --cmd 'glyph-arts art DEMO' --duration 10
python $SKILL/scripts/chart.py record-replay demo.cast --output demo.gif
python $SKILL/scripts/chart.py to-hyperframes \
--json '[{"label":"x","x":[1,2,3,4,5],"y":[10,20,15,30,25]}]' \
--frames 30 --duration 5 --output-dir ./hf-demo
Chat drawing front door:
python $SKILL/scripts/chart.py chat image --file photo.jpg --width 80 --height 30
python $SKILL/scripts/chart.py chat photo.jpg --image-style retro-art
python $SKILL/scripts/chart.py chat incplot --json 'name,value\nA,3\nB,7'
python $SKILL/scripts/chart.py chat sdr spectrum --json '{"freq":[99.0,99.3],"power":[-93,-42]}'
python $SKILL/scripts/chart.py chat sequence --json 'Client->Server: GET /health'
python $SKILL/scripts/chart.py chat mermaid --json 'graph LR\nA[开始] --> B[完成]'
python $SKILL/scripts/chart.py chat diagram note --json 'NOTE\nCache refresh required'
python $SKILL/scripts/chart.py chat textplot --json '{"expr":"sin(x) / x","xmin":-20,"xmax":20}'
python $SKILL/scripts/chart.py chat turtle --json '{"commands":[["forward",30],["right",90],["forward",20]]}'
python $SKILL/scripts/chart.py chat graph --json 'Client -> API\nAPI -> DB'
python $SKILL/scripts/chart.py chat formula --json '\int exp(-x^2) dx = \sqrt{\pi}'
python $SKILL/scripts/chart.py chat formula-pretty --json '(a+b)/(c+d)'
python $SKILL/scripts/chart.py chat effects
chat image implies --chat; chart commands imply --no-color.
stdin pipe (for large data):
echo '<json>' | python $SKILL/scripts/chart.py <type>
cat data.json | python $SKILL/scripts/chart.py line
file input:
python $SKILL/scripts/chart.py bar --file path/to/data.json
check dependencies:
python $SKILL/scripts/chart.py --check-deps
Width defaults to terminal width ($COLUMNS). Override with --width 120.
Decision Tree -- Which Chart to Use
What is your data shape?
|
+-- Time series / sequence
| +-- Unknown JSON/CSV/TSV shape -> incplot
| +-- Trend over time -> plotext line
| +-- Volume/count per period -> plotext bar
| +-- OHLC financial data -> plotext kline
| +-- Sparse scientific signal -> uniplot
|
+-- Distribution / proportion
| +-- Parts of a whole (<12 slices) -> rich pie
| +-- Frequency distribution -> plotext hist
| +-- Two variables correlated -> plotext scatter
|
+-- Comparison across categories
| +-- Side-by-side bars -> plotext bar (grouped -> multibar)
| +-- Ranked list with scores -> rich table
| +-- Performance gauge (0-100) -> rich gauge
|
+-- Hierarchy / tree structure -> rich tree
|
+-- Density / matrix
| +-- 2D correlation matrix -> plotext heatmap
| +-- RF spectrum trace -> spectrum
| +-- RF waterfall history -> waterfall
|
+-- Structure / diagram text
| +-- Formula / sequence / tree -> diagram (Diagon backend when installed)
| +-- Flowchart / DAG / planar graph -> diagram
|
+-- Continuous curve / function -> drawille curve / textplot / turtle
|
+-- Multiple metrics at once -> rich dashboard
|
+-- Inline sparkline (1 row) -> sparkline
| +-- Claude Code statusLine -> sparkline --statusline
|
+-- Progressive reveal in terminal -> animate line/bar/scatter/sparkline
|
+-- MP4/WebM/high-quality video -> to-hyperframes, then npx hyperframes render
|
+-- Network / graph topology -> graph
|
+-- Large ASCII label / banner -> banner
|
+-- Styled text art / framed wordmark -> art
Claude Code CLI Compatibility
Trigger keywords:
Claude Code statusline, statusLine.command, single-line metric -> use sparkline, indicator, or gauge with --statusline.
OSC 8, terminal hyperlink, clickable title -> add --link-title URL; for line/scatter series labels add --link-data URL.
Claude theme, dark-ansi, light-ansi, ~/.claude/themes -> use --theme claude-dark-ansi or --theme claude-light-ansi.
subagent colors, agent rainbow, red blue green yellow purple orange pink cyan -> use --theme subagent-rainbow for multi-series charts.
markdown table, GFM, Claude markdown render -> use table --output table.md.
Claude decision tree:
Need output inside Claude Code?
|
+-- Draw in the chat transcript -> chat image|bar|line|spectrum|waterfall ...
+-- Status line shell command -> sparkline/indicator/gauge --statusline
+-- Clickable terminal reference -> --link-title URL or --link-data URL
+-- Match current Claude ANSI theme -> --theme claude-dark-ansi or claude-light-ansi
+-- Match subagent color names -> --theme subagent-rainbow
+-- Render table as markdown -> table --output result.md
+-- Fullscreen TUI requested -> dashboard/rich_live only when the user accepts TUI behavior
Chart Types Reference
Engine: incplot (1 type)
| Type |
Input |
Notes |
incplot |
raw JSON, JSONL, CSV, TSV |
Auto-detects sparkline, bar, multibar, line, scatter, hist, table, and OHLC candlestick payloads. Use --prefer to override. |
Engine: plotext (15 types)
| Type |
JSON keys |
Notes |
kline |
dates[], open[], high[], low[], close[] |
dates must be DD/MM/YYYY |
candlestick |
same as kline |
alias for kline |
line |
[{"label":"A","x":[...],"y":[...]}] |
multi-series supported |
scatter |
[{"label":"A","x":[...],"y":[...]}] |
|
step |
[{"label":"A","x":[...],"y":[...]}] |
staircase; x-point duplication |
bar |
{"labels":[], "values":[]} |
use --orientation horizontal |
multibar |
{"labels":[], "series":[{"label":"A","values":[]}]} |
grouped bars |
stackedbar |
{"labels":[], "series":[{"label":"A","values":[]}]} |
|
hist |
{"values":[], "bins":20} |
|
heatmap |
{"matrix":[[]], "xlabels":[], "ylabels":[]} |
needs pandas |
box |
{"data":[[s1],[s2]], "labels":["A","B"]} |
|
indicator |
{"value":23.4, "label":"Return %"} |
big KPI number |
event |
{"data":[x1,x2,...]} |
timeline plot |
confusion |
{"actual":[], "predicted":[], "labels":[]} |
ML confusion matrix |
plotext |
{"series":[{"type":"error","x":[],"y":[],"yerr":[]}],"texts":[],"vlines":[],"hlines":[],"shapes":[]} |
plotext overlay API: error bars, date plots, labels, lines, rectangles, polygons |
Engine: SDR (2 types)
| Type |
JSON keys |
Notes |
spectrum |
{"freq":[], "power":[], "center":99.3, "bandwidth":0.2} |
chat-safe RF spectrum with center line, band edges, peak marker |
waterfall |
{"matrix":[[]], "xlabels":[], "ylabels":[], "min":-94, "max":-42} |
dense ASCII intensity map for terminal/chat panes |
Engine: rich (7 types)
| Type |
JSON keys |
table |
{"columns":[], "rows":[[]]} |
tree |
{"label":"root","children":[{"label":"A","children":[]}]} |
panel |
{"content":"text","title":"optional","box":"ROUNDED"} |
gauge |
[{"label":"CPU","value":72,"max":100,"color":"green"}] |
pie |
{"labels":["A","B","C"],"values":[30,50,20]} |
dashboard |
{"panels":[{"type":"gauge","data":{...},"title":"CPU"},...]} — delegates to scripts/dashboard.py |
rich_live |
{"panels":[{"type":"sparkline","data":{...},"title":"Left"},...],"layout":"row","frames":1} — multi-panel Rich Layout, layout:"row" or "column", frames:1 for pipe-safe snapshot |
Engine: diagram (1 type)
| Type |
Input |
Notes |
diagram |
diagram sequence --json 'A->B: msg' or {"kind":"flowchart","text":"A -> B"} |
Uses Diagon binary when installed for math/sequence/tree/table/frame/flowchart/GraphDAG/GraphPlanar. Builtin chat-safe fallback covers sequence/tree/table/frame/note/flowchart/graphdag. chat sequence ... aliases to chat diagram sequence ... |
Engine: drawille / textplots Braille (3 types)
| Type |
JSON keys |
curve |
{"points":[[x,y],...]} -- Braille Unicode, highest resolution |
textplot |
{"expr":"sin(x) / x","xmin":-20,"xmax":20} -- textplots-rs-style continuous function plot |
turtle |
{"commands":[["forward",30],["right",90],["forward",20]]} -- drawille-style Turtle/Canvas path drawing |
Engine: hires (1 type) — 24-bit Braille
| Type |
JSON keys |
Notes |
hires |
[{"label":"A","y":[...],"x":[...],"color":[r,g,b],"glow":true}] |
Catmull-Rom smooth curves, per-dot 24-bit RGB, glow halos |
Engine: radar (1 type) — Polar Braille
| Type |
JSON keys |
Notes |
radar |
{"labels":[...],"series":[{"label":"A","values":[...],"color":[r,g,b]}],"max":100} |
Spider/radar chart, min 3 axes |
Engine: plotille (1 type)
| Type |
JSON keys |
Notes |
plotille |
[{"label":"A","x":[...],"y":[...],"color":"bright_cyan"}] |
Composable braille Figure with axis labels |
Engine: uniplot (1 type)
| Type |
JSON keys |
uniplot |
[{"label":"A","x":[...],"y":[...]}] -- scientific axis formatting |
Engine: textcharts (9 types)
Zero-dependency, 15 chart types with ANSI colors. Install: pip install "glyph-arts[textcharts]" or pip install textcharts.
| Type |
JSON keys |
Notes |
comparison |
{"data":[{"label":"A","baseline":85,"comparison":89.5}]} |
A/B comparison bars with % change |
diverging |
{"data":[{"label":"A","pct_change":25}]} |
positive/negative diverging bars |
summary |
{"stats":{"cpu":73.5,"memory":58.2}} |
key statistics summary box |
sparkline-table |
{"columns":[],"values":{}} |
table with inline sparklines |
cdf |
{"series":[{"name":"A","values":[...]}]} |
cumulative distribution function |
rank |
{"items":[{"label":"A","value":89}]} or {"values":{"A":89}} |
sorted ranking table (Rich) |
percentile |
{"data":[{"name":"A","p50":50,"p90":90}]} |
percentile ladder |
boxplot |
{"series":[{"name":"A","values":[...]}]} |
statistical box plots |
stacked-text |
{"data":[{"label":"A","segments":[{"label":"X","value":30}]}]} |
stacked composition bars |
Engine: media (Pillow/chafa + ffmpeg) -- 2 types
Charts use a native Python engine. Image can render through Pillow as
plain chat-safe text, or through chafa for 24-bit truecolor terminal
rendering. Video needs ffmpeg plus chafa. These types take a
filesystem path via --file, not JSON data.
| Type |
Input |
Notes |
image |
--file path/to.png |
PNG/JPEG/GIF/BMP/WebP via Pillow or chafa. Size via --width/--height. --chat emits plain ASCII for Claude/Codex chat panes and auto-crops/suppresses flat backgrounds. --image-style classic/braille/block/edge/dot-cross/halftone/particles/retro-art/terminal mirrors the ascii-art skill styles. --color-mode grayscale/original/matrix/amber/custom, --custom-color, --background, --ratio, --dither, and .txt/.md/.html/.svg/.png/.gif/.tsx exports are supported. --image-mode raw/detail/edge/silhouette controls the Pillow preprocessing; --no-trim keeps the full frame. --media-engine pillow --symbols half emits ANSI half-block color; --media-engine chafa --symbols braille uses chafa |
video |
--file path/to.mp4 |
ffmpeg extracts frames to tempdir then chafa renders each. --fps N (default 12) paces playback. --duration SEC clips (else plays to EOF). Ctrl-C exits cleanly, cursor restored |
Formula source is not an image. If source text is available, use
glyph-arts chat formula for compact Unicode math or
glyph-arts chat formula-pretty for SymPy-backed multi-line fractions,
integrals, sums, and roots. Rasterize only screenshots/scans; for white-page
material in dark terminals, use the image path with --invert.
Rationale for chart/media split: the native curve renderer connects
every consecutive point-pair via Bresenham, which fills silhouettes into
blobs when fed pixel data. chafa is purpose-built and superset of
timg/viu on Windows. HANDOFF 2026-04-14 records the full architectural
decision.
Misc (4 types)
| Type |
JSON keys |
graph |
{"edges":[["A","B"]],"directed":true,"node_style":"ROUND"} or edge-list/DOT/GraphML text |
sparkline |
{"values":[1,3,5,2,8]} -- single-row inline |
banner |
{"text":"PROFIT","font":"big","color":"green"} |
art |
positional text: art SHIP IT --font slant --decor barcode --frame double --gradient sunset |
animate |
animate line --duration 5 --frames 30 --json '[{"label":"A","x":[...],"y":[...]}]' |
record |
record demo.cast --cmd 'glyph-arts art DEMO' --duration 10 |
record-replay |
record-replay demo.cast --output demo.gif -- .gif via agg, .svg via svg-term, .html snippet, .cast passthrough |
to-hyperframes |
to-hyperframes --json '[{"label":"A","x":[...],"y":[...]}]' --frames 30 --duration 5 --output-dir ./hf -- writes PNG frame sequence, manifest.json, and composition.html; requires HyperFrames installed separately with npx hyperframes init |
Trigger keywords for art: wordmark, figlet, framed text, gradient text,
decorated ASCII, composable text art.
Trigger keywords for animate: animation, animated chart, progressive reveal,
terminal playback, cursor-home redraw.
Trigger keywords for record: record terminal, asciinema, cast file,
terminal recording, replay export, gif export, svg export.
Trigger keywords for to-hyperframes: 视频, video, mp4, webm, 动画, motion,
GSAP, launch, demo, hyperframes.
Video output decision tree:
- User wants GIF + short demo -> use
glyph-arts record + glyph-arts record-replay
- User wants MP4 + high quality video -> use
glyph-arts to-hyperframes + npx hyperframes render
- User wants embed README -> use
record-replay --output .gif because GIF auto-plays in GitHub README
- User wants embed blog HTML -> use
record-replay --output .svg for vector scaling
Output Contract
Success: exit code 0, chart rendered to stdout.
Failure modes:
| Condition |
Exit code |
Stderr format |
| Invalid JSON |
1 |
ERROR:json: <message> |
| Missing required key |
1 |
ERROR:schema: <message> |
| Unknown chart type |
1 |
argparse error |
| Missing dependency |
2 |
ERROR:dep: pip install <package> |
| Render failed |
4 |
ERROR:render: <traceback_last_line> |
AI parsing rule: always check exit code first. If non-zero, read stderr for structured error tag.
Size & Performance Limits
| Engine |
Recommended max data points |
Hard limit |
| plotext |
10,000 |
50,000 |
| sdr spectrum/waterfall |
5,000 points / 500 rows |
resampled to terminal width/height |
| rich table |
500 rows |
2,000 rows |
| drawille |
50,000 |
200,000 |
| uniplot |
100,000 |
-- |
Over limit? Use --file + --sample <n> to auto-downsample.
AI Usage Rules
DO
- Use
sparkline for a single inline metric in the middle of analysis
- Use
dashboard when presenting 3+ related metrics simultaneously
- Use
--file or stdin pipe when data exceeds 200 characters in JSON string form
- Use
drawille curve when signal continuity matters (audio, sensor, physics)
- Use
rich table when the user needs to read exact values
- Use
chat graph for network topology, DAGs, dependency maps, and DOT/GraphML
- Use
--sample N to downsample large datasets before rendering
DO NOT
- Do not pass more than 500 rows via
--json inline -- use --file instead
- Do not use
plotext heatmap for data with more than 20x20 cells
- Do not use
rich pie with more than 12 slices -- switch to bar
- Do not call
banner except for final result banners or section headers
- Do not generate a chart without a
--title when presenting analysis results
Examples
Time series:
python $SKILL/scripts/chart.py line \
--json '[{"label":"DAU","x":[1,2,3,4,5],"y":[100,120,115,130,125]}]' \
--title "Daily Active Users"
Multi-series:
python $SKILL/scripts/chart.py line \
--json '[{"label":"Revenue","y":[100,120,90,140]},{"label":"Cost","y":[80,95,75,110]}]' \
--title "P&L Trend"
Pie chart:
python $SKILL/scripts/chart.py pie \
--json '{"labels":["Equity","Bond","Cash"],"values":[60,30,10]}' \
--title "Asset Allocation"
Dashboard (3 panels):
python $SKILL/scripts/chart.py dashboard \
--json '{
"panels": [
{"type":"gauge","data":[{"label":"CPU","value":73,"max":100}],"title":"CPU"},
{"type":"sparkline","data":{"values":[1,3,5,2,8,4,6]},"title":"Load"},
{"type":"table","data":{"columns":["Host","Status"],"rows":[["web-01","OK"],["db-01","WARN"]]},"title":"Services"}
]
}' \
--title "System Health"
Large file:
python $SKILL/scripts/chart.py scatter \
--file ./data/million_points.json \
--sample 5000 \
--title "Correlation"
From pipe:
cat metrics.json | python $SKILL/scripts/chart.py bar --title "Benchmark Results"
K-line from DuckDB:
python $SKILL/scripts/chart.py kline \
--title "600519 K-line" --width 80 --height 24 \
--duckdb "SELECT trade_date,open,high,low,close FROM stock_daily WHERE ts_code='600519.SH' ORDER BY trade_date DESC LIMIT 30" \
--db /path/to/data.duckdb
Anti-patterns (don't do these)
# BAD: inline 10000-row JSON will crash the shell
python $SKILL/scripts/chart.py line --json '[...10000 items...]'
# GOOD:
python $SKILL/scripts/chart.py line --file data.json
# BAD: pie chart with 20 slices is unreadable
python $SKILL/scripts/chart.py pie --json '{"labels":["A","B",...20 items...],"values":[...]}'
# GOOD: switch to bar for many categories
python $SKILL/scripts/chart.py bar --json '{"labels":[...],"values":[...]}' --orientation horizontal
# BAD: no title in analysis context
python $SKILL/scripts/chart.py bar --json '{"labels":["Q1","Q2"],"values":[10,12]}'
# GOOD:
python $SKILL/scripts/chart.py bar --json '{"labels":["Q1","Q2"],"values":[10,12]}' --title "Q1 vs Q2 Revenue"
Dependencies
pip install plotext rich drawille uniplot pyfiglet sparklines duckdb pandas networkx phart
# optional: LTTB shape-preserving downsampling (--sample for line/scatter/step/uniplot)
pip install lttb
# optional: Textual TUI dashboard (scripts/dashboard.py)
pip install textual
Auto-install check:
python $SKILL/scripts/chart.py --check-deps
# prints OK or lists missing packages
DuckDB Integration
| Chart type |
Column mapping |
| kline / candlestick |
col0=date, open/high/low/close by name |
| line / scatter / step / uniplot |
col0=x, col1..N=y series |
| bar / pie |
col0=labels, col1=values |
| table |
all columns as-is |
| hist |
all columns as value series |
| heatmap |
matrix from .values, col names as xlabels |
| spectrum |
col0=frequency, col1=power |
| waterfall |
matrix-shaped values only; prefer JSON/file input |
| curve |
col0=x, col1=y |
| sparkline |
col0 values |
| confusion |
col0=actual, col1=predicted |
| graph |
col0=src, col1=dst (edge list) |
Textual TUI Dashboard
scripts/dashboard.py — standalone multi-panel dashboard (separate from chart.py).
python $SKILL/scripts/dashboard.py --demo # interactive Textual TUI
python $SKILL/scripts/dashboard.py --demo --no-interactive # Rich static fallback (pipe-safe)
python $SKILL/scripts/dashboard.py --file config.json # load panel config from file
python $SKILL/scripts/dashboard.py --json '{"panels":[...]}' # inline JSON
# Panel types: gauge | sparkline | table | metric | bar
# Layout: auto-grid (<=4 panels -> 2col, >4 -> 3col) or manual via panel["row"]
# Live refresh: add "refresh_interval": 5 (seconds) to config
# Bindings: q=quit, r=refresh
Falls back to Rich static output if textual is not installed or stdout is not a tty.
Integration Notes
- Pipe-friendly: all output is pure stdout, errors go to stderr only. Safe to pipe.
- Session logging: set
CLI_CHARTS_LOG=1 to append render history to .chart_history.jsonl.
- NO_COLOR: respects
NO_COLOR env var (https://no-color.org) and --no-color flag.
- LTTB sampling:
--sample N uses Largest-Triangle-Three-Buckets for line/scatter/step/uniplot (shape-preserving), OHLC group aggregation for kline, uniform stride fallback when lttb not installed.
1---2name: glyph-arts3description: glyph-arts -- Chat Arts / chat drawing toolkit. Turns chat requests into visuals for the conversation first, with CLI, terminal, artifact, media, and host-preview paths when needed. incplot-style auto plotting (JSON/JSONL/CSV/TSV inference), plotext (kline/candlestick/line/scatter/step/bar/multibar/stackedbar/hist/heatmap/box/indicator/event/confusion/plotext overlays), SDR spectrum/waterfall, Diagram Renderer paths for Diagon-style diagrams (math/sequence/tree/table/frame/flowchart/GraphDAG/GraphPlanar), Mermaid/beautiful-mermaid-style diagrams, Draw.io Export, rich (table/tree/panel/gauge/pie/dashboard/rich_live), drawille/textplots Braille (curve/hires/radar/textplot/turtle), plotille (composable braille Figure), uniplot scientific line, media image via Pillow chat-safe ASCII or chafa truecolor, terminal host profiles for Windows Terminal/Preview/Canary and Warp, WaveTerm adapter via `wave doctor/view/render`, video via ffmpeg+chafa, backend doctor/install helpers for chafa/ffmpeg/Graphviz/Diagon/Nerd Font4---56# Glyph Arts Skill78## Vision910When AI answers in chat, useful visuals should be able to live there too.11Chat output comes first. Terminal rendering, files, and host previews are exits12for cases where the drawing needs more room, persistence, or human review.13glyph-arts gives the AI a native visual language without forcing every drawing14through a GUI.1516## Invocation1718```bash19SKILL=~/.claude/skills/glyph-arts20python $SKILL/scripts/chart.py <type> [--json '<data>'] [--file path.json] \21 [--duckdb 'SQL'] [--db PATH] \22 [--title 'T'] [--width N] [--height 20] [--theme pro] \23 [--sample N] [--xlabel X] [--ylabel Y] \24 [--xlim MIN MAX] [--ylim MIN MAX] \25 [--xscale linear|log] [--yscale linear|log] \26 [--orientation vertical|horizontal] [--output FILE] [--no-color] \27 [--link-data URL] [--link-title URL] [--statusline]2829python $SKILL/scripts/chart.py animate <line|bar|scatter|sparkline> \30 --duration SEC --frames N --json '<data>'3132python $SKILL/scripts/chart.py record demo.cast --cmd 'glyph-arts art DEMO' --duration 1033python $SKILL/scripts/chart.py record-replay demo.cast --output demo.gif3435python $SKILL/scripts/chart.py to-hyperframes \36 --json '[{"label":"x","x":[1,2,3,4,5],"y":[10,20,15,30,25]}]' \37 --frames 30 --duration 5 --output-dir ./hf-demo38```3940Chat drawing front door:4142```bash43python $SKILL/scripts/chart.py chat image --file photo.jpg --width 80 --height 3044python $SKILL/scripts/chart.py chat photo.jpg --image-style retro-art45python $SKILL/scripts/chart.py chat incplot --json 'name,value\nA,3\nB,7'46python $SKILL/scripts/chart.py chat sdr spectrum --json '{"freq":[99.0,99.3],"power":[-93,-42]}'47python $SKILL/scripts/chart.py chat sequence --json 'Client->Server: GET /health'48python $SKILL/scripts/chart.py chat mermaid --json 'graph LR\nA[开始] --> B[完成]'49python $SKILL/scripts/chart.py chat diagram note --json 'NOTE\nCache refresh required'50python $SKILL/scripts/chart.py chat textplot --json '{"expr":"sin(x) / x","xmin":-20,"xmax":20}'51python $SKILL/scripts/chart.py chat turtle --json '{"commands":[["forward",30],["right",90],["forward",20]]}'52python $SKILL/scripts/chart.py chat graph --json 'Client -> API\nAPI -> DB'53python $SKILL/scripts/chart.py chat formula --json '\int exp(-x^2) dx = \sqrt{\pi}'54python $SKILL/scripts/chart.py chat formula-pretty --json '(a+b)/(c+d)'55python $SKILL/scripts/chart.py chat effects56```5758`chat image` implies `--chat`; chart commands imply `--no-color`.5960**stdin pipe (for large data):**61```bash62echo '<json>' | python $SKILL/scripts/chart.py <type>63cat data.json | python $SKILL/scripts/chart.py line64```6566**file input:**67```bash68python $SKILL/scripts/chart.py bar --file path/to/data.json69```7071**check dependencies:**72```bash73python $SKILL/scripts/chart.py --check-deps74```7576Width defaults to terminal width (`$COLUMNS`). Override with `--width 120`.7778---7980## Decision Tree -- Which Chart to Use8182```83What is your data shape?84|85+-- Time series / sequence86| +-- Unknown JSON/CSV/TSV shape -> incplot87| +-- Trend over time -> plotext line88| +-- Volume/count per period -> plotext bar89| +-- OHLC financial data -> plotext kline90| +-- Sparse scientific signal -> uniplot91|92+-- Distribution / proportion93| +-- Parts of a whole (<12 slices) -> rich pie94| +-- Frequency distribution -> plotext hist95| +-- Two variables correlated -> plotext scatter96|97+-- Comparison across categories98| +-- Side-by-side bars -> plotext bar (grouped -> multibar)99| +-- Ranked list with scores -> rich table100| +-- Performance gauge (0-100) -> rich gauge101|102+-- Hierarchy / tree structure -> rich tree103|104+-- Density / matrix105| +-- 2D correlation matrix -> plotext heatmap106| +-- RF spectrum trace -> spectrum107| +-- RF waterfall history -> waterfall108|109+-- Structure / diagram text110| +-- Formula / sequence / tree -> diagram (Diagon backend when installed)111| +-- Flowchart / DAG / planar graph -> diagram112|113+-- Continuous curve / function -> drawille curve / textplot / turtle114|115+-- Multiple metrics at once -> rich dashboard116|117+-- Inline sparkline (1 row) -> sparkline118| +-- Claude Code statusLine -> sparkline --statusline119|120+-- Progressive reveal in terminal -> animate line/bar/scatter/sparkline121|122+-- MP4/WebM/high-quality video -> to-hyperframes, then npx hyperframes render123|124+-- Network / graph topology -> graph125|126+-- Large ASCII label / banner -> banner127|128+-- Styled text art / framed wordmark -> art129```130131## Claude Code CLI Compatibility132133Trigger keywords:134135- `Claude Code statusline`, `statusLine.command`, `single-line metric` -> use `sparkline`, `indicator`, or `gauge` with `--statusline`.136- `OSC 8`, `terminal hyperlink`, `clickable title` -> add `--link-title URL`; for line/scatter series labels add `--link-data URL`.137- `Claude theme`, `dark-ansi`, `light-ansi`, `~/.claude/themes` -> use `--theme claude-dark-ansi` or `--theme claude-light-ansi`.138- `subagent colors`, `agent rainbow`, `red blue green yellow purple orange pink cyan` -> use `--theme subagent-rainbow` for multi-series charts.139- `markdown table`, `GFM`, `Claude markdown render` -> use `table --output table.md`.140141Claude decision tree:142143```144Need output inside Claude Code?145|146+-- Draw in the chat transcript -> chat image|bar|line|spectrum|waterfall ...147+-- Status line shell command -> sparkline/indicator/gauge --statusline148+-- Clickable terminal reference -> --link-title URL or --link-data URL149+-- Match current Claude ANSI theme -> --theme claude-dark-ansi or claude-light-ansi150+-- Match subagent color names -> --theme subagent-rainbow151+-- Render table as markdown -> table --output result.md152+-- Fullscreen TUI requested -> dashboard/rich_live only when the user accepts TUI behavior153```154155---156157## Chart Types Reference158159### Engine: incplot (1 type)160| Type | Input | Notes |161|------|-------|-------|162| `incplot` | raw JSON, JSONL, CSV, TSV | Auto-detects sparkline, bar, multibar, line, scatter, hist, table, and OHLC candlestick payloads. Use `--prefer` to override. |163164### Engine: plotext (15 types)165| Type | JSON keys | Notes |166|------|-----------|-------|167| `kline` | `dates[], open[], high[], low[], close[]` | dates must be DD/MM/YYYY |168| `candlestick` | same as kline | alias for kline |169| `line` | `[{"label":"A","x":[...],"y":[...]}]` | multi-series supported |170| `scatter` | `[{"label":"A","x":[...],"y":[...]}]` | |171| `step` | `[{"label":"A","x":[...],"y":[...]}]` | staircase; x-point duplication |172| `bar` | `{"labels":[], "values":[]}` | use --orientation horizontal |173| `multibar` | `{"labels":[], "series":[{"label":"A","values":[]}]}` | grouped bars |174| `stackedbar` | `{"labels":[], "series":[{"label":"A","values":[]}]}` | |175| `hist` | `{"values":[], "bins":20}` | |176| `heatmap` | `{"matrix":[[]], "xlabels":[], "ylabels":[]}` | needs pandas |177| `box` | `{"data":[[s1],[s2]], "labels":["A","B"]}` | |178| `indicator` | `{"value":23.4, "label":"Return %"}` | big KPI number |179| `event` | `{"data":[x1,x2,...]}` | timeline plot |180| `confusion` | `{"actual":[], "predicted":[], "labels":[]}` | ML confusion matrix |181| `plotext` | `{"series":[{"type":"error","x":[],"y":[],"yerr":[]}],"texts":[],"vlines":[],"hlines":[],"shapes":[]}` | plotext overlay API: error bars, date plots, labels, lines, rectangles, polygons |182183### Engine: SDR (2 types)184| Type | JSON keys | Notes |185|------|-----------|-------|186| `spectrum` | `{"freq":[], "power":[], "center":99.3, "bandwidth":0.2}` | chat-safe RF spectrum with center line, band edges, peak marker |187| `waterfall` | `{"matrix":[[]], "xlabels":[], "ylabels":[], "min":-94, "max":-42}` | dense ASCII intensity map for terminal/chat panes |188189### Engine: rich (7 types)190| Type | JSON keys |191|------|-----------|192| `table` | `{"columns":[], "rows":[[]]}` |193| `tree` | `{"label":"root","children":[{"label":"A","children":[]}]}` |194| `panel` | `{"content":"text","title":"optional","box":"ROUNDED"}` |195| `gauge` | `[{"label":"CPU","value":72,"max":100,"color":"green"}]` |196| `pie` | `{"labels":["A","B","C"],"values":[30,50,20]}` |197| `dashboard` | `{"panels":[{"type":"gauge","data":{...},"title":"CPU"},...]}` — delegates to scripts/dashboard.py |198| `rich_live` | `{"panels":[{"type":"sparkline","data":{...},"title":"Left"},...],"layout":"row","frames":1}` — multi-panel Rich Layout, `layout:"row"` or `"column"`, `frames:1` for pipe-safe snapshot |199200### Engine: diagram (1 type)201| Type | Input | Notes |202|------|-------|-------|203| `diagram` | `diagram sequence --json 'A->B: msg'` or `{"kind":"flowchart","text":"A -> B"}` | Uses Diagon binary when installed for math/sequence/tree/table/frame/flowchart/GraphDAG/GraphPlanar. Builtin chat-safe fallback covers sequence/tree/table/frame/note/flowchart/graphdag. `chat sequence ...` aliases to `chat diagram sequence ...` |204205### Engine: drawille / textplots Braille (3 types)206| Type | JSON keys |207|------|-----------|208| `curve` | `{"points":[[x,y],...]}` -- Braille Unicode, highest resolution |209| `textplot` | `{"expr":"sin(x) / x","xmin":-20,"xmax":20}` -- textplots-rs-style continuous function plot |210| `turtle` | `{"commands":[["forward",30],["right",90],["forward",20]]}` -- drawille-style Turtle/Canvas path drawing |211212### Engine: hires (1 type) — 24-bit Braille213| Type | JSON keys | Notes |214|------|-----------|-------|215| `hires` | `[{"label":"A","y":[...],"x":[...],"color":[r,g,b],"glow":true}]` | Catmull-Rom smooth curves, per-dot 24-bit RGB, glow halos |216217### Engine: radar (1 type) — Polar Braille218| Type | JSON keys | Notes |219|------|-----------|-------|220| `radar` | `{"labels":[...],"series":[{"label":"A","values":[...],"color":[r,g,b]}],"max":100}` | Spider/radar chart, min 3 axes |221222### Engine: plotille (1 type)223| Type | JSON keys | Notes |224|------|-----------|-------|225| `plotille` | `[{"label":"A","x":[...],"y":[...],"color":"bright_cyan"}]` | Composable braille Figure with axis labels |226227### Engine: uniplot (1 type)228| Type | JSON keys |229|------|-----------|230| `uniplot` | `[{"label":"A","x":[...],"y":[...]}]` -- scientific axis formatting |231232### Engine: textcharts (9 types)233Zero-dependency, 15 chart types with ANSI colors. Install: `pip install "glyph-arts[textcharts]"` or `pip install textcharts`.234235| Type | JSON keys | Notes |236|------|-----------|-------|237| `comparison` | `{"data":[{"label":"A","baseline":85,"comparison":89.5}]}` | A/B comparison bars with % change |238| `diverging` | `{"data":[{"label":"A","pct_change":25}]}` | positive/negative diverging bars |239| `summary` | `{"stats":{"cpu":73.5,"memory":58.2}}` | key statistics summary box |240| `sparkline-table` | `{"columns":[],"values":{}}` | table with inline sparklines |241| `cdf` | `{"series":[{"name":"A","values":[...]}]}` | cumulative distribution function |242| `rank` | `{"items":[{"label":"A","value":89}]}` or `{"values":{"A":89}}` | sorted ranking table (Rich) |243| `percentile` | `{"data":[{"name":"A","p50":50,"p90":90}]}` | percentile ladder |244| `boxplot` | `{"series":[{"name":"A","values":[...]}]}` | statistical box plots |245| `stacked-text` | `{"data":[{"label":"A","segments":[{"label":"X","value":30}]}]}` | stacked composition bars |246247### Engine: media (Pillow/chafa + ffmpeg) -- 2 types248Charts use a native Python engine. Image can render through Pillow as249plain chat-safe text, or through `chafa` for 24-bit truecolor terminal250rendering. Video needs `ffmpeg` plus `chafa`. These types take a251filesystem path via `--file`, not JSON data.252253| Type | Input | Notes |254|------|-------|-------|255| `image` | `--file path/to.png` | PNG/JPEG/GIF/BMP/WebP via Pillow or chafa. Size via `--width`/`--height`. `--chat` emits plain ASCII for Claude/Codex chat panes and auto-crops/suppresses flat backgrounds. `--image-style classic/braille/block/edge/dot-cross/halftone/particles/retro-art/terminal` mirrors the ascii-art skill styles. `--color-mode grayscale/original/matrix/amber/custom`, `--custom-color`, `--background`, `--ratio`, `--dither`, and `.txt/.md/.html/.svg/.png/.gif/.tsx` exports are supported. `--image-mode raw/detail/edge/silhouette` controls the Pillow preprocessing; `--no-trim` keeps the full frame. `--media-engine pillow --symbols half` emits ANSI half-block color; `--media-engine chafa --symbols braille` uses chafa |256| `video` | `--file path/to.mp4` | ffmpeg extracts frames to tempdir then chafa renders each. `--fps N` (default 12) paces playback. `--duration SEC` clips (else plays to EOF). Ctrl-C exits cleanly, cursor restored |257258Formula source is not an image. If source text is available, use259`glyph-arts chat formula` for compact Unicode math or260`glyph-arts chat formula-pretty` for SymPy-backed multi-line fractions,261integrals, sums, and roots. Rasterize only screenshots/scans; for white-page262material in dark terminals, use the image path with `--invert`.263264Rationale for chart/media split: the native `curve` renderer connects265every consecutive point-pair via Bresenham, which fills silhouettes into266blobs when fed pixel data. chafa is purpose-built and superset of267timg/viu on Windows. HANDOFF 2026-04-14 records the full architectural268decision.269270### Misc (4 types)271| Type | JSON keys |272|------|-----------|273| `graph` | `{"edges":[["A","B"]],"directed":true,"node_style":"ROUND"}` or edge-list/DOT/GraphML text | PHART backend. Use `chat graph --json 'A -> B\nB -> C'`, `--graph-format dot`, `--graph-style round`, `--graph-charset ascii` |274| `sparkline` | `{"values":[1,3,5,2,8]}` -- single-row inline |275| `banner` | `{"text":"PROFIT","font":"big","color":"green"}` |276| `art` | positional text: `art SHIP IT --font slant --decor barcode --frame double --gradient sunset` |277| `animate` | `animate line --duration 5 --frames 30 --json '[{"label":"A","x":[...],"y":[...]}]'` |278| `record` | `record demo.cast --cmd 'glyph-arts art DEMO' --duration 10` |279| `record-replay` | `record-replay demo.cast --output demo.gif` -- `.gif` via agg, `.svg` via svg-term, `.html` snippet, `.cast` passthrough |280| `to-hyperframes` | `to-hyperframes --json '[{"label":"A","x":[...],"y":[...]}]' --frames 30 --duration 5 --output-dir ./hf` -- writes PNG frame sequence, `manifest.json`, and `composition.html`; requires HyperFrames installed separately with `npx hyperframes init` |281282Trigger keywords for `art`: wordmark, figlet, framed text, gradient text,283decorated ASCII, composable text art.284285Trigger keywords for `animate`: animation, animated chart, progressive reveal,286terminal playback, cursor-home redraw.287288Trigger keywords for `record`: record terminal, asciinema, cast file,289terminal recording, replay export, gif export, svg export.290291Trigger keywords for `to-hyperframes`: 视频, video, mp4, webm, 动画, motion,292GSAP, launch, demo, hyperframes.293294Video output decision tree:295- User wants GIF + short demo -> use `glyph-arts record` + `glyph-arts record-replay`296- User wants MP4 + high quality video -> use `glyph-arts to-hyperframes` + `npx hyperframes render`297- User wants embed README -> use `record-replay --output .gif` because GIF auto-plays in GitHub README298- User wants embed blog HTML -> use `record-replay --output .svg` for vector scaling299300---301302## Output Contract303304**Success:** exit code `0`, chart rendered to stdout.305306**Failure modes:**307| Condition | Exit code | Stderr format |308|-----------|-----------|---------------|309| Invalid JSON | `1` | `ERROR:json: <message>` |310| Missing required key | `1` | `ERROR:schema: <message>` |311| Unknown chart type | `1` | argparse error |312| Missing dependency | `2` | `ERROR:dep: pip install <package>` |313| Render failed | `4` | `ERROR:render: <traceback_last_line>` |314315**AI parsing rule:** always check exit code first. If non-zero, read stderr for structured error tag.316317---318319## Size & Performance Limits320321| Engine | Recommended max data points | Hard limit |322|--------|----------------------------|------------|323| plotext | 10,000 | 50,000 |324| sdr spectrum/waterfall | 5,000 points / 500 rows | resampled to terminal width/height |325| rich table | 500 rows | 2,000 rows |326| drawille | 50,000 | 200,000 |327| uniplot | 100,000 | -- |328329**Over limit?** Use `--file` + `--sample <n>` to auto-downsample.330331---332333## AI Usage Rules334335### DO336- Use `sparkline` for a single inline metric in the middle of analysis337- Use `dashboard` when presenting 3+ related metrics simultaneously338- Use `--file` or stdin pipe when data exceeds 200 characters in JSON string form339- Use `drawille curve` when signal continuity matters (audio, sensor, physics)340- Use `rich table` when the user needs to read exact values341- Use `chat graph` for network topology, DAGs, dependency maps, and DOT/GraphML342- Use `--sample N` to downsample large datasets before rendering343344### DO NOT345- Do not pass more than 500 rows via `--json` inline -- use `--file` instead346- Do not use `plotext heatmap` for data with more than 20x20 cells347- Do not use `rich pie` with more than 12 slices -- switch to `bar`348- Do not call `banner` except for final result banners or section headers349- Do not generate a chart without a `--title` when presenting analysis results350351---352353## Examples354355**Time series:**356```bash357python $SKILL/scripts/chart.py line \358 --json '[{"label":"DAU","x":[1,2,3,4,5],"y":[100,120,115,130,125]}]' \359 --title "Daily Active Users"360```361362**Multi-series:**363```bash364python $SKILL/scripts/chart.py line \365 --json '[{"label":"Revenue","y":[100,120,90,140]},{"label":"Cost","y":[80,95,75,110]}]' \366 --title "P&L Trend"367```368369**Pie chart:**370```bash371python $SKILL/scripts/chart.py pie \372 --json '{"labels":["Equity","Bond","Cash"],"values":[60,30,10]}' \373 --title "Asset Allocation"374```375376**Dashboard (3 panels):**377```bash378python $SKILL/scripts/chart.py dashboard \379 --json '{380 "panels": [381 {"type":"gauge","data":[{"label":"CPU","value":73,"max":100}],"title":"CPU"},382 {"type":"sparkline","data":{"values":[1,3,5,2,8,4,6]},"title":"Load"},383 {"type":"table","data":{"columns":["Host","Status"],"rows":[["web-01","OK"],["db-01","WARN"]]},"title":"Services"}384 ]385 }' \386 --title "System Health"387```388389**Large file:**390```bash391python $SKILL/scripts/chart.py scatter \392 --file ./data/million_points.json \393 --sample 5000 \394 --title "Correlation"395```396397**From pipe:**398```bash399cat metrics.json | python $SKILL/scripts/chart.py bar --title "Benchmark Results"400```401402**K-line from DuckDB:**403```bash404python $SKILL/scripts/chart.py kline \405 --title "600519 K-line" --width 80 --height 24 \406 --duckdb "SELECT trade_date,open,high,low,close FROM stock_daily WHERE ts_code='600519.SH' ORDER BY trade_date DESC LIMIT 30" \407 --db /path/to/data.duckdb408```409410---411412## Anti-patterns (don't do these)413414```bash415# BAD: inline 10000-row JSON will crash the shell416python $SKILL/scripts/chart.py line --json '[...10000 items...]'417# GOOD:418python $SKILL/scripts/chart.py line --file data.json419420# BAD: pie chart with 20 slices is unreadable421python $SKILL/scripts/chart.py pie --json '{"labels":["A","B",...20 items...],"values":[...]}'422# GOOD: switch to bar for many categories423python $SKILL/scripts/chart.py bar --json '{"labels":[...],"values":[...]}' --orientation horizontal424425# BAD: no title in analysis context426python $SKILL/scripts/chart.py bar --json '{"labels":["Q1","Q2"],"values":[10,12]}'427# GOOD:428python $SKILL/scripts/chart.py bar --json '{"labels":["Q1","Q2"],"values":[10,12]}' --title "Q1 vs Q2 Revenue"429```430431---432433## Dependencies434435```bash436pip install plotext rich drawille uniplot pyfiglet sparklines duckdb pandas networkx phart437# optional: LTTB shape-preserving downsampling (--sample for line/scatter/step/uniplot)438pip install lttb439# optional: Textual TUI dashboard (scripts/dashboard.py)440pip install textual441```442443Auto-install check:444```bash445python $SKILL/scripts/chart.py --check-deps446# prints OK or lists missing packages447```448449---450451## DuckDB Integration452453| Chart type | Column mapping |454|-----------|----------------|455| kline / candlestick | col0=date, open/high/low/close by name |456| line / scatter / step / uniplot | col0=x, col1..N=y series |457| bar / pie | col0=labels, col1=values |458| table | all columns as-is |459| hist | all columns as value series |460| heatmap | matrix from .values, col names as xlabels |461| spectrum | col0=frequency, col1=power |462| waterfall | matrix-shaped values only; prefer JSON/file input |463| curve | col0=x, col1=y |464| sparkline | col0 values |465| confusion | col0=actual, col1=predicted |466| graph | col0=src, col1=dst (edge list) |467468---469470## Textual TUI Dashboard471472`scripts/dashboard.py` — standalone multi-panel dashboard (separate from `chart.py`).473474```bash475python $SKILL/scripts/dashboard.py --demo # interactive Textual TUI476python $SKILL/scripts/dashboard.py --demo --no-interactive # Rich static fallback (pipe-safe)477python $SKILL/scripts/dashboard.py --file config.json # load panel config from file478python $SKILL/scripts/dashboard.py --json '{"panels":[...]}' # inline JSON479480# Panel types: gauge | sparkline | table | metric | bar481# Layout: auto-grid (<=4 panels -> 2col, >4 -> 3col) or manual via panel["row"]482# Live refresh: add "refresh_interval": 5 (seconds) to config483# Bindings: q=quit, r=refresh484```485486Falls back to Rich static output if `textual` is not installed or stdout is not a tty.487488---489490## Integration Notes491492- **Pipe-friendly**: all output is pure stdout, errors go to stderr only. Safe to pipe.493- **Session logging**: set `CLI_CHARTS_LOG=1` to append render history to `.chart_history.jsonl`.494- **NO_COLOR**: respects `NO_COLOR` env var (https://no-color.org) and `--no-color` flag.495- **LTTB sampling**: `--sample N` uses Largest-Triangle-Three-Buckets for line/scatter/step/uniplot (shape-preserving), OHLC group aggregation for kline, uniform stride fallback when `lttb` not installed.