Convert Figma logo to code with AI

anvaka logongraph.path

Path finding in a graph

3,076
192
3,076
17

Top Related Projects

A comprehensive path-finding library for grid based games

Graph drawing library for JavaScript

Graph theory (network) library for visualisation and analysis

A directed multi-graph library for JavaScript

Force-directed graph layout using velocity Verlet integration.

Quick Overview

ngraph.path is a JavaScript library for finding shortest paths in graphs. It provides efficient implementations of various pathfinding algorithms, including Dijkstra's algorithm and A* search, and is designed to work with large graphs efficiently.

Pros

  • Fast and efficient implementation of pathfinding algorithms
  • Supports both weighted and unweighted graphs
  • Can be used in both Node.js and browser environments
  • Extensible architecture allowing for custom heuristics and graph structures

Cons

  • Limited documentation and examples
  • May have a steeper learning curve for beginners
  • Not actively maintained (last commit was over 2 years ago)
  • Lacks some advanced features found in more comprehensive graph libraries

Code Examples

Finding the shortest path using Dijkstra's algorithm:

const ngraph = require('ngraph.graph');
const path = require('ngraph.path');

const graph = ngraph();
graph.addLink('A', 'B', {weight: 1});
graph.addLink('B', 'C', {weight: 2});
graph.addLink('A', 'C', {weight: 4});

const pathFinder = path.aStar(graph);
const foundPath = pathFinder.find('A', 'C');
console.log(foundPath); // ['A', 'B', 'C']

Using A* search with a custom heuristic:

const customHeuristic = (fromNode, toNode) => {
  // Custom logic to estimate distance between nodes
  return Math.abs(fromNode.x - toNode.x) + Math.abs(fromNode.y - toNode.y);
};

const pathFinder = path.aStar(graph, {
  distance: (fromNode, toNode, link) => link.data.weight,
  heuristic: customHeuristic
});

const foundPath = pathFinder.find('A', 'C');

Finding all paths between two nodes:

const allPaths = pathFinder.findAll('A', 'C');
console.log(allPaths); // Array of all possible paths

Getting Started

To use ngraph.path in your project:

  1. Install the library:

    npm install ngraph.path ngraph.graph
    
  2. Import and use in your code:

    const ngraph = require('ngraph.graph');
    const path = require('ngraph.path');
    
    const graph = ngraph();
    // Add nodes and links to your graph
    graph.addLink('A', 'B');
    graph.addLink('B', 'C');
    
    const pathFinder = path.aStar(graph);
    const foundPath = pathFinder.find('A', 'C');
    console.log(foundPath);
    

Competitor Comparisons

A comprehensive path-finding library for grid based games

Pros of PathFinding.js

  • Supports multiple pathfinding algorithms (A*, Dijkstra, BFS, etc.)
  • Includes a visual demo for easy understanding and testing
  • Well-documented with clear examples and API reference

Cons of PathFinding.js

  • Limited to grid-based pathfinding
  • May have performance issues with large grids or complex scenarios
  • Less flexible for custom graph structures

Code Comparison

PathFinding.js:

var finder = new PF.AStarFinder();
var path = finder.findPath(0, 0, 5, 5, grid);

ngraph.path:

var pathFinder = ngraphPath.aStar(graph);
var path = pathFinder.find('start', 'end');

Key Differences

  • PathFinding.js focuses on grid-based pathfinding, while ngraph.path works with arbitrary graph structures
  • ngraph.path is more lightweight and potentially faster for large-scale graphs
  • PathFinding.js offers more built-in algorithms and visualization tools
  • ngraph.path provides greater flexibility for custom graph implementations and heuristics

Use Cases

  • PathFinding.js: Ideal for game development, maze solving, and grid-based navigation
  • ngraph.path: Better suited for complex network analysis, route planning, and custom graph structures

Community and Maintenance

  • Both projects are open-source and available on GitHub
  • PathFinding.js has more stars and forks, indicating higher popularity
  • ngraph.path is part of a larger ecosystem of graph-related libraries by the same author

Graph drawing library for JavaScript

Pros of VivaGraphJS

  • More comprehensive graph visualization library with rendering capabilities
  • Supports multiple rendering engines (WebGL, SVG, CSS)
  • Includes layout algorithms for automatic graph positioning

Cons of VivaGraphJS

  • Larger codebase and potentially higher learning curve
  • May have more overhead for simple pathfinding tasks
  • Less focused on specific graph algorithms like pathfinding

Code Comparison

ngraph.path:

var pathFinder = require('ngraph.path');
var graph = createGraph();
var fromNodeId = 1, toNodeId = 4;
var path = pathFinder.aStar(graph).find(fromNodeId, toNodeId);

VivaGraphJS:

var graph = Viva.Graph.graph();
var renderer = Viva.Graph.View.renderer(graph);
graph.addLink(1, 2);
graph.addLink(2, 3);
renderer.run();

Key Differences

  • ngraph.path is focused specifically on pathfinding algorithms
  • VivaGraphJS is a more general-purpose graph visualization and manipulation library
  • ngraph.path is likely more efficient for pure pathfinding tasks
  • VivaGraphJS offers more features for graph rendering and interactive visualizations

Use Cases

  • Choose ngraph.path for efficient pathfinding in large graphs
  • Opt for VivaGraphJS when you need comprehensive graph visualization and manipulation tools

Graph theory (network) library for visualisation and analysis

Pros of Cytoscape.js

  • More comprehensive graph visualization and analysis library
  • Extensive documentation and active community support
  • Wider range of graph algorithms and layout options

Cons of Cytoscape.js

  • Larger file size and potentially higher memory usage
  • Steeper learning curve due to more complex API
  • May be overkill for simple pathfinding tasks

Code Comparison

ngraph.path:

var graph = require('ngraph.graph')();
var pathFinder = require('ngraph.path');
var path = pathFinder.aStar(graph).find(fromNodeId, toNodeId);

Cytoscape.js:

var cy = cytoscape({
  elements: [ /* graph elements */ ],
  layout: { name: 'preset' }
});
var aStar = cy.elements().aStar({ root: '#fromNode', goal: '#toNode' });
var path = aStar.path;

Summary

Ngraph.path is a lightweight, focused pathfinding library, while Cytoscape.js is a full-featured graph visualization and analysis tool. Ngraph.path is ideal for simple pathfinding tasks, offering a straightforward API and minimal overhead. Cytoscape.js provides a more comprehensive solution for complex graph-related projects, including visualization, but comes with a steeper learning curve and larger footprint. Choose based on your project's specific needs and complexity.

A directed multi-graph library for JavaScript

Pros of graphlib

  • More comprehensive graph library with a wider range of algorithms and operations
  • Better documentation and examples for easier integration
  • Supports directed and undirected graphs, as well as compound graphs

Cons of graphlib

  • Larger file size and potentially higher memory usage
  • May be overkill for simple pathfinding tasks
  • Less focused on performance optimization for large-scale graphs

Code Comparison

ngraph.path:

var pathFinder = require('ngraph.path');
var graph = require('ngraph.graph')();
graph.addLink(1, 2);
var finder = pathFinder.aStar(graph);
var path = finder.find(1, 2);

graphlib:

var Graph = require("graphlib").Graph;
var g = new Graph();
g.setEdge("a", "b");
var dijkstra = require("graphlib").alg.dijkstra;
var path = dijkstra(g, "a");

Summary

ngraph.path is a lightweight, performance-focused pathfinding library, while graphlib is a more comprehensive graph manipulation library. ngraph.path may be better suited for large-scale pathfinding tasks, while graphlib offers a broader range of graph operations and algorithms. The choice between the two depends on the specific requirements of your project and the complexity of graph operations needed.

Force-directed graph layout using velocity Verlet integration.

Pros of d3-force

  • Part of the widely-used D3.js ecosystem, offering seamless integration with other D3 modules
  • Provides a flexible and customizable force-directed graph layout algorithm
  • Extensive documentation and community support

Cons of d3-force

  • Primarily focused on force-directed layouts, limiting its use for other pathfinding algorithms
  • Can be computationally intensive for large graphs, potentially impacting performance

Code Comparison

d3-force:

const simulation = d3.forceSimulation(nodes)
  .force("link", d3.forceLink(links).id(d => d.id))
  .force("charge", d3.forceManyBody())
  .force("center", d3.forceCenter(width / 2, height / 2));

