fulgur-chart-cli 0.1.6

CLI for rendering chart.js-compatible JSON specs to deterministic SVG/PNG
fulgur-chart-cli-0.1.6 is not a library.

fulgur-chart

CI crates.io License: MIT OR Apache-2.0

A CLI that generates static SVG / PNG charts from a chart.js v4–compatible JSON spec (a side project of Fulgur).

Why

Generates deterministic charts — byte-identical output for the same input — without a browser or JavaScript. Combined with Fulgur, the resulting SVG can be embedded as a vector graphic in a PDF. Re-generating reports in CI produces no diff, making it easy to keep figures under version control.

Installation

cargo install --path crates/fulgur-chart-cli

This installs a binary named fulgur-chart.

Usage

Prepare a minimal chart.js spec (chart.json):

{
  "type": "bar",
  "data": {
    "labels": ["Jan", "Feb", "Mar"],
    "datasets": [
      { "label": "Revenue (k$)", "data": [120, 200, 150], "backgroundColor": "#36a2eb" }
    ]
  },
  "options": {
    "plugins": { "title": { "display": true, "text": "Monthly Revenue" } }
  }
}

Generate SVG / PNG:

# SVG (default)
fulgur-chart render chart.json -o chart.svg

# PNG (--scale sets the resolution multiplier; 2 doubles the pixel dimensions)
fulgur-chart render chart.json -o chart.png --format png --scale 2

Use - for stdin / stdout piping:

cat chart.json | fulgur-chart render - -o - > chart.svg

Key options:

  • --format svg|png — Output format. Inferred from the output extension (.png → png; otherwise / stdout → svg) when omitted.
  • --width <px> / --height <px> — Override canvas dimensions (default 800 × 450).
  • --scale <factor> — PNG resolution multiplier (default 1.0).
  • --font <path> — Replace the font used for measurement, SVG, and PNG (default: bundled Noto Sans JP).
  • --out-dir <dir> — Output directory for batch generation (see below).
  • --dsl chartjs|vegalite — Input DSL. Auto-detected when omitted: a top-level mark key selects Vega-Lite; a top-level type key selects chart.js.
  • --strict — Treat unknown / unsupported keys as errors (silently ignored by default).
# Override dimensions and detect unknown keys with --strict
fulgur-chart render chart.json -o chart.svg --width 1024 --height 576 --strict

Batch generation

Render multiple specs at once (useful for generating report figures in CI). Each input X.json is written to <out-dir>/X.<ext> (output is byte-identical per file).

