├── .babelrc
├── .eslintignore
├── .eslintrc
├── .gitignore
├── CITATION.cff
├── LICENSE
├── README.md
├── bower.json
├── cytoscape-fcose.js
├── demo
├── demo-compound.html
├── demo-constraint-control.js
├── demo-constraint.html
├── demo.gif
├── demo.html
├── incrementalConstraints.gif
└── samples
│ ├── callGraph.js
│ ├── callGraph_constraints.js
│ ├── chalk.js
│ ├── chalk_constraints.js
│ ├── unix.js
│ ├── unix_constraints.js
│ ├── uwsn.js
│ ├── uwsn_constraints.js
│ ├── wsn.js
│ └── wsn_constraints.js
├── package-lock.json
├── package.json
├── src
├── assign.js
├── fcose
│ ├── auxiliary.js
│ ├── cose.js
│ ├── index.js
│ └── spectral.js
└── index.js
├── test
└── example.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env"]
3 | }
4 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | /nbproject/*
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/CITATION.cff:
--------------------------------------------------------------------------------
1 | cff-version: 1.2.0
2 | message: "If you use this software, please cite it as below."
3 | authors:
4 | - family-names: "Balci"
5 | given-names: "Hasan"
6 | orcid: "https://orcid.org/0000-0001-8319-7758"
7 | - family-names: "Dogrusoz"
8 | given-names: "Ugur"
9 | orcid: "https://orcid.org/0000-0002-7153-0784"
10 | title: "cytoscape.js-fcose"
11 | version: 2.1.0
12 | date-released: 2021-06-25
13 | url: "https://github.com/iVis-at-Bilkent/cytoscape.js-fcose"
14 | preferred-citation:
15 | type: article
16 | authors:
17 | - family-names: "Balci"
18 | given-names: "Hasan"
19 | orcid: "https://orcid.org/0000-0001-8319-7758"
20 | - family-names: "Dogrusoz"
21 | given-names: "Ugur"
22 | orcid: "https://orcid.org/0000-0002-7153-0784"
23 | doi: "10.1109/TVCG.2021.3095303"
24 | journal: "IEEE Transactions on Visualization and Computer Graphics"
25 | title: "fCoSE: A Fast Compound Graph Layout Algorithm with Constraint Support"
26 | issue: 12
27 | volume: 28
28 | start: 4582 # First page number
29 | end: 4593 # Last page number
30 | month: 12
31 | year: 2022
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018 - present, iVis-at-Bilkent.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the “Software”), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | cytoscape-fcose
2 | ================================================================================
3 |
4 |
5 | ## Description
6 |
7 |
8 | fCoSE (pron. "f-cosay", **f**ast **Co**mpound **S**pring **E**mbedder), is a faster version of our earlier compound spring embedder algorithm named [CoSE](https://github.com/cytoscape/cytoscape.js-cose-bilkent), implemented as a Cytoscape.js extension by [i-Vis Lab](http://cs.bilkent.edu.tr/~ivis/) in Bilkent University.
9 |
10 | Here are some demos: **simple**, **compound**, and **constraints**, respectively:
11 |
12 |
13 |
14 |
15 |
16 |
17 | fCoSE layout algorithm combines the speed of spectral layout with the aesthetics of force-directed layout. fCoSE runs up to 2 times as fast as CoSE while achieving similar aesthetics.
18 |
19 |
20 |
21 | Furthermore, fCoSE also supports a fairly rich set of constraint types (i.e., fixed position, vertical/horizontal alignment and relative placement).
22 |
23 |
24 |
25 | You can see constraint support in action in the following videos: [fixed node](https://youtu.be/vRZVlwntzGY), [alignment](https://youtu.be/O5rddJ7DteU), [relative placement](https://youtu.be/Xcm87bT50RA), [hybrid](https://youtu.be/KRAQHmnTvUA), [real life graphs](https://youtu.be/vTPy9G2ALcI). Constraints can also be added [incrementally](https://youtu.be/DTm2WmzwP4k) on a given layout.
26 |
27 | Please cite the following when you use this layout:
28 |
29 | H. Balci and U. Dogrusoz, "[fCoSE: A Fast Compound Graph Layout Algorithm with Constraint Support](https://doi.org/10.1109/TVCG.2021.3095303)," in IEEE Transactions on Visualization and Computer Graphics, 28(12), pp. 4582-4593, 2022.
30 |
31 | U. Dogrusoz, E. Giral, A. Cetintas, A. Civril and E. Demir, "[A Layout Algorithm For Undirected Compound Graphs](http://www.sciencedirect.com/science/article/pii/S0020025508004799)", Information Sciences, 179, pp. 980-994, 2009.
32 |
33 | ## Dependencies
34 |
35 | * Cytoscape.js ^3.2.0
36 | * cose-base ^2.0.0
37 | * cytoscape-layout-utilities.js (optional for packing disconnected components) ^1.0.0
38 |
39 | ## Documentation
40 |
41 | fCoSE supports user-defined placement constraints as well as its full support for compound graphs. These constraints may be defined for simple nodes. Supported constraint types are:
42 |
43 | * **Fixed node constraint:** The user may provide *exact* desired positions for a set of nodes called *fixed nodes*. For example, in order to position node *n1* to *(x: 100, y: 200)* and node *n2* to *(x: 200, y: -300)* as a result of the layout, ```fixedNodeConstraint``` option should be set as follows:
44 |
45 | ```js
46 | fixedNodeConstraint: [{nodeId: 'n1', position: {x: 100, y: 200}},
47 | {nodeId: 'n2', position: {x: 200, y: -300}}],
48 | ```
49 |
50 | * **Alignment constraint:** This constraint aims to align two or more nodes (with respect to their centers) vertically or horizontally. For example, for the vertical alignment of nodes {*n1, n2, n3*} and {*n4, n5*}, and horizontal alignment of nodes {*n2, n4*} as a result of the layout, ```alignmentConstraint``` option should be set as follows:
51 | ```js
52 | alignmentConstraint: {vertical: [['n1', 'n2', 'n3'], ['n4', 'n5']], horizontal: [['n2', 'n4']]},
53 | ```
54 | ***Note:** Alignment constraints in a direction must be given in most compact form. Example: ```['n1', 'n2', 'n3']``` instead of ```['n1', 'n2'], ['n1', 'n3']```.*
55 |
56 | * **Relative placement constraint:** The user may constrain the position of a node relative to another node in either vertical or horizontal direction. For example, in order to position node *n1* to be above of node *n2* by at least 100 pixels and position node *n3* to be on the left of node *n4* by at least 75 pixels as a result of the layout, ```relativePlacementConstraint``` option should be set as follows:
57 |
58 | ```js
59 | relativePlacementConstraint: [{top: 'n1', bottom: 'n2', gap: 100},
60 | {left: 'n3', right: 'n4', gap: 75}],
61 | ```
62 | The `gap` property is optional. If it is omitted, average `idealEdgeLength` is used as the gap value.
63 |
64 | ## Usage instructions
65 |
66 | Download the library:
67 | * via npm: `npm install cytoscape-fcose`,
68 | * via bower: `bower install cytoscape-fcose`, or
69 | * via direct download in the repository (probably from a tag).
70 |
71 | Import the library as appropriate for your project:
72 |
73 | ES import:
74 |
75 | ```js
76 | import cytoscape from 'cytoscape';
77 | import fcose from 'cytoscape-fcose';
78 |
79 | cytoscape.use( fcose );
80 | ```
81 |
82 | CommonJS require:
83 |
84 | ```js
85 | let cytoscape = require('cytoscape');
86 | let fcose = require('cytoscape-fcose');
87 |
88 | cytoscape.use( fcose ); // register extension
89 | ```
90 |
91 | AMD:
92 |
93 | ```js
94 | require(['cytoscape', 'cytoscape-fcose'], function( cytoscape, fcose ){
95 | fcose( cytoscape ); // register extension
96 | });
97 | ```
98 |
99 | Plain HTML/JS has the extension registered for you automatically, because no `require()` is needed. Just add the following files:
100 |
101 | ```
102 |
103 |
104 |
105 | ```
106 |
107 |
108 | ## API
109 |
110 | When calling the layout, e.g. `cy.layout({ name: 'fcose', ... })`, the following options are supported:
111 |
112 | ```js
113 | var defaultOptions = {
114 |
115 | // 'draft', 'default' or 'proof'
116 | // - "draft" only applies spectral layout
117 | // - "default" improves the quality with incremental layout (fast cooling rate)
118 | // - "proof" improves the quality with incremental layout (slow cooling rate)
119 | quality: "default",
120 | // Use random node positions at beginning of layout
121 | // if this is set to false, then quality option must be "proof"
122 | randomize: true,
123 | // Whether or not to animate the layout
124 | animate: true,
125 | // Duration of animation in ms, if enabled
126 | animationDuration: 1000,
127 | // Easing of animation, if enabled
128 | animationEasing: undefined,
129 | // Fit the viewport to the repositioned nodes
130 | fit: true,
131 | // Padding around layout
132 | padding: 30,
133 | // Whether to include labels in node dimensions. Valid in "proof" quality
134 | nodeDimensionsIncludeLabels: false,
135 | // Whether or not simple nodes (non-compound nodes) are of uniform dimensions
136 | uniformNodeDimensions: false,
137 | // Whether to pack disconnected components - cytoscape-layout-utilities extension should be registered and initialized
138 | packComponents: true,
139 | // Layout step - all, transformed, enforced, cose - for debug purpose only
140 | step: "all",
141 |
142 | /* spectral layout options */
143 |
144 | // False for random, true for greedy sampling
145 | samplingType: true,
146 | // Sample size to construct distance matrix
147 | sampleSize: 25,
148 | // Separation amount between nodes
149 | nodeSeparation: 75,
150 | // Power iteration tolerance
151 | piTol: 0.0000001,
152 |
153 | /* incremental layout options */
154 |
155 | // Node repulsion (non overlapping) multiplier
156 | nodeRepulsion: node => 4500,
157 | // Ideal edge (non nested) length
158 | idealEdgeLength: edge => 50,
159 | // Divisor to compute edge forces
160 | edgeElasticity: edge => 0.45,
161 | // Nesting factor (multiplier) to compute ideal edge length for nested edges
162 | nestingFactor: 0.1,
163 | // Maximum number of iterations to perform - this is a suggested value and might be adjusted by the algorithm as required
164 | numIter: 2500,
165 | // For enabling tiling
166 | tile: true,
167 | // The comparison function to be used while sorting nodes during tiling operation.
168 | // Takes the ids of 2 nodes that will be compared as a parameter and the default tiling operation is performed when this option is not set.
169 | // It works similar to ``compareFunction`` parameter of ``Array.prototype.sort()``
170 | // If node1 is less then node2 by some ordering criterion ``tilingCompareBy(nodeId1, nodeId2)`` must return a negative value
171 | // If node1 is greater then node2 by some ordering criterion ``tilingCompareBy(nodeId1, nodeId2)`` must return a positive value
172 | // If node1 is equal to node2 by some ordering criterion ``tilingCompareBy(nodeId1, nodeId2)`` must return 0
173 | tilingCompareBy: undefined,
174 | // Represents the amount of the vertical space to put between the zero degree members during the tiling operation(can also be a function)
175 | tilingPaddingVertical: 10,
176 | // Represents the amount of the horizontal space to put between the zero degree members during the tiling operation(can also be a function)
177 | tilingPaddingHorizontal: 10,
178 | // Gravity force (constant)
179 | gravity: 0.25,
180 | // Gravity range (constant) for compounds
181 | gravityRangeCompound: 1.5,
182 | // Gravity force (constant) for compounds
183 | gravityCompound: 1.0,
184 | // Gravity range (constant)
185 | gravityRange: 3.8,
186 | // Initial cooling factor for incremental layout
187 | initialEnergyOnIncremental: 0.3,
188 |
189 | /* constraint options */
190 |
191 | // Fix desired nodes to predefined positions
192 | // [{nodeId: 'n1', position: {x: 100, y: 200}}, {...}]
193 | fixedNodeConstraint: undefined,
194 | // Align desired nodes in vertical/horizontal direction
195 | // {vertical: [['n1', 'n2'], [...]], horizontal: [['n2', 'n4'], [...]]}
196 | alignmentConstraint: undefined,
197 | // Place two nodes relatively in vertical/horizontal direction
198 | // [{top: 'n1', bottom: 'n2', gap: 100}, {left: 'n3', right: 'n4', gap: 75}, {...}]
199 | relativePlacementConstraint: undefined,
200 |
201 | /* layout event callbacks */
202 | ready: () => {}, // on layoutready
203 | stop: () => {} // on layoutstop
204 | };
205 | ```
206 | To be able to use `packComponents` option, `cytoscape-layout-utilities` extension should also be registered in the application.
207 | Packing related [options](https://github.com/iVis-at-Bilkent/cytoscape.js-layout-utilities#default-options) should be set via `cytoscape-layout-utilities` extension.
208 | If they are not set, fCoSE uses default options.
209 |
210 |
211 | ## Build targets
212 |
213 | * `npm run test` : Run Mocha tests in `./test`
214 | * `npm run build` : Build `./src/**` into `cytoscape-fcose.js`
215 | * `npm run watch` : Automatically build on changes with live reloading (N.b. you must already have an HTTP server running)
216 | * `npm run dev` : Automatically build on changes with live reloading with webpack dev server
217 | * `npm run lint` : Run eslint on the source
218 |
219 | N.b. all builds use babel, so modern ES features can be used in the `src`.
220 |
221 |
222 | ## Publishing instructions
223 |
224 | This project is set up to automatically be published to npm and bower. To publish:
225 |
226 | 1. Build the extension : `npm run build:release`
227 | 1. Commit the build : `git commit -am "Build for release"`
228 | 1. Bump the version number and tag: `npm version major|minor|patch`
229 | 1. Push to origin: `git push && git push --tags`
230 | 1. Publish to npm: `npm publish .`
231 | 1. If publishing to bower for the first time, you'll need to run `bower register cytoscape-fcose https://github.com/iVis-at-Bilkent/cytoscape.js-fcose.git`
232 | 1. [Make a new release](https://github.com/iVis-at-Bilkent/cytoscape.js-fcose/releases/new) for Zenodo.
233 |
234 | ## Team
235 |
236 | * [Hasan Balcı](https://github.com/hasanbalci) and [Ugur Dogrusoz](https://github.com/ugurdogrusoz) of [i-Vis at Bilkent University](http://www.cs.bilkent.edu.tr/~ivis)
237 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cytoscape-fcose",
3 | "description": "The fCoSE layout for Cytoscape.js by Bilkent with fast compound node placement",
4 | "main": "cytoscape-fcose.js",
5 | "dependencies": {
6 | "cytoscape": "^3.2.0",
7 | "cose-base": "^1.0.0"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/iVis-at-Bilkent/cytoscape.js-fcose.git"
12 | },
13 | "ignore": [
14 | "**/.*",
15 | "node_modules",
16 | "bower_components",
17 | "test",
18 | "tests"
19 | ],
20 | "keywords": [
21 | "cytoscape",
22 | "cytoscape-extension"
23 | ],
24 | "license": "MIT"
25 | }
26 |
--------------------------------------------------------------------------------
/demo/demo-compound.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | cytoscape-fcose.js demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
140 |
141 |
335 |
336 |
337 |
338 | cytoscape-fcose demo (compound)
339 |
340 |
Randomize    
341 |
fCoSE
342 |
343 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
--------------------------------------------------------------------------------
/demo/demo-constraint.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | cytoscape-fcose.js demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
187 |
188 |
189 | cytoscape-fcose demo (constraint)
190 |
191 |
192 |
193 |
 
