Library / SDK
anvaka/ngraph.graph avatar
anvaka/ngraph.graph

ngraph.graph is a graph structure that tells you when it changes

Graph data structure in JavaScript

583 stars72 forksJavaScriptBSD-3-Clause

At a glance

What is it?
ngraph.graph is a JavaScript graph data structure, and its distinguishing feature against most graph libraries in the same language is that it is an observable. Every insertion, removal and update raises a changed event carrying a record of what happened, and a beginUpdate and endUpdate pair lets you batch a hundred insertions into one notification instead of a hundred.
Who is it for?
Adopt ngraph.graph if you are building a graph interface in JavaScript and want the graph to notify your view when it changes, because the change events with records are built in and the batching calls stop a rebuild on every insertion. Skip it if you need the graph algorithms themselves, since the package is a data structure and the algorithms live in sibling ngraph packages.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 42 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A graph structure, and the family it belongs to

ngraph.graph is a graph data structure for JavaScript, and it belongs to a family of JavaScript graph packages called ngraph.

That family framing is worth pausing on, because it tells you what this package is and is not. The README is explicit that this is the data structure, and the algorithms live elsewhere. There is a separate ngraph.events package referenced for more information about events, and the topic tags cover edges, graph, graph algorithms, nodes and network.

So if you are looking for the breadth of a full graph library, this is the wrong package. If you are looking for the thing a graph visualisation library needs underneath it, with the notification behaviour built in, it is the right one.

The design of the data structure itself is what most people need to know first. The graph holds nodes and links. A node has an identifier, which is most commonly a number or a string, and optionally a data payload of any kind. A link connects two nodes and also optionally carries data.

The identifiers being simple values is the constraint that shapes the whole API. You get a node back with getNode, you look it up with forEachLinkedNode, and you remove it with removeNode, all by identifier. There is no object identity, so a node is identified by its key, and the payload is separate from the identity. That is the right choice for a graph where the nodes come from a data source with its own keys, and it is also why you cannot use the same graph with two different objects representing the same thing.

The project is JavaScript, BSD 3-clause licensed, with the last push on 2026-09-11 and no GitHub releases, since it is consumed through npm.

Three ways to install it, and one of them gives you a global

The install section offers three forms, and the differences are practical rather than stylistic.

With npm:

code
npm install ngraph.graph

Or download from a CDN:

``` html <script src='https://unpkg.com/[email protected]/dist/ngraph.graph.umd.js'></script>

code

If you download from a CDN the library will be available under the `createGraph` global name. And you can also import it as an ES module:

js import createGraph from 'ngraph.graph'; const g = createGraph();

code

The version in the CDN example is 20.1.0, which is a reminder that this package has been versioning for a long time, since a major version of 20 on a data structure library means it has changed shape that many times, or at least that it counts that way.

The CDN form with a global is the one worth thinking about, because it is how the library would be used in a page that does not have a build step. The cost is a global name in the window scope, which is exactly what bundler-based tooling exists to avoid, and it is a reasonable trade only when there is no bundler.

The ES module form is the modern default and the one to reach for in anything with a build step, since `import createGraph from 'ngraph.graph'` is the same call the CommonJS example makes with require.

The creation itself is a single call in every form, with no arguments:

js var createGraph = require('ngraph.graph'); var g = createGraph(); ```

A graph with no edges and no nodes. There is no configuration object, no options for directionality, no comparison function for keys. That is worth noticing, because a graph library that supports directed graphs usually has a flag for it, and this one does not. forEachLinkedNode enumerates both inbound and outbound links by default, so links carry direction in the model.

The simplicity is a deliberate choice for a package that has to be small and fast, and it means the mental model is: nodes are keys, links are pairs of keys, and everything else is data you attach.

Growing the graph and attaching your own objects

A graph can be grown two ways, and the second one is the one that changes how you write the code.

You can add one node at a time, as in `g.addNode('hello')` and `g.addNode('world')`. Or you can use the addLink method, and calling it with nodes which are not present in the graph creates them:

``` js g.addLink('space', 'bar'); // now graph 'g' has two new nodes: 'space' and 'bar'

code

And if the nodes are already present, addLink makes them connected:

js // Only a link between 'hello' and 'world' is created. No new nodes. g.addLink('hello', 'world');

code

That single method removes the usual two-step dance of checking whether a node exists before adding a link to it. If you are building a graph from a list of edges, which is the common case for imported data, you can iterate the edge list and call addLink for each one without any existence checks at all.

The data attachment is the other half. The most common and convenient choices for nodes and edges are numbers and strings, and you can associate arbitrary data with a node via the optional second argument of addNode:

js // Node 'world' is associated with a string object 'custom data' g.addNode('world', 'custom data');

// You can associate arbitrary objects with node: g.addNode('server', { status: 'on', ip: '127.0.0.1' });

// to get data back use `data` property of node: var server = g.getNode('server'); console.log(server.data); // prints associated object

code

The same is true for links, using the third optional argument of addLink:

js // A link between nodes '1' and '2' is now associated with object 'x' g.addLink(1, 2, x); ```

So the model is that the node or link is the identity and the payload is separate, and you retrieve the payload through the data property.

The payload is where the graph stops being abstract. A node with an id of server and a payload of an object with a status and an ip is a server, and the graph library does not care what is in the payload. That is what makes it usable with whatever shape your data already has.

Enumeration, and the one asymmetry in the API

