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
+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;