Turn data into beautiful chart images from the command line. Give it JSON, get back a polished PNG or SVG — bar, line, area, pie, doughnut, scatter, and radar charts, styled with a clean default theme so they look good out of the box.
Charts are rendered entirely offline and self-contained: no browser, no headless Chrome, no native modules, and no network access. The same install works on macOS, Linux, and Windows.
aux4 aux4 pkger install aux4/chart
This package runs on Node.js. If it is not already available, the installer can provision it:
The headline use case is charting records — a JSON array of row objects, exactly what aux4 db ... execute emits — straight from a pipe:
aux4 db mydb execute --query "SELECT month, revenue, cost FROM sales" | aux4 chart bar --x month --y revenue,cost --output sales.png
Saved PNG chart to sales.png (800x600)
The same works with inline --data:
aux4 chart bar --data '[{"month":"Jan","revenue":12000,"cost":8000},{"month":"Feb","revenue":15000,"cost":9000}]' --x month --y revenue,cost --output sales.png
One command per chart type. All commands share the same flags and the same data schema.
| Command | Chart | |---------|-------| | aux4 chart bar | Vertical bar chart | | aux4 chart line | Line chart | | aux4 chart area | Filled area chart | | aux4 chart pie | Pie chart | | aux4 chart doughnut | Doughnut (pie with a hollow center) | | aux4 chart scatter | Scatter plot of [x, y] points | | aux4 chart radar | Radar / spider chart | | aux4 chart uptime | Uptime status strip (status-page style) | | aux4 chart generate | Render any type chosen dynamically with --type |
The per-type commands are the everyday interface. generate is for scripting when the type is a variable.
Every command accepts the same flags:
--data — Input data as JSON (see Data). Required, from this flag or from standard input. When JSON is piped on stdin, it takes precedence over --data.--x — Records mode: the category/label column (x-axis, or pie slice labels). Auto-inferred when omitted.--y — Records mode: value column(s), comma-separated. Each column becomes a series (wide format). Auto-inferred (numeric columns) when omitted.--series — Records mode: optional pivot column for long/tidy data. Distinct values in this column become series; requires a single --y measure column. Not used by pie or radar.--output — Where to send the chart. A file path (e.g. chart.png) saves it, with the extension deciding the format (.png or .svg). A literal --output=- writes raw bytes to standard output for piping/redirecting. Omitting --output previews the chart inline in supported terminals (Ghostty, Kitty, iTerm2). See Terminal Preview and Piping / Redirecting the Image.--format — Explicit format override, png or svg. Defaults to the --output extension, or png otherwise. Inline previews are always PNG.--title — Title drawn at the top of the chart.--width — Width in pixels (default: 800).--height — Height in pixels (default: 600).--theme — Visual theme, light (default) or dark.--colors — Color override: name-maps and/or a positional palette, mixable in one value. See Colors.--data (or stdin) accepts three shapes; the type is detected automatically.
A JSON array of row objects — the tabular output of aux4 db ... execute and most APIs. Columns are mapped to chart dimensions with --x, --y, and --series.
[
{ "month": "Jan", "revenue": 12000, "cost": 8000 },
{ "month": "Feb", "revenue": 15000, "cost": 9000 },
{ "month": "Mar", "revenue": 18000, "cost": 11000 }
]
Wide format — one column per series. --x is the category column, --y lists the value columns (comma-separated); each becomes a series:
aux4 db mydb execute --query "SELECT month, revenue, cost FROM sales" | aux4 chart bar --x month --y revenue,cost --output sales.png
Long / tidy format — --series pivots a column into series (requires a single --y measure). Distinct values of the pivot column become the series:
echo '[{"month":"Jan","product":"A","sales":10},{"month":"Jan","product":"B","sales":20},{"month":"Feb","product":"A","sales":15},{"month":"Feb","product":"B","sales":25}]' | aux4 chart bar --x month --series product --y sales --output sales.png
Auto-inference — with no --x/--y the columns are inferred: the x-axis is the first non-numeric column (or the first column if all are numeric), and the series are the remaining numeric columns. So this just works:
echo '[{"month":"Jan","revenue":12000,"cost":8000},{"month":"Feb","revenue":15000,"cost":9000}]' | aux4 chart bar --output sales.png
How each chart type maps records:
--x; series from the --y columns (wide) or the --series pivot (long).--x, value from a single --y. --series is not applicable.--x is the numeric x column, each --y column is a series of [x, y] points; --series may group points into series.--y columns are the axes (metrics) and each row is a series named by its --x value.A JSON object with an optional title, a categories array, and a series array — for when you want to control everything by hand:
{
"title": "Monthly Sales",
"categories": ["Jan", "Feb", "Mar"],
"series": [
{ "name": "2024", "data": [120, 200, 150] },
{ "name": "2025", "data": [180, 230, 210] }
]
}
categories label the x-axis, each series[].data is a row of numbers.data: [{ "name", "value" }], as series[0].data with the same shape, or categories paired with one series' data.series[].data is an array of [x, y] pairs.indicators (or categories) name each axis; each series[].data is one value per axis. An axis may be an object { "name", "max" } to fix its scale.aux4 chart radar --data '{"indicators":["Speed","Power","Range","Defense","Agility"],"series":[{"name":"Model X","data":[80,90,70,85,60]},{"name":"Model Y","data":[70,60,90,75,95]}]}' --title "Comparison" --output radar.png
A bare array of numbers becomes a single auto-labeled series — handy for a quick sketch:
aux4 chart bar --data '[5, 10, 3, 8]' --output quick.png
(A bare array of [x, y] pairs is also accepted as a single scatter series.)
By default charts use a clean built-in categorical palette (Tableau 10). Override it with --colors — colors no longer live in the data. --colors takes a comma-separated value that mixes two kinds of entry:
name=color — force a specific part by name (e.g. Chrome=#4285F4, revenue=#DB4437). Name matching is case-insensitive and whitespace is trimmed.color — a positional palette applied in order to the parts that have no name match.Colors apply to the chart's "parts" dimension:
Accepted color forms: hex (#rgb, #rrggbb, #rrggbbaa), rgb()/rgba()/hsl()/hsla(), and CSS color names. An invalid color is ignored (it falls back) rather than failing the render.
Color a pie by category name:
echo '[{"browser":"Chrome","share":63},{"browser":"Safari","share":19},{"browser":"Edge","share":12}]' | aux4 chart pie --x browser --y share --colors "Chrome=#4285F4,Safari=#FF6B6B,Edge=#0F9D58" --output browsers.png
Color a bar chart's series by name:
aux4 chart bar --data '[{"month":"Jan","revenue":12000,"cost":8000},{"month":"Feb","revenue":15000,"cost":9000}]' --x month --y revenue,cost --colors "revenue=#4285F4,cost=#DB4437" --output sales.png
Mix a name-map with a positional palette — revenue is forced blue, every other series takes the grey palette in order:
aux4 chart bar --data '[{"month":"Jan","revenue":100,"cost":80,"tax":20}]' --x month --y revenue,cost,tax --colors "revenue=#4285F4,#cccccc,#999999" --output sales.png
--theme light|dark is independent — it controls the background, text, and axis colors only, never the categorical series/slice colors.
Give --output a path ending in .png or .svg. A short confirmation with the path and dimensions is printed to standard error, so it never pollutes piped output:
aux4 chart bar --data '[5,10,3,8]' --output quick.svg
Saved SVG chart to quick.svg (800x600)
Force a format regardless of the extension with --format:
aux4 chart bar --data '[5,10,3,8]' --output quick.dat --format png
When you omit --output, the chart is previewed inline in your terminal as a PNG. The output mode is chosen from the terminal environment (TERM, TERM_PROGRAM, LC_TERMINAL, KITTY_WINDOW_ID) — it does not depend on whether standard output is a TTY, so it works even though aux4 runs the command behind a pipe. Supported terminals:
TERM_PROGRAM=iTerm.app or LC_TERMINAL=iTerm2).TERM containing kitty or ghostty, TERM_PROGRAM=ghostty, or KITTY_WINDOW_ID being set).aux4 chart bar --data '{"categories":["A","B","C"],"series":[{"name":"n","data":[3,7,4]}]}'
If the terminal is not recognized as supporting inline images, the chart is not written to the terminal (raw image bytes are never dumped into a terminal). Instead a hint is printed to standard error and the command exits non-zero:
chart: can't preview inline in this terminal (TERM=dumb). Use --output <file.png> to save, or --output - to pipe raw bytes.
Set AUX4_CHART_FORCE_INLINE=kitty or AUX4_CHART_FORCE_INLINE=iterm2 to force the given inline protocol regardless of TERM. This is mainly useful for testing and for capturing the escape sequence to inspect it:
AUX4_CHART_FORCE_INLINE=kitty aux4 chart bar --data '[1,2,3]' > kitty.out
To send the raw image bytes to standard output — for piping to another tool or redirecting to a file — use --output=- (a literal dash). Nothing but the image bytes is written to standard output:
aux4 chart bar --data '[5,10,3,8]' --output=- > chart.png
aux4 chart pie --data '[{"name":"A","value":30},{"name":"B","value":70}]' --output=- --format svg > chart.svg
> Note: use the --output=- form (with =). aux4's argument parser treats a space-separated bare - (as in --output -) as a boolean flag, so it does not reach the command as a value.
uptime renders the familiar public status-page widget: one equal-height bar per period, where the color — not the height — carries the meaning. Periods render in input order, so put the oldest first (it lands on the left) and the most recent last (on the right).
Each bar is classified by its uptime percentage against the --thresholds cutoffs (inclusive lower bounds, default 99.9,99,95):
| Band | Condition | Default color | |------|-----------|---------------| | up | uptime >= up | green | | warn | uptime >= warn | yellow / gold | | minor | uptime >= minor | orange | | down | below minor | red |
A period with a null uptime is drawn in a neutral none color.
aux4 chart uptime --data '[{"day":"d0","uptime":100},{"day":"d1","uptime":98.6},{"day":"d2","uptime":100},{"day":"d3","uptime":72}]' --title "Pages" --startLabel "90 days ago" --endLabel "Today" --output uptime.png
By default a caption row is drawn beneath the strip — <startLabel> ——— <NN.NN % uptime> ——— <endLabel>. The ends default to the first and last period labels (override with --startLabel / --endLabel), and the center defaults to the mean uptime (override with --summary). Pass --caption false for a bare strip.
Column mapping is the same as the other types: --x is the period/label column and --y is the uptime % column, both auto-inferred when omitted (a bare array of numbers also works). --colors overrides the status palette by name (up, warn, minor, down, none) and/or as a positional palette in the order up, warn, minor, down. Uptime-specific flags:
| Flag | Purpose | Default | |------|---------|---------| | --thresholds | Status cutoffs up,warn,minor (inclusive lower bounds) | 99.9,99,95 | | --startLabel | Left caption (oldest period) | first period label | | --endLabel | Right caption (most recent period) | last period label | | --summary | Center caption text | mean uptime, e.g. 99.83 % uptime | | --caption | Set to false to hide the caption row | true |
generate renders any chart type chosen at runtime with --type, instead of a fixed subcommand. It is a thin dispatcher: aux4 chart generate --type bar ... routes through the exact same code path as aux4 chart bar ... and produces identical output. It shares all the flags above (--data, --x, --y, --series, --output, --format, --title, --width, --height, --theme, --colors).
TYPE=bar
aux4 chart generate --type "$TYPE" --data '[{"month":"Jan","revenue":12000},{"month":"Feb","revenue":15000}]' --x month --y revenue --output sales.png
Valid types are bar, line, area, pie, doughnut, scatter, radar, and uptime. An invalid or missing --type fails with a clear message listing the valid types and exits non-zero.
This package is licensed under the Apache License 2.0.