Format specification

PivoGraph project file format (.pivograph), schema version 1

Current schema Introduced in PivoGraph 1.3 Format overview Markdown version

This specification describes the project file written by File → Save Project… and read by File → Open Project… or drag-and-drop. It also defines how external tools and AI agents can create projects that open for further editing in PivoGraph.

The JSON Schema is generated from the same validator the application uses, so it is authoritative for field names, enumerations and limits. This page explains the semantics that a schema cannot express.

1. File basics

  • Plain UTF-8 JSON, no byte-order mark (a leading BOM is tolerated when reading). Extension .pivograph; MIME type application/json. Where a tool needs the JSON language recognised from the file name, .pivograph.json is also accepted when opening.
  • The whole document is one JSON object. Unknown keys are rejected at every level with the JSON path of the offending key. Tool-specific data belongs in usermeta (document level or per table).
  • Everything in the file is data. There is no executable content, no external references are fetched when a file is opened, and the application never downloads the $schema URL.
  • Write complete, read tolerant. PivoGraph writes resolved document state explicitly so that saved settings do not silently inherit future defaults. Hand-written or generated documents may omit any optional field — and may give any nested object partially; the application fills its current defaults. Optional fields are expressed by omitting the key; null is only valid where the field's type includes it (missing cells, unmapped variable roles, automatic geometry).
  • Opening reports two kinds of problems: errors (the file is not opened; the current project is untouched) and warnings (the file opens, but the listed parts were ignored or replaced by defaults, for example a style override keyed to a data set that does not exist). Both carry JSON paths.

2. Envelope

{
  "$schema": "https://pivograph.com/schemas/project/v1.json",
  "format": "pivograph-project",
  "schemaVersion": 1,
  "generator": { "name": "PivoGraph", "version": "1.3.0", "url": "https://pivograph.com" },
  "createdAt": "2026-09-06T10:00:00.000Z",
  "modifiedAt": "2026-09-06T12:00:00.000Z",
  "meta": { "title": "Dose response" },
  "usermeta": {},
  "view": { "activeTableId": "dose", "workspaceView": "graph" },
  "tables": []
}
  • format (required): always "pivograph-project". Together with schemaVersion this is how a reader recognises the file; $schema is informational.
  • schemaVersion (required): integer. See "Versioning".
  • tables (required): array of tables in sidebar order (maximum 200). A minimal valid document is exactly format + schemaVersion + tables.
  • generator (optional): the program that wrote the file (name, version, optional url). Purely informational; PivoGraph always writes it.
  • createdAt / modifiedAt (optional): ISO 8601 timestamps with timezone. PivoGraph keeps createdAt from the first save and rewrites modifiedAt.
  • meta (optional): title (required inside meta, may be empty), optional description, authors[], license, source, keywords[]. license and source are meant for example / template projects.
  • usermeta (optional): a JSON object for tool-specific data. Its JSON value is preserved across save and reopen, never interpreted, and limited to 64 KB when serialised; whitespace and object formatting are not preserved.
  • view (optional): which table is active (activeTableId, must be an explicit table id) and whether the editor opens in "table" or "graph" view. This is the only presentation state in the file.

3. Tables

Each table is one data table of one of the eight families: xy, column, grouped, nested, contingency, survival, partsOfWhole, multipleVariables.

