# DiagramModel

Import it from `@grafloria/engine`.

Also has every member of `DiagramEntity`, listed on its own entry.

```ts
class DiagramModel extends DiagramEntity
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` | `'Untitled Diagram'` |  |
| `nodes` | `Map<string, NodeModel>` |  |  |
| `links` | `Map<string, LinkModel>` |  |  |
| `groups` | `Map<string, GroupModel>` |  |  |
| `strokes` | `Map<string, StrokeModel>` |  | Freehand ink strokes. See StrokeModel for why these are not nodes. |
| `linkIntegrityOwner` | `'model' \| 'external'` | `'model'` | Who owns the invariant "a link whose node is gone is not a link"? |
| `viewport` |  |  |  |

**Methods**

- `get comments(): CommentRegisterTree` — The comment register tree. Read-only in spirit: write through writeCommentRegister. The landing pad for `applyOp`'s whole-tree write (remote ops, replay, load).
- `set comments(next: CommentRegisterTree)` — The comment register tree. Read-only in spirit: write through writeCommentRegister. The landing pad for `applyOp`'s whole-tree write (remote ops, replay, load).
- `writeCommentRegister(path: string, value: unknown): boolean` — Write ONE comment register — `t1.status`, `t1.messages.m3` — and nothing else.
- `readCommentRegister(path: string): unknown` — Read one register. Returns undefined for any missing segment — never throws.
- `constructor( name?: string, options?: { lodConfig?: LODConfig; id?: string; uuid?: string } )`
- `refreshLinkBounds(link: LinkModel): void` — Re-index a link after its geometry changed WITHOUT a `change:points` event.
- `isReadonly(): boolean` — Is this document locked against edits?
- `setReadonly(value: boolean): void` — Lock / unlock the document. Normally driven by `DiagramEngine.setMode()` — VIEW and PRESENTATION lock, DESIGNER unlocks — so `DiagramMode` finally means something. Can also be set directly for a host that has no mode concept.
- `blocksDocumentWrite(): boolean` — True when a document mutation must be refused right now.
- `inSystemWrite(): boolean` — True while a SYSTEM write is in flight. Read by the PER-NODE geometry lock (`NodeState.locked`) so it exempts measured writes exactly as the document lock does — see readonly-lock.ts.
- `runSystemWrite<T>(fn: () => T): T` — Run a SYSTEM write — a derived/measured value (auto-size, portal placement) the engine needs in order to render the document as it already is. Permitted even while locked. NOT reachable from user input; see readonly-lock.ts.
- `addNode(node: NodeModel): void`
- `removeNode(nodeId: string): NodeModel | undefined` — Remove node from diagram — AND every link attached to it.
- `replaceNode(next: NodeModel): void` — Swap the live model under `next.id` for `next`, KEEPING the links attached to it. (An id not on the canvas is simply added.)
- `getLinksForNode(nodeId: string): LinkModel[]` — Every link with an endpoint on `nodeId` (either end, including a self-loop).
- `restoreNode(data: any): NodeModel | undefined` — Restore node from serialized data
- `getNode(nodeId: string): NodeModel | undefined` — Get node by ID
- `getNodes(): NodeModel[]` — Get all nodes
- `getNodeByPortId(portId: string): NodeModel | undefined` — Get node that owns a specific port Used for connection group validation and other port-based queries. O(1) via the portIndex (was an O(nodes×ports) linear scan).
- `getPortById(portId: string): PortModel | undefined` — Get the port model for a port id, O(1) via the portIndex. Companion to getNodeByPortId for callers that need the port itself.
- `getDetachedAnchor(nodeId: string): DetachedParentAnchor | undefined` — The last-known anchor of a REMOVED node, or undefined if the id is live, was never here, or the anchor was wholesale-cleared. The tolerant readers in NodeModel (getWorldPosition / getGlobalPosition / getGlobalTransformMatrix / setGlobalPosition) resolve an unresolvable parent through this so orphaned relative children freeze in place instead of jumping to their raw offsets. See {@link detachedAnchors} and the design argument on {@link removeNode}.
- `clearNodes(): void` — Clear all nodes
- `addLink(link: LinkModel): void` — Add link to diagram
- `removeLink(linkId: string): LinkModel | undefined` — Remove link from diagram
- `restoreLink(data: any): LinkModel | undefined` — Restore link from serialized data
- `getLink(linkId: string): LinkModel | undefined` — Get link by ID
- `getLinks(): LinkModel[]` — Get all links
- `getLinksForPort(portId: string): LinkModel[]` — Get all links connected to a specific port
- `clearLinks(): void` — Clear all links
- `createSmartLink( sourceNode: NodeModel, targetNode: NodeModel, pathType: 'direct' | 'orthogonal' | 'smooth' | 'bezier' = 'smooth' ): LinkModel | undefined` — Create a smart link with automatic port selection
- `connectNodes( sourceNode: NodeModel, targetNode: NodeModel, pathType: 'direct' | 'orthogonal' | 'smooth' | 'bezier' = 'smooth' ): boolean` — High-level API to connect two nodes
- `getNodeConnections(node: NodeModel): { incoming: LinkModel[]; outgoing: LinkModel[]; all: LinkModel[]; }` — Get all connections for a node
- `disconnectNodes(sourceNode: NodeModel, targetNode: NodeModel): number` — Disconnect two nodes
- `addGroup(group: GroupModel): void` — Add group
- `removeGroup(groupId: string): GroupModel | undefined` — Remove group
- `restoreGroup(data: any): GroupModel | undefined` — Restore group from serialized data
- `getGroup(groupId: string): GroupModel | undefined` — Get group by ID
- `getGroups(): GroupModel[]` — Get all groups
- `getGroupsInRenderOrder(): GroupModel[]` — Groups in deterministic back-to-front stacking order — ascending `zIndex`, ties broken by Map insertion order (a STABLE sort keeps it). This is the model-level z-order story that replaces "stacking == Map insertion order" as the only determinant; a renderer paints groups in this order (behind their members) instead of relying on iteration order.
- `getProxyNodeForGroup(groupId: string): NodeModel | undefined` — The placeholder "group-as-node" for a collapsed group, if present. Placeholder nodes are ordinary NodeModels tagged with the group id so callers can filter them out of exports / counts.
- `isProxyNode(node: NodeModel): boolean` — Is this node a collapsed-group placeholder?
- `clearGroups(): void` — Clear all groups
- `addStroke(stroke: StrokeModel): void`
- `removeStroke(strokeId: string): StrokeModel | undefined`
- `restoreStroke(data: SerializedStroke): StrokeModel | undefined`
- `getStroke(strokeId: string): StrokeModel | undefined`
- `getStrokes(): StrokeModel[]`
- `clearStrokes(): void`
- `getVisibleStrokes(viewport: Rectangle): StrokeModel[]` — Strokes whose bounds overlap `viewport`. The ink layer's culling query.
- `getStrokesAlongSegment(a: Point, b: Point, tolerance = 0): StrokeModel[]` — Every stroke the pointer swept across travelling `a`→`b`. THE ERASER'S QUERY.
- `getAncestors(groupId: string): GroupModel[]` — Get a group's ancestor chain (nearest parent first), walking parentGroupId upward. Robust against malformed self/looping pointers.
- `getDescendants(groupId: string): GroupModel[]` — Get every group nested (directly or transitively) inside `groupId`. Breadth-first over the parentGroupId back-pointers.
- `getDepth(groupId: string): number` — Nesting depth of a group: number of ancestors (0 for a top-level group).
- `getSelectedNodes(): NodeModel[]` — Get all selected nodes
- `selectNode(node: NodeModel): void` — Select a single node (clears previous selection)
- `addToSelection(node: NodeModel): void` — Add node to selection (multi-select)
- `removeFromSelection(node: NodeModel): void` — Remove node from selection
- `toggleNodeSelection(node: NodeModel): void` — Toggle node selection (add if not selected, remove if selected)
- `clearSelection(): void` — Clear all selections
- `selectAll(): void` — Select all nodes
- `deleteSelected(): number` — Delete all selected nodes and their connected links
- `getNodeAtPosition(x: number, y: number): NodeModel | undefined` — Get node at position (for click detection) Returns the topmost node at the given position
- `isPointCoveredAbove(x: number, y: number, nodeId: string): boolean` — Is (x, y) inside any node ABOVE `nodeId` in z-order?
- `lockSelected(): number` — Option 3: Lock/pin selected nodes Locked nodes will not move during layout operations
- `unlockSelected(): number` — Option 3: Unlock selected nodes
- `getLockedNodes(): NodeModel[]` — Option 3: Get locked nodes
- `unlockAll(): number` — Option 3: Unlock all nodes
- `setViewport(x: number, y: number, width: number, height: number, zoom?: number): void` — Set viewport
- `getViewport(): { x: number; y: number; width: number; height: number; zoom: number }` — Get current viewport
- `pan(dx: number, dy: number): void` — Pan viewport
- `zoom(delta: number, center?: Point): void` — Zoom viewport (relative adjustment)
- `setZoom(level: number, center?: Point): void` — Set absolute zoom level Option B: Pan/Zoom controls
- `fitToView(padding: number = 100): void` — Fit viewport to show all nodes (without changing zoom level) Option B: Pan/Zoom controls
- `zoomToFit(targetWidth: number, targetHeight: number, padding: number = 100): void` — Fit viewport to show all nodes AND adjust zoom to fit screen Option B: Pan/Zoom controls
- `clear(): void` — Clear all nodes, links, and groups
- `getVisibleNodes(viewport: Rectangle): NodeModel[]` — Get nodes visible in viewport This enables viewport virtualization - only render visible nodes
- `getVisibleLinks(viewport: Rectangle): LinkModel[]` — Get links visible in viewport This enables viewport virtualization - only render visible links
- `findNearestPort( point: Point, options?: NearestPortOptions ): NearestPortHit | null` — The nearest port to a world point, served BY THE SPATIAL INDEX.
- `getVisibleBounds(viewport: Rectangle): Rectangle | null` — Get bounding box of all visible entities Useful for "fit to viewport" operations
- `getDirtyNodes(): NodeModel[]` — Get all dirty nodes Returns nodes that need re-rendering
- `getDirtyLinks(): LinkModel[]` — Get all dirty links Returns links that need re-rendering
- `getDirtyGroups(): GroupModel[]` — Get all dirty groups Returns groups that need re-rendering
- `markAllClean(): void` — Mark all entities as clean Call this after rendering to reset dirty flags
- `getDirtyCount(): number` — Get total count of dirty entities Useful for monitoring render performance
- `getVisibleDirtyNodes(viewport: Rectangle): NodeModel[]` — Get visible dirty nodes Combines viewport virtualization with dirty marking Only returns nodes that are both visible AND need re-rendering
- `getVisibleDirtyLinks(viewport: Rectangle): LinkModel[]` — Get visible dirty links Combines viewport virtualization with dirty marking Only returns links that are both visible AND need re-rendering
- `getLODLevel(zoom: number): LODLevel` — Get LOD level based on zoom
- `shouldRender(feature: LODFeature, lod: LODLevel): boolean` — Single feature gate that reads the active LOD tier's feature set. Renderers call this instead of hardcoding `lod === 'high'` checks, so custom tiers work automatically.
- `getLODConfig(): LODConfig` — Get the current Level-of-Detail policy.
- `setLODConfig(config: LODConfig): void` — Replace the Level-of-Detail policy wholesale. Apps use this to define their own tiers (names, breakpoints and feature sets).
- `registerLODTier(tier: LODTier): void` — Register (or replace, by name) a single LOD tier. Lets apps extend the default policy with an extra tier without rebuilding the whole config.
- `getNodesWithLOD(viewport: Rectangle, zoom: number): EntityWithLOD<NodeModel>[]` — Get visible nodes with LOD information Combines viewport virtualization with Level of Detail
- `getLinksWithLOD(viewport: Rectangle, zoom: number): EntityWithLOD<LinkModel>[]` — Get visible links with LOD information Combines viewport virtualization with Level of Detail
- `shouldRenderLabels(lod: LODLevel): boolean` — Check if labels should be rendered at this LOD level Now reads the LOD tier's feature set.
- `shouldRenderIcons(lod: LODLevel): boolean` — Check if icons should be rendered at this LOD level Now reads the LOD tier's feature set.
- `shouldRenderBorders(lod: LODLevel): boolean` — Check if borders should be rendered at this LOD level Now reads the LOD tier's feature set.
- `shouldRenderShadows(lod: LODLevel): boolean` — Check if shadows should be rendered at this LOD level Now reads the LOD tier's feature set.
- `getLayoutManager(): LayoutManager` — Get the layout manager for this diagram
- `setLayoutAlgorithm(type: LayoutAlgorithmType, config?: LayoutConfiguration): void` — Set layout algorithm
- `getLayoutAlgorithm(): LayoutAlgorithmType` — Get current layout algorithm type
- `configureLayout(config: LayoutConfiguration): void` — Configure current layout algorithm
- `getLayoutConfiguration(): LayoutConfiguration` — Get layout configuration
- `setAutoLayout(enabled: boolean): void` — Enable or disable automatic layout for new nodes When enabled, newly added nodes will be automatically positioned using the current layout algorithm
- `isAutoLayoutEnabled(): boolean` — Check if auto-layout is enabled
- `async reLayout(config?: LayoutConfiguration): Promise<void>` — Re-layout all nodes using current algorithm This will recalculate positions for all nodes in the diagram
- `override endBatch(): void` — Override: End batch update mode Fires accumulated events when all batches complete
- `serialize(): SerializedDiagram` — Serialize to JSON
- `reconcilePortConnections(): Array<{ linkId: string; portId: string; end: 'source' | 'target' }>` — Rebuild the derived port-connection registries from the diagram's links.
- `applyIncremental(patch: DiagramIncremental): void` — Apply an incremental patch (see serialization/Incremental.ts) — the receive side of toIncremental/apply. Added entities are installed through the SAME unified restore path as document load (fully wired); modified entities are updated IN PLACE so object identity is preserved for renderers holding references; normal change events fire (an applied patch IS a mutation, unlike a document load).
- `static fromJSON(data: SerializedDiagram, options?: DiagramLoadOptions): DiagramModel` (static) — Deserialize from JSON — THE document load path.
- `override dispose(): void` — Dispose diagram and all child entities Prevents memory leaks by: - Disposing all nodes, links, and groups - Breaking circular references - Clearing spatial indices - Calling parent dispose()
