Skip to content
D
Documentation

Instance and reconciliation

concept
2 min readUpdated

Choose the layer that owns the operation:

  • The DiagramInstance is the live handle to a rendered diagram. Use it for painting, events, viewport control, reconciliation, export, and text round-trips.
  • The DiagramModel owns document data: nodes, links, groups, and strokes. Read or query data here.
  • The DiagramEngine owns behavior such as commands, layout, validation, and interaction configuration.

The instance connects the rendered canvas to the model and engine. getModel() and getEngine() expose those lower layers when the instance does not provide the operation you need.

mermaid
flowchart LR
  host["render(spec, target)"] --> instance["DiagramInstance"]
  instance --> model["DiagramModel\ndata"]
  instance --> engine["DiagramEngine\nbehavior"]
  instance --> canvas["rendered canvas"]

Mount one live instance

Use render when application code needs the instance from the first line. Give the host a height; the canvas fills its container.

ts
import { render } from '@grafloria/element';
import type { DiagramInstance } from '@grafloria/renderer';

const host = document.getElementById('diagram');
if (!host) {
  throw new Error('Missing #diagram');
}
host.style.height = '400px';

const api: DiagramInstance = render({
  nodes: [
    {
      id: 'a',
      position: { x: 60, y: 80 },
      size: { width: 180, height: 80 },
      data: { label: 'Ingest' },
    },
    {
      id: 'b',
      position: { x: 380, y: 80 },
      size: { width: 180, height: 80 },
      data: { label: 'Publish' },
    },
  ],
  edges: [{ id: 'e1', source: 'a', target: 'b' }],
}, host);

const diagramModel = api.getModel();
const diagramEngine = api.getEngine();
console.log(diagramModel.getNode('a'), diagramEngine.getDiagram());

window.addEventListener('pagehide', () => api.dispose(), { once: true });

The mounted result shows two labelled boxes connected by an edge. diagramModel is the data layer and diagramEngine is the behavior layer; neither replaces the live instance.

What reconciliation preserves

setNodes() and setEdges() compare the incoming collection by ID. They add new IDs, update existing plain specifications, and remove IDs absent from the incoming collection. A plain specification for an existing ID updates that ID's current live object, so the rendered diagram changes without rebuilding every survivor. A live model passed under an existing ID is different: if it is not the current object, the reconciler replaces the current object with that model. Dropping a node also removes links attached to it.

The following sample makes the distinction observable. The first update keeps node a's object; clearing first makes the next update create a new object.

ts
import { render } from '@grafloria/element';
import type { DiagramInstance } from '@grafloria/renderer';

const host = document.getElementById('diagram');
if (!host) {
  throw new Error('Missing #diagram');
}
host.style.height = '400px';

const api: DiagramInstance = render({
  nodes: [
    { id: 'a', position: { x: 60, y: 80 }, size: { width: 180, height: 80 }, data: { label: 'Ingest' } },
    { id: 'b', position: { x: 380, y: 80 }, size: { width: 180, height: 80 }, data: { label: 'Publish' } },
  ],
  edges: [{ id: 'e1', source: 'a', target: 'b' }],
}, host);

const original = api.getModel().getNode('a');
if (!original) {
  throw new Error('Node a was not created');
}

api.setNodes([
  { id: 'a', position: { x: 100, y: 120 }, size: { width: 180, height: 80 }, data: { label: 'Updated' } },
  { id: 'b', position: { x: 380, y: 80 }, size: { width: 180, height: 80 }, data: { label: 'Publish' } },
]);
const afterPatch = api.getModel().getNode('a');
console.log(afterPatch === original); // true

api.setEdges([]);
api.setNodes([]);
api.setNodes([
  { id: 'a', position: { x: 100, y: 120 }, size: { width: 180, height: 80 }, data: { label: 'Fresh' } },
]);
const afterClear = api.getModel().getNode('a');
console.log(afterClear !== original); // true

window.addEventListener('pagehide', () => api.dispose(), { once: true });

Use the clear-then-apply sequence when an external editor produces fresh objects under IDs that already exist. Otherwise, a surviving ID keeps the current live object and can retain state from the previous document.

Text loading reconciles into the live model

exportText() produces Mermaid-compatible text with Grafloria's lossless sidecar by default. loadText(text) parses that text and reconciles the imported nodes, edges, and groups into the existing diagram; it does not swap out the instance or its model. Imported models under existing IDs replace those current live models, which lets edited text replace stale labels and styles. The method returns an ImportTextResult describing the import.

ts
import { render } from '@grafloria/element';
import type { DiagramInstance } from '@grafloria/renderer';

const host = document.getElementById('diagram');
if (!host) {
  throw new Error('Missing #diagram');
}
host.style.height = '400px';

const api: DiagramInstance = render({
  nodes: [
    { id: 'a', position: { x: 60, y: 80 }, size: { width: 180, height: 80 }, data: { label: 'Ingest' } },
    { id: 'b', position: { x: 380, y: 80 }, size: { width: 180, height: 80 }, data: { label: 'Publish' } },
  ],
  edges: [{ id: 'e1', source: 'a', target: 'b' }],
}, host);

const modelBeforeLoad = api.getModel();
const nodeBeforeLoad = modelBeforeLoad.getNode('a');
if (!nodeBeforeLoad) {
  throw new Error('Node a was not created');
}

const text = api.exportText();
const importResult = api.loadText(text);
const nodeAfterLoad = api.getModel().getNode('a');

console.log(importResult, api.getModel() === modelBeforeLoad); // same model
console.log(nodeAfterLoad !== nodeBeforeLoad); // imported model replaces the old one

window.addEventListener('pagehide', () => api.dispose(), { once: true });

The canvas remains mounted while the imported document appears. Use setNodes() and setEdges() for structured application state; use exportText() and loadText() when the interchange format is Mermaid-compatible text.

Rule of thumb

Start at the instance. Move down only for the operation's owner: model for data, engine for behavior. Reconcile ordinary spec arrays for incremental updates, clear before applying a separately rebuilt document with reused IDs, and use loadText() when the source is Mermaid-compatible text.

Related: Model and document, Text and serialization, Save and restore diagrams, and The DiagramInstance.

Was this page helpful?