194 |
195 | sample1 - fixed
196 | sample2 - alignment
197 | sample3 - relative
198 | sample4 - hybrid
199 | sample5
200 | unix-family-tree
201 | chalk-dependency
202 | UW-sensor-network
203 | python-call-graph
204 | wireless-sensor-network
205 |
206 |
207 |
208 |
209 |
210 |
211 |
Randomize  
212 |
fCoSE  
213 |
214 |
215 |
216 | Incremental
217 |
218 |
Draft  
219 |
Transform  
220 |
Enforce  
221 |
CoSE
222 |
223 |
224 |
225 |
226 |
227 |
236 |
237 |
238 |
239 |
240 |
241 |
261 |
262 |
263 |
264 |
265 |
293 |
294 |
295 |
296 |
297 |
325 |
326 |
327 |
328 |
329 |
330 | Hover a constraint row to see involved nodes.
331 |
332 |
333 | Type
334 | Nodes
335 | Info
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
--------------------------------------------------------------------------------
/demo/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iVis-at-Bilkent/cytoscape.js-fcose/78afcf96512a409abc903699277ad616c02dfad9/demo/demo.gif
--------------------------------------------------------------------------------
/demo/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | cytoscape-fcose.js demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
140 |
141 |
327 |
328 |
329 |
330 | cytoscape-fcose demo
331 |
332 |
Randomize  
333 |
fCoSE  
334 |
335 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
--------------------------------------------------------------------------------
/demo/incrementalConstraints.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iVis-at-Bilkent/cytoscape.js-fcose/78afcf96512a409abc903699277ad616c02dfad9/demo/incrementalConstraints.gif
--------------------------------------------------------------------------------
/demo/samples/callGraph_constraints.js:
--------------------------------------------------------------------------------
1 | callGraph_constraints = {
2 | "alignmentConstraint": {
3 | "horizontal": [
4 | [
5 | "288a2f9b-2930-701e-c68e-400307fbffd4",
6 | "nwtN_a44ad134-4c47-4cf5-b244-9d9fc0804e93",
7 | "nwtN_ac02fa71-2428-47ad-8c3c-80bb5659a066"
8 | ],
9 | [
10 | "nwtN_550a66a2-1061-45cc-9f78-14570daaff94",
11 | "82181180-e275-b004-243e-eb4383e1e72f",
12 | "359e920c-52a4-459e-2262-4ec90824f21e",
13 | "5a34dca5-646c-cae7-affe-ef4142c5ad73",
14 | "2f560d5b-bb1b-cc3f-d5b5-a69e6fab8d47",
15 | "d07ee031-2818-7c9c-5589-e8a161b9946c",
16 | "912c0f08-87fb-c746-18cb-19909a2b41c8"
17 | ],
18 | [
19 | "1f64237f-dd15-2d5f-18f0-02a0d0338f44",
20 | "63edffef-16cf-a216-5ace-a27a4290e3d8",
21 | "d6768356-8772-a49a-c2f7-c3b9802c954b",
22 | "05b42313-71a4-e1a9-383b-6b4be641e69d"
23 | ],
24 | [
25 | "3c161178-1fca-ec7e-3fc7-425ce940fe1a",
26 | "bab5c8a0-0f39-66d4-eaca-7f9160ef25fa",
27 | "a0d92978-e6e9-6d87-9a06-567d139d15af"
28 | ]
29 | ]
30 | },
31 | "relativePlacementConstraint": [
32 | {
33 | "top": "nwtN_6e27959a-ae83-4cb0-898f-9a1e6c5cafae",
34 | "bottom": "d2f78da5-fbd3-fe91-4046-3b5aea0648f6",
35 | "gap": 98.25
36 | },
37 | {
38 | "top": "nwtN_6e27959a-ae83-4cb0-898f-9a1e6c5cafae",
39 | "bottom": "nwtN_a44ad134-4c47-4cf5-b244-9d9fc0804e93",
40 | "gap": 98.25
41 | },
42 | {
43 | "top": "d2f78da5-fbd3-fe91-4046-3b5aea0648f6",
44 | "bottom": "288a2f9b-2930-701e-c68e-400307fbffd4",
45 | "gap": 98.25
46 | },
47 | {
48 | "top": "d2f78da5-fbd3-fe91-4046-3b5aea0648f6",
49 | "bottom": "nwtN_ac02fa71-2428-47ad-8c3c-80bb5659a066",
50 | "gap": 98.25
51 | },
52 | {
53 | "top": "nwtN_ac02fa71-2428-47ad-8c3c-80bb5659a066",
54 | "bottom": "912c0f08-87fb-c746-18cb-19909a2b41c8",
55 | "gap": 98.25
56 | },
57 | {
58 | "top": "nwtN_ac02fa71-2428-47ad-8c3c-80bb5659a066",
59 | "bottom": "82181180-e275-b004-243e-eb4383e1e72f",
60 | "gap": 98.25
61 | },
62 | {
63 | "top": "nwtN_ac02fa71-2428-47ad-8c3c-80bb5659a066",
64 | "bottom": "359e920c-52a4-459e-2262-4ec90824f21e",
65 | "gap": 98.25
66 | },
67 | {
68 | "top": "nwtN_ac02fa71-2428-47ad-8c3c-80bb5659a066",
69 | "bottom": "5a34dca5-646c-cae7-affe-ef4142c5ad73",
70 | "gap": 98.25
71 | },
72 | {
73 | "top": "nwtN_ac02fa71-2428-47ad-8c3c-80bb5659a066",
74 | "bottom": "2f560d5b-bb1b-cc3f-d5b5-a69e6fab8d47",
75 | "gap": 98.25
76 | },
77 | {
78 | "top": "nwtN_a44ad134-4c47-4cf5-b244-9d9fc0804e93",
79 | "bottom": "nwtN_550a66a2-1061-45cc-9f78-14570daaff94",
80 | "gap": 98.25
81 | },
82 | {
83 | "top": "nwtN_a44ad134-4c47-4cf5-b244-9d9fc0804e93",
84 | "bottom": "d07ee031-2818-7c9c-5589-e8a161b9946c",
85 | "gap": 98.25
86 | },
87 | {
88 | "top": "2f560d5b-bb1b-cc3f-d5b5-a69e6fab8d47",
89 | "bottom": "1f64237f-dd15-2d5f-18f0-02a0d0338f44",
90 | "gap": 98.25
91 | },
92 | {
93 | "top": "d07ee031-2818-7c9c-5589-e8a161b9946c",
94 | "bottom": "63edffef-16cf-a216-5ace-a27a4290e3d8",
95 | "gap": 98.25
96 | },
97 | {
98 | "top": "d07ee031-2818-7c9c-5589-e8a161b9946c",
99 | "bottom": "d6768356-8772-a49a-c2f7-c3b9802c954b",
100 | "gap": 98.25
101 | },
102 | {
103 | "top": "d07ee031-2818-7c9c-5589-e8a161b9946c",
104 | "bottom": "05b42313-71a4-e1a9-383b-6b4be641e69d",
105 | "gap": 98.25
106 | },
107 | {
108 | "top": "63edffef-16cf-a216-5ace-a27a4290e3d8",
109 | "bottom": "3c161178-1fca-ec7e-3fc7-425ce940fe1a",
110 | "gap": 98.25
111 | },
112 | {
113 | "top": "63edffef-16cf-a216-5ace-a27a4290e3d8",
114 | "bottom": "bab5c8a0-0f39-66d4-eaca-7f9160ef25fa",
115 | "gap": 98.25
116 | },
117 | {
118 | "top": "63edffef-16cf-a216-5ace-a27a4290e3d8",
119 | "bottom": "a0d92978-e6e9-6d87-9a06-567d139d15af",
120 | "gap": 98.25
121 | }
122 | ]
123 | }
--------------------------------------------------------------------------------
/demo/samples/chalk.js:
--------------------------------------------------------------------------------
1 | chalk = {
2 | "elements": {
3 | "nodes": [
4 | {
5 | "data": {
6 | "id": "nwtN_00dcd31b-2631-4af1-980b-4493dc7d43f7",
7 | "bbox": {
8 | "x": 506.4608860015869,
9 | "y": 105.05675506591797,
10 | "w": 60.3330078125,
11 | "h": 28.25
12 | },
13 | "originalW": 88.3330078125,
14 | "originalH": 56.25,
15 | "class": "compartment",
16 | "label": "types",
17 | "statesandinfos": [],
18 | "parent": "nwtN_80eb137f-5cd7-4584-9acc-87cc5698d255",
19 | "language": "PD",
20 | "border-width": 2.25,
21 | "border-color": "#555555",
22 | "background-color": "#f5f5f5",
23 | "background-opacity": 0.5,
24 | "background-image-opacity": "",
25 | "text-wrap": "wrap",
26 | "font-size": 11,
27 | "font-family": "Helvetica",
28 | "font-style": "normal",
29 | "font-weight": "bold",
30 | "color": "#000000",
31 | "ports": [],
32 | "background-image": "",
33 | "background-fit": "",
34 | "background-position-x": "",
35 | "background-position-y": "",
36 | "background-width": "",
37 | "background-height": "",
38 | "infoboxCalculated": true,
39 | "auxunitlayouts": {}
40 | },
41 | "position": {
42 | "x": 506.4608860015869,
43 | "y": 105.05675506591797
44 | },
45 | "group": "nodes",
46 | "removed": false,
47 | "selected": false,
48 | "selectable": true,
49 | "locked": false,
50 | "grabbable": true,
51 | "classes": ""
52 | },
53 | {
54 | "data": {
55 | "id": "nwtN_80eb137f-5cd7-4584-9acc-87cc5698d255",
56 | "bbox": {
57 | "x": 503.796484757312,
58 | "y": 149.75074733690965,
59 | "w": 141.2687438947998,
60 | "h": 150.88798454198337
61 | },
62 | "originalW": 169.2687438947998,
63 | "originalH": 178.88798454198337,
64 | "class": "compartment",
65 | "label": "chalk",
66 | "statesandinfos": [],
67 | "language": "PD",
68 | "border-width": 2.25,
69 | "border-color": "#555555",
70 | "background-color": "#f5f5f5",
71 | "background-opacity": 0.5,
72 | "background-image-opacity": "",
73 | "text-wrap": "wrap",
74 | "font-size": 11,
75 | "font-family": "Helvetica",
76 | "font-style": "normal",
77 | "font-weight": "bold",
78 | "color": "#000000",
79 | "ports": [],
80 | "background-image": "",
81 | "background-fit": "",
82 | "background-position-x": "",
83 | "background-position-y": "",
84 | "background-width": "",
85 | "background-height": "",
86 | "infoboxCalculated": true,
87 | "auxunitlayouts": {}
88 | },
89 | "position": {
90 | "x": 503.796484757312,
91 | "y": 149.75074733690965
92 | },
93 | "group": "nodes",
94 | "removed": false,
95 | "selected": false,
96 | "selectable": true,
97 | "locked": false,
98 | "grabbable": true,
99 | "classes": ""
100 | },
101 | {
102 | "data": {
103 | "id": "nwtN_23452b85-7855-413f-a03d-26412ed87bfc",
104 | "bbox": {
105 | "x": 506.4608860015869,
106 | "y": 105.05675506591797,
107 | "w": 57.0830078125,
108 | "h": 25
109 | },
110 | "class": "simple chemical",
111 | "label": "index.d.ts",
112 | "statesandinfos": [],
113 | "parent": "nwtN_00dcd31b-2631-4af1-980b-4493dc7d43f7",
114 | "language": "PD",
115 | "border-width": 1.25,
116 | "border-color": "#555555",
117 | "background-color": "#c7eae5",
118 | "background-opacity": 1,
119 | "background-image-opacity": "",
120 | "text-wrap": "wrap",
121 | "font-size": 11,
122 | "font-family": "Helvetica",
123 | "font-style": "normal",
124 | "font-weight": "normal",
125 | "color": "#000",
126 | "ports": [],
127 | "background-image": "",
128 | "background-fit": "",
129 | "background-position-x": "",
130 | "background-position-y": "",
131 | "background-width": "",
132 | "background-height": "",
133 | "infoboxCalculated": true,
134 | "auxunitlayouts": {}
135 | },
136 | "position": {
137 | "x": 506.4608860015869,
138 | "y": 105.05675506591797
139 | },
140 | "group": "nodes",
141 | "removed": false,
142 | "selected": false,
143 | "selectable": true,
144 | "locked": false,
145 | "grabbable": true,
146 | "classes": ""
147 | },
148 | {
149 | "data": {
150 | "id": "nwtN_6ffc1fdf-602f-4395-89b5-666b383a5059",
151 | "bbox": {
152 | "x": 458.4355503099121,
153 | "y": 211.06973960790134,
154 | "w": 47.296875,
155 | "h": 25
156 | },
157 | "class": "macromolecule",
158 | "label": "index.js",
159 | "statesandinfos": [],
160 | "parent": "nwtN_80eb137f-5cd7-4584-9acc-87cc5698d255",
161 | "language": "PD",
162 | "border-width": 1.25,
163 | "border-color": "#555555",
164 | "background-color": "#dfc27d",
165 | "background-opacity": 1,
166 | "background-image-opacity": "",
167 | "text-wrap": "wrap",
168 | "font-size": 11,
169 | "font-family": "Helvetica",
170 | "font-style": "normal",
171 | "font-weight": "normal",
172 | "color": "#000",
173 | "ports": [],
174 | "background-image": "",
175 | "background-fit": "",
176 | "background-position-x": "",
177 | "background-position-y": "",
178 | "background-width": "",
179 | "background-height": "",
180 | "infoboxCalculated": true,
181 | "auxunitlayouts": {}
182 | },
183 | "position": {
184 | "x": 458.4355503099121,
185 | "y": 211.06973960790134
186 | },
187 | "group": "nodes",
188 | "removed": false,
189 | "selected": false,
190 | "selectable": true,
191 | "locked": false,
192 | "grabbable": true,
193 | "classes": ""
194 | },
195 | {
196 | "data": {
197 | "id": "nwtN_c152aeef-01df-49fc-9539-9e022e8d4c64",
198 | "bbox": {
199 | "x": 538.4608860015869,
200 | "y": 205.05675506591797,
201 | "w": 68.68994140625,
202 | "h": 25
203 | },
204 | "class": "macromolecule",
205 | "label": "templates.js",
206 | "statesandinfos": [],
207 | "parent": "nwtN_80eb137f-5cd7-4584-9acc-87cc5698d255",
208 | "language": "PD",
209 | "border-width": 1.25,
210 | "border-color": "#555555",
211 | "background-color": "#dfc27d",
212 | "background-opacity": 1,
213 | "background-image-opacity": "",
214 | "text-wrap": "wrap",
215 | "font-size": 11,
216 | "font-family": "Helvetica",
217 | "font-style": "normal",
218 | "font-weight": "normal",
219 | "color": "#000",
220 | "ports": [],
221 | "background-image": "",
222 | "background-fit": "",
223 | "background-position-x": "",
224 | "background-position-y": "",
225 | "background-width": "",
226 | "background-height": "",
227 | "infoboxCalculated": true,
228 | "auxunitlayouts": {}
229 | },
230 | "position": {
231 | "x": 538.4608860015869,
232 | "y": 205.05675506591797
233 | },
234 | "group": "nodes",
235 | "removed": false,
236 | "selected": false,
237 | "selectable": true,
238 | "locked": false,
239 | "grabbable": true,
240 | "classes": ""
241 | },
242 | {
243 | "data": {
244 | "id": "nwtN_c50c8570-f7bd-46c3-b7c2-66857746b854",
245 | "bbox": {
246 | "x": 457.4608860015869,
247 | "y": 305.05675506591797,
248 | "w": 50.546875,
249 | "h": 28.25
250 | },
251 | "originalW": 78.546875,
252 | "originalH": 56.25,
253 | "class": "compartment",
254 | "label": "ansi-styles",
255 | "statesandinfos": [],
256 | "language": "PD",
257 | "border-width": 2.25,
258 | "border-color": "#555555",
259 | "background-color": "#f5f5f5",
260 | "background-opacity": 0.5,
261 | "background-image-opacity": "",
262 | "text-wrap": "wrap",
263 | "font-size": 11,
264 | "font-family": "Helvetica",
265 | "font-style": "normal",
266 | "font-weight": "bold",
267 | "color": "#000000",
268 | "ports": [],
269 | "background-image": "",
270 | "background-fit": "",
271 | "background-position-x": "",
272 | "background-position-y": "",
273 | "background-width": "",
274 | "background-height": "",
275 | "infoboxCalculated": true,
276 | "auxunitlayouts": {}
277 | },
278 | "position": {
279 | "x": 457.4608860015869,
280 | "y": 305.05675506591797
281 | },
282 | "group": "nodes",
283 | "removed": false,
284 | "selected": false,
285 | "selectable": true,
286 | "locked": false,
287 | "grabbable": true,
288 | "classes": ""
289 | },
290 | {
291 | "data": {
292 | "id": "nwtN_fa99aac7-9500-4ead-82c3-90c72e9837f3",
293 | "bbox": {
294 | "x": 457.4608860015869,
295 | "y": 305.05675506591797,
296 | "w": 47.296875,
297 | "h": 25
298 | },
299 | "class": "macromolecule",
300 | "label": "index.js",
301 | "statesandinfos": [],
302 | "parent": "nwtN_c50c8570-f7bd-46c3-b7c2-66857746b854",
303 | "language": "PD",
304 | "border-width": 1.25,
305 | "border-color": "#555555",
306 | "background-color": "#dfc27d",
307 | "background-opacity": 1,
308 | "background-image-opacity": "",
309 | "text-wrap": "wrap",
310 | "font-size": 11,
311 | "font-family": "Helvetica",
312 | "font-style": "normal",
313 | "font-weight": "normal",
314 | "color": "#000",
315 | "ports": [],
316 | "background-image": "",
317 | "background-fit": "",
318 | "background-position-x": "",
319 | "background-position-y": "",
320 | "background-width": "",
321 | "background-height": "",
322 | "infoboxCalculated": true,
323 | "auxunitlayouts": {}
324 | },
325 | "position": {
326 | "x": 457.4608860015869,
327 | "y": 305.05675506591797
328 | },
329 | "group": "nodes",
330 | "removed": false,
331 | "selected": false,
332 | "selectable": true,
333 | "locked": false,
334 | "grabbable": true,
335 | "classes": ""
336 | },
337 | {
338 | "data": {
339 | "id": "nwtN_cafd8043-b744-474c-99d0-7f33deb415ac",
340 | "bbox": {
341 | "x": 451.6038619242243,
342 | "y": 380.17711757548415,
343 | "w": 50.546875,
344 | "h": 28.25
345 | },
346 | "originalW": 78.546875,
347 | "originalH": 56.25,
348 | "class": "compartment",
349 | "label": "escape-string-regexp",
350 | "statesandinfos": [],
351 | "language": "PD",
352 | "border-width": 2.25,
353 | "border-color": "#555555",
354 | "background-color": "#f5f5f5",
355 | "background-opacity": 0.5,
356 | "background-image-opacity": "",
357 | "text-wrap": "wrap",
358 | "font-size": 11,
359 | "font-family": "Helvetica",
360 | "font-style": "normal",
361 | "font-weight": "bold",
362 | "color": "#000000",
363 | "ports": [],
364 | "background-image": "",
365 | "background-fit": "",
366 | "background-position-x": "",
367 | "background-position-y": "",
368 | "background-width": "",
369 | "background-height": "",
370 | "infoboxCalculated": true,
371 | "auxunitlayouts": {}
372 | },
373 | "position": {
374 | "x": 451.6038619242243,
375 | "y": 380.17711757548415
376 | },
377 | "group": "nodes",
378 | "removed": false,
379 | "selected": false,
380 | "selectable": true,
381 | "locked": false,
382 | "grabbable": true,
383 | "classes": ""
384 | },
385 | {
386 | "data": {
387 | "id": "nwtN_6442ea6c-8a74-4948-ab60-2a2fc6b904f7",
388 | "bbox": {
389 | "x": 451.6038619242243,
390 | "y": 380.17711757548415,
391 | "w": 47.296875,
392 | "h": 25
393 | },
394 | "class": "macromolecule",
395 | "label": "index.js",
396 | "statesandinfos": [],
397 | "parent": "nwtN_cafd8043-b744-474c-99d0-7f33deb415ac",
398 | "language": "PD",
399 | "border-width": 1.25,
400 | "border-color": "#555555",
401 | "background-color": "#dfc27d",
402 | "background-opacity": 1,
403 | "background-image-opacity": "",
404 | "text-wrap": "wrap",
405 | "font-size": 11,
406 | "font-family": "Helvetica",
407 | "font-style": "normal",
408 | "font-weight": "normal",
409 | "color": "#000",
410 | "ports": [],
411 | "background-image": "",
412 | "background-fit": "",
413 | "background-position-x": "",
414 | "background-position-y": "",
415 | "background-width": "",
416 | "background-height": "",
417 | "infoboxCalculated": true,
418 | "auxunitlayouts": {}
419 | },
420 | "position": {
421 | "x": 451.6038619242243,
422 | "y": 380.17711757548415
423 | },
424 | "group": "nodes",
425 | "removed": false,
426 | "selected": false,
427 | "selectable": true,
428 | "locked": false,
429 | "grabbable": true,
430 | "classes": ""
431 | },
432 | {
433 | "data": {
434 | "id": "nwtN_e99db3a7-4249-47e7-84cf-b61676393dc8",
435 | "bbox": {
436 | "x": 467.6089290625593,
437 | "y": 476.207520405494,
438 | "w": 50.546875,
439 | "h": 28.25
440 | },
441 | "originalW": 78.546875,
442 | "originalH": 56.25,
443 | "class": "compartment",
444 | "label": "supports-color",
445 | "statesandinfos": [],
446 | "language": "PD",
447 | "border-width": 2.25,
448 | "border-color": "#555555",
449 | "background-color": "#f5f5f5",
450 | "background-opacity": 0.5,
451 | "background-image-opacity": "",
452 | "text-wrap": "wrap",
453 | "font-size": 11,
454 | "font-family": "Helvetica",
455 | "font-style": "normal",
456 | "font-weight": "bold",
457 | "color": "#000000",
458 | "ports": [],
459 | "background-image": "",
460 | "background-fit": "",
461 | "background-position-x": "",
462 | "background-position-y": "",
463 | "background-width": "",
464 | "background-height": "",
465 | "infoboxCalculated": true,
466 | "auxunitlayouts": {}
467 | },
468 | "position": {
469 | "x": 467.6089290625593,
470 | "y": 476.207520405494
471 | },
472 | "group": "nodes",
473 | "removed": false,
474 | "selected": false,
475 | "selectable": true,
476 | "locked": false,
477 | "grabbable": true,
478 | "classes": ""
479 | },
480 | {
481 | "data": {
482 | "id": "nwtN_395e3fed-fcd4-427a-909a-c01a7fc94607",
483 | "bbox": {
484 | "x": 467.6089290625593,
485 | "y": 476.207520405494,
486 | "w": 47.296875,
487 | "h": 25
488 | },
489 | "class": "macromolecule",
490 | "label": "index.js",
491 | "statesandinfos": [],
492 | "parent": "nwtN_e99db3a7-4249-47e7-84cf-b61676393dc8",
493 | "language": "PD",
494 | "border-width": 1.25,
495 | "border-color": "#555555",
496 | "background-color": "#dfc27d",
497 | "background-opacity": 1,
498 | "background-image-opacity": "",
499 | "text-wrap": "wrap",
500 | "font-size": 11,
501 | "font-family": "Helvetica",
502 | "font-style": "normal",
503 | "font-weight": "normal",
504 | "color": "#000",
505 | "ports": [],
506 | "background-image": "",
507 | "background-fit": "",
508 | "background-position-x": "",
509 | "background-position-y": "",
510 | "background-width": "",
511 | "background-height": "",
512 | "infoboxCalculated": true,
513 | "auxunitlayouts": {}
514 | },
515 | "position": {
516 | "x": 467.6089290625593,
517 | "y": 476.207520405494
518 | },
519 | "group": "nodes",
520 | "removed": false,
521 | "selected": false,
522 | "selectable": true,
523 | "locked": false,
524 | "grabbable": true,
525 | "classes": ""
526 | },
527 | {
528 | "data": {
529 | "id": "nwtN_641f76bc-059e-4885-9e58-8bc08b26765d",
530 | "bbox": {
531 | "x": 663.6710015071626,
532 | "y": 439.19580264809434,
533 | "w": 50.546875,
534 | "h": 28.25
535 | },
536 | "originalW": 78.546875,
537 | "originalH": 56.25,
538 | "class": "compartment",
539 | "label": "has-flag",
540 | "statesandinfos": [],
541 | "language": "PD",
542 | "border-width": 2.25,
543 | "border-color": "#555555",
544 | "background-color": "#f5f5f5",
545 | "background-opacity": 0.5,
546 | "background-image-opacity": "",
547 | "text-wrap": "wrap",
548 | "font-size": 11,
549 | "font-family": "Helvetica",
550 | "font-style": "normal",
551 | "font-weight": "bold",
552 | "color": "#000000",
553 | "ports": [],
554 | "background-image": "",
555 | "background-fit": "",
556 | "background-position-x": "",
557 | "background-position-y": "",
558 | "background-width": "",
559 | "background-height": "",
560 | "infoboxCalculated": true,
561 | "auxunitlayouts": {}
562 | },
563 | "position": {
564 | "x": 663.6710015071626,
565 | "y": 439.19580264809434
566 | },
567 | "group": "nodes",
568 | "removed": false,
569 | "selected": false,
570 | "selectable": true,
571 | "locked": false,
572 | "grabbable": true,
573 | "classes": ""
574 | },
575 | {
576 | "data": {
577 | "id": "nwtN_fc4a5aa8-171f-41bd-8ab3-5eee343c4f9f",
578 | "bbox": {
579 | "x": 663.6710015071626,
580 | "y": 439.19580264809434,
581 | "w": 47.296875,
582 | "h": 25
583 | },
584 | "class": "macromolecule",
585 | "label": "index.js",
586 | "statesandinfos": [],
587 | "parent": "nwtN_641f76bc-059e-4885-9e58-8bc08b26765d",
588 | "language": "PD",
589 | "border-width": 1.25,
590 | "border-color": "#555555",
591 | "background-color": "#dfc27d",
592 | "background-opacity": 1,
593 | "background-image-opacity": "",
594 | "text-wrap": "wrap",
595 | "font-size": 11,
596 | "font-family": "Helvetica",
597 | "font-style": "normal",
598 | "font-weight": "normal",
599 | "color": "#000",
600 | "ports": [],
601 | "background-image": "",
602 | "background-fit": "",
603 | "background-position-x": "",
604 | "background-position-y": "",
605 | "background-width": "",
606 | "background-height": "",
607 | "infoboxCalculated": true,
608 | "auxunitlayouts": {}
609 | },
610 | "position": {
611 | "x": 663.6710015071626,
612 | "y": 439.19580264809434
613 | },
614 | "group": "nodes",
615 | "removed": false,
616 | "selected": false,
617 | "selectable": true,
618 | "locked": false,
619 | "grabbable": true,
620 | "classes": ""
621 | },
622 | {
623 | "data": {
624 | "id": "nwtN_e4a59b7b-986f-40c1-8211-07ecd7bd4a85",
625 | "bbox": {
626 | "x": 769.0413352597577,
627 | "y": 155.046620789248,
628 | "w": 209.7825138067825,
629 | "h": 76.26330123812932
630 | },
631 | "originalW": 237.7825138067825,
632 | "originalH": 104.26330123812932,
633 | "class": "compartment",
634 | "label": "color-convert",
635 | "statesandinfos": [],
636 | "language": "PD",
637 | "border-width": 2.25,
638 | "border-color": "#555555",
639 | "background-color": "#f5f5f5",
640 | "background-opacity": 0.5,
641 | "background-image-opacity": "",
642 | "text-wrap": "wrap",
643 | "font-size": 11,
644 | "font-family": "Helvetica",
645 | "font-style": "normal",
646 | "font-weight": "bold",
647 | "color": "#000000",
648 | "ports": [],
649 | "background-image": "",
650 | "background-fit": "",
651 | "background-position-x": "",
652 | "background-position-y": "",
653 | "background-width": "",
654 | "background-height": "",
655 | "infoboxCalculated": true,
656 | "auxunitlayouts": {}
657 | },
658 | "position": {
659 | "x": 769.0413352597577,
660 | "y": 155.046620789248
661 | },
662 | "group": "nodes",
663 | "removed": false,
664 | "selected": false,
665 | "selectable": true,
666 | "locked": false,
667 | "grabbable": true,
668 | "classes": ""
669 | },
670 | {
671 | "data": {
672 | "id": "nwtN_10cd6cfc-6d2a-48b1-82e1-29d16dadd069",
673 | "bbox": {
674 | "x": 689.4235158563665,
675 | "y": 179.05327140831267,
676 | "w": 47.296875,
677 | "h": 25
678 | },
679 | "class": "macromolecule",
680 | "label": "index.js",
681 | "statesandinfos": [],
682 | "parent": "nwtN_e4a59b7b-986f-40c1-8211-07ecd7bd4a85",
683 | "language": "PD",
684 | "border-width": 1.25,
685 | "border-color": "#555555",
686 | "background-color": "#dfc27d",
687 | "background-opacity": 1,
688 | "background-image-opacity": "",
689 | "text-wrap": "wrap",
690 | "font-size": 11,
691 | "font-family": "Helvetica",
692 | "font-style": "normal",
693 | "font-weight": "normal",
694 | "color": "#000",
695 | "ports": [],
696 | "background-image": "",
697 | "background-fit": "",
698 | "background-position-x": "",
699 | "background-position-y": "",
700 | "background-width": "",
701 | "background-height": "",
702 | "infoboxCalculated": true,
703 | "auxunitlayouts": {}
704 | },
705 | "position": {
706 | "x": 689.4235158563665,
707 | "y": 179.05327140831267
708 | },
709 | "group": "nodes",
710 | "removed": false,
711 | "selected": false,
712 | "selectable": true,
713 | "locked": false,
714 | "grabbable": true,
715 | "classes": ""
716 | },
717 | {
718 | "data": {
719 | "id": "nwtN_da2a2f8c-a185-4ffd-b577-f2c5a7680a37",
720 | "bbox": {
721 | "x": 832.459935913149,
722 | "y": 174.05612167362608,
723 | "w": 79.6953125,
724 | "h": 25
725 | },
726 | "class": "macromolecule",
727 | "label": "conversions.js",
728 | "statesandinfos": [],
729 | "parent": "nwtN_e4a59b7b-986f-40c1-8211-07ecd7bd4a85",
730 | "language": "PD",
731 | "border-width": 1.25,
732 | "border-color": "#555555",
733 | "background-color": "#dfc27d",
734 | "background-opacity": 1,
735 | "background-image-opacity": "",
736 | "text-wrap": "wrap",
737 | "font-size": 11,
738 | "font-family": "Helvetica",
739 | "font-style": "normal",
740 | "font-weight": "normal",
741 | "color": "#000",
742 | "ports": [],
743 | "background-image": "",
744 | "background-fit": "",
745 | "background-position-x": "",
746 | "background-position-y": "",
747 | "background-width": "",
748 | "background-height": "",
749 | "infoboxCalculated": true,
750 | "auxunitlayouts": {}
751 | },
752 | "position": {
753 | "x": 832.459935913149,
754 | "y": 174.05612167362608
755 | },
756 | "group": "nodes",
757 | "removed": false,
758 | "selected": false,
759 | "selectable": true,
760 | "locked": false,
761 | "grabbable": true,
762 | "classes": ""
763 | },
764 | {
765 | "data": {
766 | "id": "nwtN_ec240627-f567-42b4-a9c6-4564450e9946",
767 | "bbox": {
768 | "x": 761.4273162101177,
769 | "y": 131.03997017018335,
770 | "w": 46.072265625,
771 | "h": 25
772 | },
773 | "class": "macromolecule",
774 | "label": "route.js",
775 | "statesandinfos": [],
776 | "parent": "nwtN_e4a59b7b-986f-40c1-8211-07ecd7bd4a85",
777 | "language": "PD",
778 | "border-width": 1.25,
779 | "border-color": "#555555",
780 | "background-color": "#dfc27d",
781 | "background-opacity": 1,
782 | "background-image-opacity": "",
783 | "text-wrap": "wrap",
784 | "font-size": 11,
785 | "font-family": "Helvetica",
786 | "font-style": "normal",
787 | "font-weight": "normal",
788 | "color": "#000",
789 | "ports": [],
790 | "background-image": "",
791 | "background-fit": "",
792 | "background-position-x": "",
793 | "background-position-y": "",
794 | "background-width": "",
795 | "background-height": "",
796 | "infoboxCalculated": true,
797 | "auxunitlayouts": {}
798 | },
799 | "position": {
800 | "x": 761.4273162101177,
801 | "y": 131.03997017018335
802 | },
803 | "group": "nodes",
804 | "removed": false,
805 | "selected": false,
806 | "selectable": true,
807 | "locked": false,
808 | "grabbable": true,
809 | "classes": ""
810 | },
811 | {
812 | "data": {
813 | "id": "nwtN_bc39472b-e95f-4e5c-955e-eb6fd7fe0cdf",
814 | "bbox": {
815 | "x": 914.4608860015869,
816 | "y": 275.05675506591797,
817 | "w": 50.546875,
818 | "h": 28.25
819 | },
820 | "originalW": 78.546875,
821 | "originalH": 56.25,
822 | "class": "compartment",
823 | "label": "color-name",
824 | "statesandinfos": [],
825 | "language": "PD",
826 | "border-width": 2.25,
827 | "border-color": "#555555",
828 | "background-color": "#f5f5f5",
829 | "background-opacity": 0.5,
830 | "background-image-opacity": "",
831 | "text-wrap": "wrap",
832 | "font-size": 11,
833 | "font-family": "Helvetica",
834 | "font-style": "normal",
835 | "font-weight": "bold",
836 | "color": "#000000",
837 | "ports": [],
838 | "background-image": "",
839 | "background-fit": "",
840 | "background-position-x": "",
841 | "background-position-y": "",
842 | "background-width": "",
843 | "background-height": "",
844 | "infoboxCalculated": true,
845 | "auxunitlayouts": {}
846 | },
847 | "position": {
848 | "x": 914.4608860015869,
849 | "y": 275.05675506591797
850 | },
851 | "group": "nodes",
852 | "removed": false,
853 | "selected": false,
854 | "selectable": true,
855 | "locked": false,
856 | "grabbable": true,
857 | "classes": ""
858 | },
859 | {
860 | "data": {
861 | "id": "nwtN_2f088c82-aac6-4e48-8fde-8574a3390d24",
862 | "bbox": {
863 | "x": 914.4608860015869,
864 | "y": 275.05675506591797,
865 | "w": 47.296875,
866 | "h": 25
867 | },
868 | "class": "macromolecule",
869 | "label": "index.js",
870 | "statesandinfos": [],
871 | "parent": "nwtN_bc39472b-e95f-4e5c-955e-eb6fd7fe0cdf",
872 | "language": "PD",
873 | "border-width": 1.25,
874 | "border-color": "#555555",
875 | "background-color": "#dfc27d",
876 | "background-opacity": 1,
877 | "background-image-opacity": "",
878 | "text-wrap": "wrap",
879 | "font-size": 11,
880 | "font-family": "Helvetica",
881 | "font-style": "normal",
882 | "font-weight": "normal",
883 | "color": "#000",
884 | "ports": [],
885 | "background-image": "",
886 | "background-fit": "",
887 | "background-position-x": "",
888 | "background-position-y": "",
889 | "background-width": "",
890 | "background-height": "",
891 | "infoboxCalculated": true,
892 | "auxunitlayouts": {}
893 | },
894 | "position": {
895 | "x": 914.4608860015869,
896 | "y": 275.05675506591797
897 | },
898 | "group": "nodes",
899 | "removed": false,
900 | "selected": false,
901 | "selectable": true,
902 | "locked": false,
903 | "grabbable": true,
904 | "classes": ""
905 | },
906 | {
907 | "data": {
908 | "id": "nwtN_88c73829-9d76-4b03-ab27-335fd9581531",
909 | "bbox": {
910 | "x": 638.6630841035143,
911 | "y": 555.2325394010229,
912 | "w": 30,
913 | "h": 25
914 | },
915 | "class": "unspecified entity",
916 | "label": "os",
917 | "statesandinfos": [],
918 | "language": "PD",
919 | "border-width": 1.25,
920 | "border-color": "#555555",
921 | "background-color": "#f5f5f5",
922 | "background-opacity": 1,
923 | "background-image-opacity": "",
924 | "text-wrap": "wrap",
925 | "font-size": 11,
926 | "font-family": "Helvetica",
927 | "font-style": "normal",
928 | "font-weight": "normal",
929 | "color": "#000",
930 | "ports": [],
931 | "background-image": "",
932 | "background-fit": "",
933 | "background-position-x": "",
934 | "background-position-y": "",
935 | "background-width": "",
936 | "background-height": "",
937 | "infoboxCalculated": true,
938 | "auxunitlayouts": {}
939 | },
940 | "position": {
941 | "x": 638.6630841035143,
942 | "y": 555.2325394010229
943 | },
944 | "group": "nodes",
945 | "removed": false,
946 | "selected": false,
947 | "selectable": true,
948 | "locked": false,
949 | "grabbable": true,
950 | "classes": ""
951 | }
952 | ],
953 | "edges": [
954 | {
955 | "data": {
956 | "id": "nwtE_d1ae8562-bb81-4d93-ac24-78b9def385a2",
957 | "class": "production",
958 | "bendPointPositions": [],
959 | "language": "PD",
960 | "line-color": "#555555",
961 | "width": 1.25,
962 | "cardinality": 0,
963 | "source": "nwtN_6ffc1fdf-602f-4395-89b5-666b383a5059",
964 | "target": "nwtN_c152aeef-01df-49fc-9539-9e022e8d4c64",
965 | "portsource": "nwtN_6ffc1fdf-602f-4395-89b5-666b383a5059",
966 | "porttarget": "nwtN_c152aeef-01df-49fc-9539-9e022e8d4c64"
967 | },
968 | "position": {},
969 | "group": "edges",
970 | "removed": false,
971 | "selected": false,
972 | "selectable": true,
973 | "locked": false,
974 | "grabbable": true,
975 | "classes": ""
976 | },
977 | {
978 | "data": {
979 | "id": "nwtE_925bc3d3-7dac-4ef3-b0d7-875c78be8fbe",
980 | "class": "production",
981 | "bendPointPositions": [],
982 | "language": "PD",
983 | "line-color": "#555555",
984 | "width": 1.25,
985 | "cardinality": 0,
986 | "source": "nwtN_6ffc1fdf-602f-4395-89b5-666b383a5059",
987 | "target": "nwtN_fa99aac7-9500-4ead-82c3-90c72e9837f3",
988 | "portsource": "nwtN_6ffc1fdf-602f-4395-89b5-666b383a5059",
989 | "porttarget": "nwtN_fa99aac7-9500-4ead-82c3-90c72e9837f3"
990 | },
991 | "position": {},
992 | "group": "edges",
993 | "removed": false,
994 | "selected": false,
995 | "selectable": true,
996 | "locked": false,
997 | "grabbable": true,
998 | "classes": ""
999 | },
1000 | {
1001 | "data": {
1002 | "id": "nwtE_d32f598b-4b96-4403-87c4-870e99ee6bc1",
1003 | "class": "production",
1004 | "bendPointPositions": [],
1005 | "language": "PD",
1006 | "line-color": "#555555",
1007 | "width": 1.25,
1008 | "cardinality": 0,
1009 | "source": "nwtN_6ffc1fdf-602f-4395-89b5-666b383a5059",
1010 | "target": "nwtN_6442ea6c-8a74-4948-ab60-2a2fc6b904f7",
1011 | "portsource": "nwtN_6ffc1fdf-602f-4395-89b5-666b383a5059",
1012 | "porttarget": "nwtN_6442ea6c-8a74-4948-ab60-2a2fc6b904f7"
1013 | },
1014 | "position": {},
1015 | "group": "edges",
1016 | "removed": false,
1017 | "selected": false,
1018 | "selectable": true,
1019 | "locked": false,
1020 | "grabbable": true,
1021 | "classes": ""
1022 | },
1023 | {
1024 | "data": {
1025 | "id": "nwtE_0f92d3d4-1849-4f4c-97f0-6504eb9831ac",
1026 | "class": "production",
1027 | "bendPointPositions": [],
1028 | "language": "PD",
1029 | "line-color": "#555555",
1030 | "width": 1.25,
1031 | "cardinality": 0,
1032 | "source": "nwtN_6ffc1fdf-602f-4395-89b5-666b383a5059",
1033 | "target": "nwtN_395e3fed-fcd4-427a-909a-c01a7fc94607",
1034 | "portsource": "nwtN_6ffc1fdf-602f-4395-89b5-666b383a5059",
1035 | "porttarget": "nwtN_395e3fed-fcd4-427a-909a-c01a7fc94607"
1036 | },
1037 | "position": {},
1038 | "group": "edges",
1039 | "removed": false,
1040 | "selected": false,
1041 | "selectable": true,
1042 | "locked": false,
1043 | "grabbable": true,
1044 | "classes": ""
1045 | },
1046 | {
1047 | "data": {
1048 | "id": "nwtE_2b21b38a-8443-45ea-b067-d626a731fc32",
1049 | "class": "production",
1050 | "bendPointPositions": [],
1051 | "language": "PD",
1052 | "line-color": "#555555",
1053 | "width": 1.25,
1054 | "cardinality": 0,
1055 | "source": "nwtN_fa99aac7-9500-4ead-82c3-90c72e9837f3",
1056 | "target": "nwtN_10cd6cfc-6d2a-48b1-82e1-29d16dadd069",
1057 | "portsource": "nwtN_fa99aac7-9500-4ead-82c3-90c72e9837f3",
1058 | "porttarget": "nwtN_10cd6cfc-6d2a-48b1-82e1-29d16dadd069"
1059 | },
1060 | "position": {},
1061 | "group": "edges",
1062 | "removed": false,
1063 | "selected": false,
1064 | "selectable": true,
1065 | "locked": false,
1066 | "grabbable": true,
1067 | "classes": ""
1068 | },
1069 | {
1070 | "data": {
1071 | "id": "nwtE_cc83602e-928e-4d47-b60e-9ec94b93f9bd",
1072 | "class": "production",
1073 | "bendPointPositions": [],
1074 | "language": "PD",
1075 | "line-color": "#555555",
1076 | "width": 1.25,
1077 | "cardinality": 0,
1078 | "source": "nwtN_10cd6cfc-6d2a-48b1-82e1-29d16dadd069",
1079 | "target": "nwtN_ec240627-f567-42b4-a9c6-4564450e9946",
1080 | "portsource": "nwtN_10cd6cfc-6d2a-48b1-82e1-29d16dadd069",
1081 | "porttarget": "nwtN_ec240627-f567-42b4-a9c6-4564450e9946"
1082 | },
1083 | "position": {},
1084 | "group": "edges",
1085 | "removed": false,
1086 | "selected": false,
1087 | "selectable": true,
1088 | "locked": false,
1089 | "grabbable": true,
1090 | "classes": ""
1091 | },
1092 | {
1093 | "data": {
1094 | "id": "nwtE_66f2d0d1-0542-450a-b6ae-f1c8bfa19e23",
1095 | "class": "production",
1096 | "bendPointPositions": [],
1097 | "language": "PD",
1098 | "line-color": "#555555",
1099 | "width": 1.25,
1100 | "cardinality": 0,
1101 | "source": "nwtN_ec240627-f567-42b4-a9c6-4564450e9946",
1102 | "target": "nwtN_da2a2f8c-a185-4ffd-b577-f2c5a7680a37",
1103 | "portsource": "nwtN_ec240627-f567-42b4-a9c6-4564450e9946",
1104 | "porttarget": "nwtN_da2a2f8c-a185-4ffd-b577-f2c5a7680a37"
1105 | },
1106 | "position": {},
1107 | "group": "edges",
1108 | "removed": false,
1109 | "selected": false,
1110 | "selectable": true,
1111 | "locked": false,
1112 | "grabbable": true,
1113 | "classes": ""
1114 | },
1115 | {
1116 | "data": {
1117 | "id": "nwtE_83bd7195-5e2f-49c8-87f1-b54d74309928",
1118 | "class": "production",
1119 | "bendPointPositions": [],
1120 | "language": "PD",
1121 | "line-color": "#555555",
1122 | "width": 1.25,
1123 | "cardinality": 0,
1124 | "source": "nwtN_10cd6cfc-6d2a-48b1-82e1-29d16dadd069",
1125 | "target": "nwtN_da2a2f8c-a185-4ffd-b577-f2c5a7680a37",
1126 | "portsource": "nwtN_10cd6cfc-6d2a-48b1-82e1-29d16dadd069",
1127 | "porttarget": "nwtN_da2a2f8c-a185-4ffd-b577-f2c5a7680a37"
1128 | },
1129 | "position": {},
1130 | "group": "edges",
1131 | "removed": false,
1132 | "selected": false,
1133 | "selectable": true,
1134 | "locked": false,
1135 | "grabbable": true,
1136 | "classes": ""
1137 | },
1138 | {
1139 | "data": {
1140 | "id": "nwtE_5e327bfd-b03d-440c-8825-0f03c01c7426",
1141 | "class": "production",
1142 | "bendPointPositions": [],
1143 | "language": "PD",
1144 | "line-color": "#555555",
1145 | "width": 1.25,
1146 | "cardinality": 0,
1147 | "source": "nwtN_da2a2f8c-a185-4ffd-b577-f2c5a7680a37",
1148 | "target": "nwtN_2f088c82-aac6-4e48-8fde-8574a3390d24",
1149 | "portsource": "nwtN_da2a2f8c-a185-4ffd-b577-f2c5a7680a37",
1150 | "porttarget": "nwtN_2f088c82-aac6-4e48-8fde-8574a3390d24"
1151 | },
1152 | "position": {},
1153 | "group": "edges",
1154 | "removed": false,
1155 | "selected": false,
1156 | "selectable": true,
1157 | "locked": false,
1158 | "grabbable": true,
1159 | "classes": ""
1160 | },
1161 | {
1162 | "data": {
1163 | "id": "nwtE_93128cfa-29d1-4669-8496-85227b119c1f",
1164 | "class": "production",
1165 | "bendPointPositions": [],
1166 | "language": "PD",
1167 | "line-color": "#555555",
1168 | "width": 1.25,
1169 | "cardinality": 0,
1170 | "source": "nwtN_395e3fed-fcd4-427a-909a-c01a7fc94607",
1171 | "target": "nwtN_fc4a5aa8-171f-41bd-8ab3-5eee343c4f9f",
1172 | "portsource": "nwtN_395e3fed-fcd4-427a-909a-c01a7fc94607",
1173 | "porttarget": "nwtN_fc4a5aa8-171f-41bd-8ab3-5eee343c4f9f"
1174 | },
1175 | "position": {},
1176 | "group": "edges",
1177 | "removed": false,
1178 | "selected": false,
1179 | "selectable": true,
1180 | "locked": false,
1181 | "grabbable": true,
1182 | "classes": ""
1183 | },
1184 | {
1185 | "data": {
1186 | "id": "nwtE_95f18b40-fe6a-482c-9ff2-5296ad1923c0",
1187 | "class": "production",
1188 | "bendPointPositions": [],
1189 | "language": "PD",
1190 | "line-color": "#a8a8a8",
1191 | "width": 1.25,
1192 | "cardinality": 0,
1193 | "source": "nwtN_395e3fed-fcd4-427a-909a-c01a7fc94607",
1194 | "target": "nwtN_88c73829-9d76-4b03-ab27-335fd9581531",
1195 | "portsource": "nwtN_395e3fed-fcd4-427a-909a-c01a7fc94607",
1196 | "porttarget": "nwtN_88c73829-9d76-4b03-ab27-335fd9581531"
1197 | },
1198 | "position": {},
1199 | "group": "edges",
1200 | "removed": false,
1201 | "selected": false,
1202 | "selectable": true,
1203 | "locked": false,
1204 | "grabbable": true,
1205 | "classes": ""
1206 | }
1207 | ]
1208 | },
1209 | "style": [
1210 | {
1211 | "selector": "node",
1212 | "style": {
1213 | "shape": "rectangle",
1214 | "text-halign": "center",
1215 | "text-valign": "center",
1216 | "background-color": "#ffffff",
1217 | "opacity": "1",
1218 | "border-color": "#555555"
1219 | }
1220 | },
1221 | {
1222 | "selector": "node[class = 'simple chemical']",
1223 | "style": {
1224 | "shape": "ellipse",
1225 | "background-color": "#c7eae5"
1226 | }
1227 | },
1228 | {
1229 | "selector": "node[class = 'macromolecule']",
1230 | "style": {
1231 | "shape": "roundrectangle",
1232 | "background-color": "#dfc27d"
1233 | }
1234 | },
1235 | {
1236 | "selector": "node[class = 'unspecified entity']",
1237 | "style": {
1238 | "shape": "ellipse",
1239 | "background-color": "#f5f5f5"
1240 | }
1241 | },
1242 | {
1243 | "selector": ":parent",
1244 | "style": {
1245 | "background-opacity": "0.333",
1246 | "text-valign": "bottom",
1247 | "shape": "barrel",
1248 | "text-margin-y": "2px",
1249 | "font-weight" : "bold",
1250 | "border-color": "#555555"
1251 | }
1252 | },
1253 | {
1254 | "selector": "node:selected",
1255 | "style": {
1256 | "background-color": "#33ff00",
1257 | "border-color": "#22ee00"
1258 | }
1259 | },
1260 | {
1261 | "selector": "edge",
1262 | "style": {
1263 | "curve-style": "bezier",
1264 | "width": "2px",
1265 | "line-color": "rgb(58,126,207)",
1266 | "opacity": "1",
1267 | "target-arrow-shape": "triangle",
1268 | "target-arrow-color": "rgb(58,126,207)",
1269 | }
1270 | },
1271 | {
1272 | "selector": "edge:selected",
1273 | "style": {
1274 | "line-color": "#33ff00",
1275 | "font-size": "13px",
1276 | "text-opacity": "1",
1277 | "text-rotation": "autorotate",
1278 | "color": "#33ff00",
1279 | "font-weight": "bold",
1280 | "text-background-shape": "roundrectangle",
1281 | "text-background-opacity": "1",
1282 | "text-background-padding": "2px",
1283 | "target-arrow-color": "#33ff00",
1284 | "target-arrow-shape": "triangle",
1285 | }
1286 | }
1287 | ],
1288 | "zoomingEnabled": true,
1289 | "userZoomingEnabled": true,
1290 | "zoom": 1.7784699371350863,
1291 | "minZoom": 0.125,
1292 | "maxZoom": 16,
1293 | "panningEnabled": true,
1294 | "userPanningEnabled": true,
1295 | "pan": {
1296 | "x": -246.2736846361503,
1297 | "y": -69.57913264371261
1298 | },
1299 | "boxSelectionEnabled": true,
1300 | "renderer": {
1301 | "name": "canvas"
1302 | },
1303 | "wheelSensitivity": 0.1,
1304 | "motionBlur": true
1305 | }
--------------------------------------------------------------------------------
/demo/samples/chalk_constraints.js:
--------------------------------------------------------------------------------
1 | chalk_constraints = {
2 | "alignmentConstraint": {
3 | "vertical": [
4 | [
5 | "nwtN_c152aeef-01df-49fc-9539-9e022e8d4c64",
6 | "nwtN_fa99aac7-9500-4ead-82c3-90c72e9837f3",
7 | "nwtN_6442ea6c-8a74-4948-ab60-2a2fc6b904f7",
8 | "nwtN_395e3fed-fcd4-427a-909a-c01a7fc94607"
9 | ],
10 | [
11 | "nwtN_fc4a5aa8-171f-41bd-8ab3-5eee343c4f9f",
12 | "nwtN_10cd6cfc-6d2a-48b1-82e1-29d16dadd069",
13 | "nwtN_88c73829-9d76-4b03-ab27-335fd9581531"
14 | ],
15 | [
16 | "nwtN_23452b85-7855-413f-a03d-26412ed87bfc",
17 | "nwtN_6ffc1fdf-602f-4395-89b5-666b383a5059"
18 | ]
19 | ]
20 | },
21 | "relativePlacementConstraint": [
22 | {
23 | "left": "nwtN_6ffc1fdf-602f-4395-89b5-666b383a5059",
24 | "right": "nwtN_c152aeef-01df-49fc-9539-9e022e8d4c64",
25 | "gap": 180
26 | },
27 | {
28 | "left": "nwtN_6ffc1fdf-602f-4395-89b5-666b383a5059",
29 | "right": "nwtN_fa99aac7-9500-4ead-82c3-90c72e9837f3",
30 | "gap": 180
31 | },
32 | {
33 | "left": "nwtN_6ffc1fdf-602f-4395-89b5-666b383a5059",
34 | "right": "nwtN_6442ea6c-8a74-4948-ab60-2a2fc6b904f7",
35 | "gap": 180
36 | },
37 | {
38 | "left": "nwtN_6ffc1fdf-602f-4395-89b5-666b383a5059",
39 | "right": "nwtN_395e3fed-fcd4-427a-909a-c01a7fc94607",
40 | "gap": 180
41 | },
42 | {
43 | "left": "nwtN_fa99aac7-9500-4ead-82c3-90c72e9837f3",
44 | "right": "nwtN_10cd6cfc-6d2a-48b1-82e1-29d16dadd069",
45 | "gap": 120
46 | },
47 | {
48 | "left": "nwtN_395e3fed-fcd4-427a-909a-c01a7fc94607",
49 | "right": "nwtN_fc4a5aa8-171f-41bd-8ab3-5eee343c4f9f",
50 | "gap": 120
51 | },
52 | {
53 | "left": "nwtN_395e3fed-fcd4-427a-909a-c01a7fc94607",
54 | "right": "nwtN_88c73829-9d76-4b03-ab27-335fd9581531",
55 | "gap": 120
56 | },
57 | {
58 | "left": "nwtN_10cd6cfc-6d2a-48b1-82e1-29d16dadd069",
59 | "right": "nwtN_ec240627-f567-42b4-a9c6-4564450e9946",
60 | "gap": 100
61 | },
62 | {
63 | "left": "nwtN_10cd6cfc-6d2a-48b1-82e1-29d16dadd069",
64 | "right": "nwtN_da2a2f8c-a185-4ffd-b577-f2c5a7680a37",
65 | "gap": 120
66 | },
67 | {
68 | "left": "nwtN_ec240627-f567-42b4-a9c6-4564450e9946",
69 | "right": "nwtN_da2a2f8c-a185-4ffd-b577-f2c5a7680a37",
70 | "gap": 120
71 | },
72 | {
73 | "left": "nwtN_da2a2f8c-a185-4ffd-b577-f2c5a7680a37",
74 | "right": "nwtN_2f088c82-aac6-4e48-8fde-8574a3390d24",
75 | "gap": 120
76 | }
77 | ]
78 | }
--------------------------------------------------------------------------------
/demo/samples/unix_constraints.js:
--------------------------------------------------------------------------------
1 | unix_constraints = {
2 | "relativePlacementConstraint": [
3 | {
4 | "top": "nwtN_4451c335-4668-4f8d-9f71-274aa2e6ab06",
5 | "bottom": "nwtN_8d99e7af-1b2f-4605-a381-ce3127b6199a",
6 | "gap": 76.25
7 | },
8 | {
9 | "top": "nwtN_4451c335-4668-4f8d-9f71-274aa2e6ab06",
10 | "bottom": "nwtN_37220ed2-0cbe-4d1f-9cac-07dfb11d6eda",
11 | "gap": 76.25
12 | },
13 | {
14 | "top": "nwtN_8d99e7af-1b2f-4605-a381-ce3127b6199a",
15 | "bottom": "nwtN_020b84b2-717f-4465-b716-7520e5c9c470",
16 | "gap": 76.25
17 | },
18 | {
19 | "top": "nwtN_8d99e7af-1b2f-4605-a381-ce3127b6199a",
20 | "bottom": "nwtN_c1880331-8411-443e-9218-87e78c057b22",
21 | "gap": 76.25
22 | },
23 | {
24 | "top": "nwtN_8d99e7af-1b2f-4605-a381-ce3127b6199a",
25 | "bottom": "nwtN_3f3b6234-e449-44bb-9ba8-059d1f843c98",
26 | "gap": 76.25
27 | },
28 | {
29 | "top": "nwtN_8d99e7af-1b2f-4605-a381-ce3127b6199a",
30 | "bottom": "nwtN_d7bbe746-9028-4268-a642-70544788d687",
31 | "gap": 76.25
32 | },
33 | {
34 | "top": "nwtN_8d99e7af-1b2f-4605-a381-ce3127b6199a",
35 | "bottom": "nwtN_5bb0a938-c7ee-42d8-85c8-17bdc1ff0ade",
36 | "gap": 76.25
37 | },
38 | {
39 | "top": "nwtN_37220ed2-0cbe-4d1f-9cac-07dfb11d6eda",
40 | "bottom": "nwtN_8227939a-5dc0-433e-8c4d-987ec44a1f8f",
41 | "gap": 76.25
42 | },
43 | {
44 | "top": "nwtN_37220ed2-0cbe-4d1f-9cac-07dfb11d6eda",
45 | "bottom": "nwtN_3f7d61be-a016-4383-9b8d-8c1db26192f2",
46 | "gap": 76.25
47 | },
48 | {
49 | "top": "nwtN_020b84b2-717f-4465-b716-7520e5c9c470",
50 | "bottom": "nwtN_166d8364-fd55-499e-9098-0a48db6615c1",
51 | "gap": 76.25
52 | },
53 | {
54 | "top": "nwtN_c1880331-8411-443e-9218-87e78c057b22",
55 | "bottom": "nwtN_167a075d-9d93-49d0-b5be-446efebec06c",
56 | "gap": 76.25
57 | },
58 | {
59 | "top": "nwtN_c1880331-8411-443e-9218-87e78c057b22",
60 | "bottom": "nwtN_db35fc70-2b89-4857-acb2-9dd545e61447",
61 | "gap": 76.25
62 | },
63 | {
64 | "top": "nwtN_c1880331-8411-443e-9218-87e78c057b22",
65 | "bottom": "nwtN_9dfde51e-a410-4e3b-a472-9061ae4fae7a",
66 | "gap": 76.25
67 | },
68 | {
69 | "top": "nwtN_8227939a-5dc0-433e-8c4d-987ec44a1f8f",
70 | "bottom": "nwtN_9dfde51e-a410-4e3b-a472-9061ae4fae7a",
71 | "gap": 76.25
72 | },
73 | {
74 | "top": "nwtN_3f7d61be-a016-4383-9b8d-8c1db26192f2",
75 | "bottom": "nwtN_a186f2a4-524e-4a84-95c0-66d41dbda586",
76 | "gap": 76.25
77 | },
78 | {
79 | "top": "nwtN_3f7d61be-a016-4383-9b8d-8c1db26192f2",
80 | "bottom": "nwtN_26c73172-708d-45cc-8ba5-f29671089767",
81 | "gap": 76.25
82 | },
83 | {
84 | "top": "nwtN_167a075d-9d93-49d0-b5be-446efebec06c",
85 | "bottom": "nwtN_a2bfb9bd-8b13-43a5-87a5-d88b0137bd78",
86 | "gap": 76.25
87 | },
88 | {
89 | "top": "nwtN_167a075d-9d93-49d0-b5be-446efebec06c",
90 | "bottom": "nwtN_2d9c1f24-1482-46fb-a0f4-f963ba454710",
91 | "gap": 76.25
92 | },
93 | {
94 | "top": "nwtN_167a075d-9d93-49d0-b5be-446efebec06c",
95 | "bottom": "nwtN_15fb1e74-6b08-4914-95ac-ee4ecb1b7f17",
96 | "gap": 76.25
97 | },
98 | {
99 | "top": "nwtN_167a075d-9d93-49d0-b5be-446efebec06c",
100 | "bottom": "nwtN_485d980b-dc42-48ca-9ada-3c1b754cc2ef",
101 | "gap": 76.25
102 | },
103 | {
104 | "top": "nwtN_167a075d-9d93-49d0-b5be-446efebec06c",
105 | "bottom": "nwtN_bce869d1-7c58-4489-81aa-e87e9418ab70",
106 | "gap": 76.25
107 | },
108 | {
109 | "top": "nwtN_9dfde51e-a410-4e3b-a472-9061ae4fae7a",
110 | "bottom": "nwtN_db35fc70-2b89-4857-acb2-9dd545e61447",
111 | "gap": 76.25
112 | },
113 | {
114 | "top": "nwtN_a186f2a4-524e-4a84-95c0-66d41dbda586",
115 | "bottom": "nwtN_2cf0c5aa-67eb-4394-85e3-dc84f4b1cd52",
116 | "gap": 76.25
117 | },
118 | {
119 | "top": "nwtN_26c73172-708d-45cc-8ba5-f29671089767",
120 | "bottom": "nwtN_2b6ef682-26c7-48d7-9fd8-09fa76c7b718",
121 | "gap": 76.25
122 | },
123 | {
124 | "top": "nwtN_a2bfb9bd-8b13-43a5-87a5-d88b0137bd78",
125 | "bottom": "nwtN_be8097dc-4a84-4d3f-ade9-e80221f86dd6",
126 | "gap": 76.25
127 | },
128 | {
129 | "top": "nwtN_d53ea35c-4609-4051-917a-8a183e5976cf",
130 | "bottom": "nwtN_c261ca93-6060-49c1-8286-2a98900c3b9e",
131 | "gap": 76.25
132 | },
133 | {
134 | "top": "nwtN_db35fc70-2b89-4857-acb2-9dd545e61447",
135 | "bottom": "nwtN_a68571c0-2669-4894-aa53-332555f625a2",
136 | "gap": 76.25
137 | },
138 | {
139 | "top": "nwtN_eed8fc62-a21d-4677-85f2-f1244e3abf00",
140 | "bottom": "nwtN_2f175f07-154a-4873-98cc-bc4e4d6d13a1",
141 | "gap": 76.25
142 | },
143 | {
144 | "top": "nwtN_eed8fc62-a21d-4677-85f2-f1244e3abf00",
145 | "bottom": "nwtN_a68571c0-2669-4894-aa53-332555f625a2",
146 | "gap": 76.25
147 | },
148 | {
149 | "top": "nwtN_eed8fc62-a21d-4677-85f2-f1244e3abf00",
150 | "bottom": "nwtN_e55d29c7-89bc-4aa0-b599-3637e141f17f",
151 | "gap": 76.25
152 | },
153 | {
154 | "top": "nwtN_c261ca93-6060-49c1-8286-2a98900c3b9e",
155 | "bottom": "nwtN_02a50d8b-3939-492a-ac5b-1f356cd514f6",
156 | "gap": 76.25
157 | },
158 | {
159 | "top": "nwtN_2f175f07-154a-4873-98cc-bc4e4d6d13a1",
160 | "bottom": "nwtN_a68571c0-2669-4894-aa53-332555f625a2",
161 | "gap": 76.25
162 | },
163 | {
164 | "top": "nwtN_166d8364-fd55-499e-9098-0a48db6615c1",
165 | "bottom": "nwtN_205680d2-dc98-4faf-baf4-21e27e8585e4",
166 | "gap": 76.25
167 | },
168 | {
169 | "top": "nwtN_02a50d8b-3939-492a-ac5b-1f356cd514f6",
170 | "bottom": "nwtN_205680d2-dc98-4faf-baf4-21e27e8585e4",
171 | "gap": 76.25
172 | },
173 | {
174 | "top": "nwtN_02a50d8b-3939-492a-ac5b-1f356cd514f6",
175 | "bottom": "nwtN_75318a95-e179-40fc-b6e1-e01fcd66cd8c",
176 | "gap": 76.25
177 | },
178 | {
179 | "top": "nwtN_02a50d8b-3939-492a-ac5b-1f356cd514f6",
180 | "bottom": "nwtN_bce869d1-7c58-4489-81aa-e87e9418ab70",
181 | "gap": 76.25
182 | },
183 | {
184 | "top": "nwtN_a68571c0-2669-4894-aa53-332555f625a2",
185 | "bottom": "nwtN_8ed84db0-02da-43c6-847c-6b33f657f52c",
186 | "gap": 76.25
187 | },
188 | {
189 | "top": "nwtN_205680d2-dc98-4faf-baf4-21e27e8585e4",
190 | "bottom": "nwtN_be8097dc-4a84-4d3f-ade9-e80221f86dd6",
191 | "gap": 76.25
192 | },
193 | {
194 | "top": "nwtN_205680d2-dc98-4faf-baf4-21e27e8585e4",
195 | "bottom": "nwtN_8489a816-b7af-452f-a722-04d06226341c",
196 | "gap": 76.25
197 | },
198 | {
199 | "top": "nwtN_75318a95-e179-40fc-b6e1-e01fcd66cd8c",
200 | "bottom": "nwtN_d4bf8470-416d-4387-a563-3c9cccf55079",
201 | "gap": 76.25
202 | },
203 | {
204 | "top": "nwtN_75318a95-e179-40fc-b6e1-e01fcd66cd8c",
205 | "bottom": "nwtN_e48d9732-8ee0-4714-8b62-8de8238ec9c2",
206 | "gap": 76.25
207 | },
208 | {
209 | "top": "nwtN_bce869d1-7c58-4489-81aa-e87e9418ab70",
210 | "bottom": "nwtN_103a9105-ef50-47a4-b501-840a431fcbdd",
211 | "gap": 76.25
212 | },
213 | {
214 | "top": "nwtN_8ed84db0-02da-43c6-847c-6b33f657f52c",
215 | "bottom": "nwtN_6385f491-0d09-4d53-a34c-781a861e8b42",
216 | "gap": 76.25
217 | },
218 | {
219 | "top": "nwtN_6385f491-0d09-4d53-a34c-781a861e8b42",
220 | "bottom": "nwtN_88e20782-9221-4b12-9b62-6a6c7116a70f",
221 | "gap": 76.25
222 | },
223 | {
224 | "top": "nwtN_167a075d-9d93-49d0-b5be-446efebec06c",
225 | "bottom": "nwtN_be8097dc-4a84-4d3f-ade9-e80221f86dd6",
226 | "gap": 76.25
227 | },
228 | {
229 | "top": "nwtN_485d980b-dc42-48ca-9ada-3c1b754cc2ef",
230 | "bottom": "nwtN_d53ea35c-4609-4051-917a-8a183e5976cf",
231 | "gap": 76.25
232 | },
233 | {
234 | "top": "nwtN_2cf0c5aa-67eb-4394-85e3-dc84f4b1cd52",
235 | "bottom": "nwtN_db35fc70-2b89-4857-acb2-9dd545e61447",
236 | "gap": 76.25
237 | },
238 | {
239 | "top": "nwtN_cc15f875-f4dd-4df8-a09e-a4f2e6c8a3f5",
240 | "bottom": "nwtN_db35fc70-2b89-4857-acb2-9dd545e61447",
241 | "gap": 76.25
242 | },
243 | {
244 | "top": "nwtN_2b6ef682-26c7-48d7-9fd8-09fa76c7b718",
245 | "bottom": "nwtN_eed8fc62-a21d-4677-85f2-f1244e3abf00",
246 | "gap": 76.25
247 | }
248 | ]
249 | }
250 |
--------------------------------------------------------------------------------
/demo/samples/uwsn_constraints.js:
--------------------------------------------------------------------------------
1 | uwsn_constraints = {
2 | "fixedNodeConstraint": [
3 | {
4 | "nodeId": "nwtN_91cff953-5f55-4626-96fa-e2f675e13e54",
5 | "position": {
6 | "x": -650,
7 | "y": -230
8 | }
9 | },
10 | {
11 | "nodeId": "nwtN_b20cc3f4-dc9c-4031-ac79-df665d4c53cd",
12 | "position": {
13 | "x": 0,
14 | "y": -230
15 | }
16 | },
17 | {
18 | "nodeId": "nwtN_5d6f7198-03e4-48e5-9004-919221f87e66",
19 | "position": {
20 | "x": 650,
21 | "y": -230
22 | }
23 | }
24 | ],
25 | "alignmentConstraint": {
26 | "vertical": [
27 | [
28 | "nwtN_91cff953-5f55-4626-96fa-e2f675e13e54",
29 | "nwtN_7e09bf9e-a4da-4618-baba-60a2b3a0b134"
30 | ],
31 | [
32 | "nwtN_b20cc3f4-dc9c-4031-ac79-df665d4c53cd",
33 | "661498c7-ace9-bd34-0289-7c9931d3e1ba"
34 | ],
35 | [
36 | "nwtN_5d6f7198-03e4-48e5-9004-919221f87e66",
37 | "aeee80bf-8280-f4ec-c1f3-26f3a7a0651c"
38 | ]
39 | ]
40 | },
41 | "relativePlacementConstraint": [
42 | {
43 | "top": "nwtN_7e09bf9e-a4da-4618-baba-60a2b3a0b134",
44 | "bottom": "nwtN_be627c29-b885-460c-bf92-8f9bb7b8c8c5",
45 | "gap": 100
46 | },
47 | {
48 | "top": "nwtN_7e09bf9e-a4da-4618-baba-60a2b3a0b134",
49 | "bottom": "nwtN_64abed47-d1b1-474c-b944-b9ad6354c086",
50 | "gap": 100
51 | },
52 | {
53 | "top": "661498c7-ace9-bd34-0289-7c9931d3e1ba",
54 | "bottom": "nwtN_0185dada-235a-4b51-bce7-e8bd3b8ee661",
55 | "gap": 100
56 | },
57 | {
58 | "top": "661498c7-ace9-bd34-0289-7c9931d3e1ba",
59 | "bottom": "nwtN_273f6927-f05a-425b-aa2b-92de2be06bd9",
60 | "gap": 100
61 | },
62 | {
63 | "top": "aeee80bf-8280-f4ec-c1f3-26f3a7a0651c",
64 | "bottom": "5c108d5c-88e4-7b97-95a8-67238c33d283",
65 | "gap": 100
66 | },
67 | {
68 | "top": "aeee80bf-8280-f4ec-c1f3-26f3a7a0651c",
69 | "bottom": "3ea6020e-8918-8abf-a8e5-62427070f84f",
70 | "gap": 100
71 | },
72 | {
73 | "top": "nwtN_5d6f7198-03e4-48e5-9004-919221f87e66",
74 | "bottom": "aeee80bf-8280-f4ec-c1f3-26f3a7a0651c",
75 | "gap": 250
76 | },
77 | {
78 | "top": "nwtN_b20cc3f4-dc9c-4031-ac79-df665d4c53cd",
79 | "bottom": "661498c7-ace9-bd34-0289-7c9931d3e1ba",
80 | "gap": 250
81 | },
82 | {
83 | "top": "nwtN_91cff953-5f55-4626-96fa-e2f675e13e54",
84 | "bottom": "nwtN_7e09bf9e-a4da-4618-baba-60a2b3a0b134",
85 | "gap": 250
86 | }
87 | ]
88 | }
--------------------------------------------------------------------------------
/demo/samples/wsn_constraints.js:
--------------------------------------------------------------------------------
1 | wsn_constraints = {
2 | "fixedNodeConstraint": [
3 | {
4 | "nodeId": "86f48f58-145d-92cc-0d33-aaf639984733",
5 | "position": {
6 | "x": 399,
7 | "y": 139
8 | }
9 | },
10 | {
11 | "nodeId": "f91d914e-6ab7-92cf-b16d-b9a7661e0044",
12 | "position": {
13 | "x": 857,
14 | "y": 340
15 | }
16 | },
17 | {
18 | "nodeId": "33f80abd-028e-ad1c-068b-33d5b40d9a93",
19 | "position": {
20 | "x": 1068,
21 | "y": 288
22 | }
23 | },
24 | {
25 | "nodeId": "2b855d36-0c89-996d-9281-ea54f711cc8e",
26 | "position": {
27 | "x": 520,
28 | "y": 518
29 | }
30 | },
31 | {
32 | "nodeId": "0ddcc98c-7b7c-ac2e-2207-081c5ffc9921",
33 | "position": {
34 | "x": 533,
35 | "y": -37
36 | }
37 | },
38 | {
39 | "nodeId": "e53126c9-70fe-ca8a-3b85-bab123a93d70",
40 | "position": {
41 | "x": 816,
42 | "y": 280
43 | }
44 | },
45 | {
46 | "nodeId": "4ca3044f-9b82-020b-e1cb-bbb45e4b0088",
47 | "position": {
48 | "x": 630,
49 | "y": 205
50 | }
51 | },
52 | {
53 | "nodeId": "bc0420e3-9b6e-4c97-74c6-61278dbc18d4",
54 | "position": {
55 | "x": 507,
56 | "y": 407
57 | }
58 | },
59 | {
60 | "nodeId": "a97f8f48-6652-7e09-8eda-2d87eae7d20b",
61 | "position": {
62 | "x": 323,
63 | "y": 330
64 | }
65 | },
66 | {
67 | "nodeId": "889eedb3-8cb8-b906-8beb-904cd499c4f6",
68 | "position": {
69 | "x": 892,
70 | "y": -55
71 | }
72 | },
73 | {
74 | "nodeId": "bd77a77c-4627-ad77-1c5a-2c390ba21959",
75 | "position": {
76 | "x": 1114,
77 | "y": 570
78 | }
79 | },
80 | {
81 | "nodeId": "ffbd69bf-481f-faa8-73db-1489f3175b75",
82 | "position": {
83 | "x": 666,
84 | "y": 533
85 | }
86 | },
87 | {
88 | "nodeId": "5fdc6150-63bb-18e8-1f03-33bb8d6b7529",
89 | "position": {
90 | "x": 726,
91 | "y": 670
92 | }
93 | },
94 | {
95 | "nodeId": "b386ca2b-81a7-e365-4706-f955f13db03f",
96 | "position": {
97 | "x": 721,
98 | "y": -36
99 | }
100 | }
101 | ]
102 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cytoscape-fcose",
3 | "version": "2.2.0",
4 | "description": "The fCoSE layout for Cytoscape.js by Bilkent with fast compound node placement",
5 | "main": "cytoscape-fcose.js",
6 | "author": {
7 | "name": "iVis-at-Bilkent"
8 | },
9 | "scripts": {
10 | "copyright": "update license",
11 | "lint": "eslint src",
12 | "build": "cross-env NODE_ENV=production webpack",
13 | "build:min": "cross-env NODE_ENV=production MIN=true webpack",
14 | "build:release": "run-s build copyright",
15 | "watch": "webpack --progress --watch",
16 | "dev": "webpack-dev-server --open",
17 | "test": "mocha"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "https://github.com/iVis-at-Bilkent/cytoscape.js-fcose.git"
22 | },
23 | "keywords": [
24 | "cytoscape",
25 | "cytoscape-extension"
26 | ],
27 | "license": "MIT",
28 | "bugs": {
29 | "url": "https://github.com/iVis-at-Bilkent/cytoscape.js-fcose/issues"
30 | },
31 | "homepage": "https://github.com/iVis-at-Bilkent/cytoscape.js-fcose",
32 | "devDependencies": {
33 | "babel-core": "^6.24.1",
34 | "babel-loader": "^7.1.4",
35 | "babel-preset-env": "^1.5.1",
36 | "camelcase": "^6.2.0",
37 | "chai": "4.0.2",
38 | "cpy-cli": "^3.1.1",
39 | "cross-env": "^7.0.3",
40 | "eslint": "^7.26.0",
41 | "gh-pages": "^1.0.0",
42 | "mocha": "8.4.0",
43 | "npm-run-all": "^4.1.2",
44 | "rimraf": "^3.0.2",
45 | "update": "^0.7.4",
46 | "updater-license": "^1.0.0",
47 | "webpack": "^5.37.0",
48 | "webpack-cli": "^4.7.0",
49 | "webpack-dev-server": "^3.11.2"
50 | },
51 | "peerDependencies": {
52 | "cytoscape": "^3.2.0"
53 | },
54 | "dependencies": {
55 | "cose-base": "^2.2.0"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/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.forEach( src => {
5 | Object.keys( src ).forEach( k => tgt[k] = src[k] );
6 | } );
7 |
8 | return tgt;
9 | };
10 |
--------------------------------------------------------------------------------
/src/fcose/auxiliary.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Auxiliary functions
3 | */
4 |
5 | const LinkedList = require('cose-base').layoutBase.LinkedList;
6 |
7 | let auxiliary = {};
8 |
9 | // get the top most nodes
10 | auxiliary.getTopMostNodes = function(nodes) {
11 | let nodesMap = {};
12 | for (let i = 0; i < nodes.length; i++) {
13 | nodesMap[nodes[i].id()] = true;
14 | }
15 | let roots = nodes.filter(function (ele, i) {
16 | if(typeof ele === "number") {
17 | ele = i;
18 | }
19 | let parent = ele.parent()[0];
20 | while(parent != null){
21 | if(nodesMap[parent.id()]){
22 | return false;
23 | }
24 | parent = parent.parent()[0];
25 | }
26 | return true;
27 | });
28 |
29 | return roots;
30 | };
31 |
32 | // find disconnected components and create dummy nodes that connect them
33 | auxiliary.connectComponents = function(cy, eles, topMostNodes, dummyNodes){
34 | let queue = new LinkedList();
35 | let visited = new Set();
36 | let visitedTopMostNodes = [];
37 | let currentNeighbor;
38 | let minDegreeNode;
39 | let minDegree;
40 |
41 | let isConnected = false;
42 | let count = 1;
43 | let nodesConnectedToDummy = [];
44 | let components = [];
45 |
46 | do{
47 | let cmpt = cy.collection();
48 | components.push(cmpt);
49 |
50 | let currentNode = topMostNodes[0];
51 | let childrenOfCurrentNode = cy.collection();
52 | childrenOfCurrentNode.merge(currentNode).merge(currentNode.descendants().intersection(eles));
53 | visitedTopMostNodes.push(currentNode);
54 |
55 | childrenOfCurrentNode.forEach(function(node) {
56 | queue.push(node);
57 | visited.add(node);
58 | cmpt.merge(node);
59 | });
60 |
61 | while(queue.length != 0){
62 | currentNode = queue.shift();
63 |
64 | // Traverse all neighbors of this node
65 | let neighborNodes = cy.collection();
66 | currentNode.neighborhood().nodes().forEach(function(node){
67 | if(eles.intersection(currentNode.edgesWith(node)).length > 0){
68 | neighborNodes.merge(node);
69 | }
70 | });
71 |
72 | for(let i = 0; i < neighborNodes.length; i++){
73 | let neighborNode = neighborNodes[i];
74 | currentNeighbor = topMostNodes.intersection(neighborNode.union(neighborNode.ancestors()));
75 | if(currentNeighbor != null && !visited.has(currentNeighbor[0])){
76 | let childrenOfNeighbor = currentNeighbor.union(currentNeighbor.descendants());
77 |
78 | childrenOfNeighbor.forEach(function(node){
79 | queue.push(node);
80 | visited.add(node);
81 | cmpt.merge(node);
82 | if(topMostNodes.has(node)){
83 | visitedTopMostNodes.push(node);
84 | }
85 | });
86 |
87 | }
88 | }
89 | }
90 |
91 | cmpt.forEach(node => {
92 | eles.intersection(node.connectedEdges()).forEach(e => { // connectedEdges() usually cached
93 | if( cmpt.has(e.source()) && cmpt.has(e.target()) ){ // has() is cheap
94 | cmpt.merge(e);
95 | }
96 | });
97 | });
98 |
99 | if(visitedTopMostNodes.length == topMostNodes.length){
100 | isConnected = true;
101 | }
102 |
103 | if(!isConnected || (isConnected && count > 1)){
104 | minDegreeNode = visitedTopMostNodes[0];
105 | minDegree = minDegreeNode.connectedEdges().length;
106 | visitedTopMostNodes.forEach(function(node){
107 | if(node.connectedEdges().length < minDegree){
108 | minDegree = node.connectedEdges().length;
109 | minDegreeNode = node;
110 | }
111 | });
112 | nodesConnectedToDummy.push(minDegreeNode.id());
113 | // TO DO: Check efficiency of this part
114 | let temp = cy.collection();
115 | temp.merge(visitedTopMostNodes[0]);
116 | visitedTopMostNodes.forEach(function(node){
117 | temp.merge(node);
118 | });
119 | visitedTopMostNodes = [];
120 | topMostNodes = topMostNodes.difference(temp);
121 | count++;
122 | }
123 |
124 | }
125 | while(!isConnected);
126 |
127 | if(dummyNodes){
128 | if(nodesConnectedToDummy.length > 0 ){
129 | dummyNodes.set('dummy'+(dummyNodes.size+1), nodesConnectedToDummy);
130 | }
131 | }
132 | return components;
133 | };
134 |
135 | // relocates componentResult to originalCenter if there is no fixedNodeConstraint
136 | auxiliary.relocateComponent = function(originalCenter, componentResult, options) {
137 | if (!options.fixedNodeConstraint) {
138 | let minXCoord = Number.POSITIVE_INFINITY;
139 | let maxXCoord = Number.NEGATIVE_INFINITY;
140 | let minYCoord = Number.POSITIVE_INFINITY;
141 | let maxYCoord = Number.NEGATIVE_INFINITY;
142 | if (options.quality == "draft") {
143 | // calculate current bounding box
144 | for (let [key, value] of componentResult.nodeIndexes) {
145 | let cyNode = options.cy.getElementById(key);
146 | if (cyNode) {
147 | let nodeBB = cyNode.boundingBox();
148 | let leftX = componentResult.xCoords[value] - nodeBB.w / 2;
149 | let rightX = componentResult.xCoords[value] + nodeBB.w / 2;
150 | let topY = componentResult.yCoords[value] - nodeBB.h / 2;
151 | let bottomY = componentResult.yCoords[value] + nodeBB.h / 2;
152 |
153 | if (leftX < minXCoord)
154 | minXCoord = leftX;
155 | if (rightX > maxXCoord)
156 | maxXCoord = rightX;
157 | if (topY < minYCoord)
158 | minYCoord = topY;
159 | if (bottomY > maxYCoord)
160 | maxYCoord = bottomY;
161 | }
162 | }
163 | // find difference between current and original center
164 | let diffOnX = originalCenter.x - (maxXCoord + minXCoord) / 2;
165 | let diffOnY = originalCenter.y - (maxYCoord + minYCoord) / 2;
166 | // move component to original center
167 | componentResult.xCoords = componentResult.xCoords.map(x => x + diffOnX);
168 | componentResult.yCoords = componentResult.yCoords.map(y => y + diffOnY);
169 | }
170 | else {
171 | // calculate current bounding box
172 | Object.keys(componentResult).forEach(function (item) {
173 | let node = componentResult[item];
174 | let leftX = node.getRect().x;
175 | let rightX = node.getRect().x + node.getRect().width;
176 | let topY = node.getRect().y;
177 | let bottomY = node.getRect().y + node.getRect().height;
178 |
179 | if (leftX < minXCoord)
180 | minXCoord = leftX;
181 | if (rightX > maxXCoord)
182 | maxXCoord = rightX;
183 | if (topY < minYCoord)
184 | minYCoord = topY;
185 | if (bottomY > maxYCoord)
186 | maxYCoord = bottomY;
187 | });
188 | // find difference between current and original center
189 | let diffOnX = originalCenter.x - (maxXCoord + minXCoord) / 2;
190 | let diffOnY = originalCenter.y - (maxYCoord + minYCoord) / 2;
191 | // move component to original center
192 | Object.keys(componentResult).forEach(function (item) {
193 | let node = componentResult[item];
194 | node.setCenter(node.getCenterX() + diffOnX, node.getCenterY() + diffOnY);
195 | });
196 | }
197 | }
198 | };
199 |
200 | auxiliary.calcBoundingBox = function(parentNode, xCoords, yCoords, nodeIndexes){
201 | // calculate bounds
202 | let left = Number.MAX_SAFE_INTEGER;
203 | let right = Number.MIN_SAFE_INTEGER;
204 | let top = Number.MAX_SAFE_INTEGER;
205 | let bottom = Number.MIN_SAFE_INTEGER;
206 | let nodeLeft;
207 | let nodeRight;
208 | let nodeTop;
209 | let nodeBottom;
210 |
211 | let nodes = parentNode.descendants().not(":parent");
212 | let s = nodes.length;
213 | for (let i = 0; i < s; i++)
214 | {
215 | let node = nodes[i];
216 |
217 | nodeLeft = xCoords[nodeIndexes.get(node.id())] - node.width()/2;
218 | nodeRight = xCoords[nodeIndexes.get(node.id())] + node.width()/2;
219 | nodeTop = yCoords[nodeIndexes.get(node.id())] - node.height()/2;
220 | nodeBottom = yCoords[nodeIndexes.get(node.id())] + node.height()/2;
221 |
222 | if (left > nodeLeft)
223 | {
224 | left = nodeLeft;
225 | }
226 |
227 | if (right < nodeRight)
228 | {
229 | right = nodeRight;
230 | }
231 |
232 | if (top > nodeTop)
233 | {
234 | top = nodeTop;
235 | }
236 |
237 | if (bottom < nodeBottom)
238 | {
239 | bottom = nodeBottom;
240 | }
241 | }
242 |
243 | let boundingBox = {};
244 | boundingBox.topLeftX = left;
245 | boundingBox.topLeftY = top;
246 | boundingBox.width = right - left;
247 | boundingBox.height = bottom - top;
248 | return boundingBox;
249 | };
250 |
251 | // This function finds and returns parent nodes whose all children are hidden
252 | auxiliary.calcParentsWithoutChildren = function(cy, eles){
253 | let parentsWithoutChildren = cy.collection();
254 | eles.nodes(':parent').forEach((parent) => {
255 | let check = false;
256 | parent.children().forEach((child) => {
257 | if(child.css('display') != 'none') {
258 | check = true;
259 | }
260 | });
261 | if(!check) {
262 | parentsWithoutChildren.merge(parent);
263 | }
264 | });
265 |
266 | return parentsWithoutChildren;
267 | }
268 |
269 | module.exports = auxiliary;
--------------------------------------------------------------------------------
/src/fcose/cose.js:
--------------------------------------------------------------------------------
1 | /**
2 | The implementation of the postprocessing part that applies CoSE layout over the spectral layout
3 | */
4 |
5 | const aux = require('./auxiliary');
6 | const CoSELayout = require('cose-base').CoSELayout;
7 | const CoSENode = require('cose-base').CoSENode;
8 | const PointD = require('cose-base').layoutBase.PointD;
9 | const DimensionD = require('cose-base').layoutBase.DimensionD;
10 | const LayoutConstants = require('cose-base').layoutBase.LayoutConstants;
11 | const FDLayoutConstants = require('cose-base').layoutBase.FDLayoutConstants;
12 | const CoSEConstants = require('cose-base').CoSEConstants;
13 |
14 | // main function that cose layout is processed
15 | let coseLayout = function(options, spectralResult){
16 |
17 | let cy = options.cy;
18 | let eles = options.eles;
19 | let nodes = eles.nodes();
20 | let edges = eles.edges();
21 |
22 | let nodeIndexes;
23 | let xCoords;
24 | let yCoords;
25 | let idToLNode = {};
26 |
27 | if(options.randomize){
28 | nodeIndexes = spectralResult["nodeIndexes"];
29 | xCoords = spectralResult["xCoords"];
30 | yCoords = spectralResult["yCoords"];
31 | }
32 |
33 | const isFn = fn => typeof fn === 'function';
34 |
35 | const optFn = ( opt, ele ) => {
36 | if( isFn( opt ) ){
37 | return opt( ele );
38 | } else {
39 | return opt;
40 | }
41 | };
42 |
43 | /**** Postprocessing functions ****/
44 |
45 | let parentsWithoutChildren = aux.calcParentsWithoutChildren(cy, eles);
46 |
47 | // transfer cytoscape nodes to cose nodes
48 | let processChildrenList = function (parent, children, layout, options) {
49 | let size = children.length;
50 | for (let i = 0; i < size; i++) {
51 | let theChild = children[i];
52 | let children_of_children = null;
53 | if(theChild.intersection(parentsWithoutChildren).length == 0) {
54 | children_of_children = theChild.children();
55 | }
56 | let theNode;
57 |
58 | let dimensions = theChild.layoutDimensions({
59 | nodeDimensionsIncludeLabels: options.nodeDimensionsIncludeLabels
60 | });
61 |
62 | if (theChild.outerWidth() != null
63 | && theChild.outerHeight() != null) {
64 | if(options.randomize){
65 | if(!theChild.isParent()){
66 | theNode = parent.add(new CoSENode(layout.graphManager,
67 | new PointD(xCoords[nodeIndexes.get(theChild.id())] - dimensions.w / 2, yCoords[nodeIndexes.get(theChild.id())] - dimensions.h / 2),
68 | new DimensionD(parseFloat(dimensions.w), parseFloat(dimensions.h))));
69 | }
70 | else{
71 | let parentInfo = aux.calcBoundingBox(theChild, xCoords, yCoords, nodeIndexes);
72 | if(theChild.intersection(parentsWithoutChildren).length == 0) {
73 | theNode = parent.add(new CoSENode(layout.graphManager,
74 | new PointD(parentInfo.topLeftX, parentInfo.topLeftY),
75 | new DimensionD(parentInfo.width, parentInfo.height)));
76 | }
77 | else { // for the parentsWithoutChildren
78 | theNode = parent.add(new CoSENode(layout.graphManager,
79 | new PointD(parentInfo.topLeftX, parentInfo.topLeftY),
80 | new DimensionD(parseFloat(dimensions.w), parseFloat(dimensions.h))));
81 | }
82 | }
83 | }
84 | else{
85 | theNode = parent.add(new CoSENode(layout.graphManager,
86 | new PointD(theChild.position('x') - dimensions.w / 2, theChild.position('y') - dimensions.h / 2),
87 | new DimensionD(parseFloat(dimensions.w), parseFloat(dimensions.h))));
88 | }
89 | }
90 | else {
91 | theNode = parent.add(new CoSENode(this.graphManager));
92 | }
93 | // Attach id to the layout node and repulsion value
94 | theNode.id = theChild.data("id");
95 | theNode.nodeRepulsion = optFn( options.nodeRepulsion, theChild );
96 | // Attach the paddings of cy node to layout node
97 | theNode.paddingLeft = parseInt( theChild.css('padding') );
98 | theNode.paddingTop = parseInt( theChild.css('padding') );
99 | theNode.paddingRight = parseInt( theChild.css('padding') );
100 | theNode.paddingBottom = parseInt( theChild.css('padding') );
101 |
102 | //Attach the label properties to both compound and simple nodes if labels will be included in node dimensions
103 | //These properties will be used while updating bounds of compounds during iterations or tiling
104 | //and will be used for simple nodes while transferring final positions to cytoscape
105 | if(options.nodeDimensionsIncludeLabels){
106 | theNode.labelWidth = theChild.boundingBox({ includeLabels: true, includeNodes: false, includeOverlays: false }).w;
107 | theNode.labelHeight = theChild.boundingBox({ includeLabels: true, includeNodes: false, includeOverlays: false }).h;
108 | theNode.labelPosVertical = theChild.css("text-valign");
109 | theNode.labelPosHorizontal = theChild.css("text-halign");
110 | }
111 |
112 | // Map the layout node
113 | idToLNode[theChild.data("id")] = theNode;
114 |
115 | if (isNaN(theNode.rect.x)) {
116 | theNode.rect.x = 0;
117 | }
118 |
119 | if (isNaN(theNode.rect.y)) {
120 | theNode.rect.y = 0;
121 | }
122 |
123 | if (children_of_children != null && children_of_children.length > 0) {
124 | let theNewGraph;
125 | theNewGraph = layout.getGraphManager().add(layout.newGraph(), theNode);
126 | processChildrenList(theNewGraph, children_of_children, layout, options);
127 | }
128 | }
129 | };
130 |
131 | // transfer cytoscape edges to cose edges
132 | let processEdges = function(layout, gm, edges){
133 | let idealLengthTotal = 0;
134 | let edgeCount = 0;
135 | for (let i = 0; i < edges.length; i++) {
136 | let edge = edges[i];
137 | let sourceNode = idToLNode[edge.data("source")];
138 | let targetNode = idToLNode[edge.data("target")];
139 | if(sourceNode && targetNode && sourceNode !== targetNode && sourceNode.getEdgesBetween(targetNode).length == 0){
140 | let e1 = gm.add(layout.newEdge(), sourceNode, targetNode);
141 | e1.id = edge.id();
142 | e1.idealLength = optFn( options.idealEdgeLength, edge );
143 | e1.edgeElasticity = optFn( options.edgeElasticity, edge );
144 | idealLengthTotal += e1.idealLength;
145 | edgeCount++;
146 | }
147 | }
148 | // we need to update the ideal edge length constant with the avg. ideal length value after processing edges
149 | // in case there is no edge, use other options
150 | if (options.idealEdgeLength != null){
151 | if (edgeCount > 0)
152 | CoSEConstants.DEFAULT_EDGE_LENGTH = FDLayoutConstants.DEFAULT_EDGE_LENGTH = idealLengthTotal / edgeCount;
153 | else if(!isFn(options.idealEdgeLength)) // in case there is no edge, but option gives a value to use
154 | CoSEConstants.DEFAULT_EDGE_LENGTH = FDLayoutConstants.DEFAULT_EDGE_LENGTH = options.idealEdgeLength;
155 | else // in case there is no edge and we cannot get a value from option (because it's a function)
156 | CoSEConstants.DEFAULT_EDGE_LENGTH = FDLayoutConstants.DEFAULT_EDGE_LENGTH = 50;
157 | // we need to update these constant values based on the ideal edge length constant
158 | CoSEConstants.MIN_REPULSION_DIST = FDLayoutConstants.MIN_REPULSION_DIST = FDLayoutConstants.DEFAULT_EDGE_LENGTH / 10.0;
159 | CoSEConstants.DEFAULT_RADIAL_SEPARATION = FDLayoutConstants.DEFAULT_EDGE_LENGTH;
160 | }
161 | };
162 |
163 | // transfer cytoscape constraints to cose layout
164 | let processConstraints = function(layout, options){
165 | // get nodes to be fixed
166 | if(options.fixedNodeConstraint){
167 | layout.constraints["fixedNodeConstraint"] = options.fixedNodeConstraint;
168 | }
169 | // get nodes to be aligned
170 | if(options.alignmentConstraint){
171 | layout.constraints["alignmentConstraint"] = options.alignmentConstraint;
172 | }
173 | // get nodes to be relatively placed
174 | if(options.relativePlacementConstraint){
175 | layout.constraints["relativePlacementConstraint"] = options.relativePlacementConstraint;
176 | }
177 | };
178 |
179 | /**** Apply postprocessing ****/
180 | if (options.nestingFactor != null)
181 | CoSEConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR = FDLayoutConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR = options.nestingFactor;
182 | if (options.gravity != null)
183 | CoSEConstants.DEFAULT_GRAVITY_STRENGTH = FDLayoutConstants.DEFAULT_GRAVITY_STRENGTH = options.gravity;
184 | if (options.numIter != null)
185 | CoSEConstants.MAX_ITERATIONS = FDLayoutConstants.MAX_ITERATIONS = options.numIter;
186 | if (options.gravityRange != null)
187 | CoSEConstants.DEFAULT_GRAVITY_RANGE_FACTOR = FDLayoutConstants.DEFAULT_GRAVITY_RANGE_FACTOR = options.gravityRange;
188 | if(options.gravityCompound != null)
189 | CoSEConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH = options.gravityCompound;
190 | if(options.gravityRangeCompound != null)
191 | CoSEConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR = options.gravityRangeCompound;
192 | if (options.initialEnergyOnIncremental != null)
193 | CoSEConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL = FDLayoutConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL = options.initialEnergyOnIncremental;
194 |
195 | if (options.tilingCompareBy != null)
196 | CoSEConstants.TILING_COMPARE_BY = options.tilingCompareBy;
197 |
198 | if(options.quality == 'proof')
199 | LayoutConstants.QUALITY = 2;
200 | else
201 | LayoutConstants.QUALITY = 0;
202 |
203 | CoSEConstants.NODE_DIMENSIONS_INCLUDE_LABELS = FDLayoutConstants.NODE_DIMENSIONS_INCLUDE_LABELS = LayoutConstants.NODE_DIMENSIONS_INCLUDE_LABELS = options.nodeDimensionsIncludeLabels;
204 | CoSEConstants.DEFAULT_INCREMENTAL = FDLayoutConstants.DEFAULT_INCREMENTAL = LayoutConstants.DEFAULT_INCREMENTAL =
205 | !(options.randomize);
206 | CoSEConstants.ANIMATE = FDLayoutConstants.ANIMATE = LayoutConstants.ANIMATE = options.animate;
207 | CoSEConstants.TILE = options.tile;
208 | CoSEConstants.TILING_PADDING_VERTICAL =
209 | typeof options.tilingPaddingVertical === 'function' ? options.tilingPaddingVertical.call() : options.tilingPaddingVertical;
210 | CoSEConstants.TILING_PADDING_HORIZONTAL =
211 | typeof options.tilingPaddingHorizontal === 'function' ? options.tilingPaddingHorizontal.call() : options.tilingPaddingHorizontal;
212 |
213 | CoSEConstants.DEFAULT_INCREMENTAL = FDLayoutConstants.DEFAULT_INCREMENTAL = LayoutConstants.DEFAULT_INCREMENTAL = true;
214 | CoSEConstants.PURE_INCREMENTAL = !options.randomize;
215 | LayoutConstants.DEFAULT_UNIFORM_LEAF_NODE_SIZES = options.uniformNodeDimensions;
216 |
217 | // This part is for debug/demo purpose
218 | if(options.step == "transformed"){
219 | CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING = true;
220 | CoSEConstants.ENFORCE_CONSTRAINTS = false;
221 | CoSEConstants.APPLY_LAYOUT = false;
222 | }
223 | if(options.step == "enforced"){
224 | CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING = false;
225 | CoSEConstants.ENFORCE_CONSTRAINTS = true;
226 | CoSEConstants.APPLY_LAYOUT = false;
227 | }
228 | if(options.step == "cose"){
229 | CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING = false;
230 | CoSEConstants.ENFORCE_CONSTRAINTS = false;
231 | CoSEConstants.APPLY_LAYOUT = true;
232 | }
233 | if(options.step == "all"){
234 | if(options.randomize)
235 | CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING = true;
236 | else
237 | CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING = false;
238 | CoSEConstants.ENFORCE_CONSTRAINTS = true;
239 | CoSEConstants.APPLY_LAYOUT = true;
240 | }
241 |
242 | if(options.fixedNodeConstraint || options.alignmentConstraint || options.relativePlacementConstraint){
243 | CoSEConstants.TREE_REDUCTION_ON_INCREMENTAL = false;
244 | }
245 | else{
246 | CoSEConstants.TREE_REDUCTION_ON_INCREMENTAL = true;
247 | }
248 |
249 | let coseLayout = new CoSELayout();
250 | let gm = coseLayout.newGraphManager();
251 |
252 | processChildrenList(gm.addRoot(), aux.getTopMostNodes(nodes), coseLayout, options);
253 | processEdges(coseLayout, gm, edges);
254 | processConstraints(coseLayout, options);
255 |
256 | coseLayout.runLayout();
257 |
258 | return idToLNode;
259 | };
260 |
261 | module.exports = { coseLayout };
262 |
--------------------------------------------------------------------------------
/src/fcose/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | The implementation of the fcose layout algorithm
3 | */
4 |
5 | const assign = require('../assign');
6 | const aux = require('./auxiliary');
7 | const { spectralLayout } = require('./spectral');
8 | const { coseLayout } = require('./cose');
9 |
10 | const defaults = Object.freeze({
11 |
12 | // 'draft', 'default' or 'proof'
13 | // - 'draft' only applies spectral layout
14 | // - 'default' improves the quality with subsequent CoSE layout (fast cooling rate)
15 | // - 'proof' improves the quality with subsequent CoSE layout (slow cooling rate)
16 | quality: "default",
17 | // Use random node positions at beginning of layout
18 | // if this is set to false, then quality option must be "proof"
19 | randomize: true,
20 | // Whether or not to animate the layout
21 | animate: true,
22 | // Duration of animation in ms, if enabled
23 | animationDuration: 1000,
24 | // Easing of animation, if enabled
25 | animationEasing: undefined,
26 | // Fit the viewport to the repositioned nodes
27 | fit: true,
28 | // Padding around layout
29 | padding: 30,
30 | // Whether to include labels in node dimensions. Valid in "proof" quality
31 | nodeDimensionsIncludeLabels: false,
32 | // Whether or not simple nodes (non-compound nodes) are of uniform dimensions
33 | uniformNodeDimensions: false,
34 | // Whether to pack disconnected components - valid only if randomize: true
35 | packComponents: true,
36 | // Layout step - all, transformed, enforced, cose - for debug purpose only
37 | step: "all",
38 |
39 | /* spectral layout options */
40 |
41 | // False for random, true for greedy
42 | samplingType: true,
43 | // Sample size to construct distance matrix
44 | sampleSize: 25,
45 | // Separation amount between nodes
46 | nodeSeparation: 75,
47 | // Power iteration tolerance
48 | piTol: 0.0000001,
49 |
50 | /* CoSE layout options */
51 |
52 | // Node repulsion (non overlapping) multiplier
53 | nodeRepulsion: node => 4500,
54 | // Ideal edge (non nested) length
55 | idealEdgeLength: edge => 50,
56 | // Divisor to compute edge forces
57 | edgeElasticity: edge => 0.45,
58 | // Nesting factor (multiplier) to compute ideal edge length for nested edges
59 | nestingFactor: 0.1,
60 | // Gravity force (constant)
61 | gravity: 0.25,
62 | // Maximum number of iterations to perform
63 | numIter: 2500,
64 | // For enabling tiling
65 | tile: true,
66 | // The function that specifies the criteria for comparing nodes while sorting them during tiling operation.
67 | // Takes the node id as a parameter and the default tiling operation is perfomed when this option is not set.
68 | tilingCompareBy: undefined,
69 | // Represents the amount of the vertical space to put between the zero degree members during the tiling operation(can also be a function)
70 | tilingPaddingVertical: 10,
71 | // Represents the amount of the horizontal space to put between the zero degree members during the tiling operation(can also be a function)
72 | tilingPaddingHorizontal: 10,
73 | // Gravity range (constant) for compounds
74 | gravityRangeCompound: 1.5,
75 | // Gravity force (constant) for compounds
76 | gravityCompound: 1.0,
77 | // Gravity range (constant)
78 | gravityRange: 3.8,
79 | // Initial cooling factor for incremental layout
80 | initialEnergyOnIncremental: 0.3,
81 |
82 | /* constraint options */
83 |
84 | // Fix required nodes to predefined positions
85 | // [{nodeId: 'n1', position: {x: 100, y: 200}, {...}]
86 | fixedNodeConstraint: undefined,
87 | // Align required nodes in vertical/horizontal direction
88 | // {vertical: [['n1', 'n2')], ['n3', 'n4']], horizontal: ['n2', 'n4']}
89 | alignmentConstraint: undefined,
90 | // Place two nodes relatively in vertical/horizontal direction
91 | // [{top: 'n1', bottom: 'n2', gap: 100}, {left: 'n3', right: 'n4', gap: 75}]
92 | relativePlacementConstraint: undefined,
93 |
94 | /* layout event callbacks */
95 | ready: () => {}, // on layoutready
96 | stop: () => {} // on layoutstop
97 | });
98 |
99 | class Layout {
100 | constructor( options ){
101 | this.options = assign( {}, defaults, options );
102 | }
103 |
104 | run(){
105 | let layout = this;
106 | let options = this.options;
107 | let cy = options.cy;
108 | let eles = options.eles;
109 |
110 | let spectralResult = [];
111 | let xCoords;
112 | let yCoords;
113 | let coseResult = [];
114 | let components;
115 | let componentCenters = [];
116 |
117 | // basic validity check for constraint inputs
118 | if(options.fixedNodeConstraint && (!Array.isArray(options.fixedNodeConstraint) || options.fixedNodeConstraint.length == 0)){
119 | options.fixedNodeConstraint = undefined;
120 | }
121 |
122 | if(options.alignmentConstraint){
123 | if(options.alignmentConstraint.vertical && (!Array.isArray(options.alignmentConstraint.vertical) || options.alignmentConstraint.vertical.length == 0)){
124 | options.alignmentConstraint.vertical = undefined;
125 | }
126 | if(options.alignmentConstraint.horizontal && (!Array.isArray(options.alignmentConstraint.horizontal) || options.alignmentConstraint.horizontal.length == 0)){
127 | options.alignmentConstraint.horizontal = undefined;
128 | }
129 |
130 | }
131 |
132 | if(options.relativePlacementConstraint && (!Array.isArray(options.relativePlacementConstraint) || options.relativePlacementConstraint.length == 0)){
133 | options.relativePlacementConstraint = undefined;
134 | }
135 |
136 | // if any constraint exists, set some options
137 | let constraintExist = options.fixedNodeConstraint || options.alignmentConstraint || options.relativePlacementConstraint;
138 | if(constraintExist){
139 | // constraints work with these options
140 | options.tile = false;
141 | options.packComponents = false;
142 | }
143 |
144 | // decide component packing is enabled or not
145 | let layUtil;
146 | let packingEnabled = false;
147 | if(cy.layoutUtilities && options.packComponents){
148 | layUtil = cy.layoutUtilities("get");
149 | if(!layUtil)
150 | layUtil = cy.layoutUtilities();
151 | packingEnabled = true;
152 | }
153 |
154 | if(eles.nodes().length > 0) {
155 | // if packing is not enabled, perform layout on the whole graph
156 | if(!packingEnabled){
157 | // store component center
158 | let boundingBox = options.eles.boundingBox();
159 | componentCenters.push({x: boundingBox.x1 + boundingBox.w / 2, y: boundingBox.y1 + boundingBox.h / 2});
160 | // apply spectral layout
161 | if(options.randomize){
162 | let result = spectralLayout(options);
163 | spectralResult.push(result);
164 | }
165 | // apply cose layout as postprocessing
166 | if(options.quality == "default" || options.quality == "proof"){
167 | coseResult.push(coseLayout(options, spectralResult[0]));
168 | aux.relocateComponent(componentCenters[0], coseResult[0], options); // relocate center to original position
169 | }
170 | else{
171 | aux.relocateComponent(componentCenters[0], spectralResult[0], options); // relocate center to original position
172 | }
173 | }
174 | else{ // packing is enabled
175 | let topMostNodes = aux.getTopMostNodes(options.eles.nodes());
176 | components = aux.connectComponents(cy, options.eles, topMostNodes);
177 | // store component centers
178 | components.forEach(function(component){
179 | let boundingBox = component.boundingBox();
180 | componentCenters.push({x: boundingBox.x1 + boundingBox.w / 2, y: boundingBox.y1 + boundingBox.h / 2});
181 | });
182 |
183 | //send each component to spectral layout if randomized
184 | if(options.randomize){
185 | components.forEach(function(component){
186 | options.eles = component;
187 | spectralResult.push(spectralLayout(options));
188 | });
189 | }
190 |
191 | if(options.quality == "default" || options.quality == "proof"){
192 | let toBeTiledNodes = cy.collection();
193 | if(options.tile){ // behave nodes to be tiled as one component
194 | let nodeIndexes = new Map();
195 | let xCoords = [];
196 | let yCoords = [];
197 | let count = 0;
198 | let tempSpectralResult = {nodeIndexes: nodeIndexes, xCoords: xCoords, yCoords: yCoords};
199 | let indexesToBeDeleted = [];
200 | components.forEach(function(component, index){
201 | if(component.edges().length == 0){
202 | component.nodes().forEach(function(node, i){
203 | toBeTiledNodes.merge(component.nodes()[i]);
204 | if(!node.isParent()){
205 | tempSpectralResult.nodeIndexes.set(component.nodes()[i].id(), count++);
206 | tempSpectralResult.xCoords.push(component.nodes()[0].position().x);
207 | tempSpectralResult.yCoords.push(component.nodes()[0].position().y);
208 | }
209 | });
210 | indexesToBeDeleted.push(index);
211 | }
212 | });
213 | if(toBeTiledNodes.length > 1){
214 | let boundingBox = toBeTiledNodes.boundingBox();
215 | componentCenters.push({x: boundingBox.x1 + boundingBox.w / 2, y: boundingBox.y1 + boundingBox.h / 2});
216 | components.push(toBeTiledNodes);
217 | spectralResult.push(tempSpectralResult);
218 | for(let i = indexesToBeDeleted.length-1; i >= 0; i--){
219 | components.splice(indexesToBeDeleted[i], 1);
220 | spectralResult.splice(indexesToBeDeleted[i], 1);
221 | componentCenters.splice(indexesToBeDeleted[i], 1);
222 | };
223 | }
224 | }
225 | components.forEach(function(component, index){ // send each component to cose layout
226 | options.eles = component;
227 | coseResult.push(coseLayout(options, spectralResult[index]));
228 | aux.relocateComponent(componentCenters[index], coseResult[index], options); // relocate center to original position
229 | });
230 | }
231 | else {
232 | components.forEach(function(component, index){
233 | aux.relocateComponent(componentCenters[index], spectralResult[index], options); // relocate center to original position
234 | });
235 | }
236 |
237 | // packing
238 | let componentsEvaluated = new Set();
239 | if(components.length > 1){
240 | let subgraphs = [];
241 | let hiddenEles = eles.filter((ele) => {return ele.css('display') == 'none'});
242 | components.forEach(function(component, index){
243 | let nodeIndexes;
244 | if(options.quality == "draft"){
245 | nodeIndexes = spectralResult[index].nodeIndexes;
246 | }
247 |
248 | if(component.nodes().not(hiddenEles).length > 0) {
249 | let subgraph = {};
250 | subgraph.edges = [];
251 | subgraph.nodes = [];
252 | let nodeIndex;
253 | component.nodes().not(hiddenEles).forEach(function (node) {
254 | if(options.quality == "draft"){
255 | if(!node.isParent()){
256 | nodeIndex = nodeIndexes.get(node.id());
257 | subgraph.nodes.push({x: spectralResult[index].xCoords[nodeIndex] - node.boundingbox().w/2, y: spectralResult[index].yCoords[nodeIndex] - node.boundingbox().h/2, width: node.boundingbox().w, height: node.boundingbox().h});
258 | }
259 | else{
260 | let parentInfo = aux.calcBoundingBox(node, spectralResult[index].xCoords, spectralResult[index].yCoords, nodeIndexes);
261 | subgraph.nodes.push({x: parentInfo.topLeftX, y: parentInfo.topLeftY, width: parentInfo.width, height: parentInfo.height});
262 | }
263 | }
264 | else{
265 | if(coseResult[index][node.id()]) {
266 | subgraph.nodes.push({x: coseResult[index][node.id()].getLeft(), y: coseResult[index][node.id()].getTop(), width: coseResult[index][node.id()].getWidth(), height: coseResult[index][node.id()].getHeight()});
267 | }
268 | }
269 | });
270 | component.edges().forEach(function (edge) {
271 | let source = edge.source();
272 | let target = edge.target();
273 | if(source.css("display") != "none" && target.css("display") != "none") {
274 | if(options.quality == "draft"){
275 | let sourceNodeIndex = nodeIndexes.get(source.id());
276 | let targetNodeIndex = nodeIndexes.get(target.id());
277 | let sourceCenter = [];
278 | let targetCenter = [];
279 | if(source.isParent()){
280 | let parentInfo = aux.calcBoundingBox(source, spectralResult[index].xCoords, spectralResult[index].yCoords, nodeIndexes);
281 | sourceCenter.push(parentInfo.topLeftX + parentInfo.width / 2);
282 | sourceCenter.push(parentInfo.topLeftY + parentInfo.height / 2);
283 | }
284 | else{
285 | sourceCenter.push(spectralResult[index].xCoords[sourceNodeIndex]);
286 | sourceCenter.push(spectralResult[index].yCoords[sourceNodeIndex]);
287 | }
288 | if(target.isParent()){
289 | let parentInfo = aux.calcBoundingBox(target, spectralResult[index].xCoords, spectralResult[index].yCoords, nodeIndexes);
290 | targetCenter.push(parentInfo.topLeftX + parentInfo.width / 2);
291 | targetCenter.push(parentInfo.topLeftY + parentInfo.height / 2);
292 | }
293 | else{
294 | targetCenter.push(spectralResult[index].xCoords[targetNodeIndex]);
295 | targetCenter.push(spectralResult[index].yCoords[targetNodeIndex]);
296 | }
297 | subgraph.edges.push({startX: sourceCenter[0], startY: sourceCenter[1], endX: targetCenter[0], endY: targetCenter[1]});
298 | }
299 | else{
300 | if(coseResult[index][source.id()] && coseResult[index][target.id()]) {
301 | subgraph.edges.push({startX: coseResult[index][source.id()].getCenterX(), startY: coseResult[index][source.id()].getCenterY(), endX: coseResult[index][target.id()].getCenterX(), endY: coseResult[index][target.id()].getCenterY()});
302 | }
303 | }
304 | }
305 | });
306 | if(subgraph.nodes.length > 0) {
307 | subgraphs.push(subgraph);
308 | componentsEvaluated.add(index);
309 | }
310 | }
311 | });
312 | let shiftResult = layUtil.packComponents(subgraphs, options.randomize).shifts;
313 | if(options.quality == "draft"){
314 | spectralResult.forEach(function(result, index){
315 | let newXCoords = result.xCoords.map(x => x + shiftResult[index].dx);
316 | let newYCoords = result.yCoords.map(y => y + shiftResult[index].dy);
317 | result.xCoords = newXCoords;
318 | result.yCoords = newYCoords;
319 | });
320 | }
321 | else{
322 | let count = 0;
323 | componentsEvaluated.forEach((index) => {
324 | Object.keys(coseResult[index]).forEach(function (item) {
325 | let nodeRectangle = coseResult[index][item];
326 | nodeRectangle.setCenter(nodeRectangle.getCenterX() + shiftResult[count].dx, nodeRectangle.getCenterY() + shiftResult[count].dy);
327 | });
328 | count++;
329 | })
330 | }
331 | }
332 | }
333 | }
334 |
335 | // get each element's calculated position
336 | let getPositions = function(ele, i ){
337 | if(options.quality == "default" || options.quality == "proof") {
338 | if(typeof ele === "number") {
339 | ele = i;
340 | }
341 | let pos;
342 | let node;
343 | let theId = ele.data('id');
344 | coseResult.forEach(function(result){
345 | if (theId in result){
346 | pos = {x: result[theId].getRect().getCenterX(), y: result[theId].getRect().getCenterY()};
347 | node = result[theId];
348 | }
349 | });
350 | if(options.nodeDimensionsIncludeLabels){
351 | if(node.labelWidth){
352 | if(node.labelPosHorizontal == "left"){
353 | pos.x += node.labelWidth/2;
354 | }
355 | else if(node.labelPosHorizontal == "right"){
356 | pos.x -= node.labelWidth/2;
357 | }
358 | }
359 | if(node.labelHeight){
360 | if(node.labelPosVertical == "top"){
361 | pos.y += node.labelHeight/2;
362 | }
363 | else if(node.labelPosVertical == "bottom"){
364 | pos.y -= node.labelHeight/2;
365 | }
366 | }
367 | }
368 | if(pos == undefined)
369 | pos = {x: ele.position("x"), y: ele.position("y")};
370 | return {
371 | x: pos.x,
372 | y: pos.y
373 | };
374 | }
375 | else{
376 | let pos;
377 | spectralResult.forEach(function(result){
378 | let index = result.nodeIndexes.get(ele.id());
379 | if(index != undefined){
380 | pos = {x: result.xCoords[index], y: result.yCoords[index]};
381 | }
382 | });
383 | if(pos == undefined)
384 | pos = {x: ele.position("x"), y: ele.position("y")};
385 | return {
386 | x: pos.x,
387 | y: pos.y
388 | };
389 | }
390 | };
391 |
392 | // quality = "draft" and randomize = false are contradictive so in that case positions don't change
393 | if(options.quality == "default" || options.quality == "proof" || options.randomize) {
394 | // transfer calculated positions to nodes (positions of only simple nodes are evaluated, compounds are positioned automatically)
395 | let parentsWithoutChildren = aux.calcParentsWithoutChildren(cy, eles);
396 | let hiddenEles = eles.filter((ele) => {return ele.css('display') == 'none'});
397 | options.eles = eles.not(hiddenEles);
398 |
399 | eles.nodes().not(":parent").not(hiddenEles).layoutPositions(layout, options, getPositions);
400 |
401 | if(parentsWithoutChildren.length > 0){
402 | parentsWithoutChildren.forEach((ele) => {
403 | ele.position(getPositions(ele));
404 | });
405 | }
406 | }
407 | else{
408 | console.log("If randomize option is set to false, then quality option must be 'default' or 'proof'.");
409 | }
410 |
411 | }
412 | }
413 |
414 | module.exports = Layout;
--------------------------------------------------------------------------------
/src/fcose/spectral.js:
--------------------------------------------------------------------------------
1 | /**
2 | The implementation of the spectral layout that is the first part of the fcose layout algorithm
3 | */
4 |
5 | const aux = require('./auxiliary');
6 | const Matrix = require('cose-base').layoutBase.Matrix;
7 | const SVD = require('cose-base').layoutBase.SVD;
8 |
9 | // main function that spectral layout is processed
10 | let spectralLayout = function(options){
11 |
12 | let cy = options.cy;
13 | let eles = options.eles;
14 | let nodes = eles.nodes();
15 | let parentNodes = eles.nodes(":parent");
16 |
17 | let dummyNodes = new Map(); // map to keep dummy nodes and their neighbors
18 | let nodeIndexes = new Map(); // map to keep indexes to nodes
19 | let parentChildMap = new Map(); // mapping btw. compound and its representative node
20 | let allNodesNeighborhood = []; // array to keep neighborhood of all nodes
21 | let xCoords = [];
22 | let yCoords = [];
23 |
24 | let samplesColumn = []; // sampled vertices
25 | let minDistancesColumn = [];
26 | let C = []; // column sampling matrix
27 | let PHI = []; // intersection of column and row sampling matrices
28 | let INV = []; // inverse of PHI
29 |
30 | let firstSample; // the first sampled node
31 | let nodeSize;
32 |
33 | const infinity = 100000000;
34 | const small = 0.000000001;
35 |
36 | let piTol = options.piTol;
37 | let samplingType = options.samplingType; // false for random, true for greedy
38 | let nodeSeparation = options.nodeSeparation;
39 | let sampleSize;
40 |
41 | /**** Spectral-preprocessing functions ****/
42 |
43 | /**** Spectral layout functions ****/
44 |
45 | // determine which columns to be sampled
46 | let randomSampleCR = function() {
47 | let sample = 0;
48 | let count = 0;
49 | let flag = false;
50 |
51 | while(count < sampleSize){
52 | sample = Math.floor(Math.random() * nodeSize);
53 |
54 | flag = false;
55 | for(let i = 0; i < count; i++){
56 | if(samplesColumn[i] == sample){
57 | flag = true;
58 | break;
59 | }
60 | }
61 |
62 | if(!flag){
63 | samplesColumn[count] = sample;
64 | count++;
65 | }
66 | else{
67 | continue;
68 | }
69 | }
70 | };
71 |
72 | // takes the index of the node(pivot) to initiate BFS as a parameter
73 | let BFS = function(pivot, index, samplingMethod){
74 | let path = []; // the front of the path
75 | let front = 0; // the back of the path
76 | let back = 0;
77 | let current = 0;
78 | let temp;
79 | let distance = [];
80 |
81 | let max_dist = 0; // the furthest node to be returned
82 | let max_ind = 1;
83 |
84 | for(let i = 0; i < nodeSize; i++){
85 | distance[i] = infinity;
86 | }
87 |
88 | path[back] = pivot;
89 | distance[pivot] = 0;
90 |
91 | while(back >= front){
92 | current = path[front++];
93 | let neighbors = allNodesNeighborhood[current];
94 | for(let i = 0; i < neighbors.length; i++){
95 | temp = nodeIndexes.get(neighbors[i]);
96 | if(distance[temp] == infinity){
97 | distance[temp] = distance[current] + 1;
98 | path[++back] = temp;
99 | }
100 | }
101 | C[current][index] = distance[current] * nodeSeparation;
102 | }
103 |
104 | if(samplingMethod){
105 | for(let i = 0; i < nodeSize; i++){
106 | if(C[i][index] < minDistancesColumn[i])
107 | minDistancesColumn[i] = C[i][index];
108 | }
109 |
110 | for(let i = 0; i < nodeSize; i++){
111 | if(minDistancesColumn[i] > max_dist ){
112 | max_dist = minDistancesColumn[i];
113 | max_ind = i;
114 |
115 | }
116 | }
117 | }
118 | return max_ind;
119 | };
120 |
121 | // apply BFS to all nodes or selected samples
122 | let allBFS = function(samplingMethod){
123 |
124 | let sample;
125 |
126 | if(!samplingMethod){
127 | randomSampleCR();
128 |
129 | // call BFS
130 | for(let i = 0; i < sampleSize; i++){
131 | BFS(samplesColumn[i], i, samplingMethod, false);
132 | }
133 | }
134 | else{
135 | sample = Math.floor(Math.random() * nodeSize);
136 | firstSample = sample;
137 |
138 | for(let i = 0; i < nodeSize; i++){
139 | minDistancesColumn[i] = infinity;
140 | }
141 |
142 | for(let i = 0; i < sampleSize; i++){
143 | samplesColumn[i] = sample;
144 | sample = BFS(sample, i, samplingMethod);
145 | }
146 |
147 | }
148 |
149 | // form the squared distances for C
150 | for(let i = 0; i < nodeSize; i++){
151 | for(let j = 0; j < sampleSize; j++){
152 | C[i][j] *= C[i][j];
153 | }
154 | }
155 |
156 | // form PHI
157 | for(let i = 0; i < sampleSize; i++){
158 | PHI[i] = [];
159 | }
160 |
161 | for(let i = 0; i < sampleSize; i++){
162 | for(let j = 0; j < sampleSize; j++){
163 | PHI[i][j] = C[samplesColumn[j]][i];
164 | }
165 | }
166 | };
167 |
168 | // perform the SVD algorithm and apply a regularization step
169 | let sample = function(){
170 |
171 | let SVDResult = SVD.svd(PHI);
172 |
173 | let a_q = SVDResult.S;
174 | let a_u = SVDResult.U;
175 | let a_v = SVDResult.V;
176 |
177 | let max_s = a_q[0]*a_q[0]*a_q[0];
178 |
179 | let a_Sig = [];
180 |
181 | // regularization
182 | for(let i = 0; i < sampleSize; i++){
183 | a_Sig[i] = [];
184 | for(let j = 0; j < sampleSize; j++){
185 | a_Sig[i][j] = 0;
186 | if(i == j){
187 | a_Sig[i][j] = a_q[i]/(a_q[i]*a_q[i] + max_s/(a_q[i]*a_q[i]));
188 | }
189 | }
190 | }
191 |
192 | INV = Matrix.multMat(Matrix.multMat(a_v, a_Sig), Matrix.transpose(a_u));
193 |
194 | };
195 |
196 | // calculate final coordinates
197 | let powerIteration = function(){
198 | // two largest eigenvalues
199 | let theta1;
200 | let theta2;
201 |
202 | // initial guesses for eigenvectors
203 | let Y1 = [];
204 | let Y2 = [];
205 |
206 | let V1 = [];
207 | let V2 = [];
208 |
209 | for(let i = 0; i < nodeSize; i++){
210 | Y1[i] = Math.random();
211 | Y2[i] = Math.random();
212 | }
213 |
214 | Y1 = Matrix.normalize(Y1);
215 | Y2 = Matrix.normalize(Y2);
216 |
217 | let count = 0;
218 | // to keep track of the improvement ratio in power iteration
219 | let current = small;
220 | let previous = small;
221 |
222 | let temp;
223 |
224 | while(true){
225 | count++;
226 |
227 | for(let i = 0; i < nodeSize; i++){
228 | V1[i] = Y1[i];
229 | }
230 |
231 | Y1 = Matrix.multGamma(Matrix.multL(Matrix.multGamma(V1), C, INV));
232 | theta1 = Matrix.dotProduct(V1, Y1);
233 | Y1 = Matrix.normalize(Y1);
234 |
235 | current = Matrix.dotProduct(V1, Y1);
236 |
237 | temp = Math.abs(current/previous);
238 |
239 | if(temp <= 1 + piTol && temp >= 1){
240 | break;
241 | }
242 |
243 | previous = current;
244 | }
245 |
246 | for(let i = 0; i < nodeSize; i++){
247 | V1[i] = Y1[i];
248 | }
249 |
250 | count = 0;
251 | previous = small;
252 | while(true){
253 | count++;
254 |
255 | for(let i = 0; i < nodeSize; i++){
256 | V2[i] = Y2[i];
257 | }
258 |
259 | V2 = Matrix.minusOp(V2, Matrix.multCons(V1, (Matrix.dotProduct(V1, V2))));
260 | Y2 = Matrix.multGamma(Matrix.multL(Matrix.multGamma(V2), C, INV));
261 | theta2 = Matrix.dotProduct(V2, Y2);
262 | Y2 = Matrix.normalize(Y2);
263 |
264 | current = Matrix.dotProduct(V2, Y2);
265 |
266 | temp = Math.abs(current/previous);
267 |
268 | if(temp <= 1 + piTol && temp >= 1){
269 | break;
270 | }
271 |
272 | previous = current;
273 | }
274 |
275 | for(let i = 0; i < nodeSize; i++){
276 | V2[i] = Y2[i];
277 | }
278 |
279 | // theta1 now contains dominant eigenvalue
280 | // theta2 now contains the second-largest eigenvalue
281 | // V1 now contains theta1's eigenvector
282 | // V2 now contains theta2's eigenvector
283 |
284 | //populate the two vectors
285 | xCoords = Matrix.multCons(V1, Math.sqrt(Math.abs(theta1)));
286 | yCoords = Matrix.multCons(V2, Math.sqrt(Math.abs(theta2)));
287 |
288 | };
289 |
290 | /**** Preparation for spectral layout (Preprocessing) ****/
291 |
292 | // connect disconnected components (first top level, then inside of each compound node)
293 | aux.connectComponents(cy, eles, aux.getTopMostNodes(nodes), dummyNodes);
294 |
295 | parentNodes.forEach(function( ele ){
296 | aux.connectComponents(cy, eles, aux.getTopMostNodes(ele.descendants().intersection(eles)), dummyNodes);
297 | });
298 |
299 | // assign indexes to nodes (first real, then dummy nodes)
300 | let index = 0;
301 | for(let i = 0; i < nodes.length; i++){
302 | if(!nodes[i].isParent()){
303 | nodeIndexes.set(nodes[i].id(), index++);
304 | }
305 | }
306 |
307 | for (let key of dummyNodes.keys()) {
308 | nodeIndexes.set(key, index++);
309 | }
310 |
311 | // instantiate the neighborhood matrix
312 | for(let i = 0; i < nodeIndexes.size; i++){
313 | allNodesNeighborhood[i] = [];
314 | }
315 |
316 | // form a parent-child map to keep representative node of each compound node
317 | parentNodes.forEach(function( ele ){
318 | let children = ele.children().intersection(eles);
319 |
320 | // let random = 0;
321 | while(children.nodes(":childless").length == 0){
322 | // random = Math.floor(Math.random() * children.nodes().length); // if all children are compound then proceed randomly
323 | children = children.nodes()[0].children().intersection(eles);
324 | }
325 | // select the representative node - we can apply different methods here
326 | // random = Math.floor(Math.random() * children.nodes(":childless").length);
327 | let index = 0;
328 | let min = children.nodes(":childless")[0].connectedEdges().length;
329 | children.nodes(":childless").forEach(function(ele2, i){
330 | if(ele2.connectedEdges().length < min){
331 | min = ele2.connectedEdges().length;
332 | index = i;
333 | }
334 | });
335 | parentChildMap.set(ele.id(), children.nodes(":childless")[index].id());
336 | });
337 |
338 | // add neighborhood relations (first real, then dummy nodes)
339 | nodes.forEach(function( ele ){
340 | let eleIndex;
341 |
342 | if(ele.isParent())
343 | eleIndex = nodeIndexes.get(parentChildMap.get(ele.id()));
344 | else
345 | eleIndex = nodeIndexes.get(ele.id());
346 |
347 | ele.neighborhood().nodes().forEach(function(node){
348 | if(eles.intersection(ele.edgesWith(node)).length > 0){
349 | if(node.isParent())
350 | allNodesNeighborhood[eleIndex].push(parentChildMap.get(node.id()));
351 | else
352 | allNodesNeighborhood[eleIndex].push(node.id());
353 | }
354 | });
355 | });
356 |
357 | for (let key of dummyNodes.keys()) {
358 | let eleIndex = nodeIndexes.get(key);
359 | let disconnectedId;
360 | dummyNodes.get(key).forEach(function(id){
361 | if(cy.getElementById(id).isParent())
362 | disconnectedId = parentChildMap.get(id);
363 | else
364 | disconnectedId = id;
365 |
366 | allNodesNeighborhood[eleIndex].push(disconnectedId);
367 | allNodesNeighborhood[nodeIndexes.get(disconnectedId)].push(key);
368 | });
369 | }
370 |
371 | // nodeSize now only considers the size of transformed graph
372 | nodeSize = nodeIndexes.size;
373 |
374 | let spectralResult;
375 |
376 | // If number of nodes in transformed graph is 1 or 2, either SVD or powerIteration causes problem
377 | // So skip spectral and layout the graph with cose
378 | if(nodeSize > 2) {
379 | // if # of nodes in transformed graph is smaller than sample size,
380 | // then use # of nodes as sample size
381 | sampleSize = nodeSize < options.sampleSize ? nodeSize : options.sampleSize;
382 |
383 | // instantiates the partial matrices that will be used in spectral layout
384 | for(let i = 0; i < nodeSize; i++){
385 | C[i] = [];
386 | }
387 | for(let i = 0; i < sampleSize; i++){
388 | INV[i] = [];
389 | }
390 |
391 | /**** Apply spectral layout ****/
392 |
393 | if(options.quality == "draft" || options.step == "all"){
394 | allBFS(samplingType);
395 | sample();
396 | powerIteration();
397 |
398 | spectralResult = { nodeIndexes: nodeIndexes, xCoords: xCoords, yCoords: yCoords };
399 | }
400 | else{
401 | nodeIndexes.forEach(function(value, key){
402 | xCoords.push(cy.getElementById(key).position("x"));
403 | yCoords.push(cy.getElementById(key).position("y"));
404 | });
405 | spectralResult = { nodeIndexes: nodeIndexes, xCoords: xCoords, yCoords: yCoords };
406 | }
407 | return spectralResult;
408 | }
409 | else {
410 | let iterator = nodeIndexes.keys();
411 | let firstNode = cy.getElementById(iterator.next().value);
412 | let firstNodePos = firstNode.position();
413 | let firstNodeWidth = firstNode.outerWidth();
414 | xCoords.push(firstNodePos.x);
415 | yCoords.push(firstNodePos.y);
416 | if(nodeSize == 2){
417 | let secondNode = cy.getElementById(iterator.next().value);
418 | let secondNodeWidth = secondNode.outerWidth();
419 | xCoords.push(firstNodePos.x + firstNodeWidth / 2 + secondNodeWidth / 2 + options.idealEdgeLength);
420 | yCoords.push(firstNodePos.y);
421 | }
422 |
423 | spectralResult = { nodeIndexes: nodeIndexes, xCoords: xCoords, yCoords: yCoords };
424 | return spectralResult;
425 | }
426 | };
427 |
428 | module.exports = { spectralLayout };
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const impl = require('./fcose');
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', 'fcose', 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 |
--------------------------------------------------------------------------------
/test/example.js:
--------------------------------------------------------------------------------
1 | const chai = require('chai');
2 |
3 | describe('This', function(){
4 | it('does that', function(){
5 | expect( true ).to.be.true;
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/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 | globalObject: 'this'
20 | },
21 | module: {
22 | rules: [
23 | { test: /\.js$/, exclude: /node_modules/, use: 'babel-loader' }
24 | ]
25 | },
26 | optimization: {
27 | minimize: MIN ? true : false,
28 | },
29 | externals: PROD ? {
30 | 'cose-base': {
31 | commonjs2: 'cose-base',
32 | commonjs: 'cose-base',
33 | amd: 'cose-base',
34 | root: 'coseBase'
35 | }
36 | } : {}
37 | };
38 |
39 | module.exports = config;
40 |
--------------------------------------------------------------------------------