{
  "id": "dose",
  "name": "Dose response",
  "format": "xy",
  "replicates": 3,
  "x": [-9, -8, -7],
  "rowLabels": ["low", "mid", "high"],
  "dataSets": [
    { "name": "Control", "values": [[1.1, 1.2, null], [2.0, 2.1, 1.9], [5.0, 4.8, 5.1]] },
    { "name": "Treated", "values": [[0.9, 1.0, 1.1], [1.5, 1.6, 1.4], [2.9, 3.1, 3.0]] }
  ],
  "graph": { "variantId": "xy-points-error" },
  "analyses": [],
  "annotations": [],
  "titles": {},
  "usermeta": {}
}
  • format (required): the family.
  • dataSets (required unless derivedFrom is present; at least one): one entry per data set, corresponding to the table's data columns. values[row][replicate] holds finite numbers, null for missing, or text. Rows may be shorter than replicates (missing cells are filled with null). Data set names must be unique within a table. Text is accepted only in multipleVariables tables (text categories and labels); in every other family a text cell is an error, except that numeric-looking strings such as "1.5" are read as numbers.
  • id (optional): any non-empty string (≤ 128 characters), unique among tables; generated when omitted. Tables referenced by derivedFrom or view.activeTableId need an explicit id. Human-readable slugs are fine.
  • name (optional): defaults to Table N.
  • replicates (optional): number of sub-columns per data set. Fixed at 1 for column, contingency, survival, partsOfWhole and multipleVariables; ≥ 2 for nested; free for xy and grouped. When omitted it is inferred from the widest row (never below the family minimum).
  • x: only for xy and survival — the shared X column, one entry per row, aligned with values. In survival tables X is time and the cells are event codes (1 = event, 0 = censored).
  • rowLabels: optional row titles (categories for grouped / contingency / partsOfWhole, sample names for heat maps).
  • variableTypes: multipleVariables only — explicit overrides of the inferred variable type (continuous, categorical, label) keyed by variable (data set) name.
  • columnWidths: optional spreadsheet column widths; presentation only.
  • usermeta: optional tool-specific data for this table; same rules as the document-level slot.
  • derivedFrom: marks a results table produced by an analysis on another table: { "sourceTableId", "analysisId" }. Such tables carry no dataSets, x, rowLabels or variableTypes; their content is recomputed when the file opens. The referenced analysis must exist on the source table (with an explicit id) and must be one that produces a results table (frequencyDistribution, correlationMatrix).
  • Row count is the longest of x, rowLabels and any values; trailing empty rows are not stored.

4. Graph

graph is the graph document of the table (one graph per table). Only variantId is required; every other field, and every nested object inside them, may be given partially.

  • variantId: a graph variant from the PivoGraph registry, for example bar, column-box-whiskers, xy-points-line-error, grouped-heat-map, survival-staircase-ticks, parts-donut, mv-volcano. The full list is the GraphVariantId definition in the JSON Schema. The variant must belong to the table's family and be available for the table's replicate count (for example xy-points-error needs replicates > 1).
  • plot: statistical / topology options of the family ({ "family": "...", "options": {...} }); family must equal the table format. Options are merged over the defaults of the variant; a mode that the variant does not offer is replaced by the variant default with a warning. For multipleVariables tables plot is required because it carries the variable mapping: plot.options.kind must match the variant (bubblemv-bubble, scattermv-scatter, volcanomv-volcano, forestmv-forest) and plot.options.mapping gives data set indexes per role (x required; y, size, color, shape, label, lower, upper as the plot kind requires; omitted roles are unmapped).
  • activePresetId: built-in theme preset (modern, classic, floral, starry). applyPresetNonColor: whether the preset's non-colour constants are applied.
  • styleOverrides: user overrides on top of the theme, in three layers — global, perDataSet (keyed by data set name, or a semantic key such as volcano:up) and perPoint (data set name → point index). Each layer is a partial patch per element kind (bar, symbol, errorBar, line, …). Colours are either "theme" (follow the preset) or { "hex": "#rrggbb", "alpha": 0.8 }; hex colours have 3 or 6 digits and transparency always goes in alpha.
  • graphSettingsOverride: partial graph settings (bar width, gaps, baseline, line of identity, …).
  • axesOverrides: six partial buckets (frameAndOrigin, xCategorical, xContinuous, leftY, rightY, titlesAndFonts); "auto" means automatic where a number is otherwise expected. Nested objects (grid lines, tick appearance, number format, title font) may be partial. Title text is not part of the axes — see titles.
  • plotGeometry: plot-area size in points (null or omitted = automatic).
  • dataSetVisibility: data set index (as a string key) → false to hide; missing = visible.
  • elementActivation: optional-element switches in three scopes (global, perDataSet by index, graph).
  • legend: legend configuration including itemOverrides keyed by data set index or semantic id.
  • partsOfWholeBorders: outer / inner border of pie, donut and slice graphs.

