PCUI Graph

repository·main·Indexed 41 days ago

https://github.com/playcanvas/pcui-graph

A PCUI plugin for creating node-based visual graphs in the browser. It supports undirected, directed, and visual scripting graphs with input/output ports, and allows graphs to be serialized to and from JSON. Version 5.2.5.

Tokens
8.1K
Snippets
18
Records
28
Agent score
99%

What's inside PCUI Graph

  1. Build, lint, and watch PCUI Graph source

    main

    If you are developing on the PCUI Graph repository, use the following commands:

    • npm run build: Generates a production build including UMD, ESM, TypeScript types, and styles.
    • npm run lint: Runs ESLint and Stylelint.
    • npm run watch: Starts a watch mode for incremental rebuilds.
    • npm run docs: Builds the API reference documentation into the docs folder.
    npm run build    # Production build (UMD + ESM + types + styles)
    npm run lint     # Run ESLint and Stylelint
    npm run watch    # Watch mode with incremental rebuilds
    npm run docs     # Build API reference
  2. Initialize a PCUI Graph with a schema

    main

    After installation, import the Graph class along with the required PCUI and PCUI Graph styles. You can instantiate a graph by passing a schema object that defines nodes and edges.

    Each node in the schema can have properties like name and fill. Edges define connections between nodes using from and to arrays (specifying node types or IDs) and can include styling like stroke. Once instantiated, append graph.dom to your document to render the graph view.

    import Graph from '@playcanvas/pcui-graph';
    import '@playcanvas/pcui/styles';
    import '@playcanvas/pcui-graph/styles';
    
    const schema = {
        nodes: {
            0: {
                name: 'Hello',
                fill: 'red'
            },
            1: {
                name: 'World',
                fill: 'green'
            }
        },
        edges: {
            0: {
                from: [0], // this edge can connect nodes of type 0
                to: [1], // to nodes of type 1
                stroke: 'blue'
            }
        }
    };
    
    const graph = new Graph(schema);
    document.body.appendChild(graph.dom);
  3. Adjust sibling link vertices automatically

    main

    When multiple links (siblings) exist between the same two nodes, they can overlap. JointGraph provides an adjustVertices method to automatically calculate perpendicular offsets for these links, spreading them out symmetrically.

    To enable this behavior, you must set adjustVertices: true in your GraphConfig. Once enabled, the graph listens for cell additions, removals, and changes to sources or targets to trigger the adjustment logic. You can also manually call adjustVertices(graph, cell) if needed.

    How it works:

    • It identifies links sharing the same source and target.
    • It calculates a perpendicular offset based on a constant GAP (20px).
    • It applies these offsets to the link anchors to ensure a clean, non-overlapping visual layout.
  4. Connection validation logic

    main

    The JointGraph implements a strict validateConnection logic to ensure valid graph topology. Connections are rejected if:

    1. Self-connection: A cell attempts to connect to itself.
    2. Missing Magnets: Either the source or target magnet is invalid.
    3. Invalid Port Groups: The magnet's parent element does not contain a port attribute.
    4. Directional Mismatch:
      • An in port cannot connect to another in port.
      • An out port cannot connect to another out port.
    5. Port Occupancy: An in port that is already connected (and not hidden) cannot accept another connection.
    6. Edge Type Mismatch: The edge-type (or edgeType) attribute of the source magnet must match the target magnet.
  5. Initialize a new Graph with GraphSchema and GraphOptions

    main

    The Graph class is the primary entry point for creating node-based graphs. It extends the PCUI Element class. To instantiate a graph, you must provide a GraphSchema (defining the structure of nodes and edges) and an optional GraphOptions object.

    GraphOptions Configuration

    OptionTypeDefaultDescription
    initialDataGraphData{}The initial nodes and edges to populate the graph.
    domHTMLElementundefinedThe DOM element to which the graph will be attached.
    contextMenuItemsContextMenuItem[][]Custom menu items for the graph canvas.
    readOnlybooleanfalseIf true, the graph is non-interactive and uses selfContainedMode.
    passiveUIEventsbooleanfalseIf true, the graph won't automatically update data/view on interaction; you must handle events explicitly.
    incrementNodeNamesbooleanfalseIf true, automatically increments names for duplicate node names.
    restrictTranslatebooleanfalseRestricts graph translation to the graph area.
    edgeHoverEffectbooleantrueEnables highlighting edges on hover.
    defaultStylesPartial<DefaultStyles>DEFAULT_CONFIG.defaultStylesOverrides default styling for background, node, and edge.
    adjustVerticesbooleanundefinedIf true, multiple edges between the same two nodes are spaced apart.

    Example Usage

    import Graph from '@playcanvas/pcui-graph';
    
    const schema = {
        nodes: {
            'myNodeType': {
                attributes: [{ name: 'label', defaultValue: 'New Node' }]
            }
        },
        edges: {
            'myEdgeType': {
                from: ['myNodeType'],
                to: ['myNodeType']
            }
        }
    };
    
    const graph = new Graph(schema, {
        readOnly: false,
        edgeHoverEffect: true,
        initialData: {
            nodes: {
                'node1': { id: 'node1', nodeType: 'myNodeType', posX: 100, posY: 100, attributes: { label: 'First' } }
            },
            edges: {}
        }
    });
    
    document.body.appendChild(graph.dom);
  6. Configure the default graph settings and styles

    main

    The DEFAULT_CONFIG object provides the baseline configuration for the graph, including behavior flags and visual styling for nodes and edges. You can override these values when initializing your graph instance.

    Behavior Flags

    • readOnly: If true, prevents user interactions that modify the graph.
    • passiveUIEvents: Controls how UI events are handled.
    • incrementNodeNames: If true, automatically increments names when adding nodes.
    • restrictTranslate: Restricts translation capabilities.
    • edgeHoverEffect: Enables/disables visual feedback when hovering over edges.
    • includeFonts: Whether to include custom fonts.
    • useGlobalPCUI: Determines if the graph should use the global PCUI instance.
    • adjustVertices: Controls vertex adjustment behavior.

    Default Styles

    The defaultStyles object defines the visual properties for the graph background, nodes, and edges. Key properties include:

    Node Styles

    • fill, fillSecondary, stroke, strokeSelected, strokeHover: Color properties for the node body and borders.
    • textColor, textColorSecondary: Colors for text elements.
    • baseHeight, baseWidth: Default dimensions for nodes.
    • icon, iconColor: Icon configuration.

    Edge Styles

    • stroke, strokeSelected: Color of the edge line.
    • strokeWidth, strokeWidthSelected: Thickness of the edge line.
    • targetMarker: Boolean to show/hide markers at the edge end.
    • connectionStyle: The visual style of the connection (e.g., 'default').
    export const DEFAULT_CONFIG = {
        readOnly: false,
        passiveUIEvents: false,
        incrementNodeNames: false,
        restrictTranslate: false,
        edgeHoverEffect: true,
        includeFonts: true,
        useGlobalPCUI: false,
        adjustVertices: false,
        defaultStyles: {
            initialScale: 1,
            initialPosition: {
                x: 0,
                y: 0
            },
            background: {
                color: '#20292B',
                gridSize: 10
            },
            node: {
                fill: '#2c393c',
                fillSecondary: '#364346',
                stroke: '#293538',
                strokeSelected: '#F60',
                strokeHover: 'rgba(255, 102, 0, 0.32)',
                textColor: '#FFFFFF',
                textColorSecondary: '#b1b8ba',
                includeIcon: true,
                icon: '',
                iconColor: '#F60',
                baseHeight: 28,
                baseWidth: 226,
                textAlignMiddle: false,
                lineHeight: 12
            },
            edge: {
                stroke: 'rgb(3, 121, 238)',
                strokeSelected: '#F60',
                strokeWidth: 2,
                strokeWidthSelected: 2,
                targetMarker: true,
                connectionStyle: 'default'
            }
        }
    };
  7. Configure JointGraph via GraphConfig

    main

    The GraphConfig object allows you to customize the behavior and appearance of the graph. While the full type definition is in ./types, the following keys are used by the JointGraph constructor:

    • readOnly (boolean): If true, the graph becomes non-interactive.
    • restrictTranslate (boolean): Controls whether translation is restricted.
    • minZoom (number): The minimum zoom level allowed (default: 0.25).
    • maxZoom (number): The maximum zoom level allowed (default: 1.5).
    • adjustVertices (boolean): If true, the graph automatically calculates vertex offsets for sibling links to prevent them from overlapping.
    • defaultStyles (object): Defines the initial visual state:
      • background.color: The background color of the paper.
      • background.gridSize: The size of the background grid.
  8. Configure context menus for GraphView

    main

    You can attach custom context menus to the canvas, individual nodes, or individual edges.

    • addCanvasContextMenu(items): Adds a menu that appears when clicking on the empty graph background.
    • addNodeContextMenu(id, items): Adds a menu to a specific node.
    • addEdgeContextMenu(id, items): Adds a menu to a specific edge.

    items is an array of ContextMenuItem objects.

  9. Manage nodes in GraphView

    main

    Use GraphView to programmatically add, remove, and update nodes within the graph. When adding a node, you must provide data, a schema, and callbacks for edge creation and node selection.

    To optimize performance when adding multiple nodes at once, use batchCells() before adding nodes and applyBatchedCells() once finished. This prevents unnecessary re-renders.

    Common node operations include:

    • addNode(...): Creates a new node.
    • removeNode(modelId): Removes a node by its model ID.
    • updateNodeAttribute(id, attribute, value): Updates a specific attribute on a node.
    • setNodeAttributeErrorState(id, attribute, value): Sets the error state for a node attribute.
    • updateNodePosition(id, pos): Moves a node to a new { x, y } position.
    • updateNodeType(id, nodeType): Changes the type of a node.
    • selectNode(id) / deselectNode(id): Controls the selection state of a node.
    // Adding a node with batching for performance
    graphView.batchCells();
    
    graphView.addNode(
        { id: 'node-1', type: 'default' }, // NodeData
        nodeSchema,                        // NodeSchema
        (edgeId, edgeData) => {           // onCreateEdge callback
            console.log('Edge created:', edgeId);
        },
        (nodeData) => {                   // onNodeSelected callback
            console.log('Node selected:', nodeData.id);
        }
    );
    
    graphView.applyBatchedCells();
  10. Listen to Graph Events using the Graph.on() method

    main

    The Graph class uses DOM-based events to communicate changes. You can listen to these events using the .on() method. Note that if readOnly is enabled, most interaction events are suppressed.

    Available Graph Actions (Events)

    Events are dispatched using the GRAPH_ACTIONS constants. Common events include:

    • ADD_NODE: Fired when a node is added.
    • DELETE_NODE: Fired when a node is deleted.
    • SELECT_NODE: Fired when a node is selected.
    • UPDATE_NODE_POSITION: Fired when a node is moved.
    • UPDATE_NODE_ATTRIBUTE: Fired when a node attribute changes.
    • ADD_EDGE: Fired when an edge is created.
    • DELETE_EDGE: Fired when an edge is removed.
    • SELECT_EDGE: Fired when an edge is selected.
    • DESELECT_ITEM: Fired when selection is cleared.

    Example: Listening for Node Selection

    graph.on('EVENT_SELECT_NODE', ({ node, prevItem }) => {
        console.log('Selected node:', node.id);
        console.log('Previous item:', prevItem);
    });