Coder Social home page Coder Social logo

ngraph.graph's Introduction

ngraph.graph

Graph data structure for javascript. This library belongs to a family of javascript graph packages called ngraph.

build status

Install

With npm do:

npm install ngraph.graph

Or download from CDN:

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

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

Creating a graph

Create a graph with no edges and no nodes:

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

Growing a graph

The graph g can be grown in two ways. You can add one node at a time:

g.addNode('hello');
g.addNode('world');

Now graph g contains two nodes: hello and world. You can also use addLink() method to grow a graph. Calling this method with nodes which are not present in the graph creates them:

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

If nodes already present in the graph 'addLink()' makes them connected:

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

What to use as nodes and edges?

The most common and convenient choices are numbers and strings. You can associate arbitrary data with node via optional second argument of addNode() method:

// 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

You can also associate arbitrary object with a link using third optional argument of addLink() method:

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

Enumerating nodes and links

After you created a graph one of the most common things to do is to enumerate its nodes/links to perform an operation.

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

The function takes callback which accepts current node. Node object may contain internal information. node.id and node.data represent parameters passed to the g.addNode(id, data) method and they are guaranteed to be present in future versions of the library.

To enumerate all links in the graph use forEachLink() method:

g.forEachLink(function(link) {
    console.dir(link);
});

To enumerate all links for a specific node use forEachLinkedNode() method:

g.forEachLinkedNode('hello', function(linkedNode, link){
    console.log("Connected node: ", linkedNode.id, linkedNode.data);
    console.dir(link); // link object itself
});

This method always enumerates both inbound and outbound links. If you want to get only outbound links, pass third optional argument:

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

To get a particular node object use getNode() method. E.g.:

var world = g.getNode('world'); // returns 'world' node
console.log(world.id, world.data);

To get a particular link object use getLink() method:

var helloWorldLink = g.getLink('hello', 'world'); // returns a link from 'hello' to 'world'
console.log(helloWorldLink);

To remove a node or a link from a graph use removeNode() or removeLink() correspondingly:

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

You can also remove all nodes and links by calling

g.clear();

Listening to Events

Whenever someone changes your graph you can listen to notifications:

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

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

Each change record holds 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
}

Sometimes it is desirable to react only on bulk changes. ngraph.graph supports this via beginUpdate()/endUpdate() methods:

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

If you want to stop listen to events use off() method:

g.off('changed', yourHandler); // no longer interested in changes from graph

For more information about events, please follow to ngraph.events

License

BSD 3-clause

ngraph.graph's People

Contributors

0xflotus avatar alexclifton4 avatar anvaka avatar coraythan avatar dependabot[bot] avatar enniel avatar erenard avatar felixmo42 avatar gaponov avatar lambdalisue avatar lordnox avatar zakhenry avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ngraph.graph's Issues

forEachNode typing not quite correct

As the forEachNode loop will stop when returning true

ngraph.graph/index.js

Lines 520 to 524 in b5e768a

for (var i = 0; i < keys.length; ++i) {
if (callback(nodes[keys[i]])) {
return true; // client doesn't want to proceed. Return.
}
}

The typing of forEachNode should be amended:

/** To stop the iteration return true in the callback */
forEachNode: (callbackPerNode: (node: Node<NodeData>) => void | undefined | null | boolean) => void

This would allow the use of the function as described in the readme:

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

Typescript will fail here as the return value is undefined and not boolean

Exiting `ForEach` family of functions

Does the returning true to exit only work for .forEachNode and not forEachLink and forEachLinkedNode?

That seems to be what the typescript file suggests, but it seems like a rather odd discrepancy between functions of the same family.

PS: Also, I don't believe this feature is documented in the README

Inconsistencies with links, multigraphs and non-oriented graphs

I have been using and old version since years, but the newer versions are even worse in this aspect (due to map usage and ids).

When 2 nodes have multiple links between them (i.e. multigraph), the current getLink() method always retrieve the same link with no way to know if there are more or which one to choose. In newer versions getLink just retrieves the first one created which happens to match the non unique ID.

While this is irrelevant most times, when using a graph for path calculations, if you want to retrieve a link between 2 nodes, you usually want an specific link (i.e. the shortest one), not the first one. In previous versions you would iterate over the node links, so back to that (instead of relying on the map), you would need to add some kind of function which must be minimized.

  function getLink(aNodeId, bNodeId, minimizer) {
    // TODO: Use sorted links to speed this up
    var node = getNode(aNodeId),
      i;
    if (!node || !node.links) {
      return null;
    }
    let link = null;
    let iLink;
    let min = Infinity;
    for (i = 0; i < node.links.length; ++i) {
      iLink = node.links[i];
      if (iLink.fromId === aNodeId && iLink.toId === bNodeId) {
        if (minimizer) {
            const newMin = minimizer(iLink);
            if (newMin < min) {
               min = newMin;
               link = iLink;
            } else if (!link && newMin === min ) {
				link = iLink;
			}
        } else {link = iLink; break;}
      }
    }
    return link;
  }