fulgur-chart render specs/*.json --out-dir out/            # each → out/<name>.svg
fulgur-chart render specs/*.json --out-dir out/ --format png

Other subcommands

# Print the JSON Schema for an input DSL (useful for validation tooling)
fulgur-chart schema chartjs
fulgur-chart schema vegalite

# Inspect the semantic model (IR + layout) for a spec — pretty JSON
fulgur-chart inspect chart.json

Supported chart types

  • Bar chart (vertical / horizontal; horizontal via options.indexAxis: "y")
  • Stacked bar chart (options.scales.{x,y}.stacked: true)
  • Line chart
  • Area chart (datasets[].fill: true on a line dataset)
  • Pie chart
  • Doughnut chart
  • Scatter plot ({x, y} point data)
  • Bubble chart ({x, y, r} point data)
  • Radar chart
  • Mixed chart (per-dataset type, e.g. bar + line)
  • Progress bar chart (QuickChart-style; horizontal fill bar with centered percentage)
  • Matrix chart / heatmap ({x, y, v} point data; cells shaded by interpolating between two colors)
  • Box plot chart (5-number summary: type: "boxplot", data as nested arrays [min, q1, median, q3, max])
  • Gauge chart (QuickChart-style; semicircle with colored zones, needle, value label)
  • Radial gauge chart (QuickChart-style; full circle fill-to-value with center value text)

Supported chart.js subset

Supports a data-only, static subset:

  • typebar / line / pie / doughnut / scatter / bubble / radar / matrix / boxplot / progress / gauge / radialGauge (QuickChart's progressBar is also accepted as an alias)
  • data.labels
  • data.datasets[]label / data (numeric array; {x,y} / {x,y,r} for scatter/bubble; {x,y,v} for matrix; nested [min,q1,median,q3,max] arrays for boxplot) / backgroundColor / borderColor / borderWidth / fill / tension / pointRadius / type (per-dataset type for mixed charts)
  • For progress (alias progressBar), datasets[0].data holds each bar's value; an optional second dataset's data overrides the per-bar max (default 100). The percentage label is shown by default and can be hidden with options.plugins.datalabels.display: false.
  • For gauge, datasets[0].data holds cumulative zone thresholds, value is the needle value, and backgroundColor is the per-zone colors (minValue sets the lower bound). Configure with options.needle / options.valueLabel. The value label falls back to the rounded value (JS valueLabel.formatter is not executed).
  • For radialGauge, datasets[0].data holds a single value drawn as a fill-to-value arc on a track ring. Configure with options.domain / options.trackColor / options.centerPercentage / options.roundedCorners / options.centerArea (displayText / fontSize). The center value text falls back to the rounded value (JS centerArea.text is not executed).
  • options.indexAxis
  • options.plugins.title / options.plugins.legend (position: top/bottom/left/right; legend does not apply to gauge / radialGauge)
  • options.plugins.datalabels (display — renders a value label at each data point)
  • options.scales (stacked / suggestedMin / suggestedMax and a subset of other options)
  • options.theme (extension; see below)

Dynamic JavaScript features (callback / animation / interaction / plugin scripts) are not supported. Unknown keys are silently ignored by default; use --strict to detect them as errors.

Themes (options.theme)

chart.js v4 default colors and styles are used as a baseline. options.theme overrides the appearance (this is an extension key not present in chart.js itself; omit it to use the defaults).

  • palette — Array of color strings for automatic dataset / slice coloring
  • gridColor / textColor — Grid line color / text color
  • backgroundColor — Canvas background (transparent by default)
  • fontSize — Base font size for labels (px)

Colors accept #rgb / #rrggbb / rgb() / rgba() / hsl() / hsla() / CSS color names.

Vega-Lite input (--dsl vegalite)

In addition to chart.js specs, a minimal Vega-Lite subset is accepted as input:

# Explicit
fulgur-chart render chart.vl.json -o chart.svg --dsl vegalite

# Auto-detected (top-level "mark" key selects Vega-Lite)
fulgur-chart render chart.vl.json -o chart.svg

Supported subset: mark (bar / line / point → scatter / arc → pie), inline data.values, and encoding fields x / y / color / theta. The Tableau10 color palette is applied automatically to Vega-Lite specs. Input is converted to a shared intermediate representation, so output determinism and Fulgur integration are identical to chart.js input.

Ruby binding

An in-repository Ruby gem (crates/bindings/ruby) wraps the same rendering core via a Rust native extension (magnus / rb-sys). It is build-from-source only (not yet published to RubyGems) and requires a Rust toolchain.

cd crates/bindings/ruby
bundle install && bundle exec rake   # compile extension + run tests
require "fulgur_chart"
svg = FulgurChart.build(spec_json).width(800).height(450).render(:svg)
png = FulgurChart.build(spec_json).scale(2.0).render(:png)

See crates/bindings/ruby/README.md for the full API reference.

Fulgur integration

Embed the generated SVG in HTML with <img> and render to PDF with Fulgur:

<img src="out/bar.svg" alt="Monthly Revenue">
fulgur render -o report.pdf report.html

See examples/report.html for a minimal example. Bundling the same Noto Sans JP font on the Fulgur side ensures chart text glyphs match.

Determinism

The same input spec always produces byte-identical output. Only the bundled Noto Sans JP font is used; system fonts are never loaded.

Roadmap

The following are not yet implemented (candidates for future support):

  • Value labels on radar chart axes; data labels on scatter / radar
  • Dual-axis mixed charts (separate left/right y-scales); mixing with horizontal / stacked bars
  • Vega-Lite URL data, transform, and aggregate (currently inline data.values only)
  • Font subsetting (binary size reduction)

License

Code is dual-licensed under MIT OR Apache-2.0.

The bundled Noto Sans JP font is distributed under the SIL Open Font License 1.1 and is included as-is from the upstream notofonts / noto-cjk distribution.