cytoscape-fcose
Advanced tools
Comparing version 1.2.3 to 2.0.0
{ | ||
"name": "cytoscape-fcose", | ||
"version": "1.2.3", | ||
"version": "2.0.0", | ||
"description": "The fCoSE layout for Cytoscape.js by Bilkent with fast compound node placement", | ||
@@ -54,4 +54,4 @@ "main": "cytoscape-fcose.js", | ||
"dependencies": { | ||
"cose-base": "^1.0.0" | ||
"cose-base": "^2.0.0" | ||
} | ||
} |
@@ -7,6 +7,21 @@ cytoscape-fcose | ||
fCoSE (fast Compound Spring Embedder) 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 ([demo](https://ivis-at-bilkent.github.io/cytoscape.js-fcose/demo.html), [compound demo](https://ivis-at-bilkent.github.io/cytoscape.js-fcose/demo-compound.html)) | ||
fCoSE (fast Compound Spring Embedder) 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. | ||
fCoSE layout algorithm combines the speed of spectral layout with the aesthetics of force-directed layout. fCoSE runs up to 10 times as fast as CoSE while achieving similar aesthetics. In addition, fCoSE supports varying (non-uniform) node dimensions similar to its predecessor CoSE. | ||
<p align="center"> <a href="https://ivis-at-bilkent.github.io/cytoscape.js-fcose/demo/demo.html" title="Simple Demo">DEMO (simple)</a> | ||
| ||
<a href="https://ivis-at-bilkent.github.io/cytoscape.js-fcose/demo/demo-compound.html" title="Compound Demo">DEMO (compound)</a> | ||
| ||
<a href="https://ivis-at-bilkent.github.io/cytoscape.js-fcose/demo/demo-constraint.html" title="Constraint Demo">DEMO (constraint)</a> | ||
</p> | ||
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. | ||
<p align="center"><img src="demo/demo.gif" width="440"></p> | ||
Furthermore, fCoSE also supports a fairly rich set of constraint types (i.e., fixed position, vertical/horizontal alignment and relative placement). | ||
<p align="center"><img src="demo/incrementalConstraints.gif" width="800"></p> | ||
You can see constraint support in action in the following videos: [fixed node](https://youtu.be/OTke5XQXzQA), [alignment](https://youtu.be/XCj_-_cTuRc), [relative placement](https://youtu.be/k0PmRliwdmo), [hybrid](https://youtu.be/cS3rkTyIMqU), [real life graphs](https://youtu.be/S7aIr9cNKbI). Constraints can also be added [incrementally](https://youtu.be/mxRKGvzM900) on a given layout. | ||
Please cite the following when you use this layout until an fCoSE publication is available: | ||
@@ -18,11 +33,32 @@ | ||
<p align="center"><img src="demo.gif" width="480"></p> | ||
## Dependencies | ||
* Cytoscape.js ^3.2.0 | ||
* cose-base ^1.0.0 | ||
* cose-base ^2.0.0 | ||
* cytoscape-layout-utilities.js (optional for packing disconnected components) ^1.0.0 | ||
## Documentation | ||
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: | ||
* **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: | ||
```js | ||
fixedNodeConstraint: [{nodeId: 'n1', position: {x: 100, y: 200}}, | ||
{nodeId: 'n2', position: {x: 200, y: -300}}], | ||
``` | ||
* **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: | ||
```js | ||
alignmentConstraint: {vertical: [['n1', 'n2', 'n3'], ['n4', 'n5']], horizontal: [['n2', 'n4']]}, | ||
``` | ||
***Note:** Alignment constraints in a direction must be given in most compact form. Example: ```['n1', 'n2', 'n3']``` instead of ```['n1', 'n2'], ['n1', 'n3']```.* | ||
* **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: | ||
```js | ||
relativePlacementConstraint: [{top: 'n1', bottom: 'n2', gap: 100}, | ||
{left: 'n3', right: 'n4', gap: 75}], | ||
``` | ||
## Usage instructions | ||
@@ -103,2 +139,4 @@ | ||
packComponents: true, | ||
// Layout step - all, transformed, enforced, cose - for debug purpose only | ||
step: "all", | ||
@@ -119,7 +157,7 @@ /* spectral layout options */ | ||
// Node repulsion (non overlapping) multiplier | ||
nodeRepulsion: 4500, | ||
nodeRepulsion: node => 4500, | ||
// Ideal edge (non nested) length | ||
idealEdgeLength: 50, | ||
idealEdgeLength: edge => 50, | ||
// Divisor to compute edge forces | ||
edgeElasticity: 0.45, | ||
edgeElasticity: edge => 0.45, | ||
// Nesting factor (multiplier) to compute ideal edge length for nested edges | ||
@@ -144,4 +182,16 @@ nestingFactor: 0.1, | ||
// Initial cooling factor for incremental layout | ||
initialEnergyOnIncremental: 0.3, | ||
initialEnergyOnIncremental: 0.3, | ||
/* constraint options */ | ||
// Fix desired nodes to predefined positions | ||
// [{nodeId: 'n1', position: {x: 100, y: 200}}, {...}] | ||
fixedNodeConstraint: undefined, | ||
// Align desired nodes in vertical/horizontal direction | ||
// {vertical: [['n1', 'n2'], [...]], horizontal: [['n2', 'n4'], [...]]} | ||
alignmentConstraint: undefined, | ||
// Place two nodes relatively in vertical/horizontal direction | ||
// [{top: 'n1', bottom: 'n2', gap: 100}, {left: 'n3', right: 'n4', gap: 75}, {...}] | ||
relativePlacementConstraint: undefined, | ||
/* layout event callbacks */ | ||
@@ -148,0 +198,0 @@ ready: () => {}, // on layoutready |
@@ -9,126 +9,2 @@ /* | ||
auxiliary.multMat = function(array1, array2){ | ||
let result = []; | ||
for(let i = 0; i < array1.length; i++){ | ||
result[i] = []; | ||
for(let j = 0; j < array2[0].length; j++){ | ||
result[i][j] = 0; | ||
for(let k = 0; k < array1[0].length; k++){ | ||
result[i][j] += array1[i][k] * array2[k][j]; | ||
} | ||
} | ||
} | ||
return result; | ||
}; | ||
auxiliary.multGamma = function(array){ | ||
let result = []; | ||
let sum = 0; | ||
for(let i = 0; i < array.length; i++){ | ||
sum += array[i]; | ||
} | ||
sum *= (-1)/array.length; | ||
for(let i = 0; i < array.length; i++){ | ||
result[i] = sum + array[i]; | ||
} | ||
return result; | ||
}; | ||
auxiliary.multL = function(array, C, INV){ | ||
let result = []; | ||
let temp1 = []; | ||
let temp2 = []; | ||
// multiply by C^T | ||
for(let i = 0; i < C[0].length; i++){ | ||
let sum = 0; | ||
for(let j = 0; j < C.length; j++){ | ||
sum += -0.5 * C[j][i] * array[j]; | ||
} | ||
temp1[i] = sum; | ||
} | ||
// multiply the result by INV | ||
for(let i = 0; i < INV.length; i++){ | ||
let sum = 0; | ||
for(let j = 0; j < INV.length; j++){ | ||
sum += INV[i][j] * temp1[j]; | ||
} | ||
temp2[i] = sum; | ||
} | ||
// multiply the result by C | ||
for(let i = 0; i < C.length; i++){ | ||
let sum = 0; | ||
for(let j = 0; j < C[0].length; j++){ | ||
sum += C[i][j] * temp2[j]; | ||
} | ||
result[i] = sum; | ||
} | ||
return result; | ||
}; | ||
auxiliary.multCons = function(array, constant){ | ||
let result = []; | ||
for(let i = 0; i < array.length; i++){ | ||
result[i] = array[i] * constant; | ||
} | ||
return result; | ||
}; | ||
// assumes arrays have same size | ||
auxiliary.minusOp = function(array1, array2){ | ||
let result = []; | ||
for(let i = 0; i < array1.length; i++){ | ||
result[i] = array1[i] - array2[i]; | ||
} | ||
return result; | ||
}; | ||
// assumes arrays have same size | ||
auxiliary.dotProduct = function(array1, array2){ | ||
let product = 0; | ||
for(let i = 0; i < array1.length; i++){ | ||
product += array1[i] * array2[i]; | ||
} | ||
return product; | ||
}; | ||
auxiliary.mag = function(array){ | ||
return Math.sqrt(this.dotProduct(array, array)); | ||
}; | ||
auxiliary.normalize = function(array){ | ||
let result = []; | ||
let magnitude = this.mag(array); | ||
for(let i = 0; i < array.length; i++){ | ||
result[i] = array[i] / magnitude; | ||
} | ||
return result; | ||
}; | ||
auxiliary.transpose = function(array){ | ||
let result = []; | ||
for(let i = 0; i < array[0].length; i++){ | ||
result[i] = []; | ||
for(let j = 0; j < array.length; j++){ | ||
result[i][j] = array[j][i]; | ||
} | ||
} | ||
return result; | ||
}; | ||
// get the top most nodes | ||
@@ -192,3 +68,3 @@ auxiliary.getTopMostNodes = function(nodes) { | ||
currentNode.neighborhood().nodes().forEach(function(node){ | ||
if(eles.intersection(currentNode.edgesWith(node))){ | ||
if(eles.intersection(currentNode.edgesWith(node)).length > 0){ | ||
neighborNodes.merge(node); | ||
@@ -312,635 +188,2 @@ } | ||
/* Below singular value decomposition (svd) code including hypot function is adopted from https://github.com/dragonfly-ai/JamaJS | ||
Some changes are applied to make the code compatible with the fcose code and to make it independent from Jama. | ||
Input matrix is changed to a 2D array instead of Jama matrix. Matrix dimensions are taken according to 2D array instead of using Jama functions. | ||
An object that includes singular value components is created for return. | ||
The types of input parameters of the hypot function are removed. | ||
let is used instead of var for the variable initialization. | ||
*/ | ||
/* | ||
Apache License | ||
Version 2.0, January 2004 | ||
http://www.apache.org/licenses/ | ||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||
1. Definitions. | ||
"License" shall mean the terms and conditions for use, reproduction, | ||
and distribution as defined by Sections 1 through 9 of this document. | ||
"Licensor" shall mean the copyright owner or entity authorized by | ||
the copyright owner that is granting the License. | ||
"Legal Entity" shall mean the union of the acting entity and all | ||
other entities that control, are controlled by, or are under common | ||
control with that entity. For the purposes of this definition, | ||
"control" means (i) the power, direct or indirect, to cause the | ||
direction or management of such entity, whether by contract or | ||
otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||
outstanding shares, or (iii) beneficial ownership of such entity. | ||
"You" (or "Your") shall mean an individual or Legal Entity | ||
exercising permissions granted by this License. | ||
"Source" form shall mean the preferred form for making modifications, | ||
including but not limited to software source code, documentation | ||
source, and configuration files. | ||
"Object" form shall mean any form resulting from mechanical | ||
transformation or translation of a Source form, including but | ||
not limited to compiled object code, generated documentation, | ||
and conversions to other media types. | ||
"Work" shall mean the work of authorship, whether in Source or | ||
Object form, made available under the License, as indicated by a | ||
copyright notice that is included in or attached to the work | ||
(an example is provided in the Appendix below). | ||
"Derivative Works" shall mean any work, whether in Source or Object | ||
form, that is based on (or derived from) the Work and for which the | ||
editorial revisions, annotations, elaborations, or other modifications | ||
represent, as a whole, an original work of authorship. For the purposes | ||
of this License, Derivative Works shall not include works that remain | ||
separable from, or merely link (or bind by name) to the interfaces of, | ||
the Work and Derivative Works thereof. | ||
"Contribution" shall mean any work of authorship, including | ||
the original version of the Work and any modifications or additions | ||
to that Work or Derivative Works thereof, that is intentionally | ||
submitted to Licensor for inclusion in the Work by the copyright owner | ||
or by an individual or Legal Entity authorized to submit on behalf of | ||
the copyright owner. For the purposes of this definition, "submitted" | ||
means any form of electronic, verbal, or written communication sent | ||
to the Licensor or its representatives, including but not limited to | ||
communication on electronic mailing lists, source code control systems, | ||
and issue tracking systems that are managed by, or on behalf of, the | ||
Licensor for the purpose of discussing and improving the Work, but | ||
excluding communication that is conspicuously marked or otherwise | ||
designated in writing by the copyright owner as "Not a Contribution." | ||
"Contributor" shall mean Licensor and any individual or Legal Entity | ||
on behalf of whom a Contribution has been received by Licensor and | ||
subsequently incorporated within the Work. | ||
2. Grant of Copyright License. Subject to the terms and conditions of | ||
this License, each Contributor hereby grants to You a perpetual, | ||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||
copyright license to reproduce, prepare Derivative Works of, | ||
publicly display, publicly perform, sublicense, and distribute the | ||
Work and such Derivative Works in Source or Object form. | ||
3. Grant of Patent License. Subject to the terms and conditions of | ||
this License, each Contributor hereby grants to You a perpetual, | ||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||
(except as stated in this section) patent license to make, have made, | ||
use, offer to sell, sell, import, and otherwise transfer the Work, | ||
where such license applies only to those patent claims licensable | ||
by such Contributor that are necessarily infringed by their | ||
Contribution(s) alone or by combination of their Contribution(s) | ||
with the Work to which such Contribution(s) was submitted. If You | ||
institute patent litigation against any entity (including a | ||
cross-claim or counterclaim in a lawsuit) alleging that the Work | ||
or a Contribution incorporated within the Work constitutes direct | ||
or contributory patent infringement, then any patent licenses | ||
granted to You under this License for that Work shall terminate | ||
as of the date such litigation is filed. | ||
4. Redistribution. You may reproduce and distribute copies of the | ||
Work or Derivative Works thereof in any medium, with or without | ||
modifications, and in Source or Object form, provided that You | ||
meet the following conditions: | ||
(a) You must give any other recipients of the Work or | ||
Derivative Works a copy of this License; and | ||
(b) You must cause any modified files to carry prominent notices | ||
stating that You changed the files; and | ||
(c) You must retain, in the Source form of any Derivative Works | ||
that You distribute, all copyright, patent, trademark, and | ||
attribution notices from the Source form of the Work, | ||
excluding those notices that do not pertain to any part of | ||
the Derivative Works; and | ||
(d) If the Work includes a "NOTICE" text file as part of its | ||
distribution, then any Derivative Works that You distribute must | ||
include a readable copy of the attribution notices contained | ||
within such NOTICE file, excluding those notices that do not | ||
pertain to any part of the Derivative Works, in at least one | ||
of the following places: within a NOTICE text file distributed | ||
as part of the Derivative Works; within the Source form or | ||
documentation, if provided along with the Derivative Works; or, | ||
within a display generated by the Derivative Works, if and | ||
wherever such third-party notices normally appear. The contents | ||
of the NOTICE file are for informational purposes only and | ||
do not modify the License. You may add Your own attribution | ||
notices within Derivative Works that You distribute, alongside | ||
or as an addendum to the NOTICE text from the Work, provided | ||
that such additional attribution notices cannot be construed | ||
as modifying the License. | ||
You may add Your own copyright statement to Your modifications and | ||
may provide additional or different license terms and conditions | ||
for use, reproduction, or distribution of Your modifications, or | ||
for any such Derivative Works as a whole, provided Your use, | ||
reproduction, and distribution of the Work otherwise complies with | ||
the conditions stated in this License. | ||
5. Submission of Contributions. Unless You explicitly state otherwise, | ||
any Contribution intentionally submitted for inclusion in the Work | ||
by You to the Licensor shall be under the terms and conditions of | ||
this License, without any additional terms or conditions. | ||
Notwithstanding the above, nothing herein shall supersede or modify | ||
the terms of any separate license agreement you may have executed | ||
with Licensor regarding such Contributions. | ||
6. Trademarks. This License does not grant permission to use the trade | ||
names, trademarks, service marks, or product names of the Licensor, | ||
except as required for reasonable and customary use in describing the | ||
origin of the Work and reproducing the content of the NOTICE file. | ||
7. Disclaimer of Warranty. Unless required by applicable law or | ||
agreed to in writing, Licensor provides the Work (and each | ||
Contributor provides its Contributions) on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||
implied, including, without limitation, any warranties or conditions | ||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||
PARTICULAR PURPOSE. You are solely responsible for determining the | ||
appropriateness of using or redistributing the Work and assume any | ||
risks associated with Your exercise of permissions under this License. | ||
8. Limitation of Liability. In no event and under no legal theory, | ||
whether in tort (including negligence), contract, or otherwise, | ||
unless required by applicable law (such as deliberate and grossly | ||
negligent acts) or agreed to in writing, shall any Contributor be | ||
liable to You for damages, including any direct, indirect, special, | ||
incidental, or consequential damages of any character arising as a | ||
result of this License or out of the use or inability to use the | ||
Work (including but not limited to damages for loss of goodwill, | ||
work stoppage, computer failure or malfunction, or any and all | ||
other commercial damages or losses), even if such Contributor | ||
has been advised of the possibility of such damages. | ||
9. Accepting Warranty or Additional Liability. While redistributing | ||
the Work or Derivative Works thereof, You may choose to offer, | ||
and charge a fee for, acceptance of support, warranty, indemnity, | ||
or other liability obligations and/or rights consistent with this | ||
License. However, in accepting such obligations, You may act only | ||
on Your own behalf and on Your sole responsibility, not on behalf | ||
of any other Contributor, and only if You agree to indemnify, | ||
defend, and hold each Contributor harmless for any liability | ||
incurred by, or claims asserted against, such Contributor by reason | ||
of your accepting any such warranty or additional liability. | ||
END OF TERMS AND CONDITIONS | ||
APPENDIX: How to apply the Apache License to your work. | ||
To apply the Apache License to your work, attach the following | ||
boilerplate notice, with the fields enclosed by brackets "{}" | ||
replaced with your own identifying information. (Don't include | ||
the brackets!) The text should be enclosed in the appropriate | ||
comment syntax for the file format. We also recommend that a | ||
file or class name and description of purpose be included on the | ||
same "printed page" as the copyright notice for easier | ||
identification within third-party archives. | ||
Copyright {yyyy} {name of copyright owner} | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
auxiliary.svd = function (A) { | ||
this.U = null; | ||
this.V = null; | ||
this.s = null; | ||
this.m = 0; | ||
this.n = 0; | ||
this.m = A.length; | ||
this.n = A[0].length; | ||
let nu = Math.min(this.m, this.n); | ||
this.s = (function (s) { | ||
let a = []; | ||
while (s-- > 0) | ||
a.push(0); | ||
return a; | ||
})(Math.min(this.m + 1, this.n)); | ||
this.U = (function (dims) { | ||
let allocate = function (dims) { | ||
if (dims.length == 0) { | ||
return 0; | ||
} else { | ||
let array = []; | ||
for (let i = 0; i < dims[0]; i++) { | ||
array.push(allocate(dims.slice(1))); | ||
} | ||
return array; | ||
} | ||
}; | ||
return allocate(dims); | ||
})([this.m, nu]); | ||
this.V = (function (dims) { | ||
let allocate = function (dims) { | ||
if (dims.length == 0) { | ||
return 0; | ||
} else { | ||
let array = []; | ||
for (let i = 0; i < dims[0]; i++) { | ||
array.push(allocate(dims.slice(1))); | ||
} | ||
return array; | ||
} | ||
}; | ||
return allocate(dims); | ||
})([this.n, this.n]); | ||
let e = (function (s) { | ||
let a = []; | ||
while (s-- > 0) | ||
a.push(0); | ||
return a; | ||
})(this.n); | ||
let work = (function (s) { | ||
let a = []; | ||
while (s-- > 0) | ||
a.push(0); | ||
return a; | ||
})(this.m); | ||
let wantu = true; | ||
let wantv = true; | ||
let nct = Math.min(this.m - 1, this.n); | ||
let nrt = Math.max(0, Math.min(this.n - 2, this.m)); | ||
for (let k = 0; k < Math.max(nct, nrt); k++) { | ||
if (k < nct) { | ||
this.s[k] = 0; | ||
for (let i = k; i < this.m; i++) { | ||
this.s[k] = auxiliary.hypot(this.s[k], A[i][k]); | ||
} | ||
; | ||
if (this.s[k] !== 0.0) { | ||
if (A[k][k] < 0.0) { | ||
this.s[k] = -this.s[k]; | ||
} | ||
for (let i = k; i < this.m; i++) { | ||
A[i][k] /= this.s[k]; | ||
} | ||
; | ||
A[k][k] += 1.0; | ||
} | ||
this.s[k] = -this.s[k]; | ||
} | ||
for (let j = k + 1; j < this.n; j++) { | ||
if ((function (lhs, rhs) { | ||
return lhs && rhs; | ||
})((k < nct), (this.s[k] !== 0.0))) { | ||
let t = 0; | ||
for (let i = k; i < this.m; i++) { | ||
t += A[i][k] * A[i][j]; | ||
} | ||
; | ||
t = -t / A[k][k]; | ||
for (let i = k; i < this.m; i++) { | ||
A[i][j] += t * A[i][k]; | ||
} | ||
; | ||
} | ||
e[j] = A[k][j]; | ||
} | ||
; | ||
if ((function (lhs, rhs) { | ||
return lhs && rhs; | ||
})(wantu, (k < nct))) { | ||
for (let i = k; i < this.m; i++) { | ||
this.U[i][k] = A[i][k]; | ||
} | ||
; | ||
} | ||
if (k < nrt) { | ||
e[k] = 0; | ||
for (let i = k + 1; i < this.n; i++) { | ||
e[k] = auxiliary.hypot(e[k], e[i]); | ||
} | ||
; | ||
if (e[k] !== 0.0) { | ||
if (e[k + 1] < 0.0) { | ||
e[k] = -e[k]; | ||
} | ||
for (let i = k + 1; i < this.n; i++) { | ||
e[i] /= e[k]; | ||
} | ||
; | ||
e[k + 1] += 1.0; | ||
} | ||
e[k] = -e[k]; | ||
if ((function (lhs, rhs) { | ||
return lhs && rhs; | ||
})((k + 1 < this.m), (e[k] !== 0.0))) { | ||
for (let i = k + 1; i < this.m; i++) { | ||
work[i] = 0.0; | ||
} | ||
; | ||
for (let j = k + 1; j < this.n; j++) { | ||
for (let i = k + 1; i < this.m; i++) { | ||
work[i] += e[j] * A[i][j]; | ||
} | ||
; | ||
} | ||
; | ||
for (let j = k + 1; j < this.n; j++) { | ||
let t = -e[j] / e[k + 1]; | ||
for (let i = k + 1; i < this.m; i++) { | ||
A[i][j] += t * work[i]; | ||
} | ||
; | ||
} | ||
; | ||
} | ||
if (wantv) { | ||
for (let i = k + 1; i < this.n; i++) { | ||
this.V[i][k] = e[i]; | ||
}; | ||
} | ||
} | ||
}; | ||
let p = Math.min(this.n, this.m + 1); | ||
if (nct < this.n) { | ||
this.s[nct] = A[nct][nct]; | ||
} | ||
if (this.m < p) { | ||
this.s[p - 1] = 0.0; | ||
} | ||
if (nrt + 1 < p) { | ||
e[nrt] = A[nrt][p - 1]; | ||
} | ||
e[p - 1] = 0.0; | ||
if (wantu) { | ||
for (let j = nct; j < nu; j++) { | ||
for (let i = 0; i < this.m; i++) { | ||
this.U[i][j] = 0.0; | ||
} | ||
; | ||
this.U[j][j] = 1.0; | ||
}; | ||
for (let k = nct - 1; k >= 0; k--) { | ||
if (this.s[k] !== 0.0) { | ||
for (let j = k + 1; j < nu; j++) { | ||
let t = 0; | ||
for (let i = k; i < this.m; i++) { | ||
t += this.U[i][k] * this.U[i][j]; | ||
}; | ||
t = -t / this.U[k][k]; | ||
for (let i = k; i < this.m; i++) { | ||
this.U[i][j] += t * this.U[i][k]; | ||
}; | ||
}; | ||
for (let i = k; i < this.m; i++) { | ||
this.U[i][k] = -this.U[i][k]; | ||
}; | ||
this.U[k][k] = 1.0 + this.U[k][k]; | ||
for (let i = 0; i < k - 1; i++) { | ||
this.U[i][k] = 0.0; | ||
}; | ||
} else { | ||
for (let i = 0; i < this.m; i++) { | ||
this.U[i][k] = 0.0; | ||
}; | ||
this.U[k][k] = 1.0; | ||
} | ||
}; | ||
} | ||
if (wantv) { | ||
for (let k = this.n - 1; k >= 0; k--) { | ||
if ((function (lhs, rhs) { | ||
return lhs && rhs; | ||
})((k < nrt), (e[k] !== 0.0))) { | ||
for (let j = k + 1; j < nu; j++) { | ||
let t = 0; | ||
for (let i = k + 1; i < this.n; i++) { | ||
t += this.V[i][k] * this.V[i][j]; | ||
}; | ||
t = -t / this.V[k + 1][k]; | ||
for (let i = k + 1; i < this.n; i++) { | ||
this.V[i][j] += t * this.V[i][k]; | ||
}; | ||
}; | ||
} | ||
for (let i = 0; i < this.n; i++) { | ||
this.V[i][k] = 0.0; | ||
}; | ||
this.V[k][k] = 1.0; | ||
}; | ||
} | ||
let pp = p - 1; | ||
let iter = 0; | ||
let eps = Math.pow(2.0, -52.0); | ||
let tiny = Math.pow(2.0, -966.0); | ||
while ((p > 0)) { | ||
let k = void 0; | ||
let kase = void 0; | ||
for (k = p - 2; k >= -1; k--) { | ||
if (k === -1) { | ||
break; | ||
} | ||
if (Math.abs(e[k]) <= tiny + eps * (Math.abs(this.s[k]) + Math.abs(this.s[k + 1]))) { | ||
e[k] = 0.0; | ||
break; | ||
} | ||
}; | ||
if (k === p - 2) { | ||
kase = 4; | ||
} else { | ||
let ks = void 0; | ||
for (ks = p - 1; ks >= k; ks--) { | ||
if (ks === k) { | ||
break; | ||
} | ||
let t = (ks !== p ? Math.abs(e[ks]) : 0.0) + (ks !== k + 1 ? Math.abs(e[ks - 1]) : 0.0); | ||
if (Math.abs(this.s[ks]) <= tiny + eps * t) { | ||
this.s[ks] = 0.0; | ||
break; | ||
} | ||
}; | ||
if (ks === k) { | ||
kase = 3; | ||
} else if (ks === p - 1) { | ||
kase = 1; | ||
} else { | ||
kase = 2; | ||
k = ks; | ||
} | ||
} | ||
k++; | ||
switch ((kase)) { | ||
case 1: | ||
{ | ||
let f = e[p - 2]; | ||
e[p - 2] = 0.0; | ||
for (let j = p - 2; j >= k; j--) { | ||
let t = auxiliary.hypot(this.s[j], f); | ||
let cs = this.s[j] / t; | ||
let sn = f / t; | ||
this.s[j] = t; | ||
if (j !== k) { | ||
f = -sn * e[j - 1]; | ||
e[j - 1] = cs * e[j - 1]; | ||
} | ||
if (wantv) { | ||
for (let i = 0; i < this.n; i++) { | ||
t = cs * this.V[i][j] + sn * this.V[i][p - 1]; | ||
this.V[i][p - 1] = -sn * this.V[i][j] + cs * this.V[i][p - 1]; | ||
this.V[i][j] = t; | ||
}; | ||
} | ||
}; | ||
}; | ||
break; | ||
case 2: | ||
{ | ||
let f = e[k - 1]; | ||
e[k - 1] = 0.0; | ||
for (let j = k; j < p; j++) { | ||
let t = auxiliary.hypot(this.s[j], f); | ||
let cs = this.s[j] / t; | ||
let sn = f / t; | ||
this.s[j] = t; | ||
f = -sn * e[j]; | ||
e[j] = cs * e[j]; | ||
if (wantu) { | ||
for (let i = 0; i < this.m; i++) { | ||
t = cs * this.U[i][j] + sn * this.U[i][k - 1]; | ||
this.U[i][k - 1] = -sn * this.U[i][j] + cs * this.U[i][k - 1]; | ||
this.U[i][j] = t; | ||
}; | ||
} | ||
}; | ||
}; | ||
break; | ||
case 3: | ||
{ | ||
let scale = Math.max(Math.max(Math.max(Math.max(Math.abs(this.s[p - 1]), Math.abs(this.s[p - 2])), Math.abs(e[p - 2])), Math.abs(this.s[k])), Math.abs(e[k])); | ||
let sp = this.s[p - 1] / scale; | ||
let spm1 = this.s[p - 2] / scale; | ||
let epm1 = e[p - 2] / scale; | ||
let sk = this.s[k] / scale; | ||
let ek = e[k] / scale; | ||
let b = ((spm1 + sp) * (spm1 - sp) + epm1 * epm1) / 2.0; | ||
let c = (sp * epm1) * (sp * epm1); | ||
let shift = 0.0; | ||
if ((function (lhs, rhs) { | ||
return lhs || rhs; | ||
})((b !== 0.0), (c !== 0.0))) { | ||
shift = Math.sqrt(b * b + c); | ||
if (b < 0.0) { | ||
shift = -shift; | ||
} | ||
shift = c / (b + shift); | ||
} | ||
let f = (sk + sp) * (sk - sp) + shift; | ||
let g = sk * ek; | ||
for (let j = k; j < p - 1; j++) { | ||
let t = auxiliary.hypot(f, g); | ||
let cs = f / t; | ||
let sn = g / t; | ||
if (j !== k) { | ||
e[j - 1] = t; | ||
} | ||
f = cs * this.s[j] + sn * e[j]; | ||
e[j] = cs * e[j] - sn * this.s[j]; | ||
g = sn * this.s[j + 1]; | ||
this.s[j + 1] = cs * this.s[j + 1]; | ||
if (wantv) { | ||
for (let i = 0; i < this.n; i++) { | ||
t = cs * this.V[i][j] + sn * this.V[i][j + 1]; | ||
this.V[i][j + 1] = -sn * this.V[i][j] + cs * this.V[i][j + 1]; | ||
this.V[i][j] = t; | ||
}; | ||
} | ||
t = auxiliary.hypot(f, g); | ||
cs = f / t; | ||
sn = g / t; | ||
this.s[j] = t; | ||
f = cs * e[j] + sn * this.s[j + 1]; | ||
this.s[j + 1] = -sn * e[j] + cs * this.s[j + 1]; | ||
g = sn * e[j + 1]; | ||
e[j + 1] = cs * e[j + 1]; | ||
if (wantu && (j < this.m - 1)) { | ||
for (let i = 0; i < this.m; i++) { | ||
t = cs * this.U[i][j] + sn * this.U[i][j + 1]; | ||
this.U[i][j + 1] = -sn * this.U[i][j] + cs * this.U[i][j + 1]; | ||
this.U[i][j] = t; | ||
}; | ||
} | ||
}; | ||
e[p - 2] = f; | ||
iter = iter + 1; | ||
}; | ||
break; | ||
case 4: | ||
{ | ||
if (this.s[k] <= 0.0) { | ||
this.s[k] = (this.s[k] < 0.0 ? -this.s[k] : 0.0); | ||
if (wantv) { | ||
for (let i = 0; i <= pp; i++) { | ||
this.V[i][k] = -this.V[i][k]; | ||
}; | ||
} | ||
} | ||
while ((k < pp)) { | ||
if (this.s[k] >= this.s[k + 1]) { | ||
break; | ||
} | ||
let t = this.s[k]; | ||
this.s[k] = this.s[k + 1]; | ||
this.s[k + 1] = t; | ||
if (wantv && (k < this.n - 1)) { | ||
for (let i = 0; i < this.n; i++) { | ||
t = this.V[i][k + 1]; | ||
this.V[i][k + 1] = this.V[i][k]; | ||
this.V[i][k] = t; | ||
}; | ||
} | ||
if (wantu && (k < this.m - 1)) { | ||
for (let i = 0; i < this.m; i++) { | ||
t = this.U[i][k + 1]; | ||
this.U[i][k + 1] = this.U[i][k]; | ||
this.U[i][k] = t; | ||
}; | ||
} | ||
k++; | ||
}; | ||
iter = 0; | ||
p--; | ||
}; | ||
break; | ||
} | ||
}; | ||
let result = {U: this.U, V: this.V, S: this.s}; | ||
return result; | ||
}; | ||
// sqrt(a^2 + b^2) without under/overflow. | ||
auxiliary.hypot = function(a, b) { | ||
let r; | ||
if (Math.abs(a) > Math.abs(b)) { | ||
r = b/a; | ||
r = Math.abs(a)*Math.sqrt(1+r*r); | ||
} else if (b != 0) { | ||
r = a/b; | ||
r = Math.abs(b)*Math.sqrt(1+r*r); | ||
} else { | ||
r = 0.0; | ||
} | ||
return r; | ||
}; | ||
module.exports = auxiliary; |
@@ -32,2 +32,12 @@ /** | ||
const isFn = fn => typeof fn === 'function'; | ||
const optFn = ( opt, ele ) => { | ||
if( isFn( opt ) ){ | ||
return opt( ele ); | ||
} else { | ||
return opt; | ||
} | ||
}; | ||
/**** Postprocessing functions ****/ | ||
@@ -71,4 +81,5 @@ | ||
} | ||
// Attach id to the layout node | ||
// Attach id to the layout node and repulsion value | ||
theNode.id = theChild.data("id"); | ||
theNode.nodeRepulsion = optFn( options.nodeRepulsion, theChild ); | ||
// Attach the paddings of cy node to layout node | ||
@@ -83,8 +94,6 @@ theNode.paddingLeft = parseInt( theChild.css('padding') ); | ||
if(theChild.isParent()){ | ||
let labelWidth = theChild.boundingBox({ includeLabels: true, includeNodes: false }).w; | ||
let labelHeight = theChild.boundingBox({ includeLabels: true, includeNodes: false }).h; | ||
let labelPos = theChild.css("text-halign"); | ||
theNode.labelWidth = labelWidth; | ||
theNode.labelHeight = labelHeight; | ||
theNode.labelPos = labelPos; | ||
theNode.labelWidth = theChild.boundingBox({ includeLabels: true, includeNodes: false, includeOverlays: false }).w; | ||
theNode.labelHeight = theChild.boundingBox({ includeLabels: true, includeNodes: false, includeOverlays: false }).h; | ||
theNode.labelPosVertical = theChild.css("text-valign"); | ||
theNode.labelPosHorizontal = theChild.css("text-halign"); | ||
} | ||
@@ -114,2 +123,4 @@ } | ||
let processEdges = function(layout, gm, edges){ | ||
let idealLengthTotal = 0; | ||
let edgeCount = 0; | ||
for (let i = 0; i < edges.length; i++) { | ||
@@ -122,14 +133,40 @@ let edge = edges[i]; | ||
e1.id = edge.id(); | ||
e1.idealLength = optFn( options.idealEdgeLength, edge ); | ||
e1.edgeElasticity = optFn( options.edgeElasticity, edge ); | ||
idealLengthTotal += e1.idealLength; | ||
edgeCount++; | ||
} | ||
} | ||
}; | ||
// we need to update the ideal edge length constant with the avg. ideal length value after processing edges | ||
// in case there is no edge, use other options | ||
if (options.idealEdgeLength != null){ | ||
if (edges.length > 0) | ||
CoSEConstants.DEFAULT_EDGE_LENGTH = FDLayoutConstants.DEFAULT_EDGE_LENGTH = idealLengthTotal / edgeCount; | ||
else if(!isFn(options.idealEdgeLength)) // in case there is no edge, but option gives a value to use | ||
CoSEConstants.DEFAULT_EDGE_LENGTH = FDLayoutConstants.DEFAULT_EDGE_LENGTH = options.idealEdgeLength; | ||
else // in case there is no edge and we cannot get a value from option (because it's a function) | ||
CoSEConstants.DEFAULT_EDGE_LENGTH = FDLayoutConstants.DEFAULT_EDGE_LENGTH = 50; | ||
// we need to update these constant values based on the ideal edge length constant | ||
CoSEConstants.MIN_REPULSION_DIST = FDLayoutConstants.MIN_REPULSION_DIST = FDLayoutConstants.DEFAULT_EDGE_LENGTH / 10.0; | ||
CoSEConstants.DEFAULT_RADIAL_SEPARATION = FDLayoutConstants.DEFAULT_EDGE_LENGTH; | ||
} | ||
}; | ||
// transfer cytoscape constraints to cose layout | ||
let processConstraints = function(layout, options){ | ||
// get nodes to be fixed | ||
if(options.fixedNodeConstraint){ | ||
layout.constraints["fixedNodeConstraint"] = options.fixedNodeConstraint; | ||
} | ||
// get nodes to be aligned | ||
if(options.alignmentConstraint){ | ||
layout.constraints["alignmentConstraint"] = options.alignmentConstraint; | ||
} | ||
// get nodes to be relatively placed | ||
if(options.relativePlacementConstraint){ | ||
layout.constraints["relativePlacementConstraint"] = options.relativePlacementConstraint; | ||
} | ||
}; | ||
/**** Apply postprocessing ****/ | ||
if (options.nodeRepulsion != null) | ||
CoSEConstants.DEFAULT_REPULSION_STRENGTH = FDLayoutConstants.DEFAULT_REPULSION_STRENGTH = options.nodeRepulsion; | ||
if (options.idealEdgeLength != null) | ||
CoSEConstants.DEFAULT_EDGE_LENGTH = FDLayoutConstants.DEFAULT_EDGE_LENGTH = options.idealEdgeLength; | ||
if (options.edgeElasticity != null) | ||
CoSEConstants.DEFAULT_SPRING_STRENGTH = FDLayoutConstants.DEFAULT_SPRING_STRENGTH = options.edgeElasticity; | ||
if (options.nestingFactor != null) | ||
@@ -167,4 +204,35 @@ CoSEConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR = FDLayoutConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR = options.nestingFactor; | ||
LayoutConstants.DEFAULT_UNIFORM_LEAF_NODE_SIZES = options.uniformNodeDimensions; | ||
CoSEConstants.TREE_REDUCTION_ON_INCREMENTAL = options.randomize ? true : false; | ||
// This part is for debug/demo purpose | ||
if(options.step == "transformed"){ | ||
CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING = true; | ||
CoSEConstants.ENFORCE_CONSTRAINTS = false; | ||
CoSEConstants.APPLY_LAYOUT = false; | ||
} | ||
if(options.step == "enforced"){ | ||
CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING = false; | ||
CoSEConstants.ENFORCE_CONSTRAINTS = true; | ||
CoSEConstants.APPLY_LAYOUT = false; | ||
} | ||
if(options.step == "cose"){ | ||
CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING = false; | ||
CoSEConstants.ENFORCE_CONSTRAINTS = false; | ||
CoSEConstants.APPLY_LAYOUT = true; | ||
} | ||
if(options.step == "all"){ | ||
if(options.randomize) | ||
CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING = true; | ||
else | ||
CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING = false; | ||
CoSEConstants.ENFORCE_CONSTRAINTS = true; | ||
CoSEConstants.APPLY_LAYOUT = true; | ||
} | ||
if(options.randomize && !(options.fixedNodeConstraint || options.alignmentConstraint || options.relativePlacementConstraint)){ | ||
CoSEConstants.TREE_REDUCTION_ON_INCREMENTAL = true; | ||
} | ||
else{ | ||
CoSEConstants.TREE_REDUCTION_ON_INCREMENTAL = false; | ||
} | ||
let coseLayout = new CoSELayout(); | ||
@@ -174,4 +242,4 @@ let gm = coseLayout.newGraphManager(); | ||
processChildrenList(gm.addRoot(), aux.getTopMostNodes(nodes), coseLayout, options); | ||
processEdges(coseLayout, gm, edges); | ||
processConstraints(coseLayout, options); | ||
@@ -178,0 +246,0 @@ coseLayout.runLayout(); |
@@ -36,2 +36,4 @@ /** | ||
packComponents: true, | ||
// Layout step - all, transformed, enforced, cose - for debug purpose only | ||
step: "all", | ||
@@ -52,7 +54,7 @@ /* spectral layout options */ | ||
// Node repulsion (non overlapping) multiplier | ||
nodeRepulsion: 4500, | ||
nodeRepulsion: node => 4500, | ||
// Ideal edge (non nested) length | ||
idealEdgeLength: 50, | ||
idealEdgeLength: edge => 50, | ||
// Divisor to compute edge forces | ||
edgeElasticity: 0.45, | ||
edgeElasticity: edge => 0.45, | ||
// Nesting factor (multiplier) to compute ideal edge length for nested edges | ||
@@ -77,4 +79,16 @@ nestingFactor: 0.1, | ||
// Initial cooling factor for incremental layout | ||
initialEnergyOnIncremental: 0.3, | ||
initialEnergyOnIncremental: 0.3, | ||
/* constraint options */ | ||
// Fix required nodes to predefined positions | ||
// [{nodeId: 'n1', position: {x: 100, y: 200}, {...}] | ||
fixedNodeConstraint: undefined, | ||
// Align required nodes in vertical/horizontal direction | ||
// {vertical: [['n1', 'n2')], ['n3', 'n4']], horizontal: ['n2', 'n4']} | ||
alignmentConstraint: undefined, | ||
// Place two nodes relatively in vertical/horizontal direction | ||
// [{top: 'n1', bottom: 'n2', gap: 100}, {left: 'n3', right: 'n4', gap: 75}] | ||
relativePlacementConstraint: undefined, | ||
/* layout event callbacks */ | ||
@@ -102,2 +116,11 @@ ready: () => {}, // on layoutready | ||
let constraintExist = options.fixedNodeConstraint || options.alignmentConstraint || options.relativePlacementConstraint; | ||
// if any constraint exists, set some options | ||
if(constraintExist){ | ||
// constraints work with these options | ||
options.tile = false; | ||
options.packComponents = false; | ||
} | ||
// decide component packing is enabled or not | ||
@@ -113,29 +136,27 @@ let layUtil; | ||
// if packing is not enabled, perform layout on the whole graph | ||
if(!packingEnabled){ | ||
if(options.randomize){ | ||
// Apply spectral layout | ||
spectralResult.push(spectralLayout(options)); | ||
xCoords = spectralResult[0]["xCoords"]; | ||
yCoords = spectralResult[0]["yCoords"]; | ||
if(eles.nodes().length > 0) { | ||
// if packing is not enabled, perform layout on the whole graph | ||
if(!packingEnabled){ | ||
if(options.randomize){ | ||
let result = spectralLayout(options); // apply spectral layout | ||
spectralResult.push(result); | ||
} | ||
// apply cose layout as postprocessing | ||
if(options.quality == "default" || options.quality == "proof"){ | ||
coseResult.push(coseLayout(options, spectralResult[0])); | ||
} | ||
} | ||
else{ // packing is enabled | ||
let topMostNodes = aux.getTopMostNodes(options.eles.nodes()); | ||
components = aux.connectComponents(cy, options.eles, topMostNodes); | ||
//send each component to spectral layout | ||
if(options.randomize){ | ||
components.forEach(function(component){ | ||
options.eles = component; | ||
spectralResult.push(spectralLayout(options)); | ||
}); | ||
} | ||
// Apply cose layout as postprocessing | ||
if(options.quality == "default" || options.quality == "proof"){ | ||
coseResult.push(coseLayout(options, spectralResult[0])); | ||
} | ||
} | ||
else{ // packing is enabled | ||
let topMostNodes = aux.getTopMostNodes(options.eles.nodes()); | ||
components = aux.connectComponents(cy, options.eles, topMostNodes); | ||
//send each component to spectral layout | ||
if(options.randomize){ | ||
components.forEach(function(component){ | ||
options.eles = component; | ||
spectralResult.push(spectralLayout(options)); | ||
}); | ||
} | ||
if(options.quality == "default" || options.quality == "proof"){ | ||
if(options.quality == "default" || options.quality == "proof"){ | ||
let toBeTiledNodes = cy.collection(); | ||
@@ -150,13 +171,13 @@ if(options.tile){ // behave nodes to be tiled as one component | ||
components.forEach(function(component, index){ | ||
if(component.edges().length == 0){ | ||
component.nodes().forEach(function(node, i){ | ||
toBeTiledNodes.merge(component.nodes()[i]); | ||
if(!node.isParent()){ | ||
tempSpectralResult.nodeIndexes.set(component.nodes()[i].id(), count++); | ||
tempSpectralResult.xCoords.push(component.nodes()[0].position().x); | ||
tempSpectralResult.yCoords.push(component.nodes()[0].position().y); | ||
} | ||
}); | ||
indexesToBeDeleted.push(index); | ||
} | ||
if(component.edges().length == 0){ | ||
component.nodes().forEach(function(node, i){ | ||
toBeTiledNodes.merge(component.nodes()[i]); | ||
if(!node.isParent()){ | ||
tempSpectralResult.nodeIndexes.set(component.nodes()[i].id(), count++); | ||
tempSpectralResult.xCoords.push(component.nodes()[0].position().x); | ||
tempSpectralResult.yCoords.push(component.nodes()[0].position().y); | ||
} | ||
}); | ||
indexesToBeDeleted.push(index); | ||
} | ||
}); | ||
@@ -176,82 +197,139 @@ if(toBeTiledNodes.length > 1){ | ||
}); | ||
} | ||
// packing | ||
let subgraphs = []; | ||
components.forEach(function(component, index){ | ||
let nodeIndexes; | ||
if(options.quality == "draft"){ | ||
nodeIndexes = spectralResult[index].nodeIndexes; | ||
} | ||
let subgraph = {}; | ||
subgraph.nodes = []; | ||
subgraph.edges = []; | ||
let nodeIndex; | ||
component.nodes().forEach(function (node) { | ||
if(options.quality == "draft"){ | ||
if(!node.isParent()){ | ||
nodeIndex = nodeIndexes.get(node.id()); | ||
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}); | ||
} | ||
// packing | ||
if(components.length > 1){ | ||
let subgraphs = []; | ||
components.forEach(function(component, index){ | ||
let nodeIndexes; | ||
if(options.quality == "draft"){ | ||
nodeIndexes = spectralResult[index].nodeIndexes; | ||
} | ||
else{ | ||
let parentInfo = aux.calcBoundingBox(node, spectralResult[index].xCoords, spectralResult[index].yCoords, nodeIndexes); | ||
subgraph.nodes.push({x: parentInfo.topLeftX, y: parentInfo.topLeftY, width: parentInfo.width, height: parentInfo.height}); | ||
} | ||
} | ||
else{ | ||
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()}); | ||
} | ||
}); | ||
component.edges().forEach(function (edge) { | ||
let source = edge.source(); | ||
let target = edge.target(); | ||
let subgraph = {}; | ||
subgraph.nodes = []; | ||
subgraph.edges = []; | ||
let nodeIndex; | ||
component.nodes().forEach(function (node) { | ||
if(options.quality == "draft"){ | ||
if(!node.isParent()){ | ||
nodeIndex = nodeIndexes.get(node.id()); | ||
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}); | ||
} | ||
else{ | ||
let parentInfo = aux.calcBoundingBox(node, spectralResult[index].xCoords, spectralResult[index].yCoords, nodeIndexes); | ||
subgraph.nodes.push({x: parentInfo.topLeftX, y: parentInfo.topLeftY, width: parentInfo.width, height: parentInfo.height}); | ||
} | ||
} | ||
else{ | ||
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()}); | ||
} | ||
}); | ||
component.edges().forEach(function (edge) { | ||
let source = edge.source(); | ||
let target = edge.target(); | ||
if(options.quality == "draft"){ | ||
let sourceNodeIndex = nodeIndexes.get(source.id()); | ||
let targetNodeIndex = nodeIndexes.get(target.id()); | ||
let sourceCenter = []; | ||
let targetCenter = []; | ||
if(source.isParent()){ | ||
let parentInfo = aux.calcBoundingBox(source, spectralResult[index].xCoords, spectralResult[index].yCoords, nodeIndexes); | ||
sourceCenter.push(parentInfo.topLeftX + parentInfo.width / 2); | ||
sourceCenter.push(parentInfo.topLeftY + parentInfo.height / 2); | ||
} | ||
else{ | ||
sourceCenter.push(spectralResult[index].xCoords[sourceNodeIndex]); | ||
sourceCenter.push(spectralResult[index].yCoords[sourceNodeIndex]); | ||
} | ||
if(target.isParent()){ | ||
let parentInfo = aux.calcBoundingBox(target, spectralResult[index].xCoords, spectralResult[index].yCoords, nodeIndexes); | ||
targetCenter.push(parentInfo.topLeftX + parentInfo.width / 2); | ||
targetCenter.push(parentInfo.topLeftY + parentInfo.height / 2); | ||
} | ||
else{ | ||
targetCenter.push(spectralResult[index].xCoords[targetNodeIndex]); | ||
targetCenter.push(spectralResult[index].yCoords[targetNodeIndex]); | ||
} | ||
subgraph.edges.push({startX: sourceCenter[0], startY: sourceCenter[1], endX: targetCenter[0], endY: targetCenter[1]}); | ||
} | ||
else{ | ||
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()}); | ||
} | ||
}); | ||
subgraphs.push(subgraph); | ||
}); | ||
let shiftResult = layUtil.packComponents(subgraphs).shifts; | ||
if(options.quality == "draft"){ | ||
let sourceNodeIndex = nodeIndexes.get(source.id()); | ||
let targetNodeIndex = nodeIndexes.get(target.id()); | ||
let sourceCenter = []; | ||
let targetCenter = []; | ||
if(source.isParent()){ | ||
let parentInfo = aux.calcBoundingBox(source, spectralResult[index].xCoords, spectralResult[index].yCoords, nodeIndexes); | ||
sourceCenter.push(parentInfo.topLeftX + parentInfo.width / 2); | ||
sourceCenter.push(parentInfo.topLeftY + parentInfo.height / 2); | ||
} | ||
else{ | ||
sourceCenter.push(spectralResult[index].xCoords[sourceNodeIndex]); | ||
sourceCenter.push(spectralResult[index].yCoords[sourceNodeIndex]); | ||
} | ||
if(target.isParent()){ | ||
let parentInfo = aux.calcBoundingBox(target, spectralResult[index].xCoords, spectralResult[index].yCoords, nodeIndexes); | ||
targetCenter.push(parentInfo.topLeftX + parentInfo.width / 2); | ||
targetCenter.push(parentInfo.topLeftY + parentInfo.height / 2); | ||
} | ||
else{ | ||
targetCenter.push(spectralResult[index].xCoords[targetNodeIndex]); | ||
targetCenter.push(spectralResult[index].yCoords[targetNodeIndex]); | ||
} | ||
subgraph.edges.push({startX: sourceCenter[0], startY: sourceCenter[1], endX: targetCenter[0], endY: targetCenter[1]}); | ||
spectralResult.forEach(function(result, index){ | ||
let newXCoords = result.xCoords.map(x => x + shiftResult[index].dx); | ||
let newYCoords = result.yCoords.map(y => y + shiftResult[index].dy); | ||
result.xCoords = newXCoords; | ||
result.yCoords = newYCoords; | ||
}); | ||
} | ||
else{ | ||
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()}); | ||
coseResult.forEach(function(result, index){ | ||
Object.keys(result).forEach(function (item) { | ||
let nodeRectangle = result[item]; | ||
nodeRectangle.setCenter(nodeRectangle.getCenterX() + shiftResult[index].dx, nodeRectangle.getCenterY() + shiftResult[index].dy); | ||
}); | ||
}); | ||
} | ||
}); | ||
subgraphs.push(subgraph); | ||
}); | ||
let shiftResult = layUtil.packComponents(subgraphs).shifts; | ||
if(options.quality == "draft"){ | ||
spectralResult.forEach(function(result, index){ | ||
let newXCoords = result.xCoords.map(x => x + shiftResult[index].dx); | ||
let newYCoords = result.yCoords.map(y => y + shiftResult[index].dy); | ||
result.xCoords = newXCoords; | ||
result.yCoords = newYCoords; | ||
}); | ||
} | ||
} | ||
else{ | ||
coseResult.forEach(function(result, index){ | ||
Object.keys(result).forEach(function (item) { | ||
let nodeRectangle = result[item]; | ||
nodeRectangle.setCenter(nodeRectangle.getCenterX() + shiftResult[index].dx, nodeRectangle.getCenterY() + shiftResult[index].dy); | ||
// move graph to its original position because spectral moves it to origin | ||
if(!options.fixedNodeConstraint) { | ||
let minXCoord = Number.POSITIVE_INFINITY; | ||
let maxXCoord = Number.NEGATIVE_INFINITY; | ||
let minYCoord = Number.POSITIVE_INFINITY; | ||
let maxYCoord = Number.NEGATIVE_INFINITY; | ||
if(options.quality == "draft") { | ||
spectralResult.forEach(function(result){ | ||
result.xCoords.forEach(function (value) { | ||
if (value < minXCoord) | ||
minXCoord = value; | ||
if (value > maxXCoord) | ||
maxXCoord = value; | ||
}); | ||
result.yCoords.forEach(function (value) { | ||
if (value < minYCoord) | ||
minYCoord = value; | ||
if (value > maxYCoord) | ||
maxYCoord = value; | ||
}); | ||
}); | ||
}); | ||
let boundingBox = options.eles.boundingBox(); | ||
let diffOnX = (boundingBox.x1 + boundingBox.w / 2) - (maxXCoord + minXCoord) / 2; | ||
let diffOnY = (boundingBox.y1 + boundingBox.h / 2) - (maxYCoord + minYCoord) / 2; | ||
spectralResult.forEach(function(result){ | ||
result.xCoords = result.xCoords.map(x => x + diffOnX); | ||
result.yCoords = result.yCoords.map(y => y + diffOnY); | ||
}); | ||
} | ||
else { | ||
coseResult.forEach(function(result){ | ||
Object.keys(result).forEach(function (item) { | ||
let node = result[item]; | ||
if (node.getCenterX() < minXCoord) | ||
minXCoord = node.getCenterX(); | ||
if (node.getCenterX() > maxXCoord) | ||
maxXCoord = node.getCenterX(); | ||
if (node.getCenterY() < minYCoord) | ||
minYCoord = node.getCenterY(); | ||
if (node.getCenterY() > maxYCoord) | ||
maxYCoord = node.getCenterY(); | ||
}); | ||
}); | ||
let boundingBox = options.eles.boundingBox(); | ||
let diffOnX = (boundingBox.x1 + boundingBox.w / 2) - (maxXCoord + minXCoord) / 2; | ||
let diffOnY = (boundingBox.y1 + boundingBox.h / 2) - (maxYCoord + minYCoord) / 2; | ||
coseResult.forEach(function(result, index){ | ||
Object.keys(result).forEach(function (item) { | ||
let node = result[item]; | ||
node.setCenter(node.getCenterX() + diffOnX, node.getCenterY() + diffOnY); | ||
}); | ||
}); | ||
} | ||
} | ||
} | ||
@@ -302,3 +380,3 @@ | ||
console.log("If randomize option is set to false, then quality option must be 'default' or 'proof'."); | ||
} | ||
} | ||
@@ -305,0 +383,0 @@ } |
@@ -6,2 +6,4 @@ /** | ||
const aux = require('./auxiliary'); | ||
const Matrix = require('cose-base').layoutBase.Matrix; | ||
const SVD = require('cose-base').layoutBase.SVD; | ||
@@ -170,3 +172,3 @@ // main function that spectral layout is processed | ||
let SVDResult = aux.svd(PHI); | ||
let SVDResult = SVD.svd(PHI); | ||
@@ -192,3 +194,3 @@ let a_q = SVDResult.S; | ||
INV = aux.multMat(aux.multMat(a_v, a_Sig), aux.transpose(a_u)); | ||
INV = Matrix.multMat(Matrix.multMat(a_v, a_Sig), Matrix.transpose(a_u)); | ||
@@ -215,4 +217,4 @@ }; | ||
Y1 = aux.normalize(Y1); | ||
Y2 = aux.normalize(Y2); | ||
Y1 = Matrix.normalize(Y1); | ||
Y2 = Matrix.normalize(Y2); | ||
@@ -233,7 +235,7 @@ let count = 0; | ||
Y1 = aux.multGamma(aux.multL(aux.multGamma(V1), C, INV)); | ||
theta1 = aux.dotProduct(V1, Y1); | ||
Y1 = aux.normalize(Y1); | ||
Y1 = Matrix.multGamma(Matrix.multL(Matrix.multGamma(V1), C, INV)); | ||
theta1 = Matrix.dotProduct(V1, Y1); | ||
Y1 = Matrix.normalize(Y1); | ||
current = aux.dotProduct(V1, Y1); | ||
current = Matrix.dotProduct(V1, Y1); | ||
@@ -262,8 +264,8 @@ temp = Math.abs(current/previous); | ||
V2 = aux.minusOp(V2, aux.multCons(V1, (aux.dotProduct(V1, V2)))); | ||
Y2 = aux.multGamma(aux.multL(aux.multGamma(V2), C, INV)); | ||
theta2 = aux.dotProduct(V2, Y2); | ||
Y2 = aux.normalize(Y2); | ||
V2 = Matrix.minusOp(V2, Matrix.multCons(V1, (Matrix.dotProduct(V1, V2)))); | ||
Y2 = Matrix.multGamma(Matrix.multL(Matrix.multGamma(V2), C, INV)); | ||
theta2 = Matrix.dotProduct(V2, Y2); | ||
Y2 = Matrix.normalize(Y2); | ||
current = aux.dotProduct(V2, Y2); | ||
current = Matrix.dotProduct(V2, Y2); | ||
@@ -289,4 +291,4 @@ temp = Math.abs(current/previous); | ||
//populate the two vectors | ||
xCoords = aux.multCons(V1, Math.sqrt(Math.abs(theta1))); | ||
yCoords = aux.multCons(V2, Math.sqrt(Math.abs(theta2))); | ||
xCoords = Matrix.multCons(V1, Math.sqrt(Math.abs(theta1))); | ||
yCoords = Matrix.multCons(V2, Math.sqrt(Math.abs(theta2))); | ||
@@ -353,3 +355,3 @@ }; | ||
ele.neighborhood().nodes().forEach(function(node){ | ||
if(eles.intersection(ele.edgesWith(node))){ | ||
if(eles.intersection(ele.edgesWith(node)).length > 0){ | ||
if(node.isParent()) | ||
@@ -399,7 +401,16 @@ allNodesNeighborhood[eleIndex].push(parentChildMap.get(node.id())); | ||
allBFS(samplingType); | ||
sample(); | ||
powerIteration(); | ||
if(options.quality == "draft" || options.step == "all"){ | ||
allBFS(samplingType); | ||
sample(); | ||
powerIteration(); | ||
spectralResult = { nodeIndexes: nodeIndexes, xCoords: xCoords, yCoords: yCoords }; | ||
spectralResult = { nodeIndexes: nodeIndexes, xCoords: xCoords, yCoords: yCoords }; | ||
} | ||
else{ | ||
nodeIndexes.forEach(function(value, key){ | ||
xCoords.push(cy.getElementById(key).position("x")); | ||
yCoords.push(cy.getElementById(key).position("y")); | ||
}); | ||
spectralResult = { nodeIndexes: nodeIndexes, xCoords: xCoords, yCoords: yCoords }; | ||
} | ||
return spectralResult; | ||
@@ -406,0 +417,0 @@ } |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
8665481
32
20866
225
1
+ Addedcose-base@2.2.0(transitive)
+ Addedlayout-base@2.0.1(transitive)
- Removedcose-base@1.0.3(transitive)
- Removedlayout-base@1.0.2(transitive)
Updatedcose-base@^2.0.0