All docs

JSON format

JSON schema reference

Every field in an openCanviz JSON file, with types and defaults, plus the full JSON Schema to validate against.

Last updated

This page lists every field openCanviz reads from a JSON file, then gives the full JSON Schema (draft 2020-12). Validate your file against the schema before you import it. The schema lives at https://opencanviz.com/schema/v1.json, and the full text is at the end of this page. For the big picture, start with The openCanviz JSON format.

Fields marked "set by openCanviz" in the schema are filled in by the app. Leave them out of files you write.

Project

The top-level object. Use this form for any video, and always for more than one scene.

FieldTypeWhat it does
$schemastringOptional. Set it to https://opencanviz.com/schema/v1.json so code editors check the file and suggest fields. Import ignores it.
titlestringProject name. Defaults to "Imported project".
artboardobjectThe video frame. Always include it. See Artboard below.
diagramsarray of scenesRequired. The scenes, in playback order.
sceneNamesarray of stringsOne name per scene. Missing names become "Scene 1", "Scene 2" and so on.
transitionsarrayEntry i is the transition between scene i and scene i + 1, or null for a hard cut.
showCaptionsbooleanShow step captions on screen. Default true.
captionStyleobjectHow captions look. See Caption style below.
sketchModebooleanDraw every shape and line in a hand-drawn style. Default false.
floatEnabledbooleanGive non-text nodes a gentle idle bob. Default false.
flowEnabledbooleanRun edge flow animations. Default true.
meta.openAtLastStepbooleanOpen the editor on the last step instead of the first.

Single-scene form: instead of diagrams, you may put nodes, edges, steps, narration and paths at the top level, next to title, artboard and captionStyle. It behaves like a project with one scene.

Artboard

FieldTypeWhat it does
x, ynumberTop-left corner of the frame on the canvas.
width, heightnumberFrame size in pixels: 1920 by 1080 for 16:9, 1080 by 1920 for 9:16, 1080 by 1080 for 1:1.
backgroundcolorBackground color of the video.

Center the frame on the origin by setting x to minus half the width and y to minus half the height.

Scene

Each entry in diagrams.

FieldTypeWhat it does
nodesarray of nodesRequired. Every node in the scene as it looks in step 0, including nodes that appear later at opacity 0.
edgesarray of edgesRequired. Every edge in the scene as it looks in step 0.
stepsarray of stepsRequired, at least one. A scene with no steps shows nothing when it plays.
narrationarray of clipsNarration lines, at most one per step.
pathsobjectMotion paths by id, for pathFollow actions. Each has id and points, a list of at least two x, y points.
backgroundcolorA flat background for this scene only.

Node

Fields every node can use. id, kind, x, y, w and h are required.

FieldTypeWhat it does
idstringUnique within the scene, the same in every step.
kindstringThe node type. See Node kinds below.
x, ynumberTop-left corner, in pixels.
w, hnumberWidth and height. A node under 10 by 10 pixels, or under 2 pixels in either direction, is not drawn.
fill, strokecolorFill and outline color.
strokeWidthnumberOutline width in pixels.
strokeDashArrayarray of numbersDash pattern for the outline, for example [8, 6].
cornerRadiusnumberRounded corners on rect.
opacitynumber0 to 1. Default 1. Use 0 for nodes that appear in a later step.
zIndexnumberHigher numbers draw in front.
labelstringText drawn inside a shape.
labelColor, labelFontSizecolor, numberLabel color and size.
labelFontWeightstringnormal or bold.
labelFontStyle, labelTextAlignstringnormal or italic; left, center or right.
labelFontFamily, labelPaddingstring, numberLabel font and inner padding.
rotationZnumberRotation in degrees.
scaleX, scaleYnumberScale. Default 1.
glownumberGlow strength, 0 to 1.
rough, roughnessboolean, numberHand-drawn look for this node only.
parentIdstringId of a group node that holds this node.

Fields for text nodes:

FieldTypeWhat it does
textstringThe text. Use \n for a line break.
fontSize, fontFamily, fontWeightnumber, string, numberFont size, family and weight such as 400 or 700.
fontStylestringnormal or italic.
colorcolorText color.
textAlignstringleft, center or right.
verticalAlignstringtop, middle or bottom.
lineHeight, letterSpacingnumberLine spacing and letter spacing.
wrapstringword, char, ellipsis or clip.
autoFitstringnone, shrink or grow.
typeProgressnumberTypewriter reveal: 0 shows no text, 1 shows all of it.
syntaxStyle, highlightedLinesstring, arrayCode styling (none, pseudo, python, javascript) and line numbers to highlight.

Fields for other kinds:

FieldUsed byWhat it does
value, progressColorprogressFill amount from 0 to 1, and the fill color.
points, innerRadiusstar, polygonNumber of points or sides, and the inner radius.
startAngle, endAnglewedgeAngles of the slice.
pointerSidecalloutSide of the pointer: N, E, S or W.
assetsvgAn icon by name, for example "asset": { "type": "icon", "source": "iconify", "ref": "mdi:database" }.
iconStylesvg, iconoutline or solid.
cellValues, cellColorsalgo-array, algo-gridCell contents and colors.

