Skip to content
D
Documentation

How Grafloria works

concept
3 min readUpdated

Grafloria is one headless diagram model with framework bindings around it: the binding mounts the diagram, the instance connects its rendered canvas to its data and engine, and the renderer turns model intent into pixels.

One headless model, every framework

Use the binding that fits your application. Each example mounts the same two-node flow; drag a node or connect nodes in the resulting canvas, regardless of framework.

React

For a runnable React flow and its initial-data props, see the React quick start. The framework component mounts the shared model rather than a React-specific diagram model.

Vue

For a runnable Vue flow and its initial-data props, see the Vue quick start. Vue also mounts the shared model; the binding changes how you express the component, not the diagram's underlying model.

Qwik

Qwik renders the component on the server and resumes it in the browser; follow the Qwik quick start for a runnable component setup.

Angular

Angular uses DiagramCanvasComponent with two-way node and edge bindings. The canvas displays the same flow, and user edits update the bound arrays.

ts
import { Component } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';

@Component({
  selector: 'app-root',
  imports: [DiagramCanvasComponent],
  template: `
    <grafloria-diagram-canvas
      [(nodes)]="nodes"
      [(edges)]="edges"
      style="display: block; height: 400px" />
  `,
})
export class AppComponent {
  nodes: NodeSpec[] = [
    { id: 'ingest', position: { x: 60, y: 80 }, size: { width: 180, height: 80 }, label: 'Ingest' },
    { id: 'publish', position: { x: 380, y: 80 }, size: { width: 180, height: 80 }, label: 'Publish' },
  ];
  edges: EdgeSpec[] = [{ id: 'flow', source: 'ingest', target: 'publish' }];
}

Each binding translates its convenient spec inputs into the same live model. The model is headless: it describes the diagram without depending on React, Vue, Qwik, Angular, or a DOM. That is why the same data and engine behavior make sense in each binding.

Two layers, one truth

The diagram owns data; the engine owns behavior. A DiagramInstance is the renderer-level facade between them and the canvas.

mermaid
flowchart LR
  Specs["Framework specs"] --> Model["DiagramModel: nodes, links, groups, viewport"]
  Engine["DiagramEngine: commands, history, layout, validation"] --> Model
  Model --> Instance["DiagramInstance: canvas and model facade"]
  Instance --> Pixels["Renderer: visible diagram"]

For plain JavaScript, render mounts a spec into a real, sized host and returns that same kind of instance. This complete browser example renders two boxes, fits them into view, and reads the live model's document:

ts
import { render } from '@grafloria/element';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';

const nodes: NodeSpec[] = [
  { id: 'ingest', position: { x: 60, y: 80 }, size: { width: 180, height: 80 }, label: 'Ingest' },
  { id: 'publish', position: { x: 380, y: 80 }, size: { width: 180, height: 80 }, label: 'Publish' },
];
const edges: EdgeSpec[] = [{ id: 'flow', source: 'ingest', target: 'publish' }];

const container = document.createElement('div');
container.style.height = '400px';
document.body.append(container);

const instance = render({ nodes, edges }, container);
instance.fitView();

const diagram = instance.getModel();
const engine = instance.getEngine();
const activeDiagram = engine.getDiagram();
if (activeDiagram === null) {
  throw new Error('The engine has no active diagram.');
}
const savedDocument = activeDiagram.serialize();
const savedJson = JSON.stringify(savedDocument);

The canvas shows the two labeled boxes and their edge. getModel() returns the live DiagramModel, where diagram data lives; getEngine() returns the DiagramEngine, where behavior lives. serialize() produces the document object, and JSON.stringify() turns it into JSON text. Use the instance for canvas-facing work, the model for data queries, and the engine for behavior.

The document is the API

The model serializes the diagram as one JSON document: it includes the schema version, diagram metadata and name, nodes, links, groups, and viewport. The same document shape is used for persistence and restoration, so data does not need a separate format for each framework. savedDocument in the example above is that document object; savedJson is its JSON representation.

User edits are commands on one history stack

When a user drags, connects, deletes, or groups an item in the mounted canvas, Grafloria turns the gesture into a command on the engine's shared history stack. Try dragging either node in one of the mounted examples and pressing Ctrl+Z (or ⌘Z on macOS): the move is undone. The framework binding reflects the changed model back into application state. By contrast, adding initial specs or loading a document is setup, not a user edit to undo.

See Command history and edits for commands and programmatic edits.

The model stores intent, not pixels

Node specs provide identity, label, and geometry; an edge spec identifies its source and target. The renderer decides how to draw that intent. For example, an edge without pinned handles can use the side that faces its partner, so the connection adapts as nodes move instead of preserving a pixel path.

In the examples, position locates each node and source/target describe the edge. Grafloria computes and draws the connection on the canvas; the spec does not contain the rendered pixels. See Diagram intent and rendering for geometry and routing.

Where next

Was this page helpful?