+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": ["env"]
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
node_modules/**/*
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"node": true,
|
||||
"amd": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"rules": {
|
||||
"semi": "error"
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
cff-version: 1.2.0
|
||||
message: "If you use this software, please cite it as below."
|
||||
authors:
|
||||
- family-names: "Balci"
|
||||
given-names: "Hasan"
|
||||
orcid: "https://orcid.org/0000-0001-8319-7758"
|
||||
- family-names: "Dogrusoz"
|
||||
given-names: "Ugur"
|
||||
orcid: "https://orcid.org/0000-0002-7153-0784"
|
||||
title: "cytoscape.js-fcose"
|
||||
version: 2.1.0
|
||||
date-released: 2021-06-25
|
||||
url: "https://github.com/iVis-at-Bilkent/cytoscape.js-fcose"
|
||||
preferred-citation:
|
||||
type: article
|
||||
authors:
|
||||
- family-names: "Balci"
|
||||
given-names: "Hasan"
|
||||
orcid: "https://orcid.org/0000-0001-8319-7758"
|
||||
- family-names: "Dogrusoz"
|
||||
given-names: "Ugur"
|
||||
orcid: "https://orcid.org/0000-0002-7153-0784"
|
||||
doi: "10.1109/TVCG.2021.3095303"
|
||||
journal: "IEEE Transactions on Visualization and Computer Graphics"
|
||||
title: "fCoSE: A Fast Compound Graph Layout Algorithm with Constraint Support"
|
||||
issue: 12
|
||||
volume: 28
|
||||
start: 4582 # First page number
|
||||
end: 4593 # Last page number
|
||||
month: 12
|
||||
year: 2022
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2018 - present, iVis-at-Bilkent.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the “Software”), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
cytoscape-fcose
|
||||
================================================================================
|
||||
|
||||
|
||||
## Description
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Here are some demos: **simple**, **compound**, and **constraints**, respectively:
|
||||
<p align="center">
|
||||
<a href="https://ivis-at-bilkent.github.io/cytoscape.js-fcose/demo/demo.html" title="Simple"><img src="https://www.cs.bilkent.edu.tr/~ivis/images/demo1.png" height=42px></a>  
|
||||
<a href="https://ivis-at-bilkent.github.io/cytoscape.js-fcose/demo/demo-compound.html" title="Compound"><img src="https://www.cs.bilkent.edu.tr/~ivis/images/demo2.png" height=42px></a>  
|
||||
<a href="https://ivis-at-bilkent.github.io/cytoscape.js-fcose/demo/demo-constraint.html" title="Constraints"><img src="https://www.cs.bilkent.edu.tr/~ivis/images/demo3.png" height=42px></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/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.
|
||||
|
||||
Please cite the following when you use this layout:
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Dependencies
|
||||
|
||||
* Cytoscape.js ^3.2.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}],
|
||||
```
|
||||
The `gap` property is optional. If it is omitted, average `idealEdgeLength` is used as the gap value.
|
||||
|
||||
## Usage instructions
|
||||
|
||||
Download the library:
|
||||
* via npm: `npm install cytoscape-fcose`,
|
||||
* via bower: `bower install cytoscape-fcose`, or
|
||||
* via direct download in the repository (probably from a tag).
|
||||
|
||||
Import the library as appropriate for your project:
|
||||
|
||||
ES import:
|
||||
|
||||
```js
|
||||
import cytoscape from 'cytoscape';
|
||||
import fcose from 'cytoscape-fcose';
|
||||
|
||||
cytoscape.use( fcose );
|
||||
```
|
||||
|
||||
CommonJS require:
|
||||
|
||||
```js
|
||||
let cytoscape = require('cytoscape');
|
||||
let fcose = require('cytoscape-fcose');
|
||||
|
||||
cytoscape.use( fcose ); // register extension
|
||||
```
|
||||
|
||||
AMD:
|
||||
|
||||
```js
|
||||
require(['cytoscape', 'cytoscape-fcose'], function( cytoscape, fcose ){
|
||||
fcose( cytoscape ); // register extension
|
||||
});
|
||||
```
|
||||
|
||||
Plain HTML/JS has the extension registered for you automatically, because no `require()` is needed. Just add the following files:
|
||||
|
||||
```
|
||||
<script src="https://unpkg.com/layout-base/layout-base.js"></script>
|
||||
<script src="https://unpkg.com/cose-base/cose-base.js"></script>
|
||||
<script src="https://unpkg.com/cytoscape-fcose/cytoscape-fcose.js"></script>
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
||||
When calling the layout, e.g. `cy.layout({ name: 'fcose', ... })`, the following options are supported:
|
||||
|
||||
```js
|
||||
var defaultOptions = {
|
||||
|
||||
// 'draft', 'default' or 'proof'
|
||||
// - "draft" only applies spectral layout
|
||||
// - "default" improves the quality with incremental layout (fast cooling rate)
|
||||
// - "proof" improves the quality with incremental layout (slow cooling rate)
|
||||
quality: "default",
|
||||
// Use random node positions at beginning of layout
|
||||
// if this is set to false, then quality option must be "proof"
|
||||
randomize: true,
|
||||
// Whether or not to animate the layout
|
||||
animate: true,
|
||||
// Duration of animation in ms, if enabled
|
||||
animationDuration: 1000,
|
||||
// Easing of animation, if enabled
|
||||
animationEasing: undefined,
|
||||
// Fit the viewport to the repositioned nodes
|
||||
fit: true,
|
||||
// Padding around layout
|
||||
padding: 30,
|
||||
// Whether to include labels in node dimensions. Valid in "proof" quality
|
||||
nodeDimensionsIncludeLabels: false,
|
||||
// Whether or not simple nodes (non-compound nodes) are of uniform dimensions
|
||||
uniformNodeDimensions: false,
|
||||
// Whether to pack disconnected components - cytoscape-layout-utilities extension should be registered and initialized
|
||||
packComponents: true,
|
||||
// Layout step - all, transformed, enforced, cose - for debug purpose only
|
||||
step: "all",
|
||||
|
||||
/* spectral layout options */
|
||||
|
||||
// False for random, true for greedy sampling
|
||||
samplingType: true,
|
||||
// Sample size to construct distance matrix
|
||||
sampleSize: 25,
|
||||
// Separation amount between nodes
|
||||
nodeSeparation: 75,
|
||||
// Power iteration tolerance
|
||||
piTol: 0.0000001,
|
||||
|
||||
/* incremental layout options */
|
||||
|
||||
// Node repulsion (non overlapping) multiplier
|
||||
nodeRepulsion: node => 4500,
|
||||
// Ideal edge (non nested) length
|
||||
idealEdgeLength: edge => 50,
|
||||
// Divisor to compute edge forces
|
||||
edgeElasticity: edge => 0.45,
|
||||
// Nesting factor (multiplier) to compute ideal edge length for nested edges
|
||||
nestingFactor: 0.1,
|
||||
// Maximum number of iterations to perform - this is a suggested value and might be adjusted by the algorithm as required
|
||||
numIter: 2500,
|
||||
// For enabling tiling
|
||||
tile: true,
|
||||
// The comparison function to be used while sorting nodes during tiling operation.
|
||||
// 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.
|
||||
// It works similar to ``compareFunction`` parameter of ``Array.prototype.sort()``
|
||||
// If node1 is less then node2 by some ordering criterion ``tilingCompareBy(nodeId1, nodeId2)`` must return a negative value
|
||||
// If node1 is greater then node2 by some ordering criterion ``tilingCompareBy(nodeId1, nodeId2)`` must return a positive value
|
||||
// If node1 is equal to node2 by some ordering criterion ``tilingCompareBy(nodeId1, nodeId2)`` must return 0
|
||||
tilingCompareBy: undefined,
|
||||
// Represents the amount of the vertical space to put between the zero degree members during the tiling operation(can also be a function)
|
||||
tilingPaddingVertical: 10,
|
||||
// Represents the amount of the horizontal space to put between the zero degree members during the tiling operation(can also be a function)
|
||||
tilingPaddingHorizontal: 10,
|
||||
// Gravity force (constant)
|
||||
gravity: 0.25,
|
||||
// Gravity range (constant) for compounds
|
||||
gravityRangeCompound: 1.5,
|
||||
// Gravity force (constant) for compounds
|
||||
gravityCompound: 1.0,
|
||||
// Gravity range (constant)
|
||||
gravityRange: 3.8,
|
||||
// Initial cooling factor for incremental layout
|
||||
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 */
|
||||
ready: () => {}, // on layoutready
|
||||
stop: () => {} // on layoutstop
|
||||
};
|
||||
```
|
||||
To be able to use `packComponents` option, `cytoscape-layout-utilities` extension should also be registered in the application.
|
||||
Packing related [options](https://github.com/iVis-at-Bilkent/cytoscape.js-layout-utilities#default-options) should be set via `cytoscape-layout-utilities` extension.
|
||||
If they are not set, fCoSE uses default options.
|
||||
|
||||
|
||||
## Build targets
|
||||
|
||||
* `npm run test` : Run Mocha tests in `./test`
|
||||
* `npm run build` : Build `./src/**` into `cytoscape-fcose.js`
|
||||
* `npm run watch` : Automatically build on changes with live reloading (N.b. you must already have an HTTP server running)
|
||||
* `npm run dev` : Automatically build on changes with live reloading with webpack dev server
|
||||
* `npm run lint` : Run eslint on the source
|
||||
|
||||
N.b. all builds use babel, so modern ES features can be used in the `src`.
|
||||
|
||||
|
||||
## Publishing instructions
|
||||
|
||||
This project is set up to automatically be published to npm and bower. To publish:
|
||||
|
||||
1. Build the extension : `npm run build:release`
|
||||
1. Commit the build : `git commit -am "Build for release"`
|
||||
1. Bump the version number and tag: `npm version major|minor|patch`
|
||||
1. Push to origin: `git push && git push --tags`
|
||||
1. Publish to npm: `npm publish .`
|
||||
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`
|
||||
1. [Make a new release](https://github.com/iVis-at-Bilkent/cytoscape.js-fcose/releases/new) for Zenodo.
|
||||
|
||||
## Team
|
||||
|
||||
* [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)
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "cytoscape-fcose",
|
||||
"description": "The fCoSE layout for Cytoscape.js by Bilkent with fast compound node placement",
|
||||
"main": "cytoscape-fcose.js",
|
||||
"dependencies": {
|
||||
"cytoscape": "^3.2.0",
|
||||
"cose-base": "^1.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/iVis-at-Bilkent/cytoscape.js-fcose.git"
|
||||
},
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"keywords": [
|
||||
"cytoscape",
|
||||
"cytoscape-extension"
|
||||
],
|
||||
"license": "MIT"
|
||||
}
|
||||
+1549
File diff suppressed because it is too large
Load Diff
+465
@@ -0,0 +1,465 @@
|
||||
<!DOCTYPE>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>cytoscape-fcose.js demo</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<!-- Bootstrap, popper, jQuery and filesaver - for demo purpose only -->
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
|
||||
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<script src="https://unpkg.com/cytoscape/dist/cytoscape.min.js"></script>
|
||||
<script src="https://unpkg.com/layout-base/layout-base.js"></script>
|
||||
<script src="https://unpkg.com/cose-base/cose-base.js"></script>
|
||||
<script src="https://unpkg.com/cytoscape-layout-utilities/cytoscape-layout-utilities.js"></script>
|
||||
|
||||
<!-- for testing with local version of cytoscape.js -->
|
||||
<!--<script src="../cytoscape.js/build/cytoscape.js"></script>-->
|
||||
|
||||
<script src="../cytoscape-fcose.js"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: helvetica neue, helvetica, liberation sans, arial, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#cy {
|
||||
position: relative;
|
||||
width: auto;
|
||||
height: 90%;
|
||||
z-index: 999;
|
||||
margin-left: 290px;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
opacity: 1;
|
||||
color: #555;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
/* The sidepanel menu */
|
||||
.sidepanel {
|
||||
height: auto; /* Specify a height */
|
||||
width: 290px; /* 0 width - change this with JavaScript */
|
||||
position: absolute; /* Stay in place */
|
||||
z-index: 1000; /* Stay on top */
|
||||
float: left;
|
||||
top: auto;
|
||||
left: 0;
|
||||
background-color: #b7ffff; /* Black*/
|
||||
overflow-x: hidden; /* Disable horizontal scroll */
|
||||
padding-top: 10px; /* Place content 10px from the top */
|
||||
padding-bottom: 10px; /* Place content 10px from the bottom */
|
||||
transition: 0s; /* 0.5 second transition effect to slide in the sidepanel */
|
||||
}
|
||||
|
||||
table{
|
||||
margin-left: 4px;
|
||||
margin-right: auto;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
/* The sidepanel links */
|
||||
table td {
|
||||
padding: 0px 8px 8px 8px;
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
color: #555;
|
||||
transition: 0.3s;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Style the button that is used to open the sidepanel */
|
||||
.button {
|
||||
font-size: 15px;
|
||||
color: #555;
|
||||
cursor: pointer;
|
||||
background-color: #b7ffff;
|
||||
padding: 5px 10px;
|
||||
border: none;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.textField {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #0f0;
|
||||
}
|
||||
|
||||
.btn-group-sm>.btn, .btn-sm {
|
||||
font-size: 13px;
|
||||
background-color: #7d8991;
|
||||
border-color: #7d8991;
|
||||
}
|
||||
|
||||
.btn-primary:hover, .btn-primary:focus, .btn-primary:active, .btn-primary.active, .open>.dropdown-toggle.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #60717d;
|
||||
border-color: #60717d; /*set the color you want here*/
|
||||
}
|
||||
|
||||
.btn-outline-primary:hover, .btn-outline-primary:focus, .btn-outline-primary:active, .btn-outline-primary.active, .open>.dropdown-toggle.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #60717d;
|
||||
border-color: #60717d; /*set the color you want here*/
|
||||
}
|
||||
|
||||
.form-control-sm {
|
||||
font-size: 13px
|
||||
}
|
||||
|
||||
.custom-select-sm {
|
||||
font-size: 13px
|
||||
}
|
||||
|
||||
.custom-control-label {
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.custom-control-input:checked~.custom-control-label::before {
|
||||
color: #fff;
|
||||
border-color: #7d8991;
|
||||
background-color: #7d8991;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function(){
|
||||
|
||||
var cy = window.cy = cytoscape({
|
||||
container: document.getElementById('cy'),
|
||||
|
||||
ready: function(){
|
||||
let layoutUtilities = this.layoutUtilities({
|
||||
desiredAspectRatio: this.width()/this.height()
|
||||
});
|
||||
this.nodes().forEach(function(node){
|
||||
let size = Math.random()*120+30;
|
||||
node.css("width", size);
|
||||
node.css("height", size);
|
||||
});
|
||||
this.layout({name: 'fcose', animationEasing: 'ease-out'}).run();
|
||||
},
|
||||
|
||||
// demo your layout
|
||||
layout: {
|
||||
name: 'grid'
|
||||
},
|
||||
|
||||
style: [
|
||||
{
|
||||
selector: 'node',
|
||||
style: {
|
||||
'background-color': '#2B65EC'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
selector: ':parent',
|
||||
style: {
|
||||
'background-opacity': 0.333,
|
||||
'border-color': '#2B65EC'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
selector: 'edge',
|
||||
style: {
|
||||
'line-color': '#2B65EC'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
selector: 'node:selected',
|
||||
style: {
|
||||
'background-color': '#F08080',
|
||||
'border-color': 'red'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
selector: 'edge:selected',
|
||||
style: {
|
||||
'line-color': '#F08080'
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
elements: [{ group:'nodes', data:{ id: 'n0'}},
|
||||
{ group:'nodes', data:{ id: 'n1'}},
|
||||
{ group:'nodes', data:{ id: 'n2'}},
|
||||
{ group:'nodes', data:{ id: 'n3'}},
|
||||
{ group:'nodes', data:{ id: 'n4', parent: 'n37'}},
|
||||
{ group:'nodes', data:{ id: 'n5'}},
|
||||
{ group:'nodes', data:{ id: 'n6'}},
|
||||
{ group:'nodes', data:{ id: 'n7', parent: 'n37'}},
|
||||
{ group:'nodes', data:{ id: 'n8', parent: 'n37'}},
|
||||
{ group:'nodes', data:{ id: 'n9', parent: 'n37'}},
|
||||
{ group:'nodes', data:{ id: 'n10', parent: 'n38'}},
|
||||
{ group:'nodes', data:{ id: 'n12'}},
|
||||
{ group:'nodes', data:{ id: 'n13'}},
|
||||
{ group:'nodes', data:{ id: 'n14'}},
|
||||
{ group:'nodes', data:{ id: 'n15'}},
|
||||
{ group:'nodes', data:{ id: 'n16'}},
|
||||
{ group:'nodes', data:{ id: 'n17'}},
|
||||
{ group:'nodes', data:{ id: 'n18'}},
|
||||
{ group:'nodes', data:{ id: 'n19'}},
|
||||
{ group:'nodes', data:{ id: 'n20'}},
|
||||
{ group:'nodes', data:{ id: 'n21'}},
|
||||
{ group:'nodes', data:{ id: 'n22'}},
|
||||
{ group:'nodes', data:{ id: 'n23'}},
|
||||
{ group:'nodes', data:{ id: 'n24', parent: 'n39'}},
|
||||
{ group:'nodes', data:{ id: 'n25', parent: 'n39'}},
|
||||
{ group:'nodes', data:{ id: 'n26', parent: 'n42'}},
|
||||
{ group:'nodes', data:{ id: 'n27', parent: 'n42'}},
|
||||
{ group:'nodes', data:{ id: 'n28', parent: 'n42'}},
|
||||
{ group:'nodes', data:{ id: 'n29', parent: 'n40'}},
|
||||
{ group:'nodes', data:{ id: 'n31', parent: 'n41'}},
|
||||
{ group:'nodes', data:{ id: 'n32', parent: 'n41'}},
|
||||
{ group:'nodes', data:{ id: 'n33', parent: 'n41'}},
|
||||
{ group:'nodes', data:{ id: 'n34', parent: 'n41'}},
|
||||
{ group:'nodes', data:{ id: 'n35', parent: 'n41'}},
|
||||
{ group:'nodes', data:{ id: 'n36', parent: 'n41'}},
|
||||
{ group:'nodes', data:{ id: 'n37'}},
|
||||
{ group:'nodes', data:{ id: 'n38'}},
|
||||
{ group:'nodes', data:{ id: 'n39', parent: 'n43'}},
|
||||
{ group:'nodes', data:{ id: 'n40', parent: 'n42'}},
|
||||
{ group:'nodes', data:{ id: 'n41', parent: 'n42'}},
|
||||
{ group:'nodes', data:{ id: 'n42', parent: 'n43'}},
|
||||
{ group:'nodes', data:{ id: 'n43'}},
|
||||
{ group:'nodes', data:{ id: 'n44'}},
|
||||
{ group:'nodes', data:{ id: 'n45'}},
|
||||
{ group:'nodes', data:{ id: 'n46'}},
|
||||
{ group:'nodes', data:{ id: 'n47'}},
|
||||
{ group:'edges', data:{ id: 'e0', source: 'n0', target: 'n1'} },
|
||||
{ group:'edges', data:{ id: 'e1', source: 'n1', target: 'n2'} },
|
||||
{ group:'edges', data:{ id: 'e2', source: 'n2', target: 'n3'} },
|
||||
{ group:'edges', data:{ id: 'e3', source: 'n0', target: 'n3'} },
|
||||
{ group:'edges', data:{ id: 'e4', source: 'n1', target: 'n4'} },
|
||||
{ group:'edges', data:{ id: 'e5', source: 'n2', target: 'n4'} },
|
||||
{ group:'edges', data:{ id: 'e6', source: 'n4', target: 'n5'} },
|
||||
{ group:'edges', data:{ id: 'e7', source: 'n5', target: 'n6'} },
|
||||
{ group:'edges', data:{ id: 'e8', source: 'n4', target: 'n6'} },
|
||||
{ group:'edges', data:{ id: 'e9', source: 'n4', target: 'n7'} },
|
||||
{ group:'edges', data:{ id: 'e10', source: 'n7', target: 'n8'} },
|
||||
{ group:'edges', data:{ id: 'e11', source: 'n8', target: 'n9'} },
|
||||
{ group:'edges', data:{ id: 'e12', source: 'n7', target: 'n9'} },
|
||||
{ group:'edges', data:{ id: 'e13', source: 'n13', target: 'n14'} },
|
||||
//{ group:'edges', data:{ id: 'e14', source: 'n12', target: 'n14'} },
|
||||
{ group:'edges', data:{ id: 'e15', source: 'n14', target: 'n15'} },
|
||||
{ group:'edges', data:{ id: 'e16', source: 'n14', target: 'n16'} },
|
||||
{ group:'edges', data:{ id: 'e17', source: 'n15', target: 'n17'} },
|
||||
{ group:'edges', data:{ id: 'e18', source: 'n17', target: 'n18'} },
|
||||
{ group:'edges', data:{ id: 'e19', source: 'n18', target: 'n19'} },
|
||||
{ group:'edges', data:{ id: 'e20', source: 'n17', target: 'n20'} },
|
||||
{ group:'edges', data:{ id: 'e21', source: 'n19', target: 'n20'} },
|
||||
{ group:'edges', data:{ id: 'e22', source: 'n16', target: 'n20'} },
|
||||
{ group:'edges', data:{ id: 'e23', source: 'n20', target: 'n21'} },
|
||||
{ group:'edges', data:{ id: 'e25', source: 'n23', target: 'n24'} },
|
||||
{ group:'edges', data:{ id: 'e26', source: 'n24', target: 'n25'} },
|
||||
{ group:'edges', data:{ id: 'e27', source: 'n26', target: 'n38'} },
|
||||
{ group:'edges', data:{ id: 'e29', source: 'n26', target: 'n39'} },
|
||||
{ group:'edges', data:{ id: 'e30', source: 'n26', target: 'n27'} },
|
||||
{ group:'edges', data:{ id: 'e31', source: 'n26', target: 'n28'} },
|
||||
{ group:'edges', data:{ id: 'e33', source: 'n21', target: 'n31'} },
|
||||
{ group:'edges', data:{ id: 'e35', source: 'n31', target: 'n33'} },
|
||||
{ group:'edges', data:{ id: 'e36', source: 'n31', target: 'n34'} },
|
||||
{ group:'edges', data:{ id: 'e37', source: 'n33', target: 'n34'} },
|
||||
{ group:'edges', data:{ id: 'e38', source: 'n32', target: 'n35'} },
|
||||
{ group:'edges', data:{ id: 'e39', source: 'n32', target: 'n36'} },
|
||||
{ group:'edges', data:{ id: 'e40', source: 'n16', target: 'n40'} },
|
||||
{ group:'edges', data:{ id: 'e41', source: 'n44', target: 'n45'} },
|
||||
{ group:'edges', data:{ id: 'e42', source: 'n44', target: 'n46'} },
|
||||
{ group:'edges', data:{ id: 'e43', source: 'n45', target: 'n46'} }
|
||||
]
|
||||
|
||||
});
|
||||
|
||||
document.getElementById("randomizeButton").addEventListener("click", function(){
|
||||
var layout = cy.layout({
|
||||
name: 'random',
|
||||
animate: true,
|
||||
animationDuration: 1000
|
||||
});
|
||||
|
||||
layout.run();
|
||||
});
|
||||
|
||||
document.getElementById("fcoseButton").addEventListener("click", function(){
|
||||
let qualityItem = document.getElementById("quality");
|
||||
var layout = cy.layout({
|
||||
name: 'fcose',
|
||||
quality: qualityItem.options[qualityItem.selectedIndex].value,
|
||||
randomize: !(document.getElementById("randomize").checked),
|
||||
animate: document.getElementById("animate").checked,
|
||||
animationEasing: 'ease-out',
|
||||
fit: document.getElementById("fit").checked,
|
||||
uniformNodeDimensions: document.getElementById("uniformNodeDimensions").checked,
|
||||
packComponents: document.getElementById("packComponents").checked,
|
||||
tile: document.getElementById("tile").checked,
|
||||
nodeRepulsion: parseFloat(document.getElementById("nodeRepulsion").value),
|
||||
idealEdgeLength: parseFloat(document.getElementById("idealEdgeLength").value),
|
||||
edgeElasticity: parseFloat(document.getElementById("edgeElasticity").value),
|
||||
nestingFactor: parseFloat(document.getElementById("nestingFactor").value),
|
||||
gravity: parseFloat(document.getElementById("gravity").value),
|
||||
gravityRange: parseFloat(document.getElementById("gravityRange").value),
|
||||
gravityCompound: parseFloat(document.getElementById("gravityCompound").value),
|
||||
gravityRangeCompound: parseFloat(document.getElementById("gravityRangeCompound").value),
|
||||
numIter: parseFloat(document.getElementById("numIter").value),
|
||||
tilingPaddingVertical: parseFloat(document.getElementById("tilingPaddingVertical").value),
|
||||
tilingPaddingHorizontal: parseFloat(document.getElementById("tilingPaddingHorizontal").value),
|
||||
initialEnergyOnIncremental: document.getElementById("initialEnergyOnIncremental").value,
|
||||
step:"all"
|
||||
});
|
||||
|
||||
layout.run();
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1 class="ml-2">cytoscape-fcose demo (compound)</h1>
|
||||
<div style='width: 300px; position: absolute;'>
|
||||
<button id="randomizeButton" class="btn btn-primary btn-sm mb-2 ml-2">Randomize</button>   
|
||||
<button id="fcoseButton" class="btn btn-primary btn-sm mb-2 ml-2">fCoSE</button>
|
||||
|
||||
<div id="mySidepanel" class="sidepanel">
|
||||
<table>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Quality of the layout"> Quality </span></td>
|
||||
<td>
|
||||
<select id="quality" class='custom-select custom-select-sm'>
|
||||
<option value="draft">draft</option>
|
||||
<option value="default" selected="">default</option>
|
||||
<option value="proof">proof</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Whether to enable incremental mode"> Incremental </span></td>
|
||||
<td>
|
||||
<div class="custom-control custom-control-inline custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="randomize" name="incremental">
|
||||
<label class="custom-control-label" for="randomize"></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Whether to perform animation after layout"> Animate </span></td>
|
||||
<td>
|
||||
<div class="custom-control custom-control-inline custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="animate" name="animate" checked>
|
||||
<label class="custom-control-label" for="animate"></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Whether to fit the viewport to the repositioned nodes"> Fit </span></td>
|
||||
<td>
|
||||
<div class="custom-control custom-control-inline custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="fit" name="fit" checked>
|
||||
<label class="custom-control-label" for="fit"></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Whether nodes are of uniform dimensions"> Uniform Node Dimensions </span></td>
|
||||
<td>
|
||||
<div class="custom-control custom-control-inline custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="uniformNodeDimensions" name="uniformNodeDimensions">
|
||||
<label class="custom-control-label" for="uniformNodeDimensions"></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Whether to pack components"> Pack Components to Window </span></td>
|
||||
<td>
|
||||
<div class="custom-control custom-control-inline custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="packComponents" name="packComponents" checked>
|
||||
<label class="custom-control-label" for="packComponents"></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Whether to tile disconnected nodes"> Tile Disconnected </span></td>
|
||||
<td>
|
||||
<div class="custom-control custom-control-inline custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="tile" name="tile" checked>
|
||||
<label class="custom-control-label" for="tile"></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Node repulsion (non overlapping) multiplier"> Node Repulsion </span></td>
|
||||
<td><input id="nodeRepulsion" class="textField form-control form-control-sm" type="text" value="4500" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Ideal (intra-graph) edge length"> Ideal Edge Length </span></td>
|
||||
<td><input id="idealEdgeLength" class="textField form-control form-control-sm" type="text" value="50" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Divisor to compute edge forces"> Edge Elasticity </span></td>
|
||||
<td><input id="edgeElasticity" class="textField form-control form-control-sm" type="text" value="0.45" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Nesting factor (multiplier) to compute ideal edge length for inter-graph edges"> Nesting Factor </span></td>
|
||||
<td><input id="nestingFactor" class="textField form-control form-control-sm" type="text" value="0.1" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Gravity force (constant)"> Gravity </span></td>
|
||||
<td><input id="gravity" class="textField form-control form-control-sm" type="text" value="0.25" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Gravity range (constant)"> Gravity Range </span></td>
|
||||
<td><input id="gravityRange" class="textField form-control form-control-sm" type="text" value="3.8" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Gravity force (constant) for compounds"> Compound Gravity </span></td>
|
||||
<td><input id="gravityCompound" class="textField form-control form-control-sm" type="text" value="1" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Gravity range (constant) for compounds"> Compound Gravity Range </span></td>
|
||||
<td><input id="gravityRangeCompound" class="textField form-control form-control-sm" type="text" value="1.5" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Maximum number of iterations to perform"> Number of Iterations </span></td>
|
||||
<td><input id="numIter" class="textField form-control form-control-sm" type="text" value="2500" size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Amount of vertical space to put between degree zero nodes during tiling"> Tiling Vertical Padding </span></td>
|
||||
<td><input id="tilingPaddingVertical" class="textField form-control form-control-sm" type="text" value="10" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Amount of horizontal space to put between degree zero nodes during tiling"> Tiling Horizontal Padding </span></td>
|
||||
<td><input id="tilingPaddingHorizontal" class="textField form-control form-control-sm" type="text" value="10" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Initial cooling factor for incremental layout"> Incremental Cooling Factor </span></td>
|
||||
<td><input id="initialEnergyOnIncremental" class="textField form-control form-control-sm" type="text" value="0.3" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="cy"></div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+1482
File diff suppressed because it is too large
Load Diff
+345
@@ -0,0 +1,345 @@
|
||||
<!DOCTYPE>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>cytoscape-fcose.js demo</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<!-- Bootstrap, popper, jQuery and filesaver - for demo purpose only -->
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
|
||||
<script src="https://raw.githack.com/eligrey/FileSaver.js/master/dist/FileSaver.min.js"></script>
|
||||
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Cytoscape and fcose -->
|
||||
<script src="https://unpkg.com/cytoscape/dist/cytoscape.min.js"></script>
|
||||
<script src="https://unpkg.com/layout-base/layout-base.js"></script>
|
||||
<script src="https://unpkg.com/cose-base/cose-base.js"></script>
|
||||
<script src="../cytoscape-fcose.js"></script>
|
||||
|
||||
<script src="demo-constraint-control.js" defer></script>
|
||||
|
||||
<!-- CoLa for local comparison only -->
|
||||
<!-- <script src="https://unpkg.com/webcola/WebCola/cola.min.js"></script> -->
|
||||
<!-- <script src="cytoscape-cola.js"></script> -->
|
||||
|
||||
<script src="https://unpkg.com/cytoscape-layout-utilities/cytoscape-layout-utilities.js"></script>
|
||||
<script src="https://unpkg.com/cytoscape-view-utilities/cytoscape-view-utilities.js"></script>
|
||||
<script src="https://unpkg.com/cytoscape-graphml/cytoscape-graphml.js"></script> <!-- graphml - for demo purpose only-->
|
||||
<!-- <script src="https://raw.githack.com/iVis-at-Bilkent/cytoscape.js-layvo/unstable/cytoscape-layvo.js"></script> --> <!-- For quality metrics -->
|
||||
<!-- <script src="https://raw.githack.com/kinimesi/cytoscape-svg/master/cytoscape-svg.js"></script> --> <!-- For svg export -->
|
||||
|
||||
<!-- We use a workaround here to be able to debug real world files in local by keeping json content as js variable. -->
|
||||
<script type="text/javascript" src="samples/unix.js"></script>
|
||||
<script type="text/javascript" src="samples/unix_constraints.js"></script>
|
||||
<script type="text/javascript" src="samples/chalk.js"></script>
|
||||
<script type="text/javascript" src="samples/chalk_constraints.js"></script>
|
||||
<script type="text/javascript" src="samples/uwsn.js"></script>
|
||||
<script type="text/javascript" src="samples/uwsn_constraints.js"></script>
|
||||
<script type="text/javascript" src="samples/callGraph.js"></script>
|
||||
<script type="text/javascript" src="samples/callGraph_constraints.js"></script>
|
||||
<script type="text/javascript" src="samples/wsn.js"></script>
|
||||
<script type="text/javascript" src="samples/wsn_constraints.js"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: helvetica neue, helvetica, liberation sans, arial, sans-serif;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#cy {
|
||||
position: relative;
|
||||
width: auto;
|
||||
height: 95%;
|
||||
z-index: 999;
|
||||
margin-left: 375px;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
opacity: 1;
|
||||
color: #555;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
hr.line {
|
||||
margin-top: 9px;
|
||||
margin-bottom: 9px
|
||||
}
|
||||
|
||||
/* The sidepanel menu */
|
||||
.sidepanel {
|
||||
height: auto; /* Specify a height */
|
||||
width: 375px; /* 0 width - change this with JavaScript */
|
||||
position: absolute; /* Stay in place */
|
||||
z-index: 1000; /* Stay on top */
|
||||
float: left;
|
||||
top: auto;
|
||||
left: 0;
|
||||
background-color: #d9fbff;
|
||||
overflow-x: hidden; /* Disable horizontal scroll */
|
||||
padding-top: 10px; /* Place content 10px from the top */
|
||||
padding-bottom: 10px; /* Place content 10px from the bottom */
|
||||
transition: 0s; /* 0.5 second transition effect to slide in the sidepanel */
|
||||
}
|
||||
|
||||
table{
|
||||
margin-left: 8;
|
||||
margin-right: 10;
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
/* The sidepanel links */
|
||||
table td {
|
||||
padding: 4px 4px 4px 0px;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
transition: 0.3s;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.layoutButton {
|
||||
color: #555;
|
||||
cursor: pointer;
|
||||
background-color: #b7ffff;
|
||||
padding: 5px 10px;
|
||||
border: none;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.constraintButton {
|
||||
cursor: pointer;
|
||||
padding: 2px 5px;
|
||||
background-color: #64ffee;
|
||||
border-radius: 3px;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.constraintButtonLarge {
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
background-color: #64ffee;
|
||||
border-radius: 3px;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.btn-group-sm>.btn, .btn-sm {
|
||||
font-size: 13px;
|
||||
background-color: #7d8991;
|
||||
border-color: #7d8991;
|
||||
}
|
||||
|
||||
.btn-primary:hover, .btn-primary:focus, .btn-primary:active, .btn-primary.active, .open>.dropdown-toggle.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #60717d;
|
||||
border-color: #60717d; /*set the color you want here*/
|
||||
}
|
||||
|
||||
.btn-outline-primary:hover, .btn-outline-primary:focus, .btn-outline-primary:active, .btn-outline-primary.active, .open>.dropdown-toggle.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #60717d;
|
||||
border-color: #60717d; /*set the color you want here*/
|
||||
}
|
||||
|
||||
.btn-outline-primary{
|
||||
font-size: 13px;
|
||||
background-color: #ffffff;
|
||||
color: #7d8991;
|
||||
}
|
||||
|
||||
.form-control-sm {
|
||||
font-size: 13px
|
||||
}
|
||||
|
||||
.custom-select-sm {
|
||||
font-size: 13px
|
||||
}
|
||||
|
||||
.textField {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #0f0;
|
||||
}
|
||||
|
||||
.custom-control-label {
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.custom-control-input:checked~.custom-control-label::before {
|
||||
color: #fff;
|
||||
border-color: #7d8991;
|
||||
background-color: #7d8991;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="ml-2">cytoscape-fcose demo (constraint)</h1>
|
||||
<div style='width: 375px; position: absolute;'>
|
||||
<!-- File menu-->
|
||||
<input id="inputFile" type='file' style="display: none" />
|
||||
<input type="button" id="openFile" type="button" class="btn btn-primary btn-sm ml-2" data-toggle="tooltip" title="Load graphml file" value="Load Graph"/> 
|
||||
<select id="sample" class="custom-select custom-select-sm ml-2" style="width:auto;">
|
||||
<option value="sample1" selected="">sample1 - fixed</option>
|
||||
<option value="sample2">sample2 - alignment</option>
|
||||
<option value="sample3">sample3 - relative</option>
|
||||
<option value="sample4">sample4 - hybrid</option>
|
||||
<option value="sample4">sample5</option>
|
||||
<option value="sample5">unix-family-tree</option>
|
||||
<option value="sample6">chalk-dependency</option>
|
||||
<option value="sample7">UW-sensor-network</option>
|
||||
<option value="sample8">python-call-graph</option>
|
||||
<option value="sample9">wireless-sensor-network</option>
|
||||
</select>
|
||||
<!-- File Menu End -->
|
||||
|
||||
<hr class="line">
|
||||
|
||||
<!-- Layout Menu-->
|
||||
<button id="randomizeButton" class="btn btn-primary btn-sm mb-2 ml-2">Randomize</button> 
|
||||
<button id="fcoseButton" class="btn btn-primary btn-sm mb-2">fCoSE</button> 
|
||||
<!-- <button id="colaButton" class="btn btn-primary btn-sm mb-2">CoLa</button>  -->
|
||||
<div class="custom-control custom-control-inline custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="incremental" name="incremental">
|
||||
<label class="custom-control-label" for="incremental">Incremental</label>
|
||||
</div><br>
|
||||
<button id="draftButton" class="btn btn-outline-primary btn-sm ml-2">Draft</button> 
|
||||
<button id="transformButton" class="btn btn-outline-primary btn-sm">Transform</button> 
|
||||
<button id="enforceButton" class="btn btn-outline-primary btn-sm">Enforce</button> 
|
||||
<button id="coseButton" class="btn btn-outline-primary btn-sm">CoSE</button><br>
|
||||
<!-- Layout Menu End-->
|
||||
|
||||
<hr class="line">
|
||||
<div id="mySidepanel" class="sidepanel">
|
||||
<!-- Constraints Menu -->
|
||||
<table id="ConstraintIOTable">
|
||||
<tr>
|
||||
<td style = "width: 25%"><h7 class="card-subtitle mt-1 text-muted"><b>Constraints</b></h7></td>
|
||||
<td><button id="importConstraint" type="button" class="btn btn-primary btn-sm ml-1" onclick="document.getElementById('inputConstraint').click();" data-toggle="tooltip" title="Import constraint file">Import</button>
|
||||
<input id="inputConstraint" type='file' style="display: none" /></td>
|
||||
<td><button id="exportConstraint" type="button" class="btn btn-primary btn-sm" data-toggle="tooltip" title="Export constraint file">Export</button></td>
|
||||
<td style = "width: 35%"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<hr class="line">
|
||||
|
||||
<!-- <button id="saveAsSVG" type="button" class="layoutButton" data-toggle="tooltip" title="Save graph as SVG file">Save as SVG</button> -->
|
||||
<!-- Fixed Node Constraints -->
|
||||
<form>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-12 mb-2">
|
||||
<h7 class="card-subtitle ml-2 mt-1 text-muted"><b>Fixed Node Constraint</b></h7>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row form-inline">
|
||||
<div id="nodeListColumn" class="form-group col-md-4 ml-2 mr-3">
|
||||
</div>
|
||||
<div class="form-group col-md-5">
|
||||
<label>x : </label>
|
||||
<input type="text" class="form-control form-control-sm w-25 ml-1 mr-1" style="display:flex; flex-grow:1" id="fixedNodeX" value="">
|
||||
<label>y : </label>
|
||||
<input type="text" class="form-control form-control-sm w-25 ml-1 mr-1" style="display:flex; flex-grow:1" id="fixedNodeY" value="">
|
||||
</div>
|
||||
<div class="form-group col-md-2">
|
||||
<input type="button" id="fixedNode" class="btn btn-primary btn-sm" value="Add"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr class="line">
|
||||
|
||||
<!-- Alignment Constraints -->
|
||||
<form>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-12 mb-2">
|
||||
<h7 class="card-subtitle ml-2 mt-1 text-muted"><b>Alignment Constraint</b></h7>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row form-inline mb-2">
|
||||
<div class="form-group col-md-8">
|
||||
<h7 class="card-subtitle ml-2 mt-1 text-muted">Selected Nodes Vertically</h7>
|
||||
</div>
|
||||
<div class="form-group col-md-3">
|
||||
<input type="button" id="verticalAlignment" class="btn btn-primary btn-sm" value="Add"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row form-inline">
|
||||
<div class="form-group col-md-8">
|
||||
<h7 class="card-subtitle ml-2 mt-1 text-muted">Selected Nodes Horizontally</h7>
|
||||
</div>
|
||||
<div class="form-group col-md-3">
|
||||
<input type="button" id="horizontalAlignment" class="btn btn-primary btn-sm" value="Add"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-12 mt-2 mb-0">
|
||||
<span style="margin-left: 9px; font-size: 12px">Click on a node for selecting. Shift + click for extending selection.</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr class="line">
|
||||
|
||||
<!-- Relative Placement Constraints -->
|
||||
<form>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-12 mb-2">
|
||||
<h7 class="card-subtitle ml-2 mt-1 text-muted"><b>Relative Placement Constraint</b></h7>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row form-inline">
|
||||
<div id="nodeListColumnRP1" class="form-group col-md-12 ml-2 mb-2"></div>
|
||||
</div>
|
||||
<div class="form-row form-inline mb-2">
|
||||
<div class="form-group col-md-4">
|
||||
<select id="directionList" class='custom-select custom-select-sm ml-2' style='width:auto;'>
|
||||
<option value="left-right">left-right</option>
|
||||
<option value="top-bottom">top-bottom</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<label>Gap: </label>
|
||||
<input type="text" class="form-control form-control-sm w-50 ml-1" id="gap" value="">
|
||||
</div>
|
||||
<div class="form-group col-md-2">
|
||||
<input type="button" id="relativePlacement" class="btn btn-primary btn-sm" value="Add"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row form-inline mb-2">
|
||||
<div id="nodeListColumnRP2" class="form-group col-md-12 ml-2"></div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr class="line">
|
||||
|
||||
<table id="constraintListTable" class="table-striped">
|
||||
<div class="form-group col-md-12 mt-1 mb-1" style="padding-left: 9px">
|
||||
<span style="font-size: 12px">Hover a constraint row to see involved nodes.</span>
|
||||
</div>
|
||||
<tr>
|
||||
<th style="font-size: 0.9em; text-align: left; width:25%">Type</th>
|
||||
<th style="font-size: 0.9em; text-align: left; width:40%">Nodes</th>
|
||||
<th style="font-size: 0.9em; text-align: left;">Info</th>
|
||||
<th style="font-size: 0.9em; text-align: left;"></th>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Constraints Menu End-->
|
||||
</div>
|
||||
<div id="cy"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 4.4 MiB |
+457
@@ -0,0 +1,457 @@
|
||||
<!DOCTYPE>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>cytoscape-fcose.js demo</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<!-- Bootstrap, popper, jQuery and filesaver - for demo purpose only -->
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
|
||||
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<script src="https://unpkg.com/cytoscape/dist/cytoscape.min.js"></script>
|
||||
<script src="https://unpkg.com/layout-base/layout-base.js"></script>
|
||||
<script src="https://unpkg.com/cose-base/cose-base.js"></script>
|
||||
<script src="https://unpkg.com/cytoscape-layout-utilities/cytoscape-layout-utilities.js"></script>
|
||||
|
||||
<!-- for testing with local version of cytoscape.js -->
|
||||
<!--<script src="../cytoscape.js/build/cytoscape.js"></script>-->
|
||||
|
||||
<script src="../cytoscape-fcose.js"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: helvetica neue, helvetica, liberation sans, arial, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#cy {
|
||||
position: relative;
|
||||
width: auto;
|
||||
height: 90%;
|
||||
z-index: 999;
|
||||
margin-left: 290px;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
opacity: 1;
|
||||
color: #555;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
/* The sidepanel menu */
|
||||
.sidepanel {
|
||||
height: auto; /* Specify a height */
|
||||
width: 290px; /* 0 width - change this with JavaScript */
|
||||
position: absolute; /* Stay in place */
|
||||
z-index: 1000; /* Stay on top */
|
||||
float: left;
|
||||
top: auto;
|
||||
left: 0;
|
||||
background-color: #b7ffff; /* Black*/
|
||||
overflow-x: hidden; /* Disable horizontal scroll */
|
||||
padding-top: 10px; /* Place content 10px from the top */
|
||||
padding-bottom: 10px; /* Place content 10px from the bottom */
|
||||
transition: 0s; /* 0.5 second transition effect to slide in the sidepanel */
|
||||
}
|
||||
|
||||
table{
|
||||
margin-left: 4px;
|
||||
margin-right: auto;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
/* The sidepanel links */
|
||||
table td {
|
||||
padding: 0px 8px 8px 8px;
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
color: #555;
|
||||
transition: 0.3s;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Style the button that is used to open the sidepanel */
|
||||
.button {
|
||||
font-size: 15px;
|
||||
color: #555;
|
||||
cursor: pointer;
|
||||
background-color: #b7ffff;
|
||||
padding: 5px 10px;
|
||||
border: none;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.textField {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #0f0;
|
||||
}
|
||||
|
||||
.btn-group-sm>.btn, .btn-sm {
|
||||
font-size: 13px;
|
||||
background-color: #7d8991;
|
||||
border-color: #7d8991;
|
||||
}
|
||||
|
||||
.btn-primary:hover, .btn-primary:focus, .btn-primary:active, .btn-primary.active, .open>.dropdown-toggle.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #60717d;
|
||||
border-color: #60717d; /*set the color you want here*/
|
||||
}
|
||||
|
||||
.btn-outline-primary:hover, .btn-outline-primary:focus, .btn-outline-primary:active, .btn-outline-primary.active, .open>.dropdown-toggle.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #60717d;
|
||||
border-color: #60717d; /*set the color you want here*/
|
||||
}
|
||||
|
||||
.form-control-sm {
|
||||
font-size: 13px
|
||||
}
|
||||
|
||||
.custom-select-sm {
|
||||
font-size: 13px
|
||||
}
|
||||
|
||||
.custom-control-label {
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.custom-control-input:checked~.custom-control-label::before {
|
||||
color: #fff;
|
||||
border-color: #7d8991;
|
||||
background-color: #7d8991;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function(){
|
||||
|
||||
var cy = window.cy = cytoscape({
|
||||
container: document.getElementById('cy'),
|
||||
|
||||
ready: function(){
|
||||
let layoutUtilities = this.layoutUtilities({
|
||||
desiredAspectRatio: this.width()/this.height()
|
||||
});
|
||||
this.nodes().forEach(function(node){
|
||||
let size = Math.random()*120+30;
|
||||
node.css("width", size);
|
||||
node.css("height", size);
|
||||
});
|
||||
this.layout({name: 'fcose'}).run();
|
||||
},
|
||||
|
||||
style: [
|
||||
{
|
||||
selector: 'node',
|
||||
style: {
|
||||
'background-color': '#2B65EC'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
selector: 'edge',
|
||||
style: {
|
||||
'width': 3,
|
||||
'line-color': '#2B65EC'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
selector: 'node:selected',
|
||||
style: {
|
||||
'background-color': '#F08080',
|
||||
'border-color': 'red'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
selector: 'edge:selected',
|
||||
style: {
|
||||
'line-color': '#F08080'
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
elements: [{"data":{"id":"glyph9","position":{"x":1452.639173965406,"y":609.3619416544145},"group":"nodes"}},
|
||||
{"data":{"id":"glyph0","position":{"x":1351.3490293961959,"y":518.9529901384763},"group":"nodes"}},
|
||||
{"data":{"id":"glyph6","position":{"x":1358.2854747390154,"y":707.9866590968695},"group":"nodes"}},
|
||||
{"data":{"id":"glyph8","position":{"x":1322.9939787691299,"y":614.6878118623499},"group":"nodes"}},
|
||||
{"data":{"id":"glyph7","position":{"x":1239.4852011317887,"y":543.2369849876238},"group":"nodes"}},
|
||||
{"data":{"id":"glyph12","position":{"x":841.6855140740067,"y":765.0152660242113},"group":"nodes"}},
|
||||
{"data":{"id":"glyph13","position":{"x":1019.5908382748769,"y":841.6087025848726},"group":"nodes"}},
|
||||
{"data":{"id":"glyph1","position":{"x":1231.2768042260652,"y":673.2683218469676},"group":"nodes"}},
|
||||
{"data":{"id":"glyph2","position":{"x":1039.8995038336504,"y":730.180116446269},"group":"nodes"}},
|
||||
{"data":{"id":"glyph15","position":{"x":569.5498472077387,"y":506.89980858075364},"group":"nodes"}},
|
||||
{"data":{"id":"glyph3","position":{"x":903.0347368937041,"y":654.3308627056822},"group":"nodes"}},
|
||||
{"data":{"id":"glyph17","position":{"x":1195.6310733031135,"y":820.9504141631944},"group":"nodes"}},
|
||||
{"data":{"id":"glyph10","position":{"x":1141.2404374322139,"y":732.3190922346248},"group":"nodes"}},
|
||||
{"data":{"id":"glyph19","position":{"x":893.1427762830865,"y":856.2695126662625},"group":"nodes"}},
|
||||
{"data":{"id":"glyph11","position":{"x":939.3335184518824,"y":758.3699048922733},"group":"nodes"}},
|
||||
{"data":{"id":"glyph18","position":{"x":770.4114528170364,"y":659.2220219290564},"group":"nodes"}},
|
||||
{"data":{"id":"glyph16","position":{"x":818.0111009023315,"y":564.8072603606723},"group":"nodes"}},
|
||||
{"data":{"id":"glyph22","position":{"x":651.1292498357636,"y":314.1387423188818},"group":"nodes"}},
|
||||
{"data":{"id":"glyph4","position":{"x":792.0076145303351,"y":454.0225025614517},"group":"nodes"}},
|
||||
{"data":{"id":"glyph23","position":{"x":704.0937009722281,"y":398.0421081673902},"group":"nodes"}},
|
||||
{"data":{"id":"glyph24","position":{"x":809.2974819306742,"y":231.7141323534711},"group":"nodes"}},
|
||||
{"data":{"id":"glyph25","position":{"x":890.826951363933,"y":299.74915938409947},"group":"nodes"}},
|
||||
{"data":{"id":"glyph20","position":{"x":786.2625869125006,"y":331.67766378118495},"group":"nodes"}},
|
||||
{"data":{"id":"glyph26","position":{"x":879.2981049664311,"y":389.27232563593486},"group":"nodes"}},
|
||||
{"data":{"id":"glyph35","position":{"x":627.088268638501,"y":40.089848876876886},"group":"nodes"}},
|
||||
{"data":{"id":"glyph36","position":{"x":329.6761506918384,"y":187.20503497360494},"group":"nodes"}},
|
||||
{"data":{"id":"glyph37","position":{"x":155.12947729633356,"y":379.5263531900425},"group":"nodes"}},
|
||||
{"data":{"id":"glyph38","position":{"x":70.13952165372024,"y":581.2691021233562},"group":"nodes"}},
|
||||
{"data":{"id":"glyph21","position":{"x":713.4639263718316,"y":229.06355211274115},"group":"nodes"}},
|
||||
{"data":{"id":"glyph42","position":{"x":523.848994074475,"y":108.47701882803744},"group":"nodes"}},
|
||||
{"data":{"id":"glyph41","position":{"x":718.966532806447,"y":116.46683749236911},"group":"nodes"}},
|
||||
{"data":{"id":"glyph31","position":{"x":621.3138039842713,"y":145.7168752444793},"group":"nodes"}},
|
||||
{"data":{"id":"glyph27","position":{"x":525.2099120385327,"y":210.92542274858295},"group":"nodes"}},
|
||||
{"data":{"id":"glyph32","position":{"x":426.3492127437995,"y":257.85665030680025},"group":"nodes"}},
|
||||
{"data":{"id":"glyph28","position":{"x":346.30926488002945,"y":344.4562152937847},"group":"nodes"}},
|
||||
{"data":{"id":"glyph43","position":{"x":363.54724181648487,"y":486.5705174517715},"group":"nodes"}},
|
||||
{"data":{"id":"glyph33","position":{"x":269.87972487503066,"y":430.2423722580144},"group":"nodes"}},
|
||||
{"data":{"id":"glyph29","position":{"x":227.86139816113416,"y":531.824141876398},"group":"nodes"}},
|
||||
{"data":{"id":"glyph39","position":{"x":104.77693104995387,"y":691.8382969303054},"group":"nodes"}},
|
||||
{"data":{"id":"glyph40","position":{"x":292.039416141131,"y":643.4009391289965},"group":"nodes"}},
|
||||
{"data":{"id":"glyph34","position":{"x":193.8304385062596,"y":632.9540034207419},"group":"nodes"}},
|
||||
{"data":{"id":"glyph30","position":{"x":205.4745704273754,"y":733.5181650652648},"group":"nodes"}},
|
||||
{"data":{"id":"glyph14","position":{"x":695.1248473196924,"y":482.8828321494848},"group":"nodes"}},
|
||||
{"data":{"id":"glyph5","position":{"x":721.6687687330186,"y":570.3868893775194},"group":"nodes"}},
|
||||
{"data":{"id":"e22","source":"glyph9","target":"glyph8","group":"edges"}},
|
||||
{"data":{"id":"e23","source":"glyph0","target":"glyph8","group":"edges"}},
|
||||
{"data":{"id":"e24","source":"glyph8","target":"glyph1","group":"edges"}},
|
||||
{"data":{"id":"e25","source":"glyph6","target":"glyph8","group":"edges"}},
|
||||
{"data":{"id":"e26","source":"glyph8","target":"glyph7","group":"edges"}},
|
||||
{"data":{"id":"e27","source":"glyph11","target":"glyph12","group":"edges"}},
|
||||
{"data":{"id":"e28","source":"glyph13","target":"glyph11","group":"edges"}},
|
||||
{"data":{"id":"e29","source":"glyph1","target":"glyph10","group":"edges"}},
|
||||
{"data":{"id":"e30","source":"glyph10","target":"glyph2","group":"edges"}},
|
||||
{"data":{"id":"e31","source":"glyph2","target":"glyph11","group":"edges"}},
|
||||
//{"data":{"id":"e32","source":"glyph11","target":"glyph3","group":"edges"}},
|
||||
{"data":{"id":"e33","source":"glyph14","target":"glyph4","group":"edges"}},
|
||||
{"data":{"id":"e34","source":"glyph15","target":"glyph14","group":"edges"}},
|
||||
{"data":{"id":"e35","source":"glyph3","target":"glyph16","group":"edges"}},
|
||||
{"data":{"id":"e36","source":"glyph16","target":"glyph5","group":"edges"}},
|
||||
{"data":{"id":"e37","source":"glyph16","target":"glyph4","group":"edges"}},
|
||||
{"data":{"id":"e38","source":"glyph17","target":"glyph10","group":"edges"}},
|
||||
{"data":{"id":"e39","source":"glyph19","target":"glyph11","group":"edges"}},
|
||||
{"data":{"id":"e40","source":"glyph18","target":"glyph16","group":"edges"}},
|
||||
//{"data":{"id":"e41","source":"glyph22","target":"glyph20","group":"edges"}},
|
||||
{"data":{"id":"e42","source":"glyph4","target":"glyph20","group":"edges"}},
|
||||
{"data":{"id":"e43","source":"glyph20","target":"glyph21","group":"edges"}},
|
||||
{"data":{"id":"e44","source":"glyph23","target":"glyph20","group":"edges"}},
|
||||
{"data":{"id":"e45","source":"glyph24","target":"glyph20","group":"edges"}},
|
||||
{"data":{"id":"e46","source":"glyph20","target":"glyph25","group":"edges"}},
|
||||
{"data":{"id":"e47","source":"glyph20","target":"glyph26","group":"edges"}},
|
||||
{"data":{"id":"e48","source":"glyph35","target":"glyph31","group":"edges"}},
|
||||
{"data":{"id":"e49","source":"glyph36","target":"glyph32","group":"edges"}},
|
||||
//{"data":{"id":"e50","source":"glyph37","target":"glyph33","group":"edges"}},
|
||||
{"data":{"id":"e51","source":"glyph38","target":"glyph34","group":"edges"}},
|
||||
{"data":{"id":"e52","source":"glyph21","target":"glyph31","group":"edges"}},
|
||||
{"data":{"id":"e53","source":"glyph42","target":"glyph31","group":"edges"}},
|
||||
{"data":{"id":"e54","source":"glyph31","target":"glyph41","group":"edges"}},
|
||||
{"data":{"id":"e55","source":"glyph31","target":"glyph27","group":"edges"}},
|
||||
{"data":{"id":"e56","source":"glyph27","target":"glyph32","group":"edges"}},
|
||||
{"data":{"id":"e57","source":"glyph32","target":"glyph28","group":"edges"}},
|
||||
{"data":{"id":"e58","source":"glyph28","target":"glyph33","group":"edges"}},
|
||||
//{"data":{"id":"e59","source":"glyph33","target":"glyph43","group":"edges"}},
|
||||
//{"data":{"id":"e60","source":"glyph33","target":"glyph29","group":"edges"}},
|
||||
{"data":{"id":"e61","source":"glyph29","target":"glyph34","group":"edges"}},
|
||||
//{"data":{"id":"e62","source":"glyph39","target":"glyph34","group":"edges"}},
|
||||
{"data":{"id":"e63","source":"glyph34","target":"glyph40","group":"edges"}},
|
||||
{"data":{"id":"e64","source":"glyph34","target":"glyph30","group":"edges"}},
|
||||
{"data":{"id":"e65","source":"glyph14","target":"glyph5","group":"edges"}},
|
||||
{"data":{"id":"e66","source":"glyph33","target":"glyph35","group":"edges"}},
|
||||
{"data":{"id":"e67","source":"glyph13","target":"glyph22","group":"edges"}},
|
||||
{"data":{"id":"e68","source":"glyph17","target":"glyph6","group":"edges"}},
|
||||
{"data":{"id":"e69","source":"glyph25","target":"glyph27","group":"edges"}}]
|
||||
});
|
||||
|
||||
document.getElementById("randomizeButton").addEventListener("click", function(){
|
||||
var layout = cy.layout({
|
||||
name: 'random',
|
||||
animate: true,
|
||||
animationDuration: 1000
|
||||
});
|
||||
|
||||
layout.run();
|
||||
});
|
||||
|
||||
document.getElementById("fcoseButton").addEventListener("click", function(){
|
||||
let qualityItem = document.getElementById("quality");
|
||||
var layout = cy.layout({
|
||||
name: 'fcose',
|
||||
quality: qualityItem.options[qualityItem.selectedIndex].value,
|
||||
randomize: !(document.getElementById("randomize").checked),
|
||||
animate: document.getElementById("animate").checked,
|
||||
animationEasing: 'ease-out',
|
||||
fit: document.getElementById("fit").checked,
|
||||
uniformNodeDimensions: document.getElementById("uniformNodeDimensions").checked,
|
||||
packComponents: document.getElementById("packComponents").checked,
|
||||
tile: document.getElementById("tile").checked,
|
||||
nodeRepulsion: parseFloat(document.getElementById("nodeRepulsion").value),
|
||||
idealEdgeLength: parseFloat(document.getElementById("idealEdgeLength").value),
|
||||
edgeElasticity: parseFloat(document.getElementById("edgeElasticity").value),
|
||||
nestingFactor: parseFloat(document.getElementById("nestingFactor").value),
|
||||
gravity: parseFloat(document.getElementById("gravity").value),
|
||||
gravityRange: parseFloat(document.getElementById("gravityRange").value),
|
||||
gravityCompound: parseFloat(document.getElementById("gravityCompound").value),
|
||||
gravityRangeCompound: parseFloat(document.getElementById("gravityRangeCompound").value),
|
||||
numIter: parseFloat(document.getElementById("numIter").value),
|
||||
tilingPaddingVertical: parseFloat(document.getElementById("tilingPaddingVertical").value),
|
||||
tilingPaddingHorizontal: parseFloat(document.getElementById("tilingPaddingHorizontal").value),
|
||||
initialEnergyOnIncremental: document.getElementById("initialEnergyOnIncremental").value,
|
||||
step:"all"
|
||||
});
|
||||
|
||||
layout.run();
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1 class="ml-2">cytoscape-fcose demo</h1>
|
||||
<div style='width: 300px; position: absolute;'>
|
||||
<button id="randomizeButton" class="btn btn-primary btn-sm mb-2 ml-2">Randomize</button> 
|
||||
<button id="fcoseButton" class="btn btn-primary btn-sm mb-2">fCoSE</button> 
|
||||
|
||||
<div id="mySidepanel" class="sidepanel">
|
||||
<table>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Quality of the layout"> Quality </span></td>
|
||||
<td>
|
||||
<select id="quality" class='custom-select custom-select-sm'>
|
||||
<option value="draft">draft</option>
|
||||
<option value="default" selected="">default</option>
|
||||
<option value="proof">proof</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Whether to enable incremental mode"> Incremental </span></td>
|
||||
<td>
|
||||
<div class="custom-control custom-control-inline custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="randomize" name="incremental">
|
||||
<label class="custom-control-label" for="randomize"></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Whether to perform animation after layout"> Animate </span></td>
|
||||
<td>
|
||||
<div class="custom-control custom-control-inline custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="animate" name="animate" checked>
|
||||
<label class="custom-control-label" for="animate"></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Whether to fit the viewport to the repositioned nodes"> Fit </span></td>
|
||||
<td>
|
||||
<div class="custom-control custom-control-inline custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="fit" name="fit" checked>
|
||||
<label class="custom-control-label" for="fit"></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Whether nodes are of uniform dimensions"> Uniform Node Dimensions </span></td>
|
||||
<td>
|
||||
<div class="custom-control custom-control-inline custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="uniformNodeDimensions" name="uniformNodeDimensions">
|
||||
<label class="custom-control-label" for="uniformNodeDimensions"></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Whether to pack components"> Pack Components to Window</span></td>
|
||||
<td>
|
||||
<div class="custom-control custom-control-inline custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="packComponents" name="packComponents" checked>
|
||||
<label class="custom-control-label" for="packComponents"></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Whether to tile disconnected nodes"> Tile Disconnected </span></td>
|
||||
<td>
|
||||
<div class="custom-control custom-control-inline custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="tile" name="tile" checked>
|
||||
<label class="custom-control-label" for="tile"></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Node repulsion (non overlapping) multiplier"> Node Repulsion </span></td>
|
||||
<td><input id="nodeRepulsion" class="textField form-control form-control-sm" type="text" value="4500" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Ideal (intra-graph) edge length"> Ideal Edge Length </span></td>
|
||||
<td><input id="idealEdgeLength" class="textField form-control form-control-sm" type="text" value="50" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Divisor to compute edge forces"> Edge Elasticity </span></td>
|
||||
<td><input id="edgeElasticity" class="textField form-control form-control-sm" type="text" value="0.45" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Nesting factor (multiplier) to compute ideal edge length for inter-graph edges"> Nesting Factor </span></td>
|
||||
<td><input id="nestingFactor" class="textField form-control form-control-sm" type="text" value="0.1" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Gravity force (constant)"> Gravity </span></td>
|
||||
<td><input id="gravity" class="textField form-control form-control-sm" type="text" value="0.25" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Gravity range (constant)"> Gravity Range </span></td>
|
||||
<td><input id="gravityRange" class="textField form-control form-control-sm" type="text" value="3.8" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Gravity force (constant) for compounds"> Compound Gravity </span></td>
|
||||
<td><input id="gravityCompound" class="textField form-control form-control-sm" type="text" value="1" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Gravity range (constant) for compounds"> Compound Gravity Range </span></td>
|
||||
<td><input id="gravityRangeCompound" class="textField form-control form-control-sm" type="text" value="1.5" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Maximum number of iterations to perform"> Number of Iterations </span></td>
|
||||
<td><input id="numIter" class="textField form-control form-control-sm" type="text" value="2500" size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Amount of vertical space to put between degree zero nodes during tiling"> Tiling Vertical Padding </span></td>
|
||||
<td><input id="tilingPaddingVertical" class="textField form-control form-control-sm" type="text" value="10" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Amount of horizontal space to put between degree zero nodes during tiling"> Tiling Horizontal Padding </span></td>
|
||||
<td><input id="tilingPaddingHorizontal" class="textField form-control form-control-sm" type="text" value="10" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="add-on layout-text" title="Initial cooling factor for incremental layout"> Incremental Cooling Factor </span></td>
|
||||
<td><input id="initialEnergyOnIncremental" class="textField form-control form-control-sm" type="text" value="0.3" maxlength=5 size="5"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="cy"></div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 3.0 MiB |
+1616
File diff suppressed because it is too large
Load Diff
+123
@@ -0,0 +1,123 @@
|
||||
callGraph_constraints = {
|
||||
"alignmentConstraint": {
|
||||
"horizontal": [
|
||||
[
|
||||
"288a2f9b-2930-701e-c68e-400307fbffd4",
|
||||
"nwtN_a44ad134-4c47-4cf5-b244-9d9fc0804e93",
|
||||
"nwtN_ac02fa71-2428-47ad-8c3c-80bb5659a066"
|
||||
],
|
||||
[
|
||||
"nwtN_550a66a2-1061-45cc-9f78-14570daaff94",
|
||||
"82181180-e275-b004-243e-eb4383e1e72f",
|
||||
"359e920c-52a4-459e-2262-4ec90824f21e",
|
||||
"5a34dca5-646c-cae7-affe-ef4142c5ad73",
|
||||
"2f560d5b-bb1b-cc3f-d5b5-a69e6fab8d47",
|
||||
"d07ee031-2818-7c9c-5589-e8a161b9946c",
|
||||
"912c0f08-87fb-c746-18cb-19909a2b41c8"
|
||||
],
|
||||
[
|
||||
"1f64237f-dd15-2d5f-18f0-02a0d0338f44",
|
||||
"63edffef-16cf-a216-5ace-a27a4290e3d8",
|
||||
"d6768356-8772-a49a-c2f7-c3b9802c954b",
|
||||
"05b42313-71a4-e1a9-383b-6b4be641e69d"
|
||||
],
|
||||
[
|
||||
"3c161178-1fca-ec7e-3fc7-425ce940fe1a",
|
||||
"bab5c8a0-0f39-66d4-eaca-7f9160ef25fa",
|
||||
"a0d92978-e6e9-6d87-9a06-567d139d15af"
|
||||
]
|
||||
]
|
||||
},
|
||||
"relativePlacementConstraint": [
|
||||
{
|
||||
"top": "nwtN_6e27959a-ae83-4cb0-898f-9a1e6c5cafae",
|
||||
"bottom": "d2f78da5-fbd3-fe91-4046-3b5aea0648f6",
|
||||
"gap": 98.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_6e27959a-ae83-4cb0-898f-9a1e6c5cafae",
|
||||
"bottom": "nwtN_a44ad134-4c47-4cf5-b244-9d9fc0804e93",
|
||||
"gap": 98.25
|
||||
},
|
||||
{
|
||||
"top": "d2f78da5-fbd3-fe91-4046-3b5aea0648f6",
|
||||
"bottom": "288a2f9b-2930-701e-c68e-400307fbffd4",
|
||||
"gap": 98.25
|
||||
},
|
||||
{
|
||||
"top": "d2f78da5-fbd3-fe91-4046-3b5aea0648f6",
|
||||
"bottom": "nwtN_ac02fa71-2428-47ad-8c3c-80bb5659a066",
|
||||
"gap": 98.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_ac02fa71-2428-47ad-8c3c-80bb5659a066",
|
||||
"bottom": "912c0f08-87fb-c746-18cb-19909a2b41c8",
|
||||
"gap": 98.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_ac02fa71-2428-47ad-8c3c-80bb5659a066",
|
||||
"bottom": "82181180-e275-b004-243e-eb4383e1e72f",
|
||||
"gap": 98.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_ac02fa71-2428-47ad-8c3c-80bb5659a066",
|
||||
"bottom": "359e920c-52a4-459e-2262-4ec90824f21e",
|
||||
"gap": 98.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_ac02fa71-2428-47ad-8c3c-80bb5659a066",
|
||||
"bottom": "5a34dca5-646c-cae7-affe-ef4142c5ad73",
|
||||
"gap": 98.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_ac02fa71-2428-47ad-8c3c-80bb5659a066",
|
||||
"bottom": "2f560d5b-bb1b-cc3f-d5b5-a69e6fab8d47",
|
||||
"gap": 98.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_a44ad134-4c47-4cf5-b244-9d9fc0804e93",
|
||||
"bottom": "nwtN_550a66a2-1061-45cc-9f78-14570daaff94",
|
||||
"gap": 98.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_a44ad134-4c47-4cf5-b244-9d9fc0804e93",
|
||||
"bottom": "d07ee031-2818-7c9c-5589-e8a161b9946c",
|
||||
"gap": 98.25
|
||||
},
|
||||
{
|
||||
"top": "2f560d5b-bb1b-cc3f-d5b5-a69e6fab8d47",
|
||||
"bottom": "1f64237f-dd15-2d5f-18f0-02a0d0338f44",
|
||||
"gap": 98.25
|
||||
},
|
||||
{
|
||||
"top": "d07ee031-2818-7c9c-5589-e8a161b9946c",
|
||||
"bottom": "63edffef-16cf-a216-5ace-a27a4290e3d8",
|
||||
"gap": 98.25
|
||||
},
|
||||
{
|
||||
"top": "d07ee031-2818-7c9c-5589-e8a161b9946c",
|
||||
"bottom": "d6768356-8772-a49a-c2f7-c3b9802c954b",
|
||||
"gap": 98.25
|
||||
},
|
||||
{
|
||||
"top": "d07ee031-2818-7c9c-5589-e8a161b9946c",
|
||||
"bottom": "05b42313-71a4-e1a9-383b-6b4be641e69d",
|
||||
"gap": 98.25
|
||||
},
|
||||
{
|
||||
"top": "63edffef-16cf-a216-5ace-a27a4290e3d8",
|
||||
"bottom": "3c161178-1fca-ec7e-3fc7-425ce940fe1a",
|
||||
"gap": 98.25
|
||||
},
|
||||
{
|
||||
"top": "63edffef-16cf-a216-5ace-a27a4290e3d8",
|
||||
"bottom": "bab5c8a0-0f39-66d4-eaca-7f9160ef25fa",
|
||||
"gap": 98.25
|
||||
},
|
||||
{
|
||||
"top": "63edffef-16cf-a216-5ace-a27a4290e3d8",
|
||||
"bottom": "a0d92978-e6e9-6d87-9a06-567d139d15af",
|
||||
"gap": 98.25
|
||||
}
|
||||
]
|
||||
}
|
||||
+1305
File diff suppressed because it is too large
Load Diff
+78
@@ -0,0 +1,78 @@
|
||||
chalk_constraints = {
|
||||
"alignmentConstraint": {
|
||||
"vertical": [
|
||||
[
|
||||
"nwtN_c152aeef-01df-49fc-9539-9e022e8d4c64",
|
||||
"nwtN_fa99aac7-9500-4ead-82c3-90c72e9837f3",
|
||||
"nwtN_6442ea6c-8a74-4948-ab60-2a2fc6b904f7",
|
||||
"nwtN_395e3fed-fcd4-427a-909a-c01a7fc94607"
|
||||
],
|
||||
[
|
||||
"nwtN_fc4a5aa8-171f-41bd-8ab3-5eee343c4f9f",
|
||||
"nwtN_10cd6cfc-6d2a-48b1-82e1-29d16dadd069",
|
||||
"nwtN_88c73829-9d76-4b03-ab27-335fd9581531"
|
||||
],
|
||||
[
|
||||
"nwtN_23452b85-7855-413f-a03d-26412ed87bfc",
|
||||
"nwtN_6ffc1fdf-602f-4395-89b5-666b383a5059"
|
||||
]
|
||||
]
|
||||
},
|
||||
"relativePlacementConstraint": [
|
||||
{
|
||||
"left": "nwtN_6ffc1fdf-602f-4395-89b5-666b383a5059",
|
||||
"right": "nwtN_c152aeef-01df-49fc-9539-9e022e8d4c64",
|
||||
"gap": 180
|
||||
},
|
||||
{
|
||||
"left": "nwtN_6ffc1fdf-602f-4395-89b5-666b383a5059",
|
||||
"right": "nwtN_fa99aac7-9500-4ead-82c3-90c72e9837f3",
|
||||
"gap": 180
|
||||
},
|
||||
{
|
||||
"left": "nwtN_6ffc1fdf-602f-4395-89b5-666b383a5059",
|
||||
"right": "nwtN_6442ea6c-8a74-4948-ab60-2a2fc6b904f7",
|
||||
"gap": 180
|
||||
},
|
||||
{
|
||||
"left": "nwtN_6ffc1fdf-602f-4395-89b5-666b383a5059",
|
||||
"right": "nwtN_395e3fed-fcd4-427a-909a-c01a7fc94607",
|
||||
"gap": 180
|
||||
},
|
||||
{
|
||||
"left": "nwtN_fa99aac7-9500-4ead-82c3-90c72e9837f3",
|
||||
"right": "nwtN_10cd6cfc-6d2a-48b1-82e1-29d16dadd069",
|
||||
"gap": 120
|
||||
},
|
||||
{
|
||||
"left": "nwtN_395e3fed-fcd4-427a-909a-c01a7fc94607",
|
||||
"right": "nwtN_fc4a5aa8-171f-41bd-8ab3-5eee343c4f9f",
|
||||
"gap": 120
|
||||
},
|
||||
{
|
||||
"left": "nwtN_395e3fed-fcd4-427a-909a-c01a7fc94607",
|
||||
"right": "nwtN_88c73829-9d76-4b03-ab27-335fd9581531",
|
||||
"gap": 120
|
||||
},
|
||||
{
|
||||
"left": "nwtN_10cd6cfc-6d2a-48b1-82e1-29d16dadd069",
|
||||
"right": "nwtN_ec240627-f567-42b4-a9c6-4564450e9946",
|
||||
"gap": 100
|
||||
},
|
||||
{
|
||||
"left": "nwtN_10cd6cfc-6d2a-48b1-82e1-29d16dadd069",
|
||||
"right": "nwtN_da2a2f8c-a185-4ffd-b577-f2c5a7680a37",
|
||||
"gap": 120
|
||||
},
|
||||
{
|
||||
"left": "nwtN_ec240627-f567-42b4-a9c6-4564450e9946",
|
||||
"right": "nwtN_da2a2f8c-a185-4ffd-b577-f2c5a7680a37",
|
||||
"gap": 120
|
||||
},
|
||||
{
|
||||
"left": "nwtN_da2a2f8c-a185-4ffd-b577-f2c5a7680a37",
|
||||
"right": "nwtN_2f088c82-aac6-4e48-8fde-8574a3390d24",
|
||||
"gap": 120
|
||||
}
|
||||
]
|
||||
}
|
||||
+2868
File diff suppressed because it is too large
Load Diff
+249
@@ -0,0 +1,249 @@
|
||||
unix_constraints = {
|
||||
"relativePlacementConstraint": [
|
||||
{
|
||||
"top": "nwtN_4451c335-4668-4f8d-9f71-274aa2e6ab06",
|
||||
"bottom": "nwtN_8d99e7af-1b2f-4605-a381-ce3127b6199a",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_4451c335-4668-4f8d-9f71-274aa2e6ab06",
|
||||
"bottom": "nwtN_37220ed2-0cbe-4d1f-9cac-07dfb11d6eda",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_8d99e7af-1b2f-4605-a381-ce3127b6199a",
|
||||
"bottom": "nwtN_020b84b2-717f-4465-b716-7520e5c9c470",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_8d99e7af-1b2f-4605-a381-ce3127b6199a",
|
||||
"bottom": "nwtN_c1880331-8411-443e-9218-87e78c057b22",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_8d99e7af-1b2f-4605-a381-ce3127b6199a",
|
||||
"bottom": "nwtN_3f3b6234-e449-44bb-9ba8-059d1f843c98",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_8d99e7af-1b2f-4605-a381-ce3127b6199a",
|
||||
"bottom": "nwtN_d7bbe746-9028-4268-a642-70544788d687",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_8d99e7af-1b2f-4605-a381-ce3127b6199a",
|
||||
"bottom": "nwtN_5bb0a938-c7ee-42d8-85c8-17bdc1ff0ade",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_37220ed2-0cbe-4d1f-9cac-07dfb11d6eda",
|
||||
"bottom": "nwtN_8227939a-5dc0-433e-8c4d-987ec44a1f8f",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_37220ed2-0cbe-4d1f-9cac-07dfb11d6eda",
|
||||
"bottom": "nwtN_3f7d61be-a016-4383-9b8d-8c1db26192f2",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_020b84b2-717f-4465-b716-7520e5c9c470",
|
||||
"bottom": "nwtN_166d8364-fd55-499e-9098-0a48db6615c1",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_c1880331-8411-443e-9218-87e78c057b22",
|
||||
"bottom": "nwtN_167a075d-9d93-49d0-b5be-446efebec06c",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_c1880331-8411-443e-9218-87e78c057b22",
|
||||
"bottom": "nwtN_db35fc70-2b89-4857-acb2-9dd545e61447",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_c1880331-8411-443e-9218-87e78c057b22",
|
||||
"bottom": "nwtN_9dfde51e-a410-4e3b-a472-9061ae4fae7a",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_8227939a-5dc0-433e-8c4d-987ec44a1f8f",
|
||||
"bottom": "nwtN_9dfde51e-a410-4e3b-a472-9061ae4fae7a",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_3f7d61be-a016-4383-9b8d-8c1db26192f2",
|
||||
"bottom": "nwtN_a186f2a4-524e-4a84-95c0-66d41dbda586",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_3f7d61be-a016-4383-9b8d-8c1db26192f2",
|
||||
"bottom": "nwtN_26c73172-708d-45cc-8ba5-f29671089767",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_167a075d-9d93-49d0-b5be-446efebec06c",
|
||||
"bottom": "nwtN_a2bfb9bd-8b13-43a5-87a5-d88b0137bd78",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_167a075d-9d93-49d0-b5be-446efebec06c",
|
||||
"bottom": "nwtN_2d9c1f24-1482-46fb-a0f4-f963ba454710",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_167a075d-9d93-49d0-b5be-446efebec06c",
|
||||
"bottom": "nwtN_15fb1e74-6b08-4914-95ac-ee4ecb1b7f17",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_167a075d-9d93-49d0-b5be-446efebec06c",
|
||||
"bottom": "nwtN_485d980b-dc42-48ca-9ada-3c1b754cc2ef",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_167a075d-9d93-49d0-b5be-446efebec06c",
|
||||
"bottom": "nwtN_bce869d1-7c58-4489-81aa-e87e9418ab70",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_9dfde51e-a410-4e3b-a472-9061ae4fae7a",
|
||||
"bottom": "nwtN_db35fc70-2b89-4857-acb2-9dd545e61447",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_a186f2a4-524e-4a84-95c0-66d41dbda586",
|
||||
"bottom": "nwtN_2cf0c5aa-67eb-4394-85e3-dc84f4b1cd52",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_26c73172-708d-45cc-8ba5-f29671089767",
|
||||
"bottom": "nwtN_2b6ef682-26c7-48d7-9fd8-09fa76c7b718",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_a2bfb9bd-8b13-43a5-87a5-d88b0137bd78",
|
||||
"bottom": "nwtN_be8097dc-4a84-4d3f-ade9-e80221f86dd6",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_d53ea35c-4609-4051-917a-8a183e5976cf",
|
||||
"bottom": "nwtN_c261ca93-6060-49c1-8286-2a98900c3b9e",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_db35fc70-2b89-4857-acb2-9dd545e61447",
|
||||
"bottom": "nwtN_a68571c0-2669-4894-aa53-332555f625a2",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_eed8fc62-a21d-4677-85f2-f1244e3abf00",
|
||||
"bottom": "nwtN_2f175f07-154a-4873-98cc-bc4e4d6d13a1",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_eed8fc62-a21d-4677-85f2-f1244e3abf00",
|
||||
"bottom": "nwtN_a68571c0-2669-4894-aa53-332555f625a2",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_eed8fc62-a21d-4677-85f2-f1244e3abf00",
|
||||
"bottom": "nwtN_e55d29c7-89bc-4aa0-b599-3637e141f17f",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_c261ca93-6060-49c1-8286-2a98900c3b9e",
|
||||
"bottom": "nwtN_02a50d8b-3939-492a-ac5b-1f356cd514f6",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_2f175f07-154a-4873-98cc-bc4e4d6d13a1",
|
||||
"bottom": "nwtN_a68571c0-2669-4894-aa53-332555f625a2",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_166d8364-fd55-499e-9098-0a48db6615c1",
|
||||
"bottom": "nwtN_205680d2-dc98-4faf-baf4-21e27e8585e4",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_02a50d8b-3939-492a-ac5b-1f356cd514f6",
|
||||
"bottom": "nwtN_205680d2-dc98-4faf-baf4-21e27e8585e4",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_02a50d8b-3939-492a-ac5b-1f356cd514f6",
|
||||
"bottom": "nwtN_75318a95-e179-40fc-b6e1-e01fcd66cd8c",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_02a50d8b-3939-492a-ac5b-1f356cd514f6",
|
||||
"bottom": "nwtN_bce869d1-7c58-4489-81aa-e87e9418ab70",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_a68571c0-2669-4894-aa53-332555f625a2",
|
||||
"bottom": "nwtN_8ed84db0-02da-43c6-847c-6b33f657f52c",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_205680d2-dc98-4faf-baf4-21e27e8585e4",
|
||||
"bottom": "nwtN_be8097dc-4a84-4d3f-ade9-e80221f86dd6",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_205680d2-dc98-4faf-baf4-21e27e8585e4",
|
||||
"bottom": "nwtN_8489a816-b7af-452f-a722-04d06226341c",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_75318a95-e179-40fc-b6e1-e01fcd66cd8c",
|
||||
"bottom": "nwtN_d4bf8470-416d-4387-a563-3c9cccf55079",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_75318a95-e179-40fc-b6e1-e01fcd66cd8c",
|
||||
"bottom": "nwtN_e48d9732-8ee0-4714-8b62-8de8238ec9c2",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_bce869d1-7c58-4489-81aa-e87e9418ab70",
|
||||
"bottom": "nwtN_103a9105-ef50-47a4-b501-840a431fcbdd",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_8ed84db0-02da-43c6-847c-6b33f657f52c",
|
||||
"bottom": "nwtN_6385f491-0d09-4d53-a34c-781a861e8b42",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_6385f491-0d09-4d53-a34c-781a861e8b42",
|
||||
"bottom": "nwtN_88e20782-9221-4b12-9b62-6a6c7116a70f",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_167a075d-9d93-49d0-b5be-446efebec06c",
|
||||
"bottom": "nwtN_be8097dc-4a84-4d3f-ade9-e80221f86dd6",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_485d980b-dc42-48ca-9ada-3c1b754cc2ef",
|
||||
"bottom": "nwtN_d53ea35c-4609-4051-917a-8a183e5976cf",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_2cf0c5aa-67eb-4394-85e3-dc84f4b1cd52",
|
||||
"bottom": "nwtN_db35fc70-2b89-4857-acb2-9dd545e61447",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_cc15f875-f4dd-4df8-a09e-a4f2e6c8a3f5",
|
||||
"bottom": "nwtN_db35fc70-2b89-4857-acb2-9dd545e61447",
|
||||
"gap": 76.25
|
||||
},
|
||||
{
|
||||
"top": "nwtN_2b6ef682-26c7-48d7-9fd8-09fa76c7b718",
|
||||
"bottom": "nwtN_eed8fc62-a21d-4677-85f2-f1244e3abf00",
|
||||
"gap": 76.25
|
||||
}
|
||||
]
|
||||
}
|
||||
+2303
File diff suppressed because it is too large
Load Diff
+88
@@ -0,0 +1,88 @@
|
||||
uwsn_constraints = {
|
||||
"fixedNodeConstraint": [
|
||||
{
|
||||
"nodeId": "nwtN_91cff953-5f55-4626-96fa-e2f675e13e54",
|
||||
"position": {
|
||||
"x": -650,
|
||||
"y": -230
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeId": "nwtN_b20cc3f4-dc9c-4031-ac79-df665d4c53cd",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": -230
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeId": "nwtN_5d6f7198-03e4-48e5-9004-919221f87e66",
|
||||
"position": {
|
||||
"x": 650,
|
||||
"y": -230
|
||||
}
|
||||
}
|
||||
],
|
||||
"alignmentConstraint": {
|
||||
"vertical": [
|
||||
[
|
||||
"nwtN_91cff953-5f55-4626-96fa-e2f675e13e54",
|
||||
"nwtN_7e09bf9e-a4da-4618-baba-60a2b3a0b134"
|
||||
],
|
||||
[
|
||||
"nwtN_b20cc3f4-dc9c-4031-ac79-df665d4c53cd",
|
||||
"661498c7-ace9-bd34-0289-7c9931d3e1ba"
|
||||
],
|
||||
[
|
||||
"nwtN_5d6f7198-03e4-48e5-9004-919221f87e66",
|
||||
"aeee80bf-8280-f4ec-c1f3-26f3a7a0651c"
|
||||
]
|
||||
]
|
||||
},
|
||||
"relativePlacementConstraint": [
|
||||
{
|
||||
"top": "nwtN_7e09bf9e-a4da-4618-baba-60a2b3a0b134",
|
||||
"bottom": "nwtN_be627c29-b885-460c-bf92-8f9bb7b8c8c5",
|
||||
"gap": 100
|
||||
},
|
||||
{
|
||||
"top": "nwtN_7e09bf9e-a4da-4618-baba-60a2b3a0b134",
|
||||
"bottom": "nwtN_64abed47-d1b1-474c-b944-b9ad6354c086",
|
||||
"gap": 100
|
||||
},
|
||||
{
|
||||
"top": "661498c7-ace9-bd34-0289-7c9931d3e1ba",
|
||||
"bottom": "nwtN_0185dada-235a-4b51-bce7-e8bd3b8ee661",
|
||||
"gap": 100
|
||||
},
|
||||
{
|
||||
"top": "661498c7-ace9-bd34-0289-7c9931d3e1ba",
|
||||
"bottom": "nwtN_273f6927-f05a-425b-aa2b-92de2be06bd9",
|
||||
"gap": 100
|
||||
},
|
||||
{
|
||||
"top": "aeee80bf-8280-f4ec-c1f3-26f3a7a0651c",
|
||||
"bottom": "5c108d5c-88e4-7b97-95a8-67238c33d283",
|
||||
"gap": 100
|
||||
},
|
||||
{
|
||||
"top": "aeee80bf-8280-f4ec-c1f3-26f3a7a0651c",
|
||||
"bottom": "3ea6020e-8918-8abf-a8e5-62427070f84f",
|
||||
"gap": 100
|
||||
},
|
||||
{
|
||||
"top": "nwtN_5d6f7198-03e4-48e5-9004-919221f87e66",
|
||||
"bottom": "aeee80bf-8280-f4ec-c1f3-26f3a7a0651c",
|
||||
"gap": 250
|
||||
},
|
||||
{
|
||||
"top": "nwtN_b20cc3f4-dc9c-4031-ac79-df665d4c53cd",
|
||||
"bottom": "661498c7-ace9-bd34-0289-7c9931d3e1ba",
|
||||
"gap": 250
|
||||
},
|
||||
{
|
||||
"top": "nwtN_91cff953-5f55-4626-96fa-e2f675e13e54",
|
||||
"bottom": "nwtN_7e09bf9e-a4da-4618-baba-60a2b3a0b134",
|
||||
"gap": 250
|
||||
}
|
||||
]
|
||||
}
|
||||
+7640
File diff suppressed because it is too large
Load Diff
+102
@@ -0,0 +1,102 @@
|
||||
wsn_constraints = {
|
||||
"fixedNodeConstraint": [
|
||||
{
|
||||
"nodeId": "86f48f58-145d-92cc-0d33-aaf639984733",
|
||||
"position": {
|
||||
"x": 399,
|
||||
"y": 139
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeId": "f91d914e-6ab7-92cf-b16d-b9a7661e0044",
|
||||
"position": {
|
||||
"x": 857,
|
||||
"y": 340
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeId": "33f80abd-028e-ad1c-068b-33d5b40d9a93",
|
||||
"position": {
|
||||
"x": 1068,
|
||||
"y": 288
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeId": "2b855d36-0c89-996d-9281-ea54f711cc8e",
|
||||
"position": {
|
||||
"x": 520,
|
||||
"y": 518
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeId": "0ddcc98c-7b7c-ac2e-2207-081c5ffc9921",
|
||||
"position": {
|
||||
"x": 533,
|
||||
"y": -37
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeId": "e53126c9-70fe-ca8a-3b85-bab123a93d70",
|
||||
"position": {
|
||||
"x": 816,
|
||||
"y": 280
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeId": "4ca3044f-9b82-020b-e1cb-bbb45e4b0088",
|
||||
"position": {
|
||||
"x": 630,
|
||||
"y": 205
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeId": "bc0420e3-9b6e-4c97-74c6-61278dbc18d4",
|
||||
"position": {
|
||||
"x": 507,
|
||||
"y": 407
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeId": "a97f8f48-6652-7e09-8eda-2d87eae7d20b",
|
||||
"position": {
|
||||
"x": 323,
|
||||
"y": 330
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeId": "889eedb3-8cb8-b906-8beb-904cd499c4f6",
|
||||
"position": {
|
||||
"x": 892,
|
||||
"y": -55
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeId": "bd77a77c-4627-ad77-1c5a-2c390ba21959",
|
||||
"position": {
|
||||
"x": 1114,
|
||||
"y": 570
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeId": "ffbd69bf-481f-faa8-73db-1489f3175b75",
|
||||
"position": {
|
||||
"x": 666,
|
||||
"y": 533
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeId": "5fdc6150-63bb-18e8-1f03-33bb8d6b7529",
|
||||
"position": {
|
||||
"x": 726,
|
||||
"y": 670
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeId": "b386ca2b-81a7-e365-4706-f955f13db03f",
|
||||
"position": {
|
||||
"x": 721,
|
||||
"y": -36
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": ["env"]
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 - present, iVis@Bilkent.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
cose-base
|
||||
================================================================================
|
||||
|
||||
## Description
|
||||
|
||||
This is a core module for compound spring embedder based layout styles such as CoSE-Bilkent, fCoSE, and CiSE.
|
||||
|
||||
## Dependencies
|
||||
|
||||
* layout-base ^2.0.0
|
||||
|
||||
## Usage instructions
|
||||
|
||||
Add `cose-base` as a dependecy to your layout extension.
|
||||
|
||||
`require()` in the extension to reach functionality:
|
||||
|
||||
* `var CoSEConstants = require('cose-base').CoSEConstants`,
|
||||
* `var CoSELayout = require('cose-base').CoSELayout`,
|
||||
* `...`
|
||||
|
||||
To reach functionality of `layout-base`:
|
||||
|
||||
* `var Integer = require('cose-base').layoutBase.Integer`,
|
||||
* `var Layout = require('cose-base').layoutBase.Layout`,
|
||||
* `...`
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "cose-base",
|
||||
"description": "Core module for compound spring embedder based layout styles",
|
||||
"main": "cose-base.js",
|
||||
"dependencies": {
|
||||
"layout-base": "^1.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/iVis-at-Bilkent/cose-base.git"
|
||||
},
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"keywords": [
|
||||
"layout"
|
||||
],
|
||||
"license": "MIT"
|
||||
}
|
||||
+3214
File diff suppressed because it is too large
Load Diff
+16
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
let coseBase = {};
|
||||
|
||||
coseBase.layoutBase = require('layout-base');
|
||||
coseBase.CoSEConstants = require('./src/CoSEConstants');
|
||||
coseBase.CoSEEdge = require('./src/CoSEEdge');
|
||||
coseBase.CoSEGraph = require('./src/CoSEGraph');
|
||||
coseBase.CoSEGraphManager = require('./src/CoSEGraphManager');
|
||||
coseBase.CoSELayout = require('./src/CoSELayout');
|
||||
coseBase.CoSENode = require('./src/CoSENode');
|
||||
coseBase.ConstraintHandler = require('./src/ConstraintHandler');
|
||||
|
||||
module.exports = coseBase;
|
||||
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "cose-base",
|
||||
"version": "2.2.0",
|
||||
"description": "Core module for compound spring embedder based layout styles",
|
||||
"main": "cose-base.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "cross-env NODE_ENV=production webpack",
|
||||
"build:min": "cross-env NODE_ENV=production MIN=true webpack"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/iVis-at-Bilkent/cose-base.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/iVis-at-Bilkent/cose-base/issues"
|
||||
},
|
||||
"homepage": "https://github.com/iVis-at-Bilkent/cose-base#readme",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.24.1",
|
||||
"babel-loader": "^7.0.0",
|
||||
"babel-preset-env": "^1.5.1",
|
||||
"camelcase": "^4.1.0",
|
||||
"cross-env": "^5.1.6",
|
||||
"eslint": "^3.19.0",
|
||||
"gh-pages": "^1.1.0",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"rimraf": "^2.6.2",
|
||||
"update": "^0.7.4",
|
||||
"updater-license": "^1.0.0",
|
||||
"webpack": "^5.36.1",
|
||||
"webpack-cli": "^4.6.0",
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"layout-base": "^2.0.0"
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
var FDLayoutConstants = require('layout-base').FDLayoutConstants;
|
||||
|
||||
function CoSEConstants() {
|
||||
}
|
||||
|
||||
//CoSEConstants inherits static props in FDLayoutConstants
|
||||
for (var prop in FDLayoutConstants) {
|
||||
CoSEConstants[prop] = FDLayoutConstants[prop];
|
||||
}
|
||||
|
||||
CoSEConstants.DEFAULT_USE_MULTI_LEVEL_SCALING = false;
|
||||
CoSEConstants.DEFAULT_RADIAL_SEPARATION = FDLayoutConstants.DEFAULT_EDGE_LENGTH;
|
||||
CoSEConstants.DEFAULT_COMPONENT_SEPERATION = 60;
|
||||
CoSEConstants.TILE = true;
|
||||
CoSEConstants.TILING_PADDING_VERTICAL = 10;
|
||||
CoSEConstants.TILING_PADDING_HORIZONTAL = 10;
|
||||
CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING = true;
|
||||
CoSEConstants.ENFORCE_CONSTRAINTS = true;
|
||||
CoSEConstants.APPLY_LAYOUT = true;
|
||||
CoSEConstants.RELAX_MOVEMENT_ON_CONSTRAINTS = true;
|
||||
CoSEConstants.TREE_REDUCTION_ON_INCREMENTAL = true; // this should be set to false if there will be a constraint
|
||||
// This constant is for differentiating whether actual layout algorithm that uses cose-base wants to apply only incremental layout or
|
||||
// an incremental layout on top of a randomized layout. If it is only incremental layout, then this constant should be true.
|
||||
CoSEConstants.PURE_INCREMENTAL = CoSEConstants.DEFAULT_INCREMENTAL;
|
||||
|
||||
module.exports = CoSEConstants;
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
var FDLayoutEdge = require('layout-base').FDLayoutEdge;
|
||||
|
||||
function CoSEEdge(source, target, vEdge) {
|
||||
FDLayoutEdge.call(this, source, target, vEdge);
|
||||
}
|
||||
|
||||
CoSEEdge.prototype = Object.create(FDLayoutEdge.prototype);
|
||||
for (var prop in FDLayoutEdge) {
|
||||
CoSEEdge[prop] = FDLayoutEdge[prop];
|
||||
}
|
||||
|
||||
module.exports = CoSEEdge
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
var LGraph = require('layout-base').LGraph;
|
||||
|
||||
function CoSEGraph(parent, graphMgr, vGraph) {
|
||||
LGraph.call(this, parent, graphMgr, vGraph);
|
||||
}
|
||||
|
||||
CoSEGraph.prototype = Object.create(LGraph.prototype);
|
||||
for (var prop in LGraph) {
|
||||
CoSEGraph[prop] = LGraph[prop];
|
||||
}
|
||||
|
||||
module.exports = CoSEGraph;
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
var LGraphManager = require('layout-base').LGraphManager;
|
||||
|
||||
function CoSEGraphManager(layout) {
|
||||
LGraphManager.call(this, layout);
|
||||
}
|
||||
|
||||
CoSEGraphManager.prototype = Object.create(LGraphManager.prototype);
|
||||
for (var prop in LGraphManager) {
|
||||
CoSEGraphManager[prop] = LGraphManager[prop];
|
||||
}
|
||||
|
||||
module.exports = CoSEGraphManager;
|
||||
+2037
File diff suppressed because it is too large
Load Diff
+127
@@ -0,0 +1,127 @@
|
||||
var FDLayoutNode = require('layout-base').FDLayoutNode;
|
||||
var IMath = require('layout-base').IMath;
|
||||
|
||||
function CoSENode(gm, loc, size, vNode) {
|
||||
FDLayoutNode.call(this, gm, loc, size, vNode);
|
||||
}
|
||||
|
||||
CoSENode.prototype = Object.create(FDLayoutNode.prototype);
|
||||
for (var prop in FDLayoutNode) {
|
||||
CoSENode[prop] = FDLayoutNode[prop];
|
||||
}
|
||||
|
||||
CoSENode.prototype.calculateDisplacement = function ()
|
||||
{
|
||||
var layout = this.graphManager.getLayout();
|
||||
// this check is for compound nodes that contain fixed nodes
|
||||
if (this.getChild() != null && this.fixedNodeWeight) {
|
||||
this.displacementX += layout.coolingFactor *
|
||||
(this.springForceX + this.repulsionForceX + this.gravitationForceX) / this.fixedNodeWeight;
|
||||
this.displacementY += layout.coolingFactor *
|
||||
(this.springForceY + this.repulsionForceY + this.gravitationForceY) / this.fixedNodeWeight;
|
||||
}
|
||||
else {
|
||||
this.displacementX += layout.coolingFactor *
|
||||
(this.springForceX + this.repulsionForceX + this.gravitationForceX) / this.noOfChildren;
|
||||
this.displacementY += layout.coolingFactor *
|
||||
(this.springForceY + this.repulsionForceY + this.gravitationForceY) / this.noOfChildren;
|
||||
}
|
||||
|
||||
if (Math.abs(this.displacementX) > layout.coolingFactor * layout.maxNodeDisplacement)
|
||||
{
|
||||
this.displacementX = layout.coolingFactor * layout.maxNodeDisplacement *
|
||||
IMath.sign(this.displacementX);
|
||||
}
|
||||
|
||||
if (Math.abs(this.displacementY) > layout.coolingFactor * layout.maxNodeDisplacement)
|
||||
{
|
||||
this.displacementY = layout.coolingFactor * layout.maxNodeDisplacement *
|
||||
IMath.sign(this.displacementY);
|
||||
}
|
||||
|
||||
// non-empty compound node, propogate movement to children as well
|
||||
if(this.child && this.child.getNodes().length > 0)
|
||||
{
|
||||
this.propogateDisplacementToChildren(this.displacementX,
|
||||
this.displacementY);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CoSENode.prototype.propogateDisplacementToChildren = function (dX, dY)
|
||||
{
|
||||
var nodes = this.getChild().getNodes();
|
||||
var node;
|
||||
for (var i = 0; i < nodes.length; i++)
|
||||
{
|
||||
node = nodes[i];
|
||||
if (node.getChild() == null)
|
||||
{
|
||||
node.displacementX += dX;
|
||||
node.displacementY += dY;
|
||||
}
|
||||
else
|
||||
{
|
||||
node.propogateDisplacementToChildren(dX, dY);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CoSENode.prototype.move = function ()
|
||||
{
|
||||
var layout = this.graphManager.getLayout();
|
||||
|
||||
// a simple node or an empty compound node, move it
|
||||
if (this.child == null || this.child.getNodes().length == 0)
|
||||
{
|
||||
this.moveBy(this.displacementX, this.displacementY);
|
||||
|
||||
layout.totalDisplacement += Math.abs(this.displacementX) + Math.abs(this.displacementY);
|
||||
}
|
||||
|
||||
this.springForceX = 0;
|
||||
this.springForceY = 0;
|
||||
this.repulsionForceX = 0;
|
||||
this.repulsionForceY = 0;
|
||||
this.gravitationForceX = 0;
|
||||
this.gravitationForceY = 0;
|
||||
this.displacementX = 0;
|
||||
this.displacementY = 0;
|
||||
};
|
||||
|
||||
CoSENode.prototype.setPred1 = function (pred1)
|
||||
{
|
||||
this.pred1 = pred1;
|
||||
};
|
||||
|
||||
CoSENode.prototype.getPred1 = function ()
|
||||
{
|
||||
return pred1;
|
||||
};
|
||||
|
||||
CoSENode.prototype.getPred2 = function ()
|
||||
{
|
||||
return pred2;
|
||||
};
|
||||
|
||||
CoSENode.prototype.setNext = function (next)
|
||||
{
|
||||
this.next = next;
|
||||
};
|
||||
|
||||
CoSENode.prototype.getNext = function ()
|
||||
{
|
||||
return next;
|
||||
};
|
||||
|
||||
CoSENode.prototype.setProcessed = function (processed)
|
||||
{
|
||||
this.processed = processed;
|
||||
};
|
||||
|
||||
CoSENode.prototype.isProcessed = function ()
|
||||
{
|
||||
return processed;
|
||||
};
|
||||
|
||||
module.exports = CoSENode;
|
||||
Generated
Vendored
+848
@@ -0,0 +1,848 @@
|
||||
var CoSEConstants = require('./CoSEConstants');
|
||||
var LinkedList = require('layout-base').LinkedList;
|
||||
var Matrix = require('layout-base').Matrix;
|
||||
var SVD = require('layout-base').SVD;
|
||||
|
||||
function ConstraintHandler() {
|
||||
}
|
||||
|
||||
ConstraintHandler.handleConstraints = function (layout)
|
||||
{
|
||||
// let layout = this.graphManager.getLayout();
|
||||
|
||||
// get constraints from layout
|
||||
let constraints = {};
|
||||
constraints.fixedNodeConstraint = layout.constraints.fixedNodeConstraint;
|
||||
constraints.alignmentConstraint = layout.constraints.alignmentConstraint;
|
||||
constraints.relativePlacementConstraint = layout.constraints.relativePlacementConstraint;
|
||||
|
||||
let idToNodeMap = new Map();
|
||||
let nodeIndexes = new Map();
|
||||
let xCoords = [];
|
||||
let yCoords = [];
|
||||
|
||||
let allNodes = layout.getAllNodes();
|
||||
let index = 0;
|
||||
// fill index map and coordinates
|
||||
for (let i = 0; i < allNodes.length; i++) {
|
||||
let node = allNodes[i];
|
||||
if (node.getChild() == null) {
|
||||
nodeIndexes.set(node.id, index++);
|
||||
xCoords.push(node.getCenterX());
|
||||
yCoords.push(node.getCenterY());
|
||||
idToNodeMap.set(node.id, node);
|
||||
}
|
||||
}
|
||||
|
||||
// if there exists relative placement constraint without gap value, set it to default
|
||||
if (constraints.relativePlacementConstraint) {
|
||||
constraints.relativePlacementConstraint.forEach(function(constraint) {
|
||||
if (!constraint.gap && constraint.gap != 0) {
|
||||
if (constraint.left) {
|
||||
constraint.gap = CoSEConstants.DEFAULT_EDGE_LENGTH + idToNodeMap.get(constraint.left).getWidth()/2 + idToNodeMap.get(constraint.right).getWidth()/2;
|
||||
}
|
||||
else {
|
||||
constraint.gap = CoSEConstants.DEFAULT_EDGE_LENGTH + idToNodeMap.get(constraint.top).getHeight()/2 + idToNodeMap.get(constraint.bottom).getHeight()/2;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* auxiliary functions */
|
||||
|
||||
// calculate difference between two position objects
|
||||
let calculatePositionDiff = function(pos1, pos2) {
|
||||
return {x: pos1.x - pos2.x, y: pos1.y - pos2.y};
|
||||
};
|
||||
|
||||
// calculate average position of the nodes
|
||||
let calculateAvgPosition = function(nodeIdSet) {
|
||||
let xPosSum = 0;
|
||||
let yPosSum = 0;
|
||||
nodeIdSet.forEach(function(nodeId) {
|
||||
xPosSum += xCoords[nodeIndexes.get(nodeId)];
|
||||
yPosSum += yCoords[nodeIndexes.get(nodeId)];
|
||||
});
|
||||
|
||||
return {x: xPosSum / nodeIdSet.size, y: yPosSum / nodeIdSet.size};
|
||||
};
|
||||
|
||||
// find an appropriate positioning for the nodes in a given graph according to relative placement constraints
|
||||
// this function also takes the fixed nodes and alignment constraints into account
|
||||
// graph: dag to be evaluated, direction: "horizontal" or "vertical",
|
||||
// fixedNodes: set of fixed nodes to consider during evaluation, dummyPositions: appropriate coordinates of the dummy nodes
|
||||
let findAppropriatePositionForRelativePlacement = function(graph, direction, fixedNodes, dummyPositions, componentSources) {
|
||||
|
||||
// find union of two sets
|
||||
function setUnion(setA, setB) {
|
||||
let union = new Set(setA);
|
||||
for (let elem of setB) {
|
||||
union.add(elem);
|
||||
}
|
||||
return union;
|
||||
}
|
||||
|
||||
// find indegree count for each node
|
||||
let inDegrees = new Map();
|
||||
|
||||
graph.forEach(function(value, key) {
|
||||
inDegrees.set(key, 0);
|
||||
});
|
||||
graph.forEach(function(value, key) {
|
||||
value.forEach(function(adjacent) {
|
||||
inDegrees.set(adjacent.id, inDegrees.get(adjacent.id) + 1);
|
||||
});
|
||||
});
|
||||
|
||||
let positionMap = new Map(); // keeps the position for each node
|
||||
let pastMap = new Map(); // keeps the predecessors(past) of a node
|
||||
let queue = new LinkedList();
|
||||
inDegrees.forEach(function(value, key) {
|
||||
if (value == 0) {
|
||||
queue.push(key);
|
||||
if (!fixedNodes) {
|
||||
if (direction == "horizontal") {
|
||||
positionMap.set(key, nodeIndexes.has(key) ? xCoords[nodeIndexes.get(key)] : dummyPositions.get(key));
|
||||
}
|
||||
else {
|
||||
positionMap.set(key, nodeIndexes.has(key) ? yCoords[nodeIndexes.get(key)] : dummyPositions.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
positionMap.set(key, Number.NEGATIVE_INFINITY);
|
||||
}
|
||||
if (fixedNodes) {
|
||||
pastMap.set(key, new Set([key]));
|
||||
}
|
||||
});
|
||||
|
||||
// align sources of each component in enforcement phase
|
||||
if (fixedNodes) {
|
||||
componentSources.forEach(function(component) {
|
||||
let fixedIds = [];
|
||||
component.forEach(function(nodeId) {
|
||||
if (fixedNodes.has(nodeId)) {
|
||||
fixedIds.push(nodeId);
|
||||
}
|
||||
});
|
||||
if (fixedIds.length > 0) {
|
||||
let position = 0;
|
||||
fixedIds.forEach(function(fixedId) {
|
||||
if (direction == "horizontal") {
|
||||
positionMap.set(fixedId, nodeIndexes.has(fixedId) ? xCoords[nodeIndexes.get(fixedId)] : dummyPositions.get(fixedId));
|
||||
position += positionMap.get(fixedId);
|
||||
}
|
||||
else {
|
||||
positionMap.set(fixedId, nodeIndexes.has(fixedId) ? yCoords[nodeIndexes.get(fixedId)] : dummyPositions.get(fixedId));
|
||||
position += positionMap.get(fixedId);
|
||||
}
|
||||
});
|
||||
position = position / fixedIds.length;
|
||||
component.forEach(function(nodeId) {
|
||||
if (!fixedNodes.has(nodeId)) {
|
||||
positionMap.set(nodeId, position);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
let position = 0;
|
||||
component.forEach(function(nodeId) {
|
||||
if (direction == "horizontal") {
|
||||
position += nodeIndexes.has(nodeId) ? xCoords[nodeIndexes.get(nodeId)] : dummyPositions.get(nodeId);
|
||||
}
|
||||
else {
|
||||
position += nodeIndexes.has(nodeId) ? yCoords[nodeIndexes.get(nodeId)] : dummyPositions.get(nodeId);
|
||||
}
|
||||
});
|
||||
position = position / component.length;
|
||||
component.forEach(function(nodeId) {
|
||||
positionMap.set(nodeId, position);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// calculate positions of the nodes
|
||||
while (queue.length != 0) {
|
||||
let currentNode = queue.shift();
|
||||
let neighbors = graph.get(currentNode);
|
||||
neighbors.forEach(function(neighbor) {
|
||||
if (positionMap.get(neighbor.id) < (positionMap.get(currentNode) + neighbor.gap)) {
|
||||
if (fixedNodes && fixedNodes.has(neighbor.id)) {
|
||||
let fixedPosition;
|
||||
if (direction == "horizontal") {
|
||||
fixedPosition = nodeIndexes.has(neighbor.id) ? xCoords[nodeIndexes.get(neighbor.id)] : dummyPositions.get(neighbor.id);
|
||||
}
|
||||
else {
|
||||
fixedPosition = nodeIndexes.has(neighbor.id) ? yCoords[nodeIndexes.get(neighbor.id)] : dummyPositions.get(neighbor.id);
|
||||
}
|
||||
positionMap.set(neighbor.id, fixedPosition); // TODO: may do unnecessary work
|
||||
if (fixedPosition < (positionMap.get(currentNode) + neighbor.gap)) {
|
||||
let diff = (positionMap.get(currentNode) + neighbor.gap) - fixedPosition;
|
||||
pastMap.get(currentNode).forEach(function(nodeId) {
|
||||
positionMap.set(nodeId, positionMap.get(nodeId) - diff);
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
positionMap.set(neighbor.id, positionMap.get(currentNode) + neighbor.gap);
|
||||
}
|
||||
}
|
||||
inDegrees.set(neighbor.id, inDegrees.get(neighbor.id) - 1);
|
||||
if (inDegrees.get(neighbor.id) == 0) {
|
||||
queue.push(neighbor.id);
|
||||
}
|
||||
if (fixedNodes) {
|
||||
pastMap.set(neighbor.id, setUnion(pastMap.get(currentNode), pastMap.get(neighbor.id)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// readjust position of the nodes after enforcement
|
||||
if (fixedNodes) {
|
||||
// find indegree count for each node
|
||||
let sinkNodes = new Set();
|
||||
|
||||
graph.forEach(function(value, key) {
|
||||
if (value.length == 0) {
|
||||
sinkNodes.add(key);
|
||||
}
|
||||
});
|
||||
|
||||
let components = [];
|
||||
pastMap.forEach(function(value, key) {
|
||||
if (sinkNodes.has(key)) {
|
||||
let isFixedComponent = false;
|
||||
for (let nodeId of value) {
|
||||
if (fixedNodes.has(nodeId)) {
|
||||
isFixedComponent = true;
|
||||
}
|
||||
}
|
||||
if (!isFixedComponent) {
|
||||
let isExist = false;
|
||||
let existAt;
|
||||
components.forEach(function(component, index) {
|
||||
if (component.has([...value][0])) {
|
||||
isExist = true;
|
||||
existAt = index;
|
||||
}
|
||||
});
|
||||
if (!isExist) {
|
||||
components.push(new Set(value));
|
||||
}
|
||||
else {
|
||||
value.forEach(function(ele) {
|
||||
components[existAt].add(ele);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
components.forEach(function(component, index) {
|
||||
let minBefore = Number.POSITIVE_INFINITY;
|
||||
let minAfter = Number.POSITIVE_INFINITY;
|
||||
let maxBefore = Number.NEGATIVE_INFINITY;
|
||||
let maxAfter = Number.NEGATIVE_INFINITY;
|
||||
|
||||
for (let nodeId of component) {
|
||||
let posBefore;
|
||||
if (direction == "horizontal") {
|
||||
posBefore = nodeIndexes.has(nodeId) ? xCoords[nodeIndexes.get(nodeId)] : dummyPositions.get(nodeId);
|
||||
}
|
||||
else {
|
||||
posBefore = nodeIndexes.has(nodeId) ? yCoords[nodeIndexes.get(nodeId)] : dummyPositions.get(nodeId);
|
||||
}
|
||||
let posAfter = positionMap.get(nodeId);
|
||||
if (posBefore < minBefore) {
|
||||
minBefore = posBefore;
|
||||
}
|
||||
if (posBefore > maxBefore) {
|
||||
maxBefore = posBefore;
|
||||
}
|
||||
if (posAfter < minAfter) {
|
||||
minAfter = posAfter;
|
||||
}
|
||||
if (posAfter > maxAfter) {
|
||||
maxAfter = posAfter;
|
||||
}
|
||||
}
|
||||
let diff = (minBefore + maxBefore) / 2 - (minAfter + maxAfter) / 2;
|
||||
|
||||
for (let nodeId of component) {
|
||||
positionMap.set(nodeId, positionMap.get(nodeId) + diff);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return positionMap;
|
||||
};
|
||||
|
||||
// find transformation based on rel. placement constraints if there are both alignment and rel. placement constraints
|
||||
// or if there are only rel. placement contraints where the largest component isn't sufficiently large
|
||||
let applyReflectionForRelativePlacement = function (relativePlacementConstraints) {
|
||||
// variables to count votes
|
||||
let reflectOnY = 0, notReflectOnY = 0;
|
||||
let reflectOnX = 0, notReflectOnX = 0;
|
||||
|
||||
relativePlacementConstraints.forEach(function(constraint) {
|
||||
if (constraint.left) {
|
||||
(xCoords[nodeIndexes.get(constraint.left)] - xCoords[nodeIndexes.get(constraint.right)] >= 0) ? reflectOnY++ : notReflectOnY++;
|
||||
}
|
||||
else {
|
||||
(yCoords[nodeIndexes.get(constraint.top)] - yCoords[nodeIndexes.get(constraint.bottom)] >= 0) ? reflectOnX++ : notReflectOnX++;
|
||||
}
|
||||
});
|
||||
|
||||
if (reflectOnY > notReflectOnY && reflectOnX > notReflectOnX) {
|
||||
for (let i = 0; i < nodeIndexes.size; i++) {
|
||||
xCoords[i] = -1 * xCoords[i];
|
||||
yCoords[i] = -1 * yCoords[i];
|
||||
}
|
||||
}
|
||||
else if (reflectOnY > notReflectOnY) {
|
||||
for (let i = 0; i < nodeIndexes.size; i++) {
|
||||
xCoords[i] = -1 * xCoords[i];
|
||||
}
|
||||
}
|
||||
else if (reflectOnX > notReflectOnX) {
|
||||
for (let i = 0; i < nodeIndexes.size; i++) {
|
||||
yCoords[i] = -1 * yCoords[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// find weakly connected components in undirected graph
|
||||
let findComponents = function(graph) {
|
||||
// find weakly connected components in dag
|
||||
let components = [];
|
||||
let queue = new LinkedList();
|
||||
let visited = new Set();
|
||||
let count = 0;
|
||||
|
||||
graph.forEach(function(value, key) {
|
||||
if (!visited.has(key)) {
|
||||
components[count] = [];
|
||||
let currentNode = key;
|
||||
queue.push(currentNode);
|
||||
visited.add(currentNode);
|
||||
components[count].push(currentNode);
|
||||
|
||||
while (queue.length != 0) {
|
||||
currentNode = queue.shift();
|
||||
let neighbors = graph.get(currentNode);
|
||||
neighbors.forEach(function(neighbor) {
|
||||
if (!visited.has(neighbor.id)) {
|
||||
queue.push(neighbor.id);
|
||||
visited.add(neighbor.id);
|
||||
components[count].push(neighbor.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
count++;
|
||||
}
|
||||
});
|
||||
return components;
|
||||
};
|
||||
|
||||
// return undirected version of given dag
|
||||
let dagToUndirected = function(dag) {
|
||||
let undirected = new Map();
|
||||
|
||||
dag.forEach(function(value, key) {
|
||||
undirected.set(key, []);
|
||||
});
|
||||
|
||||
dag.forEach(function(value, key) {
|
||||
value.forEach(function(adjacent) {
|
||||
undirected.get(key).push(adjacent);
|
||||
undirected.get(adjacent.id).push({id: key, gap: adjacent.gap, direction: adjacent.direction});
|
||||
});
|
||||
});
|
||||
|
||||
return undirected;
|
||||
};
|
||||
|
||||
// return reversed (directions inverted) version of given dag
|
||||
let dagToReversed = function(dag) {
|
||||
let reversed = new Map();
|
||||
|
||||
dag.forEach(function(value, key) {
|
||||
reversed.set(key, []);
|
||||
});
|
||||
|
||||
dag.forEach(function(value, key) {
|
||||
value.forEach(function(adjacent) {
|
||||
reversed.get(adjacent.id).push({id: key, gap: adjacent.gap, direction: adjacent.direction});
|
||||
});
|
||||
});
|
||||
|
||||
return reversed;
|
||||
};
|
||||
|
||||
/**** apply transformation to the initial draft layout to better align with constrained nodes ****/
|
||||
// solve the Orthogonal Procrustean Problem to rotate and/or reflect initial draft layout
|
||||
// here we follow the solution in Chapter 20.2 of Borg, I. & Groenen, P. (2005) Modern Multidimensional Scaling: Theory and Applications
|
||||
|
||||
/* construct source and target configurations */
|
||||
|
||||
let targetMatrix = []; // A - target configuration
|
||||
let sourceMatrix = []; // B - source configuration
|
||||
let standardTransformation = false; // false for no transformation, true for standart (Procrustes) transformation (rotation and/or reflection)
|
||||
let reflectionType = false; // false/true for reflection check, 'reflectOnX', 'reflectOnY' or 'reflectOnBoth' for reflection type if necessary
|
||||
let fixedNodes = new Set();
|
||||
let dag = new Map(); // adjacency list to keep directed acyclic graph (dag) that consists of relative placement constraints
|
||||
let dagUndirected = new Map(); // undirected version of the dag
|
||||
let components = []; // weakly connected components
|
||||
|
||||
// fill fixedNodes collection to use later
|
||||
if (constraints.fixedNodeConstraint) {
|
||||
constraints.fixedNodeConstraint.forEach(function(nodeData) {
|
||||
fixedNodes.add(nodeData.nodeId);
|
||||
});
|
||||
}
|
||||
|
||||
// construct dag from relative placement constraints
|
||||
if (constraints.relativePlacementConstraint) {
|
||||
// construct both directed and undirected version of the dag
|
||||
constraints.relativePlacementConstraint.forEach(function(constraint) {
|
||||
if (constraint.left) {
|
||||
if (dag.has(constraint.left)) {
|
||||
dag.get(constraint.left).push({id: constraint.right, gap: constraint.gap, direction: "horizontal"});
|
||||
}
|
||||
else {
|
||||
dag.set(constraint.left, [{id: constraint.right, gap: constraint.gap, direction: "horizontal"}]);
|
||||
}
|
||||
if (!dag.has(constraint.right)) {
|
||||
dag.set(constraint.right, []);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (dag.has(constraint.top)) {
|
||||
dag.get(constraint.top).push({id: constraint.bottom, gap: constraint.gap, direction: "vertical"});
|
||||
}
|
||||
else {
|
||||
dag.set(constraint.top, [{id: constraint.bottom, gap: constraint.gap, direction: "vertical"}]);
|
||||
}
|
||||
if (!dag.has(constraint.bottom)) {
|
||||
dag.set(constraint.bottom, []);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
dagUndirected = dagToUndirected(dag);
|
||||
components = findComponents(dagUndirected);
|
||||
}
|
||||
|
||||
if (CoSEConstants.TRANSFORM_ON_CONSTRAINT_HANDLING) {
|
||||
// first check fixed node constraint
|
||||
if (constraints.fixedNodeConstraint && constraints.fixedNodeConstraint.length > 1) {
|
||||
constraints.fixedNodeConstraint.forEach(function(nodeData, i) {
|
||||
targetMatrix[i] = [nodeData.position.x, nodeData.position.y];
|
||||
sourceMatrix[i] = [xCoords[nodeIndexes.get(nodeData.nodeId)], yCoords[nodeIndexes.get(nodeData.nodeId)]];
|
||||
});
|
||||
standardTransformation = true;
|
||||
}
|
||||
else if (constraints.alignmentConstraint) { // then check alignment constraint
|
||||
let count = 0;
|
||||
if (constraints.alignmentConstraint.vertical) {
|
||||
let verticalAlign = constraints.alignmentConstraint.vertical;
|
||||
for (let i = 0; i < verticalAlign.length; i++) {
|
||||
let alignmentSet = new Set();
|
||||
verticalAlign[i].forEach(function(nodeId) {
|
||||
alignmentSet.add(nodeId);
|
||||
});
|
||||
let intersection = new Set([...alignmentSet].filter(x => fixedNodes.has(x)));
|
||||
let xPos;
|
||||
if (intersection.size > 0)
|
||||
xPos = xCoords[nodeIndexes.get(intersection.values().next().value)];
|
||||
else
|
||||
xPos = calculateAvgPosition(alignmentSet).x;
|
||||
|
||||
verticalAlign[i].forEach(function(nodeId) {
|
||||
targetMatrix[count] = [xPos, yCoords[nodeIndexes.get(nodeId)]];
|
||||
sourceMatrix[count] = [xCoords[nodeIndexes.get(nodeId)], yCoords[nodeIndexes.get(nodeId)]];
|
||||
count++;
|
||||
});
|
||||
}
|
||||
standardTransformation = true;
|
||||
}
|
||||
if (constraints.alignmentConstraint.horizontal) {
|
||||
let horizontalAlign = constraints.alignmentConstraint.horizontal;
|
||||
for (let i = 0; i < horizontalAlign.length; i++) {
|
||||
let alignmentSet = new Set();
|
||||
horizontalAlign[i].forEach(function(nodeId) {
|
||||
alignmentSet.add(nodeId);
|
||||
});
|
||||
let intersection = new Set([...alignmentSet].filter(x => fixedNodes.has(x)));
|
||||
let yPos;
|
||||
if (intersection.size > 0)
|
||||
yPos = xCoords[nodeIndexes.get(intersection.values().next().value)];
|
||||
else
|
||||
yPos = calculateAvgPosition(alignmentSet).y;
|
||||
|
||||
horizontalAlign[i].forEach(function(nodeId) {
|
||||
targetMatrix[count] = [xCoords[nodeIndexes.get(nodeId)], yPos];
|
||||
sourceMatrix[count] = [xCoords[nodeIndexes.get(nodeId)], yCoords[nodeIndexes.get(nodeId)]];
|
||||
count++;
|
||||
});
|
||||
}
|
||||
standardTransformation = true;
|
||||
}
|
||||
if (constraints.relativePlacementConstraint) {
|
||||
reflectionType = true;
|
||||
}
|
||||
}
|
||||
else if (constraints.relativePlacementConstraint) { // finally check relative placement constraint
|
||||
// find largest component in dag
|
||||
let largestComponentSize = 0;
|
||||
let largestComponentIndex = 0;
|
||||
for (let i = 0; i < components.length; i++) {
|
||||
if (components[i].length > largestComponentSize) {
|
||||
largestComponentSize = components[i].length;
|
||||
largestComponentIndex = i;
|
||||
}
|
||||
}
|
||||
// if largest component isn't dominant, then take the votes for reflection
|
||||
if (largestComponentSize < (dagUndirected.size / 2)) {
|
||||
applyReflectionForRelativePlacement(constraints.relativePlacementConstraint);
|
||||
standardTransformation = false;
|
||||
reflectionType = false;
|
||||
}
|
||||
else { // use largest component for transformation
|
||||
// construct horizontal and vertical subgraphs in the largest component
|
||||
let subGraphOnHorizontal = new Map();
|
||||
let subGraphOnVertical = new Map();
|
||||
let constraintsInlargestComponent = [];
|
||||
|
||||
components[largestComponentIndex].forEach(function(nodeId) {
|
||||
dag.get(nodeId).forEach(function(adjacent) {
|
||||
if (adjacent.direction == "horizontal") {
|
||||
if (subGraphOnHorizontal.has(nodeId)) {
|
||||
subGraphOnHorizontal.get(nodeId).push(adjacent);
|
||||
}
|
||||
else {
|
||||
subGraphOnHorizontal.set(nodeId, [adjacent]);
|
||||
}
|
||||
if (!subGraphOnHorizontal.has(adjacent.id)) {
|
||||
subGraphOnHorizontal.set(adjacent.id, []);
|
||||
}
|
||||
constraintsInlargestComponent.push({left: nodeId, right: adjacent.id});
|
||||
}
|
||||
else {
|
||||
if (subGraphOnVertical.has(nodeId)) {
|
||||
subGraphOnVertical.get(nodeId).push(adjacent);
|
||||
}
|
||||
else {
|
||||
subGraphOnVertical.set(nodeId, [adjacent]);
|
||||
}
|
||||
if (!subGraphOnVertical.has(adjacent.id)) {
|
||||
subGraphOnVertical.set(adjacent.id, []);
|
||||
}
|
||||
constraintsInlargestComponent.push({top: nodeId, bottom: adjacent.id});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
applyReflectionForRelativePlacement(constraintsInlargestComponent);
|
||||
reflectionType = false;
|
||||
|
||||
// calculate appropriate positioning for subgraphs
|
||||
let positionMapHorizontal = findAppropriatePositionForRelativePlacement(subGraphOnHorizontal, "horizontal");
|
||||
let positionMapVertical = findAppropriatePositionForRelativePlacement(subGraphOnVertical, "vertical");
|
||||
|
||||
// construct source and target configuration
|
||||
components[largestComponentIndex].forEach(function(nodeId, i) {
|
||||
sourceMatrix[i] = [xCoords[nodeIndexes.get(nodeId)], yCoords[nodeIndexes.get(nodeId)]];
|
||||
targetMatrix[i] = [];
|
||||
if (positionMapHorizontal.has(nodeId)) {
|
||||
targetMatrix[i][0] = positionMapHorizontal.get(nodeId);
|
||||
}
|
||||
else {
|
||||
targetMatrix[i][0] = xCoords[nodeIndexes.get(nodeId)];
|
||||
}
|
||||
if (positionMapVertical.has(nodeId)) {
|
||||
targetMatrix[i][1] = positionMapVertical.get(nodeId);
|
||||
}
|
||||
else {
|
||||
targetMatrix[i][1] = yCoords[nodeIndexes.get(nodeId)];
|
||||
}
|
||||
});
|
||||
|
||||
standardTransformation = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if transformation is required, then calculate and apply transformation matrix
|
||||
if (standardTransformation) {
|
||||
/* calculate transformation matrix */
|
||||
let transformationMatrix;
|
||||
let targetMatrixTranspose = Matrix.transpose(targetMatrix); // A'
|
||||
let sourceMatrixTranspose = Matrix.transpose(sourceMatrix); // B'
|
||||
|
||||
// centralize transpose matrices
|
||||
for (let i = 0; i < targetMatrixTranspose.length; i++) {
|
||||
targetMatrixTranspose[i] = Matrix.multGamma(targetMatrixTranspose[i]);
|
||||
sourceMatrixTranspose[i] = Matrix.multGamma(sourceMatrixTranspose[i]);
|
||||
}
|
||||
|
||||
// do actual calculation for transformation matrix
|
||||
let tempMatrix = Matrix.multMat(targetMatrixTranspose, Matrix.transpose(sourceMatrixTranspose)); // tempMatrix = A'B
|
||||
let SVDResult = SVD.svd(tempMatrix); // SVD(A'B) = USV', svd function returns U, S and V
|
||||
transformationMatrix = Matrix.multMat(SVDResult.V, Matrix.transpose(SVDResult.U)); // transformationMatrix = T = VU'
|
||||
|
||||
/* apply found transformation matrix to obtain final draft layout */
|
||||
for (let i = 0; i < nodeIndexes.size; i++) {
|
||||
let temp1 = [xCoords[i], yCoords[i]];
|
||||
let temp2 = [transformationMatrix[0][0], transformationMatrix[1][0]];
|
||||
let temp3 = [transformationMatrix[0][1], transformationMatrix[1][1]];
|
||||
xCoords[i] = Matrix.dotProduct(temp1, temp2);
|
||||
yCoords[i] = Matrix.dotProduct(temp1, temp3);
|
||||
}
|
||||
|
||||
// applied only both alignment and rel. placement constraints exist
|
||||
if (reflectionType) {
|
||||
applyReflectionForRelativePlacement(constraints.relativePlacementConstraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CoSEConstants.ENFORCE_CONSTRAINTS) {
|
||||
/**** enforce constraints on the transformed draft layout ****/
|
||||
|
||||
/* first enforce fixed node constraint */
|
||||
|
||||
if (constraints.fixedNodeConstraint && constraints.fixedNodeConstraint.length > 0) {
|
||||
let translationAmount = { x: 0, y: 0 };
|
||||
constraints.fixedNodeConstraint.forEach(function(nodeData, i) {
|
||||
let posInTheory = {x: xCoords[nodeIndexes.get(nodeData.nodeId)], y: yCoords[nodeIndexes.get(nodeData.nodeId)]};
|
||||
let posDesired = nodeData.position;
|
||||
let posDiff = calculatePositionDiff(posDesired, posInTheory);
|
||||
translationAmount.x += posDiff.x;
|
||||
translationAmount.y += posDiff.y;
|
||||
});
|
||||
translationAmount.x /= constraints.fixedNodeConstraint.length;
|
||||
translationAmount.y /= constraints.fixedNodeConstraint.length;
|
||||
|
||||
xCoords.forEach(function(value, i) {
|
||||
xCoords[i] += translationAmount.x;
|
||||
});
|
||||
|
||||
yCoords.forEach(function(value, i) {
|
||||
yCoords[i] += translationAmount.y;
|
||||
});
|
||||
|
||||
constraints.fixedNodeConstraint.forEach(function(nodeData) {
|
||||
xCoords[nodeIndexes.get(nodeData.nodeId)] = nodeData.position.x;
|
||||
yCoords[nodeIndexes.get(nodeData.nodeId)] = nodeData.position.y;
|
||||
});
|
||||
}
|
||||
|
||||
/* then enforce alignment constraint */
|
||||
|
||||
if (constraints.alignmentConstraint) {
|
||||
if (constraints.alignmentConstraint.vertical) {
|
||||
let xAlign = constraints.alignmentConstraint.vertical;
|
||||
for (let i = 0; i < xAlign.length; i++) {
|
||||
let alignmentSet = new Set();
|
||||
xAlign[i].forEach(function(nodeId) {
|
||||
alignmentSet.add(nodeId);
|
||||
});
|
||||
let intersection = new Set([...alignmentSet].filter(x => fixedNodes.has(x)));
|
||||
let xPos;
|
||||
if (intersection.size > 0)
|
||||
xPos = xCoords[nodeIndexes.get(intersection.values().next().value)];
|
||||
else
|
||||
xPos = calculateAvgPosition(alignmentSet).x;
|
||||
|
||||
alignmentSet.forEach(function(nodeId) {
|
||||
if (!fixedNodes.has(nodeId))
|
||||
xCoords[nodeIndexes.get(nodeId)] = xPos;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (constraints.alignmentConstraint.horizontal) {
|
||||
let yAlign = constraints.alignmentConstraint.horizontal;
|
||||
for (let i = 0; i < yAlign.length; i++) {
|
||||
let alignmentSet = new Set();
|
||||
yAlign[i].forEach(function(nodeId) {
|
||||
alignmentSet.add(nodeId);
|
||||
});
|
||||
let intersection = new Set([...alignmentSet].filter(x => fixedNodes.has(x)));
|
||||
let yPos;
|
||||
if (intersection.size > 0)
|
||||
yPos = yCoords[nodeIndexes.get(intersection.values().next().value)];
|
||||
else
|
||||
yPos = calculateAvgPosition(alignmentSet).y;
|
||||
|
||||
alignmentSet.forEach(function(nodeId) {
|
||||
if (!fixedNodes.has(nodeId))
|
||||
yCoords[nodeIndexes.get(nodeId)] = yPos;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* finally enforce relative placement constraint */
|
||||
|
||||
if (constraints.relativePlacementConstraint) {
|
||||
let nodeToDummyForVerticalAlignment = new Map();
|
||||
let nodeToDummyForHorizontalAlignment = new Map();
|
||||
let dummyToNodeForVerticalAlignment = new Map();
|
||||
let dummyToNodeForHorizontalAlignment = new Map();
|
||||
let dummyPositionsForVerticalAlignment = new Map();
|
||||
let dummyPositionsForHorizontalAlignment = new Map();
|
||||
let fixedNodesOnHorizontal = new Set();
|
||||
let fixedNodesOnVertical = new Set();
|
||||
|
||||
// fill maps and sets
|
||||
fixedNodes.forEach(function(nodeId) {
|
||||
fixedNodesOnHorizontal.add(nodeId);
|
||||
fixedNodesOnVertical.add(nodeId);
|
||||
});
|
||||
|
||||
if (constraints.alignmentConstraint) {
|
||||
if (constraints.alignmentConstraint.vertical) {
|
||||
let verticalAlignment = constraints.alignmentConstraint.vertical;
|
||||
for (let i = 0; i < verticalAlignment.length; i++) {
|
||||
dummyToNodeForVerticalAlignment.set("dummy" + i, []);
|
||||
verticalAlignment[i].forEach(function(nodeId) {
|
||||
nodeToDummyForVerticalAlignment.set(nodeId, "dummy" + i);
|
||||
dummyToNodeForVerticalAlignment.get("dummy" + i).push(nodeId);
|
||||
if (fixedNodes.has(nodeId)) {
|
||||
fixedNodesOnHorizontal.add("dummy" + i);
|
||||
}
|
||||
});
|
||||
dummyPositionsForVerticalAlignment.set("dummy" + i, xCoords[nodeIndexes.get(verticalAlignment[i][0])]);
|
||||
}
|
||||
}
|
||||
if (constraints.alignmentConstraint.horizontal) {
|
||||
let horizontalAlignment = constraints.alignmentConstraint.horizontal;
|
||||
for (let i = 0; i < horizontalAlignment.length; i++) {
|
||||
dummyToNodeForHorizontalAlignment.set("dummy" + i, []);
|
||||
horizontalAlignment[i].forEach(function(nodeId) {
|
||||
nodeToDummyForHorizontalAlignment.set(nodeId, "dummy" + i);
|
||||
dummyToNodeForHorizontalAlignment.get("dummy" + i).push(nodeId);
|
||||
if (fixedNodes.has(nodeId)) {
|
||||
fixedNodesOnVertical.add("dummy" + i);
|
||||
}
|
||||
});
|
||||
dummyPositionsForHorizontalAlignment.set("dummy" + i, yCoords[nodeIndexes.get(horizontalAlignment[i][0])]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// construct horizontal and vertical dags (subgraphs) from overall dag
|
||||
let dagOnHorizontal = new Map();
|
||||
let dagOnVertical = new Map();
|
||||
|
||||
for (let nodeId of dag.keys()) {
|
||||
dag.get(nodeId).forEach(function(adjacent) {
|
||||
let sourceId;
|
||||
let targetNode;
|
||||
if (adjacent["direction"] == "horizontal") {
|
||||
sourceId = nodeToDummyForVerticalAlignment.get(nodeId) ? nodeToDummyForVerticalAlignment.get(nodeId) : nodeId;
|
||||
if (nodeToDummyForVerticalAlignment.get(adjacent.id)) {
|
||||
targetNode = {id: nodeToDummyForVerticalAlignment.get(adjacent.id), gap: adjacent.gap, direction: adjacent.direction};
|
||||
}
|
||||
else {
|
||||
targetNode = adjacent;
|
||||
}
|
||||
if (dagOnHorizontal.has(sourceId)) {
|
||||
dagOnHorizontal.get(sourceId).push(targetNode);
|
||||
}
|
||||
else {
|
||||
dagOnHorizontal.set(sourceId, [targetNode]);
|
||||
}
|
||||
if (!dagOnHorizontal.has(targetNode.id)) {
|
||||
dagOnHorizontal.set(targetNode.id, []);
|
||||
}
|
||||
}
|
||||
else {
|
||||
sourceId = nodeToDummyForHorizontalAlignment.get(nodeId) ? nodeToDummyForHorizontalAlignment.get(nodeId) : nodeId;
|
||||
if (nodeToDummyForHorizontalAlignment.get(adjacent.id)) {
|
||||
targetNode = {id: nodeToDummyForHorizontalAlignment.get(adjacent.id), gap: adjacent.gap, direction: adjacent.direction};
|
||||
}
|
||||
else {
|
||||
targetNode = adjacent;
|
||||
}
|
||||
if (dagOnVertical.has(sourceId)) {
|
||||
dagOnVertical.get(sourceId).push(targetNode);
|
||||
}
|
||||
else {
|
||||
dagOnVertical.set(sourceId, [targetNode]);
|
||||
}
|
||||
if (!dagOnVertical.has(targetNode.id)) {
|
||||
dagOnVertical.set(targetNode.id, []);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// find source nodes of each component in horizontal and vertical dags
|
||||
let undirectedOnHorizontal = dagToUndirected(dagOnHorizontal);
|
||||
let undirectedOnVertical = dagToUndirected(dagOnVertical);
|
||||
let componentsOnHorizontal = findComponents(undirectedOnHorizontal);
|
||||
let componentsOnVertical = findComponents(undirectedOnVertical);
|
||||
let reversedDagOnHorizontal = dagToReversed(dagOnHorizontal);
|
||||
let reversedDagOnVertical = dagToReversed(dagOnVertical);
|
||||
let componentSourcesOnHorizontal = [];
|
||||
let componentSourcesOnVertical = [];
|
||||
|
||||
componentsOnHorizontal.forEach(function(component, index) {
|
||||
componentSourcesOnHorizontal[index] = [];
|
||||
component.forEach(function(nodeId) {
|
||||
if (reversedDagOnHorizontal.get(nodeId).length == 0) {
|
||||
componentSourcesOnHorizontal[index].push(nodeId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
componentsOnVertical.forEach(function(component, index) {
|
||||
componentSourcesOnVertical[index] = [];
|
||||
component.forEach(function(nodeId) {
|
||||
if (reversedDagOnVertical.get(nodeId).length == 0) {
|
||||
componentSourcesOnVertical[index].push(nodeId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// calculate appropriate positioning for subgraphs
|
||||
let positionMapHorizontal = findAppropriatePositionForRelativePlacement(dagOnHorizontal, "horizontal", fixedNodesOnHorizontal, dummyPositionsForVerticalAlignment, componentSourcesOnHorizontal);
|
||||
let positionMapVertical = findAppropriatePositionForRelativePlacement(dagOnVertical, "vertical", fixedNodesOnVertical, dummyPositionsForHorizontalAlignment, componentSourcesOnVertical);
|
||||
|
||||
// update positions of the nodes based on relative placement constraints
|
||||
for (let key of positionMapHorizontal.keys()) {
|
||||
if (dummyToNodeForVerticalAlignment.get(key)) {
|
||||
dummyToNodeForVerticalAlignment.get(key).forEach(function(nodeId) {
|
||||
xCoords[nodeIndexes.get(nodeId)] = positionMapHorizontal.get(key);
|
||||
});
|
||||
}
|
||||
else {
|
||||
xCoords[nodeIndexes.get(key)] = positionMapHorizontal.get(key);
|
||||
}
|
||||
}
|
||||
for (let key of positionMapVertical.keys()) {
|
||||
if (dummyToNodeForHorizontalAlignment.get(key)) {
|
||||
dummyToNodeForHorizontalAlignment.get(key).forEach(function(nodeId) {
|
||||
yCoords[nodeIndexes.get(nodeId)] = positionMapVertical.get(key);
|
||||
});
|
||||
}
|
||||
else {
|
||||
yCoords[nodeIndexes.get(key)] = positionMapVertical.get(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// assign new coordinates to nodes after constraint handling
|
||||
for (let i = 0; i < allNodes.length; i++) {
|
||||
let node = allNodes[i];
|
||||
if (node.getChild() == null) {
|
||||
node.setCenter(xCoords[nodeIndexes.get(node.id)], yCoords[nodeIndexes.get(node.id)]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ConstraintHandler;
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
const path = require('path');
|
||||
const pkg = require('./package.json');
|
||||
const camelcase = require('camelcase');
|
||||
const process = require('process');
|
||||
const webpack = require('webpack');
|
||||
const env = process.env;
|
||||
const NODE_ENV = env.NODE_ENV;
|
||||
const MIN = env.MIN;
|
||||
const PROD = NODE_ENV === 'production';
|
||||
|
||||
let config = {
|
||||
devtool: PROD ? false : 'inline-source-map',
|
||||
entry: './index.js',
|
||||
output: {
|
||||
path: path.join( __dirname ),
|
||||
filename: 'cose-base.js',
|
||||
library: camelcase( pkg.name ),
|
||||
libraryTarget: 'umd',
|
||||
globalObject: 'this'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{ test: /\.js$/, exclude: /node_modules/, use: 'babel-loader' }
|
||||
]
|
||||
},
|
||||
optimization: {
|
||||
minimize: MIN ? true : false
|
||||
},
|
||||
externals: PROD ? {
|
||||
'layout-base': {
|
||||
commonjs2: 'layout-base',
|
||||
commonjs: 'layout-base',
|
||||
amd: 'layout-base',
|
||||
root: 'layoutBase'
|
||||
}
|
||||
} : {}
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": ["env"]
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 iVis@Bilkent
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
layout-base
|
||||
================================================================================
|
||||
|
||||
## Description
|
||||
|
||||
This repository implements a basic layout model and some utilities for Cytoscape.js layout extensions.
|
||||
|
||||
## Usage instructions
|
||||
|
||||
Add `layout-base` as a dependecy to your layout extension.
|
||||
|
||||
`require()` in the extension to reach functionality:
|
||||
|
||||
* `var Integer = require(layout-base).Integer`,
|
||||
* `var Layout = require(layout-base).Layout`,
|
||||
* `...`
|
||||
|
||||
For a usage example, see [cose-base](https://github.com/iVis-at-Bilkent/cose-base) or [avsdf-base](https://github.com/iVis-at-Bilkent/avsdf-base).
|
||||
|
||||

|
||||
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "layout-base",
|
||||
"description": "Basic layout model and some utilities for Cytoscape.js layout extensions",
|
||||
"main": "layout-base.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/iVis-at-Bilkent/layout-base.git"
|
||||
},
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"keywords": [
|
||||
"layout"
|
||||
],
|
||||
"license": "MIT"
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
let layoutBase = function(){
|
||||
return;
|
||||
};
|
||||
|
||||
layoutBase.FDLayout = require('./src/fd/FDLayout');
|
||||
layoutBase.FDLayoutConstants = require('./src/fd/FDLayoutConstants');
|
||||
layoutBase.FDLayoutEdge = require('./src/fd/FDLayoutEdge');
|
||||
layoutBase.FDLayoutNode = require('./src/fd/FDLayoutNode');
|
||||
layoutBase.DimensionD = require('./src/util/DimensionD');
|
||||
layoutBase.HashMap = require('./src/util/HashMap');
|
||||
layoutBase.HashSet = require('./src/util/HashSet');
|
||||
layoutBase.IGeometry = require('./src/util/IGeometry');
|
||||
layoutBase.IMath = require('./src/util/IMath');
|
||||
layoutBase.Integer = require('./src/util/Integer');
|
||||
layoutBase.Point = require('./src/util/Point');
|
||||
layoutBase.PointD = require('./src/util/PointD');
|
||||
layoutBase.RandomSeed = require('./src/util/RandomSeed');
|
||||
layoutBase.RectangleD = require('./src/util/RectangleD');
|
||||
layoutBase.Transform = require('./src/util/Transform');
|
||||
layoutBase.UniqueIDGeneretor = require('./src/util/UniqueIDGeneretor');
|
||||
layoutBase.Quicksort = require('./src/util/Quicksort');
|
||||
layoutBase.LinkedList = require('./src/util/LinkedList');
|
||||
layoutBase.LGraphObject = require('./src/LGraphObject');
|
||||
layoutBase.LGraph = require('./src/LGraph');
|
||||
layoutBase.LEdge = require('./src/LEdge');
|
||||
layoutBase.LGraphManager = require('./src/LGraphManager');
|
||||
layoutBase.LNode = require('./src/LNode');
|
||||
layoutBase.Layout = require('./src/Layout');
|
||||
layoutBase.LayoutConstants = require('./src/LayoutConstants');
|
||||
layoutBase.NeedlemanWunsch = require('./src/util/alignment/NeedlemanWunsch');
|
||||
layoutBase.Matrix = require('./src/util/Matrix');
|
||||
layoutBase.SVD = require('./src/util/SVD');
|
||||
|
||||
module.exports = layoutBase;
|
||||
|
||||
|
||||
+5230
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
browser=Chrome.INTEGRATED
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project-private xmlns="http://www.netbeans.org/ns/project-private/1">
|
||||
<editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/2" lastBookmarkId="0"/>
|
||||
<open-files xmlns="http://www.netbeans.org/ns/projectui-open-files/2">
|
||||
<group/>
|
||||
<group name="Constraint Support"/>
|
||||
</open-files>
|
||||
</project-private>
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
files.encoding=UTF-8
|
||||
site.root.folder=
|
||||
source.folder=
|
||||
Generated
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://www.netbeans.org/ns/project/1">
|
||||
<type>org.netbeans.modules.web.clientproject</type>
|
||||
<configuration>
|
||||
<data xmlns="http://www.netbeans.org/ns/clientside-project/1">
|
||||
<name>layout-base</name>
|
||||
</data>
|
||||
</configuration>
|
||||
</project>
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "layout-base",
|
||||
"version": "2.0.1",
|
||||
"description": "Basic layout model and some utilities for Cytoscape.js layout extensions",
|
||||
"main": "layout-base.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "cross-env NODE_ENV=production webpack"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/iVis-at-Bilkent/layout-base.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/iVis-at-Bilkent/layout-base/issues"
|
||||
},
|
||||
"homepage": "https://github.com/iVis-at-Bilkent/layout-base#readme",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.24.1",
|
||||
"babel-loader": "^7.0.0",
|
||||
"babel-preset-env": "^1.5.1",
|
||||
"camelcase": "^4.1.0",
|
||||
"cpy-cli": "^1.0.1",
|
||||
"cross-env": "^5.1.6",
|
||||
"eslint": "^3.19.0",
|
||||
"gh-pages": "^1.1.0",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"rimraf": "^2.6.2",
|
||||
"update": "^0.7.4",
|
||||
"updater-license": "^1.0.0",
|
||||
"forever": "^0.15.3",
|
||||
"webpack": "^2.6.1",
|
||||
"webpack-dev-server": "^2.4.5"
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
var LGraphObject = require('./LGraphObject');
|
||||
var IGeometry = require('./util/IGeometry');
|
||||
var IMath = require('./util/IMath');
|
||||
|
||||
function LEdge(source, target, vEdge) {
|
||||
LGraphObject.call(this, vEdge);
|
||||
|
||||
this.isOverlapingSourceAndTarget = false;
|
||||
this.vGraphObject = vEdge;
|
||||
this.bendpoints = [];
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
LEdge.prototype = Object.create(LGraphObject.prototype);
|
||||
|
||||
for (var prop in LGraphObject) {
|
||||
LEdge[prop] = LGraphObject[prop];
|
||||
}
|
||||
|
||||
LEdge.prototype.getSource = function ()
|
||||
{
|
||||
return this.source;
|
||||
};
|
||||
|
||||
LEdge.prototype.getTarget = function ()
|
||||
{
|
||||
return this.target;
|
||||
};
|
||||
|
||||
LEdge.prototype.isInterGraph = function ()
|
||||
{
|
||||
return this.isInterGraph;
|
||||
};
|
||||
|
||||
LEdge.prototype.getLength = function ()
|
||||
{
|
||||
return this.length;
|
||||
};
|
||||
|
||||
LEdge.prototype.isOverlapingSourceAndTarget = function ()
|
||||
{
|
||||
return this.isOverlapingSourceAndTarget;
|
||||
};
|
||||
|
||||
LEdge.prototype.getBendpoints = function ()
|
||||
{
|
||||
return this.bendpoints;
|
||||
};
|
||||
|
||||
LEdge.prototype.getLca = function ()
|
||||
{
|
||||
return this.lca;
|
||||
};
|
||||
|
||||
LEdge.prototype.getSourceInLca = function ()
|
||||
{
|
||||
return this.sourceInLca;
|
||||
};
|
||||
|
||||
LEdge.prototype.getTargetInLca = function ()
|
||||
{
|
||||
return this.targetInLca;
|
||||
};
|
||||
|
||||
LEdge.prototype.getOtherEnd = function (node)
|
||||
{
|
||||
if (this.source === node)
|
||||
{
|
||||
return this.target;
|
||||
}
|
||||
else if (this.target === node)
|
||||
{
|
||||
return this.source;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "Node is not incident with this edge";
|
||||
}
|
||||
}
|
||||
|
||||
LEdge.prototype.getOtherEndInGraph = function (node, graph)
|
||||
{
|
||||
var otherEnd = this.getOtherEnd(node);
|
||||
var root = graph.getGraphManager().getRoot();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (otherEnd.getOwner() == graph)
|
||||
{
|
||||
return otherEnd;
|
||||
}
|
||||
|
||||
if (otherEnd.getOwner() == root)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
otherEnd = otherEnd.getOwner().getParent();
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
LEdge.prototype.updateLength = function ()
|
||||
{
|
||||
var clipPointCoordinates = new Array(4);
|
||||
|
||||
this.isOverlapingSourceAndTarget =
|
||||
IGeometry.getIntersection(this.target.getRect(),
|
||||
this.source.getRect(),
|
||||
clipPointCoordinates);
|
||||
|
||||
if (!this.isOverlapingSourceAndTarget)
|
||||
{
|
||||
this.lengthX = clipPointCoordinates[0] - clipPointCoordinates[2];
|
||||
this.lengthY = clipPointCoordinates[1] - clipPointCoordinates[3];
|
||||
|
||||
if (Math.abs(this.lengthX) < 1.0)
|
||||
{
|
||||
this.lengthX = IMath.sign(this.lengthX);
|
||||
}
|
||||
|
||||
if (Math.abs(this.lengthY) < 1.0)
|
||||
{
|
||||
this.lengthY = IMath.sign(this.lengthY);
|
||||
}
|
||||
|
||||
this.length = Math.sqrt(
|
||||
this.lengthX * this.lengthX + this.lengthY * this.lengthY);
|
||||
}
|
||||
};
|
||||
|
||||
LEdge.prototype.updateLengthSimple = function ()
|
||||
{
|
||||
this.lengthX = this.target.getCenterX() - this.source.getCenterX();
|
||||
this.lengthY = this.target.getCenterY() - this.source.getCenterY();
|
||||
|
||||
if (Math.abs(this.lengthX) < 1.0)
|
||||
{
|
||||
this.lengthX = IMath.sign(this.lengthX);
|
||||
}
|
||||
|
||||
if (Math.abs(this.lengthY) < 1.0)
|
||||
{
|
||||
this.lengthY = IMath.sign(this.lengthY);
|
||||
}
|
||||
|
||||
this.length = Math.sqrt(
|
||||
this.lengthX * this.lengthX + this.lengthY * this.lengthY);
|
||||
}
|
||||
|
||||
module.exports = LEdge;
|
||||
+477
@@ -0,0 +1,477 @@
|
||||
var LGraphObject = require('./LGraphObject');
|
||||
var Integer = require('./util/Integer');
|
||||
var LayoutConstants = require('./LayoutConstants');
|
||||
var LGraphManager = require('./LGraphManager');
|
||||
var LNode = require('./LNode');
|
||||
var LEdge = require('./LEdge');
|
||||
var RectangleD = require('./util/RectangleD');
|
||||
var Point = require('./util/Point');
|
||||
var LinkedList = require('./util/LinkedList');
|
||||
|
||||
function LGraph(parent, obj2, vGraph) {
|
||||
LGraphObject.call(this, vGraph);
|
||||
this.estimatedSize = Integer.MIN_VALUE;
|
||||
this.margin = LayoutConstants.DEFAULT_GRAPH_MARGIN;
|
||||
this.edges = [];
|
||||
this.nodes = [];
|
||||
this.isConnected = false;
|
||||
this.parent = parent;
|
||||
|
||||
if (obj2 != null && obj2 instanceof LGraphManager) {
|
||||
this.graphManager = obj2;
|
||||
}
|
||||
else if (obj2 != null && obj2 instanceof Layout) {
|
||||
this.graphManager = obj2.graphManager;
|
||||
}
|
||||
}
|
||||
|
||||
LGraph.prototype = Object.create(LGraphObject.prototype);
|
||||
for (var prop in LGraphObject) {
|
||||
LGraph[prop] = LGraphObject[prop];
|
||||
}
|
||||
|
||||
LGraph.prototype.getNodes = function () {
|
||||
return this.nodes;
|
||||
};
|
||||
|
||||
LGraph.prototype.getEdges = function () {
|
||||
return this.edges;
|
||||
};
|
||||
|
||||
LGraph.prototype.getGraphManager = function ()
|
||||
{
|
||||
return this.graphManager;
|
||||
};
|
||||
|
||||
LGraph.prototype.getParent = function ()
|
||||
{
|
||||
return this.parent;
|
||||
};
|
||||
|
||||
LGraph.prototype.getLeft = function ()
|
||||
{
|
||||
return this.left;
|
||||
};
|
||||
|
||||
LGraph.prototype.getRight = function ()
|
||||
{
|
||||
return this.right;
|
||||
};
|
||||
|
||||
LGraph.prototype.getTop = function ()
|
||||
{
|
||||
return this.top;
|
||||
};
|
||||
|
||||
LGraph.prototype.getBottom = function ()
|
||||
{
|
||||
return this.bottom;
|
||||
};
|
||||
|
||||
LGraph.prototype.isConnected = function ()
|
||||
{
|
||||
return this.isConnected;
|
||||
};
|
||||
|
||||
LGraph.prototype.add = function (obj1, sourceNode, targetNode) {
|
||||
if (sourceNode == null && targetNode == null) {
|
||||
var newNode = obj1;
|
||||
if (this.graphManager == null) {
|
||||
throw "Graph has no graph mgr!";
|
||||
}
|
||||
if (this.getNodes().indexOf(newNode) > -1) {
|
||||
throw "Node already in graph!";
|
||||
}
|
||||
newNode.owner = this;
|
||||
this.getNodes().push(newNode);
|
||||
|
||||
return newNode;
|
||||
}
|
||||
else {
|
||||
var newEdge = obj1;
|
||||
if (!(this.getNodes().indexOf(sourceNode) > -1 && (this.getNodes().indexOf(targetNode)) > -1)) {
|
||||
throw "Source or target not in graph!";
|
||||
}
|
||||
|
||||
if (!(sourceNode.owner == targetNode.owner && sourceNode.owner == this)) {
|
||||
throw "Both owners must be this graph!";
|
||||
}
|
||||
|
||||
if (sourceNode.owner != targetNode.owner)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// set source and target
|
||||
newEdge.source = sourceNode;
|
||||
newEdge.target = targetNode;
|
||||
|
||||
// set as intra-graph edge
|
||||
newEdge.isInterGraph = false;
|
||||
|
||||
// add to graph edge list
|
||||
this.getEdges().push(newEdge);
|
||||
|
||||
// add to incidency lists
|
||||
sourceNode.edges.push(newEdge);
|
||||
|
||||
if (targetNode != sourceNode)
|
||||
{
|
||||
targetNode.edges.push(newEdge);
|
||||
}
|
||||
|
||||
return newEdge;
|
||||
}
|
||||
};
|
||||
|
||||
LGraph.prototype.remove = function (obj) {
|
||||
var node = obj;
|
||||
if (obj instanceof LNode) {
|
||||
if (node == null) {
|
||||
throw "Node is null!";
|
||||
}
|
||||
if (!(node.owner != null && node.owner == this)) {
|
||||
throw "Owner graph is invalid!";
|
||||
}
|
||||
if (this.graphManager == null) {
|
||||
throw "Owner graph manager is invalid!";
|
||||
}
|
||||
// remove incident edges first (make a copy to do it safely)
|
||||
var edgesToBeRemoved = node.edges.slice();
|
||||
var edge;
|
||||
var s = edgesToBeRemoved.length;
|
||||
for (var i = 0; i < s; i++)
|
||||
{
|
||||
edge = edgesToBeRemoved[i];
|
||||
|
||||
if (edge.isInterGraph)
|
||||
{
|
||||
this.graphManager.remove(edge);
|
||||
}
|
||||
else
|
||||
{
|
||||
edge.source.owner.remove(edge);
|
||||
}
|
||||
}
|
||||
|
||||
// now the node itself
|
||||
var index = this.nodes.indexOf(node);
|
||||
if (index == -1) {
|
||||
throw "Node not in owner node list!";
|
||||
}
|
||||
|
||||
this.nodes.splice(index, 1);
|
||||
}
|
||||
else if (obj instanceof LEdge) {
|
||||
var edge = obj;
|
||||
if (edge == null) {
|
||||
throw "Edge is null!";
|
||||
}
|
||||
if (!(edge.source != null && edge.target != null)) {
|
||||
throw "Source and/or target is null!";
|
||||
}
|
||||
if (!(edge.source.owner != null && edge.target.owner != null &&
|
||||
edge.source.owner == this && edge.target.owner == this)) {
|
||||
throw "Source and/or target owner is invalid!";
|
||||
}
|
||||
|
||||
var sourceIndex = edge.source.edges.indexOf(edge);
|
||||
var targetIndex = edge.target.edges.indexOf(edge);
|
||||
if (!(sourceIndex > -1 && targetIndex > -1)) {
|
||||
throw "Source and/or target doesn't know this edge!";
|
||||
}
|
||||
|
||||
edge.source.edges.splice(sourceIndex, 1);
|
||||
|
||||
if (edge.target != edge.source)
|
||||
{
|
||||
edge.target.edges.splice(targetIndex, 1);
|
||||
}
|
||||
|
||||
var index = edge.source.owner.getEdges().indexOf(edge);
|
||||
if (index == -1) {
|
||||
throw "Not in owner's edge list!";
|
||||
}
|
||||
|
||||
edge.source.owner.getEdges().splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
LGraph.prototype.updateLeftTop = function ()
|
||||
{
|
||||
var top = Integer.MAX_VALUE;
|
||||
var left = Integer.MAX_VALUE;
|
||||
var nodeTop;
|
||||
var nodeLeft;
|
||||
var margin;
|
||||
|
||||
var nodes = this.getNodes();
|
||||
var s = nodes.length;
|
||||
|
||||
for (var i = 0; i < s; i++)
|
||||
{
|
||||
var lNode = nodes[i];
|
||||
nodeTop = lNode.getTop();
|
||||
nodeLeft = lNode.getLeft();
|
||||
|
||||
if (top > nodeTop)
|
||||
{
|
||||
top = nodeTop;
|
||||
}
|
||||
|
||||
if (left > nodeLeft)
|
||||
{
|
||||
left = nodeLeft;
|
||||
}
|
||||
}
|
||||
|
||||
// Do we have any nodes in this graph?
|
||||
if (top == Integer.MAX_VALUE)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if(nodes[0].getParent().paddingLeft != undefined){
|
||||
margin = nodes[0].getParent().paddingLeft;
|
||||
}
|
||||
else{
|
||||
margin = this.margin;
|
||||
}
|
||||
|
||||
this.left = left - margin;
|
||||
this.top = top - margin;
|
||||
|
||||
// Apply the margins and return the result
|
||||
return new Point(this.left, this.top);
|
||||
};
|
||||
|
||||
LGraph.prototype.updateBounds = function (recursive)
|
||||
{
|
||||
// calculate bounds
|
||||
var left = Integer.MAX_VALUE;
|
||||
var right = -Integer.MAX_VALUE;
|
||||
var top = Integer.MAX_VALUE;
|
||||
var bottom = -Integer.MAX_VALUE;
|
||||
var nodeLeft;
|
||||
var nodeRight;
|
||||
var nodeTop;
|
||||
var nodeBottom;
|
||||
var margin;
|
||||
|
||||
var nodes = this.nodes;
|
||||
var s = nodes.length;
|
||||
for (var i = 0; i < s; i++)
|
||||
{
|
||||
var lNode = nodes[i];
|
||||
|
||||
if (recursive && lNode.child != null)
|
||||
{
|
||||
lNode.updateBounds();
|
||||
}
|
||||
nodeLeft = lNode.getLeft();
|
||||
nodeRight = lNode.getRight();
|
||||
nodeTop = lNode.getTop();
|
||||
nodeBottom = lNode.getBottom();
|
||||
|
||||
if (left > nodeLeft)
|
||||
{
|
||||
left = nodeLeft;
|
||||
}
|
||||
|
||||
if (right < nodeRight)
|
||||
{
|
||||
right = nodeRight;
|
||||
}
|
||||
|
||||
if (top > nodeTop)
|
||||
{
|
||||
top = nodeTop;
|
||||
}
|
||||
|
||||
if (bottom < nodeBottom)
|
||||
{
|
||||
bottom = nodeBottom;
|
||||
}
|
||||
}
|
||||
|
||||
var boundingRect = new RectangleD(left, top, right - left, bottom - top);
|
||||
if (left == Integer.MAX_VALUE)
|
||||
{
|
||||
this.left = this.parent.getLeft();
|
||||
this.right = this.parent.getRight();
|
||||
this.top = this.parent.getTop();
|
||||
this.bottom = this.parent.getBottom();
|
||||
}
|
||||
|
||||
if(nodes[0].getParent().paddingLeft != undefined){
|
||||
margin = nodes[0].getParent().paddingLeft;
|
||||
}
|
||||
else{
|
||||
margin = this.margin;
|
||||
}
|
||||
|
||||
this.left = boundingRect.x - margin;
|
||||
this.right = boundingRect.x + boundingRect.width + margin;
|
||||
this.top = boundingRect.y - margin;
|
||||
this.bottom = boundingRect.y + boundingRect.height + margin;
|
||||
};
|
||||
|
||||
LGraph.calculateBounds = function (nodes)
|
||||
{
|
||||
var left = Integer.MAX_VALUE;
|
||||
var right = -Integer.MAX_VALUE;
|
||||
var top = Integer.MAX_VALUE;
|
||||
var bottom = -Integer.MAX_VALUE;
|
||||
var nodeLeft;
|
||||
var nodeRight;
|
||||
var nodeTop;
|
||||
var nodeBottom;
|
||||
|
||||
var s = nodes.length;
|
||||
|
||||
for (var i = 0; i < s; i++)
|
||||
{
|
||||
var lNode = nodes[i];
|
||||
nodeLeft = lNode.getLeft();
|
||||
nodeRight = lNode.getRight();
|
||||
nodeTop = lNode.getTop();
|
||||
nodeBottom = lNode.getBottom();
|
||||
|
||||
if (left > nodeLeft)
|
||||
{
|
||||
left = nodeLeft;
|
||||
}
|
||||
|
||||
if (right < nodeRight)
|
||||
{
|
||||
right = nodeRight;
|
||||
}
|
||||
|
||||
if (top > nodeTop)
|
||||
{
|
||||
top = nodeTop;
|
||||
}
|
||||
|
||||
if (bottom < nodeBottom)
|
||||
{
|
||||
bottom = nodeBottom;
|
||||
}
|
||||
}
|
||||
|
||||
var boundingRect = new RectangleD(left, top, right - left, bottom - top);
|
||||
|
||||
return boundingRect;
|
||||
};
|
||||
|
||||
LGraph.prototype.getInclusionTreeDepth = function ()
|
||||
{
|
||||
if (this == this.graphManager.getRoot())
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.parent.getInclusionTreeDepth();
|
||||
}
|
||||
};
|
||||
|
||||
LGraph.prototype.getEstimatedSize = function ()
|
||||
{
|
||||
if (this.estimatedSize == Integer.MIN_VALUE) {
|
||||
throw "assert failed";
|
||||
}
|
||||
return this.estimatedSize;
|
||||
};
|
||||
|
||||
LGraph.prototype.calcEstimatedSize = function ()
|
||||
{
|
||||
var size = 0;
|
||||
var nodes = this.nodes;
|
||||
var s = nodes.length;
|
||||
|
||||
for (var i = 0; i < s; i++)
|
||||
{
|
||||
var lNode = nodes[i];
|
||||
size += lNode.calcEstimatedSize();
|
||||
}
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
this.estimatedSize = LayoutConstants.EMPTY_COMPOUND_NODE_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.estimatedSize = size / Math.sqrt(this.nodes.length);
|
||||
}
|
||||
|
||||
return this.estimatedSize;
|
||||
};
|
||||
|
||||
LGraph.prototype.updateConnected = function ()
|
||||
{
|
||||
var self = this;
|
||||
if (this.nodes.length == 0)
|
||||
{
|
||||
this.isConnected = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var queue = new LinkedList();
|
||||
var visited = new Set();
|
||||
var currentNode = this.nodes[0];
|
||||
var neighborEdges;
|
||||
var currentNeighbor;
|
||||
var childrenOfNode = currentNode.withChildren();
|
||||
childrenOfNode.forEach(function(node) {
|
||||
queue.push(node);
|
||||
visited.add(node);
|
||||
});
|
||||
|
||||
while (queue.length !== 0)
|
||||
{
|
||||
currentNode = queue.shift();
|
||||
|
||||
// Traverse all neighbors of this node
|
||||
neighborEdges = currentNode.getEdges();
|
||||
var size = neighborEdges.length;
|
||||
for (var i = 0; i < size; i++)
|
||||
{
|
||||
var neighborEdge = neighborEdges[i];
|
||||
currentNeighbor =
|
||||
neighborEdge.getOtherEndInGraph(currentNode, this);
|
||||
|
||||
// Add unvisited neighbors to the list to visit
|
||||
if (currentNeighbor != null &&
|
||||
!visited.has(currentNeighbor))
|
||||
{
|
||||
var childrenOfNeighbor = currentNeighbor.withChildren();
|
||||
|
||||
childrenOfNeighbor.forEach(function(node) {
|
||||
queue.push(node);
|
||||
visited.add(node);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.isConnected = false;
|
||||
|
||||
if (visited.size >= this.nodes.length)
|
||||
{
|
||||
var noOfVisitedInThisGraph = 0;
|
||||
|
||||
visited.forEach(function(visitedNode) {
|
||||
if (visitedNode.owner == self)
|
||||
{
|
||||
noOfVisitedInThisGraph++;
|
||||
}
|
||||
});
|
||||
|
||||
if (noOfVisitedInThisGraph == this.nodes.length)
|
||||
{
|
||||
this.isConnected = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = LGraph;
|
||||
Generated
Vendored
+500
@@ -0,0 +1,500 @@
|
||||
var LGraph;
|
||||
var LEdge = require('./LEdge');
|
||||
|
||||
function LGraphManager(layout) {
|
||||
LGraph = require('./LGraph'); // It may be better to initilize this out of this function but it gives an error (Right-hand side of 'instanceof' is not callable) now.
|
||||
this.layout = layout;
|
||||
|
||||
this.graphs = [];
|
||||
this.edges = [];
|
||||
}
|
||||
|
||||
LGraphManager.prototype.addRoot = function ()
|
||||
{
|
||||
var ngraph = this.layout.newGraph();
|
||||
var nnode = this.layout.newNode(null);
|
||||
var root = this.add(ngraph, nnode);
|
||||
this.setRootGraph(root);
|
||||
return this.rootGraph;
|
||||
};
|
||||
|
||||
LGraphManager.prototype.add = function (newGraph, parentNode, newEdge, sourceNode, targetNode)
|
||||
{
|
||||
//there are just 2 parameters are passed then it adds an LGraph else it adds an LEdge
|
||||
if (newEdge == null && sourceNode == null && targetNode == null) {
|
||||
if (newGraph == null) {
|
||||
throw "Graph is null!";
|
||||
}
|
||||
if (parentNode == null) {
|
||||
throw "Parent node is null!";
|
||||
}
|
||||
if (this.graphs.indexOf(newGraph) > -1) {
|
||||
throw "Graph already in this graph mgr!";
|
||||
}
|
||||
|
||||
this.graphs.push(newGraph);
|
||||
|
||||
if (newGraph.parent != null) {
|
||||
throw "Already has a parent!";
|
||||
}
|
||||
if (parentNode.child != null) {
|
||||
throw "Already has a child!";
|
||||
}
|
||||
|
||||
newGraph.parent = parentNode;
|
||||
parentNode.child = newGraph;
|
||||
|
||||
return newGraph;
|
||||
}
|
||||
else {
|
||||
//change the order of the parameters
|
||||
targetNode = newEdge;
|
||||
sourceNode = parentNode;
|
||||
newEdge = newGraph;
|
||||
var sourceGraph = sourceNode.getOwner();
|
||||
var targetGraph = targetNode.getOwner();
|
||||
|
||||
if (!(sourceGraph != null && sourceGraph.getGraphManager() == this)) {
|
||||
throw "Source not in this graph mgr!";
|
||||
}
|
||||
if (!(targetGraph != null && targetGraph.getGraphManager() == this)) {
|
||||
throw "Target not in this graph mgr!";
|
||||
}
|
||||
|
||||
if (sourceGraph == targetGraph)
|
||||
{
|
||||
newEdge.isInterGraph = false;
|
||||
return sourceGraph.add(newEdge, sourceNode, targetNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
newEdge.isInterGraph = true;
|
||||
|
||||
// set source and target
|
||||
newEdge.source = sourceNode;
|
||||
newEdge.target = targetNode;
|
||||
|
||||
// add edge to inter-graph edge list
|
||||
if (this.edges.indexOf(newEdge) > -1) {
|
||||
throw "Edge already in inter-graph edge list!";
|
||||
}
|
||||
|
||||
this.edges.push(newEdge);
|
||||
|
||||
// add edge to source and target incidency lists
|
||||
if (!(newEdge.source != null && newEdge.target != null)) {
|
||||
throw "Edge source and/or target is null!";
|
||||
}
|
||||
|
||||
if (!(newEdge.source.edges.indexOf(newEdge) == -1 && newEdge.target.edges.indexOf(newEdge) == -1)) {
|
||||
throw "Edge already in source and/or target incidency list!";
|
||||
}
|
||||
|
||||
newEdge.source.edges.push(newEdge);
|
||||
newEdge.target.edges.push(newEdge);
|
||||
|
||||
return newEdge;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LGraphManager.prototype.remove = function (lObj) {
|
||||
if (lObj instanceof LGraph) {
|
||||
var graph = lObj;
|
||||
if (graph.getGraphManager() != this) {
|
||||
throw "Graph not in this graph mgr";
|
||||
}
|
||||
if (!(graph == this.rootGraph || (graph.parent != null && graph.parent.graphManager == this))) {
|
||||
throw "Invalid parent node!";
|
||||
}
|
||||
|
||||
// first the edges (make a copy to do it safely)
|
||||
var edgesToBeRemoved = [];
|
||||
|
||||
edgesToBeRemoved = edgesToBeRemoved.concat(graph.getEdges());
|
||||
|
||||
var edge;
|
||||
var s = edgesToBeRemoved.length;
|
||||
for (var i = 0; i < s; i++)
|
||||
{
|
||||
edge = edgesToBeRemoved[i];
|
||||
graph.remove(edge);
|
||||
}
|
||||
|
||||
// then the nodes (make a copy to do it safely)
|
||||
var nodesToBeRemoved = [];
|
||||
|
||||
nodesToBeRemoved = nodesToBeRemoved.concat(graph.getNodes());
|
||||
|
||||
var node;
|
||||
s = nodesToBeRemoved.length;
|
||||
for (var i = 0; i < s; i++)
|
||||
{
|
||||
node = nodesToBeRemoved[i];
|
||||
graph.remove(node);
|
||||
}
|
||||
|
||||
// check if graph is the root
|
||||
if (graph == this.rootGraph)
|
||||
{
|
||||
this.setRootGraph(null);
|
||||
}
|
||||
|
||||
// now remove the graph itself
|
||||
var index = this.graphs.indexOf(graph);
|
||||
this.graphs.splice(index, 1);
|
||||
|
||||
// also reset the parent of the graph
|
||||
graph.parent = null;
|
||||
}
|
||||
else if (lObj instanceof LEdge) {
|
||||
edge = lObj;
|
||||
if (edge == null) {
|
||||
throw "Edge is null!";
|
||||
}
|
||||
if (!edge.isInterGraph) {
|
||||
throw "Not an inter-graph edge!";
|
||||
}
|
||||
if (!(edge.source != null && edge.target != null)) {
|
||||
throw "Source and/or target is null!";
|
||||
}
|
||||
|
||||
// remove edge from source and target nodes' incidency lists
|
||||
|
||||
if (!(edge.source.edges.indexOf(edge) != -1 && edge.target.edges.indexOf(edge) != -1)) {
|
||||
throw "Source and/or target doesn't know this edge!";
|
||||
}
|
||||
|
||||
var index = edge.source.edges.indexOf(edge);
|
||||
edge.source.edges.splice(index, 1);
|
||||
index = edge.target.edges.indexOf(edge);
|
||||
edge.target.edges.splice(index, 1);
|
||||
|
||||
// remove edge from owner graph manager's inter-graph edge list
|
||||
|
||||
if (!(edge.source.owner != null && edge.source.owner.getGraphManager() != null)) {
|
||||
throw "Edge owner graph or owner graph manager is null!";
|
||||
}
|
||||
if (edge.source.owner.getGraphManager().edges.indexOf(edge) == -1) {
|
||||
throw "Not in owner graph manager's edge list!";
|
||||
}
|
||||
|
||||
var index = edge.source.owner.getGraphManager().edges.indexOf(edge);
|
||||
edge.source.owner.getGraphManager().edges.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
LGraphManager.prototype.updateBounds = function ()
|
||||
{
|
||||
this.rootGraph.updateBounds(true);
|
||||
};
|
||||
|
||||
LGraphManager.prototype.getGraphs = function ()
|
||||
{
|
||||
return this.graphs;
|
||||
};
|
||||
|
||||
LGraphManager.prototype.getAllNodes = function ()
|
||||
{
|
||||
if (this.allNodes == null)
|
||||
{
|
||||
var nodeList = [];
|
||||
var graphs = this.getGraphs();
|
||||
var s = graphs.length;
|
||||
for (var i = 0; i < s; i++)
|
||||
{
|
||||
nodeList = nodeList.concat(graphs[i].getNodes());
|
||||
}
|
||||
this.allNodes = nodeList;
|
||||
}
|
||||
return this.allNodes;
|
||||
};
|
||||
|
||||
LGraphManager.prototype.resetAllNodes = function ()
|
||||
{
|
||||
this.allNodes = null;
|
||||
};
|
||||
|
||||
LGraphManager.prototype.resetAllEdges = function ()
|
||||
{
|
||||
this.allEdges = null;
|
||||
};
|
||||
|
||||
LGraphManager.prototype.resetAllNodesToApplyGravitation = function ()
|
||||
{
|
||||
this.allNodesToApplyGravitation = null;
|
||||
};
|
||||
|
||||
LGraphManager.prototype.getAllEdges = function ()
|
||||
{
|
||||
if (this.allEdges == null)
|
||||
{
|
||||
var edgeList = [];
|
||||
var graphs = this.getGraphs();
|
||||
var s = graphs.length;
|
||||
for (var i = 0; i < graphs.length; i++)
|
||||
{
|
||||
edgeList = edgeList.concat(graphs[i].getEdges());
|
||||
}
|
||||
|
||||
edgeList = edgeList.concat(this.edges);
|
||||
|
||||
this.allEdges = edgeList;
|
||||
}
|
||||
return this.allEdges;
|
||||
};
|
||||
|
||||
LGraphManager.prototype.getAllNodesToApplyGravitation = function ()
|
||||
{
|
||||
return this.allNodesToApplyGravitation;
|
||||
};
|
||||
|
||||
LGraphManager.prototype.setAllNodesToApplyGravitation = function (nodeList)
|
||||
{
|
||||
if (this.allNodesToApplyGravitation != null) {
|
||||
throw "assert failed";
|
||||
}
|
||||
|
||||
this.allNodesToApplyGravitation = nodeList;
|
||||
};
|
||||
|
||||
LGraphManager.prototype.getRoot = function ()
|
||||
{
|
||||
return this.rootGraph;
|
||||
};
|
||||
|
||||
LGraphManager.prototype.setRootGraph = function (graph)
|
||||
{
|
||||
if (graph.getGraphManager() != this) {
|
||||
throw "Root not in this graph mgr!";
|
||||
}
|
||||
|
||||
this.rootGraph = graph;
|
||||
// root graph must have a root node associated with it for convenience
|
||||
if (graph.parent == null)
|
||||
{
|
||||
graph.parent = this.layout.newNode("Root node");
|
||||
}
|
||||
};
|
||||
|
||||
LGraphManager.prototype.getLayout = function ()
|
||||
{
|
||||
return this.layout;
|
||||
};
|
||||
|
||||
LGraphManager.prototype.isOneAncestorOfOther = function (firstNode, secondNode)
|
||||
{
|
||||
if (!(firstNode != null && secondNode != null)) {
|
||||
throw "assert failed";
|
||||
}
|
||||
|
||||
if (firstNode == secondNode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// Is second node an ancestor of the first one?
|
||||
var ownerGraph = firstNode.getOwner();
|
||||
var parentNode;
|
||||
|
||||
do
|
||||
{
|
||||
parentNode = ownerGraph.getParent();
|
||||
|
||||
if (parentNode == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (parentNode == secondNode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ownerGraph = parentNode.getOwner();
|
||||
if (ownerGraph == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
// Is first node an ancestor of the second one?
|
||||
ownerGraph = secondNode.getOwner();
|
||||
|
||||
do
|
||||
{
|
||||
parentNode = ownerGraph.getParent();
|
||||
|
||||
if (parentNode == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (parentNode == firstNode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ownerGraph = parentNode.getOwner();
|
||||
if (ownerGraph == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
LGraphManager.prototype.calcLowestCommonAncestors = function ()
|
||||
{
|
||||
var edge;
|
||||
var sourceNode;
|
||||
var targetNode;
|
||||
var sourceAncestorGraph;
|
||||
var targetAncestorGraph;
|
||||
|
||||
var edges = this.getAllEdges();
|
||||
var s = edges.length;
|
||||
for (var i = 0; i < s; i++)
|
||||
{
|
||||
edge = edges[i];
|
||||
|
||||
sourceNode = edge.source;
|
||||
targetNode = edge.target;
|
||||
edge.lca = null;
|
||||
edge.sourceInLca = sourceNode;
|
||||
edge.targetInLca = targetNode;
|
||||
|
||||
if (sourceNode == targetNode)
|
||||
{
|
||||
edge.lca = sourceNode.getOwner();
|
||||
continue;
|
||||
}
|
||||
|
||||
sourceAncestorGraph = sourceNode.getOwner();
|
||||
|
||||
while (edge.lca == null)
|
||||
{
|
||||
edge.targetInLca = targetNode;
|
||||
targetAncestorGraph = targetNode.getOwner();
|
||||
|
||||
while (edge.lca == null)
|
||||
{
|
||||
if (targetAncestorGraph == sourceAncestorGraph)
|
||||
{
|
||||
edge.lca = targetAncestorGraph;
|
||||
break;
|
||||
}
|
||||
|
||||
if (targetAncestorGraph == this.rootGraph)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (edge.lca != null) {
|
||||
throw "assert failed";
|
||||
}
|
||||
edge.targetInLca = targetAncestorGraph.getParent();
|
||||
targetAncestorGraph = edge.targetInLca.getOwner();
|
||||
}
|
||||
|
||||
if (sourceAncestorGraph == this.rootGraph)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (edge.lca == null)
|
||||
{
|
||||
edge.sourceInLca = sourceAncestorGraph.getParent();
|
||||
sourceAncestorGraph = edge.sourceInLca.getOwner();
|
||||
}
|
||||
}
|
||||
|
||||
if (edge.lca == null) {
|
||||
throw "assert failed";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LGraphManager.prototype.calcLowestCommonAncestor = function (firstNode, secondNode)
|
||||
{
|
||||
if (firstNode == secondNode)
|
||||
{
|
||||
return firstNode.getOwner();
|
||||
}
|
||||
var firstOwnerGraph = firstNode.getOwner();
|
||||
|
||||
do
|
||||
{
|
||||
if (firstOwnerGraph == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
var secondOwnerGraph = secondNode.getOwner();
|
||||
|
||||
do
|
||||
{
|
||||
if (secondOwnerGraph == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (secondOwnerGraph == firstOwnerGraph)
|
||||
{
|
||||
return secondOwnerGraph;
|
||||
}
|
||||
secondOwnerGraph = secondOwnerGraph.getParent().getOwner();
|
||||
} while (true);
|
||||
|
||||
firstOwnerGraph = firstOwnerGraph.getParent().getOwner();
|
||||
} while (true);
|
||||
|
||||
return firstOwnerGraph;
|
||||
};
|
||||
|
||||
LGraphManager.prototype.calcInclusionTreeDepths = function (graph, depth) {
|
||||
if (graph == null && depth == null) {
|
||||
graph = this.rootGraph;
|
||||
depth = 1;
|
||||
}
|
||||
var node;
|
||||
|
||||
var nodes = graph.getNodes();
|
||||
var s = nodes.length;
|
||||
for (var i = 0; i < s; i++)
|
||||
{
|
||||
node = nodes[i];
|
||||
node.inclusionTreeDepth = depth;
|
||||
|
||||
if (node.child != null)
|
||||
{
|
||||
this.calcInclusionTreeDepths(node.child, depth + 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LGraphManager.prototype.includesInvalidEdge = function ()
|
||||
{
|
||||
var edge;
|
||||
var edgesToRemove = [];
|
||||
|
||||
var s = this.edges.length;
|
||||
for (var i = 0; i < s; i++)
|
||||
{
|
||||
edge = this.edges[i];
|
||||
|
||||
if (this.isOneAncestorOfOther(edge.source, edge.target))
|
||||
{
|
||||
edgesToRemove.push(edge);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove invalid edges from graph manager
|
||||
for (var i = 0; i < edgesToRemove.length; i++)
|
||||
{
|
||||
this.remove(edgesToRemove[i]);
|
||||
}
|
||||
|
||||
// Invalid edges are cleared, so return false
|
||||
return false;
|
||||
};
|
||||
|
||||
module.exports = LGraphManager;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
function LGraphObject(vGraphObject) {
|
||||
this.vGraphObject = vGraphObject;
|
||||
}
|
||||
|
||||
module.exports = LGraphObject;
|
||||
+418
@@ -0,0 +1,418 @@
|
||||
var LGraphObject = require('./LGraphObject');
|
||||
var Integer = require('./util/Integer');
|
||||
var RectangleD = require('./util/RectangleD');
|
||||
var LayoutConstants = require('./LayoutConstants');
|
||||
var RandomSeed = require('./util/RandomSeed');
|
||||
var PointD = require('./util/PointD');
|
||||
|
||||
function LNode(gm, loc, size, vNode) {
|
||||
//Alternative constructor 1 : LNode(LGraphManager gm, Point loc, Dimension size, Object vNode)
|
||||
if (size == null && vNode == null) {
|
||||
vNode = loc;
|
||||
}
|
||||
|
||||
LGraphObject.call(this, vNode);
|
||||
|
||||
//Alternative constructor 2 : LNode(Layout layout, Object vNode)
|
||||
if (gm.graphManager != null)
|
||||
gm = gm.graphManager;
|
||||
|
||||
this.estimatedSize = Integer.MIN_VALUE;
|
||||
this.inclusionTreeDepth = Integer.MAX_VALUE;
|
||||
this.vGraphObject = vNode;
|
||||
this.edges = [];
|
||||
this.graphManager = gm;
|
||||
|
||||
if (size != null && loc != null)
|
||||
this.rect = new RectangleD(loc.x, loc.y, size.width, size.height);
|
||||
else
|
||||
this.rect = new RectangleD();
|
||||
}
|
||||
|
||||
LNode.prototype = Object.create(LGraphObject.prototype);
|
||||
for (var prop in LGraphObject) {
|
||||
LNode[prop] = LGraphObject[prop];
|
||||
}
|
||||
|
||||
LNode.prototype.getEdges = function ()
|
||||
{
|
||||
return this.edges;
|
||||
};
|
||||
|
||||
LNode.prototype.getChild = function ()
|
||||
{
|
||||
return this.child;
|
||||
};
|
||||
|
||||
LNode.prototype.getOwner = function ()
|
||||
{
|
||||
// if (this.owner != null) {
|
||||
// if (!(this.owner == null || this.owner.getNodes().indexOf(this) > -1)) {
|
||||
// throw "assert failed";
|
||||
// }
|
||||
// }
|
||||
|
||||
return this.owner;
|
||||
};
|
||||
|
||||
LNode.prototype.getWidth = function ()
|
||||
{
|
||||
return this.rect.width;
|
||||
};
|
||||
|
||||
LNode.prototype.setWidth = function (width)
|
||||
{
|
||||
this.rect.width = width;
|
||||
};
|
||||
|
||||
LNode.prototype.getHeight = function ()
|
||||
{
|
||||
return this.rect.height;
|
||||
};
|
||||
|
||||
LNode.prototype.setHeight = function (height)
|
||||
{
|
||||
this.rect.height = height;
|
||||
};
|
||||
|
||||
LNode.prototype.getCenterX = function ()
|
||||
{
|
||||
return this.rect.x + this.rect.width / 2;
|
||||
};
|
||||
|
||||
LNode.prototype.getCenterY = function ()
|
||||
{
|
||||
return this.rect.y + this.rect.height / 2;
|
||||
};
|
||||
|
||||
LNode.prototype.getCenter = function ()
|
||||
{
|
||||
return new PointD(this.rect.x + this.rect.width / 2,
|
||||
this.rect.y + this.rect.height / 2);
|
||||
};
|
||||
|
||||
LNode.prototype.getLocation = function ()
|
||||
{
|
||||
return new PointD(this.rect.x, this.rect.y);
|
||||
};
|
||||
|
||||
LNode.prototype.getRect = function ()
|
||||
{
|
||||
return this.rect;
|
||||
};
|
||||
|
||||
LNode.prototype.getDiagonal = function ()
|
||||
{
|
||||
return Math.sqrt(this.rect.width * this.rect.width +
|
||||
this.rect.height * this.rect.height);
|
||||
};
|
||||
|
||||
/**
|
||||
* This method returns half the diagonal length of this node.
|
||||
*/
|
||||
LNode.prototype.getHalfTheDiagonal = function () {
|
||||
return Math.sqrt(this.rect.height * this.rect.height +
|
||||
this.rect.width * this.rect.width) / 2;
|
||||
};
|
||||
|
||||
LNode.prototype.setRect = function (upperLeft, dimension)
|
||||
{
|
||||
this.rect.x = upperLeft.x;
|
||||
this.rect.y = upperLeft.y;
|
||||
this.rect.width = dimension.width;
|
||||
this.rect.height = dimension.height;
|
||||
};
|
||||
|
||||
LNode.prototype.setCenter = function (cx, cy)
|
||||
{
|
||||
this.rect.x = cx - this.rect.width / 2;
|
||||
this.rect.y = cy - this.rect.height / 2;
|
||||
};
|
||||
|
||||
LNode.prototype.setLocation = function (x, y)
|
||||
{
|
||||
this.rect.x = x;
|
||||
this.rect.y = y;
|
||||
};
|
||||
|
||||
LNode.prototype.moveBy = function (dx, dy)
|
||||
{
|
||||
this.rect.x += dx;
|
||||
this.rect.y += dy;
|
||||
};
|
||||
|
||||
LNode.prototype.getEdgeListToNode = function (to)
|
||||
{
|
||||
var edgeList = [];
|
||||
var edge;
|
||||
var self = this;
|
||||
|
||||
self.edges.forEach(function(edge) {
|
||||
|
||||
if (edge.target == to)
|
||||
{
|
||||
if (edge.source != self)
|
||||
throw "Incorrect edge source!";
|
||||
|
||||
edgeList.push(edge);
|
||||
}
|
||||
});
|
||||
|
||||
return edgeList;
|
||||
};
|
||||
|
||||
LNode.prototype.getEdgesBetween = function (other)
|
||||
{
|
||||
var edgeList = [];
|
||||
var edge;
|
||||
|
||||
var self = this;
|
||||
self.edges.forEach(function(edge) {
|
||||
|
||||
if (!(edge.source == self || edge.target == self))
|
||||
throw "Incorrect edge source and/or target";
|
||||
|
||||
if ((edge.target == other) || (edge.source == other))
|
||||
{
|
||||
edgeList.push(edge);
|
||||
}
|
||||
});
|
||||
|
||||
return edgeList;
|
||||
};
|
||||
|
||||
LNode.prototype.getNeighborsList = function ()
|
||||
{
|
||||
var neighbors = new Set();
|
||||
|
||||
var self = this;
|
||||
self.edges.forEach(function(edge) {
|
||||
|
||||
if (edge.source == self)
|
||||
{
|
||||
neighbors.add(edge.target);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (edge.target != self) {
|
||||
throw "Incorrect incidency!";
|
||||
}
|
||||
|
||||
neighbors.add(edge.source);
|
||||
}
|
||||
});
|
||||
|
||||
return neighbors;
|
||||
};
|
||||
|
||||
LNode.prototype.withChildren = function ()
|
||||
{
|
||||
var withNeighborsList = new Set();
|
||||
var childNode;
|
||||
var children;
|
||||
|
||||
withNeighborsList.add(this);
|
||||
|
||||
if (this.child != null)
|
||||
{
|
||||
var nodes = this.child.getNodes();
|
||||
for (var i = 0; i < nodes.length; i++)
|
||||
{
|
||||
childNode = nodes[i];
|
||||
children = childNode.withChildren();
|
||||
children.forEach(function(node) {
|
||||
withNeighborsList.add(node);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return withNeighborsList;
|
||||
};
|
||||
|
||||
LNode.prototype.getNoOfChildren = function ()
|
||||
{
|
||||
var noOfChildren = 0;
|
||||
var childNode;
|
||||
|
||||
if(this.child == null){
|
||||
noOfChildren = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
var nodes = this.child.getNodes();
|
||||
for (var i = 0; i < nodes.length; i++)
|
||||
{
|
||||
childNode = nodes[i];
|
||||
|
||||
noOfChildren += childNode.getNoOfChildren();
|
||||
}
|
||||
}
|
||||
|
||||
if(noOfChildren == 0){
|
||||
noOfChildren = 1;
|
||||
}
|
||||
return noOfChildren;
|
||||
};
|
||||
|
||||
LNode.prototype.getEstimatedSize = function () {
|
||||
if (this.estimatedSize == Integer.MIN_VALUE) {
|
||||
throw "assert failed";
|
||||
}
|
||||
return this.estimatedSize;
|
||||
};
|
||||
|
||||
LNode.prototype.calcEstimatedSize = function () {
|
||||
if (this.child == null)
|
||||
{
|
||||
return this.estimatedSize = (this.rect.width + this.rect.height) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.estimatedSize = this.child.calcEstimatedSize();
|
||||
this.rect.width = this.estimatedSize;
|
||||
this.rect.height = this.estimatedSize;
|
||||
|
||||
return this.estimatedSize;
|
||||
}
|
||||
};
|
||||
|
||||
LNode.prototype.scatter = function () {
|
||||
var randomCenterX;
|
||||
var randomCenterY;
|
||||
|
||||
var minX = -LayoutConstants.INITIAL_WORLD_BOUNDARY;
|
||||
var maxX = LayoutConstants.INITIAL_WORLD_BOUNDARY;
|
||||
randomCenterX = LayoutConstants.WORLD_CENTER_X +
|
||||
(RandomSeed.nextDouble() * (maxX - minX)) + minX;
|
||||
|
||||
var minY = -LayoutConstants.INITIAL_WORLD_BOUNDARY;
|
||||
var maxY = LayoutConstants.INITIAL_WORLD_BOUNDARY;
|
||||
randomCenterY = LayoutConstants.WORLD_CENTER_Y +
|
||||
(RandomSeed.nextDouble() * (maxY - minY)) + minY;
|
||||
|
||||
this.rect.x = randomCenterX;
|
||||
this.rect.y = randomCenterY
|
||||
};
|
||||
|
||||
LNode.prototype.updateBounds = function () {
|
||||
if (this.getChild() == null) {
|
||||
throw "assert failed";
|
||||
}
|
||||
if (this.getChild().getNodes().length != 0)
|
||||
{
|
||||
// wrap the children nodes by re-arranging the boundaries
|
||||
var childGraph = this.getChild();
|
||||
childGraph.updateBounds(true);
|
||||
|
||||
this.rect.x = childGraph.getLeft();
|
||||
this.rect.y = childGraph.getTop();
|
||||
|
||||
this.setWidth(childGraph.getRight() - childGraph.getLeft());
|
||||
this.setHeight(childGraph.getBottom() - childGraph.getTop());
|
||||
|
||||
// Update compound bounds considering its label properties
|
||||
if(LayoutConstants.NODE_DIMENSIONS_INCLUDE_LABELS){
|
||||
|
||||
var width = childGraph.getRight() - childGraph.getLeft();
|
||||
var height = childGraph.getBottom() - childGraph.getTop();
|
||||
|
||||
if(this.labelWidth){
|
||||
if(this.labelPosHorizontal == "left"){
|
||||
this.rect.x -= (this.labelWidth);
|
||||
this.setWidth(width + this.labelWidth);
|
||||
}
|
||||
else if(this.labelPosHorizontal == "center" && this.labelWidth > width){
|
||||
this.rect.x -= (this.labelWidth - width) / 2;
|
||||
this.setWidth(this.labelWidth);
|
||||
}
|
||||
else if(this.labelPosHorizontal == "right"){
|
||||
this.setWidth(width + this.labelWidth);
|
||||
}
|
||||
}
|
||||
|
||||
if(this.labelHeight){
|
||||
if(this.labelPosVertical == "top"){
|
||||
this.rect.y -= (this.labelHeight);
|
||||
this.setHeight(height + this.labelHeight);
|
||||
}
|
||||
else if(this.labelPosVertical == "center" && this.labelHeight > height){
|
||||
this.rect.y -= (this.labelHeight - height) / 2;
|
||||
this.setHeight(this.labelHeight);
|
||||
}
|
||||
else if(this.labelPosVertical == "bottom"){
|
||||
this.setHeight(height + this.labelHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LNode.prototype.getInclusionTreeDepth = function ()
|
||||
{
|
||||
if (this.inclusionTreeDepth == Integer.MAX_VALUE) {
|
||||
throw "assert failed";
|
||||
}
|
||||
return this.inclusionTreeDepth;
|
||||
};
|
||||
|
||||
LNode.prototype.transform = function (trans)
|
||||
{
|
||||
var left = this.rect.x;
|
||||
|
||||
if (left > LayoutConstants.WORLD_BOUNDARY)
|
||||
{
|
||||
left = LayoutConstants.WORLD_BOUNDARY;
|
||||
}
|
||||
else if (left < -LayoutConstants.WORLD_BOUNDARY)
|
||||
{
|
||||
left = -LayoutConstants.WORLD_BOUNDARY;
|
||||
}
|
||||
|
||||
var top = this.rect.y;
|
||||
|
||||
if (top > LayoutConstants.WORLD_BOUNDARY)
|
||||
{
|
||||
top = LayoutConstants.WORLD_BOUNDARY;
|
||||
}
|
||||
else if (top < -LayoutConstants.WORLD_BOUNDARY)
|
||||
{
|
||||
top = -LayoutConstants.WORLD_BOUNDARY;
|
||||
}
|
||||
|
||||
var leftTop = new PointD(left, top);
|
||||
var vLeftTop = trans.inverseTransformPoint(leftTop);
|
||||
|
||||
this.setLocation(vLeftTop.x, vLeftTop.y);
|
||||
};
|
||||
|
||||
LNode.prototype.getLeft = function ()
|
||||
{
|
||||
return this.rect.x;
|
||||
};
|
||||
|
||||
LNode.prototype.getRight = function ()
|
||||
{
|
||||
return this.rect.x + this.rect.width;
|
||||
};
|
||||
|
||||
LNode.prototype.getTop = function ()
|
||||
{
|
||||
return this.rect.y;
|
||||
};
|
||||
|
||||
LNode.prototype.getBottom = function ()
|
||||
{
|
||||
return this.rect.y + this.rect.height;
|
||||
};
|
||||
|
||||
LNode.prototype.getParent = function ()
|
||||
{
|
||||
if (this.owner == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.owner.getParent();
|
||||
};
|
||||
|
||||
module.exports = LNode;
|
||||
+672
@@ -0,0 +1,672 @@
|
||||
var LayoutConstants = require('./LayoutConstants');
|
||||
var LGraphManager = require('./LGraphManager');
|
||||
var LNode = require('./LNode');
|
||||
var LEdge = require('./LEdge');
|
||||
var LGraph = require('./LGraph');
|
||||
var PointD = require('./util/PointD');
|
||||
var Transform = require('./util/Transform');
|
||||
var Emitter = require('./util/Emitter');
|
||||
|
||||
function Layout(isRemoteUse) {
|
||||
Emitter.call( this );
|
||||
|
||||
//Layout Quality: 0:draft, 1:default, 2:proof
|
||||
this.layoutQuality = LayoutConstants.QUALITY;
|
||||
//Whether layout should create bendpoints as needed or not
|
||||
this.createBendsAsNeeded =
|
||||
LayoutConstants.DEFAULT_CREATE_BENDS_AS_NEEDED;
|
||||
//Whether layout should be incremental or not
|
||||
this.incremental = LayoutConstants.DEFAULT_INCREMENTAL;
|
||||
//Whether we animate from before to after layout node positions
|
||||
this.animationOnLayout =
|
||||
LayoutConstants.DEFAULT_ANIMATION_ON_LAYOUT;
|
||||
//Whether we animate the layout process or not
|
||||
this.animationDuringLayout = LayoutConstants.DEFAULT_ANIMATION_DURING_LAYOUT;
|
||||
//Number iterations that should be done between two successive animations
|
||||
this.animationPeriod = LayoutConstants.DEFAULT_ANIMATION_PERIOD;
|
||||
/**
|
||||
* Whether or not leaf nodes (non-compound nodes) are of uniform sizes. When
|
||||
* they are, both spring and repulsion forces between two leaf nodes can be
|
||||
* calculated without the expensive clipping point calculations, resulting
|
||||
* in major speed-up.
|
||||
*/
|
||||
this.uniformLeafNodeSizes =
|
||||
LayoutConstants.DEFAULT_UNIFORM_LEAF_NODE_SIZES;
|
||||
/**
|
||||
* This is used for creation of bendpoints by using dummy nodes and edges.
|
||||
* Maps an LEdge to its dummy bendpoint path.
|
||||
*/
|
||||
this.edgeToDummyNodes = new Map();
|
||||
this.graphManager = new LGraphManager(this);
|
||||
this.isLayoutFinished = false;
|
||||
this.isSubLayout = false;
|
||||
this.isRemoteUse = false;
|
||||
|
||||
if (isRemoteUse != null) {
|
||||
this.isRemoteUse = isRemoteUse;
|
||||
}
|
||||
}
|
||||
|
||||
Layout.RANDOM_SEED = 1;
|
||||
|
||||
Layout.prototype = Object.create( Emitter.prototype );
|
||||
|
||||
Layout.prototype.getGraphManager = function () {
|
||||
return this.graphManager;
|
||||
};
|
||||
|
||||
Layout.prototype.getAllNodes = function () {
|
||||
return this.graphManager.getAllNodes();
|
||||
};
|
||||
|
||||
Layout.prototype.getAllEdges = function () {
|
||||
return this.graphManager.getAllEdges();
|
||||
};
|
||||
|
||||
Layout.prototype.getAllNodesToApplyGravitation = function () {
|
||||
return this.graphManager.getAllNodesToApplyGravitation();
|
||||
};
|
||||
|
||||
Layout.prototype.newGraphManager = function () {
|
||||
var gm = new LGraphManager(this);
|
||||
this.graphManager = gm;
|
||||
return gm;
|
||||
};
|
||||
|
||||
Layout.prototype.newGraph = function (vGraph)
|
||||
{
|
||||
return new LGraph(null, this.graphManager, vGraph);
|
||||
};
|
||||
|
||||
Layout.prototype.newNode = function (vNode)
|
||||
{
|
||||
return new LNode(this.graphManager, vNode);
|
||||
};
|
||||
|
||||
Layout.prototype.newEdge = function (vEdge)
|
||||
{
|
||||
return new LEdge(null, null, vEdge);
|
||||
};
|
||||
|
||||
Layout.prototype.checkLayoutSuccess = function() {
|
||||
return (this.graphManager.getRoot() == null)
|
||||
|| this.graphManager.getRoot().getNodes().length == 0
|
||||
|| this.graphManager.includesInvalidEdge();
|
||||
};
|
||||
|
||||
Layout.prototype.runLayout = function ()
|
||||
{
|
||||
this.isLayoutFinished = false;
|
||||
|
||||
if (this.tilingPreLayout) {
|
||||
this.tilingPreLayout();
|
||||
}
|
||||
|
||||
this.initParameters();
|
||||
var isLayoutSuccessfull;
|
||||
|
||||
if (this.checkLayoutSuccess())
|
||||
{
|
||||
isLayoutSuccessfull = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
isLayoutSuccessfull = this.layout();
|
||||
}
|
||||
|
||||
if (LayoutConstants.ANIMATE === 'during') {
|
||||
// If this is a 'during' layout animation. Layout is not finished yet.
|
||||
// We need to perform these in index.js when layout is really finished.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isLayoutSuccessfull)
|
||||
{
|
||||
if (!this.isSubLayout)
|
||||
{
|
||||
this.doPostLayout();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.tilingPostLayout) {
|
||||
this.tilingPostLayout();
|
||||
}
|
||||
|
||||
this.isLayoutFinished = true;
|
||||
|
||||
return isLayoutSuccessfull;
|
||||
};
|
||||
|
||||
/**
|
||||
* This method performs the operations required after layout.
|
||||
*/
|
||||
Layout.prototype.doPostLayout = function ()
|
||||
{
|
||||
//assert !isSubLayout : "Should not be called on sub-layout!";
|
||||
// Propagate geometric changes to v-level objects
|
||||
if(!this.incremental){
|
||||
this.transform();
|
||||
}
|
||||
this.update();
|
||||
};
|
||||
|
||||
/**
|
||||
* This method updates the geometry of the target graph according to
|
||||
* calculated layout.
|
||||
*/
|
||||
Layout.prototype.update2 = function () {
|
||||
// update bend points
|
||||
if (this.createBendsAsNeeded)
|
||||
{
|
||||
this.createBendpointsFromDummyNodes();
|
||||
|
||||
// reset all edges, since the topology has changed
|
||||
this.graphManager.resetAllEdges();
|
||||
}
|
||||
|
||||
// perform edge, node and root updates if layout is not called
|
||||
// remotely
|
||||
if (!this.isRemoteUse)
|
||||
{
|
||||
// update all edges
|
||||
var edge;
|
||||
var allEdges = this.graphManager.getAllEdges();
|
||||
for (var i = 0; i < allEdges.length; i++)
|
||||
{
|
||||
edge = allEdges[i];
|
||||
// this.update(edge);
|
||||
}
|
||||
|
||||
// recursively update nodes
|
||||
var node;
|
||||
var nodes = this.graphManager.getRoot().getNodes();
|
||||
for (var i = 0; i < nodes.length; i++)
|
||||
{
|
||||
node = nodes[i];
|
||||
// this.update(node);
|
||||
}
|
||||
|
||||
// update root graph
|
||||
this.update(this.graphManager.getRoot());
|
||||
}
|
||||
};
|
||||
|
||||
Layout.prototype.update = function (obj) {
|
||||
if (obj == null) {
|
||||
this.update2();
|
||||
}
|
||||
else if (obj instanceof LNode) {
|
||||
var node = obj;
|
||||
if (node.getChild() != null)
|
||||
{
|
||||
// since node is compound, recursively update child nodes
|
||||
var nodes = node.getChild().getNodes();
|
||||
for (var i = 0; i < nodes.length; i++)
|
||||
{
|
||||
update(nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// if the l-level node is associated with a v-level graph object,
|
||||
// then it is assumed that the v-level node implements the
|
||||
// interface Updatable.
|
||||
if (node.vGraphObject != null)
|
||||
{
|
||||
// cast to Updatable without any type check
|
||||
var vNode = node.vGraphObject;
|
||||
|
||||
// call the update method of the interface
|
||||
vNode.update(node);
|
||||
}
|
||||
}
|
||||
else if (obj instanceof LEdge) {
|
||||
var edge = obj;
|
||||
// if the l-level edge is associated with a v-level graph object,
|
||||
// then it is assumed that the v-level edge implements the
|
||||
// interface Updatable.
|
||||
|
||||
if (edge.vGraphObject != null)
|
||||
{
|
||||
// cast to Updatable without any type check
|
||||
var vEdge = edge.vGraphObject;
|
||||
|
||||
// call the update method of the interface
|
||||
vEdge.update(edge);
|
||||
}
|
||||
}
|
||||
else if (obj instanceof LGraph) {
|
||||
var graph = obj;
|
||||
// if the l-level graph is associated with a v-level graph object,
|
||||
// then it is assumed that the v-level object implements the
|
||||
// interface Updatable.
|
||||
|
||||
if (graph.vGraphObject != null)
|
||||
{
|
||||
// cast to Updatable without any type check
|
||||
var vGraph = graph.vGraphObject;
|
||||
|
||||
// call the update method of the interface
|
||||
vGraph.update(graph);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is used to set all layout parameters to default values
|
||||
* determined at compile time.
|
||||
*/
|
||||
Layout.prototype.initParameters = function () {
|
||||
if (!this.isSubLayout)
|
||||
{
|
||||
this.layoutQuality = LayoutConstants.QUALITY;
|
||||
this.animationDuringLayout = LayoutConstants.DEFAULT_ANIMATION_DURING_LAYOUT;
|
||||
this.animationPeriod = LayoutConstants.DEFAULT_ANIMATION_PERIOD;
|
||||
this.animationOnLayout = LayoutConstants.DEFAULT_ANIMATION_ON_LAYOUT;
|
||||
this.incremental = LayoutConstants.DEFAULT_INCREMENTAL;
|
||||
this.createBendsAsNeeded = LayoutConstants.DEFAULT_CREATE_BENDS_AS_NEEDED;
|
||||
this.uniformLeafNodeSizes = LayoutConstants.DEFAULT_UNIFORM_LEAF_NODE_SIZES;
|
||||
}
|
||||
|
||||
if (this.animationDuringLayout)
|
||||
{
|
||||
this.animationOnLayout = false;
|
||||
}
|
||||
};
|
||||
|
||||
Layout.prototype.transform = function (newLeftTop) {
|
||||
if (newLeftTop == undefined) {
|
||||
this.transform(new PointD(0, 0));
|
||||
}
|
||||
else {
|
||||
// create a transformation object (from Eclipse to layout). When an
|
||||
// inverse transform is applied, we get upper-left coordinate of the
|
||||
// drawing or the root graph at given input coordinate (some margins
|
||||
// already included in calculation of left-top).
|
||||
|
||||
var trans = new Transform();
|
||||
var leftTop = this.graphManager.getRoot().updateLeftTop();
|
||||
|
||||
if (leftTop != null)
|
||||
{
|
||||
trans.setWorldOrgX(newLeftTop.x);
|
||||
trans.setWorldOrgY(newLeftTop.y);
|
||||
|
||||
trans.setDeviceOrgX(leftTop.x);
|
||||
trans.setDeviceOrgY(leftTop.y);
|
||||
|
||||
var nodes = this.getAllNodes();
|
||||
var node;
|
||||
|
||||
for (var i = 0; i < nodes.length; i++)
|
||||
{
|
||||
node = nodes[i];
|
||||
node.transform(trans);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Layout.prototype.positionNodesRandomly = function (graph) {
|
||||
|
||||
if (graph == undefined) {
|
||||
//assert !this.incremental;
|
||||
this.positionNodesRandomly(this.getGraphManager().getRoot());
|
||||
this.getGraphManager().getRoot().updateBounds(true);
|
||||
}
|
||||
else {
|
||||
var lNode;
|
||||
var childGraph;
|
||||
|
||||
var nodes = graph.getNodes();
|
||||
for (var i = 0; i < nodes.length; i++)
|
||||
{
|
||||
lNode = nodes[i];
|
||||
childGraph = lNode.getChild();
|
||||
|
||||
if (childGraph == null)
|
||||
{
|
||||
lNode.scatter();
|
||||
}
|
||||
else if (childGraph.getNodes().length == 0)
|
||||
{
|
||||
lNode.scatter();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.positionNodesRandomly(childGraph);
|
||||
lNode.updateBounds();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This method returns a list of trees where each tree is represented as a
|
||||
* list of l-nodes. The method returns a list of size 0 when:
|
||||
* - The graph is not flat or
|
||||
* - One of the component(s) of the graph is not a tree.
|
||||
*/
|
||||
Layout.prototype.getFlatForest = function ()
|
||||
{
|
||||
var flatForest = [];
|
||||
var isForest = true;
|
||||
|
||||
// Quick reference for all nodes in the graph manager associated with
|
||||
// this layout. The list should not be changed.
|
||||
var allNodes = this.graphManager.getRoot().getNodes();
|
||||
|
||||
// First be sure that the graph is flat
|
||||
var isFlat = true;
|
||||
|
||||
for (var i = 0; i < allNodes.length; i++)
|
||||
{
|
||||
if (allNodes[i].getChild() != null)
|
||||
{
|
||||
isFlat = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Return empty forest if the graph is not flat.
|
||||
if (!isFlat)
|
||||
{
|
||||
return flatForest;
|
||||
}
|
||||
|
||||
// Run BFS for each component of the graph.
|
||||
|
||||
var visited = new Set();
|
||||
var toBeVisited = [];
|
||||
var parents = new Map();
|
||||
var unProcessedNodes = [];
|
||||
|
||||
unProcessedNodes = unProcessedNodes.concat(allNodes);
|
||||
|
||||
// Each iteration of this loop finds a component of the graph and
|
||||
// decides whether it is a tree or not. If it is a tree, adds it to the
|
||||
// forest and continued with the next component.
|
||||
|
||||
while (unProcessedNodes.length > 0 && isForest)
|
||||
{
|
||||
toBeVisited.push(unProcessedNodes[0]);
|
||||
|
||||
// Start the BFS. Each iteration of this loop visits a node in a
|
||||
// BFS manner.
|
||||
while (toBeVisited.length > 0 && isForest)
|
||||
{
|
||||
//pool operation
|
||||
var currentNode = toBeVisited[0];
|
||||
toBeVisited.splice(0, 1);
|
||||
visited.add(currentNode);
|
||||
|
||||
// Traverse all neighbors of this node
|
||||
var neighborEdges = currentNode.getEdges();
|
||||
|
||||
for (var i = 0; i < neighborEdges.length; i++)
|
||||
{
|
||||
var currentNeighbor =
|
||||
neighborEdges[i].getOtherEnd(currentNode);
|
||||
|
||||
// If BFS is not growing from this neighbor.
|
||||
if (parents.get(currentNode) != currentNeighbor)
|
||||
{
|
||||
// We haven't previously visited this neighbor.
|
||||
if (!visited.has(currentNeighbor))
|
||||
{
|
||||
toBeVisited.push(currentNeighbor);
|
||||
parents.set(currentNeighbor, currentNode);
|
||||
}
|
||||
// Since we have previously visited this neighbor and
|
||||
// this neighbor is not parent of currentNode, given
|
||||
// graph contains a component that is not tree, hence
|
||||
// it is not a forest.
|
||||
else
|
||||
{
|
||||
isForest = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The graph contains a component that is not a tree. Empty
|
||||
// previously found trees. The method will end.
|
||||
if (!isForest)
|
||||
{
|
||||
flatForest = [];
|
||||
}
|
||||
// Save currently visited nodes as a tree in our forest. Reset
|
||||
// visited and parents lists. Continue with the next component of
|
||||
// the graph, if any.
|
||||
else
|
||||
{
|
||||
var temp = [...visited];
|
||||
flatForest.push(temp);
|
||||
//flatForest = flatForest.concat(temp);
|
||||
//unProcessedNodes.removeAll(visited);
|
||||
for (var i = 0; i < temp.length; i++) {
|
||||
var value = temp[i];
|
||||
var index = unProcessedNodes.indexOf(value);
|
||||
if (index > -1) {
|
||||
unProcessedNodes.splice(index, 1);
|
||||
}
|
||||
}
|
||||
visited = new Set();
|
||||
parents = new Map();
|
||||
}
|
||||
}
|
||||
|
||||
return flatForest;
|
||||
};
|
||||
|
||||
/**
|
||||
* This method creates dummy nodes (an l-level node with minimal dimensions)
|
||||
* for the given edge (one per bendpoint). The existing l-level structure
|
||||
* is updated accordingly.
|
||||
*/
|
||||
Layout.prototype.createDummyNodesForBendpoints = function (edge)
|
||||
{
|
||||
var dummyNodes = [];
|
||||
var prev = edge.source;
|
||||
|
||||
var graph = this.graphManager.calcLowestCommonAncestor(edge.source, edge.target);
|
||||
|
||||
for (var i = 0; i < edge.bendpoints.length; i++)
|
||||
{
|
||||
// create new dummy node
|
||||
var dummyNode = this.newNode(null);
|
||||
dummyNode.setRect(new Point(0, 0), new Dimension(1, 1));
|
||||
|
||||
graph.add(dummyNode);
|
||||
|
||||
// create new dummy edge between prev and dummy node
|
||||
var dummyEdge = this.newEdge(null);
|
||||
this.graphManager.add(dummyEdge, prev, dummyNode);
|
||||
|
||||
dummyNodes.add(dummyNode);
|
||||
prev = dummyNode;
|
||||
}
|
||||
|
||||
var dummyEdge = this.newEdge(null);
|
||||
this.graphManager.add(dummyEdge, prev, edge.target);
|
||||
|
||||
this.edgeToDummyNodes.set(edge, dummyNodes);
|
||||
|
||||
// remove real edge from graph manager if it is inter-graph
|
||||
if (edge.isInterGraph())
|
||||
{
|
||||
this.graphManager.remove(edge);
|
||||
}
|
||||
// else, remove the edge from the current graph
|
||||
else
|
||||
{
|
||||
graph.remove(edge);
|
||||
}
|
||||
|
||||
return dummyNodes;
|
||||
};
|
||||
|
||||
/**
|
||||
* This method creates bendpoints for edges from the dummy nodes
|
||||
* at l-level.
|
||||
*/
|
||||
Layout.prototype.createBendpointsFromDummyNodes = function ()
|
||||
{
|
||||
var edges = [];
|
||||
edges = edges.concat(this.graphManager.getAllEdges());
|
||||
edges = [...this.edgeToDummyNodes.keys()].concat(edges);
|
||||
|
||||
for (var k = 0; k < edges.length; k++)
|
||||
{
|
||||
var lEdge = edges[k];
|
||||
|
||||
if (lEdge.bendpoints.length > 0)
|
||||
{
|
||||
var path = this.edgeToDummyNodes.get(lEdge);
|
||||
|
||||
for (var i = 0; i < path.length; i++)
|
||||
{
|
||||
var dummyNode = path[i];
|
||||
var p = new PointD(dummyNode.getCenterX(),
|
||||
dummyNode.getCenterY());
|
||||
|
||||
// update bendpoint's location according to dummy node
|
||||
var ebp = lEdge.bendpoints.get(i);
|
||||
ebp.x = p.x;
|
||||
ebp.y = p.y;
|
||||
|
||||
// remove the dummy node, dummy edges incident with this
|
||||
// dummy node is also removed (within the remove method)
|
||||
dummyNode.getOwner().remove(dummyNode);
|
||||
}
|
||||
|
||||
// add the real edge to graph
|
||||
this.graphManager.add(lEdge, lEdge.source, lEdge.target);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Layout.transform = function (sliderValue, defaultValue, minDiv, maxMul) {
|
||||
if (minDiv != undefined && maxMul != undefined) {
|
||||
var value = defaultValue;
|
||||
|
||||
if (sliderValue <= 50)
|
||||
{
|
||||
var minValue = defaultValue / minDiv;
|
||||
value -= ((defaultValue - minValue) / 50) * (50 - sliderValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
var maxValue = defaultValue * maxMul;
|
||||
value += ((maxValue - defaultValue) / 50) * (sliderValue - 50);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
else {
|
||||
var a, b;
|
||||
|
||||
if (sliderValue <= 50)
|
||||
{
|
||||
a = 9.0 * defaultValue / 500.0;
|
||||
b = defaultValue / 10.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
a = 9.0 * defaultValue / 50.0;
|
||||
b = -8 * defaultValue;
|
||||
}
|
||||
|
||||
return (a * sliderValue + b);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This method finds and returns the center of the given nodes, assuming
|
||||
* that the given nodes form a tree in themselves.
|
||||
*/
|
||||
Layout.findCenterOfTree = function (nodes)
|
||||
{
|
||||
var list = [];
|
||||
list = list.concat(nodes);
|
||||
|
||||
var removedNodes = [];
|
||||
var remainingDegrees = new Map();
|
||||
var foundCenter = false;
|
||||
var centerNode = null;
|
||||
|
||||
if (list.length == 1 || list.length == 2)
|
||||
{
|
||||
foundCenter = true;
|
||||
centerNode = list[0];
|
||||
}
|
||||
|
||||
for (var i = 0; i < list.length; i++)
|
||||
{
|
||||
var node = list[i];
|
||||
var degree = node.getNeighborsList().size;
|
||||
remainingDegrees.set(node, node.getNeighborsList().size);
|
||||
|
||||
if (degree == 1)
|
||||
{
|
||||
removedNodes.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
var tempList = [];
|
||||
tempList = tempList.concat(removedNodes);
|
||||
|
||||
while (!foundCenter)
|
||||
{
|
||||
var tempList2 = [];
|
||||
tempList2 = tempList2.concat(tempList);
|
||||
tempList = [];
|
||||
|
||||
for (var i = 0; i < list.length; i++)
|
||||
{
|
||||
var node = list[i];
|
||||
|
||||
var index = list.indexOf(node);
|
||||
if (index >= 0) {
|
||||
list.splice(index, 1);
|
||||
}
|
||||
|
||||
var neighbours = node.getNeighborsList();
|
||||
|
||||
neighbours.forEach(function(neighbour) {
|
||||
if (removedNodes.indexOf(neighbour) < 0)
|
||||
{
|
||||
var otherDegree = remainingDegrees.get(neighbour);
|
||||
var newDegree = otherDegree - 1;
|
||||
|
||||
if (newDegree == 1)
|
||||
{
|
||||
tempList.push(neighbour);
|
||||
}
|
||||
|
||||
remainingDegrees.set(neighbour, newDegree);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removedNodes = removedNodes.concat(tempList);
|
||||
|
||||
if (list.length == 1 || list.length == 2)
|
||||
{
|
||||
foundCenter = true;
|
||||
centerNode = list[0];
|
||||
}
|
||||
}
|
||||
|
||||
return centerNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* During the coarsening process, this layout may be referenced by two graph managers
|
||||
* this setter function grants access to change the currently being used graph manager
|
||||
*/
|
||||
Layout.prototype.setGraphManager = function (gm)
|
||||
{
|
||||
this.graphManager = gm;
|
||||
};
|
||||
|
||||
module.exports = Layout;
|
||||
Generated
Vendored
+70
@@ -0,0 +1,70 @@
|
||||
function LayoutConstants() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout Quality: 0:draft, 1:default, 2:proof
|
||||
*/
|
||||
LayoutConstants.QUALITY = 1;
|
||||
|
||||
/**
|
||||
* Default parameters
|
||||
*/
|
||||
LayoutConstants.DEFAULT_CREATE_BENDS_AS_NEEDED = false;
|
||||
LayoutConstants.DEFAULT_INCREMENTAL = false;
|
||||
LayoutConstants.DEFAULT_ANIMATION_ON_LAYOUT = true;
|
||||
LayoutConstants.DEFAULT_ANIMATION_DURING_LAYOUT = false;
|
||||
LayoutConstants.DEFAULT_ANIMATION_PERIOD = 50;
|
||||
LayoutConstants.DEFAULT_UNIFORM_LEAF_NODE_SIZES = false;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Section: General other constants
|
||||
// -----------------------------------------------------------------------------
|
||||
/*
|
||||
* Margins of a graph to be applied on bouding rectangle of its contents. We
|
||||
* assume margins on all four sides to be uniform.
|
||||
*/
|
||||
LayoutConstants.DEFAULT_GRAPH_MARGIN = 15;
|
||||
|
||||
/*
|
||||
* Whether to consider labels in node dimensions or not
|
||||
*/
|
||||
LayoutConstants.NODE_DIMENSIONS_INCLUDE_LABELS = false;
|
||||
|
||||
/*
|
||||
* Default dimension of a non-compound node.
|
||||
*/
|
||||
LayoutConstants.SIMPLE_NODE_SIZE = 40;
|
||||
|
||||
/*
|
||||
* Default dimension of a non-compound node.
|
||||
*/
|
||||
LayoutConstants.SIMPLE_NODE_HALF_SIZE = LayoutConstants.SIMPLE_NODE_SIZE / 2;
|
||||
|
||||
/*
|
||||
* Empty compound node size. When a compound node is empty, its both
|
||||
* dimensions should be of this value.
|
||||
*/
|
||||
LayoutConstants.EMPTY_COMPOUND_NODE_SIZE = 40;
|
||||
|
||||
/*
|
||||
* Minimum length that an edge should take during layout
|
||||
*/
|
||||
LayoutConstants.MIN_EDGE_LENGTH = 1;
|
||||
|
||||
/*
|
||||
* World boundaries that layout operates on
|
||||
*/
|
||||
LayoutConstants.WORLD_BOUNDARY = 1000000;
|
||||
|
||||
/*
|
||||
* World boundaries that random positioning can be performed with
|
||||
*/
|
||||
LayoutConstants.INITIAL_WORLD_BOUNDARY = LayoutConstants.WORLD_BOUNDARY / 1000;
|
||||
|
||||
/*
|
||||
* Coordinates of the world center
|
||||
*/
|
||||
LayoutConstants.WORLD_CENTER_X = 1200;
|
||||
LayoutConstants.WORLD_CENTER_Y = 900;
|
||||
|
||||
module.exports = LayoutConstants;
|
||||
+529
@@ -0,0 +1,529 @@
|
||||
var Layout = require('../Layout');
|
||||
var FDLayoutConstants = require('./FDLayoutConstants');
|
||||
var LayoutConstants = require('../LayoutConstants');
|
||||
var IGeometry = require('../util/IGeometry');
|
||||
var IMath = require('../util/IMath');
|
||||
|
||||
function FDLayout() {
|
||||
Layout.call(this);
|
||||
|
||||
this.useSmartIdealEdgeLengthCalculation = FDLayoutConstants.DEFAULT_USE_SMART_IDEAL_EDGE_LENGTH_CALCULATION;
|
||||
this.gravityConstant = FDLayoutConstants.DEFAULT_GRAVITY_STRENGTH;
|
||||
this.compoundGravityConstant = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH;
|
||||
this.gravityRangeFactor = FDLayoutConstants.DEFAULT_GRAVITY_RANGE_FACTOR;
|
||||
this.compoundGravityRangeFactor = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR;
|
||||
this.displacementThresholdPerNode = (3.0 * FDLayoutConstants.DEFAULT_EDGE_LENGTH) / 100;
|
||||
this.coolingFactor = FDLayoutConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL;
|
||||
this.initialCoolingFactor = FDLayoutConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL;
|
||||
this.totalDisplacement = 0.0;
|
||||
this.oldTotalDisplacement = 0.0;
|
||||
this.maxIterations = FDLayoutConstants.MAX_ITERATIONS;
|
||||
}
|
||||
|
||||
FDLayout.prototype = Object.create(Layout.prototype);
|
||||
|
||||
for (var prop in Layout) {
|
||||
FDLayout[prop] = Layout[prop];
|
||||
}
|
||||
|
||||
FDLayout.prototype.initParameters = function () {
|
||||
Layout.prototype.initParameters.call(this, arguments);
|
||||
|
||||
this.totalIterations = 0;
|
||||
this.notAnimatedIterations = 0;
|
||||
|
||||
this.useFRGridVariant = FDLayoutConstants.DEFAULT_USE_SMART_REPULSION_RANGE_CALCULATION;
|
||||
|
||||
this.grid = [];
|
||||
};
|
||||
|
||||
FDLayout.prototype.calcIdealEdgeLengths = function () {
|
||||
var edge;
|
||||
var originalIdealLength;
|
||||
var lcaDepth;
|
||||
var source;
|
||||
var target;
|
||||
var sizeOfSourceInLca;
|
||||
var sizeOfTargetInLca;
|
||||
|
||||
var allEdges = this.getGraphManager().getAllEdges();
|
||||
for (var i = 0; i < allEdges.length; i++)
|
||||
{
|
||||
edge = allEdges[i];
|
||||
|
||||
originalIdealLength = edge.idealLength;
|
||||
|
||||
if (edge.isInterGraph)
|
||||
{
|
||||
source = edge.getSource();
|
||||
target = edge.getTarget();
|
||||
|
||||
sizeOfSourceInLca = edge.getSourceInLca().getEstimatedSize();
|
||||
sizeOfTargetInLca = edge.getTargetInLca().getEstimatedSize();
|
||||
|
||||
if (this.useSmartIdealEdgeLengthCalculation)
|
||||
{
|
||||
edge.idealLength += sizeOfSourceInLca + sizeOfTargetInLca -
|
||||
2 * LayoutConstants.SIMPLE_NODE_SIZE;
|
||||
}
|
||||
|
||||
lcaDepth = edge.getLca().getInclusionTreeDepth();
|
||||
|
||||
edge.idealLength += originalIdealLength *
|
||||
FDLayoutConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR *
|
||||
(source.getInclusionTreeDepth() +
|
||||
target.getInclusionTreeDepth() - 2 * lcaDepth);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
FDLayout.prototype.initSpringEmbedder = function () {
|
||||
|
||||
var s = this.getAllNodes().length;
|
||||
if (this.incremental) {
|
||||
if(s > FDLayoutConstants.ADAPTATION_LOWER_NODE_LIMIT){
|
||||
this.coolingFactor = Math.max(this.coolingFactor*FDLayoutConstants.COOLING_ADAPTATION_FACTOR, this.coolingFactor -
|
||||
(s-FDLayoutConstants.ADAPTATION_LOWER_NODE_LIMIT)/(FDLayoutConstants.ADAPTATION_UPPER_NODE_LIMIT-FDLayoutConstants.ADAPTATION_LOWER_NODE_LIMIT)*this.coolingFactor*(1-FDLayoutConstants.COOLING_ADAPTATION_FACTOR));
|
||||
}
|
||||
this.maxNodeDisplacement = FDLayoutConstants.MAX_NODE_DISPLACEMENT_INCREMENTAL;
|
||||
}
|
||||
else {
|
||||
if(s > FDLayoutConstants.ADAPTATION_LOWER_NODE_LIMIT){
|
||||
this.coolingFactor = Math.max(FDLayoutConstants.COOLING_ADAPTATION_FACTOR, 1.0 -
|
||||
(s-FDLayoutConstants.ADAPTATION_LOWER_NODE_LIMIT)/(FDLayoutConstants.ADAPTATION_UPPER_NODE_LIMIT-FDLayoutConstants.ADAPTATION_LOWER_NODE_LIMIT)*(1-FDLayoutConstants.COOLING_ADAPTATION_FACTOR));
|
||||
}
|
||||
else {
|
||||
this.coolingFactor = 1.0;
|
||||
}
|
||||
this.initialCoolingFactor = this.coolingFactor;
|
||||
this.maxNodeDisplacement = FDLayoutConstants.MAX_NODE_DISPLACEMENT;
|
||||
}
|
||||
|
||||
this.maxIterations =
|
||||
Math.max(this.getAllNodes().length * 5, this.maxIterations);
|
||||
|
||||
// Reassign this attribute by using new constant value
|
||||
this.displacementThresholdPerNode = (3.0 * FDLayoutConstants.DEFAULT_EDGE_LENGTH) / 100;
|
||||
this.totalDisplacementThreshold = this.displacementThresholdPerNode * this.getAllNodes().length;
|
||||
|
||||
this.repulsionRange = this.calcRepulsionRange();
|
||||
};
|
||||
|
||||
FDLayout.prototype.calcSpringForces = function () {
|
||||
var lEdges = this.getAllEdges();
|
||||
var edge;
|
||||
|
||||
for (var i = 0; i < lEdges.length; i++)
|
||||
{
|
||||
edge = lEdges[i];
|
||||
|
||||
this.calcSpringForce(edge, edge.idealLength);
|
||||
}
|
||||
};
|
||||
|
||||
FDLayout.prototype.calcRepulsionForces = function (gridUpdateAllowed = true, forceToNodeSurroundingUpdate = false) {
|
||||
var i, j;
|
||||
var nodeA, nodeB;
|
||||
var lNodes = this.getAllNodes();
|
||||
var processedNodeSet;
|
||||
|
||||
if (this.useFRGridVariant)
|
||||
{
|
||||
if ((this.totalIterations % FDLayoutConstants.GRID_CALCULATION_CHECK_PERIOD == 1 && gridUpdateAllowed))
|
||||
{
|
||||
this.updateGrid();
|
||||
}
|
||||
|
||||
processedNodeSet = new Set();
|
||||
|
||||
// calculate repulsion forces between each nodes and its surrounding
|
||||
for (i = 0; i < lNodes.length; i++)
|
||||
{
|
||||
nodeA = lNodes[i];
|
||||
this.calculateRepulsionForceOfANode(nodeA, processedNodeSet, gridUpdateAllowed, forceToNodeSurroundingUpdate);
|
||||
processedNodeSet.add(nodeA);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < lNodes.length; i++)
|
||||
{
|
||||
nodeA = lNodes[i];
|
||||
|
||||
for (j = i + 1; j < lNodes.length; j++)
|
||||
{
|
||||
nodeB = lNodes[j];
|
||||
|
||||
// If both nodes are not members of the same graph, skip.
|
||||
if (nodeA.getOwner() != nodeB.getOwner())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
this.calcRepulsionForce(nodeA, nodeB);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
FDLayout.prototype.calcGravitationalForces = function () {
|
||||
var node;
|
||||
var lNodes = this.getAllNodesToApplyGravitation();
|
||||
|
||||
for (var i = 0; i < lNodes.length; i++)
|
||||
{
|
||||
node = lNodes[i];
|
||||
this.calcGravitationalForce(node);
|
||||
}
|
||||
};
|
||||
|
||||
FDLayout.prototype.moveNodes = function () {
|
||||
var lNodes = this.getAllNodes();
|
||||
var node;
|
||||
|
||||
for (var i = 0; i < lNodes.length; i++)
|
||||
{
|
||||
node = lNodes[i];
|
||||
node.move();
|
||||
}
|
||||
}
|
||||
|
||||
FDLayout.prototype.calcSpringForce = function (edge, idealLength) {
|
||||
var sourceNode = edge.getSource();
|
||||
var targetNode = edge.getTarget();
|
||||
|
||||
var length;
|
||||
var springForce;
|
||||
var springForceX;
|
||||
var springForceY;
|
||||
|
||||
// Update edge length
|
||||
if (this.uniformLeafNodeSizes &&
|
||||
sourceNode.getChild() == null && targetNode.getChild() == null)
|
||||
{
|
||||
edge.updateLengthSimple();
|
||||
}
|
||||
else
|
||||
{
|
||||
edge.updateLength();
|
||||
|
||||
if (edge.isOverlapingSourceAndTarget)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
length = edge.getLength();
|
||||
|
||||
if(length == 0)
|
||||
return;
|
||||
|
||||
// Calculate spring forces
|
||||
springForce = edge.edgeElasticity * (length - idealLength);
|
||||
|
||||
// Project force onto x and y axes
|
||||
springForceX = springForce * (edge.lengthX / length);
|
||||
springForceY = springForce * (edge.lengthY / length);
|
||||
|
||||
// Apply forces on the end nodes
|
||||
sourceNode.springForceX += springForceX;
|
||||
sourceNode.springForceY += springForceY;
|
||||
targetNode.springForceX -= springForceX;
|
||||
targetNode.springForceY -= springForceY;
|
||||
};
|
||||
|
||||
FDLayout.prototype.calcRepulsionForce = function (nodeA, nodeB) {
|
||||
var rectA = nodeA.getRect();
|
||||
var rectB = nodeB.getRect();
|
||||
var overlapAmount = new Array(2);
|
||||
var clipPoints = new Array(4);
|
||||
var distanceX;
|
||||
var distanceY;
|
||||
var distanceSquared;
|
||||
var distance;
|
||||
var repulsionForce;
|
||||
var repulsionForceX;
|
||||
var repulsionForceY;
|
||||
|
||||
if (rectA.intersects(rectB))// two nodes overlap
|
||||
{
|
||||
// calculate separation amount in x and y directions
|
||||
IGeometry.calcSeparationAmount(rectA,
|
||||
rectB,
|
||||
overlapAmount,
|
||||
FDLayoutConstants.DEFAULT_EDGE_LENGTH / 2.0);
|
||||
|
||||
repulsionForceX = 2 * overlapAmount[0];
|
||||
repulsionForceY = 2 * overlapAmount[1];
|
||||
|
||||
var childrenConstant = nodeA.noOfChildren * nodeB.noOfChildren / (nodeA.noOfChildren + nodeB.noOfChildren);
|
||||
|
||||
// Apply forces on the two nodes
|
||||
nodeA.repulsionForceX -= childrenConstant * repulsionForceX;
|
||||
nodeA.repulsionForceY -= childrenConstant * repulsionForceY;
|
||||
nodeB.repulsionForceX += childrenConstant * repulsionForceX;
|
||||
nodeB.repulsionForceY += childrenConstant * repulsionForceY;
|
||||
}
|
||||
else// no overlap
|
||||
{
|
||||
// calculate distance
|
||||
|
||||
if (this.uniformLeafNodeSizes &&
|
||||
nodeA.getChild() == null && nodeB.getChild() == null)// simply base repulsion on distance of node centers
|
||||
{
|
||||
distanceX = rectB.getCenterX() - rectA.getCenterX();
|
||||
distanceY = rectB.getCenterY() - rectA.getCenterY();
|
||||
}
|
||||
else// use clipping points
|
||||
{
|
||||
IGeometry.getIntersection(rectA, rectB, clipPoints);
|
||||
|
||||
distanceX = clipPoints[2] - clipPoints[0];
|
||||
distanceY = clipPoints[3] - clipPoints[1];
|
||||
}
|
||||
|
||||
// No repulsion range. FR grid variant should take care of this.
|
||||
if (Math.abs(distanceX) < FDLayoutConstants.MIN_REPULSION_DIST)
|
||||
{
|
||||
distanceX = IMath.sign(distanceX) *
|
||||
FDLayoutConstants.MIN_REPULSION_DIST;
|
||||
}
|
||||
|
||||
if (Math.abs(distanceY) < FDLayoutConstants.MIN_REPULSION_DIST)
|
||||
{
|
||||
distanceY = IMath.sign(distanceY) *
|
||||
FDLayoutConstants.MIN_REPULSION_DIST;
|
||||
}
|
||||
|
||||
distanceSquared = distanceX * distanceX + distanceY * distanceY;
|
||||
distance = Math.sqrt(distanceSquared);
|
||||
|
||||
// Here we use half of the nodes' repulsion values for backward compatibility
|
||||
repulsionForce = (nodeA.nodeRepulsion / 2 + nodeB.nodeRepulsion / 2) * nodeA.noOfChildren * nodeB.noOfChildren / distanceSquared;
|
||||
|
||||
// Project force onto x and y axes
|
||||
repulsionForceX = repulsionForce * distanceX / distance;
|
||||
repulsionForceY = repulsionForce * distanceY / distance;
|
||||
|
||||
// Apply forces on the two nodes
|
||||
nodeA.repulsionForceX -= repulsionForceX;
|
||||
nodeA.repulsionForceY -= repulsionForceY;
|
||||
nodeB.repulsionForceX += repulsionForceX;
|
||||
nodeB.repulsionForceY += repulsionForceY;
|
||||
}
|
||||
};
|
||||
|
||||
FDLayout.prototype.calcGravitationalForce = function (node) {
|
||||
var ownerGraph;
|
||||
var ownerCenterX;
|
||||
var ownerCenterY;
|
||||
var distanceX;
|
||||
var distanceY;
|
||||
var absDistanceX;
|
||||
var absDistanceY;
|
||||
var estimatedSize;
|
||||
ownerGraph = node.getOwner();
|
||||
|
||||
ownerCenterX = (ownerGraph.getRight() + ownerGraph.getLeft()) / 2;
|
||||
ownerCenterY = (ownerGraph.getTop() + ownerGraph.getBottom()) / 2;
|
||||
distanceX = node.getCenterX() - ownerCenterX;
|
||||
distanceY = node.getCenterY() - ownerCenterY;
|
||||
absDistanceX = Math.abs(distanceX) + node.getWidth() / 2;
|
||||
absDistanceY = Math.abs(distanceY) + node.getHeight() / 2;
|
||||
|
||||
if (node.getOwner() == this.graphManager.getRoot())// in the root graph
|
||||
{
|
||||
estimatedSize = ownerGraph.getEstimatedSize() * this.gravityRangeFactor;
|
||||
|
||||
if (absDistanceX > estimatedSize || absDistanceY > estimatedSize)
|
||||
{
|
||||
node.gravitationForceX = -this.gravityConstant * distanceX;
|
||||
node.gravitationForceY = -this.gravityConstant * distanceY;
|
||||
}
|
||||
}
|
||||
else// inside a compound
|
||||
{
|
||||
estimatedSize = ownerGraph.getEstimatedSize() * this.compoundGravityRangeFactor;
|
||||
|
||||
if (absDistanceX > estimatedSize || absDistanceY > estimatedSize)
|
||||
{
|
||||
node.gravitationForceX = -this.gravityConstant * distanceX *
|
||||
this.compoundGravityConstant;
|
||||
node.gravitationForceY = -this.gravityConstant * distanceY *
|
||||
this.compoundGravityConstant;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
FDLayout.prototype.isConverged = function () {
|
||||
var converged;
|
||||
var oscilating = false;
|
||||
|
||||
if (this.totalIterations > this.maxIterations / 3)
|
||||
{
|
||||
oscilating =
|
||||
Math.abs(this.totalDisplacement - this.oldTotalDisplacement) < 2;
|
||||
}
|
||||
|
||||
converged = this.totalDisplacement < this.totalDisplacementThreshold;
|
||||
|
||||
this.oldTotalDisplacement = this.totalDisplacement;
|
||||
|
||||
return converged || oscilating;
|
||||
};
|
||||
|
||||
FDLayout.prototype.animate = function () {
|
||||
if (this.animationDuringLayout && !this.isSubLayout)
|
||||
{
|
||||
if (this.notAnimatedIterations == this.animationPeriod)
|
||||
{
|
||||
this.update();
|
||||
this.notAnimatedIterations = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.notAnimatedIterations++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//This method calculates the number of children (weight) for all nodes
|
||||
FDLayout.prototype.calcNoOfChildrenForAllNodes = function ()
|
||||
{
|
||||
var node;
|
||||
var allNodes = this.graphManager.getAllNodes();
|
||||
|
||||
for(var i = 0; i < allNodes.length; i++)
|
||||
{
|
||||
node = allNodes[i];
|
||||
node.noOfChildren = node.getNoOfChildren();
|
||||
}
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Section: FR-Grid Variant Repulsion Force Calculation
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
FDLayout.prototype.calcGrid = function (graph){
|
||||
|
||||
var sizeX = 0;
|
||||
var sizeY = 0;
|
||||
|
||||
sizeX = parseInt(Math.ceil((graph.getRight() - graph.getLeft()) / this.repulsionRange));
|
||||
sizeY = parseInt(Math.ceil((graph.getBottom() - graph.getTop()) / this.repulsionRange));
|
||||
|
||||
var grid = new Array(sizeX);
|
||||
|
||||
for(var i = 0; i < sizeX; i++){
|
||||
grid[i] = new Array(sizeY);
|
||||
}
|
||||
|
||||
for(var i = 0; i < sizeX; i++){
|
||||
for(var j = 0; j < sizeY; j++){
|
||||
grid[i][j] = new Array();
|
||||
}
|
||||
}
|
||||
|
||||
return grid;
|
||||
};
|
||||
|
||||
FDLayout.prototype.addNodeToGrid = function (v, left, top){
|
||||
|
||||
var startX = 0;
|
||||
var finishX = 0;
|
||||
var startY = 0;
|
||||
var finishY = 0;
|
||||
|
||||
startX = parseInt(Math.floor((v.getRect().x - left) / this.repulsionRange));
|
||||
finishX = parseInt(Math.floor((v.getRect().width + v.getRect().x - left) / this.repulsionRange));
|
||||
startY = parseInt(Math.floor((v.getRect().y - top) / this.repulsionRange));
|
||||
finishY = parseInt(Math.floor((v.getRect().height + v.getRect().y - top) / this.repulsionRange));
|
||||
|
||||
for (var i = startX; i <= finishX; i++)
|
||||
{
|
||||
for (var j = startY; j <= finishY; j++)
|
||||
{
|
||||
this.grid[i][j].push(v);
|
||||
v.setGridCoordinates(startX, finishX, startY, finishY);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
FDLayout.prototype.updateGrid = function() {
|
||||
var i;
|
||||
var nodeA;
|
||||
var lNodes = this.getAllNodes();
|
||||
|
||||
this.grid = this.calcGrid(this.graphManager.getRoot());
|
||||
|
||||
// put all nodes to proper grid cells
|
||||
for (i = 0; i < lNodes.length; i++)
|
||||
{
|
||||
nodeA = lNodes[i];
|
||||
this.addNodeToGrid(nodeA, this.graphManager.getRoot().getLeft(), this.graphManager.getRoot().getTop());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
FDLayout.prototype.calculateRepulsionForceOfANode = function (nodeA, processedNodeSet, gridUpdateAllowed, forceToNodeSurroundingUpdate){
|
||||
|
||||
if ((this.totalIterations % FDLayoutConstants.GRID_CALCULATION_CHECK_PERIOD == 1 && gridUpdateAllowed) || forceToNodeSurroundingUpdate)
|
||||
{
|
||||
var surrounding = new Set();
|
||||
nodeA.surrounding = new Array();
|
||||
var nodeB;
|
||||
var grid = this.grid;
|
||||
|
||||
for (var i = (nodeA.startX - 1); i < (nodeA.finishX + 2); i++)
|
||||
{
|
||||
for (var j = (nodeA.startY - 1); j < (nodeA.finishY + 2); j++)
|
||||
{
|
||||
if (!((i < 0) || (j < 0) || (i >= grid.length) || (j >= grid[0].length)))
|
||||
{
|
||||
for (var k = 0; k < grid[i][j].length; k++) {
|
||||
nodeB = grid[i][j][k];
|
||||
|
||||
// If both nodes are not members of the same graph,
|
||||
// or both nodes are the same, skip.
|
||||
if ((nodeA.getOwner() != nodeB.getOwner()) || (nodeA == nodeB))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if the repulsion force between
|
||||
// nodeA and nodeB has already been calculated
|
||||
if (!processedNodeSet.has(nodeB) && !surrounding.has(nodeB))
|
||||
{
|
||||
var distanceX = Math.abs(nodeA.getCenterX()-nodeB.getCenterX()) -
|
||||
((nodeA.getWidth()/2) + (nodeB.getWidth()/2));
|
||||
var distanceY = Math.abs(nodeA.getCenterY()-nodeB.getCenterY()) -
|
||||
((nodeA.getHeight()/2) + (nodeB.getHeight()/2));
|
||||
|
||||
// if the distance between nodeA and nodeB
|
||||
// is less then calculation range
|
||||
if ((distanceX <= this.repulsionRange) && (distanceY <= this.repulsionRange))
|
||||
{
|
||||
//then add nodeB to surrounding of nodeA
|
||||
surrounding.add(nodeB);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodeA.surrounding = [...surrounding];
|
||||
|
||||
}
|
||||
for (i = 0; i < nodeA.surrounding.length; i++)
|
||||
{
|
||||
this.calcRepulsionForce(nodeA, nodeA.surrounding[i]);
|
||||
}
|
||||
};
|
||||
|
||||
FDLayout.prototype.calcRepulsionRange = function () {
|
||||
return 0.0;
|
||||
};
|
||||
|
||||
module.exports = FDLayout;
|
||||
Generated
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
var LayoutConstants = require('../LayoutConstants');
|
||||
|
||||
function FDLayoutConstants() {
|
||||
}
|
||||
|
||||
//FDLayoutConstants inherits static props in LayoutConstants
|
||||
for (var prop in LayoutConstants) {
|
||||
FDLayoutConstants[prop] = LayoutConstants[prop];
|
||||
}
|
||||
|
||||
FDLayoutConstants.MAX_ITERATIONS = 2500;
|
||||
|
||||
FDLayoutConstants.DEFAULT_EDGE_LENGTH = 50;
|
||||
FDLayoutConstants.DEFAULT_SPRING_STRENGTH = 0.45;
|
||||
FDLayoutConstants.DEFAULT_REPULSION_STRENGTH = 4500.0;
|
||||
FDLayoutConstants.DEFAULT_GRAVITY_STRENGTH = 0.4;
|
||||
FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH = 1.0;
|
||||
FDLayoutConstants.DEFAULT_GRAVITY_RANGE_FACTOR = 3.8;
|
||||
FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR = 1.5;
|
||||
FDLayoutConstants.DEFAULT_USE_SMART_IDEAL_EDGE_LENGTH_CALCULATION = true;
|
||||
FDLayoutConstants.DEFAULT_USE_SMART_REPULSION_RANGE_CALCULATION = true;
|
||||
FDLayoutConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL = 0.3;
|
||||
FDLayoutConstants.COOLING_ADAPTATION_FACTOR = 0.33;
|
||||
FDLayoutConstants.ADAPTATION_LOWER_NODE_LIMIT = 1000;
|
||||
FDLayoutConstants.ADAPTATION_UPPER_NODE_LIMIT = 5000;
|
||||
FDLayoutConstants.MAX_NODE_DISPLACEMENT_INCREMENTAL = 100.0;
|
||||
FDLayoutConstants.MAX_NODE_DISPLACEMENT = FDLayoutConstants.MAX_NODE_DISPLACEMENT_INCREMENTAL * 3;
|
||||
FDLayoutConstants.MIN_REPULSION_DIST = FDLayoutConstants.DEFAULT_EDGE_LENGTH / 10.0;
|
||||
FDLayoutConstants.CONVERGENCE_CHECK_PERIOD = 100;
|
||||
FDLayoutConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR = 0.1;
|
||||
FDLayoutConstants.MIN_EDGE_LENGTH = 1;
|
||||
FDLayoutConstants.GRID_CALCULATION_CHECK_PERIOD = 10;
|
||||
|
||||
module.exports = FDLayoutConstants;
|
||||
Generated
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
var LEdge = require('../LEdge');
|
||||
var FDLayoutConstants = require('./FDLayoutConstants');
|
||||
|
||||
function FDLayoutEdge(source, target, vEdge) {
|
||||
LEdge.call(this, source, target, vEdge);
|
||||
|
||||
// Ideal length and elasticity value for this edge
|
||||
this.idealLength = FDLayoutConstants.DEFAULT_EDGE_LENGTH;
|
||||
this.edgeElasticity = FDLayoutConstants.DEFAULT_SPRING_STRENGTH;
|
||||
}
|
||||
|
||||
FDLayoutEdge.prototype = Object.create(LEdge.prototype);
|
||||
|
||||
for (var prop in LEdge) {
|
||||
FDLayoutEdge[prop] = LEdge[prop];
|
||||
}
|
||||
|
||||
module.exports = FDLayoutEdge;
|
||||
Generated
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
var LNode = require('../LNode');
|
||||
var FDLayoutConstants = require('./FDLayoutConstants');
|
||||
|
||||
function FDLayoutNode(gm, loc, size, vNode) {
|
||||
// alternative constructor is handled inside LNode
|
||||
LNode.call(this, gm, loc, size, vNode);
|
||||
|
||||
// Repulsion value of this node
|
||||
this.nodeRepulsion = FDLayoutConstants.DEFAULT_REPULSION_STRENGTH;
|
||||
|
||||
//Spring, repulsion and gravitational forces acting on this node
|
||||
this.springForceX = 0;
|
||||
this.springForceY = 0;
|
||||
this.repulsionForceX = 0;
|
||||
this.repulsionForceY = 0;
|
||||
this.gravitationForceX = 0;
|
||||
this.gravitationForceY = 0;
|
||||
//Amount by which this node is to be moved in this iteration
|
||||
this.displacementX = 0;
|
||||
this.displacementY = 0;
|
||||
|
||||
//Start and finish grid coordinates that this node is fallen into
|
||||
this.startX = 0;
|
||||
this.finishX = 0;
|
||||
this.startY = 0;
|
||||
this.finishY = 0;
|
||||
|
||||
//Geometric neighbors of this node
|
||||
this.surrounding = [];
|
||||
}
|
||||
|
||||
FDLayoutNode.prototype = Object.create(LNode.prototype);
|
||||
|
||||
for (var prop in LNode) {
|
||||
FDLayoutNode[prop] = LNode[prop];
|
||||
}
|
||||
|
||||
FDLayoutNode.prototype.setGridCoordinates = function (_startX, _finishX, _startY, _finishY)
|
||||
{
|
||||
this.startX = _startX;
|
||||
this.finishX = _finishX;
|
||||
this.startY = _startY;
|
||||
this.finishY = _finishY;
|
||||
|
||||
};
|
||||
|
||||
module.exports = FDLayoutNode;
|
||||
Generated
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
function DimensionD(width, height) {
|
||||
this.width = 0;
|
||||
this.height = 0;
|
||||
if (width !== null && height !== null) {
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
}
|
||||
}
|
||||
|
||||
DimensionD.prototype.getWidth = function ()
|
||||
{
|
||||
return this.width;
|
||||
};
|
||||
|
||||
DimensionD.prototype.setWidth = function (width)
|
||||
{
|
||||
this.width = width;
|
||||
};
|
||||
|
||||
DimensionD.prototype.getHeight = function ()
|
||||
{
|
||||
return this.height;
|
||||
};
|
||||
|
||||
DimensionD.prototype.setHeight = function (height)
|
||||
{
|
||||
this.height = height;
|
||||
};
|
||||
|
||||
module.exports = DimensionD;
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
function Emitter(){
|
||||
this.listeners = [];
|
||||
}
|
||||
|
||||
var p = Emitter.prototype;
|
||||
|
||||
p.addListener = function( event, callback ){
|
||||
this.listeners.push({
|
||||
event: event,
|
||||
callback: callback
|
||||
});
|
||||
};
|
||||
|
||||
p.removeListener = function( event, callback ){
|
||||
for( var i = this.listeners.length; i >= 0; i-- ){
|
||||
var l = this.listeners[i];
|
||||
|
||||
if( l.event === event && l.callback === callback ){
|
||||
this.listeners.splice( i, 1 );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
p.emit = function( event, data ){
|
||||
for( var i = 0; i < this.listeners.length; i++ ){
|
||||
var l = this.listeners[i];
|
||||
|
||||
if( event === l.event ){
|
||||
l.callback( data );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Emitter;
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
var UniqueIDGeneretor = require('./UniqueIDGeneretor');
|
||||
|
||||
function HashMap() {
|
||||
this.map = {};
|
||||
this.keys = [];
|
||||
}
|
||||
|
||||
HashMap.prototype.put = function (key, value) {
|
||||
var theId = UniqueIDGeneretor.createID(key);
|
||||
if (!this.contains(theId)) {
|
||||
this.map[theId] = value;
|
||||
this.keys.push(key);
|
||||
}
|
||||
};
|
||||
|
||||
HashMap.prototype.contains = function (key) {
|
||||
var theId = UniqueIDGeneretor.createID(key);
|
||||
return this.map[key] != null;
|
||||
};
|
||||
|
||||
HashMap.prototype.get = function (key) {
|
||||
var theId = UniqueIDGeneretor.createID(key);
|
||||
return this.map[theId];
|
||||
};
|
||||
|
||||
HashMap.prototype.keySet = function () {
|
||||
return this.keys;
|
||||
};
|
||||
|
||||
module.exports = HashMap;
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
var UniqueIDGeneretor = require('./UniqueIDGeneretor');
|
||||
|
||||
function HashSet() {
|
||||
this.set = {};
|
||||
}
|
||||
;
|
||||
|
||||
HashSet.prototype.add = function (obj) {
|
||||
var theId = UniqueIDGeneretor.createID(obj);
|
||||
if (!this.contains(theId))
|
||||
this.set[theId] = obj;
|
||||
};
|
||||
|
||||
HashSet.prototype.remove = function (obj) {
|
||||
delete this.set[UniqueIDGeneretor.createID(obj)];
|
||||
};
|
||||
|
||||
HashSet.prototype.clear = function () {
|
||||
this.set = {};
|
||||
};
|
||||
|
||||
HashSet.prototype.contains = function (obj) {
|
||||
return this.set[UniqueIDGeneretor.createID(obj)] == obj;
|
||||
};
|
||||
|
||||
HashSet.prototype.isEmpty = function () {
|
||||
return this.size() === 0;
|
||||
};
|
||||
|
||||
HashSet.prototype.size = function () {
|
||||
return Object.keys(this.set).length;
|
||||
};
|
||||
|
||||
//concats this.set to the given list
|
||||
HashSet.prototype.addAllTo = function (list) {
|
||||
var keys = Object.keys(this.set);
|
||||
var length = keys.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
list.push(this.set[keys[i]]);
|
||||
}
|
||||
};
|
||||
|
||||
HashSet.prototype.size = function () {
|
||||
return Object.keys(this.set).length;
|
||||
};
|
||||
|
||||
HashSet.prototype.addAll = function (list) {
|
||||
var s = list.length;
|
||||
for (var i = 0; i < s; i++) {
|
||||
var v = list[i];
|
||||
this.add(v);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = HashSet;
|
||||
Generated
Vendored
+621
@@ -0,0 +1,621 @@
|
||||
/**
|
||||
* This class maintains a list of static geometry related utility methods.
|
||||
*
|
||||
*
|
||||
* Copyright: i-Vis Research Group, Bilkent University, 2007 - present
|
||||
*/
|
||||
|
||||
const Point = require('./Point');
|
||||
|
||||
function IGeometry() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method calculates *half* the amount in x and y directions of the two
|
||||
* input rectangles needed to separate them keeping their respective
|
||||
* positioning, and returns the result in the input array. An input
|
||||
* separation buffer added to the amount in both directions. We assume that
|
||||
* the two rectangles do intersect.
|
||||
*/
|
||||
IGeometry.calcSeparationAmount = function (rectA, rectB, overlapAmount, separationBuffer)
|
||||
{
|
||||
if (!rectA.intersects(rectB)) {
|
||||
throw "assert failed";
|
||||
}
|
||||
|
||||
let directions = new Array(2);
|
||||
|
||||
this.decideDirectionsForOverlappingNodes(rectA, rectB, directions);
|
||||
|
||||
overlapAmount[0] = Math.min(rectA.getRight(), rectB.getRight()) -
|
||||
Math.max(rectA.x, rectB.x);
|
||||
overlapAmount[1] = Math.min(rectA.getBottom(), rectB.getBottom()) -
|
||||
Math.max(rectA.y, rectB.y);
|
||||
|
||||
// update the overlapping amounts for the following cases:
|
||||
if ((rectA.getX() <= rectB.getX()) && (rectA.getRight() >= rectB.getRight()))
|
||||
{
|
||||
/* Case x.1:
|
||||
*
|
||||
* rectA
|
||||
* | |
|
||||
* | _________ |
|
||||
* | | | |
|
||||
* |________|_______|______|
|
||||
* | |
|
||||
* | |
|
||||
* rectB
|
||||
*/
|
||||
overlapAmount[0] += Math.min((rectB.getX() - rectA.getX()),
|
||||
(rectA.getRight() - rectB.getRight()));
|
||||
}
|
||||
else if ((rectB.getX() <= rectA.getX()) && (rectB.getRight() >= rectA.getRight()))
|
||||
{
|
||||
/* Case x.2:
|
||||
*
|
||||
* rectB
|
||||
* | |
|
||||
* | _________ |
|
||||
* | | | |
|
||||
* |________|_______|______|
|
||||
* | |
|
||||
* | |
|
||||
* rectA
|
||||
*/
|
||||
overlapAmount[0] += Math.min((rectA.getX() - rectB.getX()),
|
||||
(rectB.getRight() - rectA.getRight()));
|
||||
}
|
||||
if ((rectA.getY() <= rectB.getY()) && (rectA.getBottom() >= rectB.getBottom()))
|
||||
{
|
||||
/* Case y.1:
|
||||
* ________ rectA
|
||||
* |
|
||||
* |
|
||||
* ______|____ rectB
|
||||
* | |
|
||||
* | |
|
||||
* ______|____|
|
||||
* |
|
||||
* |
|
||||
* |________
|
||||
*
|
||||
*/
|
||||
overlapAmount[1] += Math.min((rectB.getY() - rectA.getY()),
|
||||
(rectA.getBottom() - rectB.getBottom()));
|
||||
}
|
||||
else if ((rectB.getY() <= rectA.getY()) && (rectB.getBottom() >= rectA.getBottom()))
|
||||
{
|
||||
/* Case y.2:
|
||||
* ________ rectB
|
||||
* |
|
||||
* |
|
||||
* ______|____ rectA
|
||||
* | |
|
||||
* | |
|
||||
* ______|____|
|
||||
* |
|
||||
* |
|
||||
* |________
|
||||
*
|
||||
*/
|
||||
overlapAmount[1] += Math.min((rectA.getY() - rectB.getY()),
|
||||
(rectB.getBottom() - rectA.getBottom()));
|
||||
}
|
||||
|
||||
// find slope of the line passes two centers
|
||||
let slope = Math.abs((rectB.getCenterY() - rectA.getCenterY()) /
|
||||
(rectB.getCenterX() - rectA.getCenterX()));
|
||||
// if centers are overlapped
|
||||
if ((rectB.getCenterY() === rectA.getCenterY()) &&
|
||||
(rectB.getCenterX() === rectA.getCenterX()))
|
||||
{
|
||||
// assume the slope is 1 (45 degree)
|
||||
slope = 1.0;
|
||||
}
|
||||
|
||||
let moveByY = slope * overlapAmount[0];
|
||||
let moveByX = overlapAmount[1] / slope;
|
||||
if (overlapAmount[0] < moveByX)
|
||||
{
|
||||
moveByX = overlapAmount[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
moveByY = overlapAmount[1];
|
||||
}
|
||||
// return half the amount so that if each rectangle is moved by these
|
||||
// amounts in opposite directions, overlap will be resolved
|
||||
overlapAmount[0] = -1 * directions[0] * ((moveByX / 2) + separationBuffer);
|
||||
overlapAmount[1] = -1 * directions[1] * ((moveByY / 2) + separationBuffer);
|
||||
};
|
||||
|
||||
/**
|
||||
* This method decides the separation direction of overlapping nodes
|
||||
*
|
||||
* if directions[0] = -1, then rectA goes left
|
||||
* if directions[0] = 1, then rectA goes right
|
||||
* if directions[1] = -1, then rectA goes up
|
||||
* if directions[1] = 1, then rectA goes down
|
||||
*/
|
||||
IGeometry.decideDirectionsForOverlappingNodes = function (rectA, rectB, directions)
|
||||
{
|
||||
if (rectA.getCenterX() < rectB.getCenterX())
|
||||
{
|
||||
directions[0] = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
directions[0] = 1;
|
||||
}
|
||||
|
||||
if (rectA.getCenterY() < rectB.getCenterY())
|
||||
{
|
||||
directions[1] = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
directions[1] = 1;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This method calculates the intersection (clipping) points of the two
|
||||
* input rectangles with line segment defined by the centers of these two
|
||||
* rectangles. The clipping points are saved in the input double array and
|
||||
* whether or not the two rectangles overlap is returned.
|
||||
*/
|
||||
IGeometry.getIntersection2 = function(rectA, rectB, result)
|
||||
{
|
||||
//result[0-1] will contain clipPoint of rectA, result[2-3] will contain clipPoint of rectB
|
||||
let p1x = rectA.getCenterX();
|
||||
let p1y = rectA.getCenterY();
|
||||
let p2x = rectB.getCenterX();
|
||||
let p2y = rectB.getCenterY();
|
||||
|
||||
//if two rectangles intersect, then clipping points are centers
|
||||
if (rectA.intersects(rectB))
|
||||
{
|
||||
result[0] = p1x;
|
||||
result[1] = p1y;
|
||||
result[2] = p2x;
|
||||
result[3] = p2y;
|
||||
return true;
|
||||
}
|
||||
//variables for rectA
|
||||
let topLeftAx = rectA.getX();
|
||||
let topLeftAy = rectA.getY();
|
||||
let topRightAx = rectA.getRight();
|
||||
let bottomLeftAx = rectA.getX();
|
||||
let bottomLeftAy = rectA.getBottom();
|
||||
let bottomRightAx = rectA.getRight();
|
||||
let halfWidthA = rectA.getWidthHalf();
|
||||
let halfHeightA = rectA.getHeightHalf();
|
||||
//variables for rectB
|
||||
let topLeftBx = rectB.getX();
|
||||
let topLeftBy = rectB.getY();
|
||||
let topRightBx = rectB.getRight();
|
||||
let bottomLeftBx = rectB.getX();
|
||||
let bottomLeftBy = rectB.getBottom();
|
||||
let bottomRightBx = rectB.getRight();
|
||||
let halfWidthB = rectB.getWidthHalf();
|
||||
let halfHeightB = rectB.getHeightHalf();
|
||||
|
||||
//flag whether clipping points are found
|
||||
let clipPointAFound = false;
|
||||
let clipPointBFound = false;
|
||||
|
||||
// line is vertical
|
||||
if (p1x === p2x)
|
||||
{
|
||||
if (p1y > p2y)
|
||||
{
|
||||
result[0] = p1x;
|
||||
result[1] = topLeftAy;
|
||||
result[2] = p2x;
|
||||
result[3] = bottomLeftBy;
|
||||
return false;
|
||||
}
|
||||
else if (p1y < p2y)
|
||||
{
|
||||
result[0] = p1x;
|
||||
result[1] = bottomLeftAy;
|
||||
result[2] = p2x;
|
||||
result[3] = topLeftBy;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
//not line, return null;
|
||||
}
|
||||
}
|
||||
// line is horizontal
|
||||
else if (p1y === p2y)
|
||||
{
|
||||
if (p1x > p2x)
|
||||
{
|
||||
result[0] = topLeftAx;
|
||||
result[1] = p1y;
|
||||
result[2] = topRightBx;
|
||||
result[3] = p2y;
|
||||
return false;
|
||||
}
|
||||
else if (p1x < p2x)
|
||||
{
|
||||
result[0] = topRightAx;
|
||||
result[1] = p1y;
|
||||
result[2] = topLeftBx;
|
||||
result[3] = p2y;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
//not valid line, return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//slopes of rectA's and rectB's diagonals
|
||||
let slopeA = rectA.height / rectA.width;
|
||||
let slopeB = rectB.height / rectB.width;
|
||||
|
||||
//slope of line between center of rectA and center of rectB
|
||||
let slopePrime = (p2y - p1y) / (p2x - p1x);
|
||||
let cardinalDirectionA;
|
||||
let cardinalDirectionB;
|
||||
let tempPointAx;
|
||||
let tempPointAy;
|
||||
let tempPointBx;
|
||||
let tempPointBy;
|
||||
|
||||
//determine whether clipping point is the corner of nodeA
|
||||
if ((-slopeA) === slopePrime)
|
||||
{
|
||||
if (p1x > p2x)
|
||||
{
|
||||
result[0] = bottomLeftAx;
|
||||
result[1] = bottomLeftAy;
|
||||
clipPointAFound = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result[0] = topRightAx;
|
||||
result[1] = topLeftAy;
|
||||
clipPointAFound = true;
|
||||
}
|
||||
}
|
||||
else if (slopeA === slopePrime)
|
||||
{
|
||||
if (p1x > p2x)
|
||||
{
|
||||
result[0] = topLeftAx;
|
||||
result[1] = topLeftAy;
|
||||
clipPointAFound = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result[0] = bottomRightAx;
|
||||
result[1] = bottomLeftAy;
|
||||
clipPointAFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
//determine whether clipping point is the corner of nodeB
|
||||
if ((-slopeB) === slopePrime)
|
||||
{
|
||||
if (p2x > p1x)
|
||||
{
|
||||
result[2] = bottomLeftBx;
|
||||
result[3] = bottomLeftBy;
|
||||
clipPointBFound = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result[2] = topRightBx;
|
||||
result[3] = topLeftBy;
|
||||
clipPointBFound = true;
|
||||
}
|
||||
}
|
||||
else if (slopeB === slopePrime)
|
||||
{
|
||||
if (p2x > p1x)
|
||||
{
|
||||
result[2] = topLeftBx;
|
||||
result[3] = topLeftBy;
|
||||
clipPointBFound = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result[2] = bottomRightBx;
|
||||
result[3] = bottomLeftBy;
|
||||
clipPointBFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
//if both clipping points are corners
|
||||
if (clipPointAFound && clipPointBFound)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//determine Cardinal Direction of rectangles
|
||||
if (p1x > p2x)
|
||||
{
|
||||
if (p1y > p2y)
|
||||
{
|
||||
cardinalDirectionA = this.getCardinalDirection(slopeA, slopePrime, 4);
|
||||
cardinalDirectionB = this.getCardinalDirection(slopeB, slopePrime, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
cardinalDirectionA = this.getCardinalDirection(-slopeA, slopePrime, 3);
|
||||
cardinalDirectionB = this.getCardinalDirection(-slopeB, slopePrime, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (p1y > p2y)
|
||||
{
|
||||
cardinalDirectionA = this.getCardinalDirection(-slopeA, slopePrime, 1);
|
||||
cardinalDirectionB = this.getCardinalDirection(-slopeB, slopePrime, 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
cardinalDirectionA = this.getCardinalDirection(slopeA, slopePrime, 2);
|
||||
cardinalDirectionB = this.getCardinalDirection(slopeB, slopePrime, 4);
|
||||
}
|
||||
}
|
||||
//calculate clipping Point if it is not found before
|
||||
if (!clipPointAFound)
|
||||
{
|
||||
switch (cardinalDirectionA)
|
||||
{
|
||||
case 1:
|
||||
tempPointAy = topLeftAy;
|
||||
tempPointAx = p1x + (-halfHeightA) / slopePrime;
|
||||
result[0] = tempPointAx;
|
||||
result[1] = tempPointAy;
|
||||
break;
|
||||
case 2:
|
||||
tempPointAx = bottomRightAx;
|
||||
tempPointAy = p1y + halfWidthA * slopePrime;
|
||||
result[0] = tempPointAx;
|
||||
result[1] = tempPointAy;
|
||||
break;
|
||||
case 3:
|
||||
tempPointAy = bottomLeftAy;
|
||||
tempPointAx = p1x + halfHeightA / slopePrime;
|
||||
result[0] = tempPointAx;
|
||||
result[1] = tempPointAy;
|
||||
break;
|
||||
case 4:
|
||||
tempPointAx = bottomLeftAx;
|
||||
tempPointAy = p1y + (-halfWidthA) * slopePrime;
|
||||
result[0] = tempPointAx;
|
||||
result[1] = tempPointAy;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!clipPointBFound)
|
||||
{
|
||||
switch (cardinalDirectionB)
|
||||
{
|
||||
case 1:
|
||||
tempPointBy = topLeftBy;
|
||||
tempPointBx = p2x + (-halfHeightB) / slopePrime;
|
||||
result[2] = tempPointBx;
|
||||
result[3] = tempPointBy;
|
||||
break;
|
||||
case 2:
|
||||
tempPointBx = bottomRightBx;
|
||||
tempPointBy = p2y + halfWidthB * slopePrime;
|
||||
result[2] = tempPointBx;
|
||||
result[3] = tempPointBy;
|
||||
break;
|
||||
case 3:
|
||||
tempPointBy = bottomLeftBy;
|
||||
tempPointBx = p2x + halfHeightB / slopePrime;
|
||||
result[2] = tempPointBx;
|
||||
result[3] = tempPointBy;
|
||||
break;
|
||||
case 4:
|
||||
tempPointBx = bottomLeftBx;
|
||||
tempPointBy = p2y + (-halfWidthB) * slopePrime;
|
||||
result[2] = tempPointBx;
|
||||
result[3] = tempPointBy;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* This method returns in which cardinal direction does input point stays
|
||||
* 1: North
|
||||
* 2: East
|
||||
* 3: South
|
||||
* 4: West
|
||||
*/
|
||||
IGeometry.getCardinalDirection = function (slope, slopePrime, line)
|
||||
{
|
||||
if (slope > slopePrime)
|
||||
{
|
||||
return line;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1 + line % 4;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This method calculates the intersection of the two lines defined by
|
||||
* point pairs (s1,s2) and (f1,f2).
|
||||
*/
|
||||
IGeometry.getIntersection = function(s1, s2, f1, f2)
|
||||
{
|
||||
if (f2 == null) {
|
||||
return this.getIntersection2(s1, s2, f1);
|
||||
}
|
||||
|
||||
let x1 = s1.x;
|
||||
let y1 = s1.y;
|
||||
let x2 = s2.x;
|
||||
let y2 = s2.y;
|
||||
let x3 = f1.x;
|
||||
let y3 = f1.y;
|
||||
let x4 = f2.x;
|
||||
let y4 = f2.y;
|
||||
let x, y; // intersection point
|
||||
let a1, a2, b1, b2, c1, c2; // coefficients of line eqns.
|
||||
let denom;
|
||||
|
||||
a1 = y2 - y1;
|
||||
b1 = x1 - x2;
|
||||
c1 = x2 * y1 - x1 * y2; // { a1*x + b1*y + c1 = 0 is line 1 }
|
||||
|
||||
a2 = y4 - y3;
|
||||
b2 = x3 - x4;
|
||||
c2 = x4 * y3 - x3 * y4; // { a2*x + b2*y + c2 = 0 is line 2 }
|
||||
|
||||
denom = a1 * b2 - a2 * b1;
|
||||
|
||||
if (denom === 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
x = (b1 * c2 - b2 * c1) / denom;
|
||||
y = (a2 * c1 - a1 * c2) / denom;
|
||||
|
||||
return new Point(x, y);
|
||||
};
|
||||
|
||||
/**
|
||||
* This method finds and returns the angle of the vector from the + x-axis
|
||||
* in clockwise direction (compatible w/ Java coordinate system!).
|
||||
*/
|
||||
IGeometry.angleOfVector = function(Cx, Cy, Nx, Ny)
|
||||
{
|
||||
let C_angle;
|
||||
|
||||
if (Cx !== Nx)
|
||||
{
|
||||
C_angle = Math.atan((Ny - Cy) / (Nx - Cx));
|
||||
|
||||
if (Nx < Cx)
|
||||
{
|
||||
C_angle += Math.PI;
|
||||
}
|
||||
else if (Ny < Cy)
|
||||
{
|
||||
C_angle += this.TWO_PI;
|
||||
}
|
||||
}
|
||||
else if (Ny < Cy)
|
||||
{
|
||||
C_angle = this.ONE_AND_HALF_PI; // 270 degrees
|
||||
}
|
||||
else
|
||||
{
|
||||
C_angle = this.HALF_PI; // 90 degrees
|
||||
}
|
||||
|
||||
return C_angle;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This method checks whether the given two line segments (one with point
|
||||
* p1 and p2, the other with point p3 and p4) intersect at a point other
|
||||
* than these points.
|
||||
*/
|
||||
IGeometry.doIntersect = function(p1, p2, p3, p4){
|
||||
let a = p1.x;
|
||||
let b = p1.y;
|
||||
let c = p2.x;
|
||||
let d = p2.y;
|
||||
let p = p3.x;
|
||||
let q = p3.y;
|
||||
let r = p4.x;
|
||||
let s = p4.y;
|
||||
let det = (c - a) * (s - q) - (r - p) * (d - b);
|
||||
|
||||
if (det === 0) {
|
||||
return false;
|
||||
} else {
|
||||
let lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / det;
|
||||
let gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / det;
|
||||
return (0 < lambda && lambda < 1) && (0 < gamma && gamma < 1);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This method checks and calculates the intersection of
|
||||
* a line segment and a circle.
|
||||
*/
|
||||
IGeometry.findCircleLineIntersections = function(Ex, Ey, Lx, Ly, Cx, Cy, r) {
|
||||
|
||||
// E is the starting point of the ray,
|
||||
// L is the end point of the ray,
|
||||
// C is the center of sphere you're testing against
|
||||
// r is the radius of that sphere
|
||||
|
||||
// Compute:
|
||||
// d = L - E ( Direction vector of ray, from start to end )
|
||||
// f = E - C ( Vector from center sphere to ray start )
|
||||
|
||||
// Then the intersection is found by..
|
||||
// P = E + t * d
|
||||
// This is a parametric equation:
|
||||
// Px = Ex + tdx
|
||||
// Py = Ey + tdy
|
||||
|
||||
// get a, b, c values
|
||||
let a = (Lx-Ex)*(Lx-Ex) + (Ly-Ey)*(Ly-Ey);
|
||||
let b = 2*((Ex-Cx)*(Lx-Ex)+(Ey-Cy)*(Ly-Ey)) ;
|
||||
let c = (Ex-Cx)*(Ex-Cx)+(Ey-Cy)*(Ey-Cy) - r*r ;
|
||||
|
||||
// get discriminant
|
||||
var disc = b*b - 4 * a * c;
|
||||
if (disc >= 0) {
|
||||
// insert into quadratic formula
|
||||
let t1 = (-b + Math.sqrt(b*b - 4 * a * c)) / (2 * a);
|
||||
let t2 = (-b - Math.sqrt(b*b - 4 * a * c)) / (2 * a);
|
||||
let intersections = null;
|
||||
if( t1 >= 0 && t1 <= 1 )
|
||||
{
|
||||
// t1 is the intersection, and it's closer than t2
|
||||
// (since t1 uses -b - discriminant)
|
||||
// Impale, Poke
|
||||
return [t1];
|
||||
}
|
||||
|
||||
// here t1 didn't intersect so we are either started
|
||||
// inside the sphere or completely past it
|
||||
if( t2 >= 0 && t2 <= 1 )
|
||||
{
|
||||
// ExitWound
|
||||
return [t2] ;
|
||||
}
|
||||
|
||||
return intersections;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Section: Class Constants
|
||||
// -----------------------------------------------------------------------------
|
||||
/**
|
||||
* Some useful pre-calculated constants
|
||||
*/
|
||||
IGeometry.HALF_PI = 0.5 * Math.PI;
|
||||
IGeometry.ONE_AND_HALF_PI = 1.5 * Math.PI;
|
||||
IGeometry.TWO_PI = 2.0 * Math.PI;
|
||||
IGeometry.THREE_PI = 3.0 * Math.PI;
|
||||
|
||||
|
||||
module.exports = IGeometry;
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
function IMath() {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the sign of the input value.
|
||||
*/
|
||||
IMath.sign = function (value) {
|
||||
if (value > 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else if (value < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
IMath.floor = function (value) {
|
||||
return value < 0 ? Math.ceil(value) : Math.floor(value);
|
||||
};
|
||||
|
||||
IMath.ceil = function (value) {
|
||||
return value < 0 ? Math.floor(value) : Math.ceil(value);
|
||||
};
|
||||
|
||||
module.exports = IMath;
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
function Integer() {
|
||||
}
|
||||
|
||||
Integer.MAX_VALUE = 2147483647;
|
||||
Integer.MIN_VALUE = -2147483648;
|
||||
|
||||
module.exports = Integer;
|
||||
Generated
Vendored
+130
@@ -0,0 +1,130 @@
|
||||
const nodeFrom = value => ({ value, next: null, prev: null });
|
||||
|
||||
const add = ( prev, node, next, list ) => {
|
||||
if( prev !== null ){
|
||||
prev.next = node;
|
||||
} else {
|
||||
list.head = node;
|
||||
}
|
||||
|
||||
if( next !== null ){
|
||||
next.prev = node;
|
||||
} else {
|
||||
list.tail = node;
|
||||
}
|
||||
|
||||
node.prev = prev;
|
||||
node.next = next;
|
||||
|
||||
list.length++;
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
const remove = ( node, list ) => {
|
||||
let { prev, next } = node;
|
||||
|
||||
if( prev !== null ){
|
||||
prev.next = next;
|
||||
} else {
|
||||
list.head = next;
|
||||
}
|
||||
|
||||
if( next !== null ){
|
||||
next.prev = prev;
|
||||
} else {
|
||||
list.tail = prev;
|
||||
}
|
||||
|
||||
node.prev = node.next = null;
|
||||
|
||||
list.length--;
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
class LinkedList {
|
||||
constructor( vals ){
|
||||
this.length = 0;
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
|
||||
if( vals != null ){
|
||||
vals.forEach( v => this.push(v) );
|
||||
}
|
||||
}
|
||||
|
||||
size(){
|
||||
return this.length;
|
||||
}
|
||||
|
||||
insertBefore( val, otherNode ){
|
||||
return add( otherNode.prev, nodeFrom(val), otherNode, this );
|
||||
}
|
||||
|
||||
insertAfter( val, otherNode ){
|
||||
return add( otherNode, nodeFrom(val), otherNode.next, this );
|
||||
}
|
||||
|
||||
insertNodeBefore( newNode, otherNode ){
|
||||
return add( otherNode.prev, newNode, otherNode, this );
|
||||
}
|
||||
|
||||
insertNodeAfter( newNode, otherNode ){
|
||||
return add( otherNode, newNode, otherNode.next, this );
|
||||
}
|
||||
|
||||
push( val ){
|
||||
return add( this.tail, nodeFrom(val), null, this );
|
||||
}
|
||||
|
||||
unshift( val ){
|
||||
return add( null, nodeFrom(val), this.head, this );
|
||||
}
|
||||
|
||||
remove( node ){
|
||||
return remove( node, this );
|
||||
}
|
||||
|
||||
pop(){
|
||||
return remove( this.tail, this ).value;
|
||||
}
|
||||
|
||||
popNode(){
|
||||
return remove( this.tail, this );
|
||||
}
|
||||
|
||||
shift(){
|
||||
return remove( this.head, this ).value;
|
||||
}
|
||||
|
||||
shiftNode(){
|
||||
return remove( this.head, this );
|
||||
}
|
||||
|
||||
get_object_at( index ){
|
||||
if(index <= this.length()){
|
||||
var i = 1;
|
||||
var current = this.head;
|
||||
while(i < index){
|
||||
current = current.next;
|
||||
i++;
|
||||
}
|
||||
return current.value;
|
||||
}
|
||||
}
|
||||
|
||||
set_object_at( index, value){
|
||||
if(index <= this.length()) {
|
||||
var i = 1;
|
||||
var current = this.head;
|
||||
while (i < index) {
|
||||
current = current.next;
|
||||
i++;
|
||||
}
|
||||
current.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LinkedList;
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
// Some matrix (1d and 2d array) operations
|
||||
function Matrix() {
|
||||
}
|
||||
|
||||
/**
|
||||
* matrix multiplication
|
||||
* array1, array2 and result are 2d arrays
|
||||
*/
|
||||
Matrix.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;
|
||||
};
|
||||
|
||||
/**
|
||||
* matrix transpose
|
||||
* array and result are 2d arrays
|
||||
*/
|
||||
Matrix.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;
|
||||
};
|
||||
|
||||
/**
|
||||
* multiply array with constant
|
||||
* array and result are 1d arrays
|
||||
*/
|
||||
Matrix.multCons = function(array, constant){
|
||||
let result = [];
|
||||
|
||||
for(let i = 0; i < array.length; i++){
|
||||
result[i] = array[i] * constant;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* substract two arrays
|
||||
* array1, array2 and result are 1d arrays
|
||||
*/
|
||||
Matrix.minusOp = function(array1, array2){
|
||||
let result = [];
|
||||
|
||||
for(let i = 0; i < array1.length; i++){
|
||||
result[i] = array1[i] - array2[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* dot product of two arrays with same size
|
||||
* array1 and array2 are 1d arrays
|
||||
*/
|
||||
Matrix.dotProduct = function(array1, array2){
|
||||
let product = 0;
|
||||
|
||||
for(let i = 0; i < array1.length; i++){
|
||||
product += array1[i] * array2[i];
|
||||
}
|
||||
|
||||
return product;
|
||||
};
|
||||
|
||||
/**
|
||||
* magnitude of an array
|
||||
* array is 1d array
|
||||
*/
|
||||
Matrix.mag = function(array){
|
||||
return Math.sqrt(this.dotProduct(array, array));
|
||||
};
|
||||
|
||||
/**
|
||||
* normalization of an array
|
||||
* array and result are 1d array
|
||||
*/
|
||||
Matrix.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;
|
||||
};
|
||||
|
||||
/**
|
||||
* multiply an array with centering matrix
|
||||
* array and result are 1d array
|
||||
*/
|
||||
Matrix.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;
|
||||
};
|
||||
|
||||
/**
|
||||
* a special matrix multiplication
|
||||
* result = 0.5 * C * INV * C^T * array
|
||||
* array and result are 1d, C and INV are 2d arrays
|
||||
*/
|
||||
Matrix.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;
|
||||
};
|
||||
|
||||
module.exports = Matrix;
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
*This class is the javascript implementation of the Point.java class in jdk
|
||||
*/
|
||||
function Point(x, y, p) {
|
||||
this.x = null;
|
||||
this.y = null;
|
||||
if (x == null && y == null && p == null) {
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
}
|
||||
else if (typeof x == 'number' && typeof y == 'number' && p == null) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
else if (x.constructor.name == 'Point' && y == null && p == null) {
|
||||
p = x;
|
||||
this.x = p.x;
|
||||
this.y = p.y;
|
||||
}
|
||||
}
|
||||
|
||||
Point.prototype.getX = function () {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
Point.prototype.getY = function () {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
Point.prototype.getLocation = function () {
|
||||
return new Point(this.x, this.y);
|
||||
}
|
||||
|
||||
Point.prototype.setLocation = function (x, y, p) {
|
||||
if (x.constructor.name == 'Point' && y == null && p == null) {
|
||||
p = x;
|
||||
this.setLocation(p.x, p.y);
|
||||
}
|
||||
else if (typeof x == 'number' && typeof y == 'number' && p == null) {
|
||||
//if both parameters are integer just move (x,y) location
|
||||
if (parseInt(x) == x && parseInt(y) == y) {
|
||||
this.move(x, y);
|
||||
}
|
||||
else {
|
||||
this.x = Math.floor(x + 0.5);
|
||||
this.y = Math.floor(y + 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Point.prototype.move = function (x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
Point.prototype.translate = function (dx, dy) {
|
||||
this.x += dx;
|
||||
this.y += dy;
|
||||
}
|
||||
|
||||
Point.prototype.equals = function (obj) {
|
||||
if (obj.constructor.name == "Point") {
|
||||
var pt = obj;
|
||||
return (this.x == pt.x) && (this.y == pt.y);
|
||||
}
|
||||
return this == obj;
|
||||
}
|
||||
|
||||
Point.prototype.toString = function () {
|
||||
return new Point().constructor.name + "[x=" + this.x + ",y=" + this.y + "]";
|
||||
}
|
||||
|
||||
module.exports = Point;
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
function PointD(x, y) {
|
||||
if (x == null && y == null) {
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
} else {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
PointD.prototype.getX = function ()
|
||||
{
|
||||
return this.x;
|
||||
};
|
||||
|
||||
PointD.prototype.getY = function ()
|
||||
{
|
||||
return this.y;
|
||||
};
|
||||
|
||||
PointD.prototype.setX = function (x)
|
||||
{
|
||||
this.x = x;
|
||||
};
|
||||
|
||||
PointD.prototype.setY = function (y)
|
||||
{
|
||||
this.y = y;
|
||||
};
|
||||
|
||||
PointD.prototype.getDifference = function (pt)
|
||||
{
|
||||
return new DimensionD(this.x - pt.x, this.y - pt.y);
|
||||
};
|
||||
|
||||
PointD.prototype.getCopy = function ()
|
||||
{
|
||||
return new PointD(this.x, this.y);
|
||||
};
|
||||
|
||||
PointD.prototype.translate = function (dim)
|
||||
{
|
||||
this.x += dim.width;
|
||||
this.y += dim.height;
|
||||
return this;
|
||||
};
|
||||
|
||||
module.exports = PointD;
|
||||
Generated
Vendored
+77
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* A classic Quicksort algorithm with Hoare's partition
|
||||
* - Works also on LinkedList objects
|
||||
*
|
||||
* Copyright: i-Vis Research Group, Bilkent University, 2007 - present
|
||||
*/
|
||||
|
||||
const LinkedList = require('./LinkedList.js');
|
||||
|
||||
class Quicksort {
|
||||
constructor(A, compareFunction) {
|
||||
if(compareFunction !== null || compareFunction !== undefined)
|
||||
this.compareFunction = this._defaultCompareFunction;
|
||||
|
||||
let length;
|
||||
if( A instanceof LinkedList )
|
||||
length = A.size();
|
||||
else
|
||||
length = A.length;
|
||||
|
||||
this._quicksort(A, 0, length - 1);
|
||||
}
|
||||
|
||||
_quicksort(A, p, r){
|
||||
if(p < r) {
|
||||
let q = this._partition(A, p, r);
|
||||
this._quicksort(A, p, q);
|
||||
this._quicksort(A, q + 1, r);
|
||||
}
|
||||
}
|
||||
|
||||
_partition(A, p, r){
|
||||
let x = this._get(A, p);
|
||||
let i = p;
|
||||
let j = r;
|
||||
while(true){
|
||||
while (this.compareFunction(x, this._get(A, j)))
|
||||
j--;
|
||||
while (this.compareFunction(this._get(A, i), x))
|
||||
i++;
|
||||
|
||||
if (i < j){
|
||||
this._swap(A, i, j);
|
||||
i++;
|
||||
j--;
|
||||
}
|
||||
else
|
||||
return j;
|
||||
}
|
||||
}
|
||||
|
||||
_get(object, index){
|
||||
if( object instanceof LinkedList)
|
||||
return object.get_object_at(index);
|
||||
else
|
||||
return object[index];
|
||||
}
|
||||
|
||||
_set(object, index, value){
|
||||
if( object instanceof LinkedList)
|
||||
object.set_object_at(index, value);
|
||||
else
|
||||
object[index] = value;
|
||||
}
|
||||
|
||||
_swap(A, i, j){
|
||||
let temp = this._get(A, i);
|
||||
this._set(A, i, this._get(A, j));
|
||||
this._set(A, j, temp);
|
||||
}
|
||||
|
||||
_defaultCompareFunction(a, b){
|
||||
return b > a;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Quicksort;
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
function RandomSeed() {
|
||||
}
|
||||
// adapted from: https://stackoverflow.com/a/19303725
|
||||
RandomSeed.seed = 1;
|
||||
RandomSeed.x = 0;
|
||||
|
||||
RandomSeed.nextDouble = function () {
|
||||
RandomSeed.x = Math.sin(RandomSeed.seed++) * 10000;
|
||||
return RandomSeed.x - Math.floor(RandomSeed.x);
|
||||
};
|
||||
|
||||
module.exports = RandomSeed;
|
||||
Generated
Vendored
+130
@@ -0,0 +1,130 @@
|
||||
function RectangleD(x, y, width, height) {
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
this.width = 0;
|
||||
this.height = 0;
|
||||
|
||||
if (x != null && y != null && width != null && height != null) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
RectangleD.prototype.getX = function ()
|
||||
{
|
||||
return this.x;
|
||||
};
|
||||
|
||||
RectangleD.prototype.setX = function (x)
|
||||
{
|
||||
this.x = x;
|
||||
};
|
||||
|
||||
RectangleD.prototype.getY = function ()
|
||||
{
|
||||
return this.y;
|
||||
};
|
||||
|
||||
RectangleD.prototype.setY = function (y)
|
||||
{
|
||||
this.y = y;
|
||||
};
|
||||
|
||||
RectangleD.prototype.getWidth = function ()
|
||||
{
|
||||
return this.width;
|
||||
};
|
||||
|
||||
RectangleD.prototype.setWidth = function (width)
|
||||
{
|
||||
this.width = width;
|
||||
};
|
||||
|
||||
RectangleD.prototype.getHeight = function ()
|
||||
{
|
||||
return this.height;
|
||||
};
|
||||
|
||||
RectangleD.prototype.setHeight = function (height)
|
||||
{
|
||||
this.height = height;
|
||||
};
|
||||
|
||||
RectangleD.prototype.getRight = function ()
|
||||
{
|
||||
return this.x + this.width;
|
||||
};
|
||||
|
||||
RectangleD.prototype.getBottom = function ()
|
||||
{
|
||||
return this.y + this.height;
|
||||
};
|
||||
|
||||
RectangleD.prototype.intersects = function (a)
|
||||
{
|
||||
if (this.getRight() < a.x)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.getBottom() < a.y)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getRight() < this.x)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getBottom() < this.y)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
RectangleD.prototype.getCenterX = function ()
|
||||
{
|
||||
return this.x + this.width / 2;
|
||||
};
|
||||
|
||||
RectangleD.prototype.getMinX = function ()
|
||||
{
|
||||
return this.getX();
|
||||
};
|
||||
|
||||
RectangleD.prototype.getMaxX = function ()
|
||||
{
|
||||
return this.getX() + this.width;
|
||||
};
|
||||
|
||||
RectangleD.prototype.getCenterY = function ()
|
||||
{
|
||||
return this.y + this.height / 2;
|
||||
};
|
||||
|
||||
RectangleD.prototype.getMinY = function ()
|
||||
{
|
||||
return this.getY();
|
||||
};
|
||||
|
||||
RectangleD.prototype.getMaxY = function ()
|
||||
{
|
||||
return this.getY() + this.height;
|
||||
};
|
||||
|
||||
RectangleD.prototype.getWidthHalf = function ()
|
||||
{
|
||||
return this.width / 2;
|
||||
};
|
||||
|
||||
RectangleD.prototype.getHeightHalf = function ()
|
||||
{
|
||||
return this.height / 2;
|
||||
};
|
||||
|
||||
module.exports = RectangleD;
|
||||
+639
@@ -0,0 +1,639 @@
|
||||
// Singular Value Decomposition implementation
|
||||
function SVD() {
|
||||
};
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
SVD.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] = SVD.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] = SVD.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 = SVD.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 = SVD.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 = SVD.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 = SVD.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.
|
||||
SVD.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 = SVD;
|
||||
|
||||
Generated
Vendored
+157
@@ -0,0 +1,157 @@
|
||||
var PointD = require('./PointD');
|
||||
|
||||
function Transform(x, y) {
|
||||
this.lworldOrgX = 0.0;
|
||||
this.lworldOrgY = 0.0;
|
||||
this.ldeviceOrgX = 0.0;
|
||||
this.ldeviceOrgY = 0.0;
|
||||
this.lworldExtX = 1.0;
|
||||
this.lworldExtY = 1.0;
|
||||
this.ldeviceExtX = 1.0;
|
||||
this.ldeviceExtY = 1.0;
|
||||
}
|
||||
|
||||
Transform.prototype.getWorldOrgX = function ()
|
||||
{
|
||||
return this.lworldOrgX;
|
||||
}
|
||||
|
||||
Transform.prototype.setWorldOrgX = function (wox)
|
||||
{
|
||||
this.lworldOrgX = wox;
|
||||
}
|
||||
|
||||
Transform.prototype.getWorldOrgY = function ()
|
||||
{
|
||||
return this.lworldOrgY;
|
||||
}
|
||||
|
||||
Transform.prototype.setWorldOrgY = function (woy)
|
||||
{
|
||||
this.lworldOrgY = woy;
|
||||
}
|
||||
|
||||
Transform.prototype.getWorldExtX = function ()
|
||||
{
|
||||
return this.lworldExtX;
|
||||
}
|
||||
|
||||
Transform.prototype.setWorldExtX = function (wex)
|
||||
{
|
||||
this.lworldExtX = wex;
|
||||
}
|
||||
|
||||
Transform.prototype.getWorldExtY = function ()
|
||||
{
|
||||
return this.lworldExtY;
|
||||
}
|
||||
|
||||
Transform.prototype.setWorldExtY = function (wey)
|
||||
{
|
||||
this.lworldExtY = wey;
|
||||
}
|
||||
|
||||
/* Device related */
|
||||
|
||||
Transform.prototype.getDeviceOrgX = function ()
|
||||
{
|
||||
return this.ldeviceOrgX;
|
||||
}
|
||||
|
||||
Transform.prototype.setDeviceOrgX = function (dox)
|
||||
{
|
||||
this.ldeviceOrgX = dox;
|
||||
}
|
||||
|
||||
Transform.prototype.getDeviceOrgY = function ()
|
||||
{
|
||||
return this.ldeviceOrgY;
|
||||
}
|
||||
|
||||
Transform.prototype.setDeviceOrgY = function (doy)
|
||||
{
|
||||
this.ldeviceOrgY = doy;
|
||||
}
|
||||
|
||||
Transform.prototype.getDeviceExtX = function ()
|
||||
{
|
||||
return this.ldeviceExtX;
|
||||
}
|
||||
|
||||
Transform.prototype.setDeviceExtX = function (dex)
|
||||
{
|
||||
this.ldeviceExtX = dex;
|
||||
}
|
||||
|
||||
Transform.prototype.getDeviceExtY = function ()
|
||||
{
|
||||
return this.ldeviceExtY;
|
||||
}
|
||||
|
||||
Transform.prototype.setDeviceExtY = function (dey)
|
||||
{
|
||||
this.ldeviceExtY = dey;
|
||||
}
|
||||
|
||||
Transform.prototype.transformX = function (x)
|
||||
{
|
||||
var xDevice = 0.0;
|
||||
var worldExtX = this.lworldExtX;
|
||||
if (worldExtX != 0.0)
|
||||
{
|
||||
xDevice = this.ldeviceOrgX +
|
||||
((x - this.lworldOrgX) * this.ldeviceExtX / worldExtX);
|
||||
}
|
||||
|
||||
return xDevice;
|
||||
}
|
||||
|
||||
Transform.prototype.transformY = function (y)
|
||||
{
|
||||
var yDevice = 0.0;
|
||||
var worldExtY = this.lworldExtY;
|
||||
if (worldExtY != 0.0)
|
||||
{
|
||||
yDevice = this.ldeviceOrgY +
|
||||
((y - this.lworldOrgY) * this.ldeviceExtY / worldExtY);
|
||||
}
|
||||
|
||||
|
||||
return yDevice;
|
||||
}
|
||||
|
||||
Transform.prototype.inverseTransformX = function (x)
|
||||
{
|
||||
var xWorld = 0.0;
|
||||
var deviceExtX = this.ldeviceExtX;
|
||||
if (deviceExtX != 0.0)
|
||||
{
|
||||
xWorld = this.lworldOrgX +
|
||||
((x - this.ldeviceOrgX) * this.lworldExtX / deviceExtX);
|
||||
}
|
||||
|
||||
|
||||
return xWorld;
|
||||
}
|
||||
|
||||
Transform.prototype.inverseTransformY = function (y)
|
||||
{
|
||||
var yWorld = 0.0;
|
||||
var deviceExtY = this.ldeviceExtY;
|
||||
if (deviceExtY != 0.0)
|
||||
{
|
||||
yWorld = this.lworldOrgY +
|
||||
((y - this.ldeviceOrgY) * this.lworldExtY / deviceExtY);
|
||||
}
|
||||
return yWorld;
|
||||
}
|
||||
|
||||
Transform.prototype.inverseTransformPoint = function (inPoint)
|
||||
{
|
||||
var outPoint =
|
||||
new PointD(this.inverseTransformX(inPoint.x),
|
||||
this.inverseTransformY(inPoint.y));
|
||||
return outPoint;
|
||||
}
|
||||
|
||||
module.exports = Transform;
|
||||
Generated
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
function UniqueIDGeneretor() {
|
||||
}
|
||||
|
||||
UniqueIDGeneretor.lastID = 0;
|
||||
|
||||
UniqueIDGeneretor.createID = function (obj) {
|
||||
if (UniqueIDGeneretor.isPrimitive(obj)) {
|
||||
return obj;
|
||||
}
|
||||
if (obj.uniqueID != null) {
|
||||
return obj.uniqueID;
|
||||
}
|
||||
obj.uniqueID = UniqueIDGeneretor.getString();
|
||||
UniqueIDGeneretor.lastID++;
|
||||
return obj.uniqueID;
|
||||
}
|
||||
|
||||
UniqueIDGeneretor.getString = function (id) {
|
||||
if (id == null)
|
||||
id = UniqueIDGeneretor.lastID;
|
||||
return "Object#" + id + "";
|
||||
}
|
||||
|
||||
UniqueIDGeneretor.isPrimitive = function (arg) {
|
||||
var type = typeof arg;
|
||||
return arg == null || (type != "object" && type != "function");
|
||||
}
|
||||
|
||||
module.exports = UniqueIDGeneretor;
|
||||
Generated
Vendored
+158
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* Needleman-Wunsch algorithm is an procedure to compute the optimal global alignment of two string
|
||||
* sequences by S.B.Needleman and C.D.Wunsch (1970).
|
||||
*
|
||||
* Aside from the inputs, you can assign the scores for,
|
||||
* - Match: The two characters at the current index are same.
|
||||
* - Mismatch: The two characters at the current index are different.
|
||||
* - Insertion/Deletion(gaps): The best alignment involves one letter aligning to a gap in the other string.
|
||||
*/
|
||||
|
||||
class NeedlemanWunsch {
|
||||
constructor(sequence1, sequence2, match_score = 1, mismatch_penalty = -1, gap_penalty = -1) {
|
||||
this.sequence1 = sequence1;
|
||||
this.sequence2 = sequence2;
|
||||
this.match_score = match_score;
|
||||
this.mismatch_penalty = mismatch_penalty;
|
||||
this.gap_penalty = gap_penalty;
|
||||
|
||||
// Just the remove redundancy
|
||||
this.iMax = sequence1.length + 1;
|
||||
this.jMax = sequence2.length + 1;
|
||||
|
||||
// Grid matrix of scores
|
||||
this.grid = new Array(this.iMax);
|
||||
for(let i = 0; i < this.iMax; i++){
|
||||
this.grid[i] = new Array(this.jMax );
|
||||
|
||||
for(let j = 0; j < this.jMax ; j++)
|
||||
this.grid[i][j] = 0;
|
||||
}
|
||||
|
||||
// Traceback matrix (2D array, each cell is an array of boolean values for [`Diag`, `Up`, `Left`] positions)
|
||||
this.tracebackGrid = new Array(this.iMax);
|
||||
for(let i = 0; i < this.iMax; i++) {
|
||||
this.tracebackGrid[i] = new Array(this.jMax);
|
||||
|
||||
for(let j = 0; j < this.jMax ; j++)
|
||||
this.tracebackGrid[i][j] = [null, null, null];
|
||||
}
|
||||
|
||||
// The aligned sequences (return multiple possibilities)
|
||||
this.alignments = [];
|
||||
|
||||
// Final alignment score
|
||||
this.score = -1;
|
||||
|
||||
// Calculate scores and tracebacks
|
||||
this.computeGrids();
|
||||
}
|
||||
|
||||
getScore(){
|
||||
return this.score;
|
||||
}
|
||||
|
||||
getAlignments(){
|
||||
return this.alignments;
|
||||
}
|
||||
|
||||
// Main dynamic programming procedure
|
||||
computeGrids(){
|
||||
// Fill in the first row
|
||||
for (let j = 1; j < this.jMax; j++) {
|
||||
this.grid[0][j] = this.grid[0][j-1] + this.gap_penalty;
|
||||
this.tracebackGrid[0][j] = [false, false, true];
|
||||
}
|
||||
|
||||
// Fill in the first column
|
||||
for (let i = 1; i < this.iMax; i++) {
|
||||
this.grid[i][0] = this.grid[i-1][0] + this.gap_penalty;
|
||||
this.tracebackGrid[i][0] = [false, true, false];
|
||||
}
|
||||
|
||||
// Fill the rest of the grid
|
||||
for(let i = 1; i < this.iMax; i++){
|
||||
for(let j = 1; j < this.jMax; j++){
|
||||
// Find the max score(s) among [`Diag`, `Up`, `Left`]
|
||||
let diag;
|
||||
if(this.sequence1[i-1] === this.sequence2[j-1])
|
||||
diag = this.grid[i-1][j-1] + this.match_score;
|
||||
else
|
||||
diag = this.grid[i-1][j-1] + this.mismatch_penalty;
|
||||
|
||||
let up = this.grid[i-1][j] + this.gap_penalty;
|
||||
let left = this.grid[i][j-1] + this.gap_penalty;
|
||||
|
||||
// If there exists multiple max values, capture them for multiple paths
|
||||
let maxOf = [diag,up,left];
|
||||
let indices = this.arrayAllMaxIndexes(maxOf);
|
||||
|
||||
// Update Grids
|
||||
this.grid[i][j] = maxOf[indices[0]];
|
||||
this.tracebackGrid[i][j] = [indices.includes(0), indices.includes(1), indices.includes(2)];
|
||||
}
|
||||
}
|
||||
|
||||
// Update alignment score
|
||||
this.score = this.grid[this.iMax-1][this.jMax-1];
|
||||
}
|
||||
|
||||
// Gets all possible valid sequence combinations
|
||||
alignmentTraceback(){
|
||||
let inProcessAlignments = [];
|
||||
|
||||
inProcessAlignments.push({ pos: [this.sequence1.length, this.sequence2.length],
|
||||
seq1: "",
|
||||
seq2: ""
|
||||
});
|
||||
|
||||
while(inProcessAlignments[0]){
|
||||
let current = inProcessAlignments[0];
|
||||
let directions = this.tracebackGrid[current.pos[0]][current.pos[1]];
|
||||
|
||||
if(directions[0]){
|
||||
inProcessAlignments.push({ pos: [current.pos[0]-1, current.pos[1]-1],
|
||||
seq1: (this.sequence1[current.pos[0]-1] + current.seq1),
|
||||
seq2: (this.sequence2[current.pos[1]-1] + current.seq2)
|
||||
});
|
||||
}
|
||||
if(directions[1]){
|
||||
inProcessAlignments.push({ pos: [current.pos[0]-1, current.pos[1]],
|
||||
seq1: this.sequence1[current.pos[0]-1] + current.seq1,
|
||||
seq2: '-' + current.seq2
|
||||
});
|
||||
}
|
||||
if(directions[2]){
|
||||
inProcessAlignments.push({ pos: [current.pos[0], current.pos[1]-1],
|
||||
seq1:'-' + current.seq1,
|
||||
seq2: this.sequence2[current.pos[1]-1] + current.seq2
|
||||
});
|
||||
}
|
||||
|
||||
if(current.pos[0] === 0 && current.pos[1] === 0)
|
||||
this.alignments.push({sequence1 : current.seq1,
|
||||
sequence2: current.seq2
|
||||
});
|
||||
|
||||
inProcessAlignments.shift();
|
||||
}
|
||||
|
||||
return this.alignments;
|
||||
}
|
||||
|
||||
// Helper Functions
|
||||
|
||||
getAllIndexes(arr, val) {
|
||||
let indexes = [], i = -1;
|
||||
while ((i = arr.indexOf(val, i+1)) !== -1){
|
||||
indexes.push(i);
|
||||
}
|
||||
return indexes;
|
||||
}
|
||||
|
||||
arrayAllMaxIndexes(array){
|
||||
return this.getAllIndexes(array, Math.max.apply(null, array));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NeedlemanWunsch;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
const path = require('path');
|
||||
const pkg = require('./package.json');
|
||||
const camelcase = require('camelcase');
|
||||
const process = require('process');
|
||||
const webpack = require('webpack');
|
||||
const env = process.env;
|
||||
const NODE_ENV = env.NODE_ENV;
|
||||
const MIN = env.MIN;
|
||||
const PROD = NODE_ENV === 'production';
|
||||
|
||||
let config = {
|
||||
devtool: PROD ? false : 'inline-source-map',
|
||||
entry: './index.js',
|
||||
output: {
|
||||
path: path.join( __dirname ),
|
||||
filename: 'layout-base.js',
|
||||
library: camelcase( pkg.name ),
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{ test: /\.js$/, exclude: /node_modules/, use: 'babel-loader' }
|
||||
]
|
||||
},
|
||||
plugins: MIN ? [
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false,
|
||||
drop_console: false,
|
||||
}
|
||||
})
|
||||
] : []
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "cytoscape-fcose",
|
||||
"version": "2.2.0",
|
||||
"description": "The fCoSE layout for Cytoscape.js by Bilkent with fast compound node placement",
|
||||
"main": "cytoscape-fcose.js",
|
||||
"author": {
|
||||
"name": "iVis-at-Bilkent"
|
||||
},
|
||||
"scripts": {
|
||||
"copyright": "update license",
|
||||
"lint": "eslint src",
|
||||
"build": "cross-env NODE_ENV=production webpack",
|
||||
"build:min": "cross-env NODE_ENV=production MIN=true webpack",
|
||||
"build:release": "run-s build copyright",
|
||||
"watch": "webpack --progress --watch",
|
||||
"dev": "webpack-dev-server --open",
|
||||
"test": "mocha"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/iVis-at-Bilkent/cytoscape.js-fcose.git"
|
||||
},
|
||||
"keywords": [
|
||||
"cytoscape",
|
||||
"cytoscape-extension"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/iVis-at-Bilkent/cytoscape.js-fcose/issues"
|
||||
},
|
||||
"homepage": "https://github.com/iVis-at-Bilkent/cytoscape.js-fcose",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.24.1",
|
||||
"babel-loader": "^7.1.4",
|
||||
"babel-preset-env": "^1.5.1",
|
||||
"camelcase": "^6.2.0",
|
||||
"chai": "4.0.2",
|
||||
"cpy-cli": "^3.1.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^7.26.0",
|
||||
"gh-pages": "^1.0.0",
|
||||
"mocha": "8.4.0",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"update": "^0.7.4",
|
||||
"updater-license": "^1.0.0",
|
||||
"webpack": "^5.37.0",
|
||||
"webpack-cli": "^4.7.0",
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"cytoscape": "^3.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"cose-base": "^2.2.0"
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Simple, internal Object.assign() polyfill for options objects etc.
|
||||
|
||||
module.exports = Object.assign != null ? Object.assign.bind( Object ) : function( tgt, ...srcs ){
|
||||
srcs.forEach( src => {
|
||||
Object.keys( src ).forEach( k => tgt[k] = src[k] );
|
||||
} );
|
||||
|
||||
return tgt;
|
||||
};
|
||||
+269
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* Auxiliary functions
|
||||
*/
|
||||
|
||||
const LinkedList = require('cose-base').layoutBase.LinkedList;
|
||||
|
||||
let auxiliary = {};
|
||||
|
||||
// get the top most nodes
|
||||
auxiliary.getTopMostNodes = function(nodes) {
|
||||
let nodesMap = {};
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
nodesMap[nodes[i].id()] = true;
|
||||
}
|
||||
let roots = nodes.filter(function (ele, i) {
|
||||
if(typeof ele === "number") {
|
||||
ele = i;
|
||||
}
|
||||
let parent = ele.parent()[0];
|
||||
while(parent != null){
|
||||
if(nodesMap[parent.id()]){
|
||||
return false;
|
||||
}
|
||||
parent = parent.parent()[0];
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return roots;
|
||||
};
|
||||
|
||||
// find disconnected components and create dummy nodes that connect them
|
||||
auxiliary.connectComponents = function(cy, eles, topMostNodes, dummyNodes){
|
||||
let queue = new LinkedList();
|
||||
let visited = new Set();
|
||||
let visitedTopMostNodes = [];
|
||||
let currentNeighbor;
|
||||
let minDegreeNode;
|
||||
let minDegree;
|
||||
|
||||
let isConnected = false;
|
||||
let count = 1;
|
||||
let nodesConnectedToDummy = [];
|
||||
let components = [];
|
||||
|
||||
do{
|
||||
let cmpt = cy.collection();
|
||||
components.push(cmpt);
|
||||
|
||||
let currentNode = topMostNodes[0];
|
||||
let childrenOfCurrentNode = cy.collection();
|
||||
childrenOfCurrentNode.merge(currentNode).merge(currentNode.descendants().intersection(eles));
|
||||
visitedTopMostNodes.push(currentNode);
|
||||
|
||||
childrenOfCurrentNode.forEach(function(node) {
|
||||
queue.push(node);
|
||||
visited.add(node);
|
||||
cmpt.merge(node);
|
||||
});
|
||||
|
||||
while(queue.length != 0){
|
||||
currentNode = queue.shift();
|
||||
|
||||
// Traverse all neighbors of this node
|
||||
let neighborNodes = cy.collection();
|
||||
currentNode.neighborhood().nodes().forEach(function(node){
|
||||
if(eles.intersection(currentNode.edgesWith(node)).length > 0){
|
||||
neighborNodes.merge(node);
|
||||
}
|
||||
});
|
||||
|
||||
for(let i = 0; i < neighborNodes.length; i++){
|
||||
let neighborNode = neighborNodes[i];
|
||||
currentNeighbor = topMostNodes.intersection(neighborNode.union(neighborNode.ancestors()));
|
||||
if(currentNeighbor != null && !visited.has(currentNeighbor[0])){
|
||||
let childrenOfNeighbor = currentNeighbor.union(currentNeighbor.descendants());
|
||||
|
||||
childrenOfNeighbor.forEach(function(node){
|
||||
queue.push(node);
|
||||
visited.add(node);
|
||||
cmpt.merge(node);
|
||||
if(topMostNodes.has(node)){
|
||||
visitedTopMostNodes.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmpt.forEach(node => {
|
||||
eles.intersection(node.connectedEdges()).forEach(e => { // connectedEdges() usually cached
|
||||
if( cmpt.has(e.source()) && cmpt.has(e.target()) ){ // has() is cheap
|
||||
cmpt.merge(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if(visitedTopMostNodes.length == topMostNodes.length){
|
||||
isConnected = true;
|
||||
}
|
||||
|
||||
if(!isConnected || (isConnected && count > 1)){
|
||||
minDegreeNode = visitedTopMostNodes[0];
|
||||
minDegree = minDegreeNode.connectedEdges().length;
|
||||
visitedTopMostNodes.forEach(function(node){
|
||||
if(node.connectedEdges().length < minDegree){
|
||||
minDegree = node.connectedEdges().length;
|
||||
minDegreeNode = node;
|
||||
}
|
||||
});
|
||||
nodesConnectedToDummy.push(minDegreeNode.id());
|
||||
// TO DO: Check efficiency of this part
|
||||
let temp = cy.collection();
|
||||
temp.merge(visitedTopMostNodes[0]);
|
||||
visitedTopMostNodes.forEach(function(node){
|
||||
temp.merge(node);
|
||||
});
|
||||
visitedTopMostNodes = [];
|
||||
topMostNodes = topMostNodes.difference(temp);
|
||||
count++;
|
||||
}
|
||||
|
||||
}
|
||||
while(!isConnected);
|
||||
|
||||
if(dummyNodes){
|
||||
if(nodesConnectedToDummy.length > 0 ){
|
||||
dummyNodes.set('dummy'+(dummyNodes.size+1), nodesConnectedToDummy);
|
||||
}
|
||||
}
|
||||
return components;
|
||||
};
|
||||
|
||||
// relocates componentResult to originalCenter if there is no fixedNodeConstraint
|
||||
auxiliary.relocateComponent = function(originalCenter, componentResult, options) {
|
||||
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") {
|
||||
// calculate current bounding box
|
||||
for (let [key, value] of componentResult.nodeIndexes) {
|
||||
let cyNode = options.cy.getElementById(key);
|
||||
if (cyNode) {
|
||||
let nodeBB = cyNode.boundingBox();
|
||||
let leftX = componentResult.xCoords[value] - nodeBB.w / 2;
|
||||
let rightX = componentResult.xCoords[value] + nodeBB.w / 2;
|
||||
let topY = componentResult.yCoords[value] - nodeBB.h / 2;
|
||||
let bottomY = componentResult.yCoords[value] + nodeBB.h / 2;
|
||||
|
||||
if (leftX < minXCoord)
|
||||
minXCoord = leftX;
|
||||
if (rightX > maxXCoord)
|
||||
maxXCoord = rightX;
|
||||
if (topY < minYCoord)
|
||||
minYCoord = topY;
|
||||
if (bottomY > maxYCoord)
|
||||
maxYCoord = bottomY;
|
||||
}
|
||||
}
|
||||
// find difference between current and original center
|
||||
let diffOnX = originalCenter.x - (maxXCoord + minXCoord) / 2;
|
||||
let diffOnY = originalCenter.y - (maxYCoord + minYCoord) / 2;
|
||||
// move component to original center
|
||||
componentResult.xCoords = componentResult.xCoords.map(x => x + diffOnX);
|
||||
componentResult.yCoords = componentResult.yCoords.map(y => y + diffOnY);
|
||||
}
|
||||
else {
|
||||
// calculate current bounding box
|
||||
Object.keys(componentResult).forEach(function (item) {
|
||||
let node = componentResult[item];
|
||||
let leftX = node.getRect().x;
|
||||
let rightX = node.getRect().x + node.getRect().width;
|
||||
let topY = node.getRect().y;
|
||||
let bottomY = node.getRect().y + node.getRect().height;
|
||||
|
||||
if (leftX < minXCoord)
|
||||
minXCoord = leftX;
|
||||
if (rightX > maxXCoord)
|
||||
maxXCoord = rightX;
|
||||
if (topY < minYCoord)
|
||||
minYCoord = topY;
|
||||
if (bottomY > maxYCoord)
|
||||
maxYCoord = bottomY;
|
||||
});
|
||||
// find difference between current and original center
|
||||
let diffOnX = originalCenter.x - (maxXCoord + minXCoord) / 2;
|
||||
let diffOnY = originalCenter.y - (maxYCoord + minYCoord) / 2;
|
||||
// move component to original center
|
||||
Object.keys(componentResult).forEach(function (item) {
|
||||
let node = componentResult[item];
|
||||
node.setCenter(node.getCenterX() + diffOnX, node.getCenterY() + diffOnY);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auxiliary.calcBoundingBox = function(parentNode, xCoords, yCoords, nodeIndexes){
|
||||
// calculate bounds
|
||||
let left = Number.MAX_SAFE_INTEGER;
|
||||
let right = Number.MIN_SAFE_INTEGER;
|
||||
let top = Number.MAX_SAFE_INTEGER;
|
||||
let bottom = Number.MIN_SAFE_INTEGER;
|
||||
let nodeLeft;
|
||||
let nodeRight;
|
||||
let nodeTop;
|
||||
let nodeBottom;
|
||||
|
||||
let nodes = parentNode.descendants().not(":parent");
|
||||
let s = nodes.length;
|
||||
for (let i = 0; i < s; i++)
|
||||
{
|
||||
let node = nodes[i];
|
||||
|
||||
nodeLeft = xCoords[nodeIndexes.get(node.id())] - node.width()/2;
|
||||
nodeRight = xCoords[nodeIndexes.get(node.id())] + node.width()/2;
|
||||
nodeTop = yCoords[nodeIndexes.get(node.id())] - node.height()/2;
|
||||
nodeBottom = yCoords[nodeIndexes.get(node.id())] + node.height()/2;
|
||||
|
||||
if (left > nodeLeft)
|
||||
{
|
||||
left = nodeLeft;
|
||||
}
|
||||
|
||||
if (right < nodeRight)
|
||||
{
|
||||
right = nodeRight;
|
||||
}
|
||||
|
||||
if (top > nodeTop)
|
||||
{
|
||||
top = nodeTop;
|
||||
}
|
||||
|
||||
if (bottom < nodeBottom)
|
||||
{
|
||||
bottom = nodeBottom;
|
||||
}
|
||||
}
|
||||
|
||||
let boundingBox = {};
|
||||
boundingBox.topLeftX = left;
|
||||
boundingBox.topLeftY = top;
|
||||
boundingBox.width = right - left;
|
||||
boundingBox.height = bottom - top;
|
||||
return boundingBox;
|
||||
};
|
||||
|
||||
// This function finds and returns parent nodes whose all children are hidden
|
||||
auxiliary.calcParentsWithoutChildren = function(cy, eles){
|
||||
let parentsWithoutChildren = cy.collection();
|
||||
eles.nodes(':parent').forEach((parent) => {
|
||||
let check = false;
|
||||
parent.children().forEach((child) => {
|
||||
if(child.css('display') != 'none') {
|
||||
check = true;
|
||||
}
|
||||
});
|
||||
if(!check) {
|
||||
parentsWithoutChildren.merge(parent);
|
||||
}
|
||||
});
|
||||
|
||||
return parentsWithoutChildren;
|
||||
}
|
||||
|
||||
module.exports = auxiliary;
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
/**
|
||||
The implementation of the postprocessing part that applies CoSE layout over the spectral layout
|
||||
*/
|
||||
|
||||
const aux = require('./auxiliary');
|
||||
const CoSELayout = require('cose-base').CoSELayout;
|
||||
const CoSENode = require('cose-base').CoSENode;
|
||||
const PointD = require('cose-base').layoutBase.PointD;
|
||||
const DimensionD = require('cose-base').layoutBase.DimensionD;
|
||||
const LayoutConstants = require('cose-base').layoutBase.LayoutConstants;
|
||||
const FDLayoutConstants = require('cose-base').layoutBase.FDLayoutConstants;
|
||||
const CoSEConstants = require('cose-base').CoSEConstants;
|
||||
|
||||
// main function that cose layout is processed
|
||||
let coseLayout = function(options, spectralResult){
|
||||
|
||||
let cy = options.cy;
|
||||
let eles = options.eles;
|
||||
let nodes = eles.nodes();
|
||||
let edges = eles.edges();
|
||||
|
||||
let nodeIndexes;
|
||||
let xCoords;
|
||||
let yCoords;
|
||||
let idToLNode = {};
|
||||
|
||||
if(options.randomize){
|
||||
nodeIndexes = spectralResult["nodeIndexes"];
|
||||
xCoords = spectralResult["xCoords"];
|
||||
yCoords = spectralResult["yCoords"];
|
||||
}
|
||||
|
||||
const isFn = fn => typeof fn === 'function';
|
||||
|
||||
const optFn = ( opt, ele ) => {
|
||||
if( isFn( opt ) ){
|
||||
return opt( ele );
|
||||
} else {
|
||||
return opt;
|
||||
}
|
||||
};
|
||||
|
||||
/**** Postprocessing functions ****/
|
||||
|
||||
let parentsWithoutChildren = aux.calcParentsWithoutChildren(cy, eles);
|
||||
|
||||
// transfer cytoscape nodes to cose nodes
|
||||
let processChildrenList = function (parent, children, layout, options) {
|
||||
let size = children.length;
|
||||
for (let i = 0; i < size; i++) {
|
||||
let theChild = children[i];
|
||||
let children_of_children = null;
|
||||
if(theChild.intersection(parentsWithoutChildren).length == 0) {
|
||||
children_of_children = theChild.children();
|
||||
}
|
||||
let theNode;
|
||||
|
||||
let dimensions = theChild.layoutDimensions({
|
||||
nodeDimensionsIncludeLabels: options.nodeDimensionsIncludeLabels
|
||||
});
|
||||
|
||||
if (theChild.outerWidth() != null
|
||||
&& theChild.outerHeight() != null) {
|
||||
if(options.randomize){
|
||||
if(!theChild.isParent()){
|
||||
theNode = parent.add(new CoSENode(layout.graphManager,
|
||||
new PointD(xCoords[nodeIndexes.get(theChild.id())] - dimensions.w / 2, yCoords[nodeIndexes.get(theChild.id())] - dimensions.h / 2),
|
||||
new DimensionD(parseFloat(dimensions.w), parseFloat(dimensions.h))));
|
||||
}
|
||||
else{
|
||||
let parentInfo = aux.calcBoundingBox(theChild, xCoords, yCoords, nodeIndexes);
|
||||
if(theChild.intersection(parentsWithoutChildren).length == 0) {
|
||||
theNode = parent.add(new CoSENode(layout.graphManager,
|
||||
new PointD(parentInfo.topLeftX, parentInfo.topLeftY),
|
||||
new DimensionD(parentInfo.width, parentInfo.height)));
|
||||
}
|
||||
else { // for the parentsWithoutChildren
|
||||
theNode = parent.add(new CoSENode(layout.graphManager,
|
||||
new PointD(parentInfo.topLeftX, parentInfo.topLeftY),
|
||||
new DimensionD(parseFloat(dimensions.w), parseFloat(dimensions.h))));
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
theNode = parent.add(new CoSENode(layout.graphManager,
|
||||
new PointD(theChild.position('x') - dimensions.w / 2, theChild.position('y') - dimensions.h / 2),
|
||||
new DimensionD(parseFloat(dimensions.w), parseFloat(dimensions.h))));
|
||||
}
|
||||
}
|
||||
else {
|
||||
theNode = parent.add(new CoSENode(this.graphManager));
|
||||
}
|
||||
// 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
|
||||
theNode.paddingLeft = parseInt( theChild.css('padding') );
|
||||
theNode.paddingTop = parseInt( theChild.css('padding') );
|
||||
theNode.paddingRight = parseInt( theChild.css('padding') );
|
||||
theNode.paddingBottom = parseInt( theChild.css('padding') );
|
||||
|
||||
//Attach the label properties to both compound and simple nodes if labels will be included in node dimensions
|
||||
//These properties will be used while updating bounds of compounds during iterations or tiling
|
||||
//and will be used for simple nodes while transferring final positions to cytoscape
|
||||
if(options.nodeDimensionsIncludeLabels){
|
||||
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");
|
||||
}
|
||||
|
||||
// Map the layout node
|
||||
idToLNode[theChild.data("id")] = theNode;
|
||||
|
||||
if (isNaN(theNode.rect.x)) {
|
||||
theNode.rect.x = 0;
|
||||
}
|
||||
|
||||
if (isNaN(theNode.rect.y)) {
|
||||
theNode.rect.y = 0;
|
||||
}
|
||||
|
||||
if (children_of_children != null && children_of_children.length > 0) {
|
||||
let theNewGraph;
|
||||
theNewGraph = layout.getGraphManager().add(layout.newGraph(), theNode);
|
||||
processChildrenList(theNewGraph, children_of_children, layout, options);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// transfer cytoscape edges to cose edges
|
||||
let processEdges = function(layout, gm, edges){
|
||||
let idealLengthTotal = 0;
|
||||
let edgeCount = 0;
|
||||
for (let i = 0; i < edges.length; i++) {
|
||||
let edge = edges[i];
|
||||
let sourceNode = idToLNode[edge.data("source")];
|
||||
let targetNode = idToLNode[edge.data("target")];
|
||||
if(sourceNode && targetNode && sourceNode !== targetNode && sourceNode.getEdgesBetween(targetNode).length == 0){
|
||||
let e1 = gm.add(layout.newEdge(), sourceNode, targetNode);
|
||||
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 (edgeCount > 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.nestingFactor != null)
|
||||
CoSEConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR = FDLayoutConstants.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR = options.nestingFactor;
|
||||
if (options.gravity != null)
|
||||
CoSEConstants.DEFAULT_GRAVITY_STRENGTH = FDLayoutConstants.DEFAULT_GRAVITY_STRENGTH = options.gravity;
|
||||
if (options.numIter != null)
|
||||
CoSEConstants.MAX_ITERATIONS = FDLayoutConstants.MAX_ITERATIONS = options.numIter;
|
||||
if (options.gravityRange != null)
|
||||
CoSEConstants.DEFAULT_GRAVITY_RANGE_FACTOR = FDLayoutConstants.DEFAULT_GRAVITY_RANGE_FACTOR = options.gravityRange;
|
||||
if(options.gravityCompound != null)
|
||||
CoSEConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_STRENGTH = options.gravityCompound;
|
||||
if(options.gravityRangeCompound != null)
|
||||
CoSEConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR = FDLayoutConstants.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR = options.gravityRangeCompound;
|
||||
if (options.initialEnergyOnIncremental != null)
|
||||
CoSEConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL = FDLayoutConstants.DEFAULT_COOLING_FACTOR_INCREMENTAL = options.initialEnergyOnIncremental;
|
||||
|
||||
if (options.tilingCompareBy != null)
|
||||
CoSEConstants.TILING_COMPARE_BY = options.tilingCompareBy;
|
||||
|
||||
if(options.quality == 'proof')
|
||||
LayoutConstants.QUALITY = 2;
|
||||
else
|
||||
LayoutConstants.QUALITY = 0;
|
||||
|
||||
CoSEConstants.NODE_DIMENSIONS_INCLUDE_LABELS = FDLayoutConstants.NODE_DIMENSIONS_INCLUDE_LABELS = LayoutConstants.NODE_DIMENSIONS_INCLUDE_LABELS = options.nodeDimensionsIncludeLabels;
|
||||
CoSEConstants.DEFAULT_INCREMENTAL = FDLayoutConstants.DEFAULT_INCREMENTAL = LayoutConstants.DEFAULT_INCREMENTAL =
|
||||
!(options.randomize);
|
||||
CoSEConstants.ANIMATE = FDLayoutConstants.ANIMATE = LayoutConstants.ANIMATE = options.animate;
|
||||
CoSEConstants.TILE = options.tile;
|
||||
CoSEConstants.TILING_PADDING_VERTICAL =
|
||||
typeof options.tilingPaddingVertical === 'function' ? options.tilingPaddingVertical.call() : options.tilingPaddingVertical;
|
||||
CoSEConstants.TILING_PADDING_HORIZONTAL =
|
||||
typeof options.tilingPaddingHorizontal === 'function' ? options.tilingPaddingHorizontal.call() : options.tilingPaddingHorizontal;
|
||||
|
||||
CoSEConstants.DEFAULT_INCREMENTAL = FDLayoutConstants.DEFAULT_INCREMENTAL = LayoutConstants.DEFAULT_INCREMENTAL = true;
|
||||
CoSEConstants.PURE_INCREMENTAL = !options.randomize;
|
||||
LayoutConstants.DEFAULT_UNIFORM_LEAF_NODE_SIZES = options.uniformNodeDimensions;
|
||||
|
||||
// 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.fixedNodeConstraint || options.alignmentConstraint || options.relativePlacementConstraint){
|
||||
CoSEConstants.TREE_REDUCTION_ON_INCREMENTAL = false;
|
||||
}
|
||||
else{
|
||||
CoSEConstants.TREE_REDUCTION_ON_INCREMENTAL = true;
|
||||
}
|
||||
|
||||
let coseLayout = new CoSELayout();
|
||||
let gm = coseLayout.newGraphManager();
|
||||
|
||||
processChildrenList(gm.addRoot(), aux.getTopMostNodes(nodes), coseLayout, options);
|
||||
processEdges(coseLayout, gm, edges);
|
||||
processConstraints(coseLayout, options);
|
||||
|
||||
coseLayout.runLayout();
|
||||
|
||||
return idToLNode;
|
||||
};
|
||||
|
||||
module.exports = { coseLayout };
|
||||
+414
@@ -0,0 +1,414 @@
|
||||
/**
|
||||
The implementation of the fcose layout algorithm
|
||||
*/
|
||||
|
||||
const assign = require('../assign');
|
||||
const aux = require('./auxiliary');
|
||||
const { spectralLayout } = require('./spectral');
|
||||
const { coseLayout } = require('./cose');
|
||||
|
||||
const defaults = Object.freeze({
|
||||
|
||||
// 'draft', 'default' or 'proof'
|
||||
// - 'draft' only applies spectral layout
|
||||
// - 'default' improves the quality with subsequent CoSE layout (fast cooling rate)
|
||||
// - 'proof' improves the quality with subsequent CoSE layout (slow cooling rate)
|
||||
quality: "default",
|
||||
// Use random node positions at beginning of layout
|
||||
// if this is set to false, then quality option must be "proof"
|
||||
randomize: true,
|
||||
// Whether or not to animate the layout
|
||||
animate: true,
|
||||
// Duration of animation in ms, if enabled
|
||||
animationDuration: 1000,
|
||||
// Easing of animation, if enabled
|
||||
animationEasing: undefined,
|
||||
// Fit the viewport to the repositioned nodes
|
||||
fit: true,
|
||||
// Padding around layout
|
||||
padding: 30,
|
||||
// Whether to include labels in node dimensions. Valid in "proof" quality
|
||||
nodeDimensionsIncludeLabels: false,
|
||||
// Whether or not simple nodes (non-compound nodes) are of uniform dimensions
|
||||
uniformNodeDimensions: false,
|
||||
// Whether to pack disconnected components - valid only if randomize: true
|
||||
packComponents: true,
|
||||
// Layout step - all, transformed, enforced, cose - for debug purpose only
|
||||
step: "all",
|
||||
|
||||
/* spectral layout options */
|
||||
|
||||
// False for random, true for greedy
|
||||
samplingType: true,
|
||||
// Sample size to construct distance matrix
|
||||
sampleSize: 25,
|
||||
// Separation amount between nodes
|
||||
nodeSeparation: 75,
|
||||
// Power iteration tolerance
|
||||
piTol: 0.0000001,
|
||||
|
||||
/* CoSE layout options */
|
||||
|
||||
// Node repulsion (non overlapping) multiplier
|
||||
nodeRepulsion: node => 4500,
|
||||
// Ideal edge (non nested) length
|
||||
idealEdgeLength: edge => 50,
|
||||
// Divisor to compute edge forces
|
||||
edgeElasticity: edge => 0.45,
|
||||
// Nesting factor (multiplier) to compute ideal edge length for nested edges
|
||||
nestingFactor: 0.1,
|
||||
// Gravity force (constant)
|
||||
gravity: 0.25,
|
||||
// Maximum number of iterations to perform
|
||||
numIter: 2500,
|
||||
// For enabling tiling
|
||||
tile: true,
|
||||
// The function that specifies the criteria for comparing nodes while sorting them during tiling operation.
|
||||
// Takes the node id as a parameter and the default tiling operation is perfomed when this option is not set.
|
||||
tilingCompareBy: undefined,
|
||||
// Represents the amount of the vertical space to put between the zero degree members during the tiling operation(can also be a function)
|
||||
tilingPaddingVertical: 10,
|
||||
// Represents the amount of the horizontal space to put between the zero degree members during the tiling operation(can also be a function)
|
||||
tilingPaddingHorizontal: 10,
|
||||
// Gravity range (constant) for compounds
|
||||
gravityRangeCompound: 1.5,
|
||||
// Gravity force (constant) for compounds
|
||||
gravityCompound: 1.0,
|
||||
// Gravity range (constant)
|
||||
gravityRange: 3.8,
|
||||
// Initial cooling factor for incremental layout
|
||||
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 */
|
||||
ready: () => {}, // on layoutready
|
||||
stop: () => {} // on layoutstop
|
||||
});
|
||||
|
||||
class Layout {
|
||||
constructor( options ){
|
||||
this.options = assign( {}, defaults, options );
|
||||
}
|
||||
|
||||
run(){
|
||||
let layout = this;
|
||||
let options = this.options;
|
||||
let cy = options.cy;
|
||||
let eles = options.eles;
|
||||
|
||||
let spectralResult = [];
|
||||
let xCoords;
|
||||
let yCoords;
|
||||
let coseResult = [];
|
||||
let components;
|
||||
let componentCenters = [];
|
||||
|
||||
// basic validity check for constraint inputs
|
||||
if(options.fixedNodeConstraint && (!Array.isArray(options.fixedNodeConstraint) || options.fixedNodeConstraint.length == 0)){
|
||||
options.fixedNodeConstraint = undefined;
|
||||
}
|
||||
|
||||
if(options.alignmentConstraint){
|
||||
if(options.alignmentConstraint.vertical && (!Array.isArray(options.alignmentConstraint.vertical) || options.alignmentConstraint.vertical.length == 0)){
|
||||
options.alignmentConstraint.vertical = undefined;
|
||||
}
|
||||
if(options.alignmentConstraint.horizontal && (!Array.isArray(options.alignmentConstraint.horizontal) || options.alignmentConstraint.horizontal.length == 0)){
|
||||
options.alignmentConstraint.horizontal = undefined;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(options.relativePlacementConstraint && (!Array.isArray(options.relativePlacementConstraint) || options.relativePlacementConstraint.length == 0)){
|
||||
options.relativePlacementConstraint = undefined;
|
||||
}
|
||||
|
||||
// if any constraint exists, set some options
|
||||
let constraintExist = options.fixedNodeConstraint || options.alignmentConstraint || options.relativePlacementConstraint;
|
||||
if(constraintExist){
|
||||
// constraints work with these options
|
||||
options.tile = false;
|
||||
options.packComponents = false;
|
||||
}
|
||||
|
||||
// decide component packing is enabled or not
|
||||
let layUtil;
|
||||
let packingEnabled = false;
|
||||
if(cy.layoutUtilities && options.packComponents){
|
||||
layUtil = cy.layoutUtilities("get");
|
||||
if(!layUtil)
|
||||
layUtil = cy.layoutUtilities();
|
||||
packingEnabled = true;
|
||||
}
|
||||
|
||||
if(eles.nodes().length > 0) {
|
||||
// if packing is not enabled, perform layout on the whole graph
|
||||
if(!packingEnabled){
|
||||
// store component center
|
||||
let boundingBox = options.eles.boundingBox();
|
||||
componentCenters.push({x: boundingBox.x1 + boundingBox.w / 2, y: boundingBox.y1 + boundingBox.h / 2});
|
||||
// apply spectral layout
|
||||
if(options.randomize){
|
||||
let result = spectralLayout(options);
|
||||
spectralResult.push(result);
|
||||
}
|
||||
// apply cose layout as postprocessing
|
||||
if(options.quality == "default" || options.quality == "proof"){
|
||||
coseResult.push(coseLayout(options, spectralResult[0]));
|
||||
aux.relocateComponent(componentCenters[0], coseResult[0], options); // relocate center to original position
|
||||
}
|
||||
else{
|
||||
aux.relocateComponent(componentCenters[0], spectralResult[0], options); // relocate center to original position
|
||||
}
|
||||
}
|
||||
else{ // packing is enabled
|
||||
let topMostNodes = aux.getTopMostNodes(options.eles.nodes());
|
||||
components = aux.connectComponents(cy, options.eles, topMostNodes);
|
||||
// store component centers
|
||||
components.forEach(function(component){
|
||||
let boundingBox = component.boundingBox();
|
||||
componentCenters.push({x: boundingBox.x1 + boundingBox.w / 2, y: boundingBox.y1 + boundingBox.h / 2});
|
||||
});
|
||||
|
||||
//send each component to spectral layout if randomized
|
||||
if(options.randomize){
|
||||
components.forEach(function(component){
|
||||
options.eles = component;
|
||||
spectralResult.push(spectralLayout(options));
|
||||
});
|
||||
}
|
||||
|
||||
if(options.quality == "default" || options.quality == "proof"){
|
||||
let toBeTiledNodes = cy.collection();
|
||||
if(options.tile){ // behave nodes to be tiled as one component
|
||||
let nodeIndexes = new Map();
|
||||
let xCoords = [];
|
||||
let yCoords = [];
|
||||
let count = 0;
|
||||
let tempSpectralResult = {nodeIndexes: nodeIndexes, xCoords: xCoords, yCoords: yCoords};
|
||||
let indexesToBeDeleted = [];
|
||||
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(toBeTiledNodes.length > 1){
|
||||
let boundingBox = toBeTiledNodes.boundingBox();
|
||||
componentCenters.push({x: boundingBox.x1 + boundingBox.w / 2, y: boundingBox.y1 + boundingBox.h / 2});
|
||||
components.push(toBeTiledNodes);
|
||||
spectralResult.push(tempSpectralResult);
|
||||
for(let i = indexesToBeDeleted.length-1; i >= 0; i--){
|
||||
components.splice(indexesToBeDeleted[i], 1);
|
||||
spectralResult.splice(indexesToBeDeleted[i], 1);
|
||||
componentCenters.splice(indexesToBeDeleted[i], 1);
|
||||
};
|
||||
}
|
||||
}
|
||||
components.forEach(function(component, index){ // send each component to cose layout
|
||||
options.eles = component;
|
||||
coseResult.push(coseLayout(options, spectralResult[index]));
|
||||
aux.relocateComponent(componentCenters[index], coseResult[index], options); // relocate center to original position
|
||||
});
|
||||
}
|
||||
else {
|
||||
components.forEach(function(component, index){
|
||||
aux.relocateComponent(componentCenters[index], spectralResult[index], options); // relocate center to original position
|
||||
});
|
||||
}
|
||||
|
||||
// packing
|
||||
let componentsEvaluated = new Set();
|
||||
if(components.length > 1){
|
||||
let subgraphs = [];
|
||||
let hiddenEles = eles.filter((ele) => {return ele.css('display') == 'none'});
|
||||
components.forEach(function(component, index){
|
||||
let nodeIndexes;
|
||||
if(options.quality == "draft"){
|
||||
nodeIndexes = spectralResult[index].nodeIndexes;
|
||||
}
|
||||
|
||||
if(component.nodes().not(hiddenEles).length > 0) {
|
||||
let subgraph = {};
|
||||
subgraph.edges = [];
|
||||
subgraph.nodes = [];
|
||||
let nodeIndex;
|
||||
component.nodes().not(hiddenEles).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{
|
||||
if(coseResult[index][node.id()]) {
|
||||
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(source.css("display") != "none" && target.css("display") != "none") {
|
||||
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{
|
||||
if(coseResult[index][source.id()] && coseResult[index][target.id()]) {
|
||||
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()});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if(subgraph.nodes.length > 0) {
|
||||
subgraphs.push(subgraph);
|
||||
componentsEvaluated.add(index);
|
||||
}
|
||||
}
|
||||
});
|
||||
let shiftResult = layUtil.packComponents(subgraphs, options.randomize).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{
|
||||
let count = 0;
|
||||
componentsEvaluated.forEach((index) => {
|
||||
Object.keys(coseResult[index]).forEach(function (item) {
|
||||
let nodeRectangle = coseResult[index][item];
|
||||
nodeRectangle.setCenter(nodeRectangle.getCenterX() + shiftResult[count].dx, nodeRectangle.getCenterY() + shiftResult[count].dy);
|
||||
});
|
||||
count++;
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get each element's calculated position
|
||||
let getPositions = function(ele, i ){
|
||||
if(options.quality == "default" || options.quality == "proof") {
|
||||
if(typeof ele === "number") {
|
||||
ele = i;
|
||||
}
|
||||
let pos;
|
||||
let node;
|
||||
let theId = ele.data('id');
|
||||
coseResult.forEach(function(result){
|
||||
if (theId in result){
|
||||
pos = {x: result[theId].getRect().getCenterX(), y: result[theId].getRect().getCenterY()};
|
||||
node = result[theId];
|
||||
}
|
||||
});
|
||||
if(options.nodeDimensionsIncludeLabels){
|
||||
if(node.labelWidth){
|
||||
if(node.labelPosHorizontal == "left"){
|
||||
pos.x += node.labelWidth/2;
|
||||
}
|
||||
else if(node.labelPosHorizontal == "right"){
|
||||
pos.x -= node.labelWidth/2;
|
||||
}
|
||||
}
|
||||
if(node.labelHeight){
|
||||
if(node.labelPosVertical == "top"){
|
||||
pos.y += node.labelHeight/2;
|
||||
}
|
||||
else if(node.labelPosVertical == "bottom"){
|
||||
pos.y -= node.labelHeight/2;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(pos == undefined)
|
||||
pos = {x: ele.position("x"), y: ele.position("y")};
|
||||
return {
|
||||
x: pos.x,
|
||||
y: pos.y
|
||||
};
|
||||
}
|
||||
else{
|
||||
let pos;
|
||||
spectralResult.forEach(function(result){
|
||||
let index = result.nodeIndexes.get(ele.id());
|
||||
if(index != undefined){
|
||||
pos = {x: result.xCoords[index], y: result.yCoords[index]};
|
||||
}
|
||||
});
|
||||
if(pos == undefined)
|
||||
pos = {x: ele.position("x"), y: ele.position("y")};
|
||||
return {
|
||||
x: pos.x,
|
||||
y: pos.y
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// quality = "draft" and randomize = false are contradictive so in that case positions don't change
|
||||
if(options.quality == "default" || options.quality == "proof" || options.randomize) {
|
||||
// transfer calculated positions to nodes (positions of only simple nodes are evaluated, compounds are positioned automatically)
|
||||
let parentsWithoutChildren = aux.calcParentsWithoutChildren(cy, eles);
|
||||
let hiddenEles = eles.filter((ele) => {return ele.css('display') == 'none'});
|
||||
options.eles = eles.not(hiddenEles);
|
||||
|
||||
eles.nodes().not(":parent").not(hiddenEles).layoutPositions(layout, options, getPositions);
|
||||
|
||||
if(parentsWithoutChildren.length > 0){
|
||||
parentsWithoutChildren.forEach((ele) => {
|
||||
ele.position(getPositions(ele));
|
||||
});
|
||||
}
|
||||
}
|
||||
else{
|
||||
console.log("If randomize option is set to false, then quality option must be 'default' or 'proof'.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Layout;
|
||||
+428
@@ -0,0 +1,428 @@
|
||||
/**
|
||||
The implementation of the spectral layout that is the first part of the fcose layout algorithm
|
||||
*/
|
||||
|
||||
const aux = require('./auxiliary');
|
||||
const Matrix = require('cose-base').layoutBase.Matrix;
|
||||
const SVD = require('cose-base').layoutBase.SVD;
|
||||
|
||||
// main function that spectral layout is processed
|
||||
let spectralLayout = function(options){
|
||||
|
||||
let cy = options.cy;
|
||||
let eles = options.eles;
|
||||
let nodes = eles.nodes();
|
||||
let parentNodes = eles.nodes(":parent");
|
||||
|
||||
let dummyNodes = new Map(); // map to keep dummy nodes and their neighbors
|
||||
let nodeIndexes = new Map(); // map to keep indexes to nodes
|
||||
let parentChildMap = new Map(); // mapping btw. compound and its representative node
|
||||
let allNodesNeighborhood = []; // array to keep neighborhood of all nodes
|
||||
let xCoords = [];
|
||||
let yCoords = [];
|
||||
|
||||
let samplesColumn = []; // sampled vertices
|
||||
let minDistancesColumn = [];
|
||||
let C = []; // column sampling matrix
|
||||
let PHI = []; // intersection of column and row sampling matrices
|
||||
let INV = []; // inverse of PHI
|
||||
|
||||
let firstSample; // the first sampled node
|
||||
let nodeSize;
|
||||
|
||||
const infinity = 100000000;
|
||||
const small = 0.000000001;
|
||||
|
||||
let piTol = options.piTol;
|
||||
let samplingType = options.samplingType; // false for random, true for greedy
|
||||
let nodeSeparation = options.nodeSeparation;
|
||||
let sampleSize;
|
||||
|
||||
/**** Spectral-preprocessing functions ****/
|
||||
|
||||
/**** Spectral layout functions ****/
|
||||
|
||||
// determine which columns to be sampled
|
||||
let randomSampleCR = function() {
|
||||
let sample = 0;
|
||||
let count = 0;
|
||||
let flag = false;
|
||||
|
||||
while(count < sampleSize){
|
||||
sample = Math.floor(Math.random() * nodeSize);
|
||||
|
||||
flag = false;
|
||||
for(let i = 0; i < count; i++){
|
||||
if(samplesColumn[i] == sample){
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!flag){
|
||||
samplesColumn[count] = sample;
|
||||
count++;
|
||||
}
|
||||
else{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// takes the index of the node(pivot) to initiate BFS as a parameter
|
||||
let BFS = function(pivot, index, samplingMethod){
|
||||
let path = []; // the front of the path
|
||||
let front = 0; // the back of the path
|
||||
let back = 0;
|
||||
let current = 0;
|
||||
let temp;
|
||||
let distance = [];
|
||||
|
||||
let max_dist = 0; // the furthest node to be returned
|
||||
let max_ind = 1;
|
||||
|
||||
for(let i = 0; i < nodeSize; i++){
|
||||
distance[i] = infinity;
|
||||
}
|
||||
|
||||
path[back] = pivot;
|
||||
distance[pivot] = 0;
|
||||
|
||||
while(back >= front){
|
||||
current = path[front++];
|
||||
let neighbors = allNodesNeighborhood[current];
|
||||
for(let i = 0; i < neighbors.length; i++){
|
||||
temp = nodeIndexes.get(neighbors[i]);
|
||||
if(distance[temp] == infinity){
|
||||
distance[temp] = distance[current] + 1;
|
||||
path[++back] = temp;
|
||||
}
|
||||
}
|
||||
C[current][index] = distance[current] * nodeSeparation;
|
||||
}
|
||||
|
||||
if(samplingMethod){
|
||||
for(let i = 0; i < nodeSize; i++){
|
||||
if(C[i][index] < minDistancesColumn[i])
|
||||
minDistancesColumn[i] = C[i][index];
|
||||
}
|
||||
|
||||
for(let i = 0; i < nodeSize; i++){
|
||||
if(minDistancesColumn[i] > max_dist ){
|
||||
max_dist = minDistancesColumn[i];
|
||||
max_ind = i;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return max_ind;
|
||||
};
|
||||
|
||||
// apply BFS to all nodes or selected samples
|
||||
let allBFS = function(samplingMethod){
|
||||
|
||||
let sample;
|
||||
|
||||
if(!samplingMethod){
|
||||
randomSampleCR();
|
||||
|
||||
// call BFS
|
||||
for(let i = 0; i < sampleSize; i++){
|
||||
BFS(samplesColumn[i], i, samplingMethod, false);
|
||||
}
|
||||
}
|
||||
else{
|
||||
sample = Math.floor(Math.random() * nodeSize);
|
||||
firstSample = sample;
|
||||
|
||||
for(let i = 0; i < nodeSize; i++){
|
||||
minDistancesColumn[i] = infinity;
|
||||
}
|
||||
|
||||
for(let i = 0; i < sampleSize; i++){
|
||||
samplesColumn[i] = sample;
|
||||
sample = BFS(sample, i, samplingMethod);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// form the squared distances for C
|
||||
for(let i = 0; i < nodeSize; i++){
|
||||
for(let j = 0; j < sampleSize; j++){
|
||||
C[i][j] *= C[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
// form PHI
|
||||
for(let i = 0; i < sampleSize; i++){
|
||||
PHI[i] = [];
|
||||
}
|
||||
|
||||
for(let i = 0; i < sampleSize; i++){
|
||||
for(let j = 0; j < sampleSize; j++){
|
||||
PHI[i][j] = C[samplesColumn[j]][i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// perform the SVD algorithm and apply a regularization step
|
||||
let sample = function(){
|
||||
|
||||
let SVDResult = SVD.svd(PHI);
|
||||
|
||||
let a_q = SVDResult.S;
|
||||
let a_u = SVDResult.U;
|
||||
let a_v = SVDResult.V;
|
||||
|
||||
let max_s = a_q[0]*a_q[0]*a_q[0];
|
||||
|
||||
let a_Sig = [];
|
||||
|
||||
// regularization
|
||||
for(let i = 0; i < sampleSize; i++){
|
||||
a_Sig[i] = [];
|
||||
for(let j = 0; j < sampleSize; j++){
|
||||
a_Sig[i][j] = 0;
|
||||
if(i == j){
|
||||
a_Sig[i][j] = a_q[i]/(a_q[i]*a_q[i] + max_s/(a_q[i]*a_q[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INV = Matrix.multMat(Matrix.multMat(a_v, a_Sig), Matrix.transpose(a_u));
|
||||
|
||||
};
|
||||
|
||||
// calculate final coordinates
|
||||
let powerIteration = function(){
|
||||
// two largest eigenvalues
|
||||
let theta1;
|
||||
let theta2;
|
||||
|
||||
// initial guesses for eigenvectors
|
||||
let Y1 = [];
|
||||
let Y2 = [];
|
||||
|
||||
let V1 = [];
|
||||
let V2 = [];
|
||||
|
||||
for(let i = 0; i < nodeSize; i++){
|
||||
Y1[i] = Math.random();
|
||||
Y2[i] = Math.random();
|
||||
}
|
||||
|
||||
Y1 = Matrix.normalize(Y1);
|
||||
Y2 = Matrix.normalize(Y2);
|
||||
|
||||
let count = 0;
|
||||
// to keep track of the improvement ratio in power iteration
|
||||
let current = small;
|
||||
let previous = small;
|
||||
|
||||
let temp;
|
||||
|
||||
while(true){
|
||||
count++;
|
||||
|
||||
for(let i = 0; i < nodeSize; i++){
|
||||
V1[i] = Y1[i];
|
||||
}
|
||||
|
||||
Y1 = Matrix.multGamma(Matrix.multL(Matrix.multGamma(V1), C, INV));
|
||||
theta1 = Matrix.dotProduct(V1, Y1);
|
||||
Y1 = Matrix.normalize(Y1);
|
||||
|
||||
current = Matrix.dotProduct(V1, Y1);
|
||||
|
||||
temp = Math.abs(current/previous);
|
||||
|
||||
if(temp <= 1 + piTol && temp >= 1){
|
||||
break;
|
||||
}
|
||||
|
||||
previous = current;
|
||||
}
|
||||
|
||||
for(let i = 0; i < nodeSize; i++){
|
||||
V1[i] = Y1[i];
|
||||
}
|
||||
|
||||
count = 0;
|
||||
previous = small;
|
||||
while(true){
|
||||
count++;
|
||||
|
||||
for(let i = 0; i < nodeSize; i++){
|
||||
V2[i] = Y2[i];
|
||||
}
|
||||
|
||||
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 = Matrix.dotProduct(V2, Y2);
|
||||
|
||||
temp = Math.abs(current/previous);
|
||||
|
||||
if(temp <= 1 + piTol && temp >= 1){
|
||||
break;
|
||||
}
|
||||
|
||||
previous = current;
|
||||
}
|
||||
|
||||
for(let i = 0; i < nodeSize; i++){
|
||||
V2[i] = Y2[i];
|
||||
}
|
||||
|
||||
// theta1 now contains dominant eigenvalue
|
||||
// theta2 now contains the second-largest eigenvalue
|
||||
// V1 now contains theta1's eigenvector
|
||||
// V2 now contains theta2's eigenvector
|
||||
|
||||
//populate the two vectors
|
||||
xCoords = Matrix.multCons(V1, Math.sqrt(Math.abs(theta1)));
|
||||
yCoords = Matrix.multCons(V2, Math.sqrt(Math.abs(theta2)));
|
||||
|
||||
};
|
||||
|
||||
/**** Preparation for spectral layout (Preprocessing) ****/
|
||||
|
||||
// connect disconnected components (first top level, then inside of each compound node)
|
||||
aux.connectComponents(cy, eles, aux.getTopMostNodes(nodes), dummyNodes);
|
||||
|
||||
parentNodes.forEach(function( ele ){
|
||||
aux.connectComponents(cy, eles, aux.getTopMostNodes(ele.descendants().intersection(eles)), dummyNodes);
|
||||
});
|
||||
|
||||
// assign indexes to nodes (first real, then dummy nodes)
|
||||
let index = 0;
|
||||
for(let i = 0; i < nodes.length; i++){
|
||||
if(!nodes[i].isParent()){
|
||||
nodeIndexes.set(nodes[i].id(), index++);
|
||||
}
|
||||
}
|
||||
|
||||
for (let key of dummyNodes.keys()) {
|
||||
nodeIndexes.set(key, index++);
|
||||
}
|
||||
|
||||
// instantiate the neighborhood matrix
|
||||
for(let i = 0; i < nodeIndexes.size; i++){
|
||||
allNodesNeighborhood[i] = [];
|
||||
}
|
||||
|
||||
// form a parent-child map to keep representative node of each compound node
|
||||
parentNodes.forEach(function( ele ){
|
||||
let children = ele.children().intersection(eles);
|
||||
|
||||
// let random = 0;
|
||||
while(children.nodes(":childless").length == 0){
|
||||
// random = Math.floor(Math.random() * children.nodes().length); // if all children are compound then proceed randomly
|
||||
children = children.nodes()[0].children().intersection(eles);
|
||||
}
|
||||
// select the representative node - we can apply different methods here
|
||||
// random = Math.floor(Math.random() * children.nodes(":childless").length);
|
||||
let index = 0;
|
||||
let min = children.nodes(":childless")[0].connectedEdges().length;
|
||||
children.nodes(":childless").forEach(function(ele2, i){
|
||||
if(ele2.connectedEdges().length < min){
|
||||
min = ele2.connectedEdges().length;
|
||||
index = i;
|
||||
}
|
||||
});
|
||||
parentChildMap.set(ele.id(), children.nodes(":childless")[index].id());
|
||||
});
|
||||
|
||||
// add neighborhood relations (first real, then dummy nodes)
|
||||
nodes.forEach(function( ele ){
|
||||
let eleIndex;
|
||||
|
||||
if(ele.isParent())
|
||||
eleIndex = nodeIndexes.get(parentChildMap.get(ele.id()));
|
||||
else
|
||||
eleIndex = nodeIndexes.get(ele.id());
|
||||
|
||||
ele.neighborhood().nodes().forEach(function(node){
|
||||
if(eles.intersection(ele.edgesWith(node)).length > 0){
|
||||
if(node.isParent())
|
||||
allNodesNeighborhood[eleIndex].push(parentChildMap.get(node.id()));
|
||||
else
|
||||
allNodesNeighborhood[eleIndex].push(node.id());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
for (let key of dummyNodes.keys()) {
|
||||
let eleIndex = nodeIndexes.get(key);
|
||||
let disconnectedId;
|
||||
dummyNodes.get(key).forEach(function(id){
|
||||
if(cy.getElementById(id).isParent())
|
||||
disconnectedId = parentChildMap.get(id);
|
||||
else
|
||||
disconnectedId = id;
|
||||
|
||||
allNodesNeighborhood[eleIndex].push(disconnectedId);
|
||||
allNodesNeighborhood[nodeIndexes.get(disconnectedId)].push(key);
|
||||
});
|
||||
}
|
||||
|
||||
// nodeSize now only considers the size of transformed graph
|
||||
nodeSize = nodeIndexes.size;
|
||||
|
||||
let spectralResult;
|
||||
|
||||
// If number of nodes in transformed graph is 1 or 2, either SVD or powerIteration causes problem
|
||||
// So skip spectral and layout the graph with cose
|
||||
if(nodeSize > 2) {
|
||||
// if # of nodes in transformed graph is smaller than sample size,
|
||||
// then use # of nodes as sample size
|
||||
sampleSize = nodeSize < options.sampleSize ? nodeSize : options.sampleSize;
|
||||
|
||||
// instantiates the partial matrices that will be used in spectral layout
|
||||
for(let i = 0; i < nodeSize; i++){
|
||||
C[i] = [];
|
||||
}
|
||||
for(let i = 0; i < sampleSize; i++){
|
||||
INV[i] = [];
|
||||
}
|
||||
|
||||
/**** Apply spectral layout ****/
|
||||
|
||||
if(options.quality == "draft" || options.step == "all"){
|
||||
allBFS(samplingType);
|
||||
sample();
|
||||
powerIteration();
|
||||
|
||||
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;
|
||||
}
|
||||
else {
|
||||
let iterator = nodeIndexes.keys();
|
||||
let firstNode = cy.getElementById(iterator.next().value);
|
||||
let firstNodePos = firstNode.position();
|
||||
let firstNodeWidth = firstNode.outerWidth();
|
||||
xCoords.push(firstNodePos.x);
|
||||
yCoords.push(firstNodePos.y);
|
||||
if(nodeSize == 2){
|
||||
let secondNode = cy.getElementById(iterator.next().value);
|
||||
let secondNodeWidth = secondNode.outerWidth();
|
||||
xCoords.push(firstNodePos.x + firstNodeWidth / 2 + secondNodeWidth / 2 + options.idealEdgeLength);
|
||||
yCoords.push(firstNodePos.y);
|
||||
}
|
||||
|
||||
spectralResult = { nodeIndexes: nodeIndexes, xCoords: xCoords, yCoords: yCoords };
|
||||
return spectralResult;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { spectralLayout };
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
const impl = require('./fcose');
|
||||
|
||||
// registers the extension on a cytoscape lib ref
|
||||
let register = function( cytoscape ){
|
||||
if( !cytoscape ){ return; } // can't register if cytoscape unspecified
|
||||
|
||||
cytoscape( 'layout', 'fcose', impl ); // register with cytoscape.js
|
||||
};
|
||||
|
||||
if( typeof cytoscape !== 'undefined' ){ // expose to global cytoscape (i.e. window.cytoscape)
|
||||
register( cytoscape );
|
||||
}
|
||||
|
||||
module.exports = register;
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
const chai = require('chai');
|
||||
|
||||
describe('This', function(){
|
||||
it('does that', function(){
|
||||
expect( true ).to.be.true;
|
||||
});
|
||||
});
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
const path = require('path');
|
||||
const pkg = require('./package.json');
|
||||
const camelcase = require('camelcase');
|
||||
const process = require('process');
|
||||
const webpack = require('webpack');
|
||||
const env = process.env;
|
||||
const NODE_ENV = env.NODE_ENV;
|
||||
const MIN = env.MIN;
|
||||
const PROD = NODE_ENV === 'production';
|
||||
|
||||
let config = {
|
||||
devtool: PROD ? false : 'inline-source-map',
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
path: path.join( __dirname ),
|
||||
filename: pkg.name + '.js',
|
||||
library: camelcase( pkg.name ),
|
||||
libraryTarget: 'umd',
|
||||
globalObject: 'this'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{ test: /\.js$/, exclude: /node_modules/, use: 'babel-loader' }
|
||||
]
|
||||
},
|
||||
optimization: {
|
||||
minimize: MIN ? true : false,
|
||||
},
|
||||
externals: PROD ? {
|
||||
'cose-base': {
|
||||
commonjs2: 'cose-base',
|
||||
commonjs: 'cose-base',
|
||||
amd: 'cose-base',
|
||||
root: 'coseBase'
|
||||
}
|
||||
} : {}
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
Reference in New Issue
Block a user