# How Grafloria works

Grafloria puts one headless engine underneath its JavaScript, React, Vue, Angular, and Qwik
surfaces. The engine owns behavior—commands, history, layout, validation, and collaboration—while
the model owns the diagram data.

```mermaid
flowchart TD
  B["Framework binding"] --> I["DiagramInstance"]
  I --> M["DiagramModel\nnodes, links, groups, viewport"]
  I --> E["DiagramEngine\ncommands, layout, validation, history"]
  M --> R["Renderer\npositions, routes, SVG"]
  E --> R
```

## Start at the binding

Use the binding's canvas component when your application already uses a framework. The component
mounts a real diagram; its `nodes` and `edges` describe the initial or controlled graph, and its
instance callback gives you the shared handle.

### React

```tsx
import { GrafloriaFlow } from '@grafloria/react';

const 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' } },
];
const edges = [{ id: 'e1', source: 'a', target: 'b' }];

export default function App() {
  return (
    <div style={{ height: '100vh' }}>
      <GrafloriaFlow
        defaultNodes={nodes}
        defaultEdges={edges}
        plugins
        onInit={(instance) => instance.fitView()}
      />
    </div>
  );
}
```

The mounted canvas shows two connected nodes. `plugins` adds the minimap, zoom and fit controls,
and background grid. The `onInit` callback receives the live instance after mounting.

### Vue

```vue
<script setup lang="ts">
import { GrafloriaFlow } from '@grafloria/vue';

const 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' } },
];
const edges = [{ id: 'e1', source: 'a', target: 'b' }];
</script>

<template>
  <div style="height: 100vh">
    <GrafloriaFlow :default-nodes="nodes" :default-edges="edges" :plugins="true" />
  </div>
</template>
```

Vue renders the same connected diagram. Use `v-model:nodes` and `v-model:edges` when the Vue
application owns the live arrays; use the `default-*` props when the canvas owns them after mount.

### Angular

```ts
import { Component } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';

@Component({
  selector: 'app-root',
  imports: [DiagramCanvasComponent],
  template: `
    <grafloria-diagram-canvas [(nodes)]="nodes" [(edges)]="edges"
      [plugins]="true" style="display:block; height:100vh" />
  `,
})
export class AppComponent {
  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' }];
}
```

[`DiagramCanvasComponent`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-angular-components-diagramcanvascomponent#diagramcanvascomponent)
uses Angular's two-way `nodes` and `edges` binding here. A drag changes the live model and writes
the next arrays back through those bindings.

### Qwik

```tsx
import { component$, $ } from '@builder.io/qwik';
import { GrafloriaFlow, type DiagramInstance } from '@grafloria/qwik';

const 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' } },
];
const edges = [{ id: 'e1', source: 'a', target: 'b' }];

export default component$(() => (
  <div style={{ height: '100vh' }}>
    <GrafloriaFlow
      defaultNodes={nodes}
      defaultEdges={edges}
      onInit$={$((instance: DiagramInstance) => instance.fitView())}
    />
  </div>
));
```

Qwik uses a QRL callback, but the callback receives the same instance and the canvas shows the same
graph. The binding is a thin surface over the shared engine rather than a separate graph engine.

## The model is the document

The model is the single source of truth. A [`DiagramModel`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-engine-models-diagrammodel#diagrammodel)
contains nodes, links, groups, and viewport data. A node is a [`NodeModel`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-engine-models-nodemodel#nodemodel);
an input edge uses [`EdgeSpec`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-renderer-instance-edgespec#edgespec). A
[`GroupSpec`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-renderer-instance#groupspec) describes a zone around child nodes.

Bindings turn plain specs into live models and reconcile later changes by id. Existing ids keep
their live objects, new ids create objects, and missing ids are removed. This lets selection,
listeners, and renderer state survive ordinary updates. The same document can be persisted,
snapshotted, exported, or shared.

For data queries, use `instance.getModel()`. For example, the model gives you `getNodes()`,
`getLinks()`, and `getGroups()` without reaching into the renderer.

## The instance is the shared handle

Plain JavaScript uses [`render`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-element-core#render) to mount a data spec and return
the live [`DiagramInstance`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-renderer-instance-diagraminstance#diagraminstance):

```ts
import { render } from '@grafloria/element';

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

const instance = 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);

instance.fitView();
instance.on('selection:change', ({ nodes }) => console.log(nodes));
```

Use the instance for specs, painting, events, camera, export, and text round-trips.
`setNodes()` and `setEdges()` reconcile data; `render()` queues a coalesced repaint and
`renderNow()` repaints synchronously; `fitView()` frames all content. Dispose the instance from
your application's unmount or close handler, not immediately after mounting.

## Geometry comes from layout and routing

Nodes, ports, links, groups, directions, and constraints express semantic intent. Rendering and
layout determine positions and paths. Use the [`DiagramEngine`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-engine-engine#diagramengine)
for behavior below the instance:

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

async function arrange(instance: DiagramInstance): Promise<void> {
  await instance.getEngine().layout('elk');
  instance.renderNow();
}
```

This lays out the already-mounted diagram and then paints the resulting geometry. Changing node
data does not re-run a declarative `layout` prop; call the engine's layout method explicitly and
repaint when you drive the instance yourself.

## Commands, history, and events

User gestures become commands on one history. Dragging, connecting, deleting, and grouping
therefore share undo and redo without application wiring. In React, Vue, and Qwik, reach the
engine through the instance; Angular's canvas also exposes its corresponding component methods.

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

async function undoAndRedo(instance: DiagramInstance): Promise<void> {
  await instance.getEngine().undo();
  await instance.getEngine().redo();
}
```

The instance event map is the common event surface. `on()` returns an unsubscribe function, and
the framework bindings surface the same changes as React callbacks, Vue emits, Angular outputs,
or Qwik QRL callbacks. Use the component event first when the binding provides it; use
`instance.on()` for events that belong to the shared instance.

## Appearance and extension points

The renderer ships [`LIGHT_THEME`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-renderer-themes-constants#light_theme) and
[`DARK_THEME`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-renderer-themes-constants#dark_theme). Pass a theme to the component
or call `instance.setTheme()` to change the mounted diagram's appearance. The renderer also ships
[`registerTool`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-renderer-ext-functions#registertool) for canvas tools; it returns a
disposer that restores the previous tool with the same id.

Use the shipped layout adapters, themes, commands, and validators before writing replacements.
The [live demo gallery](https://grafloria.com/demos) shows the same mounted surfaces in operation.

## Next steps

- [Model and document](https://atloria.dev/p/grafloria-h7YM7amryF/developer/model-and-document) for serialization and persistence.
- [Instance and bindings](https://atloria.dev/p/grafloria-h7YM7amryF/developer/instance-and-bindings) for controlled state.
- [Layout and routing](https://atloria.dev/p/grafloria-h7YM7amryF/developer/layout-and-routing) for geometry.
- [Commands, events, and undo](https://atloria.dev/p/grafloria-h7YM7amryF/developer/commands-events-and-undo) for history and event details.
