Diagram - Architecture
Build your first canvas and learn how the model, interaction state and Vue parts work together.
#Usage
Import Diagram for the root, viewport and template definitions, the palette target from @primeui/vue-diagram/palette, and the copied UI parts. The local paths assume you copied the PrimeOne or Tailwind Diagram parts into your application under the @ui/diagram alias.
import { Diagram } from '@primeui/vue-diagram';
import { DiagramPaletteTarget } from '@primeui/vue-diagram/palette';
import { DiagramEditorUI, DiagramToolbarUI, DiagramViewControlsUI, DiagramPersistenceUI, DiagramWorkspaceUI, DiagramSidebarUI, DiagramPaletteUI, DiagramCanvasUI, DiagramInspectorUI, DiagramNodeContentUI } from '@ui/diagram';
import { DiagramOutputUI } from '@ui/diagram/output';<Diagram.Root :store="store" :page-id="pageId" :symbols="symbols" :aid-options="aids">
<Diagram.NodeDef node-type="task" v-slot="{ node }">
<DiagramNodeContentUI align="start">
<template #header><strong>{{ node.data?.['title'] }}</strong></template>
<Diagram.Field name="title" v-slot="{ getInputProps }">
<input v-bind="getInputProps({ 'aria-label': 'Task title' })" />
</Diagram.Field>
<template #footer>{{ node.data?.['status'] }}</template>
</DiagramNodeContentUI>
</Diagram.NodeDef>
<DiagramEditorUI>
<template #toolbar>
<DiagramToolbarUI title="Workflow editor">
<DiagramViewControlsUI :aid-options="aids" @aid-options-change="aids = $event" />
</DiagramToolbarUI>
</template>
<template #utilities>
<DiagramPersistenceUI :store="store" :controller="persistence" :loader="loader" :saver="saver" />
<DiagramOutputUI filename="workflow" :base-options="exportOptions" />
</template>
<template #status>
<DiagramWorkspaceUI :active-layer-id="layerId" @layer-change="layerId = $event" @page-change="changePage" />
</template>
<template #start>
<DiagramSidebarUI title="Shapes">
<DiagramPaletteUI v-if="target" :collection="collection" :registry="symbols" :target="target" />
</DiagramSidebarUI>
</template>
<template #canvas>
<DiagramCanvasUI>
<DiagramPaletteTarget ref="target" :layer-id="layerId">
<Diagram.Viewport label="Workflow editor" :auto-fit="true" style="height: 32rem">
<Diagram.ContextMenu />
<Diagram.UserHandle command-id="duplicate" v-slot="{ command, execute }">
<button type="button" :disabled="!command.available" :title="command.reason" @click="execute()">Duplicate</button>
</Diagram.UserHandle>
</Diagram.Viewport>
</DiagramPaletteTarget>
</DiagramCanvasUI>
</template>
<template #end>
<DiagramSidebarUI title="Selection">
<DiagramInspectorUI>
<Diagram.Inspector v-slot="{ selectedIds }"><p>{{ selectedIds.length }} selected</p></Diagram.Inspector>
</DiagramInspectorUI>
</DiagramSidebarUI>
</template>
</DiagramEditorUI>
</Diagram.Root>The tree above is an anatomy map: it shows how the runtime and copied UI parts compose an editor. Declare only the parts your application needs. The store holds the model and history; the root supplies the interaction session. The viewport renders shapes, connectors, labels, guides and swimlane controls from that state. Node definitions, overlays and inspector content let you add application UI while keeping runtime editing behavior.
This skeleton assumes an initialized store, a registered symbol library and palette collection, and host-owned pageId, layerId and aids refs. changePage selects a page and a valid insertion layer. Editor composition shows the complete setup, including toolbar commands, palette insertion, workspace controls and a custom sidebar. The copied parts use the same runtime with either preset; the editor shell is optional.
The persistence panel uses a persistence controller and your loader and saver callbacks; Save and Load shows how to connect them. The output panel adds SVG, PNG, JPEG and print. exportOptions supplies portable artwork and theme settings: custom HTML node content is not captured automatically. See Export for output setup. Layouts and notation parts can be added independently.
Start with First Diagram below for a complete canvas without the editor panels. Follow Setup for installation, licensing and global styles.
#Runtime Model
Diagram separates the saved graph from a particular view of it.
| Layer | Responsibility |
|---|---|
DiagramModel | Portable pages, layers, nodes, connectors, labels, and metadata. |
DiagramStore | Validated model changes, revision, history, transactions, and save checkpoints. |
DiagramSession | One page view, selection, focus, camera, commands, and interaction previews. |
Diagram.Root | Source ownership, one session, node definitions, contexts, and subscriptions. |
Diagram.Viewport | HTML node content, SVG connectors, input handling, measurement, and rendering. |
| Copied UI | Application-owned toolbar, panels, inspector, explorer, and output controls. |
Two roots can share a store and retain independent sessions. Multiple viewports inside one root share its session and camera.
#First Diagram
This component shows Request and Review joined by a connector. Drag either node to move it. The root owns the store created from value; the model reference remains stable while the user edits.
Load the global styles and register your license as described in Setup.
<script setup lang="ts">
import { Diagram, type DiagramModel } from '@primeui/vue-diagram';
const value: DiagramModel = {
schemaVersion: 1,
id: 'review-workflow',
pages: [{ id: 'page', name: 'Workflow' }],
layers: [{ id: 'layer', pageId: 'page', name: 'Steps' }],
nodes: [
{ id: 'request', type: 'task', pageId: 'page', layerId: 'layer', bounds: { x: 0, y: 0, width: 180, height: 100 }, rotation: 0, ports: [], labels: [], data: { title: 'Request' } },
{ id: 'review', type: 'task', pageId: 'page', layerId: 'layer', bounds: { x: 300, y: 0, width: 180, height: 100 }, rotation: 0, ports: [], labels: [], data: { title: 'Review' } }
],
connectors: [{ id: 'request-review', type: 'straight', pageId: 'page', layerId: 'layer', source: { kind: 'node', nodeId: 'request' }, target: { kind: 'node', nodeId: 'review' }, waypoints: [], labels: [] }]
};
</script>
<template>
<Diagram.Root :value="value" page-id="page">
<Diagram.NodeDef node-type="task" v-slot="{ node }">
<strong class="node-title">{{ node.data?.title }}</strong>
</Diagram.NodeDef>
<Diagram.Viewport label="Review workflow" auto-fit style="height: 24rem" />
</Diagram.Root>
</template>
<style scoped>
.node-title {
display: grid;
place-items: center;
height: 100%;
}
</style>:auto-fit="true" fits once after the viewport first measures a nonzero size.
Give it an explicit height. Later container resizing preserves the user's view.
A mounted command button or a descendant's current session.value.fit() fits
again; see Pan and Zoom.
#Read or share the store
Put this component under Diagram.Root, for example beside its viewport. It reads the current model and session after reloads as well as after edits. Access refs with .value in setup; templates unwrap the destructured refs.
<script setup lang="ts">
import { computed } from 'vue';
import { useDiagramContext } from '@primeui/vue-diagram';
const { model, interaction, store } = useDiagramContext();
const selectedCount = computed(() => interaction.value.selectedIds.length);
function inspectModel() {
console.log(store.value.getSnapshot());
}
</script>
<template>
<p>{{ model.nodes.length }} nodes, {{ selectedCount }} selected</p>
<button type="button" @click="inspectModel">Inspect current model</button>
</template>Calling useDiagramContext() in the parent that declares the root cannot read that root. Use a child component. Reading store.getSnapshot() alone does not establish Vue reactivity.
#Reload a root-owned model
Keep the value reference stable while editing. A new reference creates a fresh store and session, even for equal content or the same model ID. History, selection, drafts and viewport initialization start again; the new store has a clean save baseline. Assign a new model reference for Reset or loading another file.
value is one-way. Ordinary edits emit commit; reloads do not. Record commits for persistence without assigning their models back to value after every edit. An unchanged input reference preserves edits and history.
The root validates a replacement model and requested page before cancelling active work. Invalid updates emit sourceError and leave the valid store, session, history and gesture intact. Without a handler, development reports the rejection through console.error. A later valid source/page tuple recovers.
#Data Model
DiagramModel is a portable document containing pages, layers, nodes, connectors, labels and metadata. Records use stable caller-assigned IDs. Unparented bounds use page-world coordinates; child bounds are parent-local. Runtime stores, sessions, components and callbacks stay outside the model.
| Field | Meaning in the example |
|---|---|
schemaVersion | The model schema version, currently 1. |
id | A caller-assigned, case-sensitive ID. Record, port and label IDs must be unique across the model. |
pageId, layerId | An existing page and a layer belonging to that page. |
bounds | Position and positive width/height in page world units for unparented nodes. |
rotation | Degrees, initially 0. |
ports, labels | Explicit empty arrays when the node has no authored ports or labels. |
data | JSON-safe application values, such as the displayed title. |
| Connector endpoints | Existing node IDs; an optional port ID names a port on that node. |
Read Model for record fields, validation, coordinate spaces and immutable snapshots.
#State Ownership
Supply page-id and exactly one of :value or :store. The page must exist in the model. undefined means that a source is absent; null, both sources, or neither source are invalid.
| Source | Owner | Update behavior |
|---|---|---|
:value="model" | Root owns its managed store and session. | A new model reference reloads the document with empty history and a clean save baseline. |
:store="store" | Application owns the store. Root owns its session. | A different store replaces the session. The previous supplied store remains alive. |
useDiagramStore(initialModel) | The active Vue setup or effect scope owns the returned store. | Initial model is sampled once. Scope disposal releases that store. |
value is a one-way reload input. Root v-model and v-model:value are unsupported. Do not feed each commit back into value, because that clears history. Read current edits through context or the store. A page-only change keeps the store and its history while replacing the session.
For a component-owned store, use const store = useDiagramStore(initialModel) in setup, then bind :store="store". For advanced options such as controlled transactions or notation modules, use createDiagramStore(model, options), check its ok result, and dispose the successful store after its consumers detach.
Node bounds, connector endpoints and labels belong to the model. Selection,
active tools, zoom and pan belong to the session. Selecting or fitting creates
no Undo entry; a committed resize or connection does. Pointer gestures preview
changes before committing. Cancellation discards the preview, while a completed
gesture becomes one atomic change. Descendant controls can use
session.value.edit(...) with the same editing policy and validation.
#Instance types and factories
| Instance | Factory / owner | Lifetime |
|---|---|---|
DiagramStore | Root value, useDiagramStore(initialModel), or createDiagramStore(model, options) | Managed root/scope stores are released by their owner; supplied stores remain application-owned. |
DiagramSession | Root, or createDiagramSession(store, pageId, options) | One interaction view; manually created sessions require caller disposal. |
| Optional controller | Capability factory or mounted provider | Release subscriptions, workers and resources with that owner. |
Dispose a manually created session before its store. DiagramStore and
DiagramSession are instance types, not constructors exported by the Vue entry.
typeof DiagramStore and InstanceType<typeof DiagramStore> are unsupported;
use typeof createDiagramStore for the factory signature.
Factory results narrow on ok. Check diagnostics before adopting a successor model or store. Keep a shared store alive until its borrowing roots and controllers detach.
#Read-Only Diagram
Editing policy controls authoring availability. A read-only canvas can retain navigation and inspection. This example shows the resulting policy and available controls.
Try selection or pan, then try dragging a node: its saved position stays unchanged.
Set :editing-options="{ readOnly: true }" on the root; there is no separate root
readOnly prop. This policy controls editor interactions. Direct application
store transactions retain host authority and must enforce the host's rules.
#Compound Anatomy
A viewport renders the graph without requiring a copied editor. Add only the controls your product needs. DiagramEditorUI, DiagramToolbarUI, and other names ending in UI are local application components. They consume the runtime rather than creating a second graph.
Optional palette, layout, notation, and output parts come from their documented public subpaths. See Imports, UI parts, and Slots and contexts.
| Part | Role | Guide |
|---|---|---|
Diagram.Root | Connects the store to a session and supplies shared context. | First Diagram |
Diagram.Viewport | Measures/renders nodes, connectors, labels, aids and swimlane tools; no separate child renderers are required. | Pan and Zoom |
Diagram.NodeDef, Diagram.NodePortsDef | Register fallback/type-specific body and port slots independently. Render Vue components inside a slot for component-backed content. | Node UI |
Diagram.Field, Diagram.Port | Bind native controls to saved fields or an existing saved port; a marker does not create a port record. | Content, Ports |
Diagram.CommandButton | Supplies button props for root-command availability and execution. | Commands |
Diagram.Inspector | Supplies selection, command states and authoring actions through its scoped slot. | Inspector |
Diagram.UserHandle, Diagram.ContextMenu, Diagram.Tooltip | Host overlay slots under the viewport. Custom content retains roles, focusable actions and dismissal. | Overlays |
#Standalone Imports
Import individual parts when a component needs a smaller declaration set. The compound namespace and flat prefixed exports share registration and context identity.
import { DiagramRoot, DiagramViewport, DiagramNodeDef } from '@primeui/vue-diagram/components';Optional capabilities use their documented public subpaths. Copied UI parts remain application-owned source. See Imports and Tree Shaking.
#Coordinates and Rendering
Declare Diagram.NodeDef under the root. Its slot receives plain readonly node, geometry, and actions. A child component rendered by that slot can use useDiagramNodeContext(). Diagram.NodePortsDef defines port content independently of the body. For each definition family, an exact node-type match wins over a fallback with no type; the last matching declaration wins.
Unparented bounds use page-world coordinates. Child bounds are local to their parent. Zoom changes the view, not saved dimensions. Resizing a parent does not automatically scale its children. Use scene geometry for effective measured bounds and model records for authored values.
Use Content for saved fields and Geometry measurement for measured ports or content. Change records through public actions and transactions rather than mutating readonly snapshots.
For large graphs, navigate the 1,000-node fixture and limit rich views. Aggregate geometry keeps queried records visible when their rich views are absent.
#Initialize and Clean Up
Root and viewport handles become available after client mount. Descendant components use context for reactive reads; parent event handlers can use a typed template ref for imperative operations. A root releases its session and any managed store on unmount. It never disposes a supplied store.
Dispose application-created sessions, controllers, workers, subscriptions, and output resources with their owner. Keep a shared store alive until all its consumers detach. SSR and lifecycle lists those lifetimes.
#Feature Surface
| Need | Start here |
|---|---|
| An editor with a toolbar and panels | Editor Composition and UI Parts. |
| Reusable symbols and palette insertion | Symbols and Palette. |
| Pages, layers, containers or swimlanes | Pages and Layers, Groups and Containers and Swimlanes. |
| Automatic arrangement | Choose a Layout. |
| BPMN, UML Class, ER, Activity or Sequence records | Choose a Notation. Register the matching store modules and any referenced symbols. |
| Files and printed output | Save and Load, Export, Print and BPMN XML. |