Learn / The model

The model

Everything in Grafloria — the React component, the Angular canvas, the web component — is a thin skin over one headless model in @grafloria/engine. Understand the model once and every binding makes sense.

Two layers, one truth

The engine (DiagramEngine) owns behavior: commands, history, layout, validation, collaboration. The diagram (DiagramModel) owns the data: nodes, links, groups, viewport. The framework bindings hand you a friendlier spec layer ({ id, position, data, ports } objects), convert it to models internally, and hand models back in change events. This page is the layer underneath — it runs identically in the browser, in a worker, and in Node (the snippets below execute in plain Node against the published package).

import { DiagramEngine, NodeModel, PortModel, LinkModel } from '@grafloria/engine';

const engine  = new DiagramEngine();
const diagram = engine.createDiagram('order-flow');

const intake = new NodeModel({
  id: 'intake', type: 'task',
  position: { x: 40, y: 60 }, size: { width: 120, height: 48 },
});
intake.setData('label', 'Intake');

const review = new NodeModel({
  id: 'review', type: 'task',
  position: { x: 260, y: 60 }, size: { width: 120, height: 48 },
});
review.setData('label', 'Review');

diagram.addNode(intake);
diagram.addNode(review);
diagram.addLink(new LinkModel({
  id: 'l1',
  source: { nodeId: 'intake' },
  target: { nodeId: 'review' },
}));

Nodes

A node is an id, a type (which selects its renderer — a shape, a custom component, a template), geometry (position, size), and two open dictionaries: data for your payload (setData/getData) and metadata for Grafloria-adjacent settings (label, shape styling, port groups). In the framework spec layer, label: 'Intake' and data: { label: 'Intake' } are the convenient front doors to the same places.

Ports — four by default, precise on demand

Every node is born with four deterministic bi-directional ports (top, right, bottom, left) — the industry default, so plain flowcharts need zero port ceremony. The moment you need precision, add your own:

intake.addPort(new PortModel({ id: 'intake-out', type: 'output', side: 'right' }));

// reach them later
intake.getPorts();                    // all ports
intake.getPortBySide('right');
diagram.getPortById('intake-out');
diagram.getNodeByPortId('intake-out');

A port has a direction (input / output / bi), a side, an optional dataType (drives both its color and connection compatibility), connection caps, glyph shape, label, and layout strategy. The full surface is on Ports & validation.

Links

A link connects two endpoints. Each endpoint names a node — and optionally a specific port; omit portId and the renderer picks the best side as things move:

new LinkModel({
  id: 'l1',
  source: { nodeId: 'intake', portId: 'intake-out' },   // pinned to a port
  target: { nodeId: 'review' },                          // free — best side wins
});

diagram.getLinksForNode('intake');   // every link touching a node

In the spec layer this is just { source: 'intake', target: 'review' } — with sourceHandle/targetHandle to pin ports, and type: 'orthogonal' | 'smooth' | 'bezier' | 'direct' to pick geometry. Routing (including the obstacle-avoiding router) is the renderer's job; the model stores intent, not pixels.

Groups

Groups are containers with real semantics — membership (engine.addToGroup), collective drag, collapse/expand (engine.collapseGroup), and nesting (diagram.getAncestors/getDescendants). A group can render with full chrome or frameless (frameChrome: 'none') for dashboard-style packing.

Serialization — the document is the API

The whole diagram round-trips through one JSON document with a schemaVersion and migrations on load — save it anywhere:

const doc = diagram.serialize();
// { schemaVersion, id, uuid, type, version, metadata, name, nodes, links, groups, viewport }

const engine2 = new DiagramEngine();
engine2.createDiagram('restored');
engine2.deserialize(doc);
engine2.getDiagram().getNode('intake').getData('label');   // 'Intake' — everything survives
The same document shape is what snapshot()/loadSnapshot() (Angular), DiagramSerializer (React/Vue), SVG/PNG embedModel export, and the collaboration op-log all speak. There is exactly one persistence format to learn.

Where next