├── .babelrc ├── .eslintignore ├── .eslintrc ├── .github └── stale.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── bower.json ├── cytoscape-dagre.js ├── demo.html ├── package-lock.json ├── package.json ├── pages ├── cytoscape-dagre.js ├── demo.html └── index.html ├── src ├── assign.js ├── defaults.js ├── index.js └── layout.js ├── test └── example.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { "presets": ["@babel/preset-env"] } 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "node": true, 6 | "amd": true, 7 | "es6": true 8 | }, 9 | "extends": "eslint:recommended", 10 | "rules": { 11 | "semi": "error" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 14 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | # Label to use when marking an issue as stale 9 | staleLabel: stale 10 | # Comment to post when marking an issue as stale. Set to `false` to disable 11 | markComment: > 12 | This issue has been automatically marked as stale, because it has not had 13 | activity within the past 14 days. It will be closed if no further activity 14 | occurs within the next 7 days. If a feature request is important to you, 15 | please consider making a pull request. Thank you for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | 3 | Copyright (c) 2016-2018, 2020, 2022, The Cytoscape Consortium. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the “Software”), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cytoscape-dagre 2 | ================================================================================ 3 | 4 | [](https://zenodo.org/badge/latestdoi/42206402) 5 | 6 | ## Description 7 | 8 | The Dagre layout for DAGs and trees for Cytoscape.js ([demo](https://cytoscape.github.io/cytoscape.js-dagre)) 9 | 10 | The `dagre` layout organises the graph using a DAG (directed acyclic graph) system, written by [Chris Pettitt](https://www.linkedin.com/in/chrismpettitt). It is especially suitable for DAGs and trees. For more information, please refer to [Dagre's documentation](https://github.com/cpettitt/dagre). 11 | 12 | ## Dependencies 13 | 14 | * Cytoscape.js ^3.2.0 15 | * Dagre ^0.8.2 16 | 17 | 18 | ## Usage instructions 19 | 20 | Download the library: 21 | * via npm: `npm install cytoscape-dagre`, 22 | * via bower: `bower install cytoscape-dagre`, or 23 | * via direct download in the repository (probably from a tag). 24 | 25 | Import the library as appropriate for your project: 26 | 27 | ES import: 28 | 29 | ```js 30 | import cytoscape from 'cytoscape'; 31 | import dagre from 'cytoscape-dagre'; 32 | 33 | cytoscape.use( dagre ); 34 | ``` 35 | 36 | CommonJS require: 37 | 38 | ```js 39 | let cytoscape = require('cytoscape'); 40 | let dagre = require('cytoscape-dagre'); 41 | 42 | cytoscape.use( dagre ); // register extension 43 | ``` 44 | 45 | AMD: 46 | 47 | ```js 48 | require(['cytoscape', 'cytoscape-dagre'], function( cytoscape, dagre ){ 49 | dagre( cytoscape ); // register extension 50 | }); 51 | ``` 52 | 53 | Plain HTML/JS has the extension registered for you automatically, because no `require()` is needed. 54 | 55 | 56 | ## API 57 | 58 | Call the layout, e.g. `cy.layout({ name: 'dagre', ... }).run()`, with options: 59 | 60 | ```js 61 | var defaults = { 62 | // dagre algo options, uses default value on undefined 63 | nodeSep: undefined, // the separation between adjacent nodes in the same rank 64 | edgeSep: undefined, // the separation between adjacent edges in the same rank 65 | rankSep: undefined, // the separation between each rank in the layout 66 | rankDir: undefined, // 'TB' for top to bottom flow, 'LR' for left to right, 67 | align: undefined, // alignment for rank nodes. Can be 'UL', 'UR', 'DL', or 'DR', where U = up, D = down, L = left, and R = right 68 | acyclicer: undefined, // If set to 'greedy', uses a greedy heuristic for finding a feedback arc set for a graph. 69 | // A feedback arc set is a set of edges that can be removed to make a graph acyclic. 70 | ranker: undefined, // Type of algorithm to assign a rank to each node in the input graph. Possible values: 'network-simplex', 'tight-tree' or 'longest-path' 71 | minLen: function( edge ){ return 1; }, // number of ranks to keep between the source and target of the edge 72 | edgeWeight: function( edge ){ return 1; }, // higher weight edges are generally made shorter and straighter than lower weight edges 73 | 74 | // general layout options 75 | fit: true, // whether to fit to viewport 76 | padding: 30, // fit padding 77 | spacingFactor: undefined, // Applies a multiplicative factor (>0) to expand or compress the overall area that the nodes take up 78 | nodeDimensionsIncludeLabels: false, // whether labels should be included in determining the space used by a node 79 | animate: false, // whether to transition the node positions 80 | animateFilter: function( node, i ){ return true; }, // whether to animate specific nodes when animation is on; non-animated nodes immediately go to their final positions 81 | animationDuration: 500, // duration of animation in ms if enabled 82 | animationEasing: undefined, // easing of animation if enabled 83 | boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 84 | transform: function( node, pos ){ return pos; }, // a function that applies a transform to the final node position 85 | ready: function(){}, // on layoutready 86 | sort: undefined, // a sorting function to order the nodes and edges; e.g. function(a, b){ return a.data('weight') - b.data('weight') } 87 | // because cytoscape dagre creates a directed graph, and directed graphs use the node order as a tie breaker when 88 | // defining the topology of a graph, this sort function can help ensure the correct order of the nodes/edges. 89 | // this feature is most useful when adding and removing the same nodes and edges multiple times in a graph. 90 | stop: function(){} // on layoutstop 91 | }; 92 | ``` 93 | 94 | 95 | ## Build targets 96 | 97 | * `npm run test` : Run Mocha tests in `./test` 98 | * `npm run build` : Build `./src/**` into `cytoscape-dagre.js` 99 | * `npm run watch` : Automatically build on changes with live reloading (N.b. you must already have an HTTP server running) 100 | * `npm run dev` : Automatically build on changes with live reloading with webpack dev server 101 | * `npm run lint` : Run eslint on the source 102 | 103 | N.b. all builds use babel, so modern ES features can be used in the `src`. 104 | 105 | 106 | ## Publishing instructions 107 | 108 | This project is set up to automatically be published to npm and bower. To publish: 109 | 110 | 1. Build the extension : `npm run build:release` 111 | 1. Commit the build : `git commit -am "Build for release"` 112 | 1. Bump the version number and tag: `npm version major|minor|patch` 113 | 1. Push to origin: `git push && git push --tags` 114 | 1. Publish to npm: `npm publish .` 115 | 1. If publishing to bower for the first time, you'll need to run `bower register cytoscape-dagre https://github.com/cytoscape/cytoscape.js-dagre.git` 116 | 1. [Make a new release](https://github.com/cytoscape/cytoscape.js-dagre/releases/new) for Zenodo. 117 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cytoscape-dagre", 3 | "description": "The Dagre layout for DAGs and trees for Cytoscape.js", 4 | "main": "cytoscape-dagre.js", 5 | "dependencies": { 6 | "cytoscape": "^3.2.0" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/cytoscape/cytoscape.js-dagre.git" 11 | }, 12 | "ignore": [ 13 | "**/.*", 14 | "node_modules", 15 | "bower_components", 16 | "test", 17 | "tests" 18 | ], 19 | "keywords": [ 20 | "cytoscape", 21 | "cytoscape-extension" 22 | ], 23 | "license": "MIT" 24 | } 25 | -------------------------------------------------------------------------------- /cytoscape-dagre.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(require("dagre")); 4 | else if(typeof define === 'function' && define.amd) 5 | define(["dagre"], factory); 6 | else if(typeof exports === 'object') 7 | exports["cytoscapeDagre"] = factory(require("dagre")); 8 | else 9 | root["cytoscapeDagre"] = factory(root["dagre"]); 10 | })(this, function(__WEBPACK_EXTERNAL_MODULE__4__) { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | /******/ 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | /******/ 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) { 20 | /******/ return installedModules[moduleId].exports; 21 | /******/ } 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ i: moduleId, 25 | /******/ l: false, 26 | /******/ exports: {} 27 | /******/ }; 28 | /******/ 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | /******/ 32 | /******/ // Flag the module as loaded 33 | /******/ module.l = true; 34 | /******/ 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | /******/ 39 | /******/ 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | /******/ 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | /******/ 46 | /******/ // define getter function for harmony exports 47 | /******/ __webpack_require__.d = function(exports, name, getter) { 48 | /******/ if(!__webpack_require__.o(exports, name)) { 49 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); 50 | /******/ } 51 | /******/ }; 52 | /******/ 53 | /******/ // define __esModule on exports 54 | /******/ __webpack_require__.r = function(exports) { 55 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 56 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 57 | /******/ } 58 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 59 | /******/ }; 60 | /******/ 61 | /******/ // create a fake namespace object 62 | /******/ // mode & 1: value is a module id, require it 63 | /******/ // mode & 2: merge all properties of value into the ns 64 | /******/ // mode & 4: return value when already ns object 65 | /******/ // mode & 8|1: behave like require 66 | /******/ __webpack_require__.t = function(value, mode) { 67 | /******/ if(mode & 1) value = __webpack_require__(value); 68 | /******/ if(mode & 8) return value; 69 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 70 | /******/ var ns = Object.create(null); 71 | /******/ __webpack_require__.r(ns); 72 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); 73 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 74 | /******/ return ns; 75 | /******/ }; 76 | /******/ 77 | /******/ // getDefaultExport function for compatibility with non-harmony modules 78 | /******/ __webpack_require__.n = function(module) { 79 | /******/ var getter = module && module.__esModule ? 80 | /******/ function getDefault() { return module['default']; } : 81 | /******/ function getModuleExports() { return module; }; 82 | /******/ __webpack_require__.d(getter, 'a', getter); 83 | /******/ return getter; 84 | /******/ }; 85 | /******/ 86 | /******/ // Object.prototype.hasOwnProperty.call 87 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 88 | /******/ 89 | /******/ // __webpack_public_path__ 90 | /******/ __webpack_require__.p = ""; 91 | /******/ 92 | /******/ 93 | /******/ // Load entry module and return exports 94 | /******/ return __webpack_require__(__webpack_require__.s = 0); 95 | /******/ }) 96 | /************************************************************************/ 97 | /******/ ([ 98 | /* 0 */ 99 | /***/ (function(module, exports, __webpack_require__) { 100 | 101 | var impl = __webpack_require__(1); // registers the extension on a cytoscape lib ref 102 | 103 | 104 | var register = function register(cytoscape) { 105 | if (!cytoscape) { 106 | return; 107 | } // can't register if cytoscape unspecified 108 | 109 | 110 | cytoscape('layout', 'dagre', impl); // register with cytoscape.js 111 | }; 112 | 113 | if (typeof cytoscape !== 'undefined') { 114 | // expose to global cytoscape (i.e. window.cytoscape) 115 | register(cytoscape); 116 | } 117 | 118 | module.exports = register; 119 | 120 | /***/ }), 121 | /* 1 */ 122 | /***/ (function(module, exports, __webpack_require__) { 123 | 124 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 125 | 126 | var isFunction = function isFunction(o) { 127 | return typeof o === 'function'; 128 | }; 129 | 130 | var defaults = __webpack_require__(2); 131 | 132 | var assign = __webpack_require__(3); 133 | 134 | var dagre = __webpack_require__(4); // constructor 135 | // options : object containing layout options 136 | 137 | 138 | function DagreLayout(options) { 139 | this.options = assign({}, defaults, options); 140 | } // runs the layout 141 | 142 | 143 | DagreLayout.prototype.run = function () { 144 | var options = this.options; 145 | var layout = this; 146 | var cy = options.cy; // cy is automatically populated for us in the constructor 147 | 148 | var eles = options.eles; 149 | 150 | var getVal = function getVal(ele, val) { 151 | return isFunction(val) ? val.apply(ele, [ele]) : val; 152 | }; 153 | 154 | var bb = options.boundingBox || { 155 | x1: 0, 156 | y1: 0, 157 | w: cy.width(), 158 | h: cy.height() 159 | }; 160 | 161 | if (bb.x2 === undefined) { 162 | bb.x2 = bb.x1 + bb.w; 163 | } 164 | 165 | if (bb.w === undefined) { 166 | bb.w = bb.x2 - bb.x1; 167 | } 168 | 169 | if (bb.y2 === undefined) { 170 | bb.y2 = bb.y1 + bb.h; 171 | } 172 | 173 | if (bb.h === undefined) { 174 | bb.h = bb.y2 - bb.y1; 175 | } 176 | 177 | var g = new dagre.graphlib.Graph({ 178 | multigraph: true, 179 | compound: true 180 | }); 181 | var gObj = {}; 182 | 183 | var setGObj = function setGObj(name, val) { 184 | if (val != null) { 185 | gObj[name] = val; 186 | } 187 | }; 188 | 189 | setGObj('nodesep', options.nodeSep); 190 | setGObj('edgesep', options.edgeSep); 191 | setGObj('ranksep', options.rankSep); 192 | setGObj('rankdir', options.rankDir); 193 | setGObj('align', options.align); 194 | setGObj('ranker', options.ranker); 195 | setGObj('acyclicer', options.acyclicer); 196 | g.setGraph(gObj); 197 | g.setDefaultEdgeLabel(function () { 198 | return {}; 199 | }); 200 | g.setDefaultNodeLabel(function () { 201 | return {}; 202 | }); // add nodes to dagre 203 | 204 | var nodes = eles.nodes(); 205 | 206 | if (isFunction(options.sort)) { 207 | nodes = nodes.sort(options.sort); 208 | } 209 | 210 | for (var i = 0; i < nodes.length; i++) { 211 | var node = nodes[i]; 212 | var nbb = node.layoutDimensions(options); 213 | g.setNode(node.id(), { 214 | width: nbb.w, 215 | height: nbb.h, 216 | name: node.id() 217 | }); // console.log( g.node(node.id()) ); 218 | } // set compound parents 219 | 220 | 221 | for (var _i = 0; _i < nodes.length; _i++) { 222 | var _node = nodes[_i]; 223 | 224 | if (_node.isChild()) { 225 | g.setParent(_node.id(), _node.parent().id()); 226 | } 227 | } // add edges to dagre 228 | 229 | 230 | var edges = eles.edges().stdFilter(function (edge) { 231 | return !edge.source().isParent() && !edge.target().isParent(); // dagre can't handle edges on compound nodes 232 | }); 233 | 234 | if (isFunction(options.sort)) { 235 | edges = edges.sort(options.sort); 236 | } 237 | 238 | for (var _i2 = 0; _i2 < edges.length; _i2++) { 239 | var edge = edges[_i2]; 240 | g.setEdge(edge.source().id(), edge.target().id(), { 241 | minlen: getVal(edge, options.minLen), 242 | weight: getVal(edge, options.edgeWeight), 243 | name: edge.id() 244 | }, edge.id()); // console.log( g.edge(edge.source().id(), edge.target().id(), edge.id()) ); 245 | } 246 | 247 | dagre.layout(g); 248 | var gNodeIds = g.nodes(); 249 | 250 | for (var _i3 = 0; _i3 < gNodeIds.length; _i3++) { 251 | var id = gNodeIds[_i3]; 252 | var n = g.node(id); 253 | cy.getElementById(id).scratch().dagre = n; 254 | } 255 | 256 | var dagreBB; 257 | 258 | if (options.boundingBox) { 259 | dagreBB = { 260 | x1: Infinity, 261 | x2: -Infinity, 262 | y1: Infinity, 263 | y2: -Infinity 264 | }; 265 | nodes.forEach(function (node) { 266 | var dModel = node.scratch().dagre; 267 | dagreBB.x1 = Math.min(dagreBB.x1, dModel.x); 268 | dagreBB.x2 = Math.max(dagreBB.x2, dModel.x); 269 | dagreBB.y1 = Math.min(dagreBB.y1, dModel.y); 270 | dagreBB.y2 = Math.max(dagreBB.y2, dModel.y); 271 | }); 272 | dagreBB.w = dagreBB.x2 - dagreBB.x1; 273 | dagreBB.h = dagreBB.y2 - dagreBB.y1; 274 | } else { 275 | dagreBB = bb; 276 | } 277 | 278 | var constrainPos = function constrainPos(p) { 279 | if (options.boundingBox) { 280 | var xPct = dagreBB.w === 0 ? 0 : (p.x - dagreBB.x1) / dagreBB.w; 281 | var yPct = dagreBB.h === 0 ? 0 : (p.y - dagreBB.y1) / dagreBB.h; 282 | return { 283 | x: bb.x1 + xPct * bb.w, 284 | y: bb.y1 + yPct * bb.h 285 | }; 286 | } else { 287 | return p; 288 | } 289 | }; 290 | 291 | nodes.layoutPositions(layout, options, function (ele) { 292 | ele = _typeof(ele) === "object" ? ele : this; 293 | var dModel = ele.scratch().dagre; 294 | return constrainPos({ 295 | x: dModel.x, 296 | y: dModel.y 297 | }); 298 | }); 299 | return this; // chaining 300 | }; 301 | 302 | module.exports = DagreLayout; 303 | 304 | /***/ }), 305 | /* 2 */ 306 | /***/ (function(module, exports) { 307 | 308 | var defaults = { 309 | // dagre algo options, uses default value on undefined 310 | nodeSep: undefined, 311 | // the separation between adjacent nodes in the same rank 312 | edgeSep: undefined, 313 | // the separation between adjacent edges in the same rank 314 | rankSep: undefined, 315 | // the separation between adjacent nodes in the same rank 316 | rankDir: undefined, 317 | // 'TB' for top to bottom flow, 'LR' for left to right, 318 | align: undefined, 319 | // alignment for rank nodes. Can be 'UL', 'UR', 'DL', or 'DR', where U = up, D = down, L = left, and R = right 320 | acyclicer: undefined, 321 | // If set to 'greedy', uses a greedy heuristic for finding a feedback arc set for a graph. 322 | // A feedback arc set is a set of edges that can be removed to make a graph acyclic. 323 | ranker: undefined, 324 | // Type of algorithm to assigns a rank to each node in the input graph. 325 | // Possible values: network-simplex, tight-tree or longest-path 326 | minLen: function minLen(edge) { 327 | return 1; 328 | }, 329 | // number of ranks to keep between the source and target of the edge 330 | edgeWeight: function edgeWeight(edge) { 331 | return 1; 332 | }, 333 | // higher weight edges are generally made shorter and straighter than lower weight edges 334 | // general layout options 335 | fit: true, 336 | // whether to fit to viewport 337 | padding: 30, 338 | // fit padding 339 | spacingFactor: undefined, 340 | // Applies a multiplicative factor (>0) to expand or compress the overall area that the nodes take up 341 | nodeDimensionsIncludeLabels: false, 342 | // whether labels should be included in determining the space used by a node 343 | animate: false, 344 | // whether to transition the node positions 345 | animateFilter: function animateFilter(node, i) { 346 | return true; 347 | }, 348 | // whether to animate specific nodes when animation is on; non-animated nodes immediately go to their final positions 349 | animationDuration: 500, 350 | // duration of animation in ms if enabled 351 | animationEasing: undefined, 352 | // easing of animation if enabled 353 | boundingBox: undefined, 354 | // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 355 | transform: function transform(node, pos) { 356 | return pos; 357 | }, 358 | // a function that applies a transform to the final node position 359 | ready: function ready() {}, 360 | // on layoutready 361 | sort: undefined, 362 | // a sorting function to order the nodes and edges; e.g. function(a, b){ return a.data('weight') - b.data('weight') } 363 | // because cytoscape dagre creates a directed graph, and directed graphs use the node order as a tie breaker when 364 | // defining the topology of a graph, this sort function can help ensure the correct order of the nodes/edges. 365 | // this feature is most useful when adding and removing the same nodes and edges multiple times in a graph. 366 | stop: function stop() {} // on layoutstop 367 | 368 | }; 369 | module.exports = defaults; 370 | 371 | /***/ }), 372 | /* 3 */ 373 | /***/ (function(module, exports) { 374 | 375 | // Simple, internal Object.assign() polyfill for options objects etc. 376 | module.exports = Object.assign != null ? Object.assign.bind(Object) : function (tgt) { 377 | for (var _len = arguments.length, srcs = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 378 | srcs[_key - 1] = arguments[_key]; 379 | } 380 | 381 | srcs.forEach(function (src) { 382 | Object.keys(src).forEach(function (k) { 383 | return tgt[k] = src[k]; 384 | }); 385 | }); 386 | return tgt; 387 | }; 388 | 389 | /***/ }), 390 | /* 4 */ 391 | /***/ (function(module, exports) { 392 | 393 | module.exports = __WEBPACK_EXTERNAL_MODULE__4__; 394 | 395 | /***/ }) 396 | /******/ ]); 397 | }); -------------------------------------------------------------------------------- /demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |