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:
schemais not"vyuh.diagram", orversionis not exactly1.- 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: textrecord has notype,values,style,ports,clipContent,isCollapsedoraspectLocked) and every nested record, including attribute and content envelopes (type,version,data). The error names the key and its path, such asUnknown key "extra" at elements[2].labels[0]. - Two contents of one story share an
idorslotId. - 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
| Key | Type | Meaning |
|---|---|---|
schema | string | Always "vyuh.diagram". |
version | integer | Always 1. |
id | string | Document identity. |
revision | integer | Revision the document was saved at. |
elements | array | Shape, 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:
| Value | JSON |
|---|---|
| Color | Unsigned 32-bit ARGB integer. |
| Point | {x, y} finite numbers. |
| Frame | {x, y, width, height}; width and height not negative. |
| Fill | A 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:
| Field | JSON |
|---|---|
NumberValueField | Finite number within its bounds; integral when declared integer. |
TextValueField | String, from its allowed values when declared. |
ChoiceValueField | String from its values. |
BooleanValueField | true or false. |
ColorValueField | ARGB integer. |
ObjectValueField | Object 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. |
ImageValueField | null 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
| Package | Contributes |
|---|---|
vyuh_diagram_core | Shapes 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_shapes | Shapes 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_table | Shape 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_text | Text 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_connectors | Routes 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.