Skip to content

Diagram JSON format ​

A saved diagram is one JSON document, written and read by DiagramJsonCodec from vyuh_diagram_codec_json. The format has one version, 1. The codec writes version 1 and reads only version 1.

Save with controller.save(DiagramJsonCodec.new) and load with controller.edits.load(json, format: DiagramJsonCodec.new, expectedRevision: ...). Both build the codec over the installed registry's codecs, so every extension's text attributes and content persist without extra wiring. Install the same extensions when loading.

Rules ​

The decoder is strict. Each of these throws FormatException:

  • schema is not "vyuh.diagram", or version is not exactly 1.
  • A key the encoder always writes is missing, or any key has the wrong JSON type.
  • An enum name is unknown, a coordinate is not finite, or a width or height is negative.
  • A record has a key this page does not list for it. This applies to the document, every element record (a kind: text record has no type, values, style, ports, clipContent, isCollapsed or aspectLocked) and every nested record, including attribute and content envelopes (type, version, data). The error names the key and its path, such as Unknown key "extra" at elements[2].labels[0].
  • Two contents of one story share an id or slotId.
  • A text element owns a child, or a connector binds to a named port on a text element.
  • An attribute or content envelope names a codec type that is not installed, or a version that codec does not read.

Three maps are open: shape values and connector routeOptions, whose keys the installed definitions check on import, and an envelope's data, which its codec reads.

Sparse keys may be omitted. The encoder omits them at their default, and the decoder restores that default: parentId, isCollapsed, aspectLocked, textStory on shapes, textType, inkPreset, preserveRoute, routedPoints, routeOptions, bundleKey, portId, anchorMode, targetLayerId, connectedFill, slotId, attributes, contents, fontFamily, fontSize and run color.

Decoding checks the format only. load then admits the document against the installed registry and rejects it, leaving the current document in place, when an element ID is empty or repeated, a shape, text or connector type or a route is not installed, or shape values or route options do not match their declared fields.

Document ​

KeyTypeMeaning
schemastringAlways "vyuh.diagram".
versionintegerAlways 1.
idstringDocument identity.
revisionintegerRevision the document was saved at.
elementsarrayShape, text and connector records.

A rectangle, a text element and a connector between them:

json
{
  "schema": "vyuh.diagram",
  "version": 1,
  "id": "doc-1",
  "revision": 3,
  "elements": [
    {
      "kind": "shape",
      "id": "box",
      "type": "rectangle",
      "values": {},
      "zIndex": 0,
      "isLocked": false,
      "isVisible": true,
      "opacity": 1,
      "frame": { "x": 0, "y": 0, "width": 160, "height": 100 },
      "rotation": 0,
      "clipContent": false,
      "style": {
        "fill": 4294967295,
        "stroke": 4279308561,
        "strokeWidth": 2,
        "cornerRadius": 10,
        "strokeStyle": "solid"
      },
      "textSizingMode": "fixedWidth",
      "ports": []
    },
    {
      "kind": "text",
      "id": "note",
      "zIndex": 1,
      "isLocked": false,
      "isVisible": true,
      "opacity": 1,
      "frame": { "x": 260, "y": 30, "width": 120, "height": 40 },
      "rotation": 0,
      "textStory": {
        "id": "note-story",
        "blocks": [
          {
            "id": "p1",
            "alignment": "start",
            "direction": "ltr",
            "role": "body",
            "runs": [{ "text": "Hello" }]
          }
        ]
      },
      "textSizingMode": "fixedWidth"
    },
    {
      "kind": "connector",
      "type": "connector",
      "id": "link",
      "zIndex": -1,
      "isLocked": false,
      "isVisible": true,
      "opacity": 1,
      "start": { "kind": "bound", "elementId": "box", "anchor": { "x": 0.5, "y": 0.5 } },
      "end": { "kind": "bound", "elementId": "note", "anchor": { "x": 0.5, "y": 0.5 } },
      "router": "straight",
      "controlPoints": [],
      "bend": 0,
      "terminalGap": 0,
      "color": 4279308561,
      "strokeWidth": 2,
      "strokeStyle": "solid",
      "startArrowhead": { "kind": "none", "width": 12, "height": 10 },
      "endArrowhead": { "kind": "angle", "width": 12, "height": 10 },
      "labels": []
    }
  ]
}

Elements ​

Every record has kind (shape, text or connector), id (string), zIndex (integer), isLocked and isVisible (booleans), opacity (0 to 1) and an optional parentId.

Shared values:

ValueJSON
ColorUnsigned 32-bit ARGB integer.
Point{x, y} finite numbers.
Frame{x, y, width, height}; width and height not negative.
FillA color, or {kind: "none"}, {kind: "linear", begin, end, stops} or {kind: "radial", center, radius, stops}; each stop is {offset, color}.
Text story{id, blocks, contents?}. A block is {id, alignment, direction, role, slotId?, attributes?, runs}; a run is {text, attributes?, fontFamily?, fontSize?, color?}.

