Diagram - Geometry and measurement

Separate saved geometry from measured content, port positions and scene overrides.

#Usage

import { Diagram } from '@primeui/vue-diagram';
<Diagram.Root :store="store" page-id="page">
    <Diagram.NodeDef node-type="measured" v-slot="{ node }">
        <strong>{{ node.data?.['title'] }}</strong>
    </Diagram.NodeDef>
    <Diagram.Viewport label="Measured nodes" :virtualization="{ measureNodes: true }" style="height: 24rem" />
</Diagram.Root>

The saved node must have port reply. Measurements describe mounted presentation geometry; saved bounds remain model data.

This skeleton assumes an initialized store with page page. Use First Diagram for model creation, style imports and store disposal.

Saved bounds and rotation describe portable authored geometry. Rich HTML content, mounted text metrics and preview geometry can change the scene without changing those records. Choose which source you need before reading a coordinate.

#Measure intrinsic content and a port

Change Content size, then Fit. The scene follows the intrinsic card while saved bounds stay 170 × 96. Reply port is a separate boundary control. The connector uses that saved port ID and follows the control's measured local position.

Loading Demo...

The viewport's virtualization.measureNodes defaults false. Enabling it measures the first content element of mounted ordinary nodes. Containers and symbol nodes do not use intrinsic HTML body sizing. Custom content must have a stable, intentional intrinsic size; a width derived from its own measured bounds can create feedback. The runtime keeps a bounded convergence epoch and exposes root.measurements.pausedIds for entries it has paused.

Inside Diagram.NodePortsDef, Diagram.Port binds getPortProps to an existing port's native button for both measurement and connection interaction. Outside this region it only measures an anchor. Neither use creates a saved port. See Ports for the complete button composition. Measurements invert viewport CSS scaling and the node transform, including rotation, to produce a node-local point.

The ports slot supplies configured local positions before measured overrides. Use these positions to place controls without feeding a measured anchor back into its own layout. The ports region bypasses the body-shape clip and follows node rotation. Its controls do not enlarge intrinsic body size, model bounds, or Fit bounds. The same measurement owner observes both regions and deduplicates stable values. Normal connection policy still applies to the saved port.

#Read the appropriate bounds

ValueCoordinates / ownership
Saved node.boundsParent-local authored position and size
Scene localBoundsProjected local body, including content or container-derived size
Scene transformLocal-to-world affine matrix
Scene worldBoundsAxis-aligned world extent; a rotated body's width is not its local width
Scene sizeSourcemodel, measurement or preview
Scene port layoutPositionConfigured node-local position before DOM measurement overrides
Scene port positionEffective world anchor, including DOM measurement when present
Viewport screen pointViewport-local CSS pixels; use session screen/world conversion

Move commands take world deltas; editing a nested local coordinate requires conversion through its owner. See Containers and Rotate. Do not persist a DOM bounding rectangle as authored parent-local bounds.

#Convert an application pointer

For an application-owned pointer action, convert client coordinates to the viewport's content box. The viewport computes x as (clientX - rect.left) * (offsetWidth / (rect.width || 1)) - clientLeft, with the matching height/top calculation for y. This accounts for the element's CSS scale and border; simply subtracting the client rectangle is insufficient in a scaled container. Pass that viewport-local CSS-pixel point to session.value.screenToWorld(point) from descendant useDiagramContext() to obtain page-world coordinates; worldToScreen(point) performs the inverse. Do not multiply either point by device pixel ratio. A nested node still needs its owner's inverse transform before a world position becomes saved local bounds. Use reveal(id) or fit([id]) to navigate to the resulting record; those actions change the camera rather than its saved position.

#Invalidate and clean up measurements

Call root.measurements.invalidate(ids?) after an external font/theme/content change whose effect does not resize the observed element. Font loading also invalidates measurements. The service batches DOM reads before geometry writes. Explicit invalidation resets paused epochs; model changes and a resize after the quiet interval can begin another epoch too.

Mounted node/label registrations unregister when their views are destroyed. Page/session replacement reconciles their ownership. Offscreen label metrics fall back to portable estimates until remount; intrinsic node sizes keep their last known dimensions. Destroying the root disconnects its observers, font listener and scheduled measurement frame. Hiding a sidebar does not destroy it.

#Supply presentation geometry deliberately

Root sceneOptions accepts stable maps for measurements, portMeasurements, labelMeasurements, rotationOrigins, routes and connectorKinds, plus symbol and routing options. Use the exact typed scene contract. These are runtime presentation inputs, not model fields or a silent resize/history edit. An externally supplied route must agree with resolved endpoints; invalid geometry can make the scene unavailable and expose diagnostics.

import { type DiagramSceneOptions } from '@primeui/vue-diagram';

const presentation: DiagramSceneOptions = {
    measurements: new Map([['request', { width: 220, height: 120 }]])
};

The application owns supplied maps and their refresh. Keep their ref identity stable until a relevant change, and dispose any observers/workers you create. For export, intentionally choose authored versus measured presentation through the export contract; copied HTML content is not automatically converted to SVG. See the model and Virtualization.