Graph
Graphs
A powerful and flexible graph data structure implementation for working with connected data. This module provides a complete set of tools for creating, manipulating, and traversing graph structures with support for both directed and undirected weighted edges.
Overview
The Graph module allows you to:
- Create and manage nodes with custom data
- Connect nodes with weighted, directed or undirected edges
- Position nodes in 2D space for spatial algorithms
- Perform common graph traversal operations like BFS and DFS
- Find optimal paths using Dijkstra's algorithm or A* search
Basic Usage
Creating a Graph and working with Nodes and Edges
ts
import { Graph } from 'excalibur';// Create an empty graph of stringsconst graph = new Graph<string>();// Add a few nodes with string dataconst nodeA = graph.addNode("A");const nodeB = graph.addNode("B");const nodeC = graph.addNode("C");// Connect nodes with bidirectional edges (default)graph.addEdge(nodeA, nodeB);graph.addEdge(nodeB, nodeC);graph.addEdge(nodeC, nodeD);graph.addEdge(nodeD, nodeE);// Connect nodes in one direction onlygraph.addEdge(nodeA, nodeC, { directed: true });// Connect nodes with weighted edgesgraph.addEdge(nodeA, nodeB, { weight: 5 });// Use coordinates of parent nodes to dictate weightinggraph.addEdge(nodeA, nodeB, {useEuclidean: true});// Check if nodes are connectedconst connected = graph.areNodesConnected(nodeA, nodeB); // true// Get neighbors of a nodeconst neighbors = graph.getNeighbors(nodeA); // [nodeB]// Delete a node (and its edges)graph.deleteNode(nodeC);// Delete an edgegraph.deleteEdge(edges[0]);
ts
import { Graph } from 'excalibur';// Create an empty graph of stringsconst graph = new Graph<string>();// Add a few nodes with string dataconst nodeA = graph.addNode("A");const nodeB = graph.addNode("B");const nodeC = graph.addNode("C");// Connect nodes with bidirectional edges (default)graph.addEdge(nodeA, nodeB);graph.addEdge(nodeB, nodeC);graph.addEdge(nodeC, nodeD);graph.addEdge(nodeD, nodeE);// Connect nodes in one direction onlygraph.addEdge(nodeA, nodeC, { directed: true });// Connect nodes with weighted edgesgraph.addEdge(nodeA, nodeB, { weight: 5 });// Use coordinates of parent nodes to dictate weightinggraph.addEdge(nodeA, nodeB, {useEuclidean: true});// Check if nodes are connectedconst connected = graph.areNodesConnected(nodeA, nodeB); // true// Get neighbors of a nodeconst neighbors = graph.getNeighbors(nodeA); // [nodeB]// Delete a node (and its edges)graph.deleteNode(nodeC);// Delete an edgegraph.deleteEdge(edges[0]);
Core Concepts
Node Types
The Graph module supports two node types:
Node: Basic graph node with data
PositionNode: Node with 2D spatial coordinates, uses Excalibur's Native Vector type for position
ts
// Add positioned nodes, when Vector positions are attached to nodes, it returns a PositionNodeconst nodeA = graph.addNode("A", new Vector(0, 0));const nodeB = graph.addNode("B", new Vector(5, 10));const nodeC = graph.addNode("C", new Vector(10, 5));
ts
// Add positioned nodes, when Vector positions are attached to nodes, it returns a PositionNodeconst nodeA = graph.addNode("A", new Vector(0, 0));const nodeB = graph.addNode("B", new Vector(5, 10));const nodeC = graph.addNode("C", new Vector(10, 5));
Edge Options
The optional third parameter for creating an Edge has this interface (in principle):
ts
interface EdgeOptions = {weighted: numberuseEuclidean: booleandirection: boolean}
ts
interface EdgeOptions = {weighted: numberuseEuclidean: booleandirection: boolean}
It is setup as a discriminated union to allow:
- passing a basic weighted value to an edge
- directing the edge to use the positions of its parent nodes to calculate its weighting
- or use no weighting at all
You cannot pass a weighting parameter and direct the Edge to use Euclidean positions of the nodes, you have to pass one or the other.
Edge Properties
Edges connect nodes and can have properties:
weight: Numeric value representing distance or cost (default: 0) directed: Whether the edge is one-way or bidirectional (default: true) partner: reference to a paired edge if a bidirectional edge was created
ts
// Connect the nodesspatialGraph.addEdge(nodeA, nodeB, { weight: 11.2, directed: false }); // Euclidean distance
ts
// Connect the nodesspatialGraph.addEdge(nodeA, nodeB, { weight: 11.2, directed: false }); // Euclidean distance
Graph Traversal
Breadth-First Search (BFS)
Explore the graph layer by layer, visiting all direct neighbors before moving deeper:
ts
// Create and populate your graph firstconst visitedNodeIds = graph.bfs(startNode);
ts
// Create and populate your graph firstconst visitedNodeIds = graph.bfs(startNode);
Depth-First Search (DFS)
Explore the graph by moving as far as possible along each branch before backtracking:
ts
// Create and populate your graph firstconst visitedNodeIds = graph.dfs(startNode);
ts
// Create and populate your graph firstconst visitedNodeIds = graph.dfs(startNode);
Pathfinding Algorithms
Shortest Path and Dijkstra's Algorithm
Find the shortest path between two nodes in a weighted graph:
ts
// Find shortest path from A to Cconst { path, distance } = graph.shortestPathDijkstra(nodeA, nodeC);// Get full analysisconst dijkstraAnalysis = graph.dijkstra(nodeA);
ts
// Find shortest path from A to Cconst { path, distance } = graph.shortestPathDijkstra(nodeA, nodeC);// Get full analysisconst dijkstraAnalysis = graph.dijkstra(nodeA);
A* Algorithm
Find the shortest path using spatial information for better performance:
ts
const aStarResults = graph.astar(nodeA, nodeB);
ts
const aStarResults = graph.astar(nodeA, nodeB);
Returns an object with a list of nodes representing the path, a number representing the number of node steps required, the overall distance traversed, and if any nodes were skipped due to them not being PositionNodes.
Other Features
Building a Graph from Data Arrays
For convenience, you can create a graph from arrays of node data:
ts
// Create a graph with string data nodesconst cities = ["New York", "London", "Tokyo", "Sydney", "Paris"];const graph = Graph.createGraphFromNodes(cities);
ts
// Create a graph with string data nodesconst cities = ["New York", "London", "Tokyo", "Sydney", "Paris"];const graph = Graph.createGraphFromNodes(cities);