Set a node's starting dimensions, constrain a user's resize gesture, or let the renderer fit a node to its label. This page focuses on node sizing; for mounting patterns and framework-specific data bindings, see Build runnable workflows. These steps cover JavaScript, Angular, React, and Vue; the Qwik binding is outside this page's scope.
Mount nodes with sizes and sizing rules
The example renders four nodes: one with a declared starting size, one whose resize gesture has minimums and maximums, one with a locked aspect ratio, and one that opts into content-aware sizing. Give the canvas a real height so the rendered diagram has room to appear.
jsimport { render } from '@grafloria/element';
const host = document.createElement('div');
host.style.height = '700px';
document.body.append(host);
const diagram = render({
nodes: [
{
id: 'sized',
position: { x: 120, y: 80 },
size: { width: 180, height: 90 },
label: 'Declared size: 180 × 90',
},
{
id: 'resizable',
position: { x: 120, y: 220 },
size: { width: 160, height: 100 },
label: 'Resize me: 80–260 wide',
metadata: {
sizing: {
minWidth: 80,
minHeight: 60,
maxWidth: 260,
maxHeight: 200,
},
},
},
{
id: 'ratio',
position: { x: 420, y: 220 },
size: { width: 160, height: 100 },
label: 'Aspect ratio stays at 1.6',
metadata: { sizing: { aspectLock: true } },
},
{
id: 'auto',
position: { x: 120, y: 360 },
size: { width: 60, height: 36 },
label: 'This long label grows beyond the declared sixty-pixel width',
metadata: { sizing: { auto: true, padding: 10 } },
},
],
edges: [],
}, host);
tsimport { Component } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';
import type { LinkModel, NodeModel } from '@grafloria/engine';
@Component({
standalone: true,
imports: [DiagramCanvasComponent],
template: `
<grafloria-diagram-canvas
[(nodes)]="nodes"
[(edges)]="edges"
style="display:block; height:700px" />
`,
})
export class AppComponent {
nodes: readonly (NodeSpec | NodeModel)[] | undefined = [
{
id: 'sized',
position: { x: 120, y: 80 },
size: { width: 180, height: 90 },
label: 'Declared size: 180 × 90',
},
{
id: 'resizable',
position: { x: 120, y: 220 },
size: { width: 160, height: 100 },
label: 'Resize me: 80–260 wide',
metadata: {
sizing: {
minWidth: 80,
minHeight: 60,
maxWidth: 260,
maxHeight: 200,
},
},
},
{
id: 'ratio',
position: { x: 420, y: 220 },
size: { width: 160, height: 100 },
label: 'Aspect ratio stays at 1.6',
metadata: { sizing: { aspectLock: true } },
},
{
id: 'auto',
position: { x: 120, y: 360 },
size: { width: 60, height: 36 },
label: 'This long label grows beyond the declared sixty-pixel width',
metadata: { sizing: { auto: true, padding: 10 } },
},
];
edges: readonly (EdgeSpec | LinkModel)[] | undefined = [];
}
tsximport { GrafloriaFlow } from '@grafloria/react';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';
const nodes: NodeSpec[] = [
{
id: 'sized',
position: { x: 120, y: 80 },
size: { width: 180, height: 90 },
label: 'Declared size: 180 × 90',
},
{
id: 'resizable',
position: { x: 120, y: 220 },
size: { width: 160, height: 100 },
label: 'Resize me: 80–260 wide',
metadata: {
sizing: {
minWidth: 80,
minHeight: 60,
maxWidth: 260,
maxHeight: 200,
},
},
},
{
id: 'ratio',
position: { x: 420, y: 220 },
size: { width: 160, height: 100 },
label: 'Aspect ratio stays at 1.6',
metadata: { sizing: { aspectLock: true } },
},
{
id: 'auto',
position: { x: 120, y: 360 },
size: { width: 60, height: 36 },
label: 'This long label grows beyond the declared sixty-pixel width',
metadata: { sizing: { auto: true, padding: 10 } },
},
];
const edges: EdgeSpec[] = [];
export function App() {
return (
<div style={{ height: 700 }}>
<GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} />
</div>
);
}
vue<script setup lang="ts"> import { GrafloriaFlow } from '@grafloria/vue'; import type { EdgeSpec, NodeSpec } from '@grafloria/renderer'; const nodes: NodeSpec[] = [ { id: 'sized', position: { x: 120, y: 80 }, size: { width: 180, height: 90 }, label: 'Declared size: 180 × 90', }, { id: 'resizable', position: { x: 120, y: 220 }, size: { width: 160, height: 100 }, label: 'Resize me: 80–260 wide', metadata: { sizing: { minWidth: 80, minHeight: 60, maxWidth: 260, maxHeight: 200, }, }, }, { id: 'ratio', position: { x: 420, y: 220 }, size: { width: 160, height: 100 }, label: 'Aspect ratio stays at 1.6', metadata: { sizing: { aspectLock: true } }, }, { id: 'auto', position: { x: 120, y: 360 }, size: { width: 60, height: 36 }, label: 'This long label grows beyond the declared sixty-pixel width', metadata: { sizing: { auto: true, padding: 10 } }, }, ]; const edges: EdgeSpec[] = []; </script> <template> <div style="height:700px"> <GrafloriaFlow :default-nodes="nodes" :default-edges="edges" /> </div> </template>
The first box starts at its declared 180 × 90 size. Select the constrained box to show the renderer's corner and edge resize handles, then drag a handle: its dimensions stay within the declared limits. The adjacent node holds its initial 1.6 width-to-height ratio while you resize it. The bottom node opts into content-aware sizing, so its long label expands it beyond its declared 60-pixel width. The non-auto node remains at its declared dimensions until a user resizes it.
Choose the sizing fields
Put per-node constraints in metadata.sizing. The renderer applies the same constraints to the interactive resize and content-aware sizing paths.
| Option | Type | Default | What it does |
|---|---|---|---|
size | { width: number; height: number } | No default stated | Declares a node's width and height in its NodeSpec. |
metadata.sizing.auto | boolean | Off unless true | Opts that node into content-aware sizing to fit its label. |
metadata.sizing.minWidth, minHeight | number | No per-node constraint; the resizer's global floor still applies | Sets the node's minimum width or height during resizing and auto-sizing. |
metadata.sizing.maxWidth, maxHeight | number | No per-node limit | Sets the node's maximum width or height during resizing and auto-sizing. |
metadata.sizing.aspectLock | boolean | number | Unlocked | true locks to the node's current width-to-height ratio; a positive number locks to that explicit ratio. |
metadata.sizing.padding | number | 8 px | Reserves space around the label when auto-sizing. |
The selected-node handles appear for one selected, resizable, unlocked node. A multi-node selection does not provide proportional resize handles. For automatic fitting, only nodes with metadata.sizing.auto: true grow; padding defaults to 8 pixels unless you set it, as the example does. The auto-sized node's measured dimensions update on the renderer's frame, and the sizing pass uses the same min/max limits and aspect lock as dragging.
Watch the demos
- Auto-sizing nodes shows an opted-in node fitting its label and growing again when the label changes.
- Node resize gesture shows the built-in handles, per-handle cursors, and live min/max/aspect constraints.
- Node resizer contrasts the selected-node resizer with a custom resize control.
Pitfalls
- Register a custom node renderer before mounting the diagram. If a node's type has no renderer when its host mounts, that host stays empty; registering later does not back-fill it. A custom renderer runs at mount, not as a data binding, so update the DOM you own or use a component with its own reactive source when its content must change.
- Give the canvas a parent with a resolved height. A canvas inside a zero-height wrapper has no visible area to draw into.
Was this page helpful?