malevich

Look up

Specs as data

A document: the versioned envelope a plot travels in.

The serde feature supports two related formats:

  • Document is the persistent, versioned format for files, caches, and network messages.
  • Raw Plot, Grid, mark, scale, frame, and theme serde implementations stay available for source compatibility and short-lived interchange. A raw payload has no version discriminator, so new persistent data should not use it directly.

Version 1#

training ── loss ── target 4 ┤ ───╮ │ ╰──╮ │ ╰────╮ 2 ┤ ╰─────╮ │ ╰───────╮ │ ╰──────────────────────────── 0 ┤ └┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬ 0 1 2 3 4 5 6 7 8 epoch
Plate 1. The lid is off: a Plot with two layers and a title.

The plot above, as the document Document::plot produces it. Every layer, every scale, every piece of furniture, the series inline, and gaps as null:

{
  "version": 1,
  "kind": "plot",
  "spec": {
    "layers": [
      {
        "Line": {
          "x": null,
          "y": [
            4.0,
            2.8,
            1.9,
            1.2,
            0.8,
            0.6,
            0.55,
            0.5,
            0.48
          ],
          "color": null,
          "label": "loss",
          "style": "Corners"
        }
      },
      {
        "Rule": {
          "orientation": {
            "Horizontal": 0.5
          },
          "color": null,
          "label": "target"
        }
      }
    ],
    "title": "training",
    "x": "Auto",
    "y": "Auto",
    "x_label": "epoch",
    "y_label": null,
    "x_domain": null,
    "y_domain": null,
    "colorbar": false
  }
}

A document is a small envelope around an owned plot or grid:

{
  "version": 1,
  "kind": "plot",
  "spec": { "layers": [] }
}

Constructing or decoding a Document validates the whole payload. Unknown schema versions, zero-column grids, ragged channels, invalid mark/scale combinations, and other malformed states are errors, not documents that fail later at render time. Unknown additive JSON fields are ignored. Omitted plot fields take their documented defaults. Gaps stay null. Function-backed lines still refuse to serialize. Closures have no honest data representation.

# #[cfg(feature = "serde")] {
use malevich::{Document, Frame};

let document = Document::plot(malevich::line([1.0, 3.0, 2.0]))?;
let json = serde_json::to_string_pretty(&document)?;
let decoded: Document = serde_json::from_str(&json)?;
assert_eq!(decoded.version(), 1);
assert!(!decoded.try_render(&Frame::portable(40, 10))?.is_empty());
# }
# Ok::<(), Box<dyn std::error::Error>>(())

The committed fixtures under tests/fixtures/serde/ are the compatibility contract. Every supported version must keep decoding, validating, and rendering. Encoder tests also compare canonical documents to those fixtures, catching an accidental field, variant, or tagging change. JSON whitespace and object-key order are not part of the contract.

To migrate a legacy raw payload, decode it as a Plot or Grid, pass it through Document::plot or Document::grid, and serialize the returned document. Keep the old decoder until all stored raw payloads have been migrated.

Future incompatible schemas will use a new envelope version and an explicit conversion into runtime Plot/Grid values. A reader never silently interprets an unknown version as the current one.