After you have created a graph, the most common thing to do is enumerate its nodes and links, and the library has a helper for each case.

`forEachNode` takes a callback which accepts the current node, and the node object may contain internal information. The documentation is careful about which parts are guaranteed: node.id and node.data represent parameters passed to the addNode method and they are guaranteed to be present in future versions of the library. That guarantee is worth noting, because the rest of the node object is internal and could change.

So the pattern is:

``` js g.forEachNode(function(node){ console.log(node.id, node.data); });

code

`forEachLink` enumerates all links in the graph, and `forEachLinkedNode` enumerates all links for a specific node, with the callback receiving both the linked node and the link itself.

There is one asymmetry, and it is the detail worth remembering. forEachLinkedNode always enumerates both inbound and outbound links. If you want to get only outbound links, you pass a third optional argument:

js g.forEachLinkedNode('hello', function(linkedNode, link) { /* ... */ }, true // enumerate only outbound links );

code

For an undirected graph that is mostly a convenience, since a link has no direction to begin with. But if you are treating a link's payload as carrying a direction, this is where you would filter, and the default behaviour would otherwise double-count.

The removal side has a similar asymmetry, and it is the other thing to know. removeNode takes an identifier, but removing a link is a bit harder, since the method requires the actual link object:

js g.removeNode('space'); // Removing link is a bit harder, since method requires actual link object: g.forEachLinkedNode('hello', function(linkedNode, link){ g.removeLink(link); }); ```

So to remove a link you have to enumerate the links around a node and remove each one by object. That is because a link is identified by the pair of nodes it joins, so the identifier alone does not point at a specific link object.

You can also remove all nodes and links by calling `g.clear()`.

The change events are the reason to pick this library

The event system is the part that distinguishes ngraph.graph from a plain graph structure, and the mechanism is a listener on a changed event.

Whenever someone changes your graph you can listen to notifications:

``` js g.on('changed', function(changes) { console.dir(changes); // prints array of change records });

g.addNode(42); // this will trigger 'changed' event

code

And the change record carries information:

ChangeRecord = { changeType: add|remove|update - describes type of this change node: - only present when this record reflects a node change, represents actual node link: - only present when this record reflects a link change, represents actual link }

code

So a listener receives an array of records, and each record says what kind of change it was and which object was affected. The node field is only present when the record reflects a node change, and the link field only when it reflects a link change. That means a listener can handle all three change types without inspecting the object types, and can tell an add from an update from a remove.

The update case is the interesting one. A graph structure that emits an update event is one that distinguishes adding a node from changing the data on a node, which is what you need if your view is going to patch rather than rebuild.

Then there is the batching mechanism, and it is the part that makes the events usable on a real workload. Sometimes it is desirable to react only on bulk changes, and ngraph.graph supports this via the beginUpdate and endUpdate methods:

js g.beginUpdate(); for(var i = 0; i < 100; ++i) { g.addLink(i, i + 1); // no events are triggered here } g.endUpdate(); // this triggers all listeners of 'changed' event ```

So a hundred insertions inside the block produce no events at all, and the endUpdate triggers all listeners of the changed event once. The reason this matters is the cost model of a naive event system. If your listener rebuilds a layout or redraws a canvas, a hundred separate notifications means a hundred rebuilds, and the whole graph ends up looking like it is thrashing. With the batch, the graph is mutated in one go and the view is updated once.

To stop listening you use the off method, `g.off('changed', yourHandler)`, and the documentation points at the ngraph.events package for more information about the event system.

So the pairing is a mutable structure that tells you when it changes, with a way to say not yet, which is exactly the shape a visualisation or a reactive UI needs.

Editorial conclusion

Adopt ngraph.graph if you are building a graph interface in JavaScript and want the graph to notify your view when it changes, because the change events with records are built in and the batching calls stop a rebuild on every insertion. Skip it if you need the graph algorithms themselves, since the package is a data structure and the algorithms live in sibling ngraph packages. Verify first which delivery form you want, since there is an npm package, a CDN script with a global name and an ES module, and that you are happy with the node identifier being a string or number rather than an object, which is the constraint that shapes the rest of the API.

Frequently asked questions

How do I install ngraph.graph and create a graph?

Install it with `npm install ngraph.graph`, or load it from a CDN with the unpkg script tag, in which case the library is available under the `createGraph` global name, or import it as an ES module with `import createGraph from 'ngraph.graph'`. Creating a graph is then `const g = createGraph()`, which gives you a graph with no edges and no nodes.

How do I attach my own data to nodes and links in ngraph.graph?

Nodes and links are identified by a number or a string, and you associate arbitrary data with a node through the optional second argument of `addNode`, and with a link through the optional third argument of `addLink`. You read the data back through the `data` property of the node or link object returned by `getNode` or `getLink`.

How do the change events work in ngraph.graph?

Register a listener with `g.on('changed', handler)` and the handler receives an array of change records, each with a changeType of add, remove or update and either a node or a link field depending on the change. To batch changes, wrap the mutations in `beginUpdate()` and `endUpdate()`, during which no events are triggered and the listeners are called once at the end. Use `g.off('changed', yourHandler)` to stop listening.

Official sources

  1. anvaka/ngraph.graph on GitHub
  2. Issues
  3. License: BSD-3-Clause
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/anvaka-ngraph-graph.svg)](https://hysenlabs.com/projects/anvaka-ngraph-graph)

Was this useful?