The .dioptra project file
A .dioptra file is a zip archive (deflate) of pretty-printed JSON. unzip -l lists it;
unzip extracts it; git diff on the extracted tree shows exactly what changed. Keys are
written in a stable order; numbers use shortest round-trip representation; empty cells are
null.
manifest.json format version, Dioptra version, created/modified timestamps
project.json title, p-value style, display order of objects, layouts
tables/<id>.json one table: kind, datasets, subcolumns, columns (columnar arrays),
row titles, column sources and formulas, excluded cells, text cells,
edit log
analyses/<id>.json analysis spec (table id, kind, options); no results
graphs/<id>.json graph spec (source id, family, mapping, style, axes, legend, options, annotations; docs/GRAPHS.md)
results/<id>.json cached results sheets (optional; recomputed when absent or stale)
history.jsonl command log: one JSON object per line, newest last
extensions/<name> UTF-8 files kept for other components, e.g. Dioptra Lab's audit
trail; read back and saved unchanged by the core
manifest.json
{ "format": "dioptra/1.2", "app": "0.4.0", "created": "2026-10-11T09:00:00Z", "modified": "..." }
Readers must accept any dioptra/1.x; unknown keys are preserved on round trip where
possible and ignored otherwise.
tables/<id>.json
{
"id": "…", "title": "Dose response", "kind": "xy",
"x": { "title": "Dose (nM)", "values": [1, 10, 100, null], "source": {"kind": "pasted", "at": "…"} },
"row_titles": ["", "", "", ""],
"datasets": [
{ "name": "Control", "format": {"kind": "values", "n": 3},
"subcolumns": [
{ "values": [0.1, 0.5, 0.9, null], "excluded": [3], "text": {"2": "n/a"},
"source": {"kind": "formula", "expr": "log10(A)"}, "decimals": 2,
"edits": [{"row": 1, "at": "…", "from": 0.4, "to": 0.5}] }
] }
],
"variables": null
}
valuesis a dense array the length of the table’s row count;nullmeans empty.excludedis a sorted list of row indices.textholds non-numeric entries by row index (kept, shown, never used in numerics).titleon a subcolumn names it (a replicate or a nested subgroup, e.g. from Prism’s subcolumn titles); absent when unnamed. The X column’s title is the same field.sourceis one ofentered,pasted {at},imported {file, sheet, column},formula {expr},derived {table, op}.editsis the sparse per-cell edit log that, withsource, gives each cell’s provenance.- For
variablestables, each dataset has a single subcolumn and atype. - Derived tables carry
derived_from: {table, op}at the top level.
history.jsonl
Each line is { "at": "…", "command": { … }, "label": "Paste 3×120 into Table 1" }. The
engine replays nothing from it on open; it is an audit trail and the source of the undo
timeline in the UI.
graphs/<id>.json
{
"id": "…", "title": "Graph of Paired t test of Table 1",
"spec": {
"source": "…", "family": "column",
"mapping": { "datasets": [0, 1], "error_bars": "sem", "points": true },
"style": { "theme": "instrument", "size_mm": { "width": 85, "height": 70 },
"datasets": [{ "dataset": 1, "pattern": "hatch" }] },
"axes": { … }, "legend": { "position": "auto" },
"options": { "jitter": 0, …,
"connecting": { "connect": "individuals", "style": "solid", "colour_by": "single",
"opacity": 0.55, "missing": "gap", "direction": "within_groups",
"summary_style": "solid" } },
"annotations": [{ "kind": "significance_bracket", "from": 0, "to": 1, "text": "p = .002" }]
}
}
Every field of the spec is listed in docs/GRAPHS.md. Fields at their defaults may be left
out; connecting, orientation, error_direction, bar_fill and summary_line are
written only when they differ from the default, so a graph without connecting lines is
written exactly as format 1.0 wrote it.
Versions and migrations
| Format | Dioptra | Change |
|---|---|---|
dioptra/1 | 0.1, 0.2 | First format |
dioptra/1.1 | 0.3 | Graph specs gain options.connecting, orientation, error_direction, cap_pt, bar_width, group_width, bar_fill, summary_line and style.datasets[].pattern. The before_after family is retired. |
dioptra/1.2 | 0.4 | Layouts gain style (page width, font size, line width, panel letters). Files may carry extensions/<name>. Readers of 1.1 open 1.2 files and ignore the additions. |
Migration 1.0 → 1.1 runs when a file is opened: a graph whose family is before_after
becomes the strip spec that draws the same picture (jitter: 0, error_bars: none,
summary_line: false, connecting.connect both when mean_line was on and individuals
otherwise, opacity 0.4). Nothing else changes; saving writes the current format. A 0.2 reader opens a
1.1 file (the minor number is additive) and ignores the new fields, so it draws such a graph
without its connecting lines.
Compatibility promise
The format version bumps the minor number for additive changes and the major number for anything a 1.x reader could misread. Readers of version N open any file ≤ N.