Keys that refer to data sets by name and keys that refer to them by index coexist in version 1 (they mirror the application's model). Keys that match no data set are ignored with a warning. A future schema version may unify them; documents will be migrated automatically.

5. Analyses

analyses is a list of analysis instances attached to the table: { "id", "method", "params" }. id is optional (generated when omitted; required when a results table refers to it) and params may be partial (method defaults fill the rest). Results are not stored; they are recomputed from the data and the parameters.

  • ttest (column, xy): { "groupA": "name" | null, "groupB": "name" | null, "design": "unpaired" | "paired", "variance": "welch" | "equal" }. Group names refer to data set names; null uses the first two populated data sets.
  • linearRegression (xy): { "confidenceLevel": 90 | 95 | 99 }.
  • nonlinearRegression (xy, four-parameter logistic): { "model": "logX" | "linearX" }.
  • frequencyDistribution (column): { "binWidth": number | null, "binStart": number | null, "output": "count" | "fraction" | "percent", "cumulative": boolean }. Produces a results table (grouped).
  • correlationMatrix (multipleVariables): { "method": "pearson" | "spearman" }. Produces a results table (grouped) rendered as a heat map.

A method must be available for the table's family. Explicit analysis ids must be unique within their table.

6. Annotations and titles

annotations holds drawing objects placed on the graph, discriminated by type (id optional, generated when omitted):

  • bracket: significance bracket. left / right anchors are { "kind": "group", "groupIndex": n } (snapped to the n-th plotted data set) or { "kind": "free", "x": pt }; y is the bar position, text the label ("*", "ns", …).
  • text: free text at x, y (points). text may contain \n and ^{…} / _{…} for super- and subscripts.
  • shape: lines, arrows, rectangles, ellipses, brackets, … (shape enum), with two or three control points and optional bound text.

titles overrides the graph, X and Y titles (graph, x, y): text, drag offset (dx, dy), font and colour. This is the only place where title text lives.

7. Versioning and compatibility

  • schemaVersion is an integer. Every change to the document structure or to rendering-relevant defaults increases it by one and ships with a migration step. Migrations run once, at open time, before validation.
  • A reader opens every version from 1 up to its own. Older files are migrated in memory; the original file is never rewritten.
  • A file with a newer schemaVersion than the reader supports is refused with an explicit message (in the web application: reload the page to get the latest version). Nothing is guessed or silently dropped.
  • Registry identifiers (variants, presets, methods, symbols, colormaps) are part of the format contract. Deprecated identifiers are handled by migrations, not by aliases.
  • Every version has exactly one JSON Schema (/schemas/project/v<N>.json, immutable), one specification page (/developers/project-format/v<N>/, plus the Markdown copy v<N>.md) and one set of examples (/schemas/project/v<N>/examples/). /schemas/project/index.json lists all versions and which one is current. There is deliberately no "latest" schema URL: a file's $schema must stay stable.
  • Fixtures of every published version are kept in the repository and tested on every build.

8. Limits

Files larger than 50 MB, more than 200 tables, more than 2,000,000 cells in total, more than 200,000 rows or 500 data sets per table, more than 2,000 annotations or 200 analyses per table, non-finite numbers, over-long strings, or record keys named __proto__, constructor or prototype are rejected. Exact values are in the JSON Schema.

9. Validation

Opening a file goes through: size limit → strict UTF-8 → JSON parse → envelope (format, schemaVersion) → version gate → forbidden-key scan → migration → strict structural validation (JSON Schema equivalent) → semantic validation → import. Semantic errors include: duplicate ids, dangling derivedFrom or view.activeTableId references, replicate counts invalid for the family, variant / plot family not matching the table, variant unavailable for the replicate count, incomplete variable mapping, analysis methods unavailable for the family, text cells outside multipleVariables, rows wider than replicates, total cell count. Semantic warnings include: style / visibility / legend keys that match no data set, and plot modes the variant does not offer.

All problems are reported together with JSON paths (for example tables[0].graph.variantId). If any error occurs, the current project in the editor is left untouched.

10. Minimal example

{
  "format": "pivograph-project",
  "schemaVersion": 1,
  "tables": [
    {
      "name": "Cell viability",
      "format": "column",
      "dataSets": [
        { "name": "Vehicle", "values": [[98], [102], [95]] },
        { "name": "Compound", "values": [[71], [68], [74]] }
      ],
      "graph": { "variantId": "scatter-bar", "plot": { "family": "column", "options": { "mode": "meanWithSd" } } }
    }
  ]
}

More examples, one per table family, are published under https://pivograph.com/schemas/project/v1/examples/ (linked from the Developers page); representative.pivograph there is a complete document written by PivoGraph itself.

11. For AI assistants

PivoGraph's Help menu offers Copy Prompt for AI Assistants…: a prompt generated from the running version that lists every family, variant id, analysis method, the main format rules, and a minimal example. Give that prompt to an AI assistant, describe the data and intended figure, save the returned JSON as .pivograph, and open it in PivoGraph. Treat generated projects as drafts: verify the source values, analysis choices, and graph before using the result. Agents can also use this page, its Markdown copy, llms.txt, the JSON Schema, and the examples.

Changelog

  • v1 — first public version (PivoGraph 1.3).