First commit.

Signed-off-by: Chen Xiao <abigwc@gmail.com>
This commit is contained in:
Chen Xiao
2026-05-08 14:43:16 +08:00
commit 0b64e2de94
10989 changed files with 2253791 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"presets": ["env"]
}
+21
View File
@@ -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
View File
@@ -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).
![](https://github.com/iVis-at-Bilkent/layout-base/blob/master/layout-schema.png)
+20
View File
@@ -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"
}
+36
View File
@@ -0,0 +1,36 @@
'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');
module.exports = layoutBase;
+4334
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

+1
View File
@@ -0,0 +1 @@
browser=Chrome.INTEGRATED
+9
View File
@@ -0,0 +1,9 @@
<?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>
<file>file:/home/hasanbalci/Documents/github/layout-base/layout-base.js</file>
</group>
</open-files>
</project-private>
+3
View File
@@ -0,0 +1,3 @@
files.encoding=UTF-8
site.root.folder=
source.folder=
+9
View File
@@ -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
View File
@@ -0,0 +1,37 @@
{
"name": "layout-base",
"version": "1.0.2",
"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
View File
@@ -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
View File
@@ -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;
+491
View File
@@ -0,0 +1,491 @@
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 s = this.edges.length;
for (var i = 0; i < s; i++)
{
edge = this.edges[i];
if (this.isOneAncestorOfOther(edge.source, edge.target))
{
return true;
}
}
return false;
};
module.exports = LGraphManager;
+5
View File
@@ -0,0 +1,5 @@
function LGraphObject(vGraphObject) {
this.vGraphObject = vGraphObject;
}
module.exports = LGraphObject;
+405
View File
@@ -0,0 +1,405 @@
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 > width){
this.rect.x -= (this.labelWidth - width) / 2;
this.setWidth(this.labelWidth);
}
if(this.labelHeight > height){
if(this.labelPos == "center"){
this.rect.y -= (this.labelHeight - height) / 2;
}
else if(this.labelPos == "top"){
this.rect.y -= (this.labelHeight - height);
}
this.setHeight(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
View File
@@ -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;
+70
View File
@@ -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
View File
@@ -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.idealEdgeLength = FDLayoutConstants.DEFAULT_EDGE_LENGTH;
this.springConstant = FDLayoutConstants.DEFAULT_SPRING_STRENGTH;
this.repulsionConstant = FDLayoutConstants.DEFAULT_REPULSION_STRENGTH;
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 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];
edge.idealLength = this.idealEdgeLength;
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 += FDLayoutConstants.DEFAULT_EDGE_LENGTH *
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);
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 = this.springConstant * (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);
repulsionForce = this.repulsionConstant * 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;
+34
View File
@@ -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;
+15
View File
@@ -0,0 +1,15 @@
var LEdge = require('../LEdge');
var FDLayoutConstants = require('./FDLayoutConstants');
function FDLayoutEdge(source, target, vEdge) {
LEdge.call(this, source, target, vEdge);
this.idealLength = FDLayoutConstants.DEFAULT_EDGE_LENGTH;
}
FDLayoutEdge.prototype = Object.create(LEdge.prototype);
for (var prop in LEdge) {
FDLayoutEdge[prop] = LEdge[prop];
}
module.exports = FDLayoutEdge;
+42
View File
@@ -0,0 +1,42 @@
var LNode = require('../LNode');
function FDLayoutNode(gm, loc, size, vNode) {
// alternative constructor is handled inside LNode
LNode.call(this, gm, loc, size, vNode);
//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;
+30
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+567
View File
@@ -0,0 +1,567 @@
/**
* 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);
}
};
// -----------------------------------------------------------------------------
// 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
View File
@@ -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
View File
@@ -0,0 +1,7 @@
function Integer() {
}
Integer.MAX_VALUE = 2147483647;
Integer.MIN_VALUE = -2147483648;
module.exports = Integer;
+130
View File
@@ -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;
+73
View File
@@ -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
View File
@@ -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;
+77
View File
@@ -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;
+12
View File
@@ -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;
+130
View File
@@ -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;
+157
View File
@@ -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;
+29
View File
@@ -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;
+158
View File
@@ -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
View File
@@ -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;