Skip to content
D
Documentation

Diagram intent and rendering

concept
2 min readUpdated

A Grafloria spec records what nodes represent, where they belong, and how links relate them; the renderer computes link routes and paints the visible diagram.

One model, two layers

The spec is the input vocabulary shared by Grafloria's framework bindings. Bindings reconcile specs into the live diagram model; the renderer reads that model to choose geometry and draw it. The diagram instance is the facade to the mounted canvas, its data model, and its engine.

mermaid
flowchart LR
  S["Node and edge specs"] --> M["Live diagram model"]
  M --> R["Renderer: route and paint"]
  R --> V["Visible diagram"]
  U["User edit"] --> M

For nodes, the spec describes geometry such as position and size along with a label and application data. For edges, source and target identify nodes; optional handles express endpoint intent. Omit handles to let the renderer select the port-facing side as nodes move, or name a handle to pin an endpoint. A router chooses the route, while connector controls how a routed polyline is drawn. type is the edge shape shorthand; when router and connector are omitted, they are derived from it.

The model stores intent rather than a drawing frozen into pixels. An obstacle-avoiding route, for example, is recalculated from the current node positions. A hand-edited route is different: explicit waypoints record the bends the renderer must preserve.

Render a routed diagram

Use the render function when you want to mount a spec directly in a browser host. The sample's nodes place a wall between two endpoints, and its edge requests an orthogonal route with obstacle avoidance.

html
<div id="app" style="width: 800px; height: 420px"></div>
ts
import { render } from '@grafloria/element';
import type { RenderSpec } from '@grafloria/element';
import type { DiagramInstance, EdgeSpec, NodeSpec } from '@grafloria/renderer';

const nodes: NodeSpec[] = [
  { id: 'start', position: { x: 80, y: 150 }, size: { width: 130, height: 56 }, label: 'Start' },
  { id: 'wall', position: { x: 350, y: 90 }, size: { width: 140, height: 170 }, label: 'Obstacle' },
  { id: 'finish', position: { x: 650, y: 150 }, size: { width: 130, height: 56 }, label: 'Finish' },
];

const edges: EdgeSpec[] = [
  { id: 'path', source: 'start', target: 'finish', type: 'orthogonal', router: 'avoid' },
];

const spec: RenderSpec = { nodes, edges };
const container = document.getElementById('app')!;
container.style.width = '800px';
container.style.setProperty('height', '420px', 'important');
const instance: DiagramInstance = render(spec, container);
instance.fitView();

fitView() frames all three nodes in the mounted canvas. router: 'avoid' selects obstacle avoidance, and type: 'orthogonal' gives the routed path right-angle geometry. The endpoint handles are omitted, so the renderer uses the port-facing attachment behavior rather than pinning either end to a named side.

The same intent in framework bindings

The framework component changes how you mount and size the canvas; the NodeSpec and EdgeSpec data still describe the same graph. These examples use default data so the component owns the initial specs rather than receiving controlled state.

Angular

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

@Component({
  selector: 'app-diagram-intent',
  standalone: true,
  imports: [DiagramCanvasComponent],
  template: `
    <div class="canvas">
      <grafloria-diagram-canvas [(nodes)]="nodes" [(edges)]="edges" />
    </div>
  `,
  styles: [':host { display: block; } .canvas { height: 420px; }'],
})
export class DiagramIntentExampleComponent {
  nodes: NodeSpec[] = [
    { id: 'start', position: { x: 80, y: 150 }, size: { width: 130, height: 56 }, label: 'Start' },
    { id: 'wall', position: { x: 350, y: 90 }, size: { width: 140, height: 170 }, label: 'Obstacle' },
    { id: 'finish', position: { x: 650, y: 150 }, size: { width: 130, height: 56 }, label: 'Finish' },
  ];

  edges: EdgeSpec[] = [
    { id: 'path', source: 'start', target: 'finish', type: 'orthogonal', router: 'avoid' },
  ];
}

The Angular canvas starts from the same nodes and edge. Two-way bindings keep the component's edited node and edge arrays in the component fields.

Keep the distinction clear

  • Use node positions and sizes to describe box geometry; use edge endpoints and handles to describe connection intent.
  • Choose a router for where a line travels and a connector for how its path is drawn. type is the shorthand shape setting, not a node position.
  • Use waypoints when the bends themselves are part of the saved intent. Without manual waypoints, the route follows the current endpoints and routing rules.
  • The spec passed to render() is an object (or its JSON), not a Mermaid-style text DSL. Use the text import API when your input is diagram text.

For a running view of an edge routed around a movable obstacle, see the edge-routing demo. For the broader model vocabulary, continue to The graph model and document, then see Route and edit edges for user-edited bends.

Was this page helpful?

Diagram intent and rendering — Grafloria · GPT-6 Luna