Note iterating over current node links is pretty much as fast as the map, since is just a subset of links. You are not really gaining a lot, since every node should have at least a link in most use cases. So getNode iterates all nodes which should have the same size than the links map (or even smaller in most cases).

Then it's as simple as

mygraph.getLink(nodeA, nodeB, (link) => link.data.weight);

Even if you don't add something like this, the current behavior for multigraphs should be warned at least (or a new method given to retrieve all possible links between A and B).

Then... continuing with this, non oriented graphs! (which to me is equal to multigraphs handling). getLink(a,b) is different to geLink(b,a). Since there is no specific option for oriented/non oriented graphs, a new method should be created to to retrieve any of those links.

  function getNonOrientedLink(aNodeId, bNodeId, minimizer) {
    // TODO: Use sorted links to speed this up
    var node = getNode(aNodeId),
      i;
    if (!node || !node.links) {
      return null;
    }
    let link = null;
    let iLink;
    let min = Infinity;
    for (i = 0; i < node.links.length; ++i) {
      iLink = node.links[i];
      if (iLink.fromId === aNodeId && iLink.toId === bNodeId || iLink.fromId === bNodeId && iLink.toId === aNodeId) {
        if (minimizer) {
            const newMin = minimizer(iLink);
            if (newMin < min) {
               min = newMin;
               link = iLink;
            } else if (!link && newMin === min ) {
				link = iLink;
			}
        } else {link = iLink; break;}
      }
    }
    return link;
  }

Finally, if maps are to be used. A better way to handle multigraphs could be just creating a map of arrays.

function addLink(fromId, toId, data) {
    enterModification();

    var fromNode = getNode(fromId) || addNode(fromId);
    var toNode = getNode(toId) || addNode(toId);

    var link = createLink(fromId, toId, data);
    var isUpdate = links.has(link.id);

    const idLinks = links.get(link.id);
    (if !idLinks || !options.multigraph || !options.oriented) {links.set(link.id, [link]);}
    else {idLinks.push(link);}

    // TODO: this is not cool. On large graphs potentially would consume more memory.
    addLinkToNode(fromNode, link);
    if (fromId !== toId) {
      // make sure we are not duplicating links for self-loops
      addLinkToNode(toNode, link);
    }

    recordLinkChange(link, isUpdate ? 'update' : 'add');

    exitModification();

    return link;
  }

And then you can have infinite links between nodes that way, no need to differentiate between multigraphs or not (there is also no need for uniqueIds). In single-edge-graphs, getLink would just retrieve the first link of the array. On multi-graphs, either the first one or one according to the minimizer.

Note I added a check for multigraphs in case you explicitly want to force single links.

The good thing about this approach is that you can directly enforce a oriented/non oriented option. For non oriented graphs, you can check whether there is already a link a-b or b-a and treat as a non-multigraph. Oriented graphs would simply be multigraphs with arrays of length <=2.

function makeLinkId(fromId, toId) { // With this there is no need to check both cases for non oriented graphs
  return [fromId, toId].sort().join( '๐Ÿ‘‰ ');
}

Checking oriented graphs would be a matter of changing getLink, to match the from and to in the proper order for the array of 2 links.

Not doing a PR since these are breaking changes and don't want to spend time on something may not be implemented at all.

Get link by id

Hello!

Currently it's possible to get a link from the graph through several ways:

  • using getNode and iterating over the links property
  • via getLinks and getLink
  • via nodes and links iterators

However, there's no way to get a link by its id property, which would be useful if you store these ids externally (when the graph is created or changed) for later use.

Merging multiple graphs?

Hi.
Thanks for this awesome library! I was able to visualize some pretty big datasets (5-10 millions of nodes and 15 millions of links).

So I tried to increase the number of nodes(somewhere around 50 millions).
I have 32GB of RAM btw.

My current issue is that creating a ngraph.graph instance is a bit difficult. I mean because of such a large dataset my PC memory gets full very fast.

So I was wondering if there is a way to save several ngraph.graph instances into files and then merge them (so the result next can be processed with ngraph.tobinary).
Or maybe there is another approach to this issue?

Thanks!

How can I load more edges?

hi, I got an out of memory error information like that

