Skip to content

DFD Graphing Library Reference

Eric Fitzgerald edited this page Jan 26, 2026 · 1 revision

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.

Overview

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:

TMI-Specific Implementation

TMI's DFD editor uses X6 with the following architecture:

Plugins Used

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

Custom Shapes

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

Key Implementation Files

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

Core Library API (@antv/x6)

Graph Class

The central class for creating and managing a graph instance.

Constructor Options

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,
});

TMI Configuration Example

// 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,
  },
});

Key Methods

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(): this

Viewport:

graph.zoom(factor?: number): this
graph.zoomTo(scale: number): this
graph.zoomToFit(options?: ZoomOptions): this
graph.centerContent(): this
graph.fitToContent(options?: FitOptions): void

Coordinate Transformation:

graph.clientToLocal(x, y): Point
graph.localToClient(x, y): Point
graph.pageToLocal(x, y): Point
graph.localToPage(x, y): Point

Selection:

graph.getSelectedCells(): Cell[]
graph.select(cell: Cell | string): void
graph.unselect(cell: Cell | string): void
graph.resetSelection(): void
graph.isSelected(cell: Cell | string): boolean

History:

graph.undo(): void
graph.redo(): void
graph.canUndo(): boolean
graph.canRedo(): boolean
graph.clearHistory(): void

Clipboard:

graph.copy(cells: Cell[]): void
graph.cut(cells: Cell[]): void
graph.paste(): Cell[]
graph.isClipboardEmpty(): boolean
graph.cleanClipboard(): void

Event Handling

X6 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): this

Common Events

Cell 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

TMI Event Handling Example

// 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
  });
});

Cell Classes

Node Class

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): this

Edge Class

interface 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[]): this

Shape Registration

import { 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);

Port Configuration

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' },
  ],
};

Edge Routing and Connectors

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 } },
});

Plugin Configuration

Snapline Plugin

import { Snapline } from '@antv/x6-plugin-snapline';

graph.use(new Snapline({
  enabled: true,
  sharp: true,
  className: 'custom-snapline',
}));

Transform Plugin

import { Transform } from '@antv/x6-plugin-transform';

graph.use(new Transform({
  resizing: {
    enabled: true,
    minWidth: 40,
    minHeight: 30,
    orthogonal: false,
    preserveAspectRatio: false,
  },
  rotating: false,
}));

Selection Plugin

import { Selection } from '@antv/x6-plugin-selection';

graph.use(new Selection({
  enabled: true,
  multiple: true,
  rubberband: true,
  movable: true,
  showNodeSelectionBox: true,
}));

History Plugin

import { History } from '@antv/x6-plugin-history';

graph.use(new History({
  enabled: true,
  beforeAddCommand: (event, args) => boolean,  // Filter commands
}));

Clipboard Plugin

import { Clipboard } from '@antv/x6-plugin-clipboard';

graph.use(new Clipboard({
  enabled: true,
  useLocalStorage: false,
}));

Node Tools

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 Tools

edge.addTools([
  { name: 'vertices' },
  { name: 'source-arrowhead' },
  { name: 'target-arrowhead' },
  { name: 'button-remove', args: { distance: 0.5 } },
]);

Serialization

// Export to JSON
const data = graph.toJSON();
// Returns: { cells: [...] }

// Import from JSON
graph.fromJSON(data);
// or
graph.fromJSON({ cells: [...] });

X6 Native Format

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.

Best Practices

Performance

  1. Batch Operations: Use graph.batchUpdate() for multiple changes

    graph.batchUpdate(() => {
      // Multiple operations here
    });
  2. Async Rendering: Enable for large graphs

    new Graph({ async: true });
  3. Frozen Graph: Pause rendering during bulk operations

    graph.freeze();
    // ... bulk operations
    graph.unfreeze();

Memory Management

  1. Always call graph.dispose() when destroying the graph
  2. Remove event listeners with graph.off() when no longer needed
  3. Clean up tools when nodes are removed

Coordinate Systems

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);

Related Documentation

External References

Clone this wiki locally