├── .gitignore ├── .gitattributes ├── spec ├── v0.3 │ ├── hello-world-example.png │ ├── extensions │ │ ├── ports-node.json │ │ ├── relative-node.json │ │ ├── parent-child-rel.json │ │ └── hyperedge-rel.json │ ├── core │ │ ├── set-rel.json │ │ ├── group-rel.json │ │ ├── oval-node.json │ │ ├── rect-node.json │ │ ├── path-node.json │ │ ├── edge-rel.json │ │ └── arrow-node.json │ ├── schema.json │ └── extensions.md ├── v0.4 │ ├── hello-world-example.png │ ├── extensions │ │ ├── ports-node.json │ │ ├── relative-node.json │ │ ├── parent-child-rel.json │ │ └── hyperedge-rel.json │ ├── core │ │ ├── set-rel.json │ │ ├── group-rel.json │ │ ├── oval-node.json │ │ ├── rect-node.json │ │ ├── path-node.json │ │ ├── edge-rel.json │ │ └── arrow-node.json │ ├── schema.json │ └── extensions.md ├── v0.5 │ ├── hello-world-example.png │ ├── extensions │ │ ├── ports-node.json │ │ ├── transforms-node.json │ │ ├── textstyle-node.json │ │ ├── hyperedge-rel.json │ │ ├── parent-child-rel.json │ │ └── anchored-node.json │ ├── core │ │ ├── group-rel.json │ │ ├── oval-node.json │ │ ├── rect-node.json │ │ ├── path-node.json │ │ ├── edge-rel.json │ │ └── arrow-node.json │ ├── how-to-spec.md │ ├── schema.json │ └── extensions.md ├── v0.6 │ ├── hello-world-example.png │ ├── extensions │ │ ├── ports-node.json │ │ ├── page-node.json │ │ ├── oval-node.json │ │ ├── rect-node.json │ │ ├── path-node.json │ │ ├── group-rel.json │ │ ├── viewport-canvas.json │ │ ├── edge-rel.json │ │ ├── transforms-node.json │ │ ├── textstyle-node.json │ │ ├── hyperedge-rel.json │ │ ├── arrow-node.json │ │ ├── parent-child-rel.json │ │ └── anchored-node.json │ ├── how-to-spec.md │ └── schema.json ├── v0.6.1-draft │ ├── hello-world-example.png │ ├── extensions │ │ ├── ports-node.json │ │ ├── page-node.json │ │ ├── oval-node.json │ │ ├── rect-node.json │ │ ├── path-node.json │ │ ├── group-rel.json │ │ ├── viewport-canvas.json │ │ ├── edge-rel.json │ │ ├── transforms-node.json │ │ ├── textstyle-node.json │ │ ├── hyperedge-rel.json │ │ ├── arrow-node.json │ │ ├── parent-child-rel.json │ │ └── anchored-node.json │ ├── how-to-spec.md │ └── schema.json └── v0.1 │ ├── schema │ ├── schemas.schema.json │ ├── open-canvas.schema.json │ ├── node.schema.json │ ├── relation.schema.json │ └── schema.combined.schema.json │ └── spec.md ├── .editorconfig ├── .pre-commit-config.yaml ├── example ├── single-node.json ├── cookbook-sticky-note.ocif.json ├── circle-node.json └── 4x4-rect-node-grid.ocif.json ├── .github └── workflows │ ├── static_checks.yml │ └── file_format.py ├── public └── _redirects ├── making-of.md ├── design ├── requirements.md ├── adr-000-TEMPLATE.md ├── goals.md ├── resource-structure.svg └── diagram-files.svg ├── README.md ├── catalog.md └── cookbook.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /spec/v0.3/hello-world-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ocwg/ocif-spec/HEAD/spec/v0.3/hello-world-example.png -------------------------------------------------------------------------------- /spec/v0.4/hello-world-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ocwg/ocif-spec/HEAD/spec/v0.4/hello-world-example.png -------------------------------------------------------------------------------- /spec/v0.5/hello-world-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ocwg/ocif-spec/HEAD/spec/v0.5/hello-world-example.png -------------------------------------------------------------------------------- /spec/v0.6/hello-world-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ocwg/ocif-spec/HEAD/spec/v0.6/hello-world-example.png -------------------------------------------------------------------------------- /spec/v0.6.1-draft/hello-world-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ocwg/ocif-spec/HEAD/spec/v0.6.1-draft/hello-world-example.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | max_line_length = 120 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_language_version: 2 | python: python3 3 | 4 | repos: 5 | - repo: local 6 | hooks: 7 | - id: file-format 8 | name: file-format 9 | language: python 10 | entry: python .github/workflows/file_format.py 11 | types_or: [text] 12 | -------------------------------------------------------------------------------- /example/single-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "ocif": "https://canvasprotocol.org/ocif/v0.5", 3 | "nodes": [ 4 | { 5 | "id": "shape:054132af-4d2f-48ed-b239-f5129dd35288", 6 | "position": [122.34441018202554, -276.51955082999257] 7 | } 8 | ], 9 | "relations": [], 10 | "schemas": [] 11 | } 12 | -------------------------------------------------------------------------------- /spec/v0.3/extensions/ports-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocwg/node/ports", 4 | "description": "Node ports extension", 5 | "type": "object", 6 | "properties": { 7 | "ports": { 8 | "type": "array", 9 | "items": { 10 | "$comment": "Each item is an ID of a node", 11 | "type": "string" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /spec/v0.4/extensions/ports-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/ports", 4 | "description": "Node ports extension", 5 | "type": "object", 6 | "properties": { 7 | "ports": { 8 | "type": "array", 9 | "items": { 10 | "$comment": "Each item is an ID of a node", 11 | "type": "string" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /spec/v0.4/core/set-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/rel/set", 4 | "description": "Set relation extension", 5 | "type": "object", 6 | "properties": { 7 | "members": { 8 | "type": "array", 9 | "description": "IDs of members of the set", 10 | "items": { 11 | "description": "node ID or relation ID", 12 | "type": "string" 13 | } 14 | } 15 | }, 16 | "required": ["members"] 17 | } 18 | -------------------------------------------------------------------------------- /spec/v0.4/core/group-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/rel/group", 4 | "description": "Group relation extension", 5 | "type": "object", 6 | "properties": { 7 | "members": { 8 | "type": "array", 9 | "description": "IDs of members of the group", 10 | "items": { 11 | "description": "node ID or relation ID", 12 | "type": "string" 13 | } 14 | } 15 | }, 16 | "required": ["members"] 17 | } 18 | -------------------------------------------------------------------------------- /spec/v0.3/core/set-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocwg/rel/set", 4 | "description": "Set relation extension", 5 | "type": "object", 6 | "properties": { 7 | "members": { 8 | "type": "array", 9 | "description": "IDs of members of the set", 10 | "items": { 11 | "description": "node ID or relation ID", 12 | "type": "string" 13 | } 14 | } 15 | }, 16 | "required": [ 17 | "members" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /spec/v0.3/core/group-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocwg/rel/group", 4 | "description": "Group relation extension", 5 | "type": "object", 6 | "properties": { 7 | "members": { 8 | "type": "array", 9 | "description": "IDs of members of the group", 10 | "items": { 11 | "description": "node ID or relation ID", 12 | "type": "string" 13 | } 14 | } 15 | }, 16 | "required": [ 17 | "members" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /example/cookbook-sticky-note.ocif.json: -------------------------------------------------------------------------------- 1 | { 2 | "ocif": "https://canvasprotocol.org/ocif/v0.6", 3 | "nodes": [ 4 | { 5 | "id": "sticky-note-milk", "position": [ 100, 100 ], "size": [ 150, 75 ], "resource": "res_milk", "data": [ 6 | { 7 | "type": "@ocif/node/rect", "fillColor": "#FFFF00" 8 | } 9 | ] 10 | } 11 | ], "resources": [ 12 | { 13 | "id": "res_milk", "representations": [ 14 | { 15 | "mimeType": "text/plain", "content": "Buy milk" 16 | } 17 | ] 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /spec/v0.1/schema/schemas.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "schemas.schema.json", 4 | "title": "schema", 5 | "type": "object", 6 | "description": "inline definition of extended node schemas", 7 | "required": [], 8 | "propertyNames": { 9 | "pattern": "^@" 10 | }, 11 | "patternProperties": { 12 | "^@": { 13 | "type": "object", 14 | "properties": { 15 | "$schema": { "type": "string" } 16 | }, 17 | "required": ["$schema"] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spec/v0.5/extensions/ports-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/ports", 4 | "description": "Node ports extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/node/ports" 10 | }, 11 | "ports": { 12 | "type": "array", 13 | "items": { 14 | "$comment": "Each item is an ID of a node", 15 | "type": "string" 16 | } 17 | } 18 | }, 19 | "required": ["type", "ports"] 20 | } 21 | -------------------------------------------------------------------------------- /spec/v0.6/extensions/ports-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/ports", 4 | "description": "Node ports extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/node/ports" 10 | }, 11 | "ports": { 12 | "type": "array", 13 | "items": { 14 | "$comment": "Each item is an ID of a node", 15 | "type": "string" 16 | } 17 | } 18 | }, 19 | "required": ["type", "ports"] 20 | } 21 | -------------------------------------------------------------------------------- /spec/v0.6.1-draft/extensions/ports-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/ports", 4 | "description": "Node ports extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/node/ports" 10 | }, 11 | "ports": { 12 | "type": "array", 13 | "items": { 14 | "$comment": "Each item is an ID of a node", 15 | "type": "string" 16 | } 17 | } 18 | }, 19 | "required": ["type", "ports"] 20 | } 21 | -------------------------------------------------------------------------------- /spec/v0.3/core/oval-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocwg/node/oval", 4 | "description": "Oval node extension", 5 | "type": "object", 6 | "properties": { 7 | "strokeWidth": { 8 | "type": "number", 9 | "description": "The line width." 10 | }, 11 | "strokeColor": { 12 | "type": "string", 13 | "description": "The color of the stroke." 14 | }, 15 | "fillColor": { 16 | "type": "string", 17 | "description": "The color of the fill." 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spec/v0.4/core/oval-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/oval", 4 | "description": "Oval node extension", 5 | "type": "object", 6 | "properties": { 7 | "strokeWidth": { 8 | "type": "number", 9 | "description": "The line width." 10 | }, 11 | "strokeColor": { 12 | "type": "string", 13 | "description": "The color of the stroke." 14 | }, 15 | "fillColor": { 16 | "type": "string", 17 | "description": "The color of the fill." 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spec/v0.3/core/rect-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocwg/node/rect", 4 | "description": "Rectangle node extension", 5 | "type": "object", 6 | "properties": { 7 | "strokeWidth": { 8 | "type": "number", 9 | "description": "The line width." 10 | }, 11 | "strokeColor": { 12 | "type": "string", 13 | "description": "The color of the stroke." 14 | }, 15 | "fillColor": { 16 | "type": "string", 17 | "description": "The color of the fill." 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spec/v0.4/core/rect-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/rect", 4 | "description": "Rectangle node extension", 5 | "type": "object", 6 | "properties": { 7 | "strokeWidth": { 8 | "type": "number", 9 | "description": "The line width." 10 | }, 11 | "strokeColor": { 12 | "type": "string", 13 | "description": "The color of the stroke." 14 | }, 15 | "fillColor": { 16 | "type": "string", 17 | "description": "The color of the fill." 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/static_checks.yml: -------------------------------------------------------------------------------- 1 | name: 📊 Static Checks 2 | on: [push, pull_request] 3 | 4 | concurrency: 5 | group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-static 6 | 7 | jobs: 8 | static-checks: 9 | name: Code style and file formatting 10 | runs-on: ubuntu-24.04 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 2 16 | 17 | - name: Style checks via pre-commit 18 | uses: pre-commit/action@v3.0.1 19 | with: 20 | extra_args: --all-files 21 | -------------------------------------------------------------------------------- /spec/v0.6/extensions/page-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/page", 4 | "description": "Page node extension. Gives nodes page numbers and/or a page label.", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/node/page" 10 | }, 11 | "pageNumber": { 12 | "type": "number", 13 | "description": "The page number." 14 | }, 15 | "label": { 16 | "type": "string", 17 | "description": "A page label." 18 | } 19 | }, 20 | "required": ["type"] 21 | } 22 | -------------------------------------------------------------------------------- /example/circle-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "ocif": "https://canvasprotocol.org/ocif/v0.5", 3 | "nodes": [ 4 | { 5 | "id": "n1", 6 | "position": [100, 100], 7 | "size": [50, 50], 8 | "resource": "r1", 9 | "data": [ 10 | { 11 | "type": "@ocif/node/oval" 12 | } 13 | ] 14 | } 15 | ], 16 | "resources": [ 17 | { 18 | "id": "r1", 19 | "representations": [ 20 | { 21 | "mime-type": "text/plain", 22 | "content": "Hello, World!" 23 | } 24 | ] 25 | } 26 | ], 27 | "relations": [], 28 | "schemas": [] 29 | } 30 | -------------------------------------------------------------------------------- /spec/v0.6.1-draft/extensions/page-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/page", 4 | "description": "Page node extension. Gives nodes page numbers and/or a page label.", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/node/page" 10 | }, 11 | "pageNumber": { 12 | "type": "number", 13 | "description": "The page number." 14 | }, 15 | "label": { 16 | "type": "string", 17 | "description": "A page label." 18 | } 19 | }, 20 | "required": ["type"] 21 | } 22 | -------------------------------------------------------------------------------- /spec/v0.5/core/group-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/rel/group", 4 | "description": "Group relation extension", 5 | "type": "object", 6 | "properties": { 7 | "members": { 8 | "type": "array", 9 | "description": "IDs of members of the group", 10 | "items": { 11 | "description": "node ID or relation ID", 12 | "type": "string" 13 | } 14 | }, 15 | "cascadeDelete": { 16 | "type": "boolean", 17 | "description": "If true, deleting the group will delete all members. Default is true.", 18 | "default": true 19 | } 20 | }, 21 | "required": ["members"] 22 | } 23 | -------------------------------------------------------------------------------- /spec/v0.5/core/oval-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/oval", 4 | "description": "Oval node extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/node/oval" 10 | }, 11 | "strokeWidth": { 12 | "type": "number", 13 | "description": "The line width." 14 | }, 15 | "strokeColor": { 16 | "type": "string", 17 | "description": "The color of the stroke." 18 | }, 19 | "fillColor": { 20 | "type": "string", 21 | "description": "The color of the fill." 22 | } 23 | }, 24 | "required": ["type"] 25 | } 26 | -------------------------------------------------------------------------------- /spec/v0.5/core/rect-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/rect", 4 | "description": "Rectangle node extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/node/rect" 10 | }, 11 | "strokeWidth": { 12 | "type": "number", 13 | "description": "The line width." 14 | }, 15 | "strokeColor": { 16 | "type": "string", 17 | "description": "The color of the stroke." 18 | }, 19 | "fillColor": { 20 | "type": "string", 21 | "description": "The color of the fill." 22 | } 23 | }, 24 | "required": ["type"] 25 | } 26 | -------------------------------------------------------------------------------- /spec/v0.6/extensions/oval-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/oval", 4 | "description": "Oval node extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/node/oval" 10 | }, 11 | "strokeWidth": { 12 | "type": "number", 13 | "description": "The line width." 14 | }, 15 | "strokeColor": { 16 | "type": "string", 17 | "description": "The color of the stroke." 18 | }, 19 | "fillColor": { 20 | "type": "string", 21 | "description": "The color of the fill." 22 | } 23 | }, 24 | "required": ["type"] 25 | } 26 | -------------------------------------------------------------------------------- /spec/v0.6/extensions/rect-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/rect", 4 | "description": "Rectangle node extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/node/rect" 10 | }, 11 | "strokeWidth": { 12 | "type": "number", 13 | "description": "The line width." 14 | }, 15 | "strokeColor": { 16 | "type": "string", 17 | "description": "The color of the stroke." 18 | }, 19 | "fillColor": { 20 | "type": "string", 21 | "description": "The color of the fill." 22 | } 23 | }, 24 | "required": ["type"] 25 | } 26 | -------------------------------------------------------------------------------- /spec/v0.6.1-draft/extensions/oval-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/oval", 4 | "description": "Oval node extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/node/oval" 10 | }, 11 | "strokeWidth": { 12 | "type": "number", 13 | "description": "The line width." 14 | }, 15 | "strokeColor": { 16 | "type": "string", 17 | "description": "The color of the stroke." 18 | }, 19 | "fillColor": { 20 | "type": "string", 21 | "description": "The color of the fill." 22 | } 23 | }, 24 | "required": ["type"] 25 | } 26 | -------------------------------------------------------------------------------- /spec/v0.6.1-draft/extensions/rect-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/rect", 4 | "description": "Rectangle node extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/node/rect" 10 | }, 11 | "strokeWidth": { 12 | "type": "number", 13 | "description": "The line width." 14 | }, 15 | "strokeColor": { 16 | "type": "string", 17 | "description": "The color of the stroke." 18 | }, 19 | "fillColor": { 20 | "type": "string", 21 | "description": "The color of the fill." 22 | } 23 | }, 24 | "required": ["type"] 25 | } 26 | -------------------------------------------------------------------------------- /spec/v0.4/core/path-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/path", 4 | "description": "Path node extension", 5 | "type": "object", 6 | "properties": { 7 | "strokeWidth": { 8 | "type": "number", 9 | "description": "The line width." 10 | }, 11 | "strokeColor": { 12 | "type": "string", 13 | "description": "The color of the stroke." 14 | }, 15 | "fillColor": { 16 | "type": "string", 17 | "description": "The color of the fill." 18 | }, 19 | "path": { 20 | "type": "string", 21 | "description": "The path data in SVG 'd' attribute syntax." 22 | } 23 | }, 24 | "required": ["path"] 25 | } 26 | -------------------------------------------------------------------------------- /spec/v0.5/core/path-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/path", 4 | "description": "Path node extension", 5 | "type": "object", 6 | "properties": { 7 | "strokeWidth": { 8 | "type": "number", 9 | "description": "The line width." 10 | }, 11 | "strokeColor": { 12 | "type": "string", 13 | "description": "The color of the stroke." 14 | }, 15 | "fillColor": { 16 | "type": "string", 17 | "description": "The color of the fill." 18 | }, 19 | "path": { 20 | "type": "string", 21 | "description": "The path data in SVG 'd' attribute syntax." 22 | } 23 | }, 24 | "required": ["path"] 25 | } 26 | -------------------------------------------------------------------------------- /spec/v0.3/core/path-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocwg/node/path", 4 | "description": "Path node extension", 5 | "type": "object", 6 | "properties": { 7 | "strokeWidth": { 8 | "type": "number", 9 | "description": "The line width." 10 | }, 11 | "strokeColor": { 12 | "type": "string", 13 | "description": "The color of the stroke." 14 | }, 15 | "fillColor": { 16 | "type": "string", 17 | "description": "The color of the fill." 18 | }, 19 | "path": { 20 | "type": "string", 21 | "description": "The path data in SVG 'd' attribute syntax." 22 | } 23 | }, 24 | "required": [ 25 | "path" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | # Redirect root to latest version of spec 2 | / https://github.com/ocwg/spec/blob/main/spec/v0.6/spec.md 302 3 | 4 | # Version-specific spec and schema files 5 | /:version https://github.com/ocwg/spec/blob/main/spec/:version/spec.md 301 6 | /:version/spec.md https://github.com/ocwg/spec/blob/main/spec/:version/spec.md 301 7 | /:version/schema.json https://github.com/ocwg/spec/blob/main/spec/:version/schema.json 301 8 | /:version/extensions.md https://github.com/ocwg/spec/blob/main/spec/:version/extensions.md 301 9 | 10 | # Core extension URIs 11 | /:version/core/:extension.json https://github.com/ocwg/spec/blob/main/spec/:version/core/:extension.json 301 12 | 13 | # Other extension URIs 14 | /:version/extensions/:extension.json https://github.com/ocwg/spec/blob/main/spec/:version/extensions/:extension.json 301 15 | -------------------------------------------------------------------------------- /spec/v0.6/extensions/path-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/path", 4 | "description": "Path node extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/node/path" 10 | }, 11 | "strokeWidth": { 12 | "type": "number", 13 | "description": "The line width." 14 | }, 15 | "strokeColor": { 16 | "type": "string", 17 | "description": "The color of the stroke." 18 | }, 19 | "fillColor": { 20 | "type": "string", 21 | "description": "The color of the fill." 22 | }, 23 | "path": { 24 | "type": "string", 25 | "description": "The path data in SVG 'd' attribute syntax." 26 | } 27 | }, 28 | "required": ["path"] 29 | } 30 | -------------------------------------------------------------------------------- /spec/v0.6.1-draft/extensions/path-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/path", 4 | "description": "Path node extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/node/path" 10 | }, 11 | "strokeWidth": { 12 | "type": "number", 13 | "description": "The line width." 14 | }, 15 | "strokeColor": { 16 | "type": "string", 17 | "description": "The color of the stroke." 18 | }, 19 | "fillColor": { 20 | "type": "string", 21 | "description": "The color of the fill." 22 | }, 23 | "path": { 24 | "type": "string", 25 | "description": "The path data in SVG 'd' attribute syntax." 26 | } 27 | }, 28 | "required": ["path"] 29 | } 30 | -------------------------------------------------------------------------------- /spec/v0.6/extensions/group-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/rel/group", 4 | "description": "Group relation extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/rel/group", 10 | "description": "Identifies the group relation extension." 11 | }, 12 | "members": { 13 | "type": "array", 14 | "description": "IDs of members of the group", 15 | "items": { 16 | "description": "node ID or relation ID", 17 | "type": "string" 18 | } 19 | }, 20 | "cascadeDelete": { 21 | "type": "boolean", 22 | "description": "If true, deleting the group will delete all members. Default is true.", 23 | "default": true 24 | } 25 | }, 26 | "required": ["type", "members"] 27 | } 28 | -------------------------------------------------------------------------------- /spec/v0.6.1-draft/extensions/group-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/rel/group", 4 | "description": "Group relation extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/rel/group", 10 | "description": "Identifies the group relation extension." 11 | }, 12 | "members": { 13 | "type": "array", 14 | "description": "IDs of members of the group", 15 | "items": { 16 | "description": "node ID or relation ID", 17 | "type": "string" 18 | } 19 | }, 20 | "cascadeDelete": { 21 | "type": "boolean", 22 | "description": "If true, deleting the group will delete all members. Default is true.", 23 | "default": true 24 | } 25 | }, 26 | "required": ["type", "members"] 27 | } 28 | -------------------------------------------------------------------------------- /spec/v0.4/extensions/relative-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/relative", 4 | "description": "Relative constraints for nodes", 5 | "type": "object", 6 | "properties": { 7 | "source": { 8 | "type": "string", 9 | "description": "ID of the source node. The ID of the source node, which is used as a reference for the relative positioning." 10 | }, 11 | "position": { 12 | "type": "array", 13 | "items": { 14 | "type": "number" 15 | }, 16 | "description": "Relative position of the target. The numbers given in the array are vector-added to the position of the source node." 17 | }, 18 | "rotation": { 19 | "type": "number", 20 | "description": "Relative angle of the target. The rotation is added to the rotation of the source node." 21 | } 22 | }, 23 | "required": ["source"] 24 | } 25 | -------------------------------------------------------------------------------- /spec/v0.6/extensions/viewport-canvas.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/canvas/viewport", 4 | "description": "A viewport is a rectangle that defines at what part of a canvas the app should initially pan and zoom.", 5 | "type": "object", 6 | "properties": { 7 | "position": { 8 | "description": "The top-left corner of the viewport.", 9 | "type": "array", 10 | "items": { 11 | "type": "number" 12 | }, 13 | "minItems": 2, 14 | "maxItems": 3, 15 | "default": [0, 0] 16 | }, 17 | "size": { 18 | "description": "The width and height (in 3D: also depth) of the viewport.", 19 | "type": "array", 20 | "items": { 21 | "type": "number" 22 | }, 23 | "minItems": 2, 24 | "maxItems": 3, 25 | "default": [100, 100] 26 | } 27 | }, 28 | "required": ["position", "size"] 29 | } 30 | -------------------------------------------------------------------------------- /spec/v0.3/extensions/relative-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocwg/node/relative", 4 | "description": "Relative constraints for nodes", 5 | "type": "object", 6 | "properties": { 7 | "source": { 8 | "type": "string", 9 | "description": "ID of the source node. The ID of the source node, which is used as a reference for the relative positioning." 10 | }, 11 | "position": { 12 | "type": "array", 13 | "items": { 14 | "type": "number" 15 | }, 16 | "description": "Relative position of the target. The numbers given in the array are vector-added to the position of the source node." 17 | }, 18 | "rotation": { 19 | "type": "number", 20 | "description": "Relative angle of the target. The rotation is added to the rotation of the source node." 21 | } 22 | }, 23 | "required": [ 24 | "source" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /spec/v0.6.1-draft/extensions/viewport-canvas.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/canvas/viewport", 4 | "description": "A viewport is a rectangle that defines at what part of a canvas the app should initially pan and zoom.", 5 | "type": "object", 6 | "properties": { 7 | "position": { 8 | "description": "The top-left corner of the viewport.", 9 | "type": "array", 10 | "items": { 11 | "type": "number" 12 | }, 13 | "minItems": 2, 14 | "maxItems": 3, 15 | "default": [0, 0] 16 | }, 17 | "size": { 18 | "description": "The width and height (in 3D: also depth) of the viewport.", 19 | "type": "array", 20 | "items": { 21 | "type": "number" 22 | }, 23 | "minItems": 2, 24 | "maxItems": 3, 25 | "default": [100, 100] 26 | } 27 | }, 28 | "required": ["position", "size"] 29 | } 30 | -------------------------------------------------------------------------------- /spec/v0.4/core/edge-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/rel/edge", 4 | "description": "Edge relation extension", 5 | "type": "object", 6 | "properties": { 7 | "start": { 8 | "type": "string", 9 | "description": "ID of start (source) element." 10 | }, 11 | "end": { 12 | "type": "string", 13 | "description": "ID of end (target) element." 14 | }, 15 | "directed": { 16 | "type": "boolean", 17 | "description": "Is the edge directed?", 18 | "default": true 19 | }, 20 | "rel": { 21 | "type": "string", 22 | "description": "Represented relation type. Can be a URI to represent the predicate of an RDF triple." 23 | }, 24 | "node": { 25 | "type": "string", 26 | "description": "The ID of a node that visually represents the edge." 27 | } 28 | }, 29 | "required": ["start", "end"] 30 | } 31 | -------------------------------------------------------------------------------- /spec/v0.5/core/edge-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/rel/edge", 4 | "description": "Edge relation extension", 5 | "type": "object", 6 | "properties": { 7 | "start": { 8 | "type": "string", 9 | "description": "ID of start (source) element." 10 | }, 11 | "end": { 12 | "type": "string", 13 | "description": "ID of end (target) element." 14 | }, 15 | "directed": { 16 | "type": "boolean", 17 | "description": "Is the edge directed?", 18 | "default": true 19 | }, 20 | "rel": { 21 | "type": "string", 22 | "description": "Represented relation type. Can be a URI to represent the predicate of an RDF triple." 23 | }, 24 | "node": { 25 | "type": "string", 26 | "description": "The ID of a node that visually represents the edge." 27 | } 28 | }, 29 | "required": ["start", "end"] 30 | } 31 | -------------------------------------------------------------------------------- /spec/v0.3/core/edge-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocwg/rel/edge", 4 | "description": "Edge relation extension", 5 | "type": "object", 6 | "properties": { 7 | "start": { 8 | "type": "string", 9 | "description": "ID of start (source) element." 10 | }, 11 | "end": { 12 | "type": "string", 13 | "description": "ID of end (target) element." 14 | }, 15 | "directed": { 16 | "type": "boolean", 17 | "description": "Is the edge directed?", 18 | "default": true 19 | }, 20 | "rel": { 21 | "type": "string", 22 | "description": "Represented relation type. Can be a URI to represent the predicate of an RDF triple." 23 | }, 24 | "node": { 25 | "type": "string", 26 | "description": "The ID of a node that visually represents the edge." 27 | } 28 | }, 29 | "required": [ 30 | "start", "end" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /spec/v0.1/schema/open-canvas.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "open-canvas.schema.json", 4 | "title": "Open Canvas", 5 | "type": "object", 6 | "description": "The root object for a open-canvas file", 7 | "required": ["schema_version", "nodes"], 8 | "properties": { 9 | "schema_version": { 10 | "type": "string" 11 | }, 12 | "nodes": { 13 | "type": "array", 14 | "description": "A list of nodes.", 15 | "items": { 16 | "$ref": "node.schema.json" 17 | } 18 | }, 19 | "relations": { 20 | "type": "array", 21 | "description": "A list of relations.", 22 | "items": { 23 | "$ref": "relation.schema.json" 24 | } 25 | }, 26 | "schemas": { 27 | "type": "object", 28 | "description": "A dictionary of extended schemas", 29 | "$ref": "schemas.schema.json" 30 | }, 31 | "resources": { 32 | "type": "object", 33 | "description": "A dictionary of resources which can be referenced by nodes and relations." 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /spec/v0.6/extensions/edge-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/rel/edge", 4 | "description": "Edge relation extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/rel/edge", 10 | "description": "Identifies the edge relation extension." 11 | }, 12 | "start": { 13 | "type": "string", 14 | "description": "ID of start (source) element." 15 | }, 16 | "end": { 17 | "type": "string", 18 | "description": "ID of end (target) element." 19 | }, 20 | "directed": { 21 | "type": "boolean", 22 | "description": "Is the edge directed?", 23 | "default": true 24 | }, 25 | "rel": { 26 | "type": "string", 27 | "description": "Represented relation type. Can be a URI to represent the predicate of an RDF triple." 28 | }, 29 | "node": { 30 | "type": "string", 31 | "description": "The ID of a node that visually represents the edge." 32 | } 33 | }, 34 | "required": ["type", "start", "end"] 35 | } 36 | -------------------------------------------------------------------------------- /spec/v0.6.1-draft/extensions/edge-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/rel/edge", 4 | "description": "Edge relation extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/rel/edge", 10 | "description": "Identifies the edge relation extension." 11 | }, 12 | "start": { 13 | "type": "string", 14 | "description": "ID of start (source) element." 15 | }, 16 | "end": { 17 | "type": "string", 18 | "description": "ID of end (target) element." 19 | }, 20 | "directed": { 21 | "type": "boolean", 22 | "description": "Is the edge directed?", 23 | "default": true 24 | }, 25 | "rel": { 26 | "type": "string", 27 | "description": "Represented relation type. Can be a URI to represent the predicate of an RDF triple." 28 | }, 29 | "node": { 30 | "type": "string", 31 | "description": "The ID of a node that visually represents the edge." 32 | } 33 | }, 34 | "required": ["type", "start", "end"] 35 | } 36 | -------------------------------------------------------------------------------- /spec/v0.3/extensions/parent-child-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocwg/rel/parent-child", 4 | "description": "Parent-child relationship extension. A parent-child relation models a hierarchical relationship between nodes. It can be used to model inheritance, containment, or other hierarchical relationships.", 5 | "type": "object", 6 | "properties": { 7 | "parent": { 8 | "type": "string", 9 | "description": "ID of the parent node. There SHOULD be only one parent per child." 10 | }, 11 | "child": { 12 | "type": "string", 13 | "description": "ID of the child node. A parent can have multiple children (expressed my multiple parent-child relations)." 14 | }, 15 | "inherit": { 16 | "type": "boolean", 17 | "description": "Inherit properties. A boolean flag indicating if the child should inherit properties from the parent. Default is false. The exact semantics of inheritance are defined by the application. In general, when looking for JSON properties of a node and finding them undefined, an app should look for the same value in the parent node. This chain of parents should be followed until root is reached or a cycle is detected." 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spec/v0.4/extensions/parent-child-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/rel/parent-child", 4 | "description": "Parent-child relationship extension. A parent-child relation models a hierarchical relationship between nodes. It can be used to model inheritance, containment, or other hierarchical relationships.", 5 | "type": "object", 6 | "properties": { 7 | "parent": { 8 | "type": "string", 9 | "description": "ID of the parent node. There SHOULD be only one parent per child." 10 | }, 11 | "child": { 12 | "type": "string", 13 | "description": "ID of the child node. A parent can have multiple children (expressed my multiple parent-child relations)." 14 | }, 15 | "inherit": { 16 | "type": "boolean", 17 | "description": "Inherit properties. A boolean flag indicating if the child should inherit properties from the parent. Default is false. The exact semantics of inheritance are defined by the application. In general, when looking for JSON properties of a node and finding them undefined, an app should look for the same value in the parent node. This chain of parents should be followed until root is reached or a cycle is detected." 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/file_format.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | 6 | if len(sys.argv) < 2: 7 | print("Invalid usage of file_format.py, it should be called with a path to one or multiple files.") 8 | sys.exit(1) 9 | 10 | BOM = b"\xef\xbb\xbf" 11 | 12 | changed = [] 13 | invalid = [] 14 | 15 | for file in sys.argv[1:]: 16 | try: 17 | with open(file, "rt", encoding="utf-8") as f: 18 | original = f.read() 19 | except UnicodeDecodeError: 20 | invalid.append(file) 21 | continue 22 | 23 | if original == "": 24 | continue 25 | 26 | EOL = "\n" 27 | 28 | revamp = EOL.join([line.rstrip("\n\r\t ") for line in original.splitlines(True)]).rstrip(EOL) + EOL 29 | 30 | new_raw = revamp.encode(encoding="utf-8") 31 | if new_raw.startswith(BOM): 32 | new_raw = new_raw[len(BOM) :] 33 | 34 | with open(file, "rb") as f: 35 | old_raw = f.read() 36 | 37 | if old_raw != new_raw: 38 | changed.append(file) 39 | with open(file, "wb") as f: 40 | f.write(new_raw) 41 | 42 | if changed: 43 | for file in changed: 44 | print(f"FIXED: {file}") 45 | 46 | if invalid: 47 | for file in invalid: 48 | print(f"REQUIRES MANUAL CHANGES: {file}") 49 | sys.exit(1) 50 | -------------------------------------------------------------------------------- /spec/v0.5/extensions/transforms-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/transforms", 4 | "description": "Geometric transforms for nodes", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/node/transforms" 10 | }, 11 | "scale": { 12 | "type": "array", 13 | "items": { 14 | "type": "number" 15 | }, 16 | "description": "Scale factor for the node. The array contains the scale factor for each dimension." 17 | }, 18 | "offset": { 19 | "type": "array", 20 | "items": { 21 | "type": "number" 22 | }, 23 | "description": "Relative position of the child node relative to the parent node. The numbers given in the array are vector-added to the position of the source node." 24 | }, 25 | "rotation": { 26 | "type": "number", 27 | "description": "Relative angle in degrees. The rotation is added to the rotation of parent coordinate system." 28 | }, 29 | "rotationAxis": { 30 | "type": "array", 31 | "items": { 32 | "type": "number" 33 | }, 34 | "minItems": 3, 35 | "maxItems": 3, 36 | "description": "Rotation axis for 3D rotation. Default is [0,0,1] (z-axis).", 37 | "default": [0, 0, 1] 38 | } 39 | }, 40 | "required": ["type"] 41 | } 42 | -------------------------------------------------------------------------------- /spec/v0.6/extensions/transforms-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/transforms", 4 | "description": "Geometric transforms for nodes", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/node/transforms" 10 | }, 11 | "scale": { 12 | "type": "array", 13 | "items": { 14 | "type": "number" 15 | }, 16 | "description": "Scale factor for the node. The array contains the scale factor for each dimension." 17 | }, 18 | "offset": { 19 | "type": "array", 20 | "items": { 21 | "type": "number" 22 | }, 23 | "description": "Relative position of the child node relative to the parent node. The numbers given in the array are vector-added to the position of the source node." 24 | }, 25 | "rotation": { 26 | "type": "number", 27 | "description": "Relative angle in degrees. The rotation is added to the rotation of parent coordinate system." 28 | }, 29 | "rotationAxis": { 30 | "type": "array", 31 | "items": { 32 | "type": "number" 33 | }, 34 | "minItems": 3, 35 | "maxItems": 3, 36 | "description": "Rotation axis for 3D rotation. Default is [0,0,1] (z-axis).", 37 | "default": [0, 0, 1] 38 | } 39 | }, 40 | "required": ["type"] 41 | } 42 | -------------------------------------------------------------------------------- /spec/v0.1/schema/node.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "node.schema.json", 4 | "title": "node", 5 | "type": "object", 6 | "description": "A node in the node list.", 7 | "required": ["id", "position"], 8 | "properties": { 9 | "id": { "type": "string" }, 10 | "position": { 11 | "type": "array", 12 | "items": { 13 | "anyOf": [ 14 | { 15 | "type": "number" 16 | } 17 | ] 18 | }, 19 | "minItems": 2, 20 | "maxItems": 3 21 | }, 22 | "size": { 23 | "type": "array", 24 | "items": { 25 | "anyOf": [ 26 | { 27 | "type": "number" 28 | } 29 | ] 30 | }, 31 | "minItems": 2, 32 | "maxItems": 3 33 | }, 34 | "rotation": { "type": "number" }, 35 | "properties": { 36 | "type": "array", 37 | "contains": { 38 | "type": "object", 39 | "properties": { 40 | "schema": { 41 | "type": "string" 42 | }, 43 | "schema_version": { 44 | "type": "string" 45 | } 46 | }, 47 | "required": ["schema", "schema_version"] 48 | }, 49 | "items": { 50 | "anyOf": [ 51 | { 52 | "type": "object" 53 | } 54 | ] 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /spec/v0.6.1-draft/extensions/transforms-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/transforms", 4 | "description": "Geometric transforms for nodes", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/node/transforms" 10 | }, 11 | "scale": { 12 | "type": "array", 13 | "items": { 14 | "type": "number" 15 | }, 16 | "description": "Scale factor for the node. The array contains the scale factor for each dimension." 17 | }, 18 | "offset": { 19 | "type": "array", 20 | "items": { 21 | "type": "number" 22 | }, 23 | "description": "Relative position of the child node relative to the parent node. The numbers given in the array are vector-added to the position of the source node." 24 | }, 25 | "rotation": { 26 | "type": "number", 27 | "description": "Relative angle in degrees. The rotation is added to the rotation of parent coordinate system." 28 | }, 29 | "rotationAxis": { 30 | "type": "array", 31 | "items": { 32 | "type": "number" 33 | }, 34 | "minItems": 3, 35 | "maxItems": 3, 36 | "description": "Rotation axis for 3D rotation. Default is [0,0,1] (z-axis).", 37 | "default": [0, 0, 1] 38 | } 39 | }, 40 | "required": ["type"] 41 | } 42 | -------------------------------------------------------------------------------- /spec/v0.5/extensions/textstyle-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://spec.canvasprotocol.org/v0.5/extensions/textstyle-node.json", 4 | "title": "@ocif/node/textstyle", 5 | "description": "Text style extension for rendering plain text and structured text", 6 | "type": "object", 7 | "properties": { 8 | "type": { 9 | "type": "string", 10 | "const": "@ocif/node/textstyle" 11 | }, 12 | "fontSizePx": { 13 | "description": "Font size in px, as used in CSS", 14 | "type": "number", 15 | "default": 12 16 | }, 17 | "fontFamily": { 18 | "description": "Font family, as used in CSS", 19 | "type": "string", 20 | "default": "sans-serif" 21 | }, 22 | "color": { 23 | "description": "Text color", 24 | "type": "string", 25 | "pattern": "^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$", 26 | "default": "#000000" 27 | }, 28 | "align": { 29 | "description": "Text alignment", 30 | "type": "string", 31 | "enum": ["left", "right", "center", "justify"], 32 | "default": "left" 33 | }, 34 | "bold": { 35 | "description": "Bold text", 36 | "type": "boolean", 37 | "default": false 38 | }, 39 | "italic": { 40 | "description": "Italic text", 41 | "type": "boolean", 42 | "default": false 43 | } 44 | }, 45 | "required": ["type"] 46 | } 47 | -------------------------------------------------------------------------------- /spec/v0.6/extensions/textstyle-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://spec.canvasprotocol.org/v0.6/extensions/textstyle-node.json", 4 | "title": "@ocif/node/textstyle", 5 | "description": "Text style extension for rendering plain text and structured text", 6 | "type": "object", 7 | "properties": { 8 | "type": { 9 | "type": "string", 10 | "const": "@ocif/node/textstyle" 11 | }, 12 | "fontSizePx": { 13 | "description": "Font size in px, as used in CSS", 14 | "type": "number", 15 | "default": 12 16 | }, 17 | "fontFamily": { 18 | "description": "Font family, as used in CSS", 19 | "type": "string", 20 | "default": "sans-serif" 21 | }, 22 | "color": { 23 | "description": "Text color", 24 | "type": "string", 25 | "pattern": "^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$", 26 | "default": "#000000" 27 | }, 28 | "align": { 29 | "description": "Text alignment", 30 | "type": "string", 31 | "enum": ["left", "right", "center", "justify"], 32 | "default": "left" 33 | }, 34 | "bold": { 35 | "description": "Bold text", 36 | "type": "boolean", 37 | "default": false 38 | }, 39 | "italic": { 40 | "description": "Italic text", 41 | "type": "boolean", 42 | "default": false 43 | } 44 | }, 45 | "required": ["type"] 46 | } 47 | -------------------------------------------------------------------------------- /spec/v0.6.1-draft/extensions/textstyle-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://spec.canvasprotocol.org/v0.6.1/extensions/textstyle-node.json", 4 | "title": "@ocif/node/textstyle", 5 | "description": "Text style extension for rendering plain text and structured text", 6 | "type": "object", 7 | "properties": { 8 | "type": { 9 | "type": "string", 10 | "const": "@ocif/node/textstyle" 11 | }, 12 | "fontSizePx": { 13 | "description": "Font size in px, as used in CSS", 14 | "type": "number", 15 | "default": 12 16 | }, 17 | "fontFamily": { 18 | "description": "Font family, as used in CSS", 19 | "type": "string", 20 | "default": "sans-serif" 21 | }, 22 | "color": { 23 | "description": "Text color", 24 | "type": "string", 25 | "pattern": "^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$", 26 | "default": "#000000" 27 | }, 28 | "align": { 29 | "description": "Text alignment", 30 | "type": "string", 31 | "enum": ["left", "right", "center", "justify"], 32 | "default": "left" 33 | }, 34 | "bold": { 35 | "description": "Bold text", 36 | "type": "boolean", 37 | "default": false 38 | }, 39 | "italic": { 40 | "description": "Italic text", 41 | "type": "boolean", 42 | "default": false 43 | } 44 | }, 45 | "required": ["type"] 46 | } 47 | -------------------------------------------------------------------------------- /spec/v0.4/extensions/hyperedge-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/rel/hyperedge", 4 | "description": "Hyperedge relation extension", 5 | "type": "object", 6 | "properties": { 7 | "endpoints": { 8 | "type": "array", 9 | "items": { 10 | "type": "object", 11 | "properties": { 12 | "id": { 13 | "type": "string", 14 | "description": "ID of attached entity (node or relation)" 15 | }, 16 | "direction": { 17 | "type": "string", 18 | "description": "Direction of the connection: 'in' (edge is going into the hyper-edge), 'out' (edge is going out from thy hyper-edge), 'undir' (edge is attached undirected). This is the default.", 19 | "enum": ["in", "out", "undir"] 20 | }, 21 | "weight": { 22 | "type": "number", 23 | "description": "Weight of the edge" 24 | } 25 | }, 26 | "required": ["id"] 27 | } 28 | }, 29 | "weight": { 30 | "type": "number", 31 | "description": "Weight of the edge. A floating-point number, which can be used to model the strength of the connection, as a whole. More general than endpoint-specific weights, and often sufficient." 32 | }, 33 | "rel": { 34 | "type": "string", 35 | "description": "Represented relation type" 36 | } 37 | }, 38 | "required": ["members"] 39 | } 40 | -------------------------------------------------------------------------------- /spec/v0.4/core/arrow-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/arrow", 4 | "description": "Arrow node extension", 5 | "type": "object", 6 | "properties": { 7 | "strokeWidth": { 8 | "type": "number", 9 | "description": "The line width." 10 | }, 11 | "strokeColor": { 12 | "type": "string", 13 | "description": "The color of the stroke." 14 | }, 15 | "fillColor": { 16 | "type": "string", 17 | "description": "The color of the fill." 18 | }, 19 | "start": { 20 | "type": "array", 21 | "items": { 22 | "type": "number" 23 | }, 24 | "minItems": 2, 25 | "maxItems": 2, 26 | "description": "The start point." 27 | }, 28 | "end": { 29 | "type": "array", 30 | "items": { 31 | "type": "number" 32 | }, 33 | "minItems": 2, 34 | "maxItems": 2, 35 | "description": "The end point." 36 | }, 37 | "startMarker": { 38 | "type": "string", 39 | "enum": ["none", "arrowhead"], 40 | "description": "The marker at the start of the arrow." 41 | }, 42 | "endMarker": { 43 | "type": "string", 44 | "enum": ["none", "arrowhead"], 45 | "description": "The marker at the end of the arrow." 46 | }, 47 | "relation": { 48 | "type": "string", 49 | "description": "The ID of the relation defining the semantics of the arrow." 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /spec/v0.3/core/arrow-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocwg/node/arrow", 4 | "description": "Arrow node extension", 5 | "type": "object", 6 | "properties": { 7 | "strokeWidth": { 8 | "type": "number", 9 | "description": "The line width." 10 | }, 11 | "strokeColor": { 12 | "type": "string", 13 | "description": "The color of the stroke." 14 | }, 15 | "fillColor": { 16 | "type": "string", 17 | "description": "The color of the fill." 18 | }, 19 | "start": { 20 | "type": "array", 21 | "items": { 22 | "type": "number" 23 | }, 24 | "minItems": 2, 25 | "maxItems": 2, 26 | "description": "The start point." 27 | }, 28 | "end": { 29 | "type": "array", 30 | "items": { 31 | "type": "number" 32 | }, 33 | "minItems": 2, 34 | "maxItems": 2, 35 | "description": "The end point." 36 | }, 37 | "startMarker": { 38 | "type": "string", 39 | "enum": [ 40 | "none", 41 | "arrowhead" 42 | ], 43 | "description": "The marker at the start of the arrow." 44 | }, 45 | "endMarker": { 46 | "type": "string", 47 | "enum": [ 48 | "none", 49 | "arrowhead" 50 | ], 51 | "description": "The marker at the end of the arrow." 52 | }, 53 | "relation" : { 54 | "type": "string", 55 | "description": "The ID of the relation defining the semantics of the arrow." 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /spec/v0.5/extensions/hyperedge-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/rel/hyperedge", 4 | "description": "Hyperedge relation extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/rel/hyperedge" 10 | }, 11 | "endpoints": { 12 | "type": "array", 13 | "items": { 14 | "type": "object", 15 | "properties": { 16 | "id": { 17 | "type": "string", 18 | "description": "ID of attached entity (node or relation)" 19 | }, 20 | "direction": { 21 | "type": "string", 22 | "description": "Direction of the connection: 'in' (edge is going into the hyper-edge), 'out' (edge is going out from thy hyper-edge), 'undir' (edge is attached undirected). This is the default.", 23 | "enum": ["in", "out", "undir"] 24 | }, 25 | "weight": { 26 | "type": "number", 27 | "description": "Weight of the edge" 28 | } 29 | }, 30 | "required": ["id"] 31 | } 32 | }, 33 | "weight": { 34 | "type": "number", 35 | "description": "Weight of the edge. A floating-point number, which can be used to model the strength of the connection, as a whole. More general than endpoint-specific weights, and often sufficient." 36 | }, 37 | "rel": { 38 | "type": "string", 39 | "description": "Represented relation type" 40 | } 41 | }, 42 | "required": ["type", "endpoints"] 43 | } 44 | -------------------------------------------------------------------------------- /spec/v0.6/extensions/hyperedge-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/rel/hyperedge", 4 | "description": "Hyperedge relation extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/rel/hyperedge" 10 | }, 11 | "endpoints": { 12 | "type": "array", 13 | "items": { 14 | "type": "object", 15 | "properties": { 16 | "id": { 17 | "type": "string", 18 | "description": "ID of attached entity (node or relation)" 19 | }, 20 | "direction": { 21 | "type": "string", 22 | "description": "Direction of the connection: 'in' (edge is going into the hyper-edge), 'out' (edge is going out from thy hyper-edge), 'undir' (edge is attached undirected). This is the default.", 23 | "enum": ["in", "out", "undir"] 24 | }, 25 | "weight": { 26 | "type": "number", 27 | "description": "Weight of the edge" 28 | } 29 | }, 30 | "required": ["id"] 31 | } 32 | }, 33 | "weight": { 34 | "type": "number", 35 | "description": "Weight of the edge. A floating-point number, which can be used to model the strength of the connection, as a whole. More general than endpoint-specific weights, and often sufficient." 36 | }, 37 | "rel": { 38 | "type": "string", 39 | "description": "Represented relation type" 40 | } 41 | }, 42 | "required": ["type", "endpoints"] 43 | } 44 | -------------------------------------------------------------------------------- /spec/v0.6.1-draft/extensions/hyperedge-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/rel/hyperedge", 4 | "description": "Hyperedge relation extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/rel/hyperedge" 10 | }, 11 | "endpoints": { 12 | "type": "array", 13 | "items": { 14 | "type": "object", 15 | "properties": { 16 | "id": { 17 | "type": "string", 18 | "description": "ID of attached entity (node or relation)" 19 | }, 20 | "direction": { 21 | "type": "string", 22 | "description": "Direction of the connection: 'in' (edge is going into the hyper-edge), 'out' (edge is going out from thy hyper-edge), 'undir' (edge is attached undirected). This is the default.", 23 | "enum": ["in", "out", "undir"] 24 | }, 25 | "weight": { 26 | "type": "number", 27 | "description": "Weight of the edge" 28 | } 29 | }, 30 | "required": ["id"] 31 | } 32 | }, 33 | "weight": { 34 | "type": "number", 35 | "description": "Weight of the edge. A floating-point number, which can be used to model the strength of the connection, as a whole. More general than endpoint-specific weights, and often sufficient." 36 | }, 37 | "rel": { 38 | "type": "string", 39 | "description": "Represented relation type" 40 | } 41 | }, 42 | "required": ["type", "endpoints"] 43 | } 44 | -------------------------------------------------------------------------------- /spec/v0.3/extensions/hyperedge-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocwg/rel/hyperedge", 4 | "description": "Hyperedge relation extension", 5 | "type": "object", 6 | "properties": { 7 | "endpoints": { 8 | "type": "array", 9 | "items": { 10 | "type": "object", 11 | "properties": { 12 | "id": { 13 | "type": "string", 14 | "description": "ID of attached entity (node or relation)" 15 | }, 16 | "direction": { 17 | "type": "string", 18 | "description": "Direction of the connection: 'in' (edge is going into the hyper-edge), 'out' (edge is going out from thy hyper-edge), 'undir' (edge is attached undirected). This is the default.", 19 | "enum": [ 20 | "in", 21 | "out", 22 | "undir" 23 | ] 24 | }, 25 | "weight": { 26 | "type": "number", 27 | "description": "Weight of the edge" 28 | } 29 | }, 30 | "required": [ 31 | "id" 32 | ] 33 | } 34 | }, 35 | "weight": { 36 | "type": "number", 37 | "description": "Weight of the edge. A floating-point number, which can be used to model the strength of the connection, as a whole. More general than endpoint-specific weights, and often sufficient." 38 | }, 39 | "rel": { 40 | "type": "string", 41 | "description": "Represented relation type" 42 | } 43 | }, 44 | "required": [ 45 | "members" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /spec/v0.5/core/arrow-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/arrow", 4 | "description": "Arrow node extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/node/arrow" 10 | }, 11 | "strokeWidth": { 12 | "type": "number", 13 | "description": "The line width." 14 | }, 15 | "strokeColor": { 16 | "type": "string", 17 | "description": "The color of the stroke." 18 | }, 19 | "fillColor": { 20 | "type": "string", 21 | "description": "The color of the fill." 22 | }, 23 | "start": { 24 | "type": "array", 25 | "items": { 26 | "type": "number" 27 | }, 28 | "minItems": 2, 29 | "maxItems": 2, 30 | "description": "The start point." 31 | }, 32 | "end": { 33 | "type": "array", 34 | "items": { 35 | "type": "number" 36 | }, 37 | "minItems": 2, 38 | "maxItems": 2, 39 | "description": "The end point." 40 | }, 41 | "startMarker": { 42 | "type": "string", 43 | "enum": ["none", "arrowhead"], 44 | "description": "The marker at the start of the arrow." 45 | }, 46 | "endMarker": { 47 | "type": "string", 48 | "enum": ["none", "arrowhead"], 49 | "description": "The marker at the end of the arrow." 50 | }, 51 | "relation": { 52 | "type": "string", 53 | "description": "The ID of the relation defining the semantics of the arrow." 54 | } 55 | }, 56 | "required": ["type", "start", "end"] 57 | } 58 | -------------------------------------------------------------------------------- /spec/v0.5/extensions/parent-child-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/rel/parent-child", 4 | "description": "Parent-child relationship extension. A parent-child relation models a hierarchical relationship between nodes. It can be used to model inheritance, containment, or other hierarchical relationships.", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/rel/parent-child" 10 | }, 11 | "parent": { 12 | "type": "string", 13 | "description": "ID of the parent node. There SHOULD be only one parent per child." 14 | }, 15 | "child": { 16 | "type": "string", 17 | "description": "ID of the child node. A parent can have multiple children (expressed my multiple parent-child relations)." 18 | }, 19 | "inherit": { 20 | "type": "boolean", 21 | "description": "Inherit properties. A boolean flag indicating if the child should inherit properties from the parent. Default is false. The exact semantics of inheritance are defined by the application. In general, when looking for JSON properties of a node and finding them undefined, an app should look for the same value in the parent node. This chain of parents should be followed until root is reached or a cycle is detected.", 22 | "default": false 23 | }, 24 | "cascadeDelete": { 25 | "type": "boolean", 26 | "description": "A boolean flag indicating if the children should be deleted when the parent is deleted.", 27 | "default": true 28 | } 29 | }, 30 | "required": ["type", "child"] 31 | } 32 | -------------------------------------------------------------------------------- /spec/v0.6/extensions/arrow-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/arrow", 4 | "description": "Arrow node extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/node/arrow" 10 | }, 11 | "strokeWidth": { 12 | "type": "number", 13 | "description": "The line width." 14 | }, 15 | "strokeColor": { 16 | "type": "string", 17 | "description": "The color of the stroke." 18 | }, 19 | "fillColor": { 20 | "type": "string", 21 | "description": "The color of the fill." 22 | }, 23 | "start": { 24 | "type": "array", 25 | "items": { 26 | "type": "number" 27 | }, 28 | "minItems": 2, 29 | "maxItems": 2, 30 | "description": "The start point." 31 | }, 32 | "end": { 33 | "type": "array", 34 | "items": { 35 | "type": "number" 36 | }, 37 | "minItems": 2, 38 | "maxItems": 2, 39 | "description": "The end point." 40 | }, 41 | "startMarker": { 42 | "type": "string", 43 | "enum": ["none", "arrowhead"], 44 | "description": "The marker at the start of the arrow." 45 | }, 46 | "endMarker": { 47 | "type": "string", 48 | "enum": ["none", "arrowhead"], 49 | "description": "The marker at the end of the arrow." 50 | }, 51 | "relation": { 52 | "type": "string", 53 | "description": "The ID of the relation defining the semantics of the arrow." 54 | } 55 | }, 56 | "required": ["type", "start", "end"] 57 | } 58 | -------------------------------------------------------------------------------- /spec/v0.6/extensions/parent-child-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/rel/parent-child", 4 | "description": "Parent-child relationship extension. A parent-child relation models a hierarchical relationship between nodes. It can be used to model inheritance, containment, or other hierarchical relationships.", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/rel/parent-child" 10 | }, 11 | "parent": { 12 | "type": "string", 13 | "description": "ID of the parent node. There SHOULD be only one parent per child." 14 | }, 15 | "child": { 16 | "type": "string", 17 | "description": "ID of the child node. A parent can have multiple children (expressed my multiple parent-child relations)." 18 | }, 19 | "inherit": { 20 | "type": "boolean", 21 | "description": "Inherit properties. A boolean flag indicating if the child should inherit properties from the parent. Default is false. The exact semantics of inheritance are defined by the application. In general, when looking for JSON properties of a node and finding them undefined, an app should look for the same value in the parent node. This chain of parents should be followed until root is reached or a cycle is detected.", 22 | "default": false 23 | }, 24 | "cascadeDelete": { 25 | "type": "boolean", 26 | "description": "A boolean flag indicating if the children should be deleted when the parent is deleted.", 27 | "default": true 28 | } 29 | }, 30 | "required": ["type", "child"] 31 | } 32 | -------------------------------------------------------------------------------- /spec/v0.1/schema/relation.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "relation.schema.json", 4 | "title": "relation", 5 | "type": "object", 6 | "description": "relation between nodes", 7 | "required": ["name", "id", "properties"], 8 | "properties": { 9 | "name": { "type": "string" }, 10 | "id": { "type": "string" }, 11 | "properties": { 12 | "type": "array", 13 | "items": { 14 | "anyOf": [ 15 | { 16 | "type": "object", 17 | "properties": { 18 | "schema": { 19 | "enum": ["@ocwg/set"] 20 | }, 21 | "schema_version": { 22 | "type": "string" 23 | }, 24 | "members": { 25 | "type": "array", 26 | "items": { 27 | "type": "string" 28 | } 29 | } 30 | }, 31 | "required": ["schema", "schema_version", "members"] 32 | }, 33 | { 34 | "type": "object", 35 | "properties": { 36 | "schema": { 37 | "enum": ["@ocwg/edge"] 38 | }, 39 | "schema_version": { 40 | "type": "string" 41 | }, 42 | "from": { 43 | "type": "string" 44 | }, 45 | "to": { 46 | "type": "string" 47 | } 48 | }, 49 | "required": ["schema", "schema_version", "from", "to"] 50 | } 51 | ] 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /spec/v0.6.1-draft/extensions/arrow-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/node/arrow", 4 | "description": "Arrow node extension", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/node/arrow" 10 | }, 11 | "strokeWidth": { 12 | "type": "number", 13 | "description": "The line width." 14 | }, 15 | "strokeColor": { 16 | "type": "string", 17 | "description": "The color of the stroke." 18 | }, 19 | "fillColor": { 20 | "type": "string", 21 | "description": "The color of the fill." 22 | }, 23 | "start": { 24 | "type": "array", 25 | "items": { 26 | "type": "number" 27 | }, 28 | "minItems": 2, 29 | "maxItems": 2, 30 | "description": "The start point." 31 | }, 32 | "end": { 33 | "type": "array", 34 | "items": { 35 | "type": "number" 36 | }, 37 | "minItems": 2, 38 | "maxItems": 2, 39 | "description": "The end point." 40 | }, 41 | "startMarker": { 42 | "type": "string", 43 | "enum": ["none", "arrowhead"], 44 | "description": "The marker at the start of the arrow." 45 | }, 46 | "endMarker": { 47 | "type": "string", 48 | "enum": ["none", "arrowhead"], 49 | "description": "The marker at the end of the arrow." 50 | }, 51 | "relation": { 52 | "type": "string", 53 | "description": "The ID of the relation defining the semantics of the arrow." 54 | } 55 | }, 56 | "required": ["type", "start", "end"] 57 | } 58 | -------------------------------------------------------------------------------- /spec/v0.6.1-draft/extensions/parent-child-rel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "@ocif/rel/parent-child", 4 | "description": "Parent-child relationship extension. A parent-child relation models a hierarchical relationship between nodes. It can be used to model inheritance, containment, or other hierarchical relationships.", 5 | "type": "object", 6 | "properties": { 7 | "type": { 8 | "type": "string", 9 | "const": "@ocif/rel/parent-child" 10 | }, 11 | "parent": { 12 | "type": "string", 13 | "description": "ID of the parent node. There SHOULD be only one parent per child." 14 | }, 15 | "child": { 16 | "type": "string", 17 | "description": "ID of the child node. A parent can have multiple children (expressed my multiple parent-child relations)." 18 | }, 19 | "inherit": { 20 | "type": "boolean", 21 | "description": "Inherit properties. A boolean flag indicating if the child should inherit properties from the parent. Default is false. The exact semantics of inheritance are defined by the application. In general, when looking for JSON properties of a node and finding them undefined, an app should look for the same value in the parent node. This chain of parents should be followed until root is reached or a cycle is detected.", 22 | "default": false 23 | }, 24 | "cascadeDelete": { 25 | "type": "boolean", 26 | "description": "A boolean flag indicating if the children should be deleted when the parent is deleted.", 27 | "default": true 28 | } 29 | }, 30 | "required": ["type", "child"] 31 | } 32 | -------------------------------------------------------------------------------- /making-of.md: -------------------------------------------------------------------------------- 1 | Making Of 2 | ========= 3 | 4 | 5 | ## Markdown 6 | 7 | This document is written in Markdown, in the _GitHub-flavored_ variant. 8 | 9 | - https://github.github.com/gfm/ 10 | 11 | 12 | 13 | 14 | ## Other Specs as Inspiration for Structuring this Document 15 | 16 | - https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/[] 17 | via Maikel in https://github.com/orgs/ocwg/discussions/32 18 | 19 | 20 | 21 | ## W3C Tools 22 | 23 | - Sample spec (outline) 24 | - https://www.w3.org/StyleSheets/TR/2016/README (HTML) 25 | - https://w3c-ccg.github.io/markdown-to-spec/ (Markdown) 26 | 27 | - Converter from Markdown to ReSpec https://github.com/w3c-ccg/markdown-to-spec 28 | - Bikeshed: https://github.com/speced/bikeshed 29 | 30 | # Editor 31 | - IntelliJ with https://plugins.jetbrains.com/plugin/14683-gfma 32 | 33 | In IntelliJ, the markdown preview produces HTML, which allows this snippet of CSS to be used to style markdown checkboxes (a valid GitHub flavored Markdown extension). Just copy it into the spec.md file, while you work on it. 34 | 35 | ```css 36 | 37 | 58 | ``` 59 | -------------------------------------------------------------------------------- /spec/v0.5/how-to-spec.md: -------------------------------------------------------------------------------- 1 | # How To Edit The Spec 2 | 3 | * [How To Edit The Spec](#how-to-edit-the-spec) 4 | * [Release Instructions](#release-instructions) 5 | * [How To Typeset Issues](#how-to-typeset-issues) 6 | 7 | 8 | - All URIs should have the same, consistent structure 9 | - Property tables should follow these conventions 10 | - **required** is always bold, other entries are not 11 | - JSON types are set in `monospace` 12 | - OCIF types are linked to their definition 13 | - All examples start with `**Example:**` 14 | - Order of columns is always: Property, JSON Type, OCIF Type, Required, Contents, Default 15 | - Empty columns can be omitted 16 | 17 | ## Release Instructions 18 | 19 | When creating a new version of the spec: 20 | 21 | 1. `cp` the current version's directory (`/spec/vX.X`) to the next version number with `-draft` appended 22 | 2. Merge the new directory to the `main` branch. 23 | 3. Create a new branch named `vX.X-draft`. 24 | 4. Create pull requests against the new `vX.X-draft` folder until satisfied with release. These will have nice, small diffs that just highlight the major changes. 25 | - Alternatively, you can leave the new directory on a branch and create pull requests against that branch until it's ready to be merged. 26 | 5. Open a pull request to update all of the "current" version pointers in the `spec` repo: 27 | - rename the `vX.X-draft` folder to `vX.X` 28 | - update `/public/_redirects` line 2 to point spec.canvasprotocol.org to: 29 | ``` 30 | / https://github.com/ocwg/spec/blob/main/spec/vX.X/spec.md 302 31 | ``` 32 | - Excluding the `/spec` directory, find and replace the previous version with the new version (i.e., replace `v0.5` with `v0.5.1`). 33 | - That will update the Cookbook, Catalog, Examples, and README.md 34 | 6. Update the version numbers on the [website](https://github.com/ocwg/canvasprotocol.org/blob/main/index.html). 35 | 7. Consider notifying people in Discord and sending a Newsletter update. 36 | 37 | ## How To Typeset Issues 38 | - Issues are temporary TODOs, which should be resolved before the final version. The `@@` makes them easy to search in an editor. 39 | 40 | **Issue Example:** 41 | 42 | - [ ] @@ This is an issue 43 | -------------------------------------------------------------------------------- /design/requirements.md: -------------------------------------------------------------------------------- 1 | # Requirements 2 | These are requirements and goals which we defined for a canvas interchange format. 3 | 4 | 5 | * [Requirements](#requirements) 6 | * [Offline](#offline) 7 | * [Dynamic](#dynamic) 8 | * [Graceful Degradation](#graceful-degradation) 9 | * [Extensible](#extensible) 10 | * [Human Friendly](#human-friendly) 11 | * [Re-use](#re-use) 12 | * [Precise](#precise) 13 | * [Visual Fidelity](#visual-fidelity) 14 | 15 | 16 | ## Offline 17 | The format should allow to be opened in an offline scenario, i.e. all 18 | content needs to be present locally. 19 | 20 | ## Dynamic 21 | We don’t want to force a canvas to contain only offline content. Some 22 | resource are (a) dynamic (e.g. a weather forecast), or (b) large (e.g. 23 | videos) so that downloading is not always the best strategy. 24 | 25 | ## Graceful Degradation 26 | We want fall-backs for difficult content types. In email, HTML mails 27 | usually have a plain-text fall-back. 28 | 29 | ## Extensible 30 | We want a number of apps, including those not known at time of writing 31 | this spec, be able to use the export format to round-trip data. Thus, 32 | 33 | - an app may write arbitrary data into the export (given it adheres to 34 | some rules) 35 | - an app may not alter or delete data written be another app (unless the 36 | whole node is deleted) 37 | 38 | ## Human Friendly 39 | We want to make it easy for humans to inspect and understand an export 40 | files' content. And, if possible, make changes on the source file. 41 | 42 | ## Re-use 43 | When possible, we want to re-use existing standards and mechanisms, in 44 | order to not reinvent the wheel. 45 | 46 | ## Precise 47 | We want the spec to clearly define which OCWG files are valid, which are 48 | not, and especially, how to interpret the valid ones. 49 | 50 | For numeric precision, which becomes relevant in an "infinite" zoom-able 51 | canvas, see [13](https://github.com/orgs/ocwg/discussions/13). 52 | 53 | Text rendering and container dimensions have a complex interplay. See 54 | [5](https://github.com/orgs/ocwg/discussions/5). 55 | 56 | ## Visual Fidelity 57 | Although we don’t strive for 100% visual fidelity (see 58 | [11](https://github.com/orgs/ocwg/discussions/11)), there are some areas 59 | we need to look at: 60 | 61 | - More Relevant 62 | - text rendering (e.g. different browsers have different default line 63 | heights; installed fonts differ, hyphenation differs, text direction, 64 | Unicode, …) 65 | - transparency & z-Index: What is in effect visible and whats hidden? 66 | - Minor 67 | - color spaces? 68 | - path stroking (rendering a path is not 100% defined) 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OCIF (Open Canvas Interchange Format) Spec 2 | 3 | ## ⚒️ Status: Seeking Implementation Feedback 4 | The [spec](./spec/v0.6/spec.md) is in the *"Candidate Recommendation"* stage, which means it has been widely reviewed and satisfies OCWG's technical requirements. We are currently gathering implementation experience. 5 | 6 | We welcome any feedback and implementations. 7 | 8 | 📚 Read more at [canvasprotocol.org](https://canvasprotocol.org). 9 | 10 | ## Release Process 11 | 12 | When releasing a new version of the OCIF specification, follow these steps: 13 | 14 | ### 1. Prepare the Release 15 | 16 | See [detailed technical release instructions](./spec/v0.5/how-to-spec.md#release-instructions) for the complete Git workflow, including: 17 | - Creating and managing draft versions 18 | - Updating `public/_redirects` to point spec.canvasprotocol.org to the new version 19 | - Updating version numbers across Cookbook, Catalog, Examples, and README 20 | 21 | Quick checklist: 22 | - [ ] Follow the technical release steps in how-to-spec.md 23 | - [ ] Create a git tag for the release (e.g., `v0.6`) 24 | - [ ] Push the tag to GitHub: `git push origin v0.6` 25 | 26 | ### 2. Update Website 27 | 28 | - [ ] Update [canvasprotocol.org](https://canvasprotocol.org) to list the new version 29 | - Note: The current website does not list version numbers, consider if this should change 30 | 31 | ### 3. Announce on GitHub 32 | 33 | - [ ] Create a new announcement post in [GitHub Discussions](https://github.com/orgs/ocwg/discussions) 34 | - Use the Announcements category 35 | - Title format: "OCIF [version] is released" 36 | - Include: version number, status, key features/changes, call to action for implementers 37 | - Reference [example 0.5 announcement](https://github.com/orgs/ocwg/discussions/57) 38 | 39 | ### 4. Send Email Announcement 40 | 41 | - [ ] Draft email announcement via [Buttondown](https://buttondown.com/ocwg) 42 | - Subject format: "OCIF [version] is released" 43 | - Include TL;DR summary 44 | - Link to the GitHub Discussion post 45 | - Link to https://spec.canvasprotocol.org 46 | - Review [past announcements](https://buttondown.com/ocwg/archive/) for tone and format 47 | 48 | ### 5. Social Media & Community 49 | 50 | - [ ] Post in OCWG Discord #general channel 51 | - Link to GitHub Discussion post 52 | - Brief summary of key changes 53 | - [ ] Post to Twitter and Bluesky (@jessmartin or designated account) 54 | - Link to GitHub Discussion post 55 | - Link to https://spec.canvasprotocol.org 56 | - Use consistent messaging with other channels 57 | 58 | ### 6. Post-Release 59 | 60 | - [ ] Create a new draft branch for the next version 61 | - [ ] Update README to reflect "seeking implementation feedback" status if applicable 62 | - [ ] Monitor GitHub Discussions and Discord for questions and feedback 63 | -------------------------------------------------------------------------------- /spec/v0.5/extensions/anchored-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://spec.canvasprotocol.org/v0.5/extensions/anchored-node.json", 4 | "title": "@ocif/node/anchored", 5 | "description": "Anchored node extension for relative positioning anchored to a parent item", 6 | "type": "object", 7 | "properties": { 8 | "type": { 9 | "type": "string", 10 | "const": "@ocif/node/anchored" 11 | }, 12 | "topLeftPosition": { 13 | "description": "Top left anchor as percentage coordinates", 14 | "oneOf": [ 15 | { 16 | "type": "array", 17 | "items": { 18 | "type": "number" 19 | }, 20 | "minItems": 2, 21 | "maxItems": 2 22 | }, 23 | { 24 | "type": "array", 25 | "items": { 26 | "type": "number" 27 | }, 28 | "minItems": 3, 29 | "maxItems": 3 30 | } 31 | ], 32 | "default": [0, 0] 33 | }, 34 | "bottomRightPosition": { 35 | "description": "Bottom-right anchor as percentage coordinates", 36 | "oneOf": [ 37 | { 38 | "type": "array", 39 | "items": { 40 | "type": "number" 41 | }, 42 | "minItems": 2, 43 | "maxItems": 2 44 | }, 45 | { 46 | "type": "array", 47 | "items": { 48 | "type": "number" 49 | }, 50 | "minItems": 3, 51 | "maxItems": 3 52 | } 53 | ], 54 | "default": [1, 1] 55 | }, 56 | "topLeftOffset": { 57 | "description": "Top left offset as absolute coordinates", 58 | "oneOf": [ 59 | { 60 | "type": "array", 61 | "items": { 62 | "type": "number" 63 | }, 64 | "minItems": 2, 65 | "maxItems": 2 66 | }, 67 | { 68 | "type": "array", 69 | "items": { 70 | "type": "number" 71 | }, 72 | "minItems": 3, 73 | "maxItems": 3 74 | } 75 | ], 76 | "default": [0, 0] 77 | }, 78 | "bottomRightOffset": { 79 | "description": "Bottom-right offset as absolute coordinates", 80 | "oneOf": [ 81 | { 82 | "type": "array", 83 | "items": { 84 | "type": "number" 85 | }, 86 | "minItems": 2, 87 | "maxItems": 2 88 | }, 89 | { 90 | "type": "array", 91 | "items": { 92 | "type": "number" 93 | }, 94 | "minItems": 3, 95 | "maxItems": 3 96 | } 97 | ], 98 | "default": [0, 0] 99 | } 100 | }, 101 | "required": ["type"] 102 | } 103 | -------------------------------------------------------------------------------- /spec/v0.6/extensions/anchored-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://spec.canvasprotocol.org/v0.6/extensions/anchored-node.json", 4 | "title": "@ocif/node/anchored", 5 | "description": "Anchored node extension for relative positioning anchored to a parent item", 6 | "type": "object", 7 | "properties": { 8 | "type": { 9 | "type": "string", 10 | "const": "@ocif/node/anchored" 11 | }, 12 | "topLeftAnchor": { 13 | "description": "Top left anchor as percentage coordinates", 14 | "oneOf": [ 15 | { 16 | "type": "array", 17 | "items": { 18 | "type": "number" 19 | }, 20 | "minItems": 2, 21 | "maxItems": 2 22 | }, 23 | { 24 | "type": "array", 25 | "items": { 26 | "type": "number" 27 | }, 28 | "minItems": 3, 29 | "maxItems": 3 30 | } 31 | ], 32 | "default": [0.0, 0.0] 33 | }, 34 | "bottomRightAnchor": { 35 | "description": "Bottom-right anchor as percentage coordinates", 36 | "oneOf": [ 37 | { 38 | "type": "array", 39 | "items": { 40 | "type": "number" 41 | }, 42 | "minItems": 2, 43 | "maxItems": 2 44 | }, 45 | { 46 | "type": "array", 47 | "items": { 48 | "type": "number" 49 | }, 50 | "minItems": 3, 51 | "maxItems": 3 52 | } 53 | ], 54 | "default": [1.0, 1.0] 55 | }, 56 | "topLeftOffset": { 57 | "description": "Top left offset as absolute coordinates", 58 | "oneOf": [ 59 | { 60 | "type": "array", 61 | "items": { 62 | "type": "number" 63 | }, 64 | "minItems": 2, 65 | "maxItems": 2 66 | }, 67 | { 68 | "type": "array", 69 | "items": { 70 | "type": "number" 71 | }, 72 | "minItems": 3, 73 | "maxItems": 3 74 | } 75 | ], 76 | "default": [0.0, 0.0] 77 | }, 78 | "bottomRightOffset": { 79 | "description": "Bottom-right offset as absolute coordinates", 80 | "oneOf": [ 81 | { 82 | "type": "array", 83 | "items": { 84 | "type": "number" 85 | }, 86 | "minItems": 2, 87 | "maxItems": 2 88 | }, 89 | { 90 | "type": "array", 91 | "items": { 92 | "type": "number" 93 | }, 94 | "minItems": 3, 95 | "maxItems": 3 96 | } 97 | ], 98 | "default": [0.0, 0.0] 99 | } 100 | }, 101 | "required": ["type"] 102 | } 103 | -------------------------------------------------------------------------------- /spec/v0.6.1-draft/extensions/anchored-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://spec.canvasprotocol.org/v0.6.1/extensions/anchored-node.json", 4 | "title": "@ocif/node/anchored", 5 | "description": "Anchored node extension for relative positioning anchored to a parent item", 6 | "type": "object", 7 | "properties": { 8 | "type": { 9 | "type": "string", 10 | "const": "@ocif/node/anchored" 11 | }, 12 | "topLeftAnchor": { 13 | "description": "Top left anchor as percentage coordinates", 14 | "oneOf": [ 15 | { 16 | "type": "array", 17 | "items": { 18 | "type": "number" 19 | }, 20 | "minItems": 2, 21 | "maxItems": 2 22 | }, 23 | { 24 | "type": "array", 25 | "items": { 26 | "type": "number" 27 | }, 28 | "minItems": 3, 29 | "maxItems": 3 30 | } 31 | ], 32 | "default": [0.0, 0.0] 33 | }, 34 | "bottomRightAnchor": { 35 | "description": "Bottom-right anchor as percentage coordinates", 36 | "oneOf": [ 37 | { 38 | "type": "array", 39 | "items": { 40 | "type": "number" 41 | }, 42 | "minItems": 2, 43 | "maxItems": 2 44 | }, 45 | { 46 | "type": "array", 47 | "items": { 48 | "type": "number" 49 | }, 50 | "minItems": 3, 51 | "maxItems": 3 52 | } 53 | ], 54 | "default": [1.0, 1.0] 55 | }, 56 | "topLeftOffset": { 57 | "description": "Top left offset as absolute coordinates", 58 | "oneOf": [ 59 | { 60 | "type": "array", 61 | "items": { 62 | "type": "number" 63 | }, 64 | "minItems": 2, 65 | "maxItems": 2 66 | }, 67 | { 68 | "type": "array", 69 | "items": { 70 | "type": "number" 71 | }, 72 | "minItems": 3, 73 | "maxItems": 3 74 | } 75 | ], 76 | "default": [0.0, 0.0] 77 | }, 78 | "bottomRightOffset": { 79 | "description": "Bottom-right offset as absolute coordinates", 80 | "oneOf": [ 81 | { 82 | "type": "array", 83 | "items": { 84 | "type": "number" 85 | }, 86 | "minItems": 2, 87 | "maxItems": 2 88 | }, 89 | { 90 | "type": "array", 91 | "items": { 92 | "type": "number" 93 | }, 94 | "minItems": 3, 95 | "maxItems": 3 96 | } 97 | ], 98 | "default": [0.0, 0.0] 99 | } 100 | }, 101 | "required": ["type"] 102 | } 103 | -------------------------------------------------------------------------------- /spec/v0.6/how-to-spec.md: -------------------------------------------------------------------------------- 1 | # How To Edit The Spec 2 | 3 | * [How To Edit The Spec](#how-to-edit-the-spec) 4 | * [Release Instructions](#release-instructions) 5 | * [How To Typeset Issues](#how-to-typeset-issues) 6 | 7 | 8 | - All URIs should have the same, consistent structure 9 | - Property tables should follow these conventions 10 | - **required** is always bold, other entries are not 11 | - JSON types are set in `monospace` 12 | - OCIF types are linked to their definition 13 | - All examples start with `**Example:**` 14 | - Order of columns is always: Property, JSON Type, OCIF Type, Required, Contents, Default 15 | - Empty columns can be omitted 16 | 17 | ## Release Instructions 18 | 19 | See the [Release Process section in the README](../../../README.md#release-process) for the complete release checklist, including communication and announcement steps. 20 | 21 | ### Technical Release Workflow 22 | 23 | When creating a new version of the spec: 24 | 25 | 1. **Create draft version**: `cp` the current version's directory (`/spec/vX.X`) to the next version number with `-draft` appended 26 | 2. **Merge to main**: Merge the new directory to the `main` branch 27 | 3. **Create draft branch**: Create a new branch named `vX.X-draft` 28 | 4. **Iterate on draft**: Create pull requests against the new `vX.X-draft` folder until satisfied with release. These will have nice, small diffs that just highlight the major changes 29 | - Alternatively, you can leave the new directory on a branch and create pull requests against that branch until it's ready to be merged 30 | 31 | 5. **Finalize release**: Open a pull request to update all of the "current" version pointers in the `spec` repo: 32 | - Rename the `vX.X-draft` folder to `vX.X` 33 | - Update `/public/_redirects` line 2 to point spec.canvasprotocol.org to: 34 | ``` 35 | / https://github.com/ocwg/spec/blob/main/spec/vX.X/spec.md 302 36 | ``` 37 | - Excluding the `/spec` directory, find and replace the previous version with the new version (e.g., replace `v0.5` with `v0.6`) 38 | - This will update the Cookbook, Catalog, Examples, and README.md 39 | 40 | 6. **Create git tag**: After merging, create and push a git tag for the release (e.g., `git tag v0.6 && git push origin v0.6`) 41 | 42 | 7. **Update website**: Update the version numbers on the [canvasprotocol.org website](https://github.com/ocwg/canvasprotocol.org/blob/main/index.html) 43 | 44 | 8. **Announce**: Follow the communication steps in the [README Release Process](../../../README.md#release-process) to notify the community via GitHub Discussions, Buttondown newsletter, Discord, and social media 45 | 46 | ## How To Typeset Issues 47 | - Issues are temporary TODOs, which should be resolved before the final version. The `@@` makes them easy to search in an editor. 48 | 49 | **Issue Example:** 50 | 51 | - [ ] @@ This is an issue 52 | -------------------------------------------------------------------------------- /spec/v0.6.1-draft/how-to-spec.md: -------------------------------------------------------------------------------- 1 | # How To Edit The Spec 2 | 3 | 4 | 5 | - [How To Edit The Spec](#how-to-edit-the-spec) 6 | - [Release Instructions](#release-instructions) 7 | - [How To Typeset Issues](#how-to-typeset-issues) 8 | 9 | 10 | * All URIs should have the same, consistent structure 11 | * Property tables should follow these conventions 12 | - **required** is always bold, other entries are not 13 | - JSON types are set in `monospace` 14 | - OCIF types are linked to their definition 15 | - All examples start with `**Example:**` 16 | - Order of columns is always: Property, JSON Type, OCIF Type, Required, Contents, Default 17 | - Empty columns can be omitted 18 | 19 | ## Release Instructions 20 | 21 | See the [Release Process section in the README](../../../README.md#release-process) for the complete release checklist, including communication and announcement steps. 22 | 23 | ### Technical Release Workflow 24 | 25 | When creating a new version of the spec: 26 | 27 | 1. **Create draft version**: `cp` the current version's directory (`/spec/vX.X`) to the next version number with `-draft` appended 28 | 2. **Merge to main**: Merge the new directory to the `main` branch 29 | 3. **Create draft branch**: Create a new branch named `vX.X-draft` 30 | 4. **Iterate on draft**: Create pull requests against the new `vX.X-draft` folder until satisfied with release. These will have nice, small diffs that just highlight the major changes 31 | 32 | - Alternatively, you can leave the new directory on a branch and create pull requests against that branch until it's ready to be merged 33 | 34 | 5. **Finalize release**: Open a pull request to update all of the "current" version pointers in the `spec` repo: 35 | 36 | - Rename the `vX.X-draft` folder to `vX.X` 37 | - Update `/public/_redirects` line 2 to point spec.canvasprotocol.org to: 38 | ``` 39 | / https://github.com/ocwg/spec/blob/main/spec/vX.X/spec.md 302 40 | ``` 41 | - Excluding the `/spec` directory, find and replace the previous version with the new version (e.g., replace `v0.5` with `v0.6.1`) 42 | - This will update the Cookbook, Catalog, Examples, and README.md 43 | 44 | 6. **Create git tag**: After merging, create and push a git tag for the release (e.g., `git tag v0.6.1 && git push origin v0.6.1`) 45 | 46 | 7. **Update website**: Update the version numbers on the [canvasprotocol.org website](https://github.com/ocwg/canvasprotocol.org/blob/main/index.html) 47 | 48 | 8. **Announce**: Follow the communication steps in the [README Release Process](../../../README.md#release-process) to notify the community via GitHub Discussions, Buttondown newsletter, Discord, and social media 49 | 50 | ## How To Typeset Issues 51 | 52 | - Issues are temporary TODOs, which should be resolved before the final version. The `@@` makes them easy to search in an editor. 53 | 54 | **Issue Example:** 55 | 56 | - [ ] @@ This is an issue 57 | -------------------------------------------------------------------------------- /design/adr-000-TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | # These are optional metadata elements. Feel free to remove any of them. 3 | status: "{proposed | rejected | accepted | deprecated | … | superseded by ADR-0123" 4 | date: {YYYY-MM-DD when the decision was last updated} 5 | decision-makers: {list everyone involved in the decision} 6 | consulted: {list everyone whose opinions are sought (typically subject-matter experts); and with whom there is a two-way communication} 7 | informed: {list everyone who is kept up-to-date on progress; and with whom there is a one-way communication} 8 | --- 9 | 10 | # {short title, representative of solved problem and found solution} 11 | 12 | ## Context and Problem Statement 13 | 14 | {Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.} 15 | 16 | 17 | ## Decision Drivers 18 | 19 | * {decision driver 1, e.g., a force, facing concern, …} 20 | * {decision driver 2, e.g., a force, facing concern, …} 21 | * … 22 | 23 | ## Considered Options 24 | 25 | * {title of option 1} 26 | * {title of option 2} 27 | * {title of option 3} 28 | * … 29 | 30 | ## Decision Outcome 31 | 32 | Chosen option: "{title of option 1}", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | … | comes out best (see below)}. 33 | 34 | 35 | ### Consequences 36 | 37 | * Good, because {positive consequence, e.g., improvement of one or more desired qualities, …} 38 | * Bad, because {negative consequence, e.g., compromising one or more desired qualities, …} 39 | * … 40 | 41 | 42 | ### Confirmation 43 | 44 | {Describe how the implementation of/compliance with the ADR can/will be confirmed. Is the chosen design and its implementation in line with the decision? E.g., a design/code review or a test with a library such as ArchUnit can help validate this. Note that although we classify this element as optional, it is included in many ADRs.} 45 | 46 | 47 | ## Pros and Cons of the Options 48 | 49 | ### {title of option 1} 50 | 51 | 52 | {example | description | pointer to more information | …} 53 | 54 | * Good, because {argument a} 55 | * Good, because {argument b} 56 | 57 | * Neutral, because {argument c} 58 | * Bad, because {argument d} 59 | * … 60 | 61 | ### {title of other option} 62 | 63 | {example | description | pointer to more information | …} 64 | 65 | * Good, because {argument a} 66 | * Good, because {argument b} 67 | * Neutral, because {argument c} 68 | * Bad, because {argument d} 69 | * … 70 | 71 | 72 | ## More Information 73 | 74 | {You might want to provide additional evidence/confidence for the decision outcome here and/or document the team agreement on the decision and/or define when/how this decision the decision should be realized and if/when it should be re-visited. Links to other decisions and resources might appear here as well.} 75 | -------------------------------------------------------------------------------- /design/goals.md: -------------------------------------------------------------------------------- 1 | # Goals 2 | The high-level goals of the OCWG interchange format (OCIF). 3 | 4 | We want to allow a number of apps, including some not known at time of writing this spec, to be able to use the format to interchange data. 5 | There are several scenarios, which we want to support: 6 | 7 | - Allow an application A to export data, 8 | - Let application B import it, 9 | - Let application B see the data, which reasonably resembles what a user in A saw (goal: visual resemblance), 10 | - Let application B make changes to the data (i.e. add/removed/change things), export it again, 11 | - Let application A import the data, and have its special data still be there (goal: round-tripping). 12 | 13 | ## Visual Resemblance 14 | Data can be authored in any application, exported in OCIF, imported in another compliant application, and should visually resemble the original data. 15 | 16 | ## Round-Tripping 17 | An app A can export to OCIF and embed its native data structures within, so that any app B can work without, re-export, and allow app A to reconstruct its native data structures. 18 | 19 | - If a node has been deleted in app B, it should also be deleted for app A. 20 | - If a node has been added in B, no native data for app A can be present. However, app A can import the node and add some defaults. 21 | 22 | ## Useful out of the Box 23 | Balance Extensibility and Usefulness 24 | On one extreme, we could define an ultra-extensible format for nodes devoid any built-in structure or semantics, delegating all this to extensions. 25 | On the other hand, we could try to specify the super-set of all structures present in relevant apps. This would be a huge effort and likely to fail. 26 | 27 | Therefore, we must carefully balance these two forces and strive for a simple, extensible, but out-of-the-box useful format. 28 | 29 | ## Canvas Apps 30 | To state our goals, we need to define, what a canvas app is. 31 | Obviously, an application with the notion of an (infinite?) canvas is a canvas app. 32 | So a canvas app is characterized by 33 | 34 | - a visual (typically 2D) workspace, where nodes can be placed and connected. 35 | - Nodes are often rectangles, but can be of other shapes. 36 | - Nodes can contain text, scribbles, images, or other media. 37 | - Node can be connected by arrows or other connectors. 38 | 39 | However, there are also a lot of slightly more specialized apps, which should also be able to profit from OCIF: 40 | - A knowledge management tool (Hi Obsidian!) 41 | - A freeform drawing tool (Hi Excalidraw!) 42 | - A 3D modeling tool (Hi Aaron! Hi glTF!) 43 | - A flow-based programming tool (Hi Maikel!) 44 | - A mind map tool 45 | - A (semantic) wiki 46 | - A graph database / knowledge graph visualizer 47 | 48 | Less clearly in focus (although not impossible to export to OCIF) are: 49 | - A game level editor 50 | - A CAD tool 51 | - A UML tool 52 | - A music notation tool 53 | - A text editor 54 | - A spreadsheet 55 | - A video editor 56 | - A photo/raster graphics editor 57 | 58 | To make it even more clear, here is a list of apps known to us, which in our view are canvas apps: 59 | * Draw.io 60 | * Excalidraw 61 | * Heptabase 62 | * infinitymaps.io 63 | * Kinopio 64 | * Miro 65 | * Muse 66 | * Nodebook.io 67 | * Notion 68 | * Obsidian 69 | * Roam 70 | * TLDraw 71 | * Zwibbler 72 | * yEd 73 | 74 | And here is another list of apps inspected by us, to understand the space: 75 | * Blender Nodes 76 | * ReactFlow 77 | * Kosmik 78 | * Stately 79 | * Everyone Draw 80 | * Sage-3 81 | * Bezi 82 | * Dgrm 83 | * Vizio 84 | * GraphViz DOT 85 | * Breadcrumbs 86 | 87 | 88 | More are listed https://infinitecanvas.tools/ 89 | -------------------------------------------------------------------------------- /design/resource-structure.svg: -------------------------------------------------------------------------------- 1 | ResourceString idRepresentationString contentTypeBinaryContentbyte[] contentTextContentString contentordered list1..n 2 | -------------------------------------------------------------------------------- /spec/v0.4/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "OCIF core 0.4", 4 | "description": "The schema for the Open Canvas Interchange Format (OCIF) Core document structure.", 5 | "type": "object", 6 | "properties": { 7 | "ocif": { 8 | "type": "string", 9 | "description": "The URI of the OCIF schema" 10 | }, 11 | "nodes": { 12 | "type": "array", 13 | "description": "A list of nodes", 14 | "items": { 15 | "$ref": "#/$defs/node" 16 | } 17 | }, 18 | "relations": { 19 | "type": "array", 20 | "description": "A list of relations", 21 | "items": { 22 | "$ref": "#/$defs/relation" 23 | } 24 | }, 25 | "resources": { 26 | "type": "array", 27 | "description": "A list of resources", 28 | "items": { 29 | "$ref": "#/$defs/resource" 30 | } 31 | }, 32 | "schemas": { 33 | "type": "array", 34 | "description": "Declared schemas", 35 | "items": { 36 | "$ref": "#/$defs/schema" 37 | } 38 | } 39 | }, 40 | "$defs": { 41 | "node": { 42 | "type": "object", 43 | "description": "A node in the OCIF document", 44 | "properties": { 45 | "id": { 46 | "type": "string", 47 | "description": "A unique identifier for the node." 48 | }, 49 | "position": { 50 | "type": "array", 51 | "description": "Coordinate as (x,y) or (x,y,z).", 52 | "items": { 53 | "type": "number" 54 | } 55 | }, 56 | "size": { 57 | "type": "array", 58 | "description": "The size of the node per dimension.", 59 | "items": { 60 | "type": "number" 61 | } 62 | }, 63 | "resource": { 64 | "type": "string", 65 | "description": "The resource to display" 66 | }, 67 | "data": { 68 | "type": "array", 69 | "description": "Extended node data" 70 | }, 71 | "rotation": { 72 | "type": "number", 73 | "description": "+/- 360 degrees" 74 | }, 75 | "scale": { 76 | "type": "array", 77 | "description": "Scale factors to resize nodes", 78 | "items": { 79 | "type": "number" 80 | } 81 | } 82 | }, 83 | "required": ["id"] 84 | }, 85 | "relation": { 86 | "type": "object", 87 | "description": "A relation between nodes", 88 | "properties": { 89 | "id": { 90 | "type": "string", 91 | "description": "A unique identifier for the relation." 92 | }, 93 | "data": { 94 | "type": "array", 95 | "description": "Additional data for the relation." 96 | } 97 | }, 98 | "required": ["id"] 99 | }, 100 | "resource": { 101 | "type": "object", 102 | "description": "A resource in the OCIF document", 103 | "properties": { 104 | "id": { 105 | "type": "string", 106 | "description": "A unique identifier for the resource." 107 | }, 108 | "representations": { 109 | "type": "array", 110 | "description": "A list of representations of the resource.", 111 | "items": { 112 | "$ref": "#/$defs/representation" 113 | } 114 | } 115 | }, 116 | "required": ["id", "representations"] 117 | }, 118 | "representation": { 119 | "type": "object", 120 | "description": "A representation of a resource. Either content or location MUST be present. If content is used, location must be left out and vice versa.", 121 | "properties": { 122 | "location": { 123 | "type": "string", 124 | "description": "The storage location for the resource. This can be a relative URI for an external resource or an absolute URI for a remote resource. If a data: URI is used, the content and mime-type properties are implicitly defined already. Values in content and mime-type are ignored." 125 | }, 126 | "mime-type": { 127 | "type": "string", 128 | "description": "The IANA MIME Type of the resource." 129 | }, 130 | "content": { 131 | "type": "string", 132 | "description": "The content of the resource. This is the actual data of the resource as a string. Can be base64-encoded." 133 | } 134 | } 135 | }, 136 | "schema": { 137 | "type": "object", 138 | "description": "A schema in the OCIF document", 139 | "properties": { 140 | "uri": { 141 | "type": "string", 142 | "description": "The URI of the schema, Identifier (and location) of the schema." 143 | }, 144 | "schema": { 145 | "type": "object", 146 | "description": "The actual JSON schema as a JSON object." 147 | }, 148 | "location": { 149 | "type": "string", 150 | "description": "The storage location for the schema." 151 | }, 152 | "name": { 153 | "type": "string", 154 | "description": "An optional short name for the schema." 155 | } 156 | }, 157 | "required": ["uri"] 158 | } 159 | }, 160 | "required": ["ocif"] 161 | } 162 | -------------------------------------------------------------------------------- /spec/v0.1/schema/schema.combined.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "open-canvas.schema.json", 4 | "title": "Open Canvas", 5 | "type": "object", 6 | "description": "The root object for a open-canvas file", 7 | "required": ["schema_version", "nodes"], 8 | "properties": { 9 | "schema_version": { 10 | "type": "string" 11 | }, 12 | "nodes": { 13 | "type": "array", 14 | "description": "A list of nodes.", 15 | "items": { 16 | "$ref": "#/$defs/node.schema.json" 17 | } 18 | }, 19 | "relations": { 20 | "type": "array", 21 | "description": "A list of relations.", 22 | "items": { 23 | "$ref": "#/$defs/relation.schema.json" 24 | } 25 | }, 26 | "schemas": { 27 | "type": "object", 28 | "description": "A dictionary of extended schemas", 29 | "$ref": "#/$defs/schemas.schema.json" 30 | }, 31 | "resources": { 32 | "type": "object", 33 | "description": "A dictionary of resources which can be referenced by nodes and relations." 34 | } 35 | }, 36 | "$defs": { 37 | "relation.schema.json": { 38 | "$schema": "https://json-schema.org/draft/2020-12/schema", 39 | "$id": "relation.schema.json", 40 | "title": "relation", 41 | "type": "object", 42 | "description": "relation between nodes", 43 | "required": ["id", "name", "properties"], 44 | "properties": { 45 | "name": { 46 | "type": "string" 47 | }, 48 | "id": { 49 | "type": "string" 50 | }, 51 | "properties": { 52 | "type": "array", 53 | "items": { 54 | "anyOf": [ 55 | { 56 | "type": "object", 57 | "properties": { 58 | "schema": { 59 | "enum": ["@ocwg/set"] 60 | }, 61 | "schema_version": { 62 | "type": "string" 63 | }, 64 | "members": { 65 | "type": "array", 66 | "items": { 67 | "type": "string" 68 | } 69 | } 70 | }, 71 | "required": ["schema", "schema_version", "members"] 72 | }, 73 | { 74 | "type": "object", 75 | "properties": { 76 | "schema": { 77 | "enum": ["@ocwg/edge"] 78 | }, 79 | "schema_version": { 80 | "type": "string" 81 | }, 82 | "from": { 83 | "type": "string" 84 | }, 85 | "to": { 86 | "type": "string" 87 | } 88 | }, 89 | "required": ["schema", "schema_version", "from", "to"] 90 | } 91 | ] 92 | } 93 | } 94 | } 95 | }, 96 | "schemas.schema.json": { 97 | "$schema": "https://json-schema.org/draft/2020-12/schema", 98 | "$id": "schemas.schema.json", 99 | "title": "schema", 100 | "type": "object", 101 | "description": "inline definition of extended node schemas", 102 | "required": [], 103 | "propertyNames": { 104 | "pattern": "^@" 105 | }, 106 | "patternProperties": { 107 | "^@": { 108 | "type": "object", 109 | "properties": { 110 | "$schema": { 111 | "type": "string" 112 | } 113 | }, 114 | "required": ["$schema"] 115 | } 116 | } 117 | }, 118 | "node.schema.json": { 119 | "$schema": "https://json-schema.org/draft/2020-12/schema", 120 | "$id": "node.schema.json", 121 | "required": ["id", "position"], 122 | "properties": { 123 | "id": { 124 | "type": "string" 125 | }, 126 | "position": { 127 | "type": "array", 128 | "items": { 129 | "anyOf": [ 130 | { 131 | "type": "number" 132 | } 133 | ] 134 | }, 135 | "minItems": 2, 136 | "maxItems": 3 137 | }, 138 | "size": { 139 | "type": "array", 140 | "items": { 141 | "anyOf": [ 142 | { 143 | "type": "number" 144 | } 145 | ] 146 | }, 147 | "minItems": 2, 148 | "maxItems": 3 149 | }, 150 | "rotation": { 151 | "type": "number" 152 | }, 153 | "properties": { 154 | "type": "array", 155 | "contains": { 156 | "type": "object", 157 | "properties": { 158 | "schema": { 159 | "type": "string" 160 | }, 161 | "schema_version": { 162 | "type": "string" 163 | } 164 | }, 165 | "required": ["schema", "schema_version"] 166 | }, 167 | "items": { 168 | "anyOf": [ 169 | { 170 | "type": "object" 171 | } 172 | ] 173 | } 174 | } 175 | } 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /spec/v0.3/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "OCIF core 0.3", 4 | "description": "The schema for the Open Canvas Interchange Format (OCIF) Core document structure.", 5 | "type": "object", 6 | "properties": { 7 | "ocif": { 8 | "type": "string", 9 | "description": "The URI of the OCIF schema" 10 | }, 11 | "nodes": { 12 | "type": "array", 13 | "description": "A list of nodes", 14 | "items": { 15 | "$ref": "#/$defs/node" 16 | } 17 | }, 18 | "relations": { 19 | "type": "array", 20 | "description": "A list of relations", 21 | "items": { 22 | "$ref": "#/$defs/relation" 23 | } 24 | }, 25 | "resources": { 26 | "type": "array", 27 | "description": "A list of resources", 28 | "items": { 29 | "$ref": "#/$defs/resource" 30 | } 31 | }, 32 | "schemas": { 33 | "type": "array", 34 | "description": "Declared schemas", 35 | "items": { 36 | "$ref": "#/$defs/schema" 37 | } 38 | } 39 | }, 40 | "$defs": { 41 | "node": { 42 | "type": "object", 43 | "description": "A node in the OCIF document", 44 | "properties": { 45 | "id": { 46 | "type": "string", 47 | "description": "A unique identifier for the node." 48 | }, 49 | "position": { 50 | "type": "array", 51 | "description": "Coordinate as (x,y) or (x,y,z).", 52 | "items": { 53 | "type": "number" 54 | } 55 | }, 56 | "size": { 57 | "type": "array", 58 | "description": "The size of the node per dimension.", 59 | "items": { 60 | "type": "number" 61 | } 62 | }, 63 | "resource": { 64 | "type": "string", 65 | "description": "The resource to display" 66 | }, 67 | "data": { 68 | "type": "array", 69 | "description": "Extended node data" 70 | }, 71 | "rotation": { 72 | "type": "number", 73 | "description": "+/- 360 degrees" 74 | }, 75 | "scale": { 76 | "type": "array", 77 | "description": "Scale factors to resize nodes", 78 | "items": { 79 | "type": "number" 80 | } 81 | } 82 | }, 83 | "required": [ 84 | "id" 85 | ] 86 | }, 87 | "relation": { 88 | "type": "object", 89 | "description": "A relation between nodes", 90 | "properties": { 91 | "id": { 92 | "type": "string", 93 | "description": "A unique identifier for the relation." 94 | }, 95 | "data": { 96 | "type": "array", 97 | "description": "Additional data for the relation." 98 | } 99 | }, 100 | "required": [ 101 | "id" 102 | ] 103 | }, 104 | "resource": { 105 | "type": "object", 106 | "description": "A resource in the OCIF document", 107 | "properties": { 108 | "id": { 109 | "type": "string", 110 | "description": "A unique identifier for the resource." 111 | }, 112 | "representations": { 113 | "type": "array", 114 | "description": "A list of representations of the resource.", 115 | "items": { 116 | "$ref": "#/$defs/representation" 117 | } 118 | } 119 | }, 120 | "required": [ 121 | "id", 122 | "representations" 123 | ] 124 | }, 125 | "representation": { 126 | "type": "object", 127 | "description": "A representation of a resource. Either content or location MUST be present. If content is used, location must be left out and vice versa.", 128 | "properties": { 129 | "location": { 130 | "type": "string", 131 | "description": "The storage location for the resource. This can be a relative URI for an external resource or an absolute URI for a remote resource. If a data: URI is used, the content and mime-type properties are implicitly defined already. Values in content and mime-type are ignored." 132 | }, 133 | "mime-type": { 134 | "type": "string", 135 | "description": "The IANA MIME Type of the resource." 136 | }, 137 | "content": { 138 | "type": "string", 139 | "description": "The content of the resource. This is the actual data of the resource as a string. Can be base64-encoded." 140 | } 141 | } 142 | }, 143 | "schema": { 144 | "type": "object", 145 | "description": "A schema in the OCIF document", 146 | "properties": { 147 | "uri": { 148 | "type": "string", 149 | "description": "The URI of the schema, Identifier (and location) of the schema." 150 | }, 151 | "schema": { 152 | "type": "object", 153 | "description": "The actual JSON schema as a JSON object." 154 | }, 155 | "location": { 156 | "type": "string", 157 | "description": "The storage location for the schema." 158 | }, 159 | "name": { 160 | "type": "string", 161 | "description": "An optional short name for the schema." 162 | } 163 | }, 164 | "required": [ 165 | "uri" 166 | ] 167 | } 168 | }, 169 | "required": [ 170 | "ocif" 171 | ] 172 | } 173 | -------------------------------------------------------------------------------- /spec/v0.5/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "OCIF core 0.5", 4 | "description": "The schema for the Open Canvas Interchange Format (OCIF) Core document structure.", 5 | "type": "object", 6 | "properties": { 7 | "ocif": { 8 | "type": "string", 9 | "description": "The URI of the OCIF schema" 10 | }, 11 | "nodes": { 12 | "type": "array", 13 | "description": "A list of nodes", 14 | "items": { 15 | "$ref": "#/$defs/node" 16 | } 17 | }, 18 | "relations": { 19 | "type": "array", 20 | "description": "A list of relations", 21 | "items": { 22 | "$ref": "#/$defs/relation" 23 | } 24 | }, 25 | "resources": { 26 | "type": "array", 27 | "description": "A list of resources", 28 | "items": { 29 | "$ref": "#/$defs/resource" 30 | } 31 | }, 32 | "schemas": { 33 | "type": "array", 34 | "description": "Declared schemas", 35 | "items": { 36 | "$ref": "#/$defs/schema" 37 | } 38 | } 39 | }, 40 | "$defs": { 41 | "node": { 42 | "type": "object", 43 | "description": "A node in the OCIF document", 44 | "properties": { 45 | "id": { 46 | "type": "string", 47 | "description": "A unique identifier for the node." 48 | }, 49 | "position": { 50 | "type": "array", 51 | "description": "Coordinate as (x,y) or (x,y,z).", 52 | "items": { 53 | "type": "number" 54 | } 55 | }, 56 | "size": { 57 | "type": "array", 58 | "description": "The size of the node per dimension.", 59 | "items": { 60 | "type": "number" 61 | } 62 | }, 63 | "resource": { 64 | "type": "string", 65 | "description": "The resource to display" 66 | }, 67 | "resourceFit": { 68 | "type": "string", 69 | "description": "Fitting resource in item", 70 | "enum": ["none", "containX", "containY", "contain", "cover", "fill", "tile"] 71 | }, 72 | "data": { 73 | "type": "array", 74 | "description": "Extended node data" 75 | }, 76 | "rotation": { 77 | "type": "number", 78 | "description": "+/- 360 degrees" 79 | }, 80 | "relation": { 81 | "type": "string", 82 | "description": "ID of a relation" 83 | } 84 | }, 85 | "required": ["id"] 86 | }, 87 | "relation": { 88 | "type": "object", 89 | "description": "A relation between nodes", 90 | "properties": { 91 | "id": { 92 | "type": "string", 93 | "description": "A unique identifier for the relation." 94 | }, 95 | "data": { 96 | "type": "array", 97 | "description": "Additional data for the relation." 98 | }, 99 | "node": { 100 | "type": "string", 101 | "description": "ID of a visual node, which represents this relation." 102 | } 103 | }, 104 | "required": ["id"] 105 | }, 106 | "resource": { 107 | "type": "object", 108 | "description": "A resource in the OCIF document", 109 | "properties": { 110 | "id": { 111 | "type": "string", 112 | "description": "A unique identifier for the resource." 113 | }, 114 | "representations": { 115 | "type": "array", 116 | "description": "A list of representations of the resource.", 117 | "items": { 118 | "$ref": "#/$defs/representation" 119 | } 120 | } 121 | }, 122 | "required": ["id", "representations"] 123 | }, 124 | "representation": { 125 | "type": "object", 126 | "description": "A representation of a resource. Either content or location MUST be present. If content is used, location must be left out and vice versa.", 127 | "properties": { 128 | "location": { 129 | "type": "string", 130 | "description": "The storage location for the resource. This can be a relative URI for an external resource or an absolute URI for a remote resource. If a data: URI is used, the content and MIME-type properties are implicitly defined already. Values in content and mime-type are ignored." 131 | }, 132 | "mimeType": { 133 | "type": "string", 134 | "description": "The IANA MIME Type of the resource." 135 | }, 136 | "content": { 137 | "type": "string", 138 | "description": "The content of the resource. This is the actual data of the resource as a string. Can be base64-encoded." 139 | } 140 | } 141 | }, 142 | "schema": { 143 | "type": "object", 144 | "description": "A schema in the OCIF document", 145 | "properties": { 146 | "uri": { 147 | "type": "string", 148 | "description": "The URI of the schema, Identifier (and location) of the schema." 149 | }, 150 | "schema": { 151 | "type": "object", 152 | "description": "The actual JSON schema as a JSON object." 153 | }, 154 | "location": { 155 | "type": "string", 156 | "description": "The storage location for the schema." 157 | }, 158 | "name": { 159 | "type": "string", 160 | "description": "An optional short name for the schema." 161 | } 162 | }, 163 | "required": ["uri"] 164 | } 165 | }, 166 | "required": ["ocif"] 167 | } 168 | -------------------------------------------------------------------------------- /spec/v0.6/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "OCIF core 0.5", 4 | "description": "The schema for the Open Canvas Interchange Format (OCIF) Core document structure.", 5 | "type": "object", 6 | "properties": { 7 | "ocif": { 8 | "type": "string", 9 | "description": "The URI of the OCIF schema" 10 | }, 11 | "nodes": { 12 | "type": "array", 13 | "description": "A list of nodes", 14 | "items": { 15 | "$ref": "#/$defs/node" 16 | } 17 | }, 18 | "relations": { 19 | "type": "array", 20 | "description": "A list of relations", 21 | "items": { 22 | "$ref": "#/$defs/relation" 23 | } 24 | }, 25 | "resources": { 26 | "type": "array", 27 | "description": "A list of resources", 28 | "items": { 29 | "$ref": "#/$defs/resource" 30 | } 31 | }, 32 | "schemas": { 33 | "type": "array", 34 | "description": "Declared schemas", 35 | "items": { 36 | "$ref": "#/$defs/schema" 37 | } 38 | } 39 | }, 40 | "$defs": { 41 | "node": { 42 | "type": "object", 43 | "description": "A node in the OCIF document", 44 | "properties": { 45 | "id": { 46 | "type": "string", 47 | "description": "A unique identifier for the node." 48 | }, 49 | "position": { 50 | "type": "array", 51 | "description": "Coordinate as (x,y) or (x,y,z).", 52 | "items": { 53 | "type": "number" 54 | } 55 | }, 56 | "size": { 57 | "type": "array", 58 | "description": "The size of the node per dimension.", 59 | "items": { 60 | "type": "number" 61 | } 62 | }, 63 | "resource": { 64 | "type": "string", 65 | "description": "The resource to display" 66 | }, 67 | "resourceFit": { 68 | "type": "string", 69 | "description": "Fitting resource in item", 70 | "enum": ["none", "containX", "containY", "contain", "cover", "fill", "tile"] 71 | }, 72 | "data": { 73 | "type": "array", 74 | "description": "Extended node data" 75 | }, 76 | "rotation": { 77 | "type": "number", 78 | "description": "+/- 360 degrees" 79 | }, 80 | "relation": { 81 | "type": "string", 82 | "description": "ID of a relation" 83 | } 84 | }, 85 | "required": ["id"] 86 | }, 87 | "relation": { 88 | "type": "object", 89 | "description": "A relation between nodes", 90 | "properties": { 91 | "id": { 92 | "type": "string", 93 | "description": "A unique identifier for the relation." 94 | }, 95 | "data": { 96 | "type": "array", 97 | "description": "Additional data for the relation." 98 | }, 99 | "node": { 100 | "type": "string", 101 | "description": "ID of a visual node, which represents this relation." 102 | } 103 | }, 104 | "required": ["id"] 105 | }, 106 | "resource": { 107 | "type": "object", 108 | "description": "A resource in the OCIF document", 109 | "properties": { 110 | "id": { 111 | "type": "string", 112 | "description": "A unique identifier for the resource." 113 | }, 114 | "representations": { 115 | "type": "array", 116 | "description": "A list of representations of the resource.", 117 | "items": { 118 | "$ref": "#/$defs/representation" 119 | } 120 | } 121 | }, 122 | "required": ["id", "representations"] 123 | }, 124 | "representation": { 125 | "type": "object", 126 | "description": "A representation of a resource. Either content or location MUST be present. If content is used, location must be left out and vice versa.", 127 | "properties": { 128 | "location": { 129 | "type": "string", 130 | "description": "The storage location for the resource. This can be a relative URI for an external resource or an absolute URI for a remote resource. If a data: URI is used, the content and MIME-type properties are implicitly defined already. Values in content and mime-type are ignored." 131 | }, 132 | "mimeType": { 133 | "type": "string", 134 | "description": "The IANA MIME Type of the resource." 135 | }, 136 | "content": { 137 | "type": "string", 138 | "description": "The content of the resource. This is the actual data of the resource as a string. Can be base64-encoded." 139 | } 140 | }, 141 | "oneOf": [ 142 | { 143 | "required": ["content"] 144 | }, 145 | { 146 | "required": ["location"] 147 | } 148 | ] 149 | }, 150 | "schema": { 151 | "type": "object", 152 | "description": "A schema in the OCIF document", 153 | "properties": { 154 | "uri": { 155 | "type": "string", 156 | "description": "The URI of the schema, Identifier (and location) of the schema." 157 | }, 158 | "schema": { 159 | "type": "object", 160 | "description": "The actual JSON schema as a JSON object." 161 | }, 162 | "location": { 163 | "type": "string", 164 | "description": "The storage location for the schema." 165 | }, 166 | "name": { 167 | "type": "string", 168 | "description": "An optional short name for the schema." 169 | } 170 | }, 171 | "required": ["uri"] 172 | } 173 | }, 174 | "required": ["ocif"] 175 | } 176 | -------------------------------------------------------------------------------- /catalog.md: -------------------------------------------------------------------------------- 1 | # OCIF Extensions List 2 | 3 | This document lists known OCIF extensions ([see OCIF spec](spec/v0.6/spec.md)) as service to the community. 4 | Getting listed in this file is not a requirement for an extension to be used in an OCIF document. 5 | Extensions can be defined within an OCIF document, without an external schema file. 6 | It is common practice for reusable extensions to publish a JSON schema file and a README. 7 | All this is described in the OCIF spec. 8 | 9 | Listing in this file is not an endorsement of an extension. 10 | This list is merely a convenience for developers and users to find extensions that might be useful for their use case. 11 | 12 | Mandatory fields in this catalog: 13 | 14 | - **URI**: The URI of the extension (its unique id). 15 | - **Schema**: Link to JSON schema. 16 | - Can be omitted if URI points to a JSON schema. 17 | - **Documentation**: A link to the documentation, describing the semantics and usage of the extension. 18 | 19 | Optional fields: 20 | 21 | - **Name**: The proposed short name of the extension. 22 | * [OCIF Extensions List](#ocif-extensions-list) 23 | * [Node Extensions](#node-extensions) 24 | * [Arrow Node](#arrow-node) 25 | * [Oval Node](#oval-node) 26 | * [Path Node](#path-node) 27 | * [Rectangle Node](#rectangle-node) 28 | * [Ports Node](#ports-node) 29 | * [Anchored Node](#anchored-node) 30 | * [Text Style Node](#text-style-node) 31 | * [Transforms Node](#transforms-node) 32 | * [Relation Extensions](#relation-extensions) 33 | * [Edge Relation](#edge-relation) 34 | * [Group Relation](#group-relation) 35 | * [Hyperedge Relation](#hyperedge-relation) 36 | * [Parent-Child Relation](#parent-child-relation) 37 | * [Deprecated Extensions](#deprecated-extensions) 38 | peredge-relation) 39 | - [Parent-Child Relation](#parent-child-relation) 40 | - [Deprecated Extensions](#deprecated-extensions) 41 | 42 | 43 | # Node Extensions 44 | 45 | ## Arrow Node 46 | 47 | - Name: `@ocif/node/arrow` 48 | - URI: `https://spec.canvasprotocol.org/v0.5/core/arrow-node.json` 49 | - Doc: [spec/v0.6/spec.md#arrow](spec/v0.6/spec.md#arrow-extension) 50 | - Version: v0.5 51 | - Author: Open Canvas Working Group 52 | 53 | ## Oval Node 54 | 55 | - Name: `@ocif/node/oval` 56 | - URI: `https://spec.canvasprotocol.org/v0.5/core/oval-node.json` 57 | - Doc: [spec/v0.6/spec.md#oval](spec/v0.6/spec.md#oval-extension) 58 | - Version: v0.5 59 | - Author: Open Canvas Working Group 60 | 61 | ## Path Node 62 | 63 | - Name: `@ocif/node/path` 64 | - URI: `https://spec.canvasprotocol.org/v0.5/core/path-node.json` 65 | - Doc: [spec/v0.6/spec.md#path](spec/v0.6/spec.md#path-extension) 66 | - Version: v0.5 67 | - Author: Open Canvas Working Group 68 | 69 | ## Rectangle Node 70 | 71 | - Name: `@ocif/node/rect` 72 | - URI: `https://spec.canvasprotocol.org/v0.5/core/rect-node.json` 73 | - Doc: [spec/v0.6/spec.md#rectangle](spec/v0.6/spec.md#rectangle-extension) 74 | - Version: v0.5 75 | - Author: Open Canvas Working Group 76 | 77 | ## Ports Node 78 | 79 | - Name: `@ocif/node/ports` 80 | - URI: `https://spec.canvasprotocol.org/v0.5/extensions/ports-node.json` 81 | - Doc: [spec/v0.6/spec.md#ports-node](spec/v0.6/spec.md#ports-node-extension) 82 | - Version: v0.5 83 | - Author: Open Canvas Working Group 84 | 85 | ## Anchored Node 86 | 87 | - Name: `@ocif/node/anchored` 88 | - URI: `https://spec.canvasprotocol.org/v0.5/extensions/anchored-node.json` 89 | - Doc: [spec/v0.6/spec.md#anchored-node](spec/v0.6/spec.md#anchored-node-extension) 90 | - Version: v0.5 91 | - Author: Open Canvas Working Group 92 | 93 | ## Text Style Node 94 | 95 | - Name: `@ocif/node/textstyle` 96 | - URI: `https://spec.canvasprotocol.org/v0.5/extensions/textstyle-node.json` 97 | - Doc: [spec/v0.6/spec.md#text-style-node](spec/v0.6/spec.md#text-style-node-extension) 98 | - Version: v0.5 99 | - Author: Open Canvas Working Group 100 | 101 | ## Transforms Node 102 | 103 | - Name: `@ocif/node/transforms` 104 | - URI: `https://spec.canvasprotocol.org/v0.5/extensions/transforms-node.json` 105 | - Doc: [spec/v0.6/spec.md#transforms-node](spec/v0.6/spec.md#node-transforms-extension) 106 | - Version: v0.5 107 | - Author: Open Canvas Working Group 108 | 109 | # Relation Extensions 110 | 111 | ## Edge Relation 112 | 113 | - Name: `@ocif/rel/edge` 114 | - URI: `https://spec.canvasprotocol.org/v0.5/core/edge-rel.json` 115 | - Doc: [spec/v0.6/spec.md#edge-relation](spec/v0.6/spec.md#edge-relation-extension) 116 | - Version: v0.5 117 | - Author: Open Canvas Working Group 118 | 119 | ## Group Relation 120 | 121 | - Name: `@ocif/rel/group` 122 | - URI: `https://spec.canvasprotocol.org/v0.5/core/group-rel.json` 123 | - Doc: [spec/v0.6/spec.md#group-relation](spec/v0.6/spec.md#group-relation-extension) 124 | - Version: v0.5 125 | - Author: Open Canvas Working Group 126 | 127 | ## Hyperedge Relation 128 | 129 | - Name: `@ocif/rel/hyperedge` 130 | - URI: `https://spec.canvasprotocol.org/v0.5/extensions/hyperedge-rel.json` 131 | - Doc: [spec/v0.6/spec.md#hyperedge-relation](spec/v0.6/spec.md#hyperedge-relation-extension) 132 | - Version: v0.5 133 | - Author: Open Canvas Working Group 134 | 135 | ## Parent-Child Relation 136 | 137 | - Name: `@ocif/rel/parent-child` 138 | - URI: `https://spec.canvasprotocol.org/v0.5/extensions/parent-child-rel.json` 139 | - Doc: [spec/v0.6/spec.md#parent-child-relation](spec/v0.6/spec.md#parent-child-relation-extension) 140 | - Version: v0.5 141 | - Author: Open Canvas Working Group 142 | 143 | # Deprecated Extensions 144 | 145 | The authors of these extensions have deprecated them. Often this means that the functionality has been replaced by a newer version of the extension. 146 | 147 | - `https://spec.canvasprotocol.org/node/ports/0.2` 148 | - `https://spec.canvasprotocol.org/rel/hyperedge/0.2` 149 | - `https://spec.canvasprotocol.org/rel/parent-child/0.2` 150 | - `https://spec.canvasprotocol.org/rel/relative/0.2` 151 | - `@ocif/rel/set` - functionality merged into `@ocif/rel/group` in v0.5 152 | - `@ocif/node/relative` - functionality moved to `@ocif/node/transforms` in v0.5 153 | -------------------------------------------------------------------------------- /cookbook.md: -------------------------------------------------------------------------------- 1 | # OCIF Cookbook 2 | **Open Canvas Interchange Format (OCIF) Cookbook** 3 | 4 | 5 | * [OCIF Cookbook](#ocif-cookbook) 6 | * [Canvas App Users](#canvas-app-users) 7 | * [Canvas Editing in a Text Editor](#canvas-editing-in-a-text-editor) 8 | * [The Simple Sticky Note 📝](#the-simple-sticky-note-) 9 | * [Canvas App Developers](#canvas-app-developers) 10 | * [Using OCIF Core](#using-ocif-core) 11 | * [Using OCIF Extensions](#using-ocif-extensions) 12 | * [Transclusion](#transclusion) 13 | * [Developing OCIF Extensions](#developing-ocif-extensions) 14 | 15 | 16 | A collection of examples and best practices (recipes) for using OCIF. 17 | It is intended to help developers and designers to understand how to use OCIF in their applications. 18 | 19 | ## Canvas App Users 20 | 21 | Your canvas app might have either an OCIF export option or an OCIF import option. 22 | It might even use OCIF as its native format. 23 | You can export OCIF from one canvas app and import it back into another canvas app. 24 | This way, you can share your work with others or use it in different apps. 25 | 26 | OCIF defines no mechanism how the file is transported. 27 | If your canvas app does not support OCIF, consider opening a ticket with the developers to request support for OCIF. 28 | Or be the developer yourself and use the OCIF documentation to implement support for OCIF in your favorite app. 29 | 30 | ## Canvas Editing in a Text Editor 31 | Here are some examples, how to create an OCIF file by hand. This helps to understand OCIF and lets you learn how to edit and tweak existing OCIF files, created by apps. 32 | 33 | ### The Simple Sticky Note 📝 34 | Scenario: You want to represent a single yellow sticky note on a digital canvas with the text "Buy milk". This is the most basic "Hello, World!" example for OCIF. 35 | 36 | OCIF Concepts Illustrated: 37 | 38 | - nodes: A single visual item on the canvas. 39 | - resources: The content for the node (the text). 40 | - position: Where the note is placed on the canvas. 41 | - Core Structure: The basic boilerplate of an OCIF file. 42 | 43 | ```json 44 | { 45 | "ocif": "https://canvasprotocol.org/ocif/v0.6", 46 | "nodes": [ 47 | { 48 | "id": "sticky-note-milk", 49 | "position": [ 100, 100 ], 50 | "size": [ 150, 75 ], 51 | "resource": "res_milk", 52 | "data": [ 53 | { 54 | "type": "@ocif/node/rect", 55 | "fillColor": "#FFFF00" 56 | } 57 | ] 58 | } 59 | ], 60 | "resources": [ 61 | { 62 | "id": "res_milk", 63 | "representations": [ 64 | { 65 | "mimeType": "text/plain", 66 | "content": "Buy milk" 67 | } 68 | ] 69 | } 70 | ] 71 | } 72 | ``` 73 | 74 | Example Breakdown: 75 | - The file has a nodes array with one node object. This node has an id, a position like [100, 100], and a resource property pointing to the ID `res_milk`. 76 | - The resources array contains an object with the id: `res_milk` and its representations holding the inline content: `{"mimeType": "text/plain", "content": "Buy milk"}`. 77 | - A `@ocif/node/rect` extension has been added to the node's data to give it a yellow `fillColor`. 78 | 79 | 80 | 81 | 82 | ## Canvas App Developers 83 | 84 | If you are developing a canvas app, consider supporting OCIF as an import and export format. 85 | This way, users can share their work with others or use it in different apps. 86 | 87 | ### Using OCIF Core 88 | 89 | The OCIF core provides the building blocks which the working group found to be shared by many canvas apps. 90 | Technically, the OCIF core also uses the node and relation extension mechanism. 91 | 92 | ### Using OCIF Extensions 93 | 94 | Usage of non-core extensions is technically almost identical to the core extensions. 95 | The only difference is the schema. 96 | Non-core extensions SHOULD provide their schema, either within the OCIF document or at the extension URI. 97 | Extensions can be defined and used in the same OCIF document, without any prior announcement or registration process. 98 | 99 | ### Transclusion 100 | 101 | **Goal** \ 102 | One visual node shows the [resource](spec/v0.4/spec.md#resources) of another visual node. 103 | This concept is also known as _transclusion_. 104 | 105 | We have two roles: 106 | 107 | - An _original_ node -- just any existing visual node, and 108 | - a _holder_ node -- the node showing the content of the original node. 109 | 110 | **Solution** \ 111 | We use a [parent-child relation](spec/v0.6/spec.md#parent-child-relation-extension) to indicate that the holder node is a child of the original node. 112 | The original node (the parent) is included in the holder node (the child). 113 | Or in other words: The child inherits the properties (the resource) of the parent. 114 | 115 | **Example: A shows content of B** \ 116 | Given two nodes A and B, we want node A to show the content (resource) of B, but at another position on the canvas. 117 | This is a special case of the parent-child relation, where the child inherits the _resource_ property of the parent. 118 | We use the `inherit` flag to indicate that the child should inherit the properties of the parent. 119 | Node A could additionally specify a different size if it wants to show the content of node B in a different size. 120 | 121 | ```json 122 | { 123 | "nodes": [ 124 | { 125 | "id": "A", 126 | "position": [100, 100] 127 | }, 128 | { 129 | "id": "B", 130 | "position": [300, 200], 131 | "size": [20, 20], 132 | "resource": "r1" 133 | } 134 | ], 135 | "relations": [ 136 | { 137 | "type": "@ocif/rel/parent-child", 138 | "parent": "B", 139 | "child": "A", 140 | "inherit": true 141 | } 142 | ], 143 | "resources": [ 144 | { 145 | "id": "r1", 146 | "representations": [ 147 | { 148 | "mime-type": "text/plain", 149 | "content": "Hello, World!" 150 | } 151 | ] 152 | } 153 | ] 154 | } 155 | ``` 156 | 157 | ### Developing OCIF Extensions 158 | 159 | OCIF extensions ([see spec](https://spec.canvasprotocol.org/)) allow defining custom "property bags" for visual nodes or relations. 160 | If the built-in types (technically also using the extension mechanism) are not enough, you should define your own extension. 161 | Keep in mind that nodes and relations can use several extensions at the same time, so only the missing pieces need to be defined in your extension. 162 | The spec defines how to define custom extensions. 163 | -------------------------------------------------------------------------------- /spec/v0.6.1-draft/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "OCIF core v0.6.1", 4 | "description": "The schema for the Open Canvas Interchange Format (OCIF) Core document structure.", 5 | "type": "object", 6 | "allOf": [{ "$ref": "#/$defs/item" }], 7 | "properties": { 8 | "ocif": { 9 | "type": "string", 10 | "description": "The URI of the OCIF schema" 11 | }, 12 | "nodes": { 13 | "type": "array", 14 | "description": "A list of nodes", 15 | "items": { 16 | "$ref": "#/$defs/node" 17 | } 18 | }, 19 | "relations": { 20 | "type": "array", 21 | "description": "A list of relations", 22 | "items": { 23 | "$ref": "#/$defs/relation" 24 | } 25 | }, 26 | "resources": { 27 | "type": "array", 28 | "description": "A list of resources", 29 | "items": { 30 | "$ref": "#/$defs/resource" 31 | } 32 | }, 33 | "schemas": { 34 | "type": "array", 35 | "description": "Declared schemas", 36 | "items": { 37 | "$ref": "#/$defs/schema" 38 | } 39 | } 40 | }, 41 | "$defs": { 42 | "entity": { 43 | "type": "object", 44 | "description": "Abstract base type for all extensible entities in the OCIF document (extension data and comments).", 45 | "properties": { 46 | "data": { 47 | "type": "array", 48 | "description": "Data for the entity, including data supplied by extension schemas." 49 | }, 50 | "comment": { 51 | "type": "string", 52 | "description": "A comment or description of the entity. This MUST NOT be used for any functional purpose by software that processes OCIF files, it is only allowed to be used for the purpose of annotating an example OCIF file with additional information when reading the raw text of the file manually, like a comment in a programming language." 53 | } 54 | } 55 | }, 56 | "item": { 57 | "type": "object", 58 | "description": "Abstract base type for identified items in the OCIF document (includes entity properties).", 59 | "allOf": [{ "$ref": "#/$defs/entity" }], 60 | "properties": { 61 | "id": { 62 | "type": "string", 63 | "description": "A unique identifier for the item." 64 | } 65 | } 66 | }, 67 | "node": { 68 | "type": "object", 69 | "description": "A node in the OCIF document", 70 | "allOf": [{ "$ref": "#/$defs/item" }], 71 | "properties": { 72 | "position": { 73 | "type": "array", 74 | "description": "Coordinate as (x,y) or (x,y,z).", 75 | "items": { 76 | "type": "number" 77 | } 78 | }, 79 | "size": { 80 | "type": "array", 81 | "description": "The size of the node per dimension.", 82 | "items": { 83 | "type": "number" 84 | } 85 | }, 86 | "resource": { 87 | "type": "string", 88 | "description": "The resource to display" 89 | }, 90 | "resourceFit": { 91 | "type": "string", 92 | "description": "Fitting resource in item", 93 | "enum": ["none", "containX", "containY", "contain", "cover", "fill", "tile"] 94 | }, 95 | "rotation": { 96 | "type": "number", 97 | "description": "+/- 360 degrees" 98 | }, 99 | "relation": { 100 | "type": "string", 101 | "description": "ID of a relation" 102 | } 103 | }, 104 | "required": ["id"] 105 | }, 106 | "relation": { 107 | "type": "object", 108 | "description": "A relation between nodes", 109 | "allOf": [{ "$ref": "#/$defs/item" }], 110 | "properties": { 111 | "node": { 112 | "type": "string", 113 | "description": "ID of a visual node, which represents this relation." 114 | } 115 | }, 116 | "required": ["id"] 117 | }, 118 | "resource": { 119 | "type": "object", 120 | "description": "A resource in the OCIF document", 121 | "allOf": [{ "$ref": "#/$defs/item" }], 122 | "properties": { 123 | "representations": { 124 | "type": "array", 125 | "description": "A list of representations of the resource.", 126 | "items": { 127 | "$ref": "#/$defs/representation" 128 | } 129 | } 130 | }, 131 | "required": ["id", "representations"] 132 | }, 133 | "representation": { 134 | "type": "object", 135 | "description": "A representation of a resource. Either content or location MUST be present. If content is used, location must be left out and vice versa.", 136 | "allOf": [{ "$ref": "#/$defs/entity" }], 137 | "properties": { 138 | "location": { 139 | "type": "string", 140 | "description": "The storage location for the resource. This can be a relative URI for an external resource or an absolute URI for a remote resource. If a data: URI is used, the content and MIME-type properties are implicitly defined already. Values in content and mime-type are ignored." 141 | }, 142 | "mimeType": { 143 | "type": "string", 144 | "description": "The IANA MIME Type of the resource." 145 | }, 146 | "content": { 147 | "type": "string", 148 | "description": "The content of the resource. This is the actual data of the resource as a string. Can be base64-encoded." 149 | } 150 | }, 151 | "oneOf": [ 152 | { 153 | "required": ["content"] 154 | }, 155 | { 156 | "required": ["location"] 157 | } 158 | ] 159 | }, 160 | "schema": { 161 | "type": "object", 162 | "description": "A schema in the OCIF document", 163 | "properties": { 164 | "uri": { 165 | "type": "string", 166 | "description": "The URI of the schema, Identifier (and location) of the schema." 167 | }, 168 | "schema": { 169 | "type": "object", 170 | "description": "The actual JSON schema as a JSON object." 171 | }, 172 | "location": { 173 | "type": "string", 174 | "description": "The storage location for the schema." 175 | }, 176 | "name": { 177 | "type": "string", 178 | "description": "An optional short name for the schema." 179 | } 180 | }, 181 | "required": ["uri"] 182 | } 183 | }, 184 | "required": ["ocif"] 185 | } 186 | -------------------------------------------------------------------------------- /example/4x4-rect-node-grid.ocif.json: -------------------------------------------------------------------------------- 1 | { 2 | "ocif": "https://canvasprotocol.org/ocif/v0.5", 3 | "nodes": [ 4 | { 5 | "id": "n1", 6 | "position": [0, 0], 7 | "size": [150, 150], 8 | "resource": "r1", 9 | "data": [ 10 | { 11 | "type": "@ocif/node/rect", 12 | "strokeColor": "#FF5733", 13 | "strokeWidth": 4, 14 | "fillColor": "#C13D26" 15 | } 16 | ] 17 | }, 18 | { 19 | "id": "n2", 20 | "position": [160, 0], 21 | "size": [150, 150], 22 | "resource": "r2", 23 | "data": [ 24 | { 25 | "type": "@ocif/node/rect", 26 | "strokeColor": "#33FF57", 27 | "strokeWidth": 4, 28 | "fillColor": "#269E3D" 29 | } 30 | ] 31 | }, 32 | { 33 | "id": "n3", 34 | "position": [320, 0], 35 | "size": [150, 150], 36 | "resource": "r3", 37 | "data": [ 38 | { 39 | "type": "@ocif/node/rect", 40 | "strokeColor": "#3357FF", 41 | "strokeWidth": 4, 42 | "fillColor": "#263DA6" 43 | } 44 | ] 45 | }, 46 | { 47 | "id": "n4", 48 | "position": [480, 0], 49 | "size": [150, 150], 50 | "resource": "r4", 51 | "data": [ 52 | { 53 | "type": "@ocif/node/rect", 54 | "strokeColor": "#FF33A1", 55 | "strokeWidth": 4, 56 | "fillColor": "#A1266E" 57 | } 58 | ] 59 | }, 60 | { 61 | "id": "n5", 62 | "position": [0, 160], 63 | "size": [150, 150], 64 | "resource": "r5", 65 | "data": [ 66 | { 67 | "type": "@ocif/node/rect", 68 | "strokeColor": "#FFC733", 69 | "strokeWidth": 4, 70 | "fillColor": "#C19B26" 71 | } 72 | ] 73 | }, 74 | { 75 | "id": "n6", 76 | "position": [160, 160], 77 | "size": [150, 150], 78 | "resource": "r6", 79 | "data": [ 80 | { 81 | "type": "@ocif/node/rect", 82 | "strokeColor": "#8D33FF", 83 | "strokeWidth": 4, 84 | "fillColor": "#6626A6" 85 | } 86 | ] 87 | }, 88 | { 89 | "id": "n7", 90 | "position": [320, 160], 91 | "size": [150, 150], 92 | "resource": "r7", 93 | "data": [ 94 | { 95 | "type": "@ocif/node/rect", 96 | "strokeColor": "#33FFF6", 97 | "strokeWidth": 4, 98 | "fillColor": "#269EB5" 99 | } 100 | ] 101 | }, 102 | { 103 | "id": "n8", 104 | "position": [480, 160], 105 | "size": [150, 150], 106 | "resource": "r8", 107 | "data": [ 108 | { 109 | "type": "@ocif/node/rect", 110 | "strokeColor": "#FFB733", 111 | "strokeWidth": 4, 112 | "fillColor": "#C18A26" 113 | } 114 | ] 115 | }, 116 | { 117 | "id": "n9", 118 | "position": [0, 320], 119 | "size": [150, 150], 120 | "resource": "r9", 121 | "data": [ 122 | { 123 | "type": "@ocif/node/rect", 124 | "strokeColor": "#33FFB7", 125 | "strokeWidth": 4, 126 | "fillColor": "#269E85" 127 | } 128 | ] 129 | }, 130 | { 131 | "id": "n10", 132 | "position": [160, 320], 133 | "size": [150, 150], 134 | "resource": "r10", 135 | "data": [ 136 | { 137 | "type": "@ocif/node/rect", 138 | "strokeColor": "#A1FF33", 139 | "strokeWidth": 4, 140 | "fillColor": "#6EA626" 141 | } 142 | ] 143 | }, 144 | { 145 | "id": "n11", 146 | "position": [320, 320], 147 | "size": [150, 150], 148 | "resource": "r11", 149 | "data": [ 150 | { 151 | "type": "@ocif/node/rect", 152 | "strokeColor": "#FF338D", 153 | "strokeWidth": 4, 154 | "fillColor": "#A62663" 155 | } 156 | ] 157 | }, 158 | { 159 | "id": "n12", 160 | "position": [480, 320], 161 | "size": [150, 150], 162 | "resource": "r12", 163 | "data": [ 164 | { 165 | "type": "@ocif/node/rect", 166 | "strokeColor": "#33A1FF", 167 | "strokeWidth": 4, 168 | "fillColor": "#2672A6" 169 | } 170 | ] 171 | }, 172 | { 173 | "id": "n13", 174 | "position": [0, 480], 175 | "size": [150, 150], 176 | "resource": "r13", 177 | "data": [ 178 | { 179 | "type": "@ocif/node/rect", 180 | "strokeColor": "#F633FF", 181 | "strokeWidth": 4, 182 | "fillColor": "#B526C1" 183 | } 184 | ] 185 | }, 186 | { 187 | "id": "n14", 188 | "position": [160, 480], 189 | "size": [150, 150], 190 | "resource": "r14", 191 | "data": [ 192 | { 193 | "type": "@ocif/node/rect", 194 | "strokeColor": "#FF9E33", 195 | "strokeWidth": 4, 196 | "fillColor": "#C17826" 197 | } 198 | ] 199 | }, 200 | { 201 | "id": "n15", 202 | "position": [320, 480], 203 | "size": [150, 150], 204 | "resource": "r15", 205 | "data": [ 206 | { 207 | "type": "@ocif/node/rect", 208 | "strokeColor": "#33FF73", 209 | "strokeWidth": 4, 210 | "fillColor": "#269E53" 211 | } 212 | ] 213 | }, 214 | { 215 | "id": "n16", 216 | "position": [480, 480], 217 | "size": [150, 150], 218 | "resource": "r16", 219 | "data": [ 220 | { 221 | "type": "@ocif/node/rect", 222 | "strokeColor": "#FF33E4", 223 | "strokeWidth": 4, 224 | "fillColor": "#A626AB" 225 | } 226 | ] 227 | } 228 | ], 229 | "resources": [ 230 | { 231 | "id": "r1", 232 | "representations": [{ "mime-type": "text/plain", "content": "Hello" }] 233 | }, 234 | { 235 | "id": "r2", 236 | "representations": [{ "mime-type": "text/plain", "content": "World" }] 237 | }, 238 | { 239 | "id": "r3", 240 | "representations": [{ "mime-type": "text/plain", "content": "Fun Day" }] 241 | }, 242 | { 243 | "id": "r4", 244 | "representations": [{ "mime-type": "text/plain", "content": "Great" }] 245 | }, 246 | { 247 | "id": "r5", 248 | "representations": [{ "mime-type": "text/plain", "content": "Bright" }] 249 | }, 250 | { 251 | "id": "r6", 252 | "representations": [{ "mime-type": "text/plain", "content": "Cool" }] 253 | }, 254 | { 255 | "id": "r7", 256 | "representations": [{ "mime-type": "text/plain", "content": "Ocean" }] 257 | }, 258 | { 259 | "id": "r8", 260 | "representations": [{ "mime-type": "text/plain", "content": "Sunset" }] 261 | }, 262 | { 263 | "id": "r9", 264 | "representations": [{ "mime-type": "text/plain", "content": "Grass" }] 265 | }, 266 | { 267 | "id": "r10", 268 | "representations": [{ "mime-type": "text/plain", "content": "Fresh" }] 269 | }, 270 | { 271 | "id": "r11", 272 | "representations": [{ "mime-type": "text/plain", "content": "Love" }] 273 | }, 274 | { 275 | "id": "r12", 276 | "representations": [{ "mime-type": "text/plain", "content": "Sky" }] 277 | }, 278 | { 279 | "id": "r13", 280 | "representations": [{ "mime-type": "text/plain", "content": "Dream" }] 281 | }, 282 | { 283 | "id": "r14", 284 | "representations": [{ "mime-type": "text/plain", "content": "Orange" }] 285 | }, 286 | { 287 | "id": "r15", 288 | "representations": [{ "mime-type": "text/plain", "content": "Leaf" }] 289 | }, 290 | { 291 | "id": "r16", 292 | "representations": [{ "mime-type": "text/plain", "content": "Star" }] 293 | } 294 | ], 295 | "relations": [], 296 | "schemas": [] 297 | } 298 | -------------------------------------------------------------------------------- /design/diagram-files.svg: -------------------------------------------------------------------------------- 1 | FileMainFileA JSON file storingall nodessome or all resourcessome or all schemasAssetFileResourceString idRepresentationString contentTypeInlineTextContentString contentAssetContent// Can be binary or text// usually just a filenameString relativePath;RemoteContent// Can be binary or text// by using a "data:" URI, small// binary content can be inlinedString uri;ordered list1..nholdsrefers to0..n 2 | -------------------------------------------------------------------------------- /spec/v0.4/extensions.md: -------------------------------------------------------------------------------- 1 | # OCIF Extensions 2 | 3 | Copyright © 2025 the Contributors to the Open Canvas Working Group (OCWG). 4 | 5 | Versioning Policy: Currently, all extensions in this document are at version **0.4**. 6 | 7 | ## Abstract 8 | 9 | This document describes extensions to the Open Canvas Interchange Format (OCIF) that are not part of the core specification. Extensions are optional and may be used to add additional functionality to OCIF documents. 10 | See the [OCIF Core Specification](./spec) for the core features of OCIF. 11 | 12 | ## Status of this Document 13 | 14 | This document is an editor's draft and has no official standing. It is a work in progress and may be updated, replaced, or obsoleted by other documents at any time. 15 | 16 | **Legal**: 17 | Open Canvas Interchange Format (OCIF) v0.4 © 2025 by Open Canvas Working Group is licensed under CC BY-SA 4.0. To view a copy of this licence, visit https://creativecommons.org/licenses/by-sa/4.0/ 18 | 19 | ### Table of Contents 20 | 21 | 22 | 23 | - [OCIF Extensions](#ocif-extensions) 24 | - [Abstract](#abstract) 25 | - [Status of this Document](#status-of-this-document) 26 | - [Table of Contents](#table-of-contents) 27 | - [Node Extensions](#node-extensions) 28 | - [Ports Node](#ports-node) 29 | - [Relative Node](#relative-node) 30 | - [Relation Extensions](#relation-extensions) 31 | - [Hyperedge Relation](#hyperedge-relation) 32 | - [Parent-Child Relation](#parent-child-relation) 33 | - [Changes](#changes) 34 | 35 | 36 | # Node Extensions 37 | 38 | These are [extension](spec.md#extensions) that can be added to nodes in an OCIF document. 39 | To be placed inside the `data` `array`. 40 | 41 | ## Ports Node 42 | 43 | - Name: `@ocif/node/ports` 44 | - URI: `https://spec.canvasprotocol.org/v0.4/extensions/ports-node.json` 45 | 46 | It provides the familiar concept of _ports_ to a node. A port is a point, which allows geometrically controlling where, e.g., arrows are attached to a shape. 47 | 48 | Any node can act as a port. The 'container' node uses the _Ports Extension_ to define which nodes it uses as ports. 49 | The _Ports Extension_ has the following properties: 50 | 51 | | Property | JSON Type | OCIF Type | Required | Contents | Default | 52 | | -------- | --------- | ---------------- | ------------ | ----------------------------- | ------- | 53 | | `ports` | `array` | [ID](spec.md#id) | **required** | IDs of nodes acting as ports. | | 54 | 55 | Each node SHOULD appear only in **one** ports array. 56 | A port cannot be used by multiple nodes as a port. 57 | 58 | **Example** \ 59 | A node (n1) with two ports (p1, p2). 60 | Note that p1 and p2 are normal nodes. 61 | It's node n1 which uses p1 and p2 as its ports. 62 | An arrow can now start at node p1 (which is a port of n1) and end at node n2 (which is not a port in this example). 63 | 64 | ```json 65 | { 66 | "nodes": [ 67 | { 68 | "id": "n1", 69 | "position": [100, 100], 70 | "size": [100, 100], 71 | "data": [ 72 | { 73 | "type": "@ocif/node/ports", 74 | "ports": ["p1", "p2"] 75 | } 76 | ] 77 | }, 78 | { 79 | "id": "p1", 80 | "position": [200, 200] 81 | }, 82 | { 83 | "id": "p2", 84 | "position": [100, 200] 85 | }, 86 | { 87 | "id": "n2", 88 | "position": [800, 800] 89 | } 90 | ] 91 | } 92 | ``` 93 | 94 | JSON schema: [ports-node.json](extensions/ports-node.json) 95 | 96 | ## Relative Node 97 | 98 | - Name: `@ocif/node/relative` 99 | - URI: `https://spec.canvasprotocol.org/v0.4/extensions/relative-node.json` 100 | 101 | Relative constraints are used to define, e.g., the relative positioning of nodes. 102 | 103 | The node on which this extension is placed is the _target node_. 104 | It defines a _source_ node, which is used as a reference for the relative positioning. 105 | 106 | | Property | JSON Type | OCIF Type | Required | Contents | Default | 107 | | ---------- | --------- | ---------------------- | ------------ | -------------------------------- | ------- | 108 | | `source` | `string` | [ID](spec.md#id) | **required** | ID of the source node. | | 109 | | `position` | `array` | number[] | optional | Relative position of the target. | `[0,0]` | 110 | | `rotation` | `number` | [Angle](spec.md#angle) | optional | Relative angle of the target. | `0` | 111 | 112 | - **source**: The ID of the source node, which is used as a reference for the relative positioning. 113 | - **position**: The relative position of the target node to the source node. 114 | The numbers given in the array are vector-added to the position of the source node. 115 | - **rotation**: The relative rotation of the target node to the source node. The rotation is added to the rotation of the source node. 116 | 117 | JSON schema: [relative-node.json](extensions/relative-node.json) 118 | 119 | # Relation Extensions 120 | 121 | ## Hyperedge Relation 122 | 123 | - Name: `@ocif/rel/hyperedge` 124 | - URI: `https://spec.canvasprotocol.org/v0.4/extensions/hyperedge-rel.json` 125 | 126 | A hyperedge is a relation, which connects any number of nodes. 127 | Hyperedges can also be used to model simple bi-edges. 128 | 129 | Conceptually, a hyper-edge is an entity, which has a number of _endpoints_. 130 | For each endpoint, we allow defining the directionality of the connection. 131 | The endpoints are explicitly defined as an ordered list, i.e., endpoints can be addressed by their position in the list. 132 | Such a model allows representing all kinds of hyper-edges, even rather obscure one. 133 | 134 | A hyper-edge in OCIF has the following properties: 135 | 136 | | Property | JSON Type | OCIF Type | Required | Contents | Default | 137 | | ----------- | --------- | --------- | ------------ | ------------------------------------ | ------: | 138 | | `endpoints` | `array` | Endpoint | **required** | List of endpoints of the hyper-edge. | | 139 | | `weight` | `number` | | optional | Weight of the edge | `1.0` | 140 | | `rel` | `string` | | optional | Represented relation type | | 141 | 142 | - **endpoints**: See below. 143 | - **weight**: A floating-point number, which can be used to model the strength of the connection, as a whole. More general than endpoint-specific weights, and often sufficient. 144 | 147 | - **rel**: See [Edge Relation](spec.md#edge-relation) 148 | 149 | **Endpoint** \ 150 | Each endpoint is an object with the following properties: 151 | 152 | | Property | JSON Type | OCIF Type | Required | Contents | Default | 153 | | ----------- | --------- | ---------------- | ------------ | ---------------------------------------- | ------- | 154 | | `id` | `string` | [ID](spec.md#id) | **required** | ID of attached entity (node or relation) | | 155 | | `direction` | `string` | Direction | optional | Direction of the connection. | `undir` | 156 | | `weight` | `number` | | optional | Weight of the edge | `1.0` | 157 | 158 | - **id**: states which node (or relation) is attached to the edge 159 | - **direction**: See below 160 | - **weight**: A floating-point number, which can be used to model the strength of the connection, for this endpoint. 161 | 164 | 165 | **Direction** \ 166 | An enum with three values: 167 | 168 | - `in` (edge is going **into** the hyper-edge), 169 | - `out` (edge is going **out** from thy hyper-edge), 170 | - `undir` (edge is attached **undirected**). This is the default. 171 | 172 | This allows representing cases such as: 173 | 174 | - An edge with only one endpoint 175 | - An edge with no endpoints 176 | - An edge with only incoming or only outgoing endpoints. 177 | 178 | **Example** \ 179 | An hyperedge relation connecting two nodes as input (n1,n2) with one node as output (n3). 180 | 181 | ```json 182 | { 183 | "type": "@ocif/rel/hyperedge", 184 | "endpoints": [ 185 | { "id": "n1", "direction": "in" }, 186 | { "id": "n2", "direction": "in" }, 187 | { "id": "n3", "direction": "out" } 188 | ] 189 | } 190 | ``` 191 | 192 | JSON schema: [hyperedge-rel.json](extensions/hyperedge-rel.json) 193 | 194 | ## Parent-Child Relation 195 | 196 | - Name: `@ocif/rel/parent-child` 197 | - URI: `https://spec.canvasprotocol.org/v0.4/extensions/parent-child-rel.json` 198 | 199 | A parent-child relation models a hierarchical relationship between nodes. 200 | It can be used to model inheritance, containment, or other hierarchical relationships. 201 | 202 | | Property | JSON Type | OCIF Type | Required | Contents | Default | 203 | | --------- | --------- | ---------------- | ------------ | ---------------------- | :------ | 204 | | `parent` | `string` | [ID](spec.md#id) | **required** | ID of the parent node. | | 205 | | `child` | `string` | [ID](spec.md#id) | **required** | ID of the child node. | | 206 | | `inherit` | `boolean` | | optional | Inherit properties. | `false` | 207 | 208 | - **parent**: The ID of the parent node. There SHOULD be only one parent per child. 209 | 210 | - **child**: The ID of the child node. A parent can have multiple children (expressed my multiple parent-child relations). 211 | 212 | - **inherit**: A boolean flag indicating if the child should inherit properties from the parent. Default is `false`. 213 | - The Exact semantics of inheritance are defined by the application. 214 | - In general, when looking for JSON properties of a node and finding them undefined, an app should look for the same value in the parent node. 215 | The chain of parents should be followed until a root is reached or a cycle is detected. 216 | 217 | Semantics: 218 | 219 | - If a parent is deleted, all children, which inherit from the parent, SHOULD also be deleted. 220 | 221 | JSON schema: [parent-child-rel.json](extensions/parent-child-rel.json) 222 | 223 | # Changes 224 | 225 | - Simplified language from "Ports Extension" to "Ports Node", from "Relative Constraints Extension" to "Relative Node." 226 | - Moved "freehand-node" to issues https://github.com/ocwg/spec/issues/14 227 | - 2025-01-21: Initial version of the document. 228 | -------------------------------------------------------------------------------- /spec/v0.3/extensions.md: -------------------------------------------------------------------------------- 1 | # OCIF Extensions 2 | 3 | **OCWG Working Draft, 21 January 2025** 4 | 5 | Copyright © 2025 the Contributors to the Open Canvas Working Group (OCWG). 6 | 7 | Versioning Policy: Currently, all extensions in this document are at version **0.3**. 8 | 9 | ## Abstract 10 | 11 | This document describes extensions to the Open Canvas Interchange Format (OCIF) that are not part of the core specification. Extensions are optional and may be used to add additional functionality to OCIF documents. 12 | See the [OCIF Core Specification](./spec) for the core features of OCIF. 13 | 14 | ## Status of this Document 15 | 16 | This document is an editor's draft and has no official standing. It is a work in progress and may be updated, replaced, or obsoleted by other documents at any time. 17 | 18 | **Legal**: 19 | Open Canvas Interchange Format (OCIF) v0.2 © 2024 by Open Canvas Working Group is licensed under CC BY-SA 4.0. To view a copy of this licence, visit https://creativecommons.org/licenses/by-sa/4.0/ 20 | 21 | ### Table of Contents 22 | 23 | 24 | 25 | - [OCIF Extensions](#ocif-extensions) 26 | - [Abstract](#abstract) 27 | - [Status of this Document](#status-of-this-document) 28 | - [Table of Contents](#table-of-contents) 29 | - [Node Extensions](#node-extensions) 30 | - [Ports Node](#ports-node) 31 | - [Relative Node](#relative-node) 32 | - [Relation Extensions](#relation-extensions) 33 | - [Hyperedge Relation](#hyperedge-relation) 34 | - [Parent-Child Relation](#parent-child-relation) 35 | - [Changes](#changes) 36 | 37 | 38 | # Node Extensions 39 | 40 | These are [extension](spec.md#extensions) that can be added to nodes in an OCIF document. 41 | To be placed inside the `data` `array`. 42 | 43 | ## Ports Node 44 | 45 | - Name: `@ocwg/node/ports` 46 | - URI: `https://spec.canvasprotocol.org/v0.3/extensions/ports-node.json` 47 | 48 | It provides the familiar concept of _ports_ to a node. A port is a point, which allows geometrically controlling where, e.g., arrows are attached to a shape. 49 | 50 | Any node can act as a port. The 'container' node uses the _Ports Extension_ to define which nodes it uses as ports. 51 | The _Ports Extension_ has the following properties: 52 | 53 | | Property | JSON Type | OCIF Type | Required | Contents | Default | 54 | | -------- | --------- | ---------------- | ------------ | ----------------------------- | ------- | 55 | | `ports` | `array` | [ID](spec.md#id) | **required** | IDs of nodes acting as ports. | | 56 | 57 | Each node SHOULD appear only in **one** ports array. 58 | A port cannot be used by multiple nodes as a port. 59 | 60 | **Example** \ 61 | A node (n1) with two ports (p1, p2). 62 | Note that p1 and p2 are normal nodes. 63 | It's node n1 which uses p1 and p2 as its ports. 64 | An arrow can now start at node p1 (which is a port of n1) and end at node n2 (which is not a port in this example). 65 | 66 | ```json 67 | { 68 | "nodes": [ 69 | { 70 | "id": "n1", 71 | "position": [100, 100], 72 | "size": [100, 100], 73 | "data": [ 74 | { 75 | "type": "@ocwg/node/ports", 76 | "ports": ["p1", "p2"] 77 | } 78 | ] 79 | }, 80 | { 81 | "id": "p1", 82 | "position": [200, 200] 83 | }, 84 | { 85 | "id": "p2", 86 | "position": [100, 200] 87 | }, 88 | { 89 | "id": "n2", 90 | "position": [800, 800] 91 | } 92 | ] 93 | } 94 | ``` 95 | 96 | JSON schema: [ports-node.json](extensions/ports-node.json) 97 | 98 | ## Relative Node 99 | 100 | - Name: `@ocwg/node/relative` 101 | - URI: `https://spec.canvasprotocol.org/v0.3/extensions/relative-node.json` 102 | 103 | Relative constraints are used to define, e.g., the relative positioning of nodes. 104 | 105 | The node on which this extension is placed is the _target node_. 106 | It defines a _source_ node, which is used as a reference for the relative positioning. 107 | 108 | | Property | JSON Type | OCIF Type | Required | Contents | Default | 109 | | ---------- | --------- | ---------------------- | ------------ | -------------------------------- | ------- | 110 | | `source` | `string` | [ID](spec.md#id) | **required** | ID of the source node. | | 111 | | `position` | `array` | number[] | optional | Relative position of the target. | `[0,0]` | 112 | | `rotation` | `number` | [Angle](spec.md#angle) | optional | Relative angle of the target. | `0` | 113 | 114 | - **source**: The ID of the source node, which is used as a reference for the relative positioning. 115 | - **position**: The relative position of the target node to the source node. 116 | The numbers given in the array are vector-added to the position of the source node. 117 | - **rotation**: The relative rotation of the target node to the source node. The rotation is added to the rotation of the source node. 118 | 119 | JSON schema: [relative-node.json](extensions/relative-node.json) 120 | 121 | # Relation Extensions 122 | 123 | ## Hyperedge Relation 124 | 125 | - Name: `@ocwg/rel/hyperedge` 126 | - URI: `https://spec.canvasprotocol.org/v0.3/extensions/hyperedge-rel.json` 127 | 128 | A hyperedge is a relation, which connects any number of nodes. 129 | Hyperedges can also be used to model simple bi-edges. 130 | 131 | Conceptually, a hyper-edge is an entity, which has a number of _endpoints_. 132 | For each endpoint, we allow defining the directionality of the connection. 133 | The endpoints are explicitly defined as an ordered list, i.e., endpoints can be addressed by their position in the list. 134 | Such a model allows representing all kinds of hyper-edges, even rather obscure one. 135 | 136 | A hyper-edge in OCIF has the following properties: 137 | 138 | | Property | JSON Type | OCIF Type | Required | Contents | Default | 139 | | ----------- | --------- | --------- | ------------ | ------------------------------------ | ------: | 140 | | `endpoints` | `array` | Endpoint | **required** | List of endpoints of the hyper-edge. | | 141 | | `weight` | `number` | | optional | Weight of the edge | `1.0` | 142 | | `rel` | `string` | | optional | Represented relation type | | 143 | 144 | - **endpoints**: See below. 145 | - **weight**: A floating-point number, which can be used to model the strength of the connection, as a whole. More general than endpoint-specific weights, and often sufficient. 146 | 149 | - **rel**: See [Edge Relation](spec.md#edge-relation) 150 | 151 | **Endpoint** \ 152 | Each endpoint is an object with the following properties: 153 | 154 | | Property | JSON Type | OCIF Type | Required | Contents | Default | 155 | | ----------- | --------- | ---------------- | ------------ | ---------------------------------------- | ------- | 156 | | `id` | `string` | [ID](spec.md#id) | **required** | ID of attached entity (node or relation) | | 157 | | `direction` | `string` | Direction | optional | Direction of the connection. | `undir` | 158 | | `weight` | `number` | | optional | Weight of the edge | `1.0` | 159 | 160 | - **id**: states which node (or relation) is attached to the edge 161 | - **direction**: See below 162 | - **weight**: A floating-point number, which can be used to model the strength of the connection, for this endpoint. 163 | 166 | 167 | **Direction** \ 168 | An enum with three values: 169 | 170 | - `in` (edge is going **into** the hyper-edge), 171 | - `out` (edge is going **out** from thy hyper-edge), 172 | - `undir` (edge is attached **undirected**). This is the default. 173 | 174 | This allows representing cases such as: 175 | 176 | - An edge with only one endpoint 177 | - An edge with no endpoints 178 | - An edge with only incoming or only outgoing endpoints. 179 | 180 | **Example** \ 181 | An hyperedge relation connecting two nodes as input (n1,n2) with one node as output (n3). 182 | 183 | ```json 184 | { 185 | "type": "@ocwg/rel/hyperedge", 186 | "endpoints": [ 187 | { "id": "n1", "direction": "in" }, 188 | { "id": "n2", "direction": "in" }, 189 | { "id": "n3", "direction": "out" } 190 | ] 191 | } 192 | ``` 193 | 194 | JSON schema: [hyperedge-rel.json](extensions/hyperedge-rel.json) 195 | 196 | ## Parent-Child Relation 197 | 198 | - Name: `@ocwg/rel/parent-child` 199 | - URI: `https://spec.canvasprotocol.org/v0.3/extensions/parent-child-rel.json` 200 | 201 | A parent-child relation models a hierarchical relationship between nodes. 202 | It can be used to model inheritance, containment, or other hierarchical relationships. 203 | 204 | | Property | JSON Type | OCIF Type | Required | Contents | Default | 205 | | --------- | --------- | ---------------- | ------------ | ---------------------- | :------ | 206 | | `parent` | `string` | [ID](spec.md#id) | **required** | ID of the parent node. | | 207 | | `child` | `string` | [ID](spec.md#id) | **required** | ID of the child node. | | 208 | | `inherit` | `boolean` | | optional | Inherit properties. | `false` | 209 | 210 | - **parent**: The ID of the parent node. There SHOULD be only one parent per child. 211 | 212 | - **child**: The ID of the child node. A parent can have multiple children (expressed my multiple parent-child relations). 213 | 214 | - **inherit**: A boolean flag indicating if the child should inherit properties from the parent. Default is `false`. 215 | - The Exact semantics of inheritance are defined by the application. 216 | - In general, when looking for JSON properties of a node and finding them undefined, an app should look for the same value in the parent node. 217 | The chain of parents should be followed until a root is reached or a cycle is detected. 218 | 219 | Semantics: 220 | 221 | - If a parent is deleted, all children, which inherit from the parent, SHOULD also be deleted. 222 | 223 | JSON schema: [parent-child-rel.json](extensions/parent-child-rel.json) 224 | 225 | # Changes 226 | 227 | - Simplified language from "Ports Extension" to "Ports Node", from "Relative Constraints Extension" to "Relative Node." 228 | - Moved "freehand-node" to issues https://github.com/ocwg/spec/issues/14 229 | - 2025-01-21: Initial version of the document. 230 | -------------------------------------------------------------------------------- /spec/v0.1/spec.md: -------------------------------------------------------------------------------- 1 | # Open Canvas Initial Draft Spec 2 | 3 | > **This DEPRECATED version:** \ 4 | >        https://spec.canvasprotocol.org/v0.1 \ 5 | > **Latest version:** \ 6 | >        https://spec.canvasprotocol.org/v0.2 \ 7 | >        See [spec 0.2](spec-0.2.md) in this repo. 8 | 9 | ⚒️ Work on the initial spec is underway in the [Initial Draft Spec pull request](https://github.com/ocwg/spec/pull/1). 10 | 11 | 📚 Read more at [canvasprotocol.org](https://canvasprotocol.org). 12 | 13 | The purpose of this doc is to provide a plain-language description of the specification and walk through key concepts of the Open Canvas format. 14 | 15 | ## Motivation and Design Goals 16 | 17 | The goal of the Open Canvas format is to enable interoperability between different [infinite canvas tools](https://infinitecanvas.tools) by being a simple, compact "transmission" format. 18 | 19 | Open Canvas has been designed to meet the following goals: 20 | 21 | - **Balance between visual and conceptual canvases.** Nodes provide the core elements for the canvas. Relations provide rich support for creating conceptual relationships between nodes. 22 | - **Extensibility.** Open Canvas is fully extensibile using custom-defined schemas. 23 | - **Runtime-independence.** Enable different canvases to work together without canvases needing to implement or even understand all aspects of the spec. 24 | 25 | Out of scope: 26 | 27 | - **Backwards compatibility with Obsidian's JSON Canvas.** JSON Canvas isn't designed to support the full gamut of functionality supported by Open Canvas. 28 | 29 | ## Structure: Nodes, Relations, Resources, Schemas 30 | 31 | Conceptually, an infinite canvas can be thought of as `nodes` and `relations`. `nodes` represent all of the visual information on the canvas, while `relations` represent the invisible conceptual relationships between nodes. 32 | 33 | In order to enable asset re-use, Open Canvas distinguishes between a `node` with a location on the canvas and the `resource` that the `node` displays. 34 | 35 | The structure of an Open Canvas JSON consists of `nodes`, `relations`, and `resources`, with a version number that defines the shape of the top-level structure and the shapes of the base classes. The following is a valid, though empty Open Canvas file: 36 | 37 | ```json 38 | { 39 | "schema_version": "1.0", // this could be implied 40 | "nodes": [], 41 | "relations": [], 42 | "resources": [], 43 | "schemas": [] 44 | } 45 | ``` 46 | 47 | ### Nodes 48 | 49 | All visible items on the canvas are referred to as Nodes. 50 | 51 | **Node Base Class** 52 | 53 | All Nodes must implement the following properties: 54 | 55 | ```ts 56 | type Node = { 57 | id: string; 58 | position: [number, number] | [number, number, number]; // x,y or x,y,z 59 | size?: [number, number] | [number, number, number]; // w,h or w,h,d 60 | rotation?: number; // +/- 360 degrees; if not included, defaults to 0 61 | scale?: number; // defaults to 0 62 | resource?: string; // id of a resource 63 | properties?: [ 64 | { 65 | schema: string; // name of a schema listed in schemas 66 | schema_version: string; 67 | [key: string]: any; 68 | } 69 | ]; 70 | fallback?: string; 71 | }; 72 | ``` 73 | 74 | ### Relations 75 | 76 | Relations are used to indicate relationships between Nodes on the canvas. Relations are generally not visible, but rather conceptual. If a relation should be visualized, it should have a corresponding Node. 77 | 78 | **Relation Base class** 79 | 80 | ```ts 81 | type Relation = { 82 | id: string; 83 | name: string; 84 | properties?: [ 85 | { 86 | schema: string; 87 | schema_version: string; 88 | [key: string]: any; 89 | } 90 | ]; 91 | }; 92 | ``` 93 | 94 | ### Resources 95 | 96 | Resources are the hypermedia assets that Nodes display. They are stored separately from Nodes to allow for asset reuse and efficiency. 97 | 98 | **Resoruce Base class** 99 | 100 | ```ts 101 | type Resource = { 102 | id: string; 103 | uri: string; 104 | mimeType?: string; 105 | }; 106 | ``` 107 | 108 | - [ ] What happens if `id`s collide? Should we just use array indexes instead? 109 | 110 | The `uri` for a resource can either be: 111 | 112 | - embedded in the file itself 113 | - a local file relative to the open canvas file 114 | - a remote URL 115 | 116 | **Embedded in the file** 117 | 118 | ```json 119 | { 120 | "resources": { 121 | [ 122 | "id": "a-rectangle", 123 | "uri": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAfSURBVHgBpcixEQAACAIxdP8FfxrtOTpSZoCTWQVlPm0NA5S6X7P2AAAAAElFTkSuQmCC", // 5x5 white png 124 | "mimeType": "image/png" 125 | ] 126 | } 127 | } 128 | ``` 129 | 130 | - [ ] is `mimeType` duplicative? 131 | - [ ] what if `mimeType` and the mimeType specified in `data:` differ? 132 | 133 | **Local file relative to the Open Canvas file** 134 | 135 | ```json 136 | { 137 | "resources": { 138 | [ 139 | "id": "a-rectangle", 140 | "uri": "./a-rectangle.png", 141 | "mimeType": "image/png" 142 | ] 143 | } 144 | } 145 | ``` 146 | 147 | **A remote URL** 148 | 149 | ```json 150 | { 151 | "resources": { 152 | [ 153 | "id": "a-rectangle", 154 | "uri": "https://somesite.com/a-rectangle.png", 155 | "mimeType": "image/png" 156 | ] 157 | } 158 | } 159 | ``` 160 | 161 | - [ ] what if `mimeType` and the mimeType specified in the http response differ? 162 | 163 | ## Extensibility 164 | 165 | One major challenge of an interchange format for infinite canvases is that no two canvases are exactly alike. There are canvases built for informal whiteboarding, formal diagramming, quick visual sketches, node-and-wire programming, and many, many other use cases. Each of these canvases have radically different feature sets. How can we enable these canvases to work together? 166 | 167 | The core of the Open Canvas specification is extensibility and fallbacks. In the case of one canvas supporting a feature that another canvas doesn't support, we need to enable three things: 168 | 169 | 1. Canvases need to be able to read and write to an open canvas file without implementing all of the features of all other canvases and also extend the format with features unique to their canvas without breaking other canvases or changing the main specification. 170 | 2. Canvases that lack a feature need to preserve the data for features that they don't implement. 171 | 3. Canvases need to be able to optionally display reasonable fallbacks for features that they don't implement. 172 | 173 | Put simply: how do we design the specification to be extensible by individual canvas tools and robust against unknown features? 174 | 175 | ### Schemas 176 | 177 | Schemas are used to define the structure of Nodes and Relations. They provide extensibility by supporting different properties for Nodes and Relations. 178 | 179 | Schemas are included inline in the file. In the future, there may be a way to externalize schemas to a remote schema registry. 180 | 181 | Schemas have a version number to support schema evolution. 182 | 183 | ### Extensibility through preserving the property bag 184 | 185 | In order to support interchange beteween canvases when features don't overlap, canvases need to preserve nodes and relations that it doesn't support. 186 | 187 | In practice, that looks something like this: 188 | 189 | 1. Canvas A supporting Feature X creates a canvas with a Feature X node in it and exports it as an Open Canvas file. 190 | 2. Canvas B, which does not support Feature X, opens the Open Canvas file, and some edits are made to the canvas. 191 | 3. Canvas B exports the canvas to an Open Canvas file. The nodes for Feature X should still be in the Open Canvas file, unchanged. 192 | 193 | NOTE: This is perhaps the most onerous part of supporting Open Canvas; implementing canvases have to store data that they don't need or recognize. 194 | 195 | ### Extensibility through fallbacks 196 | 197 | The base node type supports an optional `fallback` property as a string. If a canvas wishes to visualize a node that it doesn't implement, the canvas can render a fallback element on the canvas at the element's x,y position using the string. 198 | 199 | ### Extensibility through schema extension 200 | 201 | Schemas can extend one another. 202 | 203 | When extending, properties are simply merged to establish the full schema. This allows extension with meaningful fallbacks: if an implementer doesn't support the extended schema, but does support the base schema, they can render the node using a simpler representation. 204 | 205 | - [ ] "merging properties" is probably a bad idea - let the implementer choose which property to use rather than using this inheritance-like behavior 206 | 207 | Schemas are defined in JSONSchema format. 208 | 209 | ```json 210 | "schemas": [ 211 | "@ocwg/text": { /* JSONSchema format */ } 212 | ] 213 | ``` 214 | 215 | For example, a `@tldraw/arrow` schema could extend a `@ocwg/arrow` schema, adding properties that are only applicable to TLDraw arrow. 216 | 217 | - [ ] Flesh out this example 218 | 219 | ## Core Node Schemas 220 | 221 | These are the node schemas included in the core `@ocwg` namespace. 222 | 223 | ### Text 224 | 225 | Text Nodes are used to display text on the canvas. 226 | 227 | ### Arrows 228 | 229 | ### Examples 230 | 231 | Here are a bunch of example Nodes: 232 | 233 | ```json 234 | { 235 | "nodes": [ 236 | { 237 | "id": "someShapeID11321", 238 | "position": [10, 10], 239 | "properties": [{ 240 | "schema": "@ocwg/text", 241 | "schema_version": "1.0", 242 | "text": "Hello, world!", 243 | "color": "#000000" 244 | }] 245 | }, 246 | { 247 | "id": "2435234634t324t3245", 248 | "position": [10, 10], 249 | "size": [200, 150], 250 | "properties": [{ 251 | "schema": "@ocwg/text", 252 | "schema_version": "1.0", 253 | "mime_type": "text/json", 254 | "uri": "data://9823fy9283hf2i3yf082ohu3fo2hufi2uh4fk234f8youhi", 255 | }] 256 | }, 257 | { 258 | "id": "oajefioajef83r8y3ehf", 259 | "position": [10, 10], 260 | "size": [200, 150], 261 | "rotation": 90, /* +/-360 degrees */ 262 | "properties": [{ 263 | "schema": "@ocwg/rectangle", 264 | "schema_version": "1.0", 265 | "text": "Hello, world!", 266 | "stroke_color": "red", 267 | "stroke_weight": , 268 | "fill_color": , 269 | }] 270 | }, 271 | { 272 | "id": "someShapeID124135", 273 | "position": [10, 10], 274 | "size": [200, 150], 275 | "properties": [{ 276 | "schema": "@stately/typed-rectangle", 277 | "schema_version": "3.1", 278 | "type": "rectangle", 279 | }] 280 | }, 281 | { 282 | "id": "someShapeID535", 283 | "position": [10, 10], 284 | "properties": [{ 285 | "schema": "@tldraw/arrow", 286 | "schema_version": "1.0", 287 | "start": { 288 | "position": [1, 1], 289 | "terminal": "", 290 | "connected_to": "someShapeID124135", 291 | }, 292 | "end": { 293 | "position": [10, 10], 294 | "terminal": "", 295 | "connected_to": "someShapeID124135", 296 | }, 297 | "bend": 200, 298 | }] 299 | } 300 | ] 301 | } 302 | ``` 303 | 304 | ## Core Relation Schemas 305 | 306 | These are the relation schemas included in the core `@ocwg` namespace. 307 | 308 | ### Sets 309 | 310 | Sets can be used to model groups, frames, and many other things. Their basic representation is a collection of Nodes or Relations. 311 | 312 | Here's an example: 313 | 314 | ```json 315 | { 316 | "id": "setRelationID1234", 317 | "name": "group a", 318 | "properties": [ 319 | { 320 | "schema": "@ocwg/set", 321 | "schema_version": "1.0", 322 | "members": ["someID", "someOtherID"] 323 | } 324 | ] 325 | } 326 | ``` 327 | 328 | - `members` can be Nodes or Relations. 329 | 330 | ### Edges 331 | 332 | Edge Relations are used to connect two Nodes or Relations together. Edge Relations are not the same as Nodes that are Arrows. Relations may not be visible on the canvas. 333 | 334 | Here's an example: 335 | 336 | ```json 337 | { 338 | "id": "edgeRelationID1234", 339 | "name": "a named edge", 340 | "properties": [ 341 | { 342 | "schema": "@ocwg/edge", 343 | "schema_version": "1.0", 344 | "from": "someNodeID124135", 345 | "to": "someNodeID11321" 346 | } 347 | ] 348 | } 349 | ``` 350 | 351 | - `from` and `to` can be Nodes or Relations. 352 | 353 | ### Hyper-edges 354 | 355 | Hyper-edges represent many-to-many relationships. 356 | 357 | ```json 358 | { 359 | "id": "hyperEdgeRelation1234", 360 | "name": "a named hyperedge", 361 | "properties": [ 362 | { 363 | "schema": "@ocwg/hyperedge", 364 | "schema_version": "1.0", 365 | "from": ["someShapeID124135", "someShapeID11321"], 366 | "to": ["someShapeID124135", "someShapeID11321"] 367 | } 368 | ] 369 | } 370 | ``` 371 | 372 | - The `from[]` and `to[]` arrays can contain Nodes or Relations. 373 | -------------------------------------------------------------------------------- /spec/v0.5/extensions.md: -------------------------------------------------------------------------------- 1 | # OCIF Extensions 2 | 3 | Copyright © 2025 the Contributors to the Open Canvas Working Group (OCWG). 4 | 5 | Versioning Policy: Currently, all extensions in this document are at version **0.5**. 6 | 7 | ## Abstract 8 | 9 | This document describes extensions to the Open Canvas Interchange Format (OCIF) that are not part of the core specification. Extensions are optional and may be used to add additional functionality to OCIF documents. 10 | See the [OCIF Core Specification](./spec) for the core features of OCIF. 11 | 12 | ## Status of this Document 13 | 14 | This document is an editor's draft and has no official standing. It is a work in progress and may be updated, replaced, or obsoleted by other documents at any time. 15 | 16 | **Legal**: 17 | Open Canvas Interchange Format (OCIF) v0.5 © 2025 by Open Canvas Working Group is licensed under CC BY-SA 4.0. To view a copy of this licence, visit https://creativecommons.org/licenses/by-sa/4.0/ 18 | 19 | ### Table of Contents 20 | 21 | 22 | * [OCIF Extensions](#ocif-extensions) 23 | * [Abstract](#abstract) 24 | * [Status of this Document](#status-of-this-document) 25 | * [Table of Contents](#table-of-contents) 26 | * [Node Extensions](#node-extensions) 27 | * [Ports Node](#ports-node) 28 | * [Node Transforms](#node-transforms) 29 | * [Anchored Node](#anchored-node) 30 | * [Text Style Node](#text-style-node) 31 | * [Relation Extensions](#relation-extensions) 32 | * [Hyperedge Relation](#hyperedge-relation) 33 | * [Parent-Child Relation](#parent-child-relation) 34 | * [Changes](#changes) 35 | * [From v0.4 to v0.5](#from-v04-to-v05) 36 | * [Up to v0.4](#up-to-v04) 37 | 38 | 39 | # Node Extensions 40 | 41 | These are [extensions](spec.md#extensions) that can be added to nodes in an OCIF document. 42 | To be placed inside the `data` `array`. 43 | 44 | ## Ports Node 45 | 46 | - Name: `@ocif/node/ports` 47 | - URI: `https://spec.canvasprotocol.org/v0.5/extensions/ports-node.json` 48 | 49 | It provides the familiar concept of _ports_ to a node. A port is a point that allows geometrically controlling where, e.g., arrows are attached to a shape. 50 | 51 | Any node can act as a port. The 'container' node uses the _Ports Extension_ to define which nodes it uses as ports. 52 | The _Ports Extension_ has the following properties: 53 | 54 | | Property | JSON Type | OCIF Type | Required | Contents | Default | 55 | |----------|-----------|------------------|--------------|-------------------------------|---------| 56 | | `ports` | `array` | [ID](spec.md#id) | **required** | IDs of nodes acting as ports. | | 57 | 58 | Each node SHOULD appear only in **one** ports array. 59 | A port cannot be used by multiple nodes as a port. 60 | 61 | **Example** \ 62 | A node (n1) with two ports (p1, p2). 63 | Note that _p1_ and _p2_ are normal nodes. 64 | It is the node _n1_ which uses _p1_ and _p2_ as its ports. 65 | An arrow can now start at node p1 (which is a port of n1) and end at node n2 (which is not a port in this example). 66 | 67 | ```json 68 | { 69 | "nodes": [ 70 | { 71 | "id": "n1", 72 | "position": [100, 100], 73 | "size": [100, 100], 74 | "data": [ 75 | { 76 | "type": "@ocif/node/ports", 77 | "ports": ["p1", "p2"] 78 | } 79 | ] 80 | }, 81 | { 82 | "id": "p1", 83 | "position": [200, 200] 84 | }, 85 | { 86 | "id": "p2", 87 | "position": [100, 200] 88 | }, 89 | { 90 | "id": "n2", 91 | "position": [800, 800] 92 | } 93 | ] 94 | } 95 | ``` 96 | 97 | JSON schema: [ports-node.json](extensions/ports-node.json) 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | ## Node Transforms 106 | 107 | - Name: `@ocif/node/transforms` 108 | - URI: `https://spec.canvasprotocol.org/v0.5/extensions/transforms-node.json` 109 | 110 | The node transform extension allows customizing the local coordinate system of a node relative to the parent coordinate system. 111 | This is a concept commonly found in game engines and infinitely zoomable canvases. 112 | 113 | The default parent coordinate system is the global, canvas-wide coordinate system. 114 | If the [parent-child-relation](#parent-child-relation) is used, the defined parent node MUST be used as the one, 115 | from which the reference coordinate system is used. 116 | 117 | The transforms affect the local coordinate system, which is used to display resources (see [spec](spec.md#size-and-resource)) and child nodes. The child nodes have global coordinates, and the node transform extension can provide the "recipe" how to calculate the global positions of a node when, e.g., the parent has been moved, rotated, or scaled. 118 | 119 | Transforms are chainable. For example, a node A may transform its coordinate system relative to the canvas. Node B may transform relative to the coordinate system of its parent A. Then node C transforms again relative to its parent B. The resulting scale, rotation, and offset computation requires computing first A, then B, then C. 120 | 121 | | Property | JSON Type | OCIF Type | Required | Contents | Default | 122 | |----------------|------------------------------------|-----------|--------------|---------------------|-----------| 123 | | `scale` | `number`, `number[2]`, `number[3]` | Vector | **optional** | Scale factor | `1` | 124 | | `rotation` | `number` | Angle | **optional** | Rotation in degrees | `0` | 125 | | `rotationAxis` | `number[3]` | | **optional** | Rotation axis | `[0,0,1]` | 126 | | `offset` | `number`, `number[2]`, `number[3]` | Vector | **optional** | Offset | `0` | 127 | 128 | - **scale**: A number-vector (floating-point) to override (set) the automatic scale factor of the node. This defines the scale of the local coordinate system. A larger scale SHOULD also affect font sizes. The scale factors are multiplied component-wise to the parent coordinate system. 129 | 130 | - NOTE: The scale factors cannot be computed from global positions alone. 131 | Scale factors provide additional state which influences interaction behaviour, e.g., an item drag-dropped into an item with a scale factor of less than 1 causes the item to shrink, when released. 132 | 133 | - **rotation**: A number-vector (floating-point) to override (set) the rotation of the node. 134 | - This (relative, local) rotation is added to the rotation of the parent. 135 | - A single number around the axis defined in `rotationAxis`, in degrees in counter-clockwise direction. 136 | 137 | - **rotationAxis**: The default is `(0,0,1)`. This is the [axis-angle notation](https://en.wikipedia.org/wiki/Axis%E2%80%93angle_representation). The default is to use the z-axis, which results in 'normal' 2D rotation in the x-y-plane. 138 | 139 | - **offset**: A number-vector (floating-point). This vector is added to the parent position to result in the childs position. This is the 'recipe' how the global child positions have been computed. 140 | 141 | - Semantics: When the parent is moved in an app, the app SHOULD update the children's position accordingly. The offset remains unchanged, unless the child itself is moved. In that case, the offset and the position of the child should be adapted. 142 | 143 | **Practical Advice**\ 144 | On import, the global positions can be used as-is. 145 | For text rendering, the scale factors SHOULD be taken into account. 146 | For interactive apps, the transforms allow to adapt on parent changes. 147 | Furthermore, when zooming very large maps, position and size should be computed on the fly using node transforms, as global positions would become unstable due to numeric precision. 148 | 149 | 150 | **Example:** A node with a scale factor: 151 | 152 | ```json 153 | { 154 | "id": "node-with-image", 155 | "position": [100, 100], 156 | "size": [100, 200], 157 | "resource": "frog", 158 | "data": [ 159 | { "type": "@ocif/node/transform", 160 | "scale": 0.5 } 161 | ] 162 | } 163 | ``` 164 | JSON schema: [transform-node.json](extensions/transforms-node.json) 165 | 166 | 167 | 168 | ## Anchored Node 169 | 170 | - Name: `@ocif/node/anchored` 171 | - URI: `https://spec.canvasprotocol.org/v0.5/extensions/anchored-node.json` 172 | 173 | Relative positioning requires anchoring to a parent item. 174 | The parent position is interpreted as the root of a local coordinate system. 175 | The parent size is added to the position and yields the coordindat of the _one_. 176 | This is (1,1) in 2D and (1,1,1) in 3D. 177 | Now nodes can be positioned relative to the parent using relative positions. 178 | The coordinates in [0,1]x[0,1] (or [0,1]x[0,1]x[0,1] in 3D) cover any position within the parent item. 179 | These percentage-coordinates are now used to position the item. 180 | 181 | | Property | JSON Type | OCIF Type | Required | Contents | Default | 182 | |-----------------------|----------------------------|-----------------------|--------------|---------------------|-----------------| 183 | | `topLeftPosition` | `number[2]` or `number[3]` | Percentage Coordinate | **optional** | Top left anchor | [0,0] / [0,0,0] | 184 | | `bottomRightPosition` | `number[2]` or `number[3]` | Percentage Coordinate | **optional** | Bottom-right anchor | [1,1] / [1,1,1] | 185 | | `topLeftOffset` | `number[2]` or `number[3]` | Absolute offset | **optional** | Top left offset | [0,0] / [0,0,0] | 186 | | `bottomRightOffset` | `number[2]` or `number[3]` | Absolute offset | **optional** | Bottom-right offset | [0,0] / [0,0,0] | 187 | 188 | The offsets are interpreted in the parent's coordinate system. 189 | 190 | If only the top-left position is given, the bottom-right position defaults to [1,1] (or [1,1,1] in 3D) as specified in the default values. This means the node would be anchored at the specified top-left position, and would extend to the bottom-right of the parent. 191 | 192 | JSON schema: [anchored-node.json](extensions/anchored-node.json) 193 | 194 | 195 | ## Text Style Node 196 | 197 | - Name: `@ocif/node/textstyle` 198 | - URI: `https://spec.canvasprotocol.org/v0.5/extensions/textstyle-node.json` 199 | 200 | The text style extension allows setting common properties for rendering plain text and structured text (such as Markdown or AsciiDoc). 201 | 202 | 203 | | Property | JSON Type | OCIF Type | Required | Contents | Default | 204 | |--------------|-----------|-----------------|----------|-------------|--------------| 205 | | `fontSizePx` | `number` | | optional | Font size | `12px` | 206 | | `fontFamily` | `string` | | optional | Font family | `sans-serif` | 207 | | `color` | `string` | Color | optional | Text color | `#000000` | 208 | | `align` | `string` | enum, see below | optional | Alignment | `left` | 209 | | `bold` | `boolean` | | optional | Bold text | `false` | 210 | | `italic` | `boolean` | | optional | Italic text | `false` | 211 | 212 | * **fontSize**: The font size in `px`, as used in CSS. 213 | * **fontFamily**: The font family, as used in CSS. 214 | * **color**: The text color. See [Color](spec.md#color). 215 | * **align**: The text alignment. Possible values are `left`, `right`, `center`, `justify`. 216 | * **bold**: A boolean flag indicating if the text should be bold. 217 | * **italic**: A boolean flag indicating if the text should be italic. 218 | 219 | JSON schema: [textstyle-node.json](extensions/textstyle-node.json) 220 | 221 | 222 | # Relation Extensions 223 | 224 | ## Hyperedge Relation 225 | 226 | - Name: `@ocif/rel/hyperedge` 227 | - URI: `https://spec.canvasprotocol.org/v0.5/extensions/hyperedge-rel.json` 228 | 229 | A hyperedge is a relation that connects any number of nodes. 230 | Hyperedges can also be used to model simple bi-edges. 231 | 232 | Conceptually, a hyper-edge is an entity that has a number of _endpoints_. 233 | For each endpoint, we allow defining the directionality of the connection. 234 | The endpoints are explicitly defined as an ordered list, i.e., endpoints can be addressed by their position in the list. 235 | Such a model allows representing all kinds of hyper-edges, even rather obscure one. 236 | 237 | A hyper-edge in OCIF has the following properties: 238 | 239 | | Property | JSON Type | OCIF Type | Required | Contents | Default | 240 | |-------------|-----------|-----------|--------------|--------------------------------------|--------:| 241 | | `endpoints` | `array` | Endpoint | **required** | List of endpoints of the hyper-edge. | | 242 | | `weight` | `number` | | optional | Weight of the edge | `1.0` | 243 | | `rel` | `string` | | optional | Represented relation type | | 244 | 245 | - **endpoints**: See below. 246 | - **weight**: A floating-point number, which can be used to model the strength of the connection, as a whole. More general than endpoint-specific weights, and often sufficient. 247 | 250 | - **rel**: See [Edge Relation](spec.md#edge-relation) 251 | 252 | **Endpoint** \ 253 | Each endpoint is an object with the following properties: 254 | 255 | | Property | JSON Type | OCIF Type | Required | Contents | Default | 256 | |-------------|-----------|------------------|--------------|------------------------------------------|---------| 257 | | `id` | `string` | [ID](spec.md#id) | **required** | ID of attached entity (node or relation) | | 258 | | `direction` | `string` | Direction | optional | Direction of the connection. | `undir` | 259 | | `weight` | `number` | | optional | Weight of the edge | `1.0` | 260 | 261 | - **id**: states which node (or relation) is attached to the edge 262 | - **direction**: See below 263 | - **weight**: A floating-point number, which can be used to model the strength of the connection, for this endpoint. 264 | 267 | 268 | **Direction** \ 269 | An enum with three values: 270 | 271 | - `in` (edge is going **into** the hyper-edge), 272 | - `out` (edge is going **out** from thy hyper-edge), 273 | - `undir` (edge is attached **undirected**). This is the default. 274 | 275 | This allows representing cases such as: 276 | 277 | - An edge with only one endpoint 278 | - An edge with no endpoints 279 | - An edge with only incoming or only outgoing endpoints. 280 | 281 | **Example** \ 282 | An hyperedge relation connecting two nodes as input (n1,n2) with one node as output (n3). 283 | 284 | ```json 285 | { 286 | "type": "@ocif/rel/hyperedge", 287 | "endpoints": [ 288 | { "id": "n1", "direction": "in" }, 289 | { "id": "n2", "direction": "in" }, 290 | { "id": "n3", "direction": "out" } 291 | ] 292 | } 293 | ``` 294 | 295 | JSON schema: [hyperedge-rel.json](extensions/hyperedge-rel.json) 296 | 297 | ## Parent-Child Relation 298 | 299 | - Name: `@ocif/rel/parent-child` 300 | - URI: `https://spec.canvasprotocol.org/v0.5/extensions/parent-child-rel.json` 301 | 302 | A parent-child relation models a strict hierarchical relationship between nodes or relations. 303 | It can be used to model inheritance, containment, or other hierarchical relationships. 304 | 305 | | Property | JSON Type | OCIF Type | Required | Contents | Default | 306 | |-----------------|-----------|------------------|--------------|-----------------------------------------|:--------| 307 | | `parent` | `string` | [ID](spec.md#id) | optional | ID of the parent. | empty | 308 | | `child` | `string` | [ID](spec.md#id) | **required** | ID of the child. | | 309 | | `inherit` | `boolean` | | optional | Inherit properties. | `false` | 310 | | `cascadeDelete` | `boolean` | | optional | Delete children when parent is deleted. | `true` | 311 | 312 | - **parent**: The ID of the parent node or relation. There MUST be only one parent per child. 313 | - If empty, the canvas itself acts as the parent node. This is relevant for [node transforms](#node-transforms). 314 | 315 | - **child**: The ID of the child node or relation. A parent can have multiple children (expressed my multiple parent-child relations). 316 | 317 | - **inherit**: A boolean flag indicating if the child should inherit properties from the parent. Default is `false`. 318 | 319 | - The Exact semantics of inheritance are defined by the application. 320 | - In general, when looking for JSON properties of a child and finding them undefined, an app should look for the same value in the parent. 321 | The chain of parents should be followed until a root is reached or a cycle is detected. 322 | 323 | - **cascadeDelete**: A boolean flag indiciating if the children should be deleted when the parent is deleted. Default it `true`. 324 | 325 | Semantics: 326 | 327 | - If a parent is deleted, all children, which inherit from the parent, SHOULD also be deleted. (see **cascadeDelete** property) 328 | 329 | JSON schema: [parent-child-rel.json](extensions/parent-child-rel.json) 330 | 331 | 332 | ## Changes 333 | 334 | ### From v0.4 to v0.5 335 | - "Relative Node" has been replaced by "Node Transforms" 336 | - "Anchored Node" has been added 337 | 338 | ### Up to v0.4 339 | - Simplified language from "Ports Extension" to "Ports Node", from "Relative Constraints Extension" to "Relative Node." 340 | - Moved "freehand-node" to issues https://github.com/ocwg/spec/issues/14 341 | - 2025-01-21: Initial version of the document. 342 | --------------------------------------------------------------------------------