belt-charts
A CLI tool for generating charts and visualizations from Belt benchmark verbose metrics data.
Installation
From npm (once published)
npm install -g belt-chartsFor Development
npm install
npm run build
npm linkUsage
After installation, the belt-charts command will be globally available:
belt-charts --helpCommand Reference
All commands share a common set of base options, plus command-specific options listed below.
Base Options (all commands)
| Option | Default | Description |
|---|---|---|
<glob-pattern> |
(required) | Glob pattern for input CSV files, e.g. "results/*.csv" |
-o, --output <file> |
verbose_metrics.png |
Output file path. Use .svg for vector output |
-w, --width <px> |
1400 |
Chart width in pixels |
-h, --height <px> |
800 |
Chart height in pixels |
--remove-first-ticks <n> |
1 |
Skip the first N ticks (removes benchmark warm-up spikes) |
--max-ticks <n> |
0 |
Only include ticks up to N. 0 = no limit |
--trim-prefix <string> |
"" |
Strip a common prefix from all file names in chart labels |
--metrics <list> |
default set | Comma-separated list of metric names to include, e.g. "wholeUpdate,entityUpdate". Use * for all defaults |
--min-percent <number> |
0 |
Hide any metric/entity whose max value never exceeds this % of the reference total across all files. 0 = no filter |
--aggregate-file <path> |
"" |
Path to a run-results file for outlier filtering |
--stddev-filter <n> |
3 |
Remove runs outside N standard deviations from the mean (requires --aggregate-file) |
summary
Stacked-bar chart aggregating all metrics across input files, with an optional in-chart table.
| Option | Default | Description |
|---|---|---|
-a, --aggregate-strategy |
average |
How to aggregate ticks per run: average, minimum, maximum, median, standard_deviation |
--summary-table <bool> |
true |
Render a stats table inside the chart |
--summary-table-file <bool> |
true |
Export the table as .csv and .md alongside the chart |
--title-override <string> |
(auto) | Override the chart title |
belt-charts summary "results/my_amazing_map*.csv"
-w 1500 -h 800
--remove-first-ticks 30
-o "charts/all_metrics.png"
-a average
--metrics "wholeUpdate,entityUpdate,controlBehaviorUpdate,transportLinesUpdate,electricHeatFluidCircuitUpdate"
--summary-table true
--summary-table-file true
--trim-prefix "my_amazing_map_"
--min-percent 1
summary-per-run
Same as summary but shows one bar per individual run instead of averaging across runs.
| Option | Default | Description |
|---|---|---|
-a, --aggregate-strategy |
average |
Per-run statistic to display (average, minimum, maximum, median, standard_deviation) |
--summary-table <bool> |
true |
Render a stats table inside the chart |
--summary-table-file <bool> |
true |
Export the table as .csv and .md |
--title-override <string> |
(auto) | Override the chart title |
--sort-by <run|total> |
total |
Sort bars by run number or by total wholeUpdate time |
belt-charts summary-per-run "results/my_amazing_map*.csv"
-w 2000 -h 1000
--remove-first-ticks 30
-o "charts/per_run_metrics.png"
-a average
--sort-by total
--summary-table true
--summary-table-file true
--trim-prefix "my_amazing_map_"
bar / line
Timeseries charts — one output file per input CSV. bar renders stacked areas; line renders lines.
| Option | Default | Description |
|---|---|---|
-a, --aggregate-strategy |
average |
How to aggregate runs per tick |
--tick-window-aggregation <n> |
0 |
Time-weighted average over a rolling window of N ticks. 0 = no windowing |
--max-update <number> |
(auto) | Y-axis ceiling in µs. Auto-detected from data if omitted |
When any PascalCase entity metric (e.g.
Inserter,AssemblingMachine) is included in--metrics,entityUpdateis automatically excluded from the stacked areas and becomes the "Total Entity Update Average" reference line instead of "Whole Update Average".
belt-charts bar "results/my_amazing_map*.csv"
-w 1200 -h 800
--remove-first-ticks 30
-o "charts/timeseries.png"
-a average
--max-ticks 18000
--max-update 3000
--trim-prefix "my_amazing_map_"
--metrics "wholeUpdate,controlBehaviorUpdate,transportLinesUpdate,electricHeatFluidCircuitUpdate,entityUpdate"
--tick-window-aggregation 60
boxplot
Box-and-whisker chart showing the distribution of wholeUpdate across all runs per file.
| Option | Default | Description |
|---|---|---|
--min-update <number> |
(auto) | Y-axis floor in µs |
--max-update <number> |
(auto) | Y-axis ceiling in µs |
belt-charts boxplot "results/my_amazing_map*.csv"
-w 1000 -h 800
--remove-first-ticks 30
-o "charts/run_distribution.png"
--trim-prefix "my_amazing_map_"
table
Exports aggregate statistics to .csv and .md without generating a chart image.
| Option | Default | Description |
|---|---|---|
-a, --aggregate-strategy |
average |
How to aggregate runs |
belt-charts table "results/my_amazing_map*.csv"
--remove-first-ticks 30
-o "charts/stats.csv"
-a average
--trim-prefix "my_amazing_map_"
entity-summary
Stacked-bar chart decomposing entityUpdate into per-entity-type contributions. Requires Factorio verbose metrics CSVs that include the PascalCase per-entity columns (e.g. Inserter, AssemblingMachine).
| Option | Default | Description |
|---|---|---|
-a, --aggregate-strategy |
average |
How to aggregate runs |
--top-n <n> |
15 |
Show only the top N entity types; the rest fold into "Other Entity Update". 0 = show all |
--sort-by <run|total> |
total |
(entity-summary-per-run only) Sort bars by run number or by entityUpdate total |
--summary-table <bool> |
true |
Render a stats table inside the chart |
--summary-table-file <bool> |
true |
Export the table as .csv and .md |
--title-override <string> |
(auto) | Override the chart title |
belt-charts entity-summary "results/my_amazing_map*.csv"
-w 1600 -h 1000
--remove-first-ticks 30
-o "charts/entity_summary.png"
-a average
--top-n 15
--min-percent 1
--summary-table true
--summary-table-file true
--trim-prefix "my_amazing_map_"
entity-summary-per-run
Same as entity-summary but shows one bar per individual run (no averaging across runs).
| Option | Default | Description |
|---|---|---|
-a, --aggregate-strategy |
average |
Per-run statistic to display |
--top-n <n> |
15 |
Top N entity types |
--sort-by <run|total> |
total |
Sort bars by run number or entityUpdate total |
--summary-table <bool> |
true |
Render a stats table inside the chart |
--summary-table-file <bool> |
true |
Export the table as .csv and .md |
--title-override <string> |
(auto) | Override the chart title |
belt-charts entity-summary-per-run "results/my_amazing_map*.csv"
-w 1600 -h 1000
--remove-first-ticks 30
-o "charts/entity_summary_per_run.png"
-a average
--top-n 15
--sort-by run
--min-percent 1
--summary-table true
--summary-table-file true
--trim-prefix "my_amazing_map_"
entity-matrix
Panel chart — rows = entity types, columns = benchmark files, cells = horizontal bar scaled to a shared x-axis. Good for comparing the same entity type across many designs at a glance.
| Option | Default | Description |
|---|---|---|
-a, --aggregate-strategy |
average |
How to aggregate runs |
--top-n <n> |
15 |
Show only the top N entity types |
--title-override <string> |
(auto) | Override the chart title |
belt-charts entity-matrix "results/my_amazing_map*.csv"
-w 1400 -h 800
--remove-first-ticks 30
-o "charts/entity_matrix.png"
-a average
--top-n 15
--min-percent 1
--trim-prefix "my_amazing_map_"
entity-heatmap
2-D heatmap — rows = entity types, columns = benchmark files, cells colored by µs intensity using a viridis-style gradient.
| Option | Default | Description |
|---|---|---|
-a, --aggregate-strategy |
average |
How to aggregate runs |
--top-n <n> |
20 |
Show only the top N entity types |
--normalize <global|column|row> |
global |
Color scale normalization. global = single scale for all cells (best for spotting the absolute hottest cell); column = per-design scale (best for comparing entity profiles within one design); row = per-entity scale (best for spotting which design stresses a particular entity the most) |
--show-values <bool> |
true |
Render µs values inside each cell |
--title-override <string> |
(auto) | Override the chart title |
belt-charts entity-heatmap "results/my_amazing_map*.csv"
-w 1400 -h 900
--remove-first-ticks 30
-o "charts/entity_heatmap_global.png"
-a average
--top-n 20
--min-percent 1
--normalize global
--show-values true
--trim-prefix "my_amazing_map_"
SVG Export
All chart commands support SVG output in addition to PNG. The format is inferred from the --output file extension — simply use .svg instead of .png:
belt-charts summary "results/*.csv" -o "charts/summary.svg" ...
belt-charts boxplot "results/*.csv" -o "charts/boxplot.svg" ...
belt-charts bar "results/*.csv" -o "charts/timeseries.svg" ...SVG files are rendered using skia-canvas's native vector backend, producing true vector output rather than an embedded bitmap. This makes them resolution-independent and suitable for embedding in reports or web pages.
For line and bar commands (which generate one file per input CSV), the extension is preserved per-file:
# Input: results/map_a.csv, results/map_b.csv
# Output: charts/timeseries_map_a.svg, charts/timeseries_map_b.svg
belt-charts bar "results/*.csv" -o "charts/timeseries.svg" ...PNG is the default when no extension is specified or when .png is used.
Requirements
- Node.js >= 14.0.0
- npm or yarn
License
MIT