ngraph.path:

const graph = createGraph();
nodes.forEach(node => graph.addNode(node.id));
links.forEach(link => graph.addLink(link.source, link.target));
const pathFinder = path.aStar(graph);
const foundPath = pathFinder.find('start', 'end');

Key Differences

  • d3-force focuses on force-directed layouts, while ngraph.path specializes in pathfinding algorithms
  • ngraph.path offers more flexibility for various graph algorithms beyond force-directed layouts
  • d3-force is better suited for interactive visualizations, while ngraph.path excels in finding optimal paths between nodes

Use Cases

  • Choose d3-force for creating dynamic, force-directed graph visualizations
  • Opt for ngraph.path when implementing specific pathfinding algorithms or working with large, complex graphs

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

ngraph.path

Fast path finding for arbitrary graphs. Play with a demo or watch it on YouTube.

demo

If you want to learn how the demo was made, please refer to the demo's source code. I tried to describe it in great details.

Performance

I measured performance of this library on New York City roads graph (733,844 edges, 264,346 nodes). It was done by solving 250 random path finding problems. Each algorithm was solving the same set of problems. Table below shows required time to solve one problem.

AverageMedianMinMaxp90p99
A* greedy (suboptimal)32ms24ms0ms179ms73ms136ms
NBA*44ms34ms0ms222ms107ms172ms
A*, unidirectional55ms38ms0ms356ms123ms287ms
Dijkstra264ms258ms0ms782ms483ms631ms

"A* greedy" converged the fastest, however, as name implies the found path is not necessary globally optimal.

Source code for performance measurements

Why is it fast?

There are a few things that contribute to the performance of this library.

I'm using heap-based priority queue, built specifically for the path finding. I modified a heap's implementation, so that changing priority of any element takes O(lg n) time.

Each path finder opens many graph nodes during its exploration, which creates pressure on garbage collector. To avoid the pressure, I've created an object pool, which recycles nodes when possible.

In general, the A* algorithm helps to converge to the optimal solution faster than Dijkstra, because it uses "hints" from the heuristic function. When search is performed in both directions (source -> target and target -> source), the convergence can be improved even more. The NBA* algorithm is a bi-directional path finder, that guarantees optimal shortest path. At the same time it removes balanced heuristic requirement. It also seem to be the fastest algorithm, among implemented here (NB: If you have suggestions how to improve this even further - please let me know!)

I also tried to create my own version of bi-directional A* search, which turned out to be harder than I expected - the two searches met each other quickly, but the point where they met was not necessary on the shortest global path. It was close to optimal, but not the optimal. I wanted to remove the code, but then changed my mind: It finds a path very quickly. So, in case when speed matters more than correctness, this could be a good trade off. I called this algorithm A* greedy, but maybe it should be A* lazy.

usage

installation

You can install this module, bu requiring it from npm:

npm i ngraph.path

Or download from CDN:

<script src='https://unpkg.com/ngraph.path@1.3.1/dist/ngraph.path.min.js'></script>

If you download from CDN the library will be available under ngraphPath global name.

Basic usage

This is a basic example, which finds a path between arbitrary two nodes in arbitrary graph

let path = require('ngraph.path');
let pathFinder = path.aStar(graph); // graph is https://github.com/anvaka/ngraph.graph

// now we can find a path between two nodes:
let fromNodeId = 40;
let toNodeId = 42;
let foundPath = pathFinder.find(fromNodeId, toNodeId);
// foundPath is array of nodes in the graph

Example above works for any graph, and it's equivalent to unweighted Dijkstra's algorithm.

Weighted graph

Let's say we have the following graph:

let createGraph = require('ngraph.graph');
let graph = createGraph();

graph.addLink('a', 'b', {weight: 10});
graph.addLink('a', 'c', {weight: 10});
graph.addLink('c', 'd', {weight: 5});
graph.addLink('b', 'd', {weight: 10});

weighted

We want to find a path with the smallest possible weight:

let pathFinder = aStar(graph, {
  // We tell our pathfinder what should it use as a distance function:
  distance(fromNode, toNode, link) {
    // We don't really care about from/to nodes in this case,
    // as link.data has all needed information:
    return link.data.weight;
  }
});
let path = pathFinder.find('a', 'd');

