benday-core 0.1.0

Spec-to-chart rendering core for benday: terminal charts from a Vega-Lite-style JSON spec
Documentation
  • Coverage
  • 23.74%
    47 out of 198 items documented0 out of 43 items with examples
  • Size
  • Source code size: 149.82 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 2.69 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 13s Average build duration of successful builds.
  • all releases: 13s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • fwojciec/benday
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • fwojciec

benday

Terminal charts from a Vega-Lite-style JSON spec, drawn in braille dots. Built for AI agents: pipe query results in, get a chart the human can read in the transcript.

Demo: rendering charts from JSON specs in the terminal

Install

cargo install benday

Or from source: git clone https://github.com/fwojciec/benday && cargo install --path benday/crates/benday-cli.

Use

Pipe rows in, pass a spec:

echo '{"columns":[{"name":"day","type":"STRING"},{"name":"n","type":"INT64"}],
       "rows":[["mon",32],["tue",78],["wed",51]]}' \
  | benday --spec '{"mark":"bar","encoding":{"x":{"field":"day"},"y":{"field":"n"}}}'

The spec can instead carry its data inline and arrive on stdin by itself. benday --help documents everything: the spec grammar, both stdin shapes, the bar rules, and worked examples — an agent needs nothing else.

The spec

A strict subset of Vega-Lite:

{
  "data"?: {                                   // optional — omit to pipe rows in
    "values": [ /* one JSON object per row */ ]           // tidy rows, OR
    // "columns": [ {"name":"day","type":"STRING"} ], "rows": [ ["mon",32] ]  // columnar
  },
  "mark": "bar" | "line" | "point" | "area",
  "encoding": {
    "x":      { "field": "...", "type"?: "quantitative" | "nominal" | "ordinal" },
    "y":      { "field": "...", "aggregate"?: "sum" | "mean" | "median" | "min" | "max" | "count" },
    "color"?: { "field": "..." }   // series split, or bar grouping
  },
  "title"?: "...", "width"?: 72, "height"?: 13   // plot area, in cells
}

Types are inferred from the data when omitted; a declared column type (INT64, DATE, …) beats inference, and an explicit spec "type" beats both. Two bar rules follow from the encoding, with no extra flags:

  • Orientation. Quantitative x + categorical y draws horizontal bars — built for rankings: one row per bar, height sized to the category count, names in a label column. Categorical x + quantitative y stays vertical.
  • Grouping. color naming a third field splits each category into a grouped cluster, one bar per value, with a legend. color naming the category field itself just tints the bars.

Rows chart in arrival order, so ORDER BY in the producing query is the sort. Unknown spec fields are errors that name the fix, never silently ignored — a silently wrong chart is the one failure a caller can't detect.

Data on stdin

With --spec/--spec-file, stdin carries the data, auto-detected between two shapes:

  • Columnar envelope{"columns": [...], "rows": [[...]]}, the shape an MCP query tool emits as structuredContent. Extra keys are ignored, so pipe it straight in; truncated and total_rows flow to --meta.
  • Bare array of row objects[{"day":"mon","n":32}, ...].

Declared dates chart as ordinal (ISO strings sort chronologically); date bucketing and label formatting belong to the SQL that produced the rows.

Flags and output

--marker braille|octant · --bar-style dots|blocks · --theme benday|lichtenstein|rotogravure · --width/--height · --no-color · --meta

Output is deterministic and color stays on when piped — the transcript is the display. Errors are JSON on stderr with the fix in the message; exit 2 = invalid spec, 3 = data doesn't fit the encoding. --meta prints scale domains, resolved series colors, and dropped-row counts to stderr, so a caller can verify what was drawn without parsing dot art.

Development

Two-stage pipeline: compile(spec, data) -> Scene makes every data- and layout-dependent decision; rasterize(scene) -> glyphs turns the geometry into cells. A new chart type is compiler work; a new visual style is rasterizer work. Three test layers: a spec→scene snapshot corpus (crates/benday-core/tests/cases/), rasterizer unit tests, and a rendered glyph gallery. make validate runs exactly what CI runs. Design records live in docs/plans/.

Status

Works: bars (vertical, horizontal, grouped), line, point, area, multi-series legends, aggregation, type inference, three themes. Planned: stacked bars, value labels at bar ends, benday schema (JSON Schema output). Deliberately absent: temporal scales and sort grammar — SQL owns sorting, bucketing, and date formatting. Negative bars are a hard error, not a silent miss.

Named for Ben-Day dots: images composed from a raster of small marks, which is what terminal cells are. MIT. Octant glyph table derived from ratatui (MIT).