fulgur-chart
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
This installs a binary named fulgur-chart.
Usage
Prepare a minimal chart.js spec (chart.json):
Generate SVG / PNG:
# SVG (default)
# PNG (--scale sets the resolution multiplier; 2 doubles the pixel dimensions)
Use - for stdin / stdout piping:
|
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 (defaultchartjs; see Vega-Lite section below).--strict— Treat unknown / unsupported keys as errors (silently ignored by default).
# Override dimensions and detect unknown keys with --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).
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: trueon 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)
Supported chart.js subset
Supports a data-only, static subset:
type—bar/line/pie/doughnut/scatter/bubble/radar/progress(QuickChart'sprogressBaris also accepted as an alias)data.labelsdata.datasets[]—label/data(numeric array, or{x,y}/{x,y,r}for scatter/bubble) /backgroundColor/borderColor/borderWidth/fill/tension/pointRadius/type(per-dataset type for mixed charts)- For
progress(aliasprogressBar),datasets[0].dataholds each bar's value; an optional second dataset'sdataoverrides the per-bar max (default 100). The percentage label is shown by default and can be hidden withoptions.plugins.datalabels.display: false. options.indexAxisoptions.plugins.title/options.plugins.legend(position: top/bottom/left/right)options.plugins.datalabels(display— renders a value label at each data point)options.scales(stackedand 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 coloringgridColor/textColor— Grid line color / text colorbackgroundColor— 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:
Supported subset: mark (bar / line / point → scatter / arc → pie), inline
data.values, and encoding fields x / y / color / theta. Input is converted
to a shared intermediate representation, so output determinism and Fulgur integration
are identical to chart.js input.
Fulgur integration
Embed the generated SVG in HTML with <img> and render to PDF with Fulgur:
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, andaggregate(currently inlinedata.valuesonly) - 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.