This code will correctly print a path: d <- c <- a.

Guided (A-Star)

When pathfinder searches for a path between two nodes it considers all neighbors of a given node without any preference. In some cases we may want to guide the pathfinder and tell it our preferred exploration direction.

For example, when each node in a graph has coordinates, we can assume that nodes that are closer towards the path-finder's target should be explored before other nodes.

let createGraph = require('ngraph.graph');
let graph = createGraph();

// Our graph has cities:
graph.addNode('NYC', {x: 0, y: 0});
graph.addNode('Boston', {x: 1, y: 1});
graph.addNode('Philadelphia', {x: -1, y: -1});
graph.addNode('Washington', {x: -2, y: -2});

// and railroads:
graph.addLink('NYC', 'Boston');
graph.addLink('NYC', 'Philadelphia');
graph.addLink('Philadelphia', 'Washington');

guided

When we build the shortest path from NYC to Washington, we want to tell the pathfinder that it should prefer Philadelphia over Boston.

let pathFinder = aStar(graph, {
  distance(fromNode, toNode) {
    // In this case we have coordinates. Lets use them as
    // distance between two nodes:
    let dx = fromNode.data.x - toNode.data.x;
    let dy = fromNode.data.y - toNode.data.y;

    return Math.sqrt(dx * dx + dy * dy);
  },
  heuristic(fromNode, toNode) {
    // this is where we "guess" distance between two nodes.
    // In this particular case our guess is the same as our distance
    // function:
    let dx = fromNode.data.x - toNode.data.x;
    let dy = fromNode.data.y - toNode.data.y;

    return Math.sqrt(dx * dx + dy * dy);
  }
});
let path = pathFinder.find('NYC', 'Washington');

With this simple heuristic our algorithm becomes smarter and faster.

It is very important that our heuristic function does not overestimate actual distance between two nodes. If it does so, then algorithm cannot guarantee the shortest path.

oriented graphs

If you want the pathfinder to treat your graph as oriented - pass oriented: true setting:

let pathFinder = aStar(graph, {
  oriented: true
});

blocked paths

In scenarios where a path might be temporarily blocked between two nodes a blocked() function may be supplied to resolve blocked routes during path finding.

For example, train routes with service disruptions could be modelled as follows:

let createGraph = require('ngraph.graph');
let graph = createGraph();

// Our graph has cities:
graph.addNode('NYC');
graph.addNode('Philadelphia');
graph.addNode('Baltimore');
graph.addNode('Pittsburgh');
graph.addNode('Washington');

// and railroads:
graph.addLink('NYC', 'Philadelphia', { disruption: false });
graph.addLink('Philadelphia', 'Baltimore', { disruption: true });
graph.addLink('Philadelphia', 'Pittsburgh', { disruption: false });
graph.addLink('Pittsburgh', 'Washington', { disruption: false });
graph.addLink('Baltimore', 'Washington', { disruption: false });

While the Philadelphia to Baltimore route is facing a service disruption, the alternative route to Washington is via Pittsburgh. The following is an example blocked() function implementation that may be supplied to yield this result:

let path = require('ngraph.path');

let pathFinder = path.aStar(graph, {
  blocked(fromNode, toNode, link) {
    return link.data.disruption;
  },
});
let result = pathFinder.find('NYC', 'Washington');

available finders

The library implements a few A* based path finders:

let aStarPathFinder = path.aStar(graph, options);
let aGreedyStar = path.aGreedy(graph, options);
let nbaFinder = path.nba(graph, options);

Each finder has just one method find(fromNodeId, toNodeId), which returns array of nodes, that belongs to the found path. If no path exists - empty array is returned.

Which finder to choose?

With many options available, it may be confusing whether to pick Dijkstra or A*.

I would pick Dijkstra if there is no way to guess a distance between two arbitrary nodes in a graph. If we can guess distance between two nodes - pick A*.

Among algorithms presented above, I'd recommend A* greedy if you care more about speed and less about accuracy. However if accuracy is your top priority - choose NBA*. This is a bi-directional, optimal A* algorithm with very good exit criteria. You can read about it here: https://repub.eur.nl/pub/16100/ei2009-10.pdf

license

MIT

NPM DownloadsLast 30 Days