<--- Last few GCs --->
[10428:0x2fef4e0] 18700 ms: Mark-sweep 1250.5 (1319.9) -> 1250.5 (1288.9) MB, 1319.0 / 0.0 ms (average mu = 0.279, current mu = 0.000) last resort GC in old space requested
[10428:0x2fef4e0] 20101 ms: Mark-sweep 1250.5 (1288.9) -> 1250.5 (1288.9) MB, 1401.4 / 0.0 ms (average mu = 0.153, current mu = 0.000) last resort GC in old space requested
...
ATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

when i want to load 500W edges (400W edges are loaded well).

But I got the same error when I used a 196GB memory machine instead of 64GB mem machine.
So could you tell me how setup the memory to let me load more edges?

Thanks.

Add more advanced ways of iterating nodes and links

Hi @anvaka!

Thank you for your huge work on the ngraph packages family. It is cool, except there is a vital thing not implemented yet - so is a convenient and universal way to iterate through nodes/links. The existing way of iteration via callback provided to internal foreach cycle is not enough, as:

  1. The ngraph.graph package controls the iteration and one cannot suspend execution somewhere in the middle of the cycle.

  2. It requires an intermediate array to write graph elements to if one wants to pass them anywhere else.

#1 is a pretty similar issue, @ZigGreen probably wanted the same thing - a convenient way to iterate through elements.

I suppose adding methods that will return ECMAScript 6 iterators a cool way to deal with this problem. An iterator is a simple object so it is completely compatible with vanilla JavaScript.

Everyone who is happy to afford using ECMAScript 6 features can then use for..of and *yield syntax with the returned iterator. For everybody else we could create a simple ngraph.iteration package with common functional methods, such as map/filter/forEach, written on vanilla JS.

Another benefit of this approach is that the internal nodes object won't be able to be changed by user.

The only possible problem may be a concurrency issue... or not?

What do you think about it?

Documentation Error: `removeLink` using two arguments

The removeLink method has the following passage in the documentation:

If you pass two arguments, the function assumes they represent from/to node ids

I assume this means something like removeLink(fromID,toID)? If so, the method's typing doesn't support this signature.

Linking nodes in the graph doesn't create links in the nodes

Hello! I am using ngraph.graph version ^20.0.0 as specified in my package.json.

I am creating a graph from a file planet-route-data.json like so:

import createGraph from 'ngraph.graph'

function makeTravelGraph () {
    const graph = createGraph()
    const travelGraphData = JSON.parse(fs.readFileSync('./src/planet-route-data.json'))

    // first, we create nodes
    travelGraphData.forEach(sourcePlanet => {
        graph.addNode(sourcePlanet.name)
    })

    // then, we link them
    travelGraphData.forEach(sourcePlanet => {
        sourcePlanet.destinations.forEach(destination => {
            graph.addLink(sourcePlanet.name, destination.name, { fuelCost: destination.cost })
        })
    })

   return graph
}

My planet-route-data.json file looks like this (please ignore the values themselves, haha):

[
    {
        "name": "Earth",
        "destinations": [
            {
                "name": "Mars",
                "cost": 13
            },
            {
                "name": "Venus",
                "cost": 23 
            }
        ]
    },
    {
        "name": "Mars",
        "destinations": [
            {
                "name": "Jupiter",
                "cost": 22
            },
            {
                "name": "Saturn",
                "cost": 25 
            }
        ]
    },
    {
        "name": "Saturn",
        "destinations": [
            {
                "name": "Jupiter",
                "cost": 30 
            },
            {
                "name": "Earth",
                "cost": 12 
            }
        ]
    },

    {
        "name": "Venus",
        "destinations": [
            {
                "name": "Earth",
                "cost": 20 
            },
            {
                "name": "Mars",
                "cost": 25
            },
            {
                "name": "Mercury",
                "cost": 15
            }
        ]
    }
]

The nodes in the graph are being created, and links seem to be created successfully -- that is, I can list them as expected with something like:

returnedGraph.forEachLink(link => {
    console.log(link)
}

However, the nodes themselves, which contain a property named links, do not seem to be updated. If I try to print them, their links property looks like an empty object: {}. If I do:

returnedGraph.forEachLinkedNode(node => {
    console.log(node)
}

nothing is iterated upon, presumably as there are no nodes with links to be found.

Is this, by any chance, by design, or is it not intended behavior?

Extending ngraph.graph for visibility graph

Gday @anvaka

Im looking at adopting ngraph.graph for my visibility graph algorithm - basically my library reads in polygons and returns a graph showing other visible nodes in the graph, this can be used for path finding by vessels (eg finding me the shortest path but avoid sailing on land!).

My library is functional but I think it would be nice to hook into the ngraph ecosystem you've created which has some neat features like saving graphs etc.

I'm trying to work out the best way to extend ngraph.graph, eg im envisaging something that looks like

var createGraph = require('ngraph.graph');
var g = createGraph();
g.loadPolygons(somepolygons);

// or

var g = require('ngraph.vis-graph').createGraph();
g.loadPolygons(somepolygons);
 

Any tips as to which of your repos might provide a good example of extending things in this sort of manner? I'm open to any ideas you might have as the best way to do this.

Thanks,
Rowan

Circular Detection

It would be nice to have a utility in the graph to detect if the graph was circular or not. This is useful for determining what kinds of graph layouts are most optimal given the data.

About weighted graph support.

Hello, lots of thanks for your great work on graph in js.

Here is question I've encountered in using these awesome projects. I am using pm to visualize some GitHub projects networks and it works fine right now but I need link weight support from the layout generation method.

I am using ngraph.offline.layout right now and I looked into the projects and I found that actually the physical simulator supports link weight/length in spring force calculation. But as this project is the basic data structure of ngraph and the type of addLink method only supports fromId, toId and data for custom data store.

Right now I need to do this as a work around:

(addLink(fromId, toId) as any).length = l;

Should I modify the interface to let users know that link length is supported already?

Thank you for your awesome work again~

Directed Graph Support

Is there any way to force the graph and pathfinder to obey link directions? The following example illustrates the problem. The path finder finds a path from "A" to "E" as expected. Unfortunately, it also finds a path from "E" to "A" even though no directed links exists between many of the nodes in this direction. Additionally, where links do exist ("C1" -> "B" and "C2" -> "B"), the given weights are ignored.

const createGraph = require('ngraph.graph');
const pathFinders = require("ngraph.path");

let graph = createGraph();

graph.addLink("A", "B", { weight: 1 });

graph.addLink("B", "C1", { weight: 1 });
graph.addLink("B", "C2", { weight: 5 });
graph.addLink("C1", "B", { weight: 5 });
graph.addLink("C2", "B", { weight: 1 });

graph.addLink("C1", "D", { weight: 1 });
graph.addLink("C2", "D", { weight: 1 });

graph.addLink("D", "E", { weight: 1 });
let finder = pathFinders.aStar(graph, {
distance: (fromNode, toNode, link) => link.data.weight
});

const test1 = finder.find("A", "E").reverse();
const test2 = finder.find("E", "A").reverse();

console.log("TEST 1");
test1.forEach(p => console.log("id:",p.id));

console.log("\nTEST 2");
test2.forEach(p => console.log("id:",p.id));

OUTPUT:
TEST 1
id: A
id: B
id: C1
id: D
id: E

TEST 2
id: E
id: D
id: C1
id: B
id: A

Finding total of linked nodes?

Sorry if this is outside the scope of the project, but could you advise on ways to find groups of linked nodes in the graph? I needed to count their total.

Many thanks

Updating beginUpdate to point to enterModificationReal

createGraph sets beginUpdate to enterModification,

When calling the 'on' function:
enterModification = enterModificationReal, gets executed, BUT beginUpdate doesn't get updated, the function still holds the original value of enterModification which is noop.

tested on Chrome 41.0.2272.

the following example illustrates the problem:
var c = function() { console.log('c'); };
var b = c;
var a = b;

b = function() { console.log('b'); };

a(); // Prints 'c'
Would like a to print 'b'.

Add hasNode function

As the title describes a hasNode function is missing and it is one of the basic building blocks of a graph data structure.

it would be great to implement one

Support for ECMA Script import

Hi, first of all, ngraph is a great project!

Maybe I'm overworked but trying to import ngraph.graph in my TypeScript project I'm not successfull:

import createGraph from 'ngraph.graph' cause TypeError: createGraph.default is not a function
import { createGraph } from 'ngraph.graph' cause Module '"ngraph.graph"' has no exported member 'createGraph'.

Am I doing something wrong or I missed something? Couldn't find any solution on the web :(

Code in bundle does not match source

The code in dist/ngraph.graph.js for v20.0.0 does not seem to match its source.

Comparison of lines 53-57 below (v20.0.0).

dist/ngraph.graph.js:

var nodes = new Map();
  var links = [],
    // Hash of multi-edges. Used to track ids of edges between same nodes
    multiEdges = {},
    suspendEvents = 0,

index.js:

var nodes = new Map(); // nodeId => Node
  var links = new Map(); // linkId => Link
    // Hash of multi-edges. Used to track ids of edges between same nodes
  var multiEdges = {};
  var suspendEvents = 0;

It looks like the code in the bundle actually matches the index.js of the previous version (v19.1.0).

getlink for multigraph

From reading the code, it seems that getLlink(a,b) just returns the first link it finds between a and b. But if you have the multigraph option set to true, there may be multiple links.
The reason I ask is I am using ngraph.path on a multigraph, and I want to get which particular links are being used.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.