├── .travis.yml ├── doc ├── grid.png ├── grid3.png ├── ladder.png ├── path.png ├── complete.png ├── noLinks.png ├── balancedBinTree.png ├── circularLadder.png ├── completeBipartite.png ├── wattsStrogatz_100_20_00.png ├── wattsStrogatz_100_20_01.png ├── wattsStrogatz_100_20_10.png ├── wattsStrogatz_100_20_50.png ├── wattsStrogatz_20_04_02.png ├── package.json └── index.js ├── .gitignore ├── .github └── workflows │ └── tests.yaml ├── package.json ├── LICENSE ├── index.d.ts ├── README.md ├── test └── create.js ├── index.js └── dist ├── ngraph.generators.min.js └── ngraph.generators.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | -------------------------------------------------------------------------------- /doc/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anvaka/ngraph.generators/HEAD/doc/grid.png -------------------------------------------------------------------------------- /doc/grid3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anvaka/ngraph.generators/HEAD/doc/grid3.png -------------------------------------------------------------------------------- /doc/ladder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anvaka/ngraph.generators/HEAD/doc/ladder.png -------------------------------------------------------------------------------- /doc/path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anvaka/ngraph.generators/HEAD/doc/path.png -------------------------------------------------------------------------------- /doc/complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anvaka/ngraph.generators/HEAD/doc/complete.png -------------------------------------------------------------------------------- /doc/noLinks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anvaka/ngraph.generators/HEAD/doc/noLinks.png -------------------------------------------------------------------------------- /doc/balancedBinTree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anvaka/ngraph.generators/HEAD/doc/balancedBinTree.png -------------------------------------------------------------------------------- /doc/circularLadder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anvaka/ngraph.generators/HEAD/doc/circularLadder.png -------------------------------------------------------------------------------- /doc/completeBipartite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anvaka/ngraph.generators/HEAD/doc/completeBipartite.png -------------------------------------------------------------------------------- /doc/wattsStrogatz_100_20_00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anvaka/ngraph.generators/HEAD/doc/wattsStrogatz_100_20_00.png -------------------------------------------------------------------------------- /doc/wattsStrogatz_100_20_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anvaka/ngraph.generators/HEAD/doc/wattsStrogatz_100_20_01.png -------------------------------------------------------------------------------- /doc/wattsStrogatz_100_20_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anvaka/ngraph.generators/HEAD/doc/wattsStrogatz_100_20_10.png -------------------------------------------------------------------------------- /doc/wattsStrogatz_100_20_50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anvaka/ngraph.generators/HEAD/doc/wattsStrogatz_100_20_50.png -------------------------------------------------------------------------------- /doc/wattsStrogatz_20_04_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anvaka/ngraph.generators/HEAD/doc/wattsStrogatz_20_04_02.png -------------------------------------------------------------------------------- /doc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "ngraph.fabric": "0.0.1", 4 | "ngraph.forcelayout": "0.0.10", 5 | "fabric": "^1.4.3" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | reports 14 | .nyc_output 15 | .tap 16 | 17 | npm-debug.log 18 | node_modules 19 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [20.x, 18.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v2 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - run: npm ci 25 | - run: npm run build --if-present 26 | - run: npm test 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngraph.generators", 3 | "version": "21.0.0", 4 | "description": "Graph generators library", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "tap test/*.js", 8 | "build": "browserify index.js -s generators -o dist/ngraph.generators.js && uglifyjs dist/ngraph.generators.js -o dist/ngraph.generators.min.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/anvaka/ngraph.generators.git" 13 | }, 14 | "keywords": [ 15 | "graph", 16 | "generator", 17 | "random", 18 | "ngraph", 19 | "ngraphjs" 20 | ], 21 | "author": "Andrei Kashcha", 22 | "license": "BSD-3-Clause", 23 | "bugs": { 24 | "url": "https://github.com/anvaka/ngraph.generators/issues" 25 | }, 26 | "devDependencies": { 27 | "browserify": "^17.0.0", 28 | "tap": "^21.1.0", 29 | "uglify-js": "^3.14.5" 30 | }, 31 | "dependencies": { 32 | "miserables": "^3.0.0", 33 | "ngraph.graph": "^20.0.1", 34 | "ngraph.random": "^1.0.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2025, Andrei Kashcha 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | Neither the name of the Andrei Kashcha nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /doc/index.js: -------------------------------------------------------------------------------- 1 | var createFabricGraphics = require('ngraph.fabric'); 2 | var generators = require('../'); 3 | 4 | var factoryParams = { 5 | ladder: { args: [10] }, 6 | complete: { args: [6] }, 7 | completeBipartite: { args: [3 , 3] }, 8 | balancedBinTree: { args: [5] }, 9 | path: { args: [10] }, 10 | circularLadder: { args: [5] }, 11 | grid: { args: [10, 10] }, 12 | grid3: { args: [5, 5, 5] }, 13 | noLinks: { args: [100] }, 14 | wattsStrogatz_100_20_00: { args: [100, 20, 0] }, 15 | wattsStrogatz_100_20_01: { args: [100, 20, 0.01] }, 16 | wattsStrogatz_100_20_10: { args: [100, 20, 0.1] }, 17 | wattsStrogatz_100_20_50: { args: [100, 20, 0.5] }, 18 | wattsStrogatz_20_04_02: { args: [20, 4, 0.02] } 19 | }; 20 | 21 | Object.keys(factoryParams).forEach(renderGraph); 22 | 23 | function renderGraph(name) { 24 | var generatorName = name.split('_')[0]; 25 | var factorySettings = factoryParams[name]; 26 | var graph = generators[generatorName].apply(null, factorySettings.args); 27 | var layout = layoutGraph(graph, factorySettings.iterations || 200); 28 | var canvas = renderToCanvas(graph, layout); 29 | saveCanvasToFile(canvas, name + '.png'); 30 | } 31 | 32 | function layoutGraph(graph, iterationsCount) { 33 | // we are going to use our own layout: 34 | var layout = require('ngraph.forcelayout')(graph); 35 | console.log('Running layout...'); 36 | for (var i = 0; i < iterationsCount; ++i) { 37 | layout.step(); 38 | } 39 | console.log('Done. Rendering graph...'); 40 | return layout; 41 | } 42 | 43 | function renderToCanvas(graph, layout) { 44 | var graphRect = layout.getGraphRect(); 45 | var size = Math.max(graphRect.x2 - graphRect.x1, graphRect.y2 - graphRect.y1) + 200; 46 | 47 | var fabricGraphics = createFabricGraphics(graph, { width: size, height: size, layout: layout }); 48 | var fabric = require('fabric').fabric; 49 | 50 | var scale = 1; 51 | fabricGraphics.setTransform(size/2, size/2, scale); 52 | fabricGraphics.renderOneFrame(); // One frame is enough 53 | 54 | return fabricGraphics.canvas; 55 | } 56 | 57 | function saveCanvasToFile(canvas, fileName) { 58 | var fs = require('fs'); 59 | var path = require('path'); 60 | var fullName = path.join(__dirname, fileName); 61 | var outFile = fs.createWriteStream(fullName); 62 | 63 | canvas.createPNGStream().on('data', function(chunk) { 64 | outFile.write(chunk); 65 | }).on('end', function () { 66 | console.log('Graph saved to: ' + fullName); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "ngraph.generators" { 2 | import { Graph } from 'ngraph.graph' 3 | 4 | interface Factory { 5 | /** 6 | * Ladder graph is a graph in form of ladder 7 | * @param {Number} n Represents number of steps in the ladder 8 | */ 9 | ladder(n: number): Graph 10 | /** 11 | * Complete graph Kn. 12 | * 13 | * @param {Number} n represents number of nodes in the complete graph. 14 | */ 15 | complete(n: number): Graph 16 | /** 17 | * Complete bipartite graph K n,m. Each node in the 18 | * first partition is connected to all nodes in the second partition. 19 | * 20 | * @param {Number} n represents number of nodes in the first graph partition 21 | * @param {Number} m represents number of nodes in the second graph partition 22 | */ 23 | completeBipartite(n: number, m: number): Graph 24 | 25 | /** 26 | * Balanced binary tree with n levels. 27 | * 28 | * @param {Number} n of levels in the binary tree 29 | */ 30 | balancedBinTree(n: number): Graph 31 | balancedBinaryTree(n: number): Graph 32 | binaryTree(n: number): Graph 33 | binTree(n: number): Graph 34 | 35 | /** 36 | * Path graph with n steps. 37 | * 38 | * @param {Number} n number of nodes in the path 39 | */ 40 | path(n: number): Graph 41 | /** 42 | * Circular ladder with n steps. 43 | * 44 | * @param {Number} n of steps in the ladder. 45 | */ 46 | circularLadder(n: number): Graph 47 | /** 48 | * Grid graph with n rows and m columns. 49 | * 50 | * @param {Number} n of rows in the graph. 51 | * @param {Number} m of columns in the graph. 52 | */ 53 | grid(n: number, m: number): Graph 54 | /** 55 | * 3D grid with n rows and m columns and z levels. 56 | * 57 | * @param {Number} n of rows in the graph. 58 | * @param {Number} m of columns in the graph. 59 | * @param {Number} z of levels in the graph. 60 | */ 61 | grid3(n: number, m: number, z: number): Graph 62 | /** 63 | * Graph with no links 64 | * 65 | * @param {Number} n of nodes in the graph 66 | */ 67 | noLinks(n: number): Graph 68 | /** 69 | * Watts-Strogatz small-world graph. 70 | * 71 | * @param {Number} n The number of nodes 72 | * @param {Number} k Each node is connected to k nearest neighbors in ring topology 73 | * @param {Number} p The probability of rewiring each edge 74 | * @see https://github.com/networkx/networkx/blob/master/networkx/generators/random_graphs.py 75 | */ 76 | wattsStrogatz(n: number, m: number, p: number): Graph 77 | /** 78 | * A circular graph with cliques instead of individual nodes 79 | * 80 | * @param {Number} cliqueCount number of cliques inside circle 81 | * @param {Number} cliqueSize number of nodes inside each clique 82 | */ 83 | cliqueCircle(n: number, m: number): Graph 84 | } 85 | 86 | const factory: Factory; 87 | 88 | export default factory; 89 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Graph generators 2 | ================= 3 | This module generates various graphs. It is part of larger [ngraph](https://github.com/anvaka/ngraph) 4 | family. If you need something not as simple as generated graphs, please check out 5 | [ngraph.sparce-collection](https://github.com/anvaka/ngraph.sparse-collection) repository 6 | which contains graphs from University of Florida collection. 7 | 8 | [![Build Status](https://github.com/anvaka/ngraph.generators/actions/workflows/tests.yaml/badge.svg)](https://github.com/anvaka/ngraph.generators/actions/workflows/tests.yaml) 9 | 10 | 11 | # Install 12 | 13 | With npm do: 14 | 15 | ``` 16 | npm install ngraph.generators 17 | ``` 18 | 19 | If you prefer CDN you can also do: 20 | 21 | ``` html 22 | 23 | ``` 24 | 25 | In case of CDN the library will be accessible under global name `generators` 26 | 27 | # Supported graphs 28 | 29 | All images below are clickable and point to interactive 3d visualization, done by `ngraph.three` library. 30 | 31 | ## ladder 32 | 33 | ``` js 34 | // Creates a ladder with 10 steps 35 | var graph = require('ngraph.generators').ladder(10); 36 | ``` 37 | [![Ladder](https://raw.githubusercontent.com/anvaka/ngraph.generators/main/doc/ladder.png)](http://anvaka.github.io/ngraph/examples/three.js/Basic/index.html?graph=ladder&n=10) 38 | 39 | ## complete 40 | 41 | ``` js 42 | // Creates complete graph K6 43 | var graph = require('ngraph.generators').complete(6); 44 | ``` 45 | [![Complete](https://raw.githubusercontent.com/anvaka/ngraph.generators/main/doc/complete.png)](http://anvaka.github.io/ngraph/examples/three.js/Basic/index.html?graph=complete&n=6) 46 | 47 | ## completeBipartite 48 | 49 | ``` js 50 | // Creates complete bipartite graph K 3,3. 51 | var graph = require('ngraph.generators').completeBipartite(3, 3); 52 | ``` 53 | [![Complete Bipartite](https://raw.githubusercontent.com/anvaka/ngraph.generators/main/doc/completeBipartite.png)](http://anvaka.github.io/ngraph/examples/three.js/Basic/index.html?graph=completeBipartite&n=3&m=3) 54 | 55 | ## balancedBinTree 56 | 57 | ``` js 58 | // Creates balanced binary tree with n levels. 59 | var graph = require('ngraph.generators').balancedBinTree(5); 60 | ``` 61 | [![Balanced Binary Tree](https://raw.githubusercontent.com/anvaka/ngraph.generators/main/doc/balancedBinTree.png)](http://anvaka.github.io/ngraph/examples/three.js/Basic/index.html?graph=balancedBinTree&n=5) 62 | 63 | ## path 64 | 65 | ``` js 66 | // Generates a path-graph with 10 steps. 67 | var graph = require('ngraph.generators').path(10); 68 | ``` 69 | [![Path](https://raw.githubusercontent.com/anvaka/ngraph.generators/main/doc/path.png)](http://anvaka.github.io/ngraph/examples/three.js/Basic/index.html?graph=path&n=10) 70 | 71 | ## circularLadder 72 | 73 | ``` js 74 | // Generates a graph in a form of a circular ladder with 5 steps. 75 | var graph = require('ngraph.generators').circularLadder(5); 76 | ``` 77 | [![Circular Ladder](https://raw.githubusercontent.com/anvaka/ngraph.generators/main/doc/circularLadder.png)](http://anvaka.github.io/ngraph/examples/three.js/Basic/index.html?graph=circularLadder&n=5) 78 | 79 | ## grid 80 | 81 | ``` js 82 | // Generates a graph in a form of a grid with 10 rows and 10 columns. 83 | var graph = require('ngraph.generators').grid(10, 10); 84 | ``` 85 | [![Grid](https://raw.githubusercontent.com/anvaka/ngraph.generators/main/doc/grid.png)](http://anvaka.github.io/ngraph/examples/three.js/Basic/index.html?graph=grid&n=10&m=10) 86 | 87 | ## grid3 88 | 89 | ``` js 90 | // Generates a graph in a form of a 3d grid with 5 rows and 5 columns and 5 levels. 91 | var graph = require('ngraph.generators').grid3(5, 5, 5); 92 | ``` 93 | [![Grid 3d](https://raw.githubusercontent.com/anvaka/ngraph.generators/main/doc/grid3.png)](http://anvaka.github.io/ngraph/examples/three.js/Basic/index.html?graph=grid3&z=800&n=10&m=10&k=10) 94 | 95 | ## noLinks 96 | 97 | ``` js 98 | // Creates graph with 100 nodes and 0 links 99 | var graph = require('ngraph.generators').noLinks(100); 100 | ``` 101 | [![No Links](https://raw.githubusercontent.com/anvaka/ngraph.generators/main/doc/noLinks.png)](http://anvaka.github.io/ngraph/examples/three.js/Basic/index.html?graph=noLinks&n=100) 102 | 103 | ## cliqueCircle 104 | 105 | ``` js 106 | var cliqueCount = 10; 107 | var cliqueSize = 5; 108 | 109 | // create a circle, with `cliqueCount` nodes. Each node is a fully connected 110 | // graph with `cliqueSize` nodes 111 | var graph = require('ngraph.generators').cliqueCircle(cliqueCount, cliqueSize); 112 | ``` 113 | 114 | ## WattsStrogatz 115 | 116 | This is a "small world" random graph, generated by [Watts and Strogatz model](http://en.wikipedia.org/wiki/Watts_and_Strogatz_model). 117 | In this model generator takes three arguments: 118 | 119 | * `n` - number of nodes 120 | * `k` - number of edges for each node. Originally node is connected with `k` nearest neighbours on a circle graph 121 | * `b` - probability of an edge rewrite. In other words node changes it's nearest neighbor to a random 122 | node inside graph with probability `b`. 123 | 124 | ``` js 125 | // Creates graph with 100 nodes, each node is connected with 20 neighbours, 126 | // and probability of neighbour to be outside of local node community is 1%. 127 | var g1 = require('ngraph.generators').wattsStrogatz(100, 20, 0.00); 128 | var g2 = require('ngraph.generators').wattsStrogatz(100, 20, 0.01); 129 | var g3 = require('ngraph.generators').wattsStrogatz(100, 20, 0.10); 130 | var g4 = require('ngraph.generators').wattsStrogatz(100, 20, 0.50); 131 | var g5 = require('ngraph.generators').wattsStrogatz(20, 4, 0.02); 132 | ``` 133 | 134 | ### Watts Strogatz n = 100 k = 20 b = 0.00 (g1): 135 | [![Watts Strogatz n = 100 k = 20 b = 0.00](https://raw.githubusercontent.com/anvaka/ngraph.generators/main/doc/wattsStrogatz_100_20_00.png)](http://anvaka.github.io/ngraph/examples/three.js/Basic/index.html?graph=wattsStrogatz&n=100&m=20&k=0.00) 136 | 137 | ### Watts Strogatz n = 100 k = 20 b = 0.01 (g2): 138 | [![Watts Strogatz n = 100 k = 20 b = 0.01](https://raw.githubusercontent.com/anvaka/ngraph.generators/main/doc/wattsStrogatz_100_20_01.png)](http://anvaka.github.io/ngraph/examples/three.js/Basic/index.html?graph=wattsStrogatz&n=100&m=20&k=0.01) 139 | 140 | ### Watts Strogatz n = 100 k = 20 b = 0.10 (g3): 141 | [![Watts Strogatz n = 100 k = 20 b = 0.10](https://raw.githubusercontent.com/anvaka/ngraph.generators/main/doc/wattsStrogatz_100_20_10.png)](http://anvaka.github.io/ngraph/examples/three.js/Basic/index.html?graph=wattsStrogatz&n=100&m=20&k=0.10) 142 | 143 | ### Watts Strogatz n = 100 k = 20 b = 0.50 (g4): 144 | [![Watts Strogatz n = 100 k = 20 b = 0.50](https://raw.githubusercontent.com/anvaka/ngraph.generators/main/doc/wattsStrogatz_100_20_50.png)](http://anvaka.github.io/ngraph/examples/three.js/Basic/index.html?graph=wattsStrogatz&n=100&m=20&k=0.50) 145 | 146 | ### Watts Strogatz n = 20 k = 04 b = 0.02 (g5): 147 | [![Watts Strogatz n = 20 k = 04 b = 0.02](https://raw.githubusercontent.com/anvaka/ngraph.generators/main/doc/wattsStrogatz_20_04_02.png)](http://anvaka.github.io/ngraph/examples/three.js/Basic/index.html?graph=wattsStrogatz&n=20&m=4&k=0.02) 148 | 149 | # Custom `createGraph()` 150 | 151 | By default this library uses `ngraph.graph` module to create new instances 152 | of a graph. If you want to use your own module, you can use `factory` method: 153 | 154 | ``` js 155 | var generate = require('ngraph.generators').factory(function createGraph() { 156 | // the following methods are required from the createGraph api: 157 | return { 158 | addLink(from, to) { 159 | // ... 160 | }, 161 | addNode(nodeId) { 162 | }, 163 | getNodesCount() { 164 | } 165 | } 166 | }); 167 | 168 | // now generators have the same methods as regular ngraph.generators: 169 | var graph = generate.ladder(3); 170 | generate.grid(10, 10); 171 | generate.balancedBinTree(4); 172 | // etc. 173 | ``` 174 | -------------------------------------------------------------------------------- /test/create.js: -------------------------------------------------------------------------------- 1 | var test = require('tap').test, 2 | generators = require('../'); 3 | 4 | test('Create ladder', function(t) { 5 | var graph = generators.ladder(3); 6 | // Ladder graph looks like this: 7 | // *--*--* 8 | // | | | 9 | // *--*--* 10 | t.equal(graph.getNodesCount(), 6, 'Unexpected number of nodes for ladder graph'); 11 | t.equal(graph.getLinksCount(), 7, 'Unexpected number of links for ladder graph'); 12 | t.end(); 13 | }); 14 | 15 | test('Create miserables', function(t) { 16 | var graph = generators.miserables(); 17 | t.equal(graph.getNodesCount(), 77, 'Unexpected number of nodes for miserables graph'); 18 | t.equal(graph.getLinksCount(), 254, 'Unexpected number of links for miserables graph'); 19 | t.end(); 20 | }); 21 | 22 | test('boundary conditions are checked', function(t) { 23 | t.throws(() => generators.ladder(), 'Ladder graph with no nodes should throw'); 24 | t.throws(() => generators.ladder(0), 'Ladder graph with 0 nodes should throw'); 25 | t.throws(() => generators.ladder(-10), 'Ladder graph with negative nodes should throw'); 26 | 27 | t.throws(() => generators.circularLadder(), 'Circular ladder graph with no nodes should throw'); 28 | t.throws(() => generators.circularLadder(0), 'Circular ladder graph with 0 nodes should throw'); 29 | t.throws(() => generators.circularLadder(-10), 'Circular ladder graph with negative nodes should throw'); 30 | 31 | t.throws(() => generators.complete( ), 'Complete graph with no nodes should throw'); 32 | t.throws(() => generators.complete( 0), 'Complete graph with 0 nodes should throw'); 33 | t.throws(() => generators.complete(-10), 'Complete graph with negative nodes should throw'); 34 | 35 | t.throws(() => generators.path( ), 'Path graph with no nodes should throw'); 36 | t.throws(() => generators.path( 0), 'Path graph with 0 nodes should throw'); 37 | t.throws(() => generators.path(-10), 'Path graph with negative nodes should throw'); 38 | 39 | t.throws(() => generators.balancedBinaryTree( ), 'Binary tree graph with no nodes should throw'); 40 | t.throws(() => generators.balancedBinaryTree(-10), 'Binary tree graph with negative nodes should throw'); 41 | 42 | t.throws(() => generators.noLinks(-10), 'No links graph with negative nodes should throw'); 43 | 44 | t.throws(() => generators.cliqueCircle(0, 1), 'Clique circle graph with 0 clique should throw'); 45 | t.throws(() => generators.cliqueCircle(1, 0), 'Clique circle graph with 0 clique size throw'); 46 | 47 | t.throws(() => generators.completeBipartite(0, 1), 'Complete bipartite graph with 0 nodes should throw'); 48 | t.throws(() => generators.completeBipartite(1, 0), 'Complete bipartite graph with 0 nodes should throw'); 49 | t.throws(() => generators.completeBipartite(), 'Complete bipartite graph with 0 nodes should throw'); 50 | 51 | t.throws(() => generators.grid(0, 0), 'Grid graph with 0 nodes should throw'); 52 | t.throws(() => generators.grid(1, 0), 'Grid graph with 0 nodes should throw'); 53 | t.throws(() => generators.grid(0, 1), 'Grid graph with 0 nodes should throw'); 54 | 55 | t.throws(() => generators.grid3(0, 1), 'Grid3 graph with missing nodes should throw'); 56 | t.end(); 57 | }); 58 | 59 | test('Create path', function(t) { 60 | var size = 5; 61 | var graph = generators.path(size); 62 | // Path is simple: 63 | // *--*--*--*--* 64 | t.equal(graph.getNodesCount(), size, 'Unexpected number of nodes for path graph'); 65 | t.equal(graph.getLinksCount(), (size - 1), 'Unexpected number of links for path graph'); 66 | t.end(); 67 | }); 68 | 69 | test('Create complete', function(t) { 70 | var size = 5; 71 | var graph = generators.complete(size); 72 | // Complete graph has all nodes connected with each other. 73 | t.equal(graph.getNodesCount(), size, 'Unexpected number of nodes for complete graph'); 74 | t.equal(graph.getLinksCount(), (size * (size - 1)/2), 'Unexpected number of links for complete graph'); 75 | t.end(); 76 | }); 77 | 78 | test('Create complete', function(t) { 79 | // see http://en.wikipedia.org/wiki/Complete_bipartite_graph 80 | var m = 3, n = 4; 81 | var graph = generators.completeBipartite(m, n); 82 | 83 | t.equal(graph.getNodesCount(), m + n, 'Unexpected number of nodes for complete bipartite graph'); 84 | t.equal(graph.getLinksCount(), (m * n), 'Unexpected number of links for complete bipartite graph'); 85 | t.end(); 86 | }); 87 | 88 | test('Circular ladder', function(t) { 89 | var size = 3; 90 | var graph = generators.circularLadder(size); 91 | // circular ladder is like a ladder, but last two and first two 92 | // nodes are connected 93 | // ┌-----┐ 94 | // *--*--* 95 | // | | | 96 | // *--*--* 97 | // └-----┘ 98 | t.equal(graph.getNodesCount(), 6, 'Unexpected number of nodes for circular ladder graph'); 99 | t.equal(graph.getLinksCount(), 9, 'Unexpected number of links for circular ladder graph'); 100 | t.end(); 101 | }); 102 | 103 | // grid is a grid. This is 3 x 4 grid: 104 | // *--*--*--* 105 | // | | | | 106 | // *--*--*--* 107 | // | | | | 108 | // *--*--*--* 109 | test('3x4 grid', function (t) { 110 | var graph = generators.grid(3, 4); 111 | t.equal(graph.getNodesCount(), 12, 'Unexpected number of nodes for grid graph'); 112 | t.equal(graph.getLinksCount(), 17, 'Unexpected number of links for grid graph'); 113 | t.end(); 114 | }); 115 | 116 | test('1x1 grid', function (t) { 117 | var graph = generators.grid(1, 1); 118 | t.equal(graph.getNodesCount(), 1, 'Unexpected number of nodes for 1x1 grid graph'); 119 | t.equal(graph.getLinksCount(), 0, 'Unexpected number of links for 1x1 grid graph'); 120 | t.end(); 121 | }); 122 | 123 | test('1x2 grid', function (t) { 124 | // this is a path of two nodes *--* 125 | var graph = generators.grid(1, 2); 126 | t.equal(graph.getNodesCount(), 2, 'Unexpected number of nodes for 1x2 grid graph'); 127 | t.equal(graph.getLinksCount(), 1, 'Unexpected number of links for 1x2 grid graph'); 128 | t.end(); 129 | }); 130 | 131 | test('Create balanced binary tree', function(t) { 132 | // This is a binary tree graph, of height 2: 133 | // * 134 | // * * 135 | // * * * * 136 | let names = ['balancedBinTree', 'balancedBinaryTree', 'binTree', 'binaryTree']; 137 | names.forEach(name => { 138 | var graph = generators[name](2); 139 | t.equal(graph.getNodesCount(), 7, 'Unexpected number of nodes for balanced binary tree'); 140 | t.equal(graph.getLinksCount(), 6, 'Unexpected number of links for balanced binary tree'); 141 | }); 142 | t.end(); 143 | }); 144 | 145 | test('Create no links', function(t) { 146 | // no links graph has no links. 147 | var graph = generators.noLinks(42); 148 | t.equal(graph.getNodesCount(), 42, 'Unexpected number of nodes in noLinks graph'); 149 | t.equal(graph.getLinksCount(), 0, 'Unexpected number of links in noLinks graph'); 150 | t.end(); 151 | }); 152 | 153 | 154 | // grid is a grid. This is 3 x 4 x 2 grid: 155 | // 156 | // *---*--*--* 157 | // / / / / | 158 | // *--*--*--* * 159 | // | | | | /| 160 | // *--*--*--* * 161 | // | | | | / 162 | // *--*--*--* 163 | // 164 | test('3x4x2 grid', function (t) { 165 | var graph = generators.grid3(3, 4, 2); 166 | t.equal(graph.getNodesCount(), 24, 'Unexpected number of nodes for grid3 graph'); 167 | t.equal(graph.getLinksCount(), 46, 'Unexpected number of links for grid3 graph'); 168 | t.end(); 169 | }); 170 | 171 | test('1x1x1 grid', function (t) { 172 | var graph = generators.grid3(1, 1, 1); 173 | t.equal(graph.getNodesCount(), 1, 'Unexpected number of nodes for 1x1x1 grid3 graph'); 174 | t.equal(graph.getLinksCount(), 0, 'Unexpected number of links for 1x1x1 grid3 graph'); 175 | t.end(); 176 | }); 177 | 178 | test('1x2x1 grid', function (t) { 179 | // this is a path of two nodes *--* 180 | var graph = generators.grid3(1, 2, 1); 181 | t.equal(graph.getNodesCount(), 2, 'Unexpected number of nodes for 1x2x1 grid3 graph'); 182 | t.equal(graph.getLinksCount(), 1, 'Unexpected number of links for 1x2x1 grid3 graph'); 183 | t.end(); 184 | }); 185 | 186 | test('cliqueCircle graph', function(t) { 187 | var graph = generators.cliqueCircle(2, 10); 188 | t.equal(graph.getNodesCount(), 20, 'Unexpected number of nodes for clique graph'); 189 | t.equal(graph.getLinksCount(), 92, 'Unexpected number of links for clique graph'); 190 | t.end(); 191 | }); 192 | 193 | test('4 nodes 2 link, 0% rebuild', function (t) { 194 | var graph = generators.wattsStrogatz(4, 2, 0); 195 | t.equal(graph.getNodesCount(), 4, 'Unexpected number of nodes for Watts-Strogatz graph n=4, k=2, p=0'); 196 | t.equal(graph.getLinksCount(), 4, 'Unexpected number of links for Watts-Strogatz graph n=4, k=2, p=0'); 197 | t.end(); 198 | }); 199 | 200 | test('20 nodes 0 links, 100% rebuild', function (t) { 201 | var graph = generators.wattsStrogatz(20, 0, 1); 202 | t.equal(graph.getNodesCount(), 20, 'Unexpected number of nodes for Watts-Strogatz graph n=20, k=0, p=1'); 203 | t.equal(graph.getLinksCount(), 0, 'Unexpected number of links for Watts-Strogatz graph n=20, k=0, p=1'); 204 | t.end(); 205 | }); 206 | 207 | test('20 nodes, 100% rebuild', function (t) { 208 | var graph = generators.wattsStrogatz(10, 9, 1, 1); 209 | t.equal(graph.getNodesCount(), 10, 'Unexpected number of nodes for Watts-Strogatz graph n=10, k=9, p=1'); 210 | t.equal(graph.getLinksCount(), 40, 'Unexpected number of links for Watts-Strogatz graph n=10, k=9, p=1'); 211 | t.end(); 212 | }); 213 | 214 | test('it can use custom factory', function(t) { 215 | var called = 0; 216 | generators.factory(createGraph).ladder(3); 217 | // t.equal(graph.getNodesCount(), 6, 'Unexpected number of nodes for ladder graph'); 218 | // t.equal(graph.getLinksCount(), 7, 'Unexpected number of links for ladder graph'); 219 | t.ok(called > 0, 'Factory was called') 220 | t.end(); 221 | 222 | function createGraph() { 223 | return { 224 | addLink: addLink 225 | } 226 | } 227 | 228 | function addLink(/* from, to */) { 229 | called += 1; 230 | } 231 | }); 232 | 233 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var createGraph = require('ngraph.graph'); 2 | 3 | var createMiserables = require('miserables').create; 4 | 5 | module.exports = factory(createGraph); 6 | 7 | // Allow other developers have their own createGraph 8 | module.exports.factory = factory; 9 | 10 | function factory(createGraph) { 11 | return { 12 | ladder: ladder, 13 | complete: complete, 14 | completeBipartite: completeBipartite, 15 | 16 | // these are synonyms 17 | balancedBinTree: balancedBinTree, 18 | balancedBinaryTree: balancedBinTree, 19 | binaryTree: balancedBinTree, 20 | binTree: balancedBinTree, 21 | 22 | path: path, 23 | circularLadder: circularLadder, 24 | grid: grid, 25 | grid3: grid3, 26 | noLinks: noLinks, 27 | wattsStrogatz: wattsStrogatz, 28 | cliqueCircle: cliqueCircle, 29 | miserables: createMiserables 30 | }; 31 | 32 | 33 | function ladder(n) { 34 | /** 35 | * Ladder graph is a graph in form of ladder 36 | * @param {Number} n Represents number of steps in the ladder 37 | */ 38 | if (!n || n < 0) { 39 | throw new Error('Invalid number of nodes'); 40 | } 41 | 42 | var g = createGraph(), 43 | i; 44 | 45 | for (i = 0; i < n - 1; ++i) { 46 | g.addLink(i, i + 1); 47 | // first row 48 | g.addLink(n + i, n + i + 1); 49 | // second row 50 | g.addLink(i, n + i); 51 | // ladder's step 52 | } 53 | 54 | g.addLink(n - 1, 2 * n - 1); 55 | // last step in the ladder; 56 | 57 | return g; 58 | } 59 | 60 | function circularLadder(n) { 61 | /** 62 | * Circular ladder with n steps. 63 | * 64 | * @param {Number} n of steps in the ladder. 65 | */ 66 | if (!n || n < 0) { 67 | throw new Error('Invalid number of nodes'); 68 | } 69 | 70 | var g = ladder(n); 71 | 72 | g.addLink(0, n - 1); 73 | g.addLink(n, 2 * n - 1); 74 | return g; 75 | } 76 | 77 | function complete(n) { 78 | /** 79 | * Complete graph Kn. 80 | * 81 | * @param {Number} n represents number of nodes in the complete graph. 82 | */ 83 | if (!n || n < 1) { 84 | throw new Error('At least two nodes are expected for complete graph'); 85 | } 86 | 87 | var g = createGraph(), 88 | i, 89 | j; 90 | 91 | for (i = 0; i < n; ++i) { 92 | for (j = i + 1; j < n; ++j) { 93 | if (i !== j) { 94 | g.addLink(i, j); 95 | } 96 | } 97 | } 98 | 99 | return g; 100 | } 101 | 102 | function completeBipartite(n, m) { 103 | /** 104 | * Complete bipartite graph K n,m. Each node in the 105 | * first partition is connected to all nodes in the second partition. 106 | * 107 | * @param {Number} n represents number of nodes in the first graph partition 108 | * @param {Number} m represents number of nodes in the second graph partition 109 | */ 110 | if (!n || !m || n < 0 || m < 0) { 111 | throw new Error('Graph dimensions are invalid. Number of nodes in each partition should be greater than 0'); 112 | } 113 | 114 | var g = createGraph(), 115 | i, j; 116 | 117 | for (i = 0; i < n; ++i) { 118 | for (j = n; j < n + m; ++j) { 119 | g.addLink(i, j); 120 | } 121 | } 122 | 123 | return g; 124 | } 125 | 126 | function path(n) { 127 | /** 128 | * Path graph with n steps. 129 | * 130 | * @param {Number} n number of nodes in the path 131 | */ 132 | if (!n || n < 0) { 133 | throw new Error('Invalid number of nodes'); 134 | } 135 | 136 | var g = createGraph(), 137 | i; 138 | 139 | g.addNode(0); 140 | 141 | for (i = 1; i < n; ++i) { 142 | g.addLink(i - 1, i); 143 | } 144 | 145 | return g; 146 | } 147 | 148 | 149 | function grid(n, m) { 150 | /** 151 | * Grid graph with n rows and m columns. 152 | * 153 | * @param {Number} n of rows in the graph. 154 | * @param {Number} m of columns in the graph. 155 | */ 156 | if (n < 1 || m < 1) { 157 | throw new Error('Invalid number of nodes in grid graph'); 158 | } 159 | var g = createGraph(), 160 | i, 161 | j; 162 | if (n === 1 && m === 1) { 163 | g.addNode(0); 164 | return g; 165 | } 166 | 167 | for (i = 0; i < n; ++i) { 168 | for (j = 0; j < m; ++j) { 169 | var node = i + j * n; 170 | if (i > 0) { g.addLink(node, i - 1 + j * n); } 171 | if (j > 0) { g.addLink(node, i + (j - 1) * n); } 172 | } 173 | } 174 | 175 | return g; 176 | } 177 | 178 | function grid3(n, m, z) { 179 | /** 180 | * 3D grid with n rows and m columns and z levels. 181 | * 182 | * @param {Number} n of rows in the graph. 183 | * @param {Number} m of columns in the graph. 184 | * @param {Number} z of levels in the graph. 185 | */ 186 | if (n < 1 || m < 1 || z < 1) { 187 | throw new Error('Invalid number of nodes in grid3 graph'); 188 | } 189 | var g = createGraph(), 190 | i, j, k; 191 | 192 | if (n === 1 && m === 1 && z === 1) { 193 | g.addNode(0); 194 | return g; 195 | } 196 | 197 | for (k = 0; k < z; ++k) { 198 | for (i = 0; i < n; ++i) { 199 | for (j = 0; j < m; ++j) { 200 | var level = k * n * m; 201 | var node = i + j * n + level; 202 | if (i > 0) { g.addLink(node, i - 1 + j * n + level); } 203 | if (j > 0) { g.addLink(node, i + (j - 1) * n + level); } 204 | if (k > 0) { g.addLink(node, i + j * n + (k - 1) * n * m ); } 205 | } 206 | } 207 | } 208 | 209 | return g; 210 | } 211 | 212 | function balancedBinTree(n) { 213 | /** 214 | * Balanced binary tree with n levels. 215 | * 216 | * @param {Number} n of levels in the binary tree 217 | */ 218 | if (n === undefined || n < 0) { 219 | throw new Error('Invalid number of levels in balanced tree'); 220 | } 221 | var g = createGraph(), 222 | count = Math.pow(2, n), 223 | level; 224 | 225 | if (n === 0) { 226 | g.addNode(1); 227 | } 228 | 229 | for (level = 1; level < count; ++level) { 230 | var root = level, 231 | left = root * 2, 232 | right = root * 2 + 1; 233 | 234 | g.addLink(root, left); 235 | g.addLink(root, right); 236 | } 237 | 238 | return g; 239 | } 240 | 241 | function noLinks(n) { 242 | /** 243 | * Graph with no links 244 | * 245 | * @param {Number} n of nodes in the graph 246 | */ 247 | if (n < 0) { 248 | throw new Error('Number of nodes should be >= 0'); 249 | } 250 | 251 | var g = createGraph(), i; 252 | for (i = 0; i < n; ++i) { 253 | g.addNode(i); 254 | } 255 | 256 | return g; 257 | } 258 | 259 | function cliqueCircle(cliqueCount, cliqueSize) { 260 | /** 261 | * A circular graph with cliques instead of individual nodes 262 | * 263 | * @param {Number} cliqueCount number of cliques inside circle 264 | * @param {Number} cliqueSize number of nodes inside each clique 265 | */ 266 | 267 | if (cliqueCount < 1) throw new Error('Invalid number of cliqueCount in cliqueCircle'); 268 | if (cliqueSize < 1) throw new Error('Invalid number of cliqueSize in cliqueCircle'); 269 | 270 | var graph = createGraph(); 271 | 272 | for (var i = 0; i < cliqueCount; ++i) { 273 | appendClique(cliqueSize, i * cliqueSize) 274 | 275 | if (i > 0) { 276 | graph.addLink(i * cliqueSize, i * cliqueSize - 1); 277 | } 278 | } 279 | graph.addLink(0, graph.getNodesCount() - 1); 280 | 281 | return graph; 282 | 283 | function appendClique(size, from) { 284 | for (var i = 0; i < size; ++i) { 285 | graph.addNode(i + from) 286 | } 287 | 288 | for (var i = 0; i < size; ++i) { 289 | for (var j = i + 1; j < size; ++j) { 290 | graph.addLink(i + from, j + from) 291 | } 292 | } 293 | } 294 | } 295 | 296 | function wattsStrogatz(n, k, p, seed) { 297 | /** 298 | * Watts-Strogatz small-world graph. 299 | * 300 | * @param {Number} n The number of nodes 301 | * @param {Number} k Each node is connected to k nearest neighbors in ring topology 302 | * @param {Number} p The probability of rewiring each edge 303 | 304 | * @see https://github.com/networkx/networkx/blob/master/networkx/generators/random_graphs.py 305 | */ 306 | if (k >= n) throw new Error('Choose smaller `k`. It cannot be larger than number of nodes `n`'); 307 | 308 | 309 | var random = require('ngraph.random').random(seed || 42); 310 | 311 | var g = createGraph(), i, to; 312 | for (i = 0; i < n; ++i) { 313 | g.addNode(i); 314 | } 315 | 316 | // connect each node to k/2 neighbors 317 | var neighborsSize = Math.floor(k/2 + 1); 318 | for (var j = 1; j < neighborsSize; ++j) { 319 | for (i = 0; i < n; ++i) { 320 | to = (j + i) % n; 321 | g.addLink(i, to); 322 | } 323 | } 324 | 325 | // rewire edges from each node 326 | // loop over all nodes in order (label) and neighbors in order (distance) 327 | // no self loops or multiple edges allowed 328 | for (j = 1; j < neighborsSize; ++j) { 329 | for (i = 0; i < n; ++i) { 330 | if (random.nextDouble() < p) { 331 | var from = i; 332 | to = (j + i) % n; 333 | 334 | var newTo = random.next(n); 335 | var needsRewire = (newTo === from || g.hasLink(from, newTo)); 336 | if (needsRewire && g.getLinks(from).length === n - 1) { 337 | // we cannot rewire this node, it has too many links. 338 | continue; 339 | } 340 | // Enforce no self-loops or multiple edges 341 | while (needsRewire) { 342 | newTo = random.next(n); 343 | needsRewire = (newTo === from || g.hasLink(from, newTo)); 344 | } 345 | var link = g.hasLink(from, to); 346 | g.removeLink(link); 347 | g.addLink(from, newTo); 348 | } 349 | } 350 | } 351 | 352 | return g; 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /dist/ngraph.generators.min.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.generators=f()}})(function(){var define,module,exports;return function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i0){g.addLink(node,i-1+j*n)}if(j>0){g.addLink(node,i+(j-1)*n)}}}return g}function grid3(n,m,z){if(n<1||m<1||z<1){throw new Error("Invalid number of nodes in grid3 graph")}var g=createGraph(),i,j,k;if(n===1&&m===1&&z===1){g.addNode(0);return g}for(k=0;k0){g.addLink(node,i-1+j*n+level)}if(j>0){g.addLink(node,i+(j-1)*n+level)}if(k>0){g.addLink(node,i+j*n+(k-1)*n*m)}}}}return g}function balancedBinTree(n){if(n===undefined||n<0){throw new Error("Invalid number of levels in balanced tree")}var g=createGraph(),count=Math.pow(2,n),level;if(n===0){g.addNode(1)}for(level=1;level= 0")}var g=createGraph(),i;for(i=0;i0){graph.addLink(i*cliqueSize,i*cliqueSize-1)}}graph.addLink(0,graph.getNodesCount()-1);return graph;function appendClique(size,from){for(var i=0;i=n)throw new Error("Choose smaller `k`. It cannot be larger than number of nodes `n`");var random=require("ngraph.random").random(seed||42);var g=createGraph(),i,to;for(i=0;i1){fireArguments=Array.prototype.splice.call(arguments,1)}for(var i=0;i0){graphPart.fire("changed",changes);changes.length=0}}function forEachNode(callback){if(typeof callback!=="function"){throw new Error("Function is expected to iterate over graph nodes. You passed "+callback)}var valuesIterator=nodes.values();var nextValue=valuesIterator.next();while(!nextValue.done){if(callback(nextValue.value)){return true}nextValue=valuesIterator.next()}}}function Node(id,data){this.id=id;this.links=null;this.data=data}function addLinkToNode(node,link){if(node.links){node.links.add(link)}else{node.links=new Set([link])}}function Link(fromId,toId,data,id){this.fromId=fromId;this.toId=toId;this.data=data;this.id=id}function makeLinkId(fromId,toId){return fromId.toString()+"👉 "+toId.toString()}},{"ngraph.events":4}],6:[function(require,module,exports){module.exports=random;module.exports.random=random,module.exports.randomIterator=randomIterator;function random(inputSeed){var seed=typeof inputSeed==="number"?inputSeed:+new Date;return new Generator(seed)}function Generator(seed){this.seed=seed}Generator.prototype.next=next;Generator.prototype.nextDouble=nextDouble;Generator.prototype.uniform=nextDouble;Generator.prototype.gaussian=gaussian;Generator.prototype.random=nextDouble;function gaussian(){var r,x,y;do{x=this.nextDouble()*2-1;y=this.nextDouble()*2-1;r=x*x+y*y}while(r>=1||r===0);return x*Math.sqrt(-2*Math.log(r)/r)}Generator.prototype.levy=levy;function levy(){var beta=3/2;var sigma=Math.pow(gamma(1+beta)*Math.sin(Math.PI*beta/2)/(gamma((1+beta)/2)*beta*Math.pow(2,(beta-1)/2)),1/beta);return this.gaussian()*sigma/Math.pow(Math.abs(this.gaussian()),1/beta)}function gamma(z){return Math.sqrt(2*Math.PI/z)*Math.pow(1/Math.E*(z+1/(12*z-1/(10*z))),z)}function nextDouble(){var seed=this.seed;seed=seed+2127912214+(seed<<12)&4294967295;seed=(seed^3345072700^seed>>>19)&4294967295;seed=seed+374761393+(seed<<5)&4294967295;seed=(seed+3550635116^seed<<9)&4294967295;seed=seed+4251993797+(seed<<3)&4294967295;seed=(seed^3042594569^seed>>>16)&4294967295;this.seed=seed;return(seed&268435455)/268435456}function next(maxValue){return Math.floor(this.nextDouble()*maxValue)}function randomIterator(array,customRandom){var localRandom=customRandom||random();if(typeof localRandom.next!=="function"){throw new Error("customRandom does not match expected API: next() function is missing")}return{forEach:forEach,shuffle:shuffle};function shuffle(){var i,j,t;for(i=array.length-1;i>0;--i){j=localRandom.next(i+1);t=array[j];array[j]=array[i];array[i]=t}return array}function forEach(callback){var i,j,t;for(i=array.length-1;i>0;--i){j=localRandom.next(i+1);t=array[j];array[j]=array[i];array[i]=t;callback(t)}if(array.length){callback(array[0])}}}},{}]},{},[1])(1)}); -------------------------------------------------------------------------------- /dist/ngraph.generators.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.generators = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 0) { g.addLink(node, i - 1 + j * n); } 172 | if (j > 0) { g.addLink(node, i + (j - 1) * n); } 173 | } 174 | } 175 | 176 | return g; 177 | } 178 | 179 | function grid3(n, m, z) { 180 | /** 181 | * 3D grid with n rows and m columns and z levels. 182 | * 183 | * @param {Number} n of rows in the graph. 184 | * @param {Number} m of columns in the graph. 185 | * @param {Number} z of levels in the graph. 186 | */ 187 | if (n < 1 || m < 1 || z < 1) { 188 | throw new Error('Invalid number of nodes in grid3 graph'); 189 | } 190 | var g = createGraph(), 191 | i, j, k; 192 | 193 | if (n === 1 && m === 1 && z === 1) { 194 | g.addNode(0); 195 | return g; 196 | } 197 | 198 | for (k = 0; k < z; ++k) { 199 | for (i = 0; i < n; ++i) { 200 | for (j = 0; j < m; ++j) { 201 | var level = k * n * m; 202 | var node = i + j * n + level; 203 | if (i > 0) { g.addLink(node, i - 1 + j * n + level); } 204 | if (j > 0) { g.addLink(node, i + (j - 1) * n + level); } 205 | if (k > 0) { g.addLink(node, i + j * n + (k - 1) * n * m ); } 206 | } 207 | } 208 | } 209 | 210 | return g; 211 | } 212 | 213 | function balancedBinTree(n) { 214 | /** 215 | * Balanced binary tree with n levels. 216 | * 217 | * @param {Number} n of levels in the binary tree 218 | */ 219 | if (n === undefined || n < 0) { 220 | throw new Error('Invalid number of levels in balanced tree'); 221 | } 222 | var g = createGraph(), 223 | count = Math.pow(2, n), 224 | level; 225 | 226 | if (n === 0) { 227 | g.addNode(1); 228 | } 229 | 230 | for (level = 1; level < count; ++level) { 231 | var root = level, 232 | left = root * 2, 233 | right = root * 2 + 1; 234 | 235 | g.addLink(root, left); 236 | g.addLink(root, right); 237 | } 238 | 239 | return g; 240 | } 241 | 242 | function noLinks(n) { 243 | /** 244 | * Graph with no links 245 | * 246 | * @param {Number} n of nodes in the graph 247 | */ 248 | if (n < 0) { 249 | throw new Error('Number of nodes should be >= 0'); 250 | } 251 | 252 | var g = createGraph(), i; 253 | for (i = 0; i < n; ++i) { 254 | g.addNode(i); 255 | } 256 | 257 | return g; 258 | } 259 | 260 | function cliqueCircle(cliqueCount, cliqueSize) { 261 | /** 262 | * A circular graph with cliques instead of individual nodes 263 | * 264 | * @param {Number} cliqueCount number of cliques inside circle 265 | * @param {Number} cliqueSize number of nodes inside each clique 266 | */ 267 | 268 | if (cliqueCount < 1) throw new Error('Invalid number of cliqueCount in cliqueCircle'); 269 | if (cliqueSize < 1) throw new Error('Invalid number of cliqueSize in cliqueCircle'); 270 | 271 | var graph = createGraph(); 272 | 273 | for (var i = 0; i < cliqueCount; ++i) { 274 | appendClique(cliqueSize, i * cliqueSize) 275 | 276 | if (i > 0) { 277 | graph.addLink(i * cliqueSize, i * cliqueSize - 1); 278 | } 279 | } 280 | graph.addLink(0, graph.getNodesCount() - 1); 281 | 282 | return graph; 283 | 284 | function appendClique(size, from) { 285 | for (var i = 0; i < size; ++i) { 286 | graph.addNode(i + from) 287 | } 288 | 289 | for (var i = 0; i < size; ++i) { 290 | for (var j = i + 1; j < size; ++j) { 291 | graph.addLink(i + from, j + from) 292 | } 293 | } 294 | } 295 | } 296 | 297 | function wattsStrogatz(n, k, p, seed) { 298 | /** 299 | * Watts-Strogatz small-world graph. 300 | * 301 | * @param {Number} n The number of nodes 302 | * @param {Number} k Each node is connected to k nearest neighbors in ring topology 303 | * @param {Number} p The probability of rewiring each edge 304 | 305 | * @see https://github.com/networkx/networkx/blob/master/networkx/generators/random_graphs.py 306 | */ 307 | if (k >= n) throw new Error('Choose smaller `k`. It cannot be larger than number of nodes `n`'); 308 | 309 | 310 | var random = require('ngraph.random').random(seed || 42); 311 | 312 | var g = createGraph(), i, to; 313 | for (i = 0; i < n; ++i) { 314 | g.addNode(i); 315 | } 316 | 317 | // connect each node to k/2 neighbors 318 | var neighborsSize = Math.floor(k/2 + 1); 319 | for (var j = 1; j < neighborsSize; ++j) { 320 | for (i = 0; i < n; ++i) { 321 | to = (j + i) % n; 322 | g.addLink(i, to); 323 | } 324 | } 325 | 326 | // rewire edges from each node 327 | // loop over all nodes in order (label) and neighbors in order (distance) 328 | // no self loops or multiple edges allowed 329 | for (j = 1; j < neighborsSize; ++j) { 330 | for (i = 0; i < n; ++i) { 331 | if (random.nextDouble() < p) { 332 | var from = i; 333 | to = (j + i) % n; 334 | 335 | var newTo = random.next(n); 336 | var needsRewire = (newTo === from || g.hasLink(from, newTo)); 337 | if (needsRewire && g.getLinks(from).length === n - 1) { 338 | // we cannot rewire this node, it has too many links. 339 | continue; 340 | } 341 | // Enforce no self-loops or multiple edges 342 | while (needsRewire) { 343 | newTo = random.next(n); 344 | needsRewire = (newTo === from || g.hasLink(from, newTo)); 345 | } 346 | var link = g.hasLink(from, to); 347 | g.removeLink(link); 348 | g.addLink(from, newTo); 349 | } 350 | } 351 | } 352 | 353 | return g; 354 | } 355 | } 356 | 357 | },{"miserables":3,"ngraph.graph":5,"ngraph.random":6}],2:[function(require,module,exports){ 358 | module.exports = {"nodes":[{"name":"Myriel","group":1},{"name":"Napoleon","group":1},{"name":"Mlle.Baptistine","group":1},{"name":"Mme.Magloire","group":1},{"name":"CountessdeLo","group":1},{"name":"Geborand","group":1},{"name":"Champtercier","group":1},{"name":"Cravatte","group":1},{"name":"Count","group":1},{"name":"OldMan","group":1},{"name":"Labarre","group":2},{"name":"Valjean","group":2},{"name":"Marguerite","group":3},{"name":"Mme.deR","group":2},{"name":"Isabeau","group":2},{"name":"Gervais","group":2},{"name":"Tholomyes","group":3},{"name":"Listolier","group":3},{"name":"Fameuil","group":3},{"name":"Blacheville","group":3},{"name":"Favourite","group":3},{"name":"Dahlia","group":3},{"name":"Zephine","group":3},{"name":"Fantine","group":3},{"name":"Mme.Thenardier","group":4},{"name":"Thenardier","group":4},{"name":"Cosette","group":5},{"name":"Javert","group":4},{"name":"Fauchelevent","group":0},{"name":"Bamatabois","group":2},{"name":"Perpetue","group":3},{"name":"Simplice","group":2},{"name":"Scaufflaire","group":2},{"name":"Woman1","group":2},{"name":"Judge","group":2},{"name":"Champmathieu","group":2},{"name":"Brevet","group":2},{"name":"Chenildieu","group":2},{"name":"Cochepaille","group":2},{"name":"Pontmercy","group":4},{"name":"Boulatruelle","group":6},{"name":"Eponine","group":4},{"name":"Anzelma","group":4},{"name":"Woman2","group":5},{"name":"MotherInnocent","group":0},{"name":"Gribier","group":0},{"name":"Jondrette","group":7},{"name":"Mme.Burgon","group":7},{"name":"Gavroche","group":8},{"name":"Gillenormand","group":5},{"name":"Magnon","group":5},{"name":"Mlle.Gillenormand","group":5},{"name":"Mme.Pontmercy","group":5},{"name":"Mlle.Vaubois","group":5},{"name":"Lt.Gillenormand","group":5},{"name":"Marius","group":8},{"name":"BaronessT","group":5},{"name":"Mabeuf","group":8},{"name":"Enjolras","group":8},{"name":"Combeferre","group":8},{"name":"Prouvaire","group":8},{"name":"Feuilly","group":8},{"name":"Courfeyrac","group":8},{"name":"Bahorel","group":8},{"name":"Bossuet","group":8},{"name":"Joly","group":8},{"name":"Grantaire","group":8},{"name":"MotherPlutarch","group":9},{"name":"Gueulemer","group":4},{"name":"Babet","group":4},{"name":"Claquesous","group":4},{"name":"Montparnasse","group":4},{"name":"Toussaint","group":5},{"name":"Child1","group":10},{"name":"Child2","group":10},{"name":"Brujon","group":4},{"name":"Mme.Hucheloup","group":8}],"links":[{"source":1,"target":0,"value":1},{"source":2,"target":0,"value":8},{"source":3,"target":0,"value":10},{"source":3,"target":2,"value":6},{"source":4,"target":0,"value":1},{"source":5,"target":0,"value":1},{"source":6,"target":0,"value":1},{"source":7,"target":0,"value":1},{"source":8,"target":0,"value":2},{"source":9,"target":0,"value":1},{"source":11,"target":10,"value":1},{"source":11,"target":3,"value":3},{"source":11,"target":2,"value":3},{"source":11,"target":0,"value":5},{"source":12,"target":11,"value":1},{"source":13,"target":11,"value":1},{"source":14,"target":11,"value":1},{"source":15,"target":11,"value":1},{"source":17,"target":16,"value":4},{"source":18,"target":16,"value":4},{"source":18,"target":17,"value":4},{"source":19,"target":16,"value":4},{"source":19,"target":17,"value":4},{"source":19,"target":18,"value":4},{"source":20,"target":16,"value":3},{"source":20,"target":17,"value":3},{"source":20,"target":18,"value":3},{"source":20,"target":19,"value":4},{"source":21,"target":16,"value":3},{"source":21,"target":17,"value":3},{"source":21,"target":18,"value":3},{"source":21,"target":19,"value":3},{"source":21,"target":20,"value":5},{"source":22,"target":16,"value":3},{"source":22,"target":17,"value":3},{"source":22,"target":18,"value":3},{"source":22,"target":19,"value":3},{"source":22,"target":20,"value":4},{"source":22,"target":21,"value":4},{"source":23,"target":16,"value":3},{"source":23,"target":17,"value":3},{"source":23,"target":18,"value":3},{"source":23,"target":19,"value":3},{"source":23,"target":20,"value":4},{"source":23,"target":21,"value":4},{"source":23,"target":22,"value":4},{"source":23,"target":12,"value":2},{"source":23,"target":11,"value":9},{"source":24,"target":23,"value":2},{"source":24,"target":11,"value":7},{"source":25,"target":24,"value":13},{"source":25,"target":23,"value":1},{"source":25,"target":11,"value":12},{"source":26,"target":24,"value":4},{"source":26,"target":11,"value":31},{"source":26,"target":16,"value":1},{"source":26,"target":25,"value":1},{"source":27,"target":11,"value":17},{"source":27,"target":23,"value":5},{"source":27,"target":25,"value":5},{"source":27,"target":24,"value":1},{"source":27,"target":26,"value":1},{"source":28,"target":11,"value":8},{"source":28,"target":27,"value":1},{"source":29,"target":23,"value":1},{"source":29,"target":27,"value":1},{"source":29,"target":11,"value":2},{"source":30,"target":23,"value":1},{"source":31,"target":30,"value":2},{"source":31,"target":11,"value":3},{"source":31,"target":23,"value":2},{"source":31,"target":27,"value":1},{"source":32,"target":11,"value":1},{"source":33,"target":11,"value":2},{"source":33,"target":27,"value":1},{"source":34,"target":11,"value":3},{"source":34,"target":29,"value":2},{"source":35,"target":11,"value":3},{"source":35,"target":34,"value":3},{"source":35,"target":29,"value":2},{"source":36,"target":34,"value":2},{"source":36,"target":35,"value":2},{"source":36,"target":11,"value":2},{"source":36,"target":29,"value":1},{"source":37,"target":34,"value":2},{"source":37,"target":35,"value":2},{"source":37,"target":36,"value":2},{"source":37,"target":11,"value":2},{"source":37,"target":29,"value":1},{"source":38,"target":34,"value":2},{"source":38,"target":35,"value":2},{"source":38,"target":36,"value":2},{"source":38,"target":37,"value":2},{"source":38,"target":11,"value":2},{"source":38,"target":29,"value":1},{"source":39,"target":25,"value":1},{"source":40,"target":25,"value":1},{"source":41,"target":24,"value":2},{"source":41,"target":25,"value":3},{"source":42,"target":41,"value":2},{"source":42,"target":25,"value":2},{"source":42,"target":24,"value":1},{"source":43,"target":11,"value":3},{"source":43,"target":26,"value":1},{"source":43,"target":27,"value":1},{"source":44,"target":28,"value":3},{"source":44,"target":11,"value":1},{"source":45,"target":28,"value":2},{"source":47,"target":46,"value":1},{"source":48,"target":47,"value":2},{"source":48,"target":25,"value":1},{"source":48,"target":27,"value":1},{"source":48,"target":11,"value":1},{"source":49,"target":26,"value":3},{"source":49,"target":11,"value":2},{"source":50,"target":49,"value":1},{"source":50,"target":24,"value":1},{"source":51,"target":49,"value":9},{"source":51,"target":26,"value":2},{"source":51,"target":11,"value":2},{"source":52,"target":51,"value":1},{"source":52,"target":39,"value":1},{"source":53,"target":51,"value":1},{"source":54,"target":51,"value":2},{"source":54,"target":49,"value":1},{"source":54,"target":26,"value":1},{"source":55,"target":51,"value":6},{"source":55,"target":49,"value":12},{"source":55,"target":39,"value":1},{"source":55,"target":54,"value":1},{"source":55,"target":26,"value":21},{"source":55,"target":11,"value":19},{"source":55,"target":16,"value":1},{"source":55,"target":25,"value":2},{"source":55,"target":41,"value":5},{"source":55,"target":48,"value":4},{"source":56,"target":49,"value":1},{"source":56,"target":55,"value":1},{"source":57,"target":55,"value":1},{"source":57,"target":41,"value":1},{"source":57,"target":48,"value":1},{"source":58,"target":55,"value":7},{"source":58,"target":48,"value":7},{"source":58,"target":27,"value":6},{"source":58,"target":57,"value":1},{"source":58,"target":11,"value":4},{"source":59,"target":58,"value":15},{"source":59,"target":55,"value":5},{"source":59,"target":48,"value":6},{"source":59,"target":57,"value":2},{"source":60,"target":48,"value":1},{"source":60,"target":58,"value":4},{"source":60,"target":59,"value":2},{"source":61,"target":48,"value":2},{"source":61,"target":58,"value":6},{"source":61,"target":60,"value":2},{"source":61,"target":59,"value":5},{"source":61,"target":57,"value":1},{"source":61,"target":55,"value":1},{"source":62,"target":55,"value":9},{"source":62,"target":58,"value":17},{"source":62,"target":59,"value":13},{"source":62,"target":48,"value":7},{"source":62,"target":57,"value":2},{"source":62,"target":41,"value":1},{"source":62,"target":61,"value":6},{"source":62,"target":60,"value":3},{"source":63,"target":59,"value":5},{"source":63,"target":48,"value":5},{"source":63,"target":62,"value":6},{"source":63,"target":57,"value":2},{"source":63,"target":58,"value":4},{"source":63,"target":61,"value":3},{"source":63,"target":60,"value":2},{"source":63,"target":55,"value":1},{"source":64,"target":55,"value":5},{"source":64,"target":62,"value":12},{"source":64,"target":48,"value":5},{"source":64,"target":63,"value":4},{"source":64,"target":58,"value":10},{"source":64,"target":61,"value":6},{"source":64,"target":60,"value":2},{"source":64,"target":59,"value":9},{"source":64,"target":57,"value":1},{"source":64,"target":11,"value":1},{"source":65,"target":63,"value":5},{"source":65,"target":64,"value":7},{"source":65,"target":48,"value":3},{"source":65,"target":62,"value":5},{"source":65,"target":58,"value":5},{"source":65,"target":61,"value":5},{"source":65,"target":60,"value":2},{"source":65,"target":59,"value":5},{"source":65,"target":57,"value":1},{"source":65,"target":55,"value":2},{"source":66,"target":64,"value":3},{"source":66,"target":58,"value":3},{"source":66,"target":59,"value":1},{"source":66,"target":62,"value":2},{"source":66,"target":65,"value":2},{"source":66,"target":48,"value":1},{"source":66,"target":63,"value":1},{"source":66,"target":61,"value":1},{"source":66,"target":60,"value":1},{"source":67,"target":57,"value":3},{"source":68,"target":25,"value":5},{"source":68,"target":11,"value":1},{"source":68,"target":24,"value":1},{"source":68,"target":27,"value":1},{"source":68,"target":48,"value":1},{"source":68,"target":41,"value":1},{"source":69,"target":25,"value":6},{"source":69,"target":68,"value":6},{"source":69,"target":11,"value":1},{"source":69,"target":24,"value":1},{"source":69,"target":27,"value":2},{"source":69,"target":48,"value":1},{"source":69,"target":41,"value":1},{"source":70,"target":25,"value":4},{"source":70,"target":69,"value":4},{"source":70,"target":68,"value":4},{"source":70,"target":11,"value":1},{"source":70,"target":24,"value":1},{"source":70,"target":27,"value":1},{"source":70,"target":41,"value":1},{"source":70,"target":58,"value":1},{"source":71,"target":27,"value":1},{"source":71,"target":69,"value":2},{"source":71,"target":68,"value":2},{"source":71,"target":70,"value":2},{"source":71,"target":11,"value":1},{"source":71,"target":48,"value":1},{"source":71,"target":41,"value":1},{"source":71,"target":25,"value":1},{"source":72,"target":26,"value":2},{"source":72,"target":27,"value":1},{"source":72,"target":11,"value":1},{"source":73,"target":48,"value":2},{"source":74,"target":48,"value":2},{"source":74,"target":73,"value":3},{"source":75,"target":69,"value":3},{"source":75,"target":68,"value":3},{"source":75,"target":25,"value":3},{"source":75,"target":48,"value":1},{"source":75,"target":41,"value":1},{"source":75,"target":70,"value":1},{"source":75,"target":71,"value":1},{"source":76,"target":64,"value":1},{"source":76,"target":65,"value":1},{"source":76,"target":66,"value":1},{"source":76,"target":63,"value":1},{"source":76,"target":62,"value":1},{"source":76,"target":48,"value":1},{"source":76,"target":58,"value":1}]}; 359 | 360 | },{}],3:[function(require,module,exports){ 361 | var data = require('./data.js'); 362 | var createGraph = require('ngraph.graph'); 363 | // By default we are exporting the graph itself 364 | module.exports = createMiserablesGraph(); 365 | 366 | // But we also let people create new instances of the graph: 367 | module.exports.create = createMiserablesGraph; 368 | 369 | function createMiserablesGraph(){ 370 | var graph = createGraph(); 371 | 372 | for (var i = 0; i < data.nodes.length; ++i){ 373 | graph.addNode(i, data.nodes[i]); 374 | } 375 | 376 | for (i = 0; i < data.links.length; ++i){ 377 | var link = data.links[i]; 378 | graph.addLink(link.source, link.target, link.value); 379 | } 380 | 381 | return graph; 382 | } 383 | 384 | },{"./data.js":2,"ngraph.graph":5}],4:[function(require,module,exports){ 385 | module.exports = function eventify(subject) { 386 | validateSubject(subject); 387 | 388 | var eventsStorage = createEventsStorage(subject); 389 | subject.on = eventsStorage.on; 390 | subject.off = eventsStorage.off; 391 | subject.fire = eventsStorage.fire; 392 | return subject; 393 | }; 394 | 395 | function createEventsStorage(subject) { 396 | // Store all event listeners to this hash. Key is event name, value is array 397 | // of callback records. 398 | // 399 | // A callback record consists of callback function and its optional context: 400 | // { 'eventName' => [{callback: function, ctx: object}] } 401 | var registeredEvents = Object.create(null); 402 | 403 | return { 404 | on: function (eventName, callback, ctx) { 405 | if (typeof callback !== 'function') { 406 | throw new Error('callback is expected to be a function'); 407 | } 408 | var handlers = registeredEvents[eventName]; 409 | if (!handlers) { 410 | handlers = registeredEvents[eventName] = []; 411 | } 412 | handlers.push({callback: callback, ctx: ctx}); 413 | 414 | return subject; 415 | }, 416 | 417 | off: function (eventName, callback) { 418 | var wantToRemoveAll = (typeof eventName === 'undefined'); 419 | if (wantToRemoveAll) { 420 | // Killing old events storage should be enough in this case: 421 | registeredEvents = Object.create(null); 422 | return subject; 423 | } 424 | 425 | if (registeredEvents[eventName]) { 426 | var deleteAllCallbacksForEvent = (typeof callback !== 'function'); 427 | if (deleteAllCallbacksForEvent) { 428 | delete registeredEvents[eventName]; 429 | } else { 430 | var callbacks = registeredEvents[eventName]; 431 | for (var i = 0; i < callbacks.length; ++i) { 432 | if (callbacks[i].callback === callback) { 433 | callbacks.splice(i, 1); 434 | } 435 | } 436 | } 437 | } 438 | 439 | return subject; 440 | }, 441 | 442 | fire: function (eventName) { 443 | var callbacks = registeredEvents[eventName]; 444 | if (!callbacks) { 445 | return subject; 446 | } 447 | 448 | var fireArguments; 449 | if (arguments.length > 1) { 450 | fireArguments = Array.prototype.splice.call(arguments, 1); 451 | } 452 | for(var i = 0; i < callbacks.length; ++i) { 453 | var callbackInfo = callbacks[i]; 454 | callbackInfo.callback.apply(callbackInfo.ctx, fireArguments); 455 | } 456 | 457 | return subject; 458 | } 459 | }; 460 | } 461 | 462 | function validateSubject(subject) { 463 | if (!subject) { 464 | throw new Error('Eventify cannot use falsy object as events subject'); 465 | } 466 | var reservedWords = ['on', 'fire', 'off']; 467 | for (var i = 0; i < reservedWords.length; ++i) { 468 | if (subject.hasOwnProperty(reservedWords[i])) { 469 | throw new Error("Subject cannot be eventified, since it already has property '" + reservedWords[i] + "'"); 470 | } 471 | } 472 | } 473 | 474 | },{}],5:[function(require,module,exports){ 475 | /** 476 | * @fileOverview Contains definition of the core graph object. 477 | */ 478 | 479 | // TODO: need to change storage layer: 480 | // 1. Be able to get all nodes O(1) 481 | // 2. Be able to get number of links O(1) 482 | 483 | /** 484 | * @example 485 | * var graph = require('ngraph.graph')(); 486 | * graph.addNode(1); // graph has one node. 487 | * graph.addLink(2, 3); // now graph contains three nodes and one link. 488 | * 489 | */ 490 | module.exports = createGraph; 491 | 492 | var eventify = require('ngraph.events'); 493 | 494 | /** 495 | * Creates a new graph 496 | */ 497 | function createGraph(options) { 498 | // Graph structure is maintained as dictionary of nodes 499 | // and array of links. Each node has 'links' property which 500 | // hold all links related to that node. And general links 501 | // array is used to speed up all links enumeration. This is inefficient 502 | // in terms of memory, but simplifies coding. 503 | options = options || {}; 504 | if ('uniqueLinkId' in options) { 505 | console.warn( 506 | 'ngraph.graph: Starting from version 0.14 `uniqueLinkId` is deprecated.\n' + 507 | 'Use `multigraph` option instead\n', 508 | '\n', 509 | 'Note: there is also change in default behavior: From now on each graph\n'+ 510 | 'is considered to be not a multigraph by default (each edge is unique).' 511 | ); 512 | 513 | options.multigraph = options.uniqueLinkId; 514 | } 515 | 516 | // Dear reader, the non-multigraphs do not guarantee that there is only 517 | // one link for a given pair of node. When this option is set to false 518 | // we can save some memory and CPU (18% faster for non-multigraph); 519 | if (options.multigraph === undefined) options.multigraph = false; 520 | 521 | if (typeof Map !== 'function') { 522 | // TODO: Should we polyfill it ourselves? We don't use much operations there.. 523 | throw new Error('ngraph.graph requires `Map` to be defined. Please polyfill it before using ngraph'); 524 | } 525 | 526 | var nodes = new Map(); // nodeId => Node 527 | var links = new Map(); // linkId => Link 528 | // Hash of multi-edges. Used to track ids of edges between same nodes 529 | var multiEdges = {}; 530 | var suspendEvents = 0; 531 | 532 | var createLink = options.multigraph ? createUniqueLink : createSingleLink, 533 | 534 | // Our graph API provides means to listen to graph changes. Users can subscribe 535 | // to be notified about changes in the graph by using `on` method. However 536 | // in some cases they don't use it. To avoid unnecessary memory consumption 537 | // we will not record graph changes until we have at least one subscriber. 538 | // Code below supports this optimization. 539 | // 540 | // Accumulates all changes made during graph updates. 541 | // Each change element contains: 542 | // changeType - one of the strings: 'add', 'remove' or 'update'; 543 | // node - if change is related to node this property is set to changed graph's node; 544 | // link - if change is related to link this property is set to changed graph's link; 545 | changes = [], 546 | recordLinkChange = noop, 547 | recordNodeChange = noop, 548 | enterModification = noop, 549 | exitModification = noop; 550 | 551 | // this is our public API: 552 | var graphPart = { 553 | /** 554 | * Sometimes duck typing could be slow. Giving clients a hint about data structure 555 | * via explicit version number here: 556 | */ 557 | version: 20.0, 558 | 559 | /** 560 | * Adds node to the graph. If node with given id already exists in the graph 561 | * its data is extended with whatever comes in 'data' argument. 562 | * 563 | * @param nodeId the node's identifier. A string or number is preferred. 564 | * @param [data] additional data for the node being added. If node already 565 | * exists its data object is augmented with the new one. 566 | * 567 | * @return {node} The newly added node or node with given id if it already exists. 568 | */ 569 | addNode: addNode, 570 | 571 | /** 572 | * Adds a link to the graph. The function always create a new 573 | * link between two nodes. If one of the nodes does not exists 574 | * a new node is created. 575 | * 576 | * @param fromId link start node id; 577 | * @param toId link end node id; 578 | * @param [data] additional data to be set on the new link; 579 | * 580 | * @return {link} The newly created link 581 | */ 582 | addLink: addLink, 583 | 584 | /** 585 | * Removes link from the graph. If link does not exist does nothing. 586 | * 587 | * @param link - object returned by addLink() or getLinks() methods. 588 | * 589 | * @returns true if link was removed; false otherwise. 590 | */ 591 | removeLink: removeLink, 592 | 593 | /** 594 | * Removes node with given id from the graph. If node does not exist in the graph 595 | * does nothing. 596 | * 597 | * @param nodeId node's identifier passed to addNode() function. 598 | * 599 | * @returns true if node was removed; false otherwise. 600 | */ 601 | removeNode: removeNode, 602 | 603 | /** 604 | * Gets node with given identifier. If node does not exist undefined value is returned. 605 | * 606 | * @param nodeId requested node identifier; 607 | * 608 | * @return {node} in with requested identifier or undefined if no such node exists. 609 | */ 610 | getNode: getNode, 611 | 612 | /** 613 | * Gets number of nodes in this graph. 614 | * 615 | * @return number of nodes in the graph. 616 | */ 617 | getNodeCount: getNodeCount, 618 | 619 | /** 620 | * Gets total number of links in the graph. 621 | */ 622 | getLinkCount: getLinkCount, 623 | 624 | /** 625 | * Gets total number of links in the graph. 626 | */ 627 | getEdgeCount: getLinkCount, 628 | 629 | /** 630 | * Synonym for `getLinkCount()` 631 | */ 632 | getLinksCount: getLinkCount, 633 | 634 | /** 635 | * Synonym for `getNodeCount()` 636 | */ 637 | getNodesCount: getNodeCount, 638 | 639 | /** 640 | * Gets all links (inbound and outbound) from the node with given id. 641 | * If node with given id is not found null is returned. 642 | * 643 | * @param nodeId requested node identifier. 644 | * 645 | * @return Set of links from and to requested node if such node exists; 646 | * otherwise null is returned. 647 | */ 648 | getLinks: getLinks, 649 | 650 | /** 651 | * Invokes callback on each node of the graph. 652 | * 653 | * @param {Function(node)} callback Function to be invoked. The function 654 | * is passed one argument: visited node. 655 | */ 656 | forEachNode: forEachNode, 657 | 658 | /** 659 | * Invokes callback on every linked (adjacent) node to the given one. 660 | * 661 | * @param nodeId Identifier of the requested node. 662 | * @param {Function(node, link)} callback Function to be called on all linked nodes. 663 | * The function is passed two parameters: adjacent node and link object itself. 664 | * @param oriented if true graph treated as oriented. 665 | */ 666 | forEachLinkedNode: forEachLinkedNode, 667 | 668 | /** 669 | * Enumerates all links in the graph 670 | * 671 | * @param {Function(link)} callback Function to be called on all links in the graph. 672 | * The function is passed one parameter: graph's link object. 673 | * 674 | * Link object contains at least the following fields: 675 | * fromId - node id where link starts; 676 | * toId - node id where link ends, 677 | * data - additional data passed to graph.addLink() method. 678 | */ 679 | forEachLink: forEachLink, 680 | 681 | /** 682 | * Suspend all notifications about graph changes until 683 | * endUpdate is called. 684 | */ 685 | beginUpdate: enterModification, 686 | 687 | /** 688 | * Resumes all notifications about graph changes and fires 689 | * graph 'changed' event in case there are any pending changes. 690 | */ 691 | endUpdate: exitModification, 692 | 693 | /** 694 | * Removes all nodes and links from the graph. 695 | */ 696 | clear: clear, 697 | 698 | /** 699 | * Detects whether there is a link between two nodes. 700 | * Operation complexity is O(n) where n - number of links of a node. 701 | * NOTE: this function is synonym for getLink() 702 | * 703 | * @returns link if there is one. null otherwise. 704 | */ 705 | hasLink: getLink, 706 | 707 | /** 708 | * Detects whether there is a node with given id 709 | * 710 | * Operation complexity is O(1) 711 | * NOTE: this function is synonym for getNode() 712 | * 713 | * @returns node if there is one; Falsy value otherwise. 714 | */ 715 | hasNode: getNode, 716 | 717 | /** 718 | * Gets an edge between two nodes. 719 | * Operation complexity is O(n) where n - number of links of a node. 720 | * 721 | * @param {string} fromId link start identifier 722 | * @param {string} toId link end identifier 723 | * 724 | * @returns link if there is one; undefined otherwise. 725 | */ 726 | getLink: getLink 727 | }; 728 | 729 | // this will add `on()` and `fire()` methods. 730 | eventify(graphPart); 731 | 732 | monitorSubscribers(); 733 | 734 | return graphPart; 735 | 736 | function monitorSubscribers() { 737 | var realOn = graphPart.on; 738 | 739 | // replace real `on` with our temporary on, which will trigger change 740 | // modification monitoring: 741 | graphPart.on = on; 742 | 743 | function on() { 744 | // now it's time to start tracking stuff: 745 | graphPart.beginUpdate = enterModification = enterModificationReal; 746 | graphPart.endUpdate = exitModification = exitModificationReal; 747 | recordLinkChange = recordLinkChangeReal; 748 | recordNodeChange = recordNodeChangeReal; 749 | 750 | // this will replace current `on` method with real pub/sub from `eventify`. 751 | graphPart.on = realOn; 752 | // delegate to real `on` handler: 753 | return realOn.apply(graphPart, arguments); 754 | } 755 | } 756 | 757 | function recordLinkChangeReal(link, changeType) { 758 | changes.push({ 759 | link: link, 760 | changeType: changeType 761 | }); 762 | } 763 | 764 | function recordNodeChangeReal(node, changeType) { 765 | changes.push({ 766 | node: node, 767 | changeType: changeType 768 | }); 769 | } 770 | 771 | function addNode(nodeId, data) { 772 | if (nodeId === undefined) { 773 | throw new Error('Invalid node identifier'); 774 | } 775 | 776 | enterModification(); 777 | 778 | var node = getNode(nodeId); 779 | if (!node) { 780 | node = new Node(nodeId, data); 781 | recordNodeChange(node, 'add'); 782 | } else { 783 | node.data = data; 784 | recordNodeChange(node, 'update'); 785 | } 786 | 787 | nodes.set(nodeId, node); 788 | 789 | exitModification(); 790 | return node; 791 | } 792 | 793 | function getNode(nodeId) { 794 | return nodes.get(nodeId); 795 | } 796 | 797 | function removeNode(nodeId) { 798 | var node = getNode(nodeId); 799 | if (!node) { 800 | return false; 801 | } 802 | 803 | enterModification(); 804 | 805 | var prevLinks = node.links; 806 | if (prevLinks) { 807 | prevLinks.forEach(removeLinkInstance); 808 | node.links = null; 809 | } 810 | 811 | nodes.delete(nodeId); 812 | 813 | recordNodeChange(node, 'remove'); 814 | 815 | exitModification(); 816 | 817 | return true; 818 | } 819 | 820 | 821 | function addLink(fromId, toId, data) { 822 | enterModification(); 823 | 824 | var fromNode = getNode(fromId) || addNode(fromId); 825 | var toNode = getNode(toId) || addNode(toId); 826 | 827 | var link = createLink(fromId, toId, data); 828 | var isUpdate = links.has(link.id); 829 | 830 | links.set(link.id, link); 831 | 832 | // TODO: this is not cool. On large graphs potentially would consume more memory. 833 | addLinkToNode(fromNode, link); 834 | if (fromId !== toId) { 835 | // make sure we are not duplicating links for self-loops 836 | addLinkToNode(toNode, link); 837 | } 838 | 839 | recordLinkChange(link, isUpdate ? 'update' : 'add'); 840 | 841 | exitModification(); 842 | 843 | return link; 844 | } 845 | 846 | function createSingleLink(fromId, toId, data) { 847 | var linkId = makeLinkId(fromId, toId); 848 | var prevLink = links.get(linkId); 849 | if (prevLink) { 850 | prevLink.data = data; 851 | return prevLink; 852 | } 853 | 854 | return new Link(fromId, toId, data, linkId); 855 | } 856 | 857 | function createUniqueLink(fromId, toId, data) { 858 | // TODO: Find a better/faster way to store multigraphs 859 | var linkId = makeLinkId(fromId, toId); 860 | var isMultiEdge = multiEdges.hasOwnProperty(linkId); 861 | if (isMultiEdge || getLink(fromId, toId)) { 862 | if (!isMultiEdge) { 863 | multiEdges[linkId] = 0; 864 | } 865 | var suffix = '@' + (++multiEdges[linkId]); 866 | linkId = makeLinkId(fromId + suffix, toId + suffix); 867 | } 868 | 869 | return new Link(fromId, toId, data, linkId); 870 | } 871 | 872 | function getNodeCount() { 873 | return nodes.size; 874 | } 875 | 876 | function getLinkCount() { 877 | return links.size; 878 | } 879 | 880 | function getLinks(nodeId) { 881 | var node = getNode(nodeId); 882 | return node ? node.links : null; 883 | } 884 | 885 | function removeLink(link, otherId) { 886 | if (otherId !== undefined) { 887 | link = getLink(link, otherId); 888 | } 889 | return removeLinkInstance(link); 890 | } 891 | 892 | function removeLinkInstance(link) { 893 | if (!link) { 894 | return false; 895 | } 896 | if (!links.get(link.id)) return false; 897 | 898 | enterModification(); 899 | 900 | links.delete(link.id); 901 | 902 | var fromNode = getNode(link.fromId); 903 | var toNode = getNode(link.toId); 904 | 905 | if (fromNode) { 906 | fromNode.links.delete(link); 907 | } 908 | 909 | if (toNode) { 910 | toNode.links.delete(link); 911 | } 912 | 913 | recordLinkChange(link, 'remove'); 914 | 915 | exitModification(); 916 | 917 | return true; 918 | } 919 | 920 | function getLink(fromNodeId, toNodeId) { 921 | if (fromNodeId === undefined || toNodeId === undefined) return undefined; 922 | return links.get(makeLinkId(fromNodeId, toNodeId)); 923 | } 924 | 925 | function clear() { 926 | enterModification(); 927 | forEachNode(function(node) { 928 | removeNode(node.id); 929 | }); 930 | exitModification(); 931 | } 932 | 933 | function forEachLink(callback) { 934 | if (typeof callback === 'function') { 935 | var valuesIterator = links.values(); 936 | var nextValue = valuesIterator.next(); 937 | while (!nextValue.done) { 938 | if (callback(nextValue.value)) { 939 | return true; // client doesn't want to proceed. Return. 940 | } 941 | nextValue = valuesIterator.next(); 942 | } 943 | } 944 | } 945 | 946 | function forEachLinkedNode(nodeId, callback, oriented) { 947 | var node = getNode(nodeId); 948 | 949 | if (node && node.links && typeof callback === 'function') { 950 | if (oriented) { 951 | return forEachOrientedLink(node.links, nodeId, callback); 952 | } else { 953 | return forEachNonOrientedLink(node.links, nodeId, callback); 954 | } 955 | } 956 | } 957 | 958 | // eslint-disable-next-line no-shadow 959 | function forEachNonOrientedLink(links, nodeId, callback) { 960 | var quitFast; 961 | 962 | var valuesIterator = links.values(); 963 | var nextValue = valuesIterator.next(); 964 | while (!nextValue.done) { 965 | var link = nextValue.value; 966 | var linkedNodeId = link.fromId === nodeId ? link.toId : link.fromId; 967 | quitFast = callback(nodes.get(linkedNodeId), link); 968 | if (quitFast) { 969 | return true; // Client does not need more iterations. Break now. 970 | } 971 | nextValue = valuesIterator.next(); 972 | } 973 | } 974 | 975 | // eslint-disable-next-line no-shadow 976 | function forEachOrientedLink(links, nodeId, callback) { 977 | var quitFast; 978 | var valuesIterator = links.values(); 979 | var nextValue = valuesIterator.next(); 980 | while (!nextValue.done) { 981 | var link = nextValue.value; 982 | if (link.fromId === nodeId) { 983 | quitFast = callback(nodes.get(link.toId), link); 984 | if (quitFast) { 985 | return true; // Client does not need more iterations. Break now. 986 | } 987 | } 988 | nextValue = valuesIterator.next(); 989 | } 990 | } 991 | 992 | // we will not fire anything until users of this library explicitly call `on()` 993 | // method. 994 | function noop() {} 995 | 996 | // Enter, Exit modification allows bulk graph updates without firing events. 997 | function enterModificationReal() { 998 | suspendEvents += 1; 999 | } 1000 | 1001 | function exitModificationReal() { 1002 | suspendEvents -= 1; 1003 | if (suspendEvents === 0 && changes.length > 0) { 1004 | graphPart.fire('changed', changes); 1005 | changes.length = 0; 1006 | } 1007 | } 1008 | 1009 | function forEachNode(callback) { 1010 | if (typeof callback !== 'function') { 1011 | throw new Error('Function is expected to iterate over graph nodes. You passed ' + callback); 1012 | } 1013 | 1014 | var valuesIterator = nodes.values(); 1015 | var nextValue = valuesIterator.next(); 1016 | while (!nextValue.done) { 1017 | if (callback(nextValue.value)) { 1018 | return true; // client doesn't want to proceed. Return. 1019 | } 1020 | nextValue = valuesIterator.next(); 1021 | } 1022 | } 1023 | } 1024 | 1025 | /** 1026 | * Internal structure to represent node; 1027 | */ 1028 | function Node(id, data) { 1029 | this.id = id; 1030 | this.links = null; 1031 | this.data = data; 1032 | } 1033 | 1034 | function addLinkToNode(node, link) { 1035 | if (node.links) { 1036 | node.links.add(link); 1037 | } else { 1038 | node.links = new Set([link]); 1039 | } 1040 | } 1041 | 1042 | /** 1043 | * Internal structure to represent links; 1044 | */ 1045 | function Link(fromId, toId, data, id) { 1046 | this.fromId = fromId; 1047 | this.toId = toId; 1048 | this.data = data; 1049 | this.id = id; 1050 | } 1051 | 1052 | function makeLinkId(fromId, toId) { 1053 | return fromId.toString() + '👉 ' + toId.toString(); 1054 | } 1055 | 1056 | },{"ngraph.events":4}],6:[function(require,module,exports){ 1057 | module.exports = random; 1058 | 1059 | module.exports.random = random, 1060 | module.exports.randomIterator = randomIterator 1061 | 1062 | /** 1063 | * Creates seeded PRNG with two methods: 1064 | * next() and nextDouble() 1065 | */ 1066 | function random(inputSeed) { 1067 | var seed = typeof inputSeed === 'number' ? inputSeed : (+new Date()); 1068 | return new Generator(seed) 1069 | } 1070 | 1071 | function Generator(seed) { 1072 | this.seed = seed; 1073 | } 1074 | 1075 | /** 1076 | * Generates random integer number in the range from 0 (inclusive) to maxValue (exclusive) 1077 | * 1078 | * @param maxValue Number REQUIRED. Omitting this number will result in NaN values from PRNG. 1079 | */ 1080 | Generator.prototype.next = next; 1081 | 1082 | /** 1083 | * Generates random double number in the range from 0 (inclusive) to 1 (exclusive) 1084 | * This function is the same as Math.random() (except that it could be seeded) 1085 | */ 1086 | Generator.prototype.nextDouble = nextDouble; 1087 | 1088 | /** 1089 | * Returns a random real number from uniform distribution in [0, 1) 1090 | */ 1091 | Generator.prototype.uniform = nextDouble; 1092 | 1093 | /** 1094 | * Returns a random real number from a Gaussian distribution 1095 | * with 0 as a mean, and 1 as standard deviation u ~ N(0,1) 1096 | */ 1097 | Generator.prototype.gaussian = gaussian; 1098 | 1099 | /** 1100 | * Returns a floating-point, pseudo-random number that's greater than 1101 | * or equal to 0 and less than 1, with approximately uniform distribution over that range 1102 | * 1103 | * Note: This method is the same as nextDouble(), but is here for 1104 | * compatibility with similar Math.random() 1105 | */ 1106 | Generator.prototype.random = nextDouble; 1107 | 1108 | function gaussian() { 1109 | // use the polar form of the Box-Muller transform 1110 | // based on https://introcs.cs.princeton.edu/java/23recursion/StdRandom.java 1111 | var r, x, y; 1112 | do { 1113 | x = this.nextDouble() * 2 - 1; 1114 | y = this.nextDouble() * 2 - 1; 1115 | r = x * x + y * y; 1116 | } while (r >= 1 || r === 0); 1117 | 1118 | return x * Math.sqrt(-2 * Math.log(r)/r); 1119 | } 1120 | 1121 | /** 1122 | * See https://twitter.com/anvaka/status/1296182534150135808 1123 | */ 1124 | Generator.prototype.levy = levy; 1125 | 1126 | function levy() { 1127 | var beta = 3 / 2; 1128 | var sigma = Math.pow( 1129 | gamma( 1 + beta ) * Math.sin(Math.PI * beta / 2) / 1130 | (gamma((1 + beta) / 2) * beta * Math.pow(2, (beta - 1) / 2)), 1131 | 1/beta 1132 | ); 1133 | return this.gaussian() * sigma / Math.pow(Math.abs(this.gaussian()), 1/beta); 1134 | } 1135 | 1136 | // gamma function approximation 1137 | function gamma(z) { 1138 | return Math.sqrt(2 * Math.PI / z) * Math.pow((1 / Math.E) * (z + 1 / (12 * z - 1 / (10 * z))), z); 1139 | } 1140 | 1141 | function nextDouble() { 1142 | var seed = this.seed; 1143 | // Robert Jenkins' 32 bit integer hash function. 1144 | seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff; 1145 | seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff; 1146 | seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff; 1147 | seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff; 1148 | seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff; 1149 | seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff; 1150 | this.seed = seed; 1151 | return (seed & 0xfffffff) / 0x10000000; 1152 | } 1153 | 1154 | function next(maxValue) { 1155 | return Math.floor(this.nextDouble() * maxValue); 1156 | } 1157 | 1158 | /* 1159 | * Creates iterator over array, which returns items of array in random order 1160 | * Time complexity is guaranteed to be O(n); 1161 | */ 1162 | function randomIterator(array, customRandom) { 1163 | var localRandom = customRandom || random(); 1164 | if (typeof localRandom.next !== 'function') { 1165 | throw new Error('customRandom does not match expected API: next() function is missing'); 1166 | } 1167 | 1168 | return { 1169 | /** 1170 | * Visits every single element of a collection once, in a random order. 1171 | * Note: collection is modified in place. 1172 | */ 1173 | forEach: forEach, 1174 | 1175 | /** 1176 | * Shuffles array randomly, in place. 1177 | */ 1178 | shuffle: shuffle 1179 | }; 1180 | 1181 | function shuffle() { 1182 | var i, j, t; 1183 | for (i = array.length - 1; i > 0; --i) { 1184 | j = localRandom.next(i + 1); // i inclusive 1185 | t = array[j]; 1186 | array[j] = array[i]; 1187 | array[i] = t; 1188 | } 1189 | 1190 | return array; 1191 | } 1192 | 1193 | function forEach(callback) { 1194 | var i, j, t; 1195 | for (i = array.length - 1; i > 0; --i) { 1196 | j = localRandom.next(i + 1); // i inclusive 1197 | t = array[j]; 1198 | array[j] = array[i]; 1199 | array[i] = t; 1200 | 1201 | callback(t); 1202 | } 1203 | 1204 | if (array.length) { 1205 | callback(array[0]); 1206 | } 1207 | } 1208 | } 1209 | 1210 | },{}]},{},[1])(1) 1211 | }); 1212 | --------------------------------------------------------------------------------