Node kinds

GroupKinds
Shapesrect, circle, diamond, triangle, polygon, star, arrow, cylinder, callout, wedge, chevron, funnel
Text and datatext, progress
Picturessvg, icon, glyph, lottie
Layoutgroup
UMLuml-actor, uml-lifeline, uml-class, uml-note
Algorithms and chartsalgo-array, algo-pointer, algo-grid, algo-variable, algo-tree, algo-linked-list, algo-graph, algo-chart, algo-chart-line, algo-chart-scatter

For most videos, rect, circle, cylinder, text, progress and svg icons are enough.

Edge

An edge needs an id and two ends. Each end is either a node id (from, to) or a fixed point (fromXY, toXY).

FieldTypeWhat it does
idstringUnique within the scene.
from, tostringIds of the start and end nodes. Both must be in the scene's nodes, or the edge is removed when the project opens.
fromXY, toXYpointA fixed start or end point, x and y in pixels.
fromPort, toPortstringWhich side of the node to attach to: N, E, S or W.
strokecolorLine color.
widthnumberLine width in pixels. This is width, not strokeWidth.
opacitynumber0 to 1.
trimnumberHow much of the line is drawn, from the start: 0 is none, 1 is all. Default 1.
arrowstringnone, end, start or both.
endArrowType, startArrowTypestringtriangle, open-triangle, diamond-filled, diamond-hollow, circle or bar.
strokeStylestringsolid, dashed or dotted.
lineTypestringstraight, curved or elbowed.
label, labelColor, labelFontSizestring, color, numberText on the line.
labelPositionstringcenter, above or below.
animationStylestringMotion along the line: flow, ball, pulse or none.
flowSpeednumberSpeed of that motion.
waypointsarray of pointsPoints the line passes through.

Step

FieldTypeWhat it does
idstringOptional name for the step.
durationMsnumberLength of the step in milliseconds. 0 or missing counts as 900.
captionstringThe line shown on screen during this step.
notestringA private note, not shown in the video.
deltabooleantrue means the step lists only changes. Set it on every step after step 0.
nodesarrayStep 0: every node in full. Later steps: id plus only the fields that change.
edgesarraySame as nodes, for edges.
addNodes, addEdgesarrayFull new nodes or edges that first appear in this step.
removeNodeIds, removeEdgeIdsarray of stringsIds to remove in this step. They fade or draw out.
actionsarrayTimed effects inside the step. See Actions below.
easingeasingEasing for the automatic change from the previous step. Default ease.
transitionsobjectPer node id: morph, instant, fade, flip-x or flip-y. instant snaps the change.
delaysobjectPer node id: milliseconds to wait before its automatic change starts.
cameracameraWhere the view moves in this step: x, y (the world point at the center) and zoom (1 is 100 percent).
cameraDurationMs, cameraOffsetMs, cameraEasingnumber, number, easingTiming of the camera move.

Changes between steps animate on their own. Numbers and colors such as x, y, w, h, opacity, fontSize, value, fill, stroke, color and labelColor tween across the whole step. Nodes added with addNodes fade in, removed nodes fade out, and added edges draw in.

Actions

Each action has a type, a target (kind is node or edge, plus the id), and optional offsetMs (delay from the start of the step) and durationMs. An action controls only the properties it covers; everything else keeps its automatic change.

TypeFieldsWhat it does
visibilitymode, durationMs (default 200), slideDistance (default 150), easeShows or hides a node. Modes: fadeIn, fadeOut, show, hide, slideInLeft, slideInRight, slideInUp, slideInDown, slideOutLeft, slideOutRight, slideOutUp, slideOutDown.
transformTweenfrom, to, ease, iterationsTweens the node properties named in to, such as opacity, scaleX, scaleY, rotationZ, fill, value or typeProgress.
positionTweenfrom, to (points), easeMoves a node from one point to another.
edgeTweenfrom, to, easeTweens an edge's trim, trimStart, stroke, width or opacity. Use trim 0 to 1 to draw a line in.
pathFollowpathId, from, to (0 to 1), rotateToTangentMoves a node along a path from the scene's paths.

Slide modes name the side: slideInLeft enters from the left and slideOutLeft leaves to the left. slideInUp enters from above and slideInDown enters from below.

A transformTween with a to.x or to.y always starts from the node's current position; from.x and from.y are ignored. Other from values, such as scaleX, are used.

Narration clip

FieldTypeWhat it does
idstringUnique within the scene.
textstringWhat the voice says. Usually the same as the step's caption.
stepIndexintegerWhich step, counting from 0.
voiceobjectprovider "openai", plus voiceId and voiceName. Voice ids: alloy, ash, ballad, coral, echo, fable, nova, onyx, sage, shimmer, verse.
speednumberSpeaking rate. Default 1.

Clips import as text. Click Generate All Scenes in the Narration panel to record them.

Scene transition

