├── pages
├── demo.html
├── index.html
├── cytoscape-cola.js
├── demo-compound.html
├── demo-constraints.html
└── demo-non-animated.html
├── .eslintignore
├── .babelrc
├── .gitignore
├── .npmignore
├── .eslintrc
├── src
├── assign.js
├── raf.js
├── index.js
├── defaults.js
└── cola.js
├── bower.json
├── .github
└── stale.yml
├── webpack.config.js
├── LICENSE
├── package.json
├── demo-constraints.html
├── README.md
├── demo-compound.html
├── demo.html
├── demo-non-animated.html
└── cytoscape-cola.js
/pages/demo.html:
--------------------------------------------------------------------------------
1 | ../demo.html
--------------------------------------------------------------------------------
/pages/index.html:
--------------------------------------------------------------------------------
1 | ../demo.html
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 |
--------------------------------------------------------------------------------
/pages/cytoscape-cola.js:
--------------------------------------------------------------------------------
1 | ../cytoscape-cola.js
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env"]
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 |
--------------------------------------------------------------------------------
/pages/demo-compound.html:
--------------------------------------------------------------------------------
1 | ../demo-compound.html
--------------------------------------------------------------------------------
/pages/demo-constraints.html:
--------------------------------------------------------------------------------
1 | ../demo-constraints.html
--------------------------------------------------------------------------------
/pages/demo-non-animated.html:
--------------------------------------------------------------------------------
1 | ../demo-non-animated.html
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | package-lock.json
4 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/assign.js:
--------------------------------------------------------------------------------
1 | // Simple, internal Object.assign() polyfill for options objects etc.
2 |
3 | module.exports = Object.assign != null ? Object.assign.bind( Object ) : function( tgt, ...srcs ){
4 | srcs.filter(src => src != null).forEach( src => {
5 | Object.keys( src ).forEach( k => tgt[k] = src[k] );
6 | } );
7 |
8 | return tgt;
9 | };
10 |
--------------------------------------------------------------------------------
/src/raf.js:
--------------------------------------------------------------------------------
1 | let raf;
2 |
3 | if( typeof window !== typeof undefined ){
4 | raf = ( window.requestAnimationFrame ||
5 | window.webkitRequestAnimationFrame ||
6 | window.mozRequestAnimationFrame ||
7 | window.msRequestAnimationFrame ||
8 | (fn => setTimeout(fn, 16))
9 | );
10 | } else { // if not available, all you get is immediate calls
11 | raf = function( cb ){
12 | cb();
13 | };
14 | }
15 |
16 | module.exports = raf;
17 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const impl = require('./cola');
2 |
3 | // registers the extension on a cytoscape lib ref
4 | let register = function( cytoscape ){
5 | if( !cytoscape ){ return; } // can't register if cytoscape unspecified
6 |
7 | cytoscape( 'layout', 'cola', impl ); // register with cytoscape.js
8 | };
9 |
10 | if( typeof cytoscape !== 'undefined' ){ // expose to global cytoscape (i.e. window.cytoscape)
11 | register( cytoscape );
12 | }
13 |
14 | module.exports = register;
15 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cytoscape-cola",
3 | "description": "The Cola.js physics simulation layout for Cytoscape.js",
4 | "main": "cytoscape-cola.js",
5 | "dependencies": {
6 | "cytoscape": "^3.2.0"
7 | },
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/cytoscape/cytoscape.js-cola.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 |
--------------------------------------------------------------------------------
/.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 30 days. It will be closed if no further activity
14 | occurs within the next 30 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 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const pkg = require('./package.json');
3 | const camelcase = require('camelcase');
4 | const process = require('process');
5 | const webpack = require('webpack');
6 | const env = process.env;
7 | const NODE_ENV = env.NODE_ENV;
8 | const MIN = env.MIN;
9 | const PROD = NODE_ENV === 'production';
10 |
11 | let config = {
12 | devtool: PROD ? false : 'inline-source-map',
13 | entry: './src/index.js',
14 | output: {
15 | path: path.join( __dirname ),
16 | filename: pkg.name + '.js',
17 | library: camelcase( pkg.name ),
18 | libraryTarget: 'umd'
19 | },
20 | module: {
21 | rules: [
22 | { test: /\.js$/, exclude: /node_modules/, use: 'babel-loader' }
23 | ]
24 | },
25 | externals: PROD ? Object.keys( pkg.dependencies || {} ) : [],
26 | plugins: MIN ? [
27 | new webpack.optimize.UglifyJsPlugin({
28 | compress: {
29 | warnings: false,
30 | drop_console: false,
31 | }
32 | })
33 | ] : []
34 | };
35 |
36 | module.exports = config;
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 |
3 | Copyright (c) 2016-2018, 2020-2021, 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.
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cytoscape-cola",
3 | "version": "2.5.1",
4 | "description": "The Cola.js physics simulation layout for Cytoscape.js",
5 | "main": "cytoscape-cola.js",
6 | "author": {
7 | "name": "Max Franz",
8 | "email": "maxkfranz@gmail.com"
9 | },
10 | "scripts": {
11 | "postpublish": "run-s gh-pages",
12 | "gh-pages": "gh-pages -d pages",
13 | "copyright": "update license",
14 | "lint": "eslint src",
15 | "build": "cross-env NODE_ENV=production webpack",
16 | "build:min": "cross-env NODE_ENV=production MIN=true webpack",
17 | "build:release": "run-s build copyright",
18 | "watch": "webpack --progress --watch",
19 | "dev": "webpack-dev-server --open",
20 | "test": "mocha"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "https://github.com/cytoscape/cytoscape.js-cola.git"
25 | },
26 | "keywords": [
27 | "cytoscape",
28 | "cytoscape-extension"
29 | ],
30 | "license": "MIT",
31 | "bugs": {
32 | "url": "https://github.com/cytoscape/cytoscape.js-cola/issues"
33 | },
34 | "homepage": "https://github.com/cytoscape/cytoscape.js-cola",
35 | "devDependencies": {
36 | "babel-core": "^6.24.1",
37 | "babel-loader": "^7.0.0",
38 | "babel-preset-env": "^1.5.1",
39 | "camelcase": "^4.1.0",
40 | "chai": "4.0.2",
41 | "cpy-cli": "^1.0.1",
42 | "cross-env": "^5.2.1",
43 | "eslint": "^3.9.1",
44 | "gh-pages": "^1.0.0",
45 | "mocha": "3.4.2",
46 | "npm-run-all": "^4.1.5",
47 | "rimraf": "^2.7.1",
48 | "update": "^0.7.4",
49 | "updater-license": "^1.0.0",
50 | "webpack": "^2.6.1",
51 | "webpack-dev-server": "^2.11.5"
52 | },
53 | "peerDependencies": {
54 | "cytoscape": "^3.2.0"
55 | },
56 | "dependencies": {
57 | "webcola": "^3.4.0"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/defaults.js:
--------------------------------------------------------------------------------
1 | // default layout options
2 | let defaults = {
3 | animate: true, // whether to show the layout as it's running
4 | refresh: 1, // number of ticks per frame; higher is faster but more jerky
5 | maxSimulationTime: 4000, // max length in ms to run the layout
6 | ungrabifyWhileSimulating: false, // so you can't drag nodes during layout
7 | fit: true, // on every layout reposition of nodes, fit the viewport
8 | padding: 30, // padding around the simulation
9 | boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
10 | nodeDimensionsIncludeLabels: false, // whether labels should be included in determining the space used by a node
11 |
12 | // layout event callbacks
13 | ready: function(){}, // on layoutready
14 | stop: function(){}, // on layoutstop
15 |
16 | // positioning options
17 | randomize: false, // use random node positions at beginning of layout
18 | avoidOverlap: true, // if true, prevents overlap of node bounding boxes
19 | handleDisconnected: true, // if true, avoids disconnected components from overlapping
20 | convergenceThreshold: 0.01, // when the alpha value (system energy) falls below this value, the layout stops
21 | nodeSpacing: function( node ){ return 10; }, // extra spacing around nodes
22 | flow: undefined, // use DAG/tree flow layout if specified, e.g. { axis: 'y', minSeparation: 30 }
23 | alignment: undefined, // relative alignment constraints on nodes, e.g. function( node ){ return { x: 0, y: 1 } }
24 | gapInequalities: undefined, // list of inequality constraints for the gap between the nodes, e.g. [{"axis":"y", "left":node1, "right":node2, "gap":25}]
25 | centerGraph: true, // adjusts the node positions initially to center the graph (pass false if you want to start the layout from the current position)
26 |
27 |
28 | // different methods of specifying edge length
29 | // each can be a constant numerical value or a function like `function( edge ){ return 2; }`
30 | edgeLength: undefined, // sets edge length directly in simulation
31 | edgeSymDiffLength: undefined, // symmetric diff edge length in simulation
32 | edgeJaccardLength: undefined, // jaccard edge length in simulation
33 |
34 | // iterations of cola algorithm; uses default values on undefined
35 | unconstrIter: undefined, // unconstrained initial layout iterations
36 | userConstIter: undefined, // initial layout iterations with user-specified constraints
37 | allConstIter: undefined, // initial layout iterations with all constraints including non-overlap
38 |
39 | // infinite layout options
40 | infinite: false // overrides all other options for a forces-all-the-time mode
41 | };
42 |
43 | module.exports = defaults;
44 |
--------------------------------------------------------------------------------
/demo-constraints.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | cytoscape-cola.js demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
38 |
39 |
164 |
165 |
166 |
167 | cytoscape-cola demo
168 | n3 is fixed to x: 100, y: 100
169 | n1 - n2 vertical alignment on left side
170 | n3 - n4 vertical alignment on center
171 | n5 - n6 vertical alignment on right side
172 | n1 - n3 - n5 horizontal alignment
173 | n1.x + 100 = n3.x
174 | n3.x + 100 = n5.x
175 |
176 |
177 |
178 |
179 |
180 |
181 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | cytoscape-cola
2 | ================================================================================
3 |
4 | [](https://zenodo.org/badge/latestdoi/42205998)
5 |
6 |
7 | ## Description
8 |
9 | The Cola.js physics simulation layout for Cytoscape.js ([demo](https://cytoscape.github.io/cytoscape.js-cola), [non-animated demo](https://cytoscape.github.io/cytoscape.js-cola/demo-non-animated.html), [compound demo](https://cytoscape.github.io/cytoscape.js-cola/demo-compound.html), [constraint demo](https://cytoscape.github.io/cytoscape.js-cola/demo-constraints.html))
10 |
11 |
12 | The `cola` layout uses a [force-directed](http://en.wikipedia.org/wiki/Force-directed_graph_drawing) physics simulation with several sophisticated constraints, written by [Tim Dwyer](http://www.csse.monash.edu.au/~tdwyer/). For more information about Cola and its parameters, refer to [its documentation](http://marvl.infotech.monash.edu/webcola/).
13 |
14 | It supports noncompound and compound graphs well.
15 |
16 | ## Dependencies
17 |
18 | * Cytoscape.js ^3.2.0
19 | * Cola.js ^3.1.2
20 |
21 |
22 | ## Usage instructions
23 |
24 | Download the library:
25 | * via npm: `npm install cytoscape-cola`,
26 | * via bower: `bower install cytoscape-cola`, or
27 | * via direct download in the repository (probably from a tag).
28 |
29 | Import the library as appropriate for your project:
30 |
31 | ES import:
32 |
33 | ```js
34 | import cytoscape from 'cytoscape';
35 | import cola from 'cytoscape-cola';
36 |
37 | cytoscape.use( cola );
38 | ```
39 |
40 | CommonJS require:
41 |
42 | ```js
43 | let cytoscape = require('cytoscape');
44 | let cola = require('cytoscape-cola');
45 |
46 | cytoscape.use( cola ); // register extension
47 | ```
48 |
49 | AMD:
50 |
51 | ```js
52 | require(['cytoscape', 'cytoscape-cola'], function( cytoscape, cola ){
53 | cola( cytoscape ); // register extension
54 | });
55 | ```
56 |
57 | Plain HTML/JS has the extension registered for you automatically, because no `require()` is needed.
58 |
59 |
60 |
61 | ## API
62 |
63 | Call the layout, e.g. `cy.layout({ name: 'cola', ... })`, with options:
64 |
65 | ```js
66 | // default layout options
67 | var defaults = {
68 | animate: true, // whether to show the layout as it's running
69 | refresh: 1, // number of ticks per frame; higher is faster but more jerky
70 | maxSimulationTime: 4000, // max length in ms to run the layout
71 | ungrabifyWhileSimulating: false, // so you can't drag nodes during layout
72 | fit: true, // on every layout reposition of nodes, fit the viewport
73 | padding: 30, // padding around the simulation
74 | boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
75 | nodeDimensionsIncludeLabels: false, // whether labels should be included in determining the space used by a node
76 |
77 | // layout event callbacks
78 | ready: function(){}, // on layoutready
79 | stop: function(){}, // on layoutstop
80 |
81 | // positioning options
82 | randomize: false, // use random node positions at beginning of layout
83 | avoidOverlap: true, // if true, prevents overlap of node bounding boxes
84 | handleDisconnected: true, // if true, avoids disconnected components from overlapping
85 | convergenceThreshold: 0.01, // when the alpha value (system energy) falls below this value, the layout stops
86 | nodeSpacing: function( node ){ return 10; }, // extra spacing around nodes
87 | flow: undefined, // use DAG/tree flow layout if specified, e.g. { axis: 'y', minSeparation: 30 }
88 | alignment: undefined, // relative alignment constraints on nodes, e.g. {vertical: [[{node: node1, offset: 0}, {node: node2, offset: 5}]], horizontal: [[{node: node3}, {node: node4}], [{node: node5}, {node: node6}]]}
89 | gapInequalities: undefined, // list of inequality constraints for the gap between the nodes, e.g. [{"axis":"y", "left":node1, "right":node2, "gap":25}]
90 | centerGraph: true, // adjusts the node positions initially to center the graph (pass false if you want to start the layout from the current position)
91 |
92 | // different methods of specifying edge length
93 | // each can be a constant numerical value or a function like `function( edge ){ return 2; }`
94 | edgeLength: undefined, // sets edge length directly in simulation
95 | edgeSymDiffLength: undefined, // symmetric diff edge length in simulation
96 | edgeJaccardLength: undefined, // jaccard edge length in simulation
97 |
98 | // iterations of cola algorithm; uses default values on undefined
99 | unconstrIter: undefined, // unconstrained initial layout iterations
100 | userConstIter: undefined, // initial layout iterations with user-specified constraints
101 | allConstIter: undefined, // initial layout iterations with all constraints including non-overlap
102 | };
103 | ```
104 |
105 |
106 | ## Notes
107 |
108 | - The `alignment` option isn't as flexible as the raw Cola option. Here, only integers can be used to specify relative positioning, so it's a bit limited. If you'd like to see a more sophisticated implementation, please send a pull request.
109 |
110 |
111 |
112 | ## Build targets
113 |
114 | * `npm run test` : Run Mocha tests in `./test`
115 | * `npm run build` : Build `./src/**` into `cytoscape-cola.js`
116 | * `npm run watch` : Automatically build on changes with live reloading (N.b. you must already have an HTTP server running)
117 | * `npm run dev` : Automatically build on changes with live reloading with webpack dev server
118 | * `npm run lint` : Run eslint on the source
119 |
120 | N.b. all builds use babel, so modern ES features can be used in the `src`.
121 |
122 |
123 | ## Publishing instructions
124 |
125 | This project is set up to automatically be published to npm and bower. To publish:
126 |
127 | 1. Build the extension : `npm run build:release`
128 | 1. Commit the build : `git commit -am "Build for release"`
129 | 1. Bump the version number and tag: `npm version major|minor|patch`
130 | 1. Push to origin: `git push && git push --tags`
131 | 1. Publish to npm: `npm publish .`
132 | 1. If publishing to bower for the first time, you'll need to run `bower register cytoscape-cola https://github.com/cytoscape/cytoscape.js-cola.git`
133 | 1. [Make a new release](https://github.com/cytoscape/cytoscape.js-cola/releases/new) for Zenodo.
134 |
--------------------------------------------------------------------------------
/demo-compound.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | cytoscape-cola.js demo (compound)
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
38 |
39 |
165 |
166 |
167 |
168 | cytoscape-cola demo (compound)
169 |
170 |
171 |
172 |
173 |
174 |
--------------------------------------------------------------------------------
/src/cola.js:
--------------------------------------------------------------------------------
1 | const assign = require('./assign');
2 | const defaults = require('./defaults');
3 | const cola = require('webcola') || ( typeof window !== 'undefined' ? window.cola : null );
4 | const raf = require('./raf');
5 | const isString = function(o){ return typeof o === typeof ''; };
6 | const isNumber = function(o){ return typeof o === typeof 0; };
7 | const isObject = function(o){ return o != null && typeof o === typeof {}; };
8 | const isFunction = function(o){ return o != null && typeof o === typeof function(){}; };
9 | const nop = function(){};
10 |
11 | const getOptVal = function( val, ele ){
12 | if( isFunction(val) ){
13 | let fn = val;
14 | return fn.apply( ele, [ ele ] );
15 | } else {
16 | return val;
17 | }
18 | };
19 |
20 | // constructor
21 | // options : object containing layout options
22 | function ColaLayout( options ){
23 | this.options = assign( {}, defaults, options );
24 | }
25 |
26 | // runs the layout
27 | ColaLayout.prototype.run = function(){
28 | let layout = this;
29 | let options = this.options;
30 |
31 | layout.manuallyStopped = false;
32 |
33 | let cy = options.cy; // cy is automatically populated for us in the constructor
34 | let eles = options.eles;
35 | let nodes = eles.nodes();
36 | let edges = eles.edges();
37 | let ready = false;
38 |
39 | let isParent = ele => ele.isParent();
40 |
41 | let parentNodes = nodes.filter(isParent);
42 |
43 | let nonparentNodes = nodes.subtract(parentNodes);
44 |
45 | let bb = options.boundingBox || { x1: 0, y1: 0, w: cy.width(), h: cy.height() };
46 | if( bb.x2 === undefined ){ bb.x2 = bb.x1 + bb.w; }
47 | if( bb.w === undefined ){ bb.w = bb.x2 - bb.x1; }
48 | if( bb.y2 === undefined ){ bb.y2 = bb.y1 + bb.h; }
49 | if( bb.h === undefined ){ bb.h = bb.y2 - bb.y1; }
50 |
51 | let updateNodePositions = function(){
52 | for( let i = 0; i < nodes.length; i++ ){
53 | let node = nodes[i];
54 | let dimensions = node.layoutDimensions( options );
55 | let scratch = node.scratch('cola');
56 |
57 | // update node dims
58 | if( !scratch.updatedDims ){
59 | let padding = getOptVal( options.nodeSpacing, node );
60 |
61 | scratch.width = dimensions.w + 2*padding;
62 | scratch.height = dimensions.h + 2*padding;
63 | }
64 | }
65 |
66 | nodes.positions(function(node){
67 | let scratch = node.scratch().cola;
68 | let retPos;
69 |
70 | if( !node.grabbed() && nonparentNodes.contains(node) ){
71 | retPos = {
72 | x: bb.x1 + scratch.x,
73 | y: bb.y1 + scratch.y
74 | };
75 |
76 | if( !isNumber(retPos.x) || !isNumber(retPos.y) ){
77 | retPos = undefined;
78 | }
79 | }
80 |
81 | return retPos;
82 | });
83 |
84 | nodes.updateCompoundBounds(); // because the way this layout sets positions is buggy for some reason; ref #878
85 |
86 | if( !ready ){
87 | onReady();
88 | ready = true;
89 | }
90 |
91 | if( options.fit ){
92 | cy.fit( options.padding );
93 | }
94 | };
95 |
96 | let onDone = function(){
97 | if( options.ungrabifyWhileSimulating ){
98 | grabbableNodes.grabify();
99 | }
100 |
101 | cy.off('destroy', destroyHandler);
102 |
103 | nodes.off('grab free position', grabHandler);
104 | nodes.off('lock unlock', lockHandler);
105 |
106 | // trigger layoutstop when the layout stops (e.g. finishes)
107 | layout.one('layoutstop', options.stop);
108 | layout.trigger({ type: 'layoutstop', layout: layout });
109 | };
110 |
111 | let onReady = function(){
112 | // trigger layoutready when each node has had its position set at least once
113 | layout.one('layoutready', options.ready);
114 | layout.trigger({ type: 'layoutready', layout: layout });
115 | };
116 |
117 | let ticksPerFrame = options.refresh;
118 |
119 | if( options.refresh < 0 ){
120 | ticksPerFrame = 1;
121 | } else {
122 | ticksPerFrame = Math.max( 1, ticksPerFrame ); // at least 1
123 | }
124 |
125 | let adaptor = layout.adaptor = cola.adaptor({
126 | trigger: function( e ){ // on sim event
127 | let TICK = cola.EventType ? cola.EventType.tick : null;
128 | let END = cola.EventType ? cola.EventType.end : null;
129 |
130 | switch( e.type ){
131 | case 'tick':
132 | case TICK:
133 | if( options.animate ){
134 | updateNodePositions();
135 | }
136 | break;
137 |
138 | case 'end':
139 | case END:
140 | updateNodePositions();
141 | if( !options.infinite ){ onDone(); }
142 | break;
143 | }
144 | },
145 |
146 | kick: function(){ // kick off the simulation
147 | //let skip = 0;
148 |
149 | let firstTick = true;
150 |
151 | let inftick = function(){
152 | if( layout.manuallyStopped ){
153 | onDone();
154 |
155 | return true;
156 | }
157 |
158 | let ret = adaptor.tick();
159 |
160 | if( !options.infinite && !firstTick ){
161 | adaptor.convergenceThreshold(options.convergenceThreshold);
162 | }
163 |
164 | firstTick = false;
165 |
166 | if( ret && options.infinite ){ // resume layout if done
167 | adaptor.resume(); // resume => new kick
168 | }
169 |
170 | return ret; // allow regular finish b/c of new kick
171 | };
172 |
173 | let multitick = function(){ // multiple ticks in a row
174 | let ret;
175 |
176 | for( let i = 0; i < ticksPerFrame && !ret; i++ ){
177 | ret = ret || inftick(); // pick up true ret vals => sim done
178 | }
179 |
180 | return ret;
181 | };
182 |
183 | if( options.animate ){
184 | let frame = function(){
185 | if( multitick() ){ return; }
186 |
187 | raf( frame );
188 | };
189 |
190 | raf( frame );
191 | } else {
192 | while( !inftick() ){
193 | // keep going...
194 | }
195 | }
196 | },
197 |
198 | on: nop, // dummy; not needed
199 |
200 | drag: nop // not needed for our case
201 | });
202 | layout.adaptor = adaptor;
203 |
204 | // if set no grabbing during layout
205 | let grabbableNodes = nodes.filter(':grabbable');
206 | if( options.ungrabifyWhileSimulating ){
207 | grabbableNodes.ungrabify();
208 | }
209 |
210 | let destroyHandler;
211 | cy.one('destroy', destroyHandler = function(){
212 | layout.stop();
213 | });
214 |
215 | // handle node dragging
216 | let grabHandler;
217 | nodes.on('grab free position', grabHandler = function(e){
218 | let node = this;
219 | let scrCola = node.scratch().cola;
220 | let pos = node.position();
221 | let nodeIsTarget = e.cyTarget === node || e.target === node;
222 |
223 | if( !nodeIsTarget ){ return; }
224 |
225 | switch( e.type ){
226 | case 'grab':
227 | adaptor.dragstart( scrCola );
228 | break;
229 | case 'free':
230 | adaptor.dragend( scrCola );
231 | break;
232 | case 'position':
233 | // only update when different (i.e. manual .position() call or drag) so we don't loop needlessly
234 | if( scrCola.px !== pos.x - bb.x1 || scrCola.py !== pos.y - bb.y1 ){
235 | scrCola.px = pos.x - bb.x1;
236 | scrCola.py = pos.y - bb.y1;
237 | }
238 | break;
239 | }
240 |
241 | });
242 |
243 | let lockHandler;
244 | nodes.on('lock unlock', lockHandler = function(){
245 | let node = this;
246 | let scrCola = node.scratch().cola;
247 |
248 | scrCola.fixed = node.locked();
249 |
250 | if( node.locked() ){
251 | adaptor.dragstart( scrCola );
252 | } else {
253 | adaptor.dragend( scrCola );
254 | }
255 | });
256 |
257 | // add nodes to cola
258 | adaptor.nodes( nonparentNodes.map(function( node, i ){
259 | let padding = getOptVal( options.nodeSpacing, node );
260 | let pos = node.position();
261 | let dimensions = node.layoutDimensions( options );
262 |
263 | let struct = node.scratch().cola = {
264 | x: (options.randomize && !node.locked()) || pos.x === undefined ? Math.round( Math.random() * bb.w ) : pos.x,
265 | y: (options.randomize && !node.locked()) || pos.y === undefined ? Math.round( Math.random() * bb.h ) : pos.y,
266 | width: dimensions.w + 2*padding,
267 | height: dimensions.h + 2*padding,
268 | index: i,
269 | fixed: node.locked()
270 | };
271 |
272 | return struct;
273 | }) );
274 |
275 | // the constraints to be added on nodes
276 | let constraints = [];
277 |
278 | if( options.alignment ){ // then set alignment constraints
279 |
280 | if(options.alignment.vertical) {
281 | let verticalAlignments = options.alignment.vertical;
282 | verticalAlignments.forEach(function(alignment){
283 | let offsetsX = [];
284 | alignment.forEach(function(nodeData){
285 | let node = nodeData.node;
286 | let scrCola = node.scratch().cola;
287 | let index = scrCola.index;
288 | offsetsX.push({
289 | node: index,
290 | offset: nodeData.offset ? nodeData.offset : 0
291 | });
292 | });
293 | constraints.push({
294 | type: 'alignment',
295 | axis: 'x',
296 | offsets: offsetsX
297 | });
298 | });
299 | }
300 |
301 | if(options.alignment.horizontal) {
302 | let horizontalAlignments = options.alignment.horizontal;
303 | horizontalAlignments.forEach(function(alignment){
304 | let offsetsY = [];
305 | alignment.forEach(function(nodeData){
306 | let node = nodeData.node;
307 | let scrCola = node.scratch().cola;
308 | let index = scrCola.index;
309 | offsetsY.push({
310 | node: index,
311 | offset: nodeData.offset ? nodeData.offset : 0
312 | });
313 | });
314 | constraints.push({
315 | type: 'alignment',
316 | axis: 'y',
317 | offsets: offsetsY
318 | });
319 | });
320 | }
321 |
322 | }
323 |
324 | // if gapInequalities variable is set add each inequality constraint to list of constraints
325 | if ( options.gapInequalities ) {
326 | options.gapInequalities.forEach( inequality => {
327 |
328 | // for the constraints to be passed to cola layout adaptor use indices of nodes,
329 | // not the nodes themselves
330 | let leftIndex = inequality.left.scratch().cola.index;
331 | let rightIndex = inequality.right.scratch().cola.index;
332 |
333 | constraints.push({
334 | axis: inequality.axis,
335 | left: leftIndex,
336 | right: rightIndex,
337 | gap: inequality.gap,
338 | equality: inequality.equality
339 | });
340 |
341 | } );
342 | }
343 |
344 | // add constraints if any
345 | if ( constraints.length > 0 ) {
346 | adaptor.constraints( constraints );
347 | }
348 |
349 | // add compound nodes to cola
350 | adaptor.groups( parentNodes.map(function( node, i ){ // add basic group incl leaf nodes
351 | let optPadding = getOptVal( options.nodeSpacing, node );
352 | let getPadding = function(d){
353 | return parseFloat( node.style('padding-'+d) );
354 | };
355 |
356 | let pleft = getPadding('left') + optPadding;
357 | let pright = getPadding('right') + optPadding;
358 | let ptop = getPadding('top') + optPadding;
359 | let pbottom = getPadding('bottom') + optPadding;
360 |
361 | node.scratch().cola = {
362 | index: i,
363 |
364 | padding: Math.max( pleft, pright, ptop, pbottom ),
365 |
366 | // leaves should only contain direct descendants (children),
367 | // not the leaves of nested compound nodes or any nodes that are compounds themselves
368 | leaves: node.children()
369 | .intersection(nonparentNodes)
370 | .map(function( child ){
371 | return child[0].scratch().cola.index;
372 | }),
373 |
374 | fixed: node.locked()
375 | };
376 |
377 | return node;
378 | }).map(function( node ){ // add subgroups
379 | node.scratch().cola.groups = node.children()
380 | .intersection(parentNodes)
381 | .map(function( child ){
382 | return child.scratch().cola.index;
383 | });
384 |
385 | return node.scratch().cola;
386 | }) );
387 |
388 | // get the edge length setting mechanism
389 | let length;
390 | let lengthFnName;
391 | if( options.edgeLength != null ){
392 | length = options.edgeLength;
393 | lengthFnName = 'linkDistance';
394 | } else if( options.edgeSymDiffLength != null ){
395 | length = options.edgeSymDiffLength;
396 | lengthFnName = 'symmetricDiffLinkLengths';
397 | } else if( options.edgeJaccardLength != null ){
398 | length = options.edgeJaccardLength;
399 | lengthFnName = 'jaccardLinkLengths';
400 | } else {
401 | length = 100;
402 | lengthFnName = 'linkDistance';
403 | }
404 |
405 | let lengthGetter = function( link ){
406 | return link.calcLength;
407 | };
408 |
409 | // add the edges to cola
410 | adaptor.links( edges.stdFilter(function( edge ){
411 | return nonparentNodes.contains(edge.source()) && nonparentNodes.contains(edge.target());
412 | }).map(function( edge ){
413 | let c = edge.scratch().cola = {
414 | source: edge.source()[0].scratch().cola.index,
415 | target: edge.target()[0].scratch().cola.index
416 | };
417 |
418 | if( length != null ){
419 | c.calcLength = getOptVal( length, edge );
420 | }
421 |
422 | return c;
423 | }) );
424 |
425 | adaptor.size([ bb.w, bb.h ]);
426 |
427 | if( length != null ){
428 | adaptor[ lengthFnName ]( lengthGetter );
429 | }
430 |
431 | // set the flow of cola
432 | if( options.flow ){
433 | let flow;
434 | let defAxis = 'y';
435 | let defMinSep = 50;
436 |
437 | if( isString(options.flow) ){
438 | flow = {
439 | axis: options.flow,
440 | minSeparation: defMinSep
441 | };
442 | } else if( isNumber(options.flow) ){
443 | flow = {
444 | axis: defAxis,
445 | minSeparation: options.flow
446 | };
447 | } else if( isObject(options.flow) ){
448 | flow = options.flow;
449 |
450 | flow.axis = flow.axis || defAxis;
451 | flow.minSeparation = flow.minSeparation != null ? flow.minSeparation : defMinSep;
452 | } else { // e.g. options.flow: true
453 | flow = {
454 | axis: defAxis,
455 | minSeparation: defMinSep
456 | };
457 | }
458 |
459 | adaptor.flowLayout( flow.axis , flow.minSeparation );
460 | }
461 |
462 | layout.trigger({ type: 'layoutstart', layout: layout });
463 |
464 | adaptor
465 | .avoidOverlaps( options.avoidOverlap )
466 | .handleDisconnected( options.handleDisconnected )
467 | .start(
468 | options.unconstrIter,
469 | options.userConstIter,
470 | options.allConstIter,
471 | undefined, // gridSnapIterations = 0
472 | undefined, // keepRunning = true
473 | options.centerGraph
474 | )
475 | ;
476 |
477 | if( !options.infinite ){
478 | setTimeout(function(){
479 | if( !layout.manuallyStopped ){
480 | adaptor.stop();
481 | }
482 | }, options.maxSimulationTime);
483 | }
484 |
485 | return this; // chaining
486 | };
487 |
488 | // called on continuous layouts to stop them before they finish
489 | ColaLayout.prototype.stop = function(){
490 | if( this.adaptor ){
491 | this.manuallyStopped = true;
492 | this.adaptor.stop();
493 | }
494 |
495 | return this; // chaining
496 | };
497 |
498 | module.exports = ColaLayout;
499 |
--------------------------------------------------------------------------------
/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | cytoscape-cola.js demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
38 |
39 |
709 |
710 |
711 |
712 | cytoscape-cola demo
713 |
714 |
715 |
716 |
717 |
718 |
719 |
--------------------------------------------------------------------------------
/demo-non-animated.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | cytoscape-cola.js demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
38 |
39 |
711 |
712 |
713 |
714 | cytoscape-cola demo
715 |
716 |
717 |
718 |
719 |
720 |
721 |
--------------------------------------------------------------------------------
/cytoscape-cola.js:
--------------------------------------------------------------------------------
1 | (function webpackUniversalModuleDefinition(root, factory) {
2 | if(typeof exports === 'object' && typeof module === 'object')
3 | module.exports = factory(require("webcola"));
4 | else if(typeof define === 'function' && define.amd)
5 | define(["webcola"], factory);
6 | else if(typeof exports === 'object')
7 | exports["cytoscapeCola"] = factory(require("webcola"));
8 | else
9 | root["cytoscapeCola"] = factory(root["webcola"]);
10 | })(this, function(__WEBPACK_EXTERNAL_MODULE_5__) {
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 | /******/ // identity function for calling harmony imports with the correct context
47 | /******/ __webpack_require__.i = function(value) { return value; };
48 | /******/
49 | /******/ // define getter function for harmony exports
50 | /******/ __webpack_require__.d = function(exports, name, getter) {
51 | /******/ if(!__webpack_require__.o(exports, name)) {
52 | /******/ Object.defineProperty(exports, name, {
53 | /******/ configurable: false,
54 | /******/ enumerable: true,
55 | /******/ get: getter
56 | /******/ });
57 | /******/ }
58 | /******/ };
59 | /******/
60 | /******/ // getDefaultExport function for compatibility with non-harmony modules
61 | /******/ __webpack_require__.n = function(module) {
62 | /******/ var getter = module && module.__esModule ?
63 | /******/ function getDefault() { return module['default']; } :
64 | /******/ function getModuleExports() { return module; };
65 | /******/ __webpack_require__.d(getter, 'a', getter);
66 | /******/ return getter;
67 | /******/ };
68 | /******/
69 | /******/ // Object.prototype.hasOwnProperty.call
70 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
71 | /******/
72 | /******/ // __webpack_public_path__
73 | /******/ __webpack_require__.p = "";
74 | /******/
75 | /******/ // Load entry module and return exports
76 | /******/ return __webpack_require__(__webpack_require__.s = 3);
77 | /******/ })
78 | /************************************************************************/
79 | /******/ ([
80 | /* 0 */
81 | /***/ (function(module, exports, __webpack_require__) {
82 |
83 | "use strict";
84 |
85 |
86 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
87 |
88 | var assign = __webpack_require__(1);
89 | var defaults = __webpack_require__(2);
90 | var cola = __webpack_require__(5) || (typeof window !== 'undefined' ? window.cola : null);
91 | var raf = __webpack_require__(4);
92 | var isString = function isString(o) {
93 | return (typeof o === 'undefined' ? 'undefined' : _typeof(o)) === _typeof('');
94 | };
95 | var isNumber = function isNumber(o) {
96 | return (typeof o === 'undefined' ? 'undefined' : _typeof(o)) === _typeof(0);
97 | };
98 | var isObject = function isObject(o) {
99 | return o != null && (typeof o === 'undefined' ? 'undefined' : _typeof(o)) === _typeof({});
100 | };
101 | var isFunction = function isFunction(o) {
102 | return o != null && (typeof o === 'undefined' ? 'undefined' : _typeof(o)) === _typeof(function () {});
103 | };
104 | var nop = function nop() {};
105 |
106 | var getOptVal = function getOptVal(val, ele) {
107 | if (isFunction(val)) {
108 | var fn = val;
109 | return fn.apply(ele, [ele]);
110 | } else {
111 | return val;
112 | }
113 | };
114 |
115 | // constructor
116 | // options : object containing layout options
117 | function ColaLayout(options) {
118 | this.options = assign({}, defaults, options);
119 | }
120 |
121 | // runs the layout
122 | ColaLayout.prototype.run = function () {
123 | var layout = this;
124 | var options = this.options;
125 |
126 | layout.manuallyStopped = false;
127 |
128 | var cy = options.cy; // cy is automatically populated for us in the constructor
129 | var eles = options.eles;
130 | var nodes = eles.nodes();
131 | var edges = eles.edges();
132 | var ready = false;
133 |
134 | var isParent = function isParent(ele) {
135 | return ele.isParent();
136 | };
137 |
138 | var parentNodes = nodes.filter(isParent);
139 |
140 | var nonparentNodes = nodes.subtract(parentNodes);
141 |
142 | var bb = options.boundingBox || { x1: 0, y1: 0, w: cy.width(), h: cy.height() };
143 | if (bb.x2 === undefined) {
144 | bb.x2 = bb.x1 + bb.w;
145 | }
146 | if (bb.w === undefined) {
147 | bb.w = bb.x2 - bb.x1;
148 | }
149 | if (bb.y2 === undefined) {
150 | bb.y2 = bb.y1 + bb.h;
151 | }
152 | if (bb.h === undefined) {
153 | bb.h = bb.y2 - bb.y1;
154 | }
155 |
156 | var updateNodePositions = function updateNodePositions() {
157 | for (var i = 0; i < nodes.length; i++) {
158 | var node = nodes[i];
159 | var dimensions = node.layoutDimensions(options);
160 | var scratch = node.scratch('cola');
161 |
162 | // update node dims
163 | if (!scratch.updatedDims) {
164 | var padding = getOptVal(options.nodeSpacing, node);
165 |
166 | scratch.width = dimensions.w + 2 * padding;
167 | scratch.height = dimensions.h + 2 * padding;
168 | }
169 | }
170 |
171 | nodes.positions(function (node) {
172 | var scratch = node.scratch().cola;
173 | var retPos = void 0;
174 |
175 | if (!node.grabbed() && nonparentNodes.contains(node)) {
176 | retPos = {
177 | x: bb.x1 + scratch.x,
178 | y: bb.y1 + scratch.y
179 | };
180 |
181 | if (!isNumber(retPos.x) || !isNumber(retPos.y)) {
182 | retPos = undefined;
183 | }
184 | }
185 |
186 | return retPos;
187 | });
188 |
189 | nodes.updateCompoundBounds(); // because the way this layout sets positions is buggy for some reason; ref #878
190 |
191 | if (!ready) {
192 | onReady();
193 | ready = true;
194 | }
195 |
196 | if (options.fit) {
197 | cy.fit(options.padding);
198 | }
199 | };
200 |
201 | var onDone = function onDone() {
202 | if (options.ungrabifyWhileSimulating) {
203 | grabbableNodes.grabify();
204 | }
205 |
206 | cy.off('destroy', destroyHandler);
207 |
208 | nodes.off('grab free position', grabHandler);
209 | nodes.off('lock unlock', lockHandler);
210 |
211 | // trigger layoutstop when the layout stops (e.g. finishes)
212 | layout.one('layoutstop', options.stop);
213 | layout.trigger({ type: 'layoutstop', layout: layout });
214 | };
215 |
216 | var onReady = function onReady() {
217 | // trigger layoutready when each node has had its position set at least once
218 | layout.one('layoutready', options.ready);
219 | layout.trigger({ type: 'layoutready', layout: layout });
220 | };
221 |
222 | var ticksPerFrame = options.refresh;
223 |
224 | if (options.refresh < 0) {
225 | ticksPerFrame = 1;
226 | } else {
227 | ticksPerFrame = Math.max(1, ticksPerFrame); // at least 1
228 | }
229 |
230 | var adaptor = layout.adaptor = cola.adaptor({
231 | trigger: function trigger(e) {
232 | // on sim event
233 | var TICK = cola.EventType ? cola.EventType.tick : null;
234 | var END = cola.EventType ? cola.EventType.end : null;
235 |
236 | switch (e.type) {
237 | case 'tick':
238 | case TICK:
239 | if (options.animate) {
240 | updateNodePositions();
241 | }
242 | break;
243 |
244 | case 'end':
245 | case END:
246 | updateNodePositions();
247 | if (!options.infinite) {
248 | onDone();
249 | }
250 | break;
251 | }
252 | },
253 |
254 | kick: function kick() {
255 | // kick off the simulation
256 | //let skip = 0;
257 |
258 | var firstTick = true;
259 |
260 | var inftick = function inftick() {
261 | if (layout.manuallyStopped) {
262 | onDone();
263 |
264 | return true;
265 | }
266 |
267 | var ret = adaptor.tick();
268 |
269 | if (!options.infinite && !firstTick) {
270 | adaptor.convergenceThreshold(options.convergenceThreshold);
271 | }
272 |
273 | firstTick = false;
274 |
275 | if (ret && options.infinite) {
276 | // resume layout if done
277 | adaptor.resume(); // resume => new kick
278 | }
279 |
280 | return ret; // allow regular finish b/c of new kick
281 | };
282 |
283 | var multitick = function multitick() {
284 | // multiple ticks in a row
285 | var ret = void 0;
286 |
287 | for (var i = 0; i < ticksPerFrame && !ret; i++) {
288 | ret = ret || inftick(); // pick up true ret vals => sim done
289 | }
290 |
291 | return ret;
292 | };
293 |
294 | if (options.animate) {
295 | var frame = function frame() {
296 | if (multitick()) {
297 | return;
298 | }
299 |
300 | raf(frame);
301 | };
302 |
303 | raf(frame);
304 | } else {
305 | while (!inftick()) {
306 | // keep going...
307 | }
308 | }
309 | },
310 |
311 | on: nop, // dummy; not needed
312 |
313 | drag: nop // not needed for our case
314 | });
315 | layout.adaptor = adaptor;
316 |
317 | // if set no grabbing during layout
318 | var grabbableNodes = nodes.filter(':grabbable');
319 | if (options.ungrabifyWhileSimulating) {
320 | grabbableNodes.ungrabify();
321 | }
322 |
323 | var destroyHandler = void 0;
324 | cy.one('destroy', destroyHandler = function destroyHandler() {
325 | layout.stop();
326 | });
327 |
328 | // handle node dragging
329 | var grabHandler = void 0;
330 | nodes.on('grab free position', grabHandler = function grabHandler(e) {
331 | var node = this;
332 | var scrCola = node.scratch().cola;
333 | var pos = node.position();
334 | var nodeIsTarget = e.cyTarget === node || e.target === node;
335 |
336 | if (!nodeIsTarget) {
337 | return;
338 | }
339 |
340 | switch (e.type) {
341 | case 'grab':
342 | adaptor.dragstart(scrCola);
343 | break;
344 | case 'free':
345 | adaptor.dragend(scrCola);
346 | break;
347 | case 'position':
348 | // only update when different (i.e. manual .position() call or drag) so we don't loop needlessly
349 | if (scrCola.px !== pos.x - bb.x1 || scrCola.py !== pos.y - bb.y1) {
350 | scrCola.px = pos.x - bb.x1;
351 | scrCola.py = pos.y - bb.y1;
352 | }
353 | break;
354 | }
355 | });
356 |
357 | var lockHandler = void 0;
358 | nodes.on('lock unlock', lockHandler = function lockHandler() {
359 | var node = this;
360 | var scrCola = node.scratch().cola;
361 |
362 | scrCola.fixed = node.locked();
363 |
364 | if (node.locked()) {
365 | adaptor.dragstart(scrCola);
366 | } else {
367 | adaptor.dragend(scrCola);
368 | }
369 | });
370 |
371 | // add nodes to cola
372 | adaptor.nodes(nonparentNodes.map(function (node, i) {
373 | var padding = getOptVal(options.nodeSpacing, node);
374 | var pos = node.position();
375 | var dimensions = node.layoutDimensions(options);
376 |
377 | var struct = node.scratch().cola = {
378 | x: options.randomize && !node.locked() || pos.x === undefined ? Math.round(Math.random() * bb.w) : pos.x,
379 | y: options.randomize && !node.locked() || pos.y === undefined ? Math.round(Math.random() * bb.h) : pos.y,
380 | width: dimensions.w + 2 * padding,
381 | height: dimensions.h + 2 * padding,
382 | index: i,
383 | fixed: node.locked()
384 | };
385 |
386 | return struct;
387 | }));
388 |
389 | // the constraints to be added on nodes
390 | var constraints = [];
391 |
392 | if (options.alignment) {
393 | // then set alignment constraints
394 |
395 | if (options.alignment.vertical) {
396 | var verticalAlignments = options.alignment.vertical;
397 | verticalAlignments.forEach(function (alignment) {
398 | var offsetsX = [];
399 | alignment.forEach(function (nodeData) {
400 | var node = nodeData.node;
401 | var scrCola = node.scratch().cola;
402 | var index = scrCola.index;
403 | offsetsX.push({
404 | node: index,
405 | offset: nodeData.offset ? nodeData.offset : 0
406 | });
407 | });
408 | constraints.push({
409 | type: 'alignment',
410 | axis: 'x',
411 | offsets: offsetsX
412 | });
413 | });
414 | }
415 |
416 | if (options.alignment.horizontal) {
417 | var horizontalAlignments = options.alignment.horizontal;
418 | horizontalAlignments.forEach(function (alignment) {
419 | var offsetsY = [];
420 | alignment.forEach(function (nodeData) {
421 | var node = nodeData.node;
422 | var scrCola = node.scratch().cola;
423 | var index = scrCola.index;
424 | offsetsY.push({
425 | node: index,
426 | offset: nodeData.offset ? nodeData.offset : 0
427 | });
428 | });
429 | constraints.push({
430 | type: 'alignment',
431 | axis: 'y',
432 | offsets: offsetsY
433 | });
434 | });
435 | }
436 | }
437 |
438 | // if gapInequalities variable is set add each inequality constraint to list of constraints
439 | if (options.gapInequalities) {
440 | options.gapInequalities.forEach(function (inequality) {
441 |
442 | // for the constraints to be passed to cola layout adaptor use indices of nodes,
443 | // not the nodes themselves
444 | var leftIndex = inequality.left.scratch().cola.index;
445 | var rightIndex = inequality.right.scratch().cola.index;
446 |
447 | constraints.push({
448 | axis: inequality.axis,
449 | left: leftIndex,
450 | right: rightIndex,
451 | gap: inequality.gap,
452 | equality: inequality.equality
453 | });
454 | });
455 | }
456 |
457 | // add constraints if any
458 | if (constraints.length > 0) {
459 | adaptor.constraints(constraints);
460 | }
461 |
462 | // add compound nodes to cola
463 | adaptor.groups(parentNodes.map(function (node, i) {
464 | // add basic group incl leaf nodes
465 | var optPadding = getOptVal(options.nodeSpacing, node);
466 | var getPadding = function getPadding(d) {
467 | return parseFloat(node.style('padding-' + d));
468 | };
469 |
470 | var pleft = getPadding('left') + optPadding;
471 | var pright = getPadding('right') + optPadding;
472 | var ptop = getPadding('top') + optPadding;
473 | var pbottom = getPadding('bottom') + optPadding;
474 |
475 | node.scratch().cola = {
476 | index: i,
477 |
478 | padding: Math.max(pleft, pright, ptop, pbottom),
479 |
480 | // leaves should only contain direct descendants (children),
481 | // not the leaves of nested compound nodes or any nodes that are compounds themselves
482 | leaves: node.children().intersection(nonparentNodes).map(function (child) {
483 | return child[0].scratch().cola.index;
484 | }),
485 |
486 | fixed: node.locked()
487 | };
488 |
489 | return node;
490 | }).map(function (node) {
491 | // add subgroups
492 | node.scratch().cola.groups = node.children().intersection(parentNodes).map(function (child) {
493 | return child.scratch().cola.index;
494 | });
495 |
496 | return node.scratch().cola;
497 | }));
498 |
499 | // get the edge length setting mechanism
500 | var length = void 0;
501 | var lengthFnName = void 0;
502 | if (options.edgeLength != null) {
503 | length = options.edgeLength;
504 | lengthFnName = 'linkDistance';
505 | } else if (options.edgeSymDiffLength != null) {
506 | length = options.edgeSymDiffLength;
507 | lengthFnName = 'symmetricDiffLinkLengths';
508 | } else if (options.edgeJaccardLength != null) {
509 | length = options.edgeJaccardLength;
510 | lengthFnName = 'jaccardLinkLengths';
511 | } else {
512 | length = 100;
513 | lengthFnName = 'linkDistance';
514 | }
515 |
516 | var lengthGetter = function lengthGetter(link) {
517 | return link.calcLength;
518 | };
519 |
520 | // add the edges to cola
521 | adaptor.links(edges.stdFilter(function (edge) {
522 | return nonparentNodes.contains(edge.source()) && nonparentNodes.contains(edge.target());
523 | }).map(function (edge) {
524 | var c = edge.scratch().cola = {
525 | source: edge.source()[0].scratch().cola.index,
526 | target: edge.target()[0].scratch().cola.index
527 | };
528 |
529 | if (length != null) {
530 | c.calcLength = getOptVal(length, edge);
531 | }
532 |
533 | return c;
534 | }));
535 |
536 | adaptor.size([bb.w, bb.h]);
537 |
538 | if (length != null) {
539 | adaptor[lengthFnName](lengthGetter);
540 | }
541 |
542 | // set the flow of cola
543 | if (options.flow) {
544 | var flow = void 0;
545 | var defAxis = 'y';
546 | var defMinSep = 50;
547 |
548 | if (isString(options.flow)) {
549 | flow = {
550 | axis: options.flow,
551 | minSeparation: defMinSep
552 | };
553 | } else if (isNumber(options.flow)) {
554 | flow = {
555 | axis: defAxis,
556 | minSeparation: options.flow
557 | };
558 | } else if (isObject(options.flow)) {
559 | flow = options.flow;
560 |
561 | flow.axis = flow.axis || defAxis;
562 | flow.minSeparation = flow.minSeparation != null ? flow.minSeparation : defMinSep;
563 | } else {
564 | // e.g. options.flow: true
565 | flow = {
566 | axis: defAxis,
567 | minSeparation: defMinSep
568 | };
569 | }
570 |
571 | adaptor.flowLayout(flow.axis, flow.minSeparation);
572 | }
573 |
574 | layout.trigger({ type: 'layoutstart', layout: layout });
575 |
576 | adaptor.avoidOverlaps(options.avoidOverlap).handleDisconnected(options.handleDisconnected).start(options.unconstrIter, options.userConstIter, options.allConstIter, undefined, // gridSnapIterations = 0
577 | undefined, // keepRunning = true
578 | options.centerGraph);
579 |
580 | if (!options.infinite) {
581 | setTimeout(function () {
582 | if (!layout.manuallyStopped) {
583 | adaptor.stop();
584 | }
585 | }, options.maxSimulationTime);
586 | }
587 |
588 | return this; // chaining
589 | };
590 |
591 | // called on continuous layouts to stop them before they finish
592 | ColaLayout.prototype.stop = function () {
593 | if (this.adaptor) {
594 | this.manuallyStopped = true;
595 | this.adaptor.stop();
596 | }
597 |
598 | return this; // chaining
599 | };
600 |
601 | module.exports = ColaLayout;
602 |
603 | /***/ }),
604 | /* 1 */
605 | /***/ (function(module, exports, __webpack_require__) {
606 |
607 | "use strict";
608 |
609 |
610 | // Simple, internal Object.assign() polyfill for options objects etc.
611 |
612 | module.exports = Object.assign != null ? Object.assign.bind(Object) : function (tgt) {
613 | for (var _len = arguments.length, srcs = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
614 | srcs[_key - 1] = arguments[_key];
615 | }
616 |
617 | srcs.filter(function (src) {
618 | return src != null;
619 | }).forEach(function (src) {
620 | Object.keys(src).forEach(function (k) {
621 | return tgt[k] = src[k];
622 | });
623 | });
624 |
625 | return tgt;
626 | };
627 |
628 | /***/ }),
629 | /* 2 */
630 | /***/ (function(module, exports, __webpack_require__) {
631 |
632 | "use strict";
633 |
634 |
635 | // default layout options
636 | var defaults = {
637 | animate: true, // whether to show the layout as it's running
638 | refresh: 1, // number of ticks per frame; higher is faster but more jerky
639 | maxSimulationTime: 4000, // max length in ms to run the layout
640 | ungrabifyWhileSimulating: false, // so you can't drag nodes during layout
641 | fit: true, // on every layout reposition of nodes, fit the viewport
642 | padding: 30, // padding around the simulation
643 | boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
644 | nodeDimensionsIncludeLabels: false, // whether labels should be included in determining the space used by a node
645 |
646 | // layout event callbacks
647 | ready: function ready() {}, // on layoutready
648 | stop: function stop() {}, // on layoutstop
649 |
650 | // positioning options
651 | randomize: false, // use random node positions at beginning of layout
652 | avoidOverlap: true, // if true, prevents overlap of node bounding boxes
653 | handleDisconnected: true, // if true, avoids disconnected components from overlapping
654 | convergenceThreshold: 0.01, // when the alpha value (system energy) falls below this value, the layout stops
655 | nodeSpacing: function nodeSpacing(node) {
656 | return 10;
657 | }, // extra spacing around nodes
658 | flow: undefined, // use DAG/tree flow layout if specified, e.g. { axis: 'y', minSeparation: 30 }
659 | alignment: undefined, // relative alignment constraints on nodes, e.g. function( node ){ return { x: 0, y: 1 } }
660 | gapInequalities: undefined, // list of inequality constraints for the gap between the nodes, e.g. [{"axis":"y", "left":node1, "right":node2, "gap":25}]
661 | centerGraph: true, // adjusts the node positions initially to center the graph (pass false if you want to start the layout from the current position)
662 |
663 |
664 | // different methods of specifying edge length
665 | // each can be a constant numerical value or a function like `function( edge ){ return 2; }`
666 | edgeLength: undefined, // sets edge length directly in simulation
667 | edgeSymDiffLength: undefined, // symmetric diff edge length in simulation
668 | edgeJaccardLength: undefined, // jaccard edge length in simulation
669 |
670 | // iterations of cola algorithm; uses default values on undefined
671 | unconstrIter: undefined, // unconstrained initial layout iterations
672 | userConstIter: undefined, // initial layout iterations with user-specified constraints
673 | allConstIter: undefined, // initial layout iterations with all constraints including non-overlap
674 |
675 | // infinite layout options
676 | infinite: false // overrides all other options for a forces-all-the-time mode
677 | };
678 |
679 | module.exports = defaults;
680 |
681 | /***/ }),
682 | /* 3 */
683 | /***/ (function(module, exports, __webpack_require__) {
684 |
685 | "use strict";
686 |
687 |
688 | var impl = __webpack_require__(0);
689 |
690 | // registers the extension on a cytoscape lib ref
691 | var register = function register(cytoscape) {
692 | if (!cytoscape) {
693 | return;
694 | } // can't register if cytoscape unspecified
695 |
696 | cytoscape('layout', 'cola', impl); // register with cytoscape.js
697 | };
698 |
699 | if (typeof cytoscape !== 'undefined') {
700 | // expose to global cytoscape (i.e. window.cytoscape)
701 | register(cytoscape);
702 | }
703 |
704 | module.exports = register;
705 |
706 | /***/ }),
707 | /* 4 */
708 | /***/ (function(module, exports, __webpack_require__) {
709 |
710 | "use strict";
711 |
712 |
713 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
714 |
715 | var raf = void 0;
716 |
717 | if ((typeof window === "undefined" ? "undefined" : _typeof(window)) !== ( true ? "undefined" : _typeof(undefined))) {
718 | raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || function (fn) {
719 | return setTimeout(fn, 16);
720 | };
721 | } else {
722 | // if not available, all you get is immediate calls
723 | raf = function raf(cb) {
724 | cb();
725 | };
726 | }
727 |
728 | module.exports = raf;
729 |
730 | /***/ }),
731 | /* 5 */
732 | /***/ (function(module, exports) {
733 |
734 | module.exports = __WEBPACK_EXTERNAL_MODULE_5__;
735 |
736 | /***/ })
737 | /******/ ]);
738 | });
--------------------------------------------------------------------------------