-
Notifications
You must be signed in to change notification settings - Fork 0
DFD Graphing Library Reference
This document provides a developer reference for the AntV X6 graphing library (v2.x) used in TMI's Data Flow Diagram (DFD) editor.
TMI uses AntV X6 version 2.x as the underlying graph visualization library for the DFD editor. This reference covers the core API patterns, plugin configurations, and common usage scenarios.
Current Version: 2.19.2 (see package.json for exact version)
Key Resources:
- Repository: https://github.com/antvis/X6
- Source Code: https://github.com/antvis/X6/tree/master/packages
- Examples: https://github.com/antvis/X6/tree/master/examples/x6-example-features/src/pages
TMI's DFD editor uses X6 with the following architecture:
| Plugin | Purpose | Configuration |
|---|---|---|
@antv/x6-plugin-snapline |
Visual alignment guides when moving nodes | Red snaplines, sharp mode |
@antv/x6-plugin-transform |
Node resizing capabilities | Resizing enabled, no rotation |
@antv/x6-plugin-export |
SVG/PNG export functionality | Standard configuration |
@antv/x6-plugin-clipboard |
Cut/copy/paste operations | In-memory clipboard |
@antv/x6-plugin-selection |
Multi-select with rubberband | Custom selection styling |
@antv/x6-plugin-history |
Undo/redo functionality | Custom history management |
TMI defines custom shapes for DFD elements in infra-x6-shape-definitions.ts:
| Shape Name | Base Class | Visual |
|---|---|---|
process |
Shape.Ellipse |
Oval/ellipse |
store |
Shape.Rect |
Rectangle with top/bottom borders only |
actor |
Shape.Rect |
Standard rectangle |
security-boundary |
Shape.Rect |
Dashed border rectangle |
text-box |
Shape.Rect |
Transparent background |
| File | Purpose |
|---|---|
infra-x6-graph.adapter.ts |
Main graph adapter with configuration |
infra-x6-shape-definitions.ts |
Custom shape definitions |
infra-x6-event-handlers.adapter.ts |
Centralized event management |
infra-x6-selection.adapter.ts |
Selection handling |
styling-constants.ts |
Centralized styling values |
The central class for creating and managing a graph instance.
import { Graph } from '@antv/x6';
const graph = new Graph({
container: HTMLElement, // Required: DOM container
width?: number, // Canvas width (default: container width)
height?: number, // Canvas height (default: container height)
background?: BackgroundOptions,
grid?: GridOptions,
interacting?: InteractingOptions,
connecting?: ConnectingOptions,
selecting?: SelectingOptions,
panning?: PanningOptions,
mousewheel?: MouseWheelOptions,
history?: HistoryOptions,
clipboard?: ClipboardOptions,
embedding?: EmbeddingOptions,
snapline?: SnaplineOptions,
highlighting?: HighlightingOptions,
});// From infra-x6-graph.adapter.ts
this._graph = new Graph({
container,
width: container.clientWidth,
height: container.clientHeight,
grid: {
size: 10,
visible: true,
type: 'dot',
args: [
{ color: '#666666', thickness: 1 },
{ color: '#888888', thickness: 1, factor: 4 },
],
},
panning: {
enabled: true,
eventTypes: ['leftMouseDown'],
modifiers: ['shift'],
},
mousewheel: {
enabled: true,
modifiers: ['shift'],
factor: 1.1,
maxScale: 3.0,
minScale: 0.2,
},
interacting: {
nodeMovable: true,
edgeMovable: true,
edgeLabelMovable: true,
arrowheadMovable: true,
vertexMovable: true,
vertexAddable: true,
vertexDeletable: true,
magnetConnectable: true,
},
embedding: {
enabled: true,
findParent: 'bbox',
validate: ({ child, parent }) => boolean,
},
connecting: {
allowNode: false,
allowPort: true,
allowBlank: false,
allowLoop: true,
allowMulti: true,
snap: { radius: 20 },
highlight: true,
validateConnection: (options) => boolean,
createEdge: () => Edge,
},
});Cell Management:
graph.addNode(metadata: Node.Metadata): Node
graph.addNodes(nodes: Node.Metadata[]): Node[]
graph.removeNode(nodeId: string): Node | null
graph.addEdge(metadata: Edge.Metadata): Edge
graph.addEdges(edges: Edge.Metadata[]): Edge[]
graph.removeEdge(edgeId: string): Edge | null
graph.getNodes(): Node[]
graph.getEdges(): Edge[]
graph.getCellById(id: string): Cell | null
graph.clearCells(): thisViewport:
graph.zoom(factor?: number): this
graph.zoomTo(scale: number): this
graph.zoomToFit(options?: ZoomOptions): this
graph.centerContent(): this
graph.fitToContent(options?: FitOptions): voidCoordinate Transformation:
graph.clientToLocal(x, y): Point
graph.localToClient(x, y): Point
graph.pageToLocal(x, y): Point
graph.localToPage(x, y): PointSelection:
graph.getSelectedCells(): Cell[]
graph.select(cell: Cell | string): void
graph.unselect(cell: Cell | string): void
graph.resetSelection(): void
graph.isSelected(cell: Cell | string): booleanHistory:
graph.undo(): void
graph.redo(): void
graph.canUndo(): boolean
graph.canRedo(): boolean
graph.clearHistory(): voidClipboard:
graph.copy(cells: Cell[]): void
graph.cut(cells: Cell[]): void
graph.paste(): Cell[]
graph.isClipboardEmpty(): boolean
graph.cleanClipboard(): voidX6 uses an event emitter pattern:
graph.on(eventName: string, handler: Function): this
graph.once(eventName: string, handler: Function): this
graph.off(eventName?: string, handler?: Function): thisCell Events:
-
cell:added,cell:removed,cell:changed:* -
cell:click,cell:dblclick,cell:contextmenu -
cell:mouseenter,cell:mouseleave
Node Events:
-
node:added,node:removed,node:moved,node:resized -
node:change:position,node:change:size,node:change:parent -
node:embedding,node:embedded
Edge Events:
-
edge:added,edge:removed,edge:connected,edge:disconnected -
edge:change:source,edge:change:target,edge:change:vertices
Selection Events:
selection:changed
Graph Events:
-
blank:click,blank:dblclick,blank:contextmenu -
scale,translate,resize
// From infra-x6-event-handlers.adapter.ts
graph.on('node:added', ({ node }) => {
this.logger.info('Node added', { nodeId: node.id });
this.emitEvent('nodeAdded', { node });
});
graph.on('selection:changed', ({ added, removed }) => {
this.emitEvent('selectionChanged', {
selected: added,
deselected: removed
});
});interface Node.Metadata {
id?: string;
x?: number;
y?: number;
width?: number;
height?: number;
label?: string;
shape?: string;
markup?: Markup;
attrs?: CellAttrs;
ports?: PortConfiguration;
zIndex?: number;
data?: any;
}Key Methods:
node.getPosition(): { x: number, y: number }
node.setPosition(x: number, y: number): this
node.getSize(): { width: number, height: number }
node.setSize(width: number, height: number): this
node.getPorts(): Port[]
node.addPort(port: PortMetadata): this
node.removePort(portId: string): thisinterface Edge.Metadata {
id?: string;
source: TerminalData; // { cell: string, port?: string } or { x, y }
target: TerminalData;
labels?: Label[];
vertices?: Point[];
router?: RouterConfig;
connector?: ConnectorConfig;
attrs?: CellAttrs;
}Key Methods:
edge.getSource(): TerminalData
edge.setSource(source: TerminalData): this
edge.getTarget(): TerminalData
edge.setTarget(target: TerminalData): this
edge.getVertices(): Point[]
edge.setVertices(vertices: Point[]): this
edge.getLabels(): Label[]
edge.setLabels(labels: Label[]): thisimport { Graph, Shape } from '@antv/x6';
// Define custom shape
Shape.Rect.define({
shape: 'custom-rect',
markup: [
{ tagName: 'rect', selector: 'body' },
{ tagName: 'text', selector: 'text' },
],
attrs: {
body: {
fill: '#ffffff',
stroke: '#000000',
strokeWidth: 2,
},
text: {
refX: '50%',
refY: '50%',
textAnchor: 'middle',
textVerticalAnchor: 'middle',
},
},
});
// Register with Graph
Graph.registerNode('custom-rect', CustomRectClass, true);const ports = {
groups: {
top: {
position: 'top',
attrs: {
circle: {
r: 5,
magnet: 'active',
stroke: '#000',
fill: '#fff',
},
},
},
// ... other groups: right, bottom, left
},
items: [
{ group: 'top', id: 'top' },
{ group: 'right', id: 'right' },
{ group: 'bottom', id: 'bottom' },
{ group: 'left', id: 'left' },
],
};Routers:
-
'normal'- Direct line with vertices -
'orth'- Orthogonal segments -
'manhattan'- Orthogonal, avoiding crossings -
'metro'- Like manhattan with bend radius options -
'er'- Entity-relationship style
Connectors:
-
'normal'- Straight line -
'smooth'- Bezier curve -
'rounded'- Polyline with rounded corners -
'jumpover'- Line jumps over intersections
const edge = graph.addEdge({
source: { cell: nodeId1, port: 'right' },
target: { cell: nodeId2, port: 'left' },
router: { name: 'manhattan' },
connector: { name: 'rounded', args: { radius: 8 } },
});import { Snapline } from '@antv/x6-plugin-snapline';
graph.use(new Snapline({
enabled: true,
sharp: true,
className: 'custom-snapline',
}));import { Transform } from '@antv/x6-plugin-transform';
graph.use(new Transform({
resizing: {
enabled: true,
minWidth: 40,
minHeight: 30,
orthogonal: false,
preserveAspectRatio: false,
},
rotating: false,
}));import { Selection } from '@antv/x6-plugin-selection';
graph.use(new Selection({
enabled: true,
multiple: true,
rubberband: true,
movable: true,
showNodeSelectionBox: true,
}));import { History } from '@antv/x6-plugin-history';
graph.use(new History({
enabled: true,
beforeAddCommand: (event, args) => boolean, // Filter commands
}));import { Clipboard } from '@antv/x6-plugin-clipboard';
graph.use(new Clipboard({
enabled: true,
useLocalStorage: false,
}));X6 provides built-in tools for node/edge interaction:
// Add tools to a node
node.addTools([
{
name: 'button-remove',
args: { x: '100%', y: 0, offset: { x: -10, y: 10 } },
},
{
name: 'boundary',
args: {
padding: 5,
attrs: {
fill: 'none',
stroke: '#fe854f',
'stroke-width': 2,
'stroke-dasharray': '5,5',
},
},
},
]);
// Remove tools
node.removeTools();edge.addTools([
{ name: 'vertices' },
{ name: 'source-arrowhead' },
{ name: 'target-arrowhead' },
{ name: 'button-remove', args: { distance: 0.5 } },
]);// Export to JSON
const data = graph.toJSON();
// Returns: { cells: [...] }
// Import from JSON
graph.fromJSON(data);
// or
graph.fromJSON({ cells: [...] });TMI uses X6's native toJSON() format for all cell persistence with zero transformation overhead. Key characteristics:
Node Labels: Set via attrs.text.text, not a top-level label property
{
id: 'node-1',
shape: 'process',
x: 100, y: 100, width: 120, height: 60,
attrs: {
body: { fill: '#ffffff', stroke: '#000000' },
text: { text: 'Process Name', fontSize: 14 } // Label here
}
}Edge Labels: Set via labels array with positioning control
{
id: 'edge-1',
shape: 'flow',
source: { cell: 'node-1', port: 'right' },
target: { cell: 'node-2', port: 'left' },
attrs: {
line: { stroke: '#808080', strokeWidth: 1 }
},
labels: [{
attrs: { text: { text: 'Data Flow', fontSize: 12 } },
position: 0.5
}]
}Styling: All visual properties use the X6 attrs object structure, not convenience style properties. This enables direct serialization/deserialization with X6 without transformation.
-
Batch Operations: Use
graph.batchUpdate()for multiple changesgraph.batchUpdate(() => { // Multiple operations here });
-
Async Rendering: Enable for large graphs
new Graph({ async: true });
-
Frozen Graph: Pause rendering during bulk operations
graph.freeze(); // ... bulk operations graph.unfreeze();
- Always call
graph.dispose()when destroying the graph - Remove event listeners with
graph.off()when no longer needed - Clean up tools when nodes are removed
X6 uses multiple coordinate systems:
- Client: Browser viewport coordinates
- Local: Graph canvas coordinates (affected by zoom/pan)
- Page: Document coordinates
Use transformation methods:
const localPoint = graph.clientToLocal(clientX, clientY);
const clientPoint = graph.localToClient(localX, localY);- Working with Data Flow Diagrams - User guide for DFD features
- Architecture and Design - System architecture overview
- Getting Started with Development - Development setup
- Using TMI for Threat Modeling
- Accessing TMI
- Creating Your First Threat Model
- Understanding the User Interface
- Working with Data Flow Diagrams
- Managing Threats
- Collaborative Threat Modeling
- Using Notes and Documentation
- Metadata and Extensions
- Planning Your Deployment
- Deploying TMI Server
- OCI Container Deployment
- Terraform Deployment
- Certificate Automation
- Deploying TMI Web Application
- Setting Up Authentication
- Database Setup
- Component Integration
- Post-Deployment
- Monitoring and Health
- Cloud Logging
- Database Operations
- Security Operations
- Performance and Scaling
- Maintenance Tasks