FieldTypeWhat it does
kindstringcrossfade, slideDown, slideUp, slideLeft, slideRight, pushUp, pushDown, pushLeft, pushRight, travelOffUp, travelOffDown, travelOffLeft, travelOffRight, scribbleWipe, scribbleWipeLeft, scribbleWipeUp, scribbleWipeDown, colorWipe, colorWipeLeft, colorWipeUp, colorWipeDown or morph.
durationMsnumberLength of the transition.
colorcolorWipe color, for the colorWipe kinds only.

Caption style

FieldTypeWhat it does
variantstringband (a lower-third strip behind the text) or display (heavy outlined capitals on the picture, one word lit at a time).
fontScale, fontWeightnumberText size multiplier and weight.
textColor, bandColorcolorText color and strip color.
bandOpacitynumberStrip opacity, 0 to 1. Default 0.74.
highlightColor, outlineColorcolorFor display: the color of the word being spoken, and the letter outline.

Easing values

linear, ease, ease-in, ease-out, spring, bounce, elastic, back, back-in and step.

Full JSON Schema

The same schema as https://opencanviz.com/schema/v1.json. It describes the fields above; fields it does not list are passed through unchanged.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://opencanviz.com/schema/v1.json",
  "title": "openCanviz project JSON",
  "description": "The JSON that openCanviz accepts on Import (Create > Import > Paste JSON or Mermaid). Two shapes are accepted: a project with a diagrams array (one entry per scene), or a single scene with nodes, edges and steps at the top level. Add \"$schema\": \"https://opencanviz.com/schema/v1.json\" to a file to get editor autocomplete. Fields not listed here are passed through; leave out anything marked as set by openCanviz.",
  "type": "object",
  "anyOf": [
    {
      "$ref": "#/$defs/projectDocument"
    },
    {
      "$ref": "#/$defs/singleSceneDocument"
    }
  ],
  "$defs": {
    "projectDocument": {
      "title": "Project (recommended)",
      "type": "object",
      "required": [
        "diagrams"
      ],
      "properties": {
        "$schema": {
          "type": "string",
          "description": "Optional. \"https://opencanviz.com/schema/v1.json\" for editor autocomplete; openCanviz ignores it."
        },
        "title": {
          "type": "string",
          "description": "Project name shown on the Projects page. Defaults to \"Imported project\"."
        },
        "artboard": {
          "$ref": "#/$defs/artboard"
        },
        "diagrams": {
          "type": "array",
          "minItems": 1,
          "description": "Scenes, in playback order.",
          "items": {
            "$ref": "#/$defs/scene"
          }
        },
        "sceneNames": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "One name per scene, same order as diagrams. Missing names default to \"Scene N\"."
        },
        "transitions": {
          "type": "array",
          "description": "Index i is the transition between scene i and scene i+1. Length should be diagrams.length - 1. null means a hard cut.",
          "items": {
            "anyOf": [
              {
                "$ref": "#/$defs/sceneTransition"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "showCaptions": {
          "type": "boolean",
          "description": "Show step captions on screen. Default true."
        },
        "captionStyle": {
          "$ref": "#/$defs/captionStyle"
        },
        "sketchMode": {
          "type": "boolean",
          "description": "Draw every shape and edge in a hand-drawn style. Default false."
        },
        "floatEnabled": {
          "type": "boolean",
          "description": "Gentle idle bob on non-text nodes in every scene. Default false."
        },
        "floatOffScenes": {
          "type": "array",
          "items": {
            "type": "boolean"
          },
          "description": "Per scene: true keeps that scene still when floatEnabled is on."
        },
        "floatOnScenes": {
          "type": "array",
          "items": {
            "type": "boolean"
          },
          "description": "Per scene: true makes that scene float even when floatEnabled is off."
        },
        "flowEnabled": {
          "type": "boolean",
          "description": "Run edge flow animations. Default true."
        },
        "meta": {
          "type": "object",
          "properties": {
            "openAtLastStep": {
              "type": "boolean",
              "description": "Open the editor on the last step instead of the first."
            }
          }
        }
      }
    },
    "singleSceneDocument": {
      "title": "Single scene (flat)",
      "type": "object",
      "required": [
        "nodes"
      ],
      "properties": {
        "$schema": {
          "type": "string",
          "description": "Optional. \"https://opencanviz.com/schema/v1.json\" for editor autocomplete; openCanviz ignores it."
        },
        "title": {
          "type": "string"
        },
        "artboard": {
          "$ref": "#/$defs/artboard"
        },
        "nodes": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/node"
          }
        },
        "edges": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/edge"
          }
        },
        "steps": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/step"
          },
          "prefixItems": [
            {
              "$ref": "#/$defs/firstStep"
            }
          ]
        },
        "paths": {
          "$ref": "#/$defs/paths"
        },
        "narration": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/narrationClip"
          }
        },
        "camera": {
          "$ref": "#/$defs/camera"
        },
        "showCaptions": {
          "type": "boolean"
        },
        "captionStyle": {
          "$ref": "#/$defs/captionStyle"
        },
        "meta": {
          "type": "object",
          "properties": {
            "openAtLastStep": {
              "type": "boolean"
            }
          }
        }
      }
    },
    "color": {
      "type": "string",
      "description": "CSS color: \"#rrggbb\", \"#rgb\", \"rgba(r,g,b,a)\", \"transparent\" or \"none\". Hex is safest."
    },
    "easing": {
      "type": "string",
      "enum": [
        "linear",
        "ease",
        "ease-in",
        "ease-out",
        "spring",
        "bounce",
        "elastic",
        "back",
        "back-in",
        "step"
      ]
    },
    "artboard": {
      "type": "object",
      "description": "The video frame, in world coordinates. x and y are its top-left corner.",
      "required": [
        "x",
        "y",
        "width",
        "height",
        "background"
      ],
      "properties": {
        "x": {
          "type": "number"
        },
        "y": {
          "type": "number"
        },
        "width": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Frame width in px, for example 1920 for 16:9 or 1080 for 9:16 and 1:1."
        },
        "height": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "background": {
          "$ref": "#/$defs/color"
        }
      }
    },
    "camera": {
      "type": "object",
      "required": [
        "x",
        "y",
        "zoom"
      ],
      "properties": {
        "x": {
          "type": "number",
          "description": "World x at the center of the view."
        },
        "y": {
          "type": "number",
          "description": "World y at the center of the view."
        },
        "zoom": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "1 = 100%."
        }
      }
    },
    "scene": {
      "type": "object",
      "required": [
        "nodes",
        "edges",
        "steps"
      ],
      "properties": {
        "nodes": {
          "type": "array",
          "description": "Every node in the scene, as it looks in step 0. Usually identical to steps[0].nodes.",
          "items": {
            "$ref": "#/$defs/node"
          }
        },
        "edges": {
          "type": "array",
          "description": "Every edge in the scene, as it looks in step 0. Usually identical to steps[0].edges.",
          "items": {
            "$ref": "#/$defs/edge"
          }
        },
        "steps": {
          "type": "array",
          "minItems": 1,
          "description": "The timeline. Step 0 is a full snapshot; later steps list changes.",
          "items": {
            "$ref": "#/$defs/step"
          },
          "prefixItems": [
            {
              "$ref": "#/$defs/firstStep"
            }
          ]
        },
        "paths": {
          "$ref": "#/$defs/paths"
        },
        "narration": {
          "type": "array",
          "description": "Narration lines, at most one per step. Text only; record the audio in the Narration panel.",
          "items": {
            "$ref": "#/$defs/narrationClip"
          }
        },
        "background": {
          "$ref": "#/$defs/color",
          "description": "Flat background color for this scene only."
        }
      }
    },
    "node": {
      "type": "object",
      "required": [
        "id",
        "kind",
        "x",
        "y",
        "w",
        "h"
      ],
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1,
          "description": "Unique within the scene. Keep the same id across steps."
        },
        "kind": {
          "type": "string",
          "enum": [
            "rect",
            "circle",
            "diamond",
            "triangle",
            "polygon",
            "star",
            "arrow",
            "cylinder",
            "text",
            "callout",
            "wedge",
            "chevron",
            "funnel",
            "progress",
            "svg",
            "icon",
            "group",
            "glyph",
            "lottie",
            "uml-actor",
            "uml-lifeline",
            "uml-class",
            "uml-note",
            "algo-array",
            "algo-pointer",
            "algo-grid",
            "algo-variable",
            "algo-tree",
            "algo-linked-list",
            "algo-graph",
            "algo-chart",
            "algo-chart-line",
            "algo-chart-scatter"
          ]
        },
        "x": {
          "type": "number",
          "description": "Left edge, world px."
        },
        "y": {
          "type": "number",
          "description": "Top edge, world px."
        },
        "w": {
          "type": "number",
          "minimum": 0,
          "description": "Width px. Nodes under 10 x 10 px, or under 2 px in either direction, are not drawn."
        },
        "h": {
          "type": "number",
          "minimum": 0
        },
        "label": {
          "type": "string",
          "description": "Text drawn inside a shape."
        },
        "labelColor": {
          "$ref": "#/$defs/color"
        },
        "labelFontSize": {
          "type": "number"
        },
        "labelFontWeight": {
          "enum": [
            "normal",
            "bold"
          ]
        },
        "labelFontStyle": {
          "enum": [
            "normal",
            "italic"
          ]
        },
        "labelFontFamily": {
          "type": "string"
        },
        "labelPadding": {
          "type": "number"
        },
        "labelTextAlign": {
          "enum": [
            "left",
            "center",
            "right"
          ]
        },
        "fill": {
          "$ref": "#/$defs/color"
        },
        "stroke": {
          "$ref": "#/$defs/color"
        },
        "strokeWidth": {
          "type": "number",
          "minimum": 0
        },
        "strokeDashArray": {
          "type": "array",
          "items": {
            "type": "number"
          }
        },
        "cornerRadius": {
          "type": "number",
          "minimum": 0
        },
        "opacity": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "zIndex": {
          "type": "number",
          "description": "Higher draws in front."
        },
        "text": {
          "type": "string",
          "description": "Content of a text node. Use \\n for line breaks."
        },
        "fontFamily": {
          "type": "string"
        },
        "fontSize": {
          "type": "number"
        },
        "fontWeight": {
          "type": "number",
          "minimum": 100,
          "maximum": 900
        },
        "fontStyle": {
          "enum": [
            "normal",
            "italic"
          ]
        },
        "lineHeight": {
          "type": "number"
        },
        "letterSpacing": {
          "type": "number"
        },
        "color": {
          "$ref": "#/$defs/color",
          "description": "Text color of a text node."
        },
        "textAlign": {
          "enum": [
            "left",
            "center",
            "right"
          ]
        },
        "verticalAlign": {
          "enum": [
            "top",
            "middle",
            "bottom"
          ]
        },
        "wrap": {
          "enum": [
            "word",
            "char",
            "ellipsis",
            "clip"
          ]
        },
        "autoFit": {
          "enum": [
            "none",
            "shrink",
            "grow"
          ]
        },
        "typeProgress": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "description": "Typewriter reveal of a text node, 1 = all text."
        },
        "highlightedLines": {
          "type": "array",
          "items": {
            "type": "integer",
            "minimum": 0
          }
        },
        "syntaxStyle": {
          "enum": [
            "none",
            "pseudo",
            "python",
            "javascript"
          ]
        },
        "rotationZ": {
          "type": "number",
          "description": "2D rotation in degrees."
        },
        "rotationX": {
          "type": "number"
        },
        "rotationY": {
          "type": "number"
        },
        "scaleX": {
          "type": "number"
        },
        "scaleY": {
          "type": "number"
        },
        "pivotX": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "pivotY": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "parentId": {
          "type": "string",
          "description": "Id of a group node that contains this node."
        },
        "rough": {
          "type": "boolean"
        },
        "roughness": {
          "type": "number",
          "minimum": 0,
          "maximum": 3
        },
        "glow": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "pointerSide": {
          "enum": [
            "N",
            "E",
            "S",
            "W"
          ]
        },
        "points": {
          "type": "integer",
          "minimum": 3
        },
        "innerRadius": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "startAngle": {
          "type": "number"
        },
        "endAngle": {
          "type": "number"
        },
        "value": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "progressColor": {
          "$ref": "#/$defs/color"
        },
        "asset": {
          "type": "object",
          "description": "For kind svg: an icon by reference, for example {\"type\":\"icon\",\"source\":\"iconify\",\"ref\":\"mdi:database\"}.",
          "required": [
            "type",
            "source"
          ],
          "properties": {
            "type": {
              "enum": [
                "icon",
                "lottie",
                "image",
                "svg"
              ]
            },
            "source": {
              "enum": [
                "iconify",
                "lottie-library",
                "lottie-icons",
                "bundled",
                "url",
                "user",
                "embedded"
              ]
            },
            "ref": {
              "type": "string"
            },
            "name": {
              "type": "string"
            }
          }
        },
        "iconStyle": {
          "enum": [
            "outline",
            "solid"
          ]
        },
        "cellValues": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "cellColors": {
          "type": "array",
          "items": {
            "anyOf": [
              {
                "$ref": "#/$defs/color"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "_selected": {
          "description": "Set by openCanviz, leave out."
        },
        "_animatedProps": {
          "description": "Set by openCanviz, leave out."
        },
        "illuOf": {
          "description": "Set by openCanviz, leave out."
        },
        "imageFailed": {
          "description": "Set by openCanviz, leave out."
        }
      }
    },
    "edge": {
      "type": "object",
      "required": [
        "id"
      ],
      "anyOf": [
        {
          "required": [
            "from",
            "to"
          ]
        },
        {
          "required": [
            "fromXY",
            "toXY"
          ]
        },
        {
          "required": [
            "from",
            "toXY"
          ]
        },
        {
          "required": [
            "fromXY",
            "to"
          ]
        }
      ],
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1
        },
        "from": {
          "type": "string",
          "description": "Id of the start node. Must exist in the scene."
        },
        "to": {
          "type": "string",
          "description": "Id of the end node. Must exist in the scene."
        },
        "fromXY": {
          "$ref": "#/$defs/point"
        },
        "toXY": {
          "$ref": "#/$defs/point"
        },
        "fromPort": {
          "enum": [
            "N",
            "E",
            "S",
            "W"
          ]
        },
        "toPort": {
          "enum": [
            "N",
            "E",
            "S",
            "W"
          ]
        },
        "stroke": {
          "$ref": "#/$defs/color"
        },
        "width": {
          "type": "number",
          "minimum": 0,
          "description": "Line width in px. This is width, not strokeWidth."
        },
        "opacity": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "trim": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "description": "How much of the line is drawn, from the start. 0 = none, 1 = all."
        },
        "trimStart": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "arrow": {
          "enum": [
            "none",
            "end",
            "start",
            "both"
          ]
        },
        "endArrowType": {
          "enum": [
            "triangle",
            "open-triangle",
            "diamond-filled",
            "diamond-hollow",
            "circle",
            "bar"
          ]
        },
        "startArrowType": {
          "enum": [
            "triangle",
            "open-triangle",
            "diamond-filled",
            "diamond-hollow",
            "circle",
            "bar"
          ]
        },
        "arrowSize": {
          "type": "number"
        },
        "strokeStyle": {
          "enum": [
            "solid",
            "dashed",
            "dotted"
          ]
        },
        "lineType": {
          "enum": [
            "straight",
            "curved",
            "elbowed"
          ]
        },
        "router": {
          "enum": [
            "AUTO",
            "VHV",
            "HVH"
          ]
        },
        "cornerRadius": {
          "type": "number"
        },
        "label": {
          "type": "string"
        },
        "labelColor": {
          "$ref": "#/$defs/color"
        },
        "labelFontSize": {
          "type": "number"
        },
        "labelPosition": {
          "enum": [
            "center",
            "above",
            "below"
          ]
        },
        "labelPositionT": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "labelBgColor": {
          "$ref": "#/$defs/color"
        },
        "animationStyle": {
          "enum": [
            "flow",
            "ball",
            "pulse",
            "none"
          ]
        },
        "flowSpeed": {
          "type": "number"
        },
        "waypoints": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/point"
          }
        },
        "zIndex": {
          "type": "number"
        },
        "_animatedProps": {
          "description": "Set by openCanviz, leave out."
        }
      }
    },
    "point": {
      "type": "object",
      "required": [
        "x",
        "y"
      ],
      "properties": {
        "x": {
          "type": "number"
        },
        "y": {
          "type": "number"
        }
      }
    },
    "paths": {
      "type": "object",
      "description": "Motion paths by id, used by pathFollow actions.",
      "additionalProperties": {
        "type": "object",
        "required": [
          "id",
          "points"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "points": {
            "type": "array",
            "minItems": 2,
            "items": {
              "$ref": "#/$defs/point"
            }
          },
          "closed": {
            "type": "boolean"
          },
          "length": {
            "description": "Set by openCanviz, leave out."
          },
          "cum": {
            "description": "Set by openCanviz, leave out."
          }
        }
      }
    },
    "nodePatch": {
      "type": "object",
      "description": "In step 0: a full node. In a delta step: id plus only the fields that change.",
      "required": [
        "id"
      ],
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1
        }
      }
    },
    "edgePatch": {
      "type": "object",
      "description": "In step 0: a full edge. In a delta step: id plus only the fields that change.",
      "required": [
        "id"
      ],
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1
        }
      }
    },
    "step": {
      "type": "object",
      "required": [
        "durationMs",
        "nodes",
        "edges"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "note": {
          "type": "string",
          "description": "Private note, not shown in the video."
        },
        "caption": {
          "type": "string",
          "description": "The narration line for this step. Shown as an on-screen caption."
        },
        "durationMs": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Step length in ms. 0 or missing is treated as 900."
        },
        "delta": {
          "type": "boolean",
          "description": "true = this step lists only changes. Put it on every step after step 0."
        },
        "easing": {
          "$ref": "#/$defs/easing",
          "description": "Easing for the automatic tween from the previous step. Default ease."
        },
        "propertyEasings": {
          "type": "object",
          "additionalProperties": {
            "$ref": "#/$defs/easing"
          }
        },
        "nodes": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/nodePatch"
          }
        },
        "edges": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/edgePatch"
          }
        },
        "addNodes": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/node"
          },
          "description": "Delta steps: full new nodes that appear in this step."
        },
        "removeNodeIds": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "addEdges": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/edge"
          }
        },
        "removeEdgeIds": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "actions": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/action"
          }
        },
        "transitions": {
          "type": "object",
          "description": "Per node id: how the node changes into this step.",
          "additionalProperties": {
            "enum": [
              "morph",
              "instant",
              "fade",
              "flip-x",
              "flip-y"
            ]
          }
        },
        "delays": {
          "type": "object",
          "description": "Per node id: delay in ms before its automatic tween starts.",
          "additionalProperties": {
            "type": "number",
            "minimum": 0
          }
        },
        "camera": {
          "$ref": "#/$defs/camera"
        },
        "cameraDurationMs": {
          "type": "number",
          "minimum": 0
        },
        "cameraEasing": {
          "$ref": "#/$defs/easing"
        },
        "cameraOffsetMs": {
          "type": "number",
          "minimum": 0
        },
        "cameraEnd": {
          "$ref": "#/$defs/camera"
        },
        "_addedNodeIds": {
          "description": "Set by openCanviz, leave out."
        },
        "_removedNodeIds": {
          "description": "Set by openCanviz, leave out."
        },
        "_addedEdgeIds": {
          "description": "Set by openCanviz, leave out."
        },
        "_removedEdgeIds": {
          "description": "Set by openCanviz, leave out."
        }
      }
    },
    "nodeTarget": {
      "type": "object",
      "required": [
        "kind",
        "id"
      ],
      "properties": {
        "kind": {
          "const": "node"
        },
        "id": {
          "type": "string"
        }
      }
    },
    "edgeTarget": {
      "type": "object",
      "required": [
        "kind",
        "id"
      ],
      "properties": {
        "kind": {
          "const": "edge"
        },
        "id": {
          "type": "string"
        }
      }
    },
    "action": {
      "type": "object",
      "required": [
        "type"
      ],
      "properties": {
        "type": {
          "enum": [
            "visibility",
            "transformTween",
            "positionTween",
            "edgeTween",
            "pathFollow",
            "pathAnimate",
            "algoSwap",
            "containerTransform",
            "voiceOver"
          ]
        }
      },
      "oneOf": [
        {
          "$ref": "#/$defs/visibilityAction"
        },
        {
          "$ref": "#/$defs/transformTweenAction"
        },
        {
          "$ref": "#/$defs/positionTweenAction"
        },
        {
          "$ref": "#/$defs/edgeTweenAction"
        },
        {
          "$ref": "#/$defs/pathFollowAction"
        },
        {
          "$ref": "#/$defs/otherAction"
        }
      ]
    },
    "timing": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "description": "Optional. Generated when missing."
        },
        "offsetMs": {
          "type": "number",
          "minimum": 0,
          "description": "Delay from the start of the step."
        },
        "durationMs": {
          "type": "number",
          "minimum": 0
        },
        "disabled": {
          "type": "boolean"
        }
      }
    },
    "visibilityAction": {
      "allOf": [
        {
          "$ref": "#/$defs/timing"
        }
      ],
      "type": "object",
      "required": [
        "type",
        "target",
        "mode"
      ],
      "properties": {
        "type": {
          "const": "visibility"
        },
        "target": {
          "$ref": "#/$defs/nodeTarget"
        },
        "mode": {
          "enum": [
            "show",
            "hide",
            "fadeIn",
            "fadeOut",
            "slideInLeft",
            "slideInRight",
            "slideInUp",
            "slideInDown",
            "slideOutLeft",
            "slideOutRight",
            "slideOutUp",
            "slideOutDown"
          ]
        },
        "durationMs": {
          "type": "number",
          "minimum": 0,
          "description": "Default 200."
        },
        "slideDistance": {
          "type": "number",
          "description": "Slide modes: distance in px. Default 150."
        },
        "iterations": {
          "type": "integer",
          "minimum": 1
        },
        "ease": {
          "$ref": "#/$defs/easing"
        }
      }
    },
    "tweenProps": {
      "type": "object",
      "properties": {
        "x": {
          "type": "number"
        },
        "y": {
          "type": "number"
        },
        "w": {
          "type": "number"
        },
        "h": {
          "type": "number"
        },
        "scaleX": {
          "type": "number"
        },
        "scaleY": {
          "type": "number"
        },
        "rotationX": {
          "type": "number"
        },
        "rotationY": {
          "type": "number"
        },
        "rotationZ": {
          "type": "number"
        },
        "opacity": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "fill": {
          "$ref": "#/$defs/color"
        },
        "stroke": {
          "$ref": "#/$defs/color"
        },
        "strokeWidth": {
          "type": "number"
        },
        "strokeTrimStart": {
          "type": "number"
        },
        "strokeTrimEnd": {
          "type": "number"
        },
        "typeProgress": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "drawProgress": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "blur": {
          "type": "number",
          "minimum": 0
        },
        "value": {
          "type": "number"
        }
      }
    },
    "transformTweenAction": {
      "allOf": [
        {
          "$ref": "#/$defs/timing"
        }
      ],
      "type": "object",
      "required": [
        "type",
        "target",
        "to"
      ],
      "properties": {
        "type": {
          "const": "transformTween"
        },
        "target": {
          "$ref": "#/$defs/nodeTarget"
        },
        "from": {
          "$ref": "#/$defs/tweenProps",
          "description": "Start values. from.x and from.y are ignored; position always starts where the node is."
        },
        "to": {
          "$ref": "#/$defs/tweenProps",
          "description": "End values. Only the properties named here are controlled by this action; set the same end values in the step's snapshot."
        },
        "ease": {
          "$ref": "#/$defs/easing"
        },
        "iterations": {
          "type": "integer",
          "minimum": 1,
          "maximum": 10
        },
        "isRelative": {
          "type": "boolean",
          "description": "Advanced, not recommended. Use an absolute to.x and to.y instead."
        },
        "dx": {
          "type": "number",
          "description": "Used only with isRelative."
        },
        "dy": {
          "type": "number",
          "description": "Used only with isRelative."
        }
      }
    },
    "positionTweenAction": {
      "allOf": [
        {
          "$ref": "#/$defs/timing"
        }
      ],
      "type": "object",
      "required": [
        "type",
        "target",
        "from",
        "to",
        "durationMs"
      ],
      "properties": {
        "type": {
          "const": "positionTween"
        },
        "target": {
          "$ref": "#/$defs/nodeTarget"
        },
        "from": {
          "$ref": "#/$defs/point"
        },
        "to": {
          "$ref": "#/$defs/point"
        },
        "ease": {
          "$ref": "#/$defs/easing"
        },
        "isRelative": {
          "type": "boolean"
        },
        "dx": {
          "type": "number"
        },
        "dy": {
          "type": "number"
        }
      },
      "description": "Moves a node from one point to another. transformTween with to.x and to.y is simpler and starts from wherever the node is."
    },
    "edgeTweenAction": {
      "allOf": [
        {
          "$ref": "#/$defs/timing"
        }
      ],
      "type": "object",
      "required": [
        "type",
        "target",
        "to"
      ],
      "properties": {
        "type": {
          "const": "edgeTween"
        },
        "target": {
          "$ref": "#/$defs/edgeTarget"
        },
        "from": {
          "$ref": "#/$defs/edgeTweenProps"
        },
        "to": {
          "$ref": "#/$defs/edgeTweenProps"
        },
        "ease": {
          "$ref": "#/$defs/easing"
        }
      }
    },
    "edgeTweenProps": {
      "type": "object",
      "properties": {
        "trim": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "trimStart": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "stroke": {
          "$ref": "#/$defs/color"
        },
        "width": {
          "type": "number"
        },
        "opacity": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        }
      }
    },
    "pathFollowAction": {
      "allOf": [
        {
          "$ref": "#/$defs/timing"
        }
      ],
      "type": "object",
      "required": [
        "type",
        "target",
        "pathId",
        "from",
        "to"
      ],
      "properties": {
        "type": {
          "const": "pathFollow"
        },
        "target": {
          "$ref": "#/$defs/nodeTarget"
        },
        "pathId": {
          "type": "string",
          "description": "Key of an entry in the scene's paths."
        },
        "from": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "to": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "ease": {
          "$ref": "#/$defs/easing"
        },
        "rotateToTangent": {
          "type": "boolean"
        }
      }
    },
    "otherAction": {
      "type": "object",
      "description": "pathAnimate, algoSwap, containerTransform and voiceOver are accepted but are advanced; prefer the actions above.",
      "required": [
        "type"
      ],
      "properties": {
        "type": {
          "enum": [
            "pathAnimate",
            "algoSwap",
            "containerTransform",
            "voiceOver"
          ]
        }
      }
    },
    "narrationClip": {
      "type": "object",
      "required": [
        "id",
        "text",
        "stepIndex",
        "voice"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "text": {
          "type": "string"
        },
        "stepIndex": {
          "type": "integer",
          "minimum": 0,
          "description": "Index of the step this line belongs to."
        },
        "voice": {
          "type": "object",
          "required": [
            "provider",
            "voiceId",
            "voiceName"
          ],
          "properties": {
            "provider": {
              "type": "string",
              "description": "Use \"openai\"."
            },
            "voiceId": {
              "type": "string",
              "description": "For example alloy, ash, ballad, coral, echo, fable, nova, onyx, sage, shimmer or verse."
            },
            "voiceName": {
              "type": "string"
            }
          }
        },
        "speed": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Playback rate. Default 1."
        },
        "audioData": {
          "description": "Set by openCanviz, leave out."
        },
        "audioUrl": {
          "description": "Set by openCanviz, leave out."
        },
        "audioDurationMs": {
          "description": "Set by openCanviz, leave out."
        },
        "words": {
          "description": "Set by openCanviz, leave out."
        }
      }
    },
    "sceneTransition": {
      "type": "object",
      "required": [
        "kind",
        "durationMs"
      ],
      "properties": {
        "kind": {
          "enum": [
            "crossfade",
            "slideDown",
            "slideUp",
            "slideLeft",
            "slideRight",
            "pushUp",
            "pushDown",
            "pushLeft",
            "pushRight",
            "travelOffUp",
            "travelOffDown",
            "travelOffLeft",
            "travelOffRight",
            "scribbleWipe",
            "scribbleWipeLeft",
            "scribbleWipeUp",
            "scribbleWipeDown",
            "colorWipe",
            "colorWipeLeft",
            "colorWipeUp",
            "colorWipeDown",
            "morph"
          ]
        },
        "durationMs": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "color": {
          "$ref": "#/$defs/color",
          "description": "colorWipe kinds only."
        }
      }
    },
    "captionStyle": {
      "type": "object",
      "properties": {
        "variant": {
          "enum": [
            "band",
            "display"
          ]
        },
        "fontScale": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "fontWeight": {
          "type": "number"
        },
        "textColor": {
          "$ref": "#/$defs/color"
        },
        "bandColor": {
          "$ref": "#/$defs/color"
        },
        "bandOpacity": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "highlightColor": {
          "$ref": "#/$defs/color"
        },
        "outlineColor": {
          "$ref": "#/$defs/color"
        }
      }
    },
    "firstStep": {
      "description": "Step 0: a full snapshot. Every node and edge in full.",
      "allOf": [
        {
          "$ref": "#/$defs/step"
        }
      ],
      "properties": {
        "nodes": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/node"
          }
        },
        "edges": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/edge"
          }
        }
      }
    }
  }
}

All Rights Reserved.