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.
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
| Value | Coordinates / ownership |
|---|---|
Saved node.bounds | Parent-local authored position and size |
Scene localBounds | Projected local body, including content or container-derived size |
Scene transform | Local-to-world affine matrix |
Scene worldBounds | Axis-aligned world extent; a rotated body's width is not its local width |
Scene sizeSource | model, measurement or preview |
Scene port layoutPosition | Configured node-local position before DOM measurement overrides |
Scene port position | Effective world anchor, including DOM measurement when present |
| Viewport screen point | Viewport-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.