Shape (kind: shape). Required: type (installed shape type), values (object, see below), frame, rotation, clipContent, style, textSizingMode (fixedWidth or autoWidth) and ports. Optional: isCollapsed, aspectLocked and textStory. style is {fill, stroke, strokeWidth, cornerRadius, strokeStyle, inkPreset?}; strokeStyle is solid, dashed, dotted or handDrawn; inkPreset is pen, brush or marker. A port is {id, side, offsetKind, offset, shape, width, height, fill, stroke, strokeWidth, targetLayerId?, connectedFill?}, where side is top, right, bottom or left and offsetKind is fraction, distanceFromStart or distanceFromEnd.

Text (kind: text). Required: frame, rotation, textStory and textSizingMode. Optional: textType, the installed text element type when it is not text.

Connector (kind: connector). Required: type (installed connector type), start, end, router (installed route), controlPoints, bend, terminalGap, color, strokeWidth, strokeStyle, startArrowhead, endArrowhead and labels. Optional: preserveRoute, routedPoints, routeOptions and bundleKey. An endpoint is {kind: "free", point} or {kind: "bound", elementId, anchor, portId?, anchorMode?} with anchorModeoutline or point. An arrowhead is {kind, width, height}. A label is {id, story, fraction, offset, width, height, presentation}; presentation is {kind: "plain"} or {kind: "badge", fill, cornerRadius, padding, stroke, strokeWidth, strokeStyle}.

Extending the format ​

Extensions add data in four places. The registry applies their codecs automatically; element records keep the same shape.

Shape values. A shape's values holds exactly the fields its definition declares, each validated by its ValueField:

FieldJSON
NumberValueFieldFinite number within its bounds; integral when declared integer.
TextValueFieldString, from its allowed values when declared.
ChoiceValueFieldString from its values.
BooleanValueFieldtrue or false.
ColorValueFieldARGB integer.
ObjectValueFieldObject of finite JSON values.
PathValueField{closed, nodes}; a node is {id, position, incoming, outgoing, mode, segment} with [x, y] points, mode corner, smooth or symmetric, and segment line or cubic.
InkValueField{points, widths?}; points is a flat [x0, y0, x1, y1, ...] list in unit coordinates, widths one factor in (0, 2] per point.
ImageValueFieldnull or {uri, fit, fileName?} with fit contain, cover or fill.
json
{ "type": "polygon", "values": { "sides": 5 } }

Route options. A connector's routeOptions maps option keys its route declares to values accepted by the option's ValueField. Omitted options use their default.

json
{ "router": "orthogonal", "routeOptions": { "cornerRadius": 12 } }

Text attributes. A block's or run's attributes is an envelope written by the DiagramTextCodec of the text definition that owns them: {type, version, data}.

json
{ "type": "richText", "version": 1, "data": { "kind": "orderedListItem", "nestingLevel": 1 } }

Content. A story's contents holds envelopes written by the DiagramContentCodec of each content type: {type, version, data}. Every codec in this SDK is version 1 and reads only version 1.

json
{ "type": "vyuh.content.table", "version": 1, "data": { "id": "t1", "slotId": "table", "columns": [], "rows": [] } }

What each package contributes ​

PackageContributes
vyuh_diagram_coreShapes rectangle, ellipse, card, group, frame, comment; path and line with path (PathValueField); polygon with sides (NumberValueField, integer 3 to 20); freehand with ink (InkValueField); image with image (ImageValueField). Text element text (no attributes). Connector type connector, route straight. Arrowheads none, angle. Port shapes circle, square, diamond, halfCapsule.
vyuh_diagram_extension_shapesShapes shapes.start, shapes.end, shapes.subprocess, shapes.junction, shapes.parallelogram, shapes.trapezoid, shapes.cylinder, shapes.document; shapes.decision, shapes.preparation, shapes.extract with sides; shapes.entity with secondaryEnabled (BooleanValueField). shapes.arrow: direction (TextValueField: right, left, up, down), doubleHeaded, shaft, head, headWidth. shapes.star: points (integer), innerRadius. shapes.arc: closeToCenter, startAngle, sweepAngle. shapes.callout: pointer, depth. Numbers are NumberValueField, flags BooleanValueField.
vyuh_diagram_extension_tableShape table. Content vyuh.content.table: {id, slotId, cellPadding, outerBorder, rowBorder, columnBorder, merges, columns, rows}; a border is {color, width, style}, a merge {anchorCellId, rowSpan, columnSpan}, a column {id, width, borderAfter?}, a row {id, height, borderAfter?, cells}, a cell exactly {id, blocks}.
vyuh_diagram_extension_rich_textText element richText. Text codec richText: paragraph data is {kind?, heading?, nestingLevel?, checked?, markerStyle?}, omitting defaults, with kind unorderedListItem, orderedListItem or checklistItem, heading h1 to h6, and markerStyle disc, circle, square, decimal, lowerAlpha, upperAlpha, lowerRoman or upperRoman; run data is {marks}, a nonempty list of bold, italic, underline, strikethrough, superscript, subscript.
vyuh_diagram_extension_connectorsRoutes orthogonal (options terminalExtension and cornerRadius, NumberValueField at least 0), quadratic, bezier. Arrowheads triangle, filledTriangle, square, dot, diamond, invertedTriangle, bar, filledDiamond, cross, capsule, halfCapsule.

Mermaid import, layout, editor, minimap and snap guides add nothing to the format: they produce or edit the records above.