mirror of
https://git.code.sf.net/p/seeddms/code
synced 2025-02-07 07:34:58 +00:00
3158 lines
88 KiB
JavaScript
3158 lines
88 KiB
JavaScript
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||
"use strict"
|
||
|
||
module.exports = createRBTree
|
||
|
||
var RED = 0
|
||
var BLACK = 1
|
||
|
||
function RBNode(color, key, value, left, right, count) {
|
||
this._color = color
|
||
this.key = key
|
||
this.value = value
|
||
this.left = left
|
||
this.right = right
|
||
this._count = count
|
||
}
|
||
|
||
function cloneNode(node) {
|
||
return new RBNode(node._color, node.key, node.value, node.left, node.right, node._count)
|
||
}
|
||
|
||
function repaint(color, node) {
|
||
return new RBNode(color, node.key, node.value, node.left, node.right, node._count)
|
||
}
|
||
|
||
function recount(node) {
|
||
node._count = 1 + (node.left ? node.left._count : 0) + (node.right ? node.right._count : 0)
|
||
}
|
||
|
||
function RedBlackTree(compare, root) {
|
||
this._compare = compare
|
||
this.root = root
|
||
}
|
||
|
||
var proto = RedBlackTree.prototype
|
||
|
||
Object.defineProperty(proto, "keys", {
|
||
get: function() {
|
||
var result = []
|
||
this.forEach(function(k,v) {
|
||
result.push(k)
|
||
})
|
||
return result
|
||
}
|
||
})
|
||
|
||
Object.defineProperty(proto, "values", {
|
||
get: function() {
|
||
var result = []
|
||
this.forEach(function(k,v) {
|
||
result.push(v)
|
||
})
|
||
return result
|
||
}
|
||
})
|
||
|
||
//Returns the number of nodes in the tree
|
||
Object.defineProperty(proto, "length", {
|
||
get: function() {
|
||
if(this.root) {
|
||
return this.root._count
|
||
}
|
||
return 0
|
||
}
|
||
})
|
||
|
||
//Insert a new item into the tree
|
||
proto.insert = function(key, value) {
|
||
var cmp = this._compare
|
||
//Find point to insert new node at
|
||
var n = this.root
|
||
var n_stack = []
|
||
var d_stack = []
|
||
while(n) {
|
||
var d = cmp(key, n.key)
|
||
n_stack.push(n)
|
||
d_stack.push(d)
|
||
if(d <= 0) {
|
||
n = n.left
|
||
} else {
|
||
n = n.right
|
||
}
|
||
}
|
||
//Rebuild path to leaf node
|
||
n_stack.push(new RBNode(RED, key, value, null, null, 1))
|
||
for(var s=n_stack.length-2; s>=0; --s) {
|
||
var n = n_stack[s]
|
||
if(d_stack[s] <= 0) {
|
||
n_stack[s] = new RBNode(n._color, n.key, n.value, n_stack[s+1], n.right, n._count+1)
|
||
} else {
|
||
n_stack[s] = new RBNode(n._color, n.key, n.value, n.left, n_stack[s+1], n._count+1)
|
||
}
|
||
}
|
||
//Rebalance tree using rotations
|
||
//console.log("start insert", key, d_stack)
|
||
for(var s=n_stack.length-1; s>1; --s) {
|
||
var p = n_stack[s-1]
|
||
var n = n_stack[s]
|
||
if(p._color === BLACK || n._color === BLACK) {
|
||
break
|
||
}
|
||
var pp = n_stack[s-2]
|
||
if(pp.left === p) {
|
||
if(p.left === n) {
|
||
var y = pp.right
|
||
if(y && y._color === RED) {
|
||
//console.log("LLr")
|
||
p._color = BLACK
|
||
pp.right = repaint(BLACK, y)
|
||
pp._color = RED
|
||
s -= 1
|
||
} else {
|
||
//console.log("LLb")
|
||
pp._color = RED
|
||
pp.left = p.right
|
||
p._color = BLACK
|
||
p.right = pp
|
||
n_stack[s-2] = p
|
||
n_stack[s-1] = n
|
||
recount(pp)
|
||
recount(p)
|
||
if(s >= 3) {
|
||
var ppp = n_stack[s-3]
|
||
if(ppp.left === pp) {
|
||
ppp.left = p
|
||
} else {
|
||
ppp.right = p
|
||
}
|
||
}
|
||
break
|
||
}
|
||
} else {
|
||
var y = pp.right
|
||
if(y && y._color === RED) {
|
||
//console.log("LRr")
|
||
p._color = BLACK
|
||
pp.right = repaint(BLACK, y)
|
||
pp._color = RED
|
||
s -= 1
|
||
} else {
|
||
//console.log("LRb")
|
||
p.right = n.left
|
||
pp._color = RED
|
||
pp.left = n.right
|
||
n._color = BLACK
|
||
n.left = p
|
||
n.right = pp
|
||
n_stack[s-2] = n
|
||
n_stack[s-1] = p
|
||
recount(pp)
|
||
recount(p)
|
||
recount(n)
|
||
if(s >= 3) {
|
||
var ppp = n_stack[s-3]
|
||
if(ppp.left === pp) {
|
||
ppp.left = n
|
||
} else {
|
||
ppp.right = n
|
||
}
|
||
}
|
||
break
|
||
}
|
||
}
|
||
} else {
|
||
if(p.right === n) {
|
||
var y = pp.left
|
||
if(y && y._color === RED) {
|
||
//console.log("RRr", y.key)
|
||
p._color = BLACK
|
||
pp.left = repaint(BLACK, y)
|
||
pp._color = RED
|
||
s -= 1
|
||
} else {
|
||
//console.log("RRb")
|
||
pp._color = RED
|
||
pp.right = p.left
|
||
p._color = BLACK
|
||
p.left = pp
|
||
n_stack[s-2] = p
|
||
n_stack[s-1] = n
|
||
recount(pp)
|
||
recount(p)
|
||
if(s >= 3) {
|
||
var ppp = n_stack[s-3]
|
||
if(ppp.right === pp) {
|
||
ppp.right = p
|
||
} else {
|
||
ppp.left = p
|
||
}
|
||
}
|
||
break
|
||
}
|
||
} else {
|
||
var y = pp.left
|
||
if(y && y._color === RED) {
|
||
//console.log("RLr")
|
||
p._color = BLACK
|
||
pp.left = repaint(BLACK, y)
|
||
pp._color = RED
|
||
s -= 1
|
||
} else {
|
||
//console.log("RLb")
|
||
p.left = n.right
|
||
pp._color = RED
|
||
pp.right = n.left
|
||
n._color = BLACK
|
||
n.right = p
|
||
n.left = pp
|
||
n_stack[s-2] = n
|
||
n_stack[s-1] = p
|
||
recount(pp)
|
||
recount(p)
|
||
recount(n)
|
||
if(s >= 3) {
|
||
var ppp = n_stack[s-3]
|
||
if(ppp.right === pp) {
|
||
ppp.right = n
|
||
} else {
|
||
ppp.left = n
|
||
}
|
||
}
|
||
break
|
||
}
|
||
}
|
||
}
|
||
}
|
||
//Return new tree
|
||
n_stack[0]._color = BLACK
|
||
return new RedBlackTree(cmp, n_stack[0])
|
||
}
|
||
|
||
|
||
//Visit all nodes inorder
|
||
function doVisitFull(visit, node) {
|
||
if(node.left) {
|
||
var v = doVisitFull(visit, node.left)
|
||
if(v) { return v }
|
||
}
|
||
var v = visit(node.key, node.value)
|
||
if(v) { return v }
|
||
if(node.right) {
|
||
return doVisitFull(visit, node.right)
|
||
}
|
||
}
|
||
|
||
//Visit half nodes in order
|
||
function doVisitHalf(lo, compare, visit, node) {
|
||
var l = compare(lo, node.key)
|
||
if(l <= 0) {
|
||
if(node.left) {
|
||
var v = doVisitHalf(lo, compare, visit, node.left)
|
||
if(v) { return v }
|
||
}
|
||
var v = visit(node.key, node.value)
|
||
if(v) { return v }
|
||
}
|
||
if(node.right) {
|
||
return doVisitHalf(lo, compare, visit, node.right)
|
||
}
|
||
}
|
||
|
||
//Visit all nodes within a range
|
||
function doVisit(lo, hi, compare, visit, node) {
|
||
var l = compare(lo, node.key)
|
||
var h = compare(hi, node.key)
|
||
var v
|
||
if(l <= 0) {
|
||
if(node.left) {
|
||
v = doVisit(lo, hi, compare, visit, node.left)
|
||
if(v) { return v }
|
||
}
|
||
if(h > 0) {
|
||
v = visit(node.key, node.value)
|
||
if(v) { return v }
|
||
}
|
||
}
|
||
if(h > 0 && node.right) {
|
||
return doVisit(lo, hi, compare, visit, node.right)
|
||
}
|
||
}
|
||
|
||
|
||
proto.forEach = function rbTreeForEach(visit, lo, hi) {
|
||
if(!this.root) {
|
||
return
|
||
}
|
||
switch(arguments.length) {
|
||
case 1:
|
||
return doVisitFull(visit, this.root)
|
||
break
|
||
|
||
case 2:
|
||
return doVisitHalf(lo, this._compare, visit, this.root)
|
||
break
|
||
|
||
case 3:
|
||
if(this._compare(lo, hi) >= 0) {
|
||
return
|
||
}
|
||
return doVisit(lo, hi, this._compare, visit, this.root)
|
||
break
|
||
}
|
||
}
|
||
|
||
//First item in list
|
||
Object.defineProperty(proto, "begin", {
|
||
get: function() {
|
||
var stack = []
|
||
var n = this.root
|
||
while(n) {
|
||
stack.push(n)
|
||
n = n.left
|
||
}
|
||
return new RedBlackTreeIterator(this, stack)
|
||
}
|
||
})
|
||
|
||
//Last item in list
|
||
Object.defineProperty(proto, "end", {
|
||
get: function() {
|
||
var stack = []
|
||
var n = this.root
|
||
while(n) {
|
||
stack.push(n)
|
||
n = n.right
|
||
}
|
||
return new RedBlackTreeIterator(this, stack)
|
||
}
|
||
})
|
||
|
||
//Find the ith item in the tree
|
||
proto.at = function(idx) {
|
||
if(idx < 0) {
|
||
return new RedBlackTreeIterator(this, [])
|
||
}
|
||
var n = this.root
|
||
var stack = []
|
||
while(true) {
|
||
stack.push(n)
|
||
if(n.left) {
|
||
if(idx < n.left._count) {
|
||
n = n.left
|
||
continue
|
||
}
|
||
idx -= n.left._count
|
||
}
|
||
if(!idx) {
|
||
return new RedBlackTreeIterator(this, stack)
|
||
}
|
||
idx -= 1
|
||
if(n.right) {
|
||
if(idx >= n.right._count) {
|
||
break
|
||
}
|
||
n = n.right
|
||
} else {
|
||
break
|
||
}
|
||
}
|
||
return new RedBlackTreeIterator(this, [])
|
||
}
|
||
|
||
proto.ge = function(key) {
|
||
var cmp = this._compare
|
||
var n = this.root
|
||
var stack = []
|
||
var last_ptr = 0
|
||
while(n) {
|
||
var d = cmp(key, n.key)
|
||
stack.push(n)
|
||
if(d <= 0) {
|
||
last_ptr = stack.length
|
||
}
|
||
if(d <= 0) {
|
||
n = n.left
|
||
} else {
|
||
n = n.right
|
||
}
|
||
}
|
||
stack.length = last_ptr
|
||
return new RedBlackTreeIterator(this, stack)
|
||
}
|
||
|
||
proto.gt = function(key) {
|
||
var cmp = this._compare
|
||
var n = this.root
|
||
var stack = []
|
||
var last_ptr = 0
|
||
while(n) {
|
||
var d = cmp(key, n.key)
|
||
stack.push(n)
|
||
if(d < 0) {
|
||
last_ptr = stack.length
|
||
}
|
||
if(d < 0) {
|
||
n = n.left
|
||
} else {
|
||
n = n.right
|
||
}
|
||
}
|
||
stack.length = last_ptr
|
||
return new RedBlackTreeIterator(this, stack)
|
||
}
|
||
|
||
proto.lt = function(key) {
|
||
var cmp = this._compare
|
||
var n = this.root
|
||
var stack = []
|
||
var last_ptr = 0
|
||
while(n) {
|
||
var d = cmp(key, n.key)
|
||
stack.push(n)
|
||
if(d > 0) {
|
||
last_ptr = stack.length
|
||
}
|
||
if(d <= 0) {
|
||
n = n.left
|
||
} else {
|
||
n = n.right
|
||
}
|
||
}
|
||
stack.length = last_ptr
|
||
return new RedBlackTreeIterator(this, stack)
|
||
}
|
||
|
||
proto.le = function(key) {
|
||
var cmp = this._compare
|
||
var n = this.root
|
||
var stack = []
|
||
var last_ptr = 0
|
||
while(n) {
|
||
var d = cmp(key, n.key)
|
||
stack.push(n)
|
||
if(d >= 0) {
|
||
last_ptr = stack.length
|
||
}
|
||
if(d < 0) {
|
||
n = n.left
|
||
} else {
|
||
n = n.right
|
||
}
|
||
}
|
||
stack.length = last_ptr
|
||
return new RedBlackTreeIterator(this, stack)
|
||
}
|
||
|
||
//Finds the item with key if it exists
|
||
proto.find = function(key) {
|
||
var cmp = this._compare
|
||
var n = this.root
|
||
var stack = []
|
||
while(n) {
|
||
var d = cmp(key, n.key)
|
||
stack.push(n)
|
||
if(d === 0) {
|
||
return new RedBlackTreeIterator(this, stack)
|
||
}
|
||
if(d <= 0) {
|
||
n = n.left
|
||
} else {
|
||
n = n.right
|
||
}
|
||
}
|
||
return new RedBlackTreeIterator(this, [])
|
||
}
|
||
|
||
//Removes item with key from tree
|
||
proto.remove = function(key) {
|
||
var iter = this.find(key)
|
||
if(iter) {
|
||
return iter.remove()
|
||
}
|
||
return this
|
||
}
|
||
|
||
//Returns the item at `key`
|
||
proto.get = function(key) {
|
||
var cmp = this._compare
|
||
var n = this.root
|
||
while(n) {
|
||
var d = cmp(key, n.key)
|
||
if(d === 0) {
|
||
return n.value
|
||
}
|
||
if(d <= 0) {
|
||
n = n.left
|
||
} else {
|
||
n = n.right
|
||
}
|
||
}
|
||
return
|
||
}
|
||
|
||
//Iterator for red black tree
|
||
function RedBlackTreeIterator(tree, stack) {
|
||
this.tree = tree
|
||
this._stack = stack
|
||
}
|
||
|
||
var iproto = RedBlackTreeIterator.prototype
|
||
|
||
//Test if iterator is valid
|
||
Object.defineProperty(iproto, "valid", {
|
||
get: function() {
|
||
return this._stack.length > 0
|
||
}
|
||
})
|
||
|
||
//Node of the iterator
|
||
Object.defineProperty(iproto, "node", {
|
||
get: function() {
|
||
if(this._stack.length > 0) {
|
||
return this._stack[this._stack.length-1]
|
||
}
|
||
return null
|
||
},
|
||
enumerable: true
|
||
})
|
||
|
||
//Makes a copy of an iterator
|
||
iproto.clone = function() {
|
||
return new RedBlackTreeIterator(this.tree, this._stack.slice())
|
||
}
|
||
|
||
//Swaps two nodes
|
||
function swapNode(n, v) {
|
||
n.key = v.key
|
||
n.value = v.value
|
||
n.left = v.left
|
||
n.right = v.right
|
||
n._color = v._color
|
||
n._count = v._count
|
||
}
|
||
|
||
//Fix up a double black node in a tree
|
||
function fixDoubleBlack(stack) {
|
||
var n, p, s, z
|
||
for(var i=stack.length-1; i>=0; --i) {
|
||
n = stack[i]
|
||
if(i === 0) {
|
||
n._color = BLACK
|
||
return
|
||
}
|
||
//console.log("visit node:", n.key, i, stack[i].key, stack[i-1].key)
|
||
p = stack[i-1]
|
||
if(p.left === n) {
|
||
//console.log("left child")
|
||
s = p.right
|
||
if(s.right && s.right._color === RED) {
|
||
//console.log("case 1: right sibling child red")
|
||
s = p.right = cloneNode(s)
|
||
z = s.right = cloneNode(s.right)
|
||
p.right = s.left
|
||
s.left = p
|
||
s.right = z
|
||
s._color = p._color
|
||
n._color = BLACK
|
||
p._color = BLACK
|
||
z._color = BLACK
|
||
recount(p)
|
||
recount(s)
|
||
if(i > 1) {
|
||
var pp = stack[i-2]
|
||
if(pp.left === p) {
|
||
pp.left = s
|
||
} else {
|
||
pp.right = s
|
||
}
|
||
}
|
||
stack[i-1] = s
|
||
return
|
||
} else if(s.left && s.left._color === RED) {
|
||
//console.log("case 1: left sibling child red")
|
||
s = p.right = cloneNode(s)
|
||
z = s.left = cloneNode(s.left)
|
||
p.right = z.left
|
||
s.left = z.right
|
||
z.left = p
|
||
z.right = s
|
||
z._color = p._color
|
||
p._color = BLACK
|
||
s._color = BLACK
|
||
n._color = BLACK
|
||
recount(p)
|
||
recount(s)
|
||
recount(z)
|
||
if(i > 1) {
|
||
var pp = stack[i-2]
|
||
if(pp.left === p) {
|
||
pp.left = z
|
||
} else {
|
||
pp.right = z
|
||
}
|
||
}
|
||
stack[i-1] = z
|
||
return
|
||
}
|
||
if(s._color === BLACK) {
|
||
if(p._color === RED) {
|
||
//console.log("case 2: black sibling, red parent", p.right.value)
|
||
p._color = BLACK
|
||
p.right = repaint(RED, s)
|
||
return
|
||
} else {
|
||
//console.log("case 2: black sibling, black parent", p.right.value)
|
||
p.right = repaint(RED, s)
|
||
continue
|
||
}
|
||
} else {
|
||
//console.log("case 3: red sibling")
|
||
s = cloneNode(s)
|
||
p.right = s.left
|
||
s.left = p
|
||
s._color = p._color
|
||
p._color = RED
|
||
recount(p)
|
||
recount(s)
|
||
if(i > 1) {
|
||
var pp = stack[i-2]
|
||
if(pp.left === p) {
|
||
pp.left = s
|
||
} else {
|
||
pp.right = s
|
||
}
|
||
}
|
||
stack[i-1] = s
|
||
stack[i] = p
|
||
if(i+1 < stack.length) {
|
||
stack[i+1] = n
|
||
} else {
|
||
stack.push(n)
|
||
}
|
||
i = i+2
|
||
}
|
||
} else {
|
||
//console.log("right child")
|
||
s = p.left
|
||
if(s.left && s.left._color === RED) {
|
||
//console.log("case 1: left sibling child red", p.value, p._color)
|
||
s = p.left = cloneNode(s)
|
||
z = s.left = cloneNode(s.left)
|
||
p.left = s.right
|
||
s.right = p
|
||
s.left = z
|
||
s._color = p._color
|
||
n._color = BLACK
|
||
p._color = BLACK
|
||
z._color = BLACK
|
||
recount(p)
|
||
recount(s)
|
||
if(i > 1) {
|
||
var pp = stack[i-2]
|
||
if(pp.right === p) {
|
||
pp.right = s
|
||
} else {
|
||
pp.left = s
|
||
}
|
||
}
|
||
stack[i-1] = s
|
||
return
|
||
} else if(s.right && s.right._color === RED) {
|
||
//console.log("case 1: right sibling child red")
|
||
s = p.left = cloneNode(s)
|
||
z = s.right = cloneNode(s.right)
|
||
p.left = z.right
|
||
s.right = z.left
|
||
z.right = p
|
||
z.left = s
|
||
z._color = p._color
|
||
p._color = BLACK
|
||
s._color = BLACK
|
||
n._color = BLACK
|
||
recount(p)
|
||
recount(s)
|
||
recount(z)
|
||
if(i > 1) {
|
||
var pp = stack[i-2]
|
||
if(pp.right === p) {
|
||
pp.right = z
|
||
} else {
|
||
pp.left = z
|
||
}
|
||
}
|
||
stack[i-1] = z
|
||
return
|
||
}
|
||
if(s._color === BLACK) {
|
||
if(p._color === RED) {
|
||
//console.log("case 2: black sibling, red parent")
|
||
p._color = BLACK
|
||
p.left = repaint(RED, s)
|
||
return
|
||
} else {
|
||
//console.log("case 2: black sibling, black parent")
|
||
p.left = repaint(RED, s)
|
||
continue
|
||
}
|
||
} else {
|
||
//console.log("case 3: red sibling")
|
||
s = cloneNode(s)
|
||
p.left = s.right
|
||
s.right = p
|
||
s._color = p._color
|
||
p._color = RED
|
||
recount(p)
|
||
recount(s)
|
||
if(i > 1) {
|
||
var pp = stack[i-2]
|
||
if(pp.right === p) {
|
||
pp.right = s
|
||
} else {
|
||
pp.left = s
|
||
}
|
||
}
|
||
stack[i-1] = s
|
||
stack[i] = p
|
||
if(i+1 < stack.length) {
|
||
stack[i+1] = n
|
||
} else {
|
||
stack.push(n)
|
||
}
|
||
i = i+2
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//Removes item at iterator from tree
|
||
iproto.remove = function() {
|
||
var stack = this._stack
|
||
if(stack.length === 0) {
|
||
return this.tree
|
||
}
|
||
//First copy path to node
|
||
var cstack = new Array(stack.length)
|
||
var n = stack[stack.length-1]
|
||
cstack[cstack.length-1] = new RBNode(n._color, n.key, n.value, n.left, n.right, n._count)
|
||
for(var i=stack.length-2; i>=0; --i) {
|
||
var n = stack[i]
|
||
if(n.left === stack[i+1]) {
|
||
cstack[i] = new RBNode(n._color, n.key, n.value, cstack[i+1], n.right, n._count)
|
||
} else {
|
||
cstack[i] = new RBNode(n._color, n.key, n.value, n.left, cstack[i+1], n._count)
|
||
}
|
||
}
|
||
|
||
//Get node
|
||
n = cstack[cstack.length-1]
|
||
//console.log("start remove: ", n.value)
|
||
|
||
//If not leaf, then swap with previous node
|
||
if(n.left && n.right) {
|
||
//console.log("moving to leaf")
|
||
|
||
//First walk to previous leaf
|
||
var split = cstack.length
|
||
n = n.left
|
||
while(n.right) {
|
||
cstack.push(n)
|
||
n = n.right
|
||
}
|
||
//Copy path to leaf
|
||
var v = cstack[split-1]
|
||
cstack.push(new RBNode(n._color, v.key, v.value, n.left, n.right, n._count))
|
||
cstack[split-1].key = n.key
|
||
cstack[split-1].value = n.value
|
||
|
||
//Fix up stack
|
||
for(var i=cstack.length-2; i>=split; --i) {
|
||
n = cstack[i]
|
||
cstack[i] = new RBNode(n._color, n.key, n.value, n.left, cstack[i+1], n._count)
|
||
}
|
||
cstack[split-1].left = cstack[split]
|
||
}
|
||
//console.log("stack=", cstack.map(function(v) { return v.value }))
|
||
|
||
//Remove leaf node
|
||
n = cstack[cstack.length-1]
|
||
if(n._color === RED) {
|
||
//Easy case: removing red leaf
|
||
//console.log("RED leaf")
|
||
var p = cstack[cstack.length-2]
|
||
if(p.left === n) {
|
||
p.left = null
|
||
} else if(p.right === n) {
|
||
p.right = null
|
||
}
|
||
cstack.pop()
|
||
for(var i=0; i<cstack.length; ++i) {
|
||
cstack[i]._count--
|
||
}
|
||
return new RedBlackTree(this.tree._compare, cstack[0])
|
||
} else {
|
||
if(n.left || n.right) {
|
||
//Second easy case: Single child black parent
|
||
//console.log("BLACK single child")
|
||
if(n.left) {
|
||
swapNode(n, n.left)
|
||
} else if(n.right) {
|
||
swapNode(n, n.right)
|
||
}
|
||
//Child must be red, so repaint it black to balance color
|
||
n._color = BLACK
|
||
for(var i=0; i<cstack.length-1; ++i) {
|
||
cstack[i]._count--
|
||
}
|
||
return new RedBlackTree(this.tree._compare, cstack[0])
|
||
} else if(cstack.length === 1) {
|
||
//Third easy case: root
|
||
//console.log("ROOT")
|
||
return new RedBlackTree(this.tree._compare, null)
|
||
} else {
|
||
//Hard case: Repaint n, and then do some nasty stuff
|
||
//console.log("BLACK leaf no children")
|
||
for(var i=0; i<cstack.length; ++i) {
|
||
cstack[i]._count--
|
||
}
|
||
var parent = cstack[cstack.length-2]
|
||
fixDoubleBlack(cstack)
|
||
//Fix up links
|
||
if(parent.left === n) {
|
||
parent.left = null
|
||
} else {
|
||
parent.right = null
|
||
}
|
||
}
|
||
}
|
||
return new RedBlackTree(this.tree._compare, cstack[0])
|
||
}
|
||
|
||
//Returns key
|
||
Object.defineProperty(iproto, "key", {
|
||
get: function() {
|
||
if(this._stack.length > 0) {
|
||
return this._stack[this._stack.length-1].key
|
||
}
|
||
return
|
||
},
|
||
enumerable: true
|
||
})
|
||
|
||
//Returns value
|
||
Object.defineProperty(iproto, "value", {
|
||
get: function() {
|
||
if(this._stack.length > 0) {
|
||
return this._stack[this._stack.length-1].value
|
||
}
|
||
return
|
||
},
|
||
enumerable: true
|
||
})
|
||
|
||
|
||
//Returns the position of this iterator in the sorted list
|
||
Object.defineProperty(iproto, "index", {
|
||
get: function() {
|
||
var idx = 0
|
||
var stack = this._stack
|
||
if(stack.length === 0) {
|
||
var r = this.tree.root
|
||
if(r) {
|
||
return r._count
|
||
}
|
||
return 0
|
||
} else if(stack[stack.length-1].left) {
|
||
idx = stack[stack.length-1].left._count
|
||
}
|
||
for(var s=stack.length-2; s>=0; --s) {
|
||
if(stack[s+1] === stack[s].right) {
|
||
++idx
|
||
if(stack[s].left) {
|
||
idx += stack[s].left._count
|
||
}
|
||
}
|
||
}
|
||
return idx
|
||
},
|
||
enumerable: true
|
||
})
|
||
|
||
//Advances iterator to next element in list
|
||
iproto.next = function() {
|
||
var stack = this._stack
|
||
if(stack.length === 0) {
|
||
return
|
||
}
|
||
var n = stack[stack.length-1]
|
||
if(n.right) {
|
||
n = n.right
|
||
while(n) {
|
||
stack.push(n)
|
||
n = n.left
|
||
}
|
||
} else {
|
||
stack.pop()
|
||
while(stack.length > 0 && stack[stack.length-1].right === n) {
|
||
n = stack[stack.length-1]
|
||
stack.pop()
|
||
}
|
||
}
|
||
}
|
||
|
||
//Checks if iterator is at end of tree
|
||
Object.defineProperty(iproto, "hasNext", {
|
||
get: function() {
|
||
var stack = this._stack
|
||
if(stack.length === 0) {
|
||
return false
|
||
}
|
||
if(stack[stack.length-1].right) {
|
||
return true
|
||
}
|
||
for(var s=stack.length-1; s>0; --s) {
|
||
if(stack[s-1].left === stack[s]) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
})
|
||
|
||
//Update value
|
||
iproto.update = function(value) {
|
||
var stack = this._stack
|
||
if(stack.length === 0) {
|
||
throw new Error("Can't update empty node!")
|
||
}
|
||
var cstack = new Array(stack.length)
|
||
var n = stack[stack.length-1]
|
||
cstack[cstack.length-1] = new RBNode(n._color, n.key, value, n.left, n.right, n._count)
|
||
for(var i=stack.length-2; i>=0; --i) {
|
||
n = stack[i]
|
||
if(n.left === stack[i+1]) {
|
||
cstack[i] = new RBNode(n._color, n.key, n.value, cstack[i+1], n.right, n._count)
|
||
} else {
|
||
cstack[i] = new RBNode(n._color, n.key, n.value, n.left, cstack[i+1], n._count)
|
||
}
|
||
}
|
||
return new RedBlackTree(this.tree._compare, cstack[0])
|
||
}
|
||
|
||
//Moves iterator backward one element
|
||
iproto.prev = function() {
|
||
var stack = this._stack
|
||
if(stack.length === 0) {
|
||
return
|
||
}
|
||
var n = stack[stack.length-1]
|
||
if(n.left) {
|
||
n = n.left
|
||
while(n) {
|
||
stack.push(n)
|
||
n = n.right
|
||
}
|
||
} else {
|
||
stack.pop()
|
||
while(stack.length > 0 && stack[stack.length-1].left === n) {
|
||
n = stack[stack.length-1]
|
||
stack.pop()
|
||
}
|
||
}
|
||
}
|
||
|
||
//Checks if iterator is at start of tree
|
||
Object.defineProperty(iproto, "hasPrev", {
|
||
get: function() {
|
||
var stack = this._stack
|
||
if(stack.length === 0) {
|
||
return false
|
||
}
|
||
if(stack[stack.length-1].left) {
|
||
return true
|
||
}
|
||
for(var s=stack.length-1; s>0; --s) {
|
||
if(stack[s-1].right === stack[s]) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
})
|
||
|
||
//Default comparison function
|
||
function defaultCompare(a, b) {
|
||
if(a < b) {
|
||
return -1
|
||
}
|
||
if(a > b) {
|
||
return 1
|
||
}
|
||
return 0
|
||
}
|
||
|
||
//Build a tree
|
||
function createRBTree(compare) {
|
||
return new RedBlackTree(compare || defaultCompare, null)
|
||
}
|
||
},{}],2:[function(require,module,exports){
|
||
module.exports = function (cytoscape, cy, $, apiRegistered) {
|
||
|
||
// Needed because parent nodes cannot be moved in Cytoscape.js < v3.2
|
||
function moveTopDown(node, dx, dy) {
|
||
var nodes = node.union(node.descendants());
|
||
|
||
nodes.filter(":childless").positions(function (node, i) {
|
||
if(typeof node === "number") {
|
||
node = i;
|
||
}
|
||
var pos = node.position();
|
||
return {
|
||
x: pos.x + dx,
|
||
y: pos.y + dy
|
||
};
|
||
});
|
||
}
|
||
|
||
function getTopMostNodes(nodes) {
|
||
var nodesMap = {};
|
||
for (var i = 0; i < nodes.length; i++) {
|
||
nodesMap[nodes[i].id()] = true;
|
||
}
|
||
var roots = nodes.filter(function (ele, i) {
|
||
if(typeof ele === "number") {
|
||
ele = i;
|
||
}
|
||
|
||
var parent = ele.parent()[0];
|
||
while(parent != null){
|
||
if(nodesMap[parent.id()]){
|
||
return false;
|
||
}
|
||
parent = parent.parent()[0];
|
||
}
|
||
return true;
|
||
});
|
||
|
||
return roots;
|
||
}
|
||
|
||
|
||
// If extension api functions are not registed to cytoscape yet register them here.
|
||
// Note that ideally these functions should not be directly registered to core from cytoscape.js
|
||
// extensions
|
||
if ( !apiRegistered ) {
|
||
|
||
cytoscape( "collection", "align", function (horizontal, vertical, alignTo) {
|
||
|
||
var eles = getTopMostNodes(this.nodes(":visible"));
|
||
|
||
var modelNode = alignTo ? alignTo : eles[0];
|
||
|
||
eles = eles.not(modelNode);
|
||
|
||
horizontal = horizontal ? horizontal : "none";
|
||
vertical = vertical ? vertical : "none";
|
||
|
||
|
||
// 0 for center
|
||
var xFactor = 0;
|
||
var yFactor = 0;
|
||
|
||
if (vertical == "left")
|
||
xFactor = -1;
|
||
else if (vertical == "right")
|
||
xFactor = 1;
|
||
|
||
if (horizontal == "top")
|
||
yFactor = -1;
|
||
else if (horizontal == "bottom")
|
||
yFactor = 1;
|
||
|
||
|
||
for (var i = 0; i < eles.length; i++) {
|
||
var node = eles[i];
|
||
var oldPos = $.extend({}, node.position());
|
||
var newPos = $.extend({}, node.position());
|
||
|
||
if (vertical != "none")
|
||
newPos.x = modelNode.position("x") + xFactor * (modelNode.outerWidth() - node.outerWidth()) / 2;
|
||
|
||
|
||
if (horizontal != "none")
|
||
newPos.y = modelNode.position("y") + yFactor * (modelNode.outerHeight() - node.outerHeight()) / 2;
|
||
|
||
moveTopDown(node, newPos.x - oldPos.x, newPos.y - oldPos.y);
|
||
}
|
||
|
||
return this;
|
||
});
|
||
|
||
}
|
||
|
||
|
||
if (cy.undoRedo) {
|
||
function getNodePositions() {
|
||
var positionsAndSizes = {};
|
||
var nodes = cy.nodes();
|
||
|
||
for (var i = 0; i < nodes.length; i++) {
|
||
var ele = nodes[i];
|
||
positionsAndSizes[ele.id()] = {
|
||
x: ele.position("x"),
|
||
y: ele.position("y")
|
||
};
|
||
}
|
||
|
||
return positionsAndSizes;
|
||
}
|
||
|
||
function returnToPositions(nodesData) {
|
||
var currentPositions = {};
|
||
cy.nodes().positions(function (ele, i) {
|
||
if(typeof ele === "number") {
|
||
ele = i;
|
||
}
|
||
currentPositions[ele.id()] = {
|
||
x: ele.position("x"),
|
||
y: ele.position("y")
|
||
};
|
||
var data = nodesData[ele.id()];
|
||
return {
|
||
x: data.x,
|
||
y: data.y
|
||
};
|
||
});
|
||
|
||
return currentPositions
|
||
}
|
||
|
||
var ur = cy.undoRedo(null, true);
|
||
|
||
ur.action("align", function (args) {
|
||
|
||
var nodesData;
|
||
if (args.firstTime){
|
||
nodesData = getNodePositions();
|
||
args.nodes.align(args.horizontal, args.vertical, args.alignTo);
|
||
}
|
||
else
|
||
nodesData = returnToPositions(args);
|
||
|
||
return nodesData;
|
||
|
||
}, function (nodesData) {
|
||
return returnToPositions(nodesData);
|
||
});
|
||
|
||
}
|
||
|
||
|
||
|
||
};
|
||
|
||
},{}],3:[function(require,module,exports){
|
||
|
||
var debounce = (function(){
|
||
/**
|
||
* lodash 3.1.1 (Custom Build) <https://lodash.com/>
|
||
* Build: `lodash modern modularize exports="npm" -o ./`
|
||
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
|
||
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
||
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
||
* Available under MIT license <https://lodash.com/license>
|
||
*/
|
||
/** Used as the `TypeError` message for "Functions" methods. */
|
||
var FUNC_ERROR_TEXT = 'Expected a function';
|
||
|
||
/* Native method references for those with the same name as other `lodash` methods. */
|
||
var nativeMax = Math.max,
|
||
nativeNow = Date.now;
|
||
|
||
/**
|
||
* Gets the number of milliseconds that have elapsed since the Unix epoch
|
||
* (1 January 1970 00:00:00 UTC).
|
||
*
|
||
* @static
|
||
* @memberOf _
|
||
* @category Date
|
||
* @example
|
||
*
|
||
* _.defer(function(stamp) {
|
||
* console.log(_.now() - stamp);
|
||
* }, _.now());
|
||
* // => logs the number of milliseconds it took for the deferred function to be invoked
|
||
*/
|
||
var now = nativeNow || function() {
|
||
return new Date().getTime();
|
||
};
|
||
|
||
/**
|
||
* Creates a debounced function that delays invoking `func` until after `wait`
|
||
* milliseconds have elapsed since the last time the debounced function was
|
||
* invoked. The debounced function comes with a `cancel` method to cancel
|
||
* delayed invocations. Provide an options object to indicate that `func`
|
||
* should be invoked on the leading and/or trailing edge of the `wait` timeout.
|
||
* Subsequent calls to the debounced function return the result of the last
|
||
* `func` invocation.
|
||
*
|
||
* **Note:** If `leading` and `trailing` options are `true`, `func` is invoked
|
||
* on the trailing edge of the timeout only if the the debounced function is
|
||
* invoked more than once during the `wait` timeout.
|
||
*
|
||
* See [David Corbacho's article](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation)
|
||
* for details over the differences between `_.debounce` and `_.throttle`.
|
||
*
|
||
* @static
|
||
* @memberOf _
|
||
* @category Function
|
||
* @param {Function} func The function to debounce.
|
||
* @param {number} [wait=0] The number of milliseconds to delay.
|
||
* @param {Object} [options] The options object.
|
||
* @param {boolean} [options.leading=false] Specify invoking on the leading
|
||
* edge of the timeout.
|
||
* @param {number} [options.maxWait] The maximum time `func` is allowed to be
|
||
* delayed before it's invoked.
|
||
* @param {boolean} [options.trailing=true] Specify invoking on the trailing
|
||
* edge of the timeout.
|
||
* @returns {Function} Returns the new debounced function.
|
||
* @example
|
||
*
|
||
* // avoid costly calculations while the window size is in flux
|
||
* jQuery(window).on('resize', _.debounce(calculateLayout, 150));
|
||
*
|
||
* // invoke `sendMail` when the click event is fired, debouncing subsequent calls
|
||
* jQuery('#postbox').on('click', _.debounce(sendMail, 300, {
|
||
* 'leading': true,
|
||
* 'trailing': false
|
||
* }));
|
||
*
|
||
* // ensure `batchLog` is invoked once after 1 second of debounced calls
|
||
* var source = new EventSource('/stream');
|
||
* jQuery(source).on('message', _.debounce(batchLog, 250, {
|
||
* 'maxWait': 1000
|
||
* }));
|
||
*
|
||
* // cancel a debounced call
|
||
* var todoChanges = _.debounce(batchLog, 1000);
|
||
* Object.observe(models.todo, todoChanges);
|
||
*
|
||
* Object.observe(models, function(changes) {
|
||
* if (_.find(changes, { 'user': 'todo', 'type': 'delete'})) {
|
||
* todoChanges.cancel();
|
||
* }
|
||
* }, ['delete']);
|
||
*
|
||
* // ...at some point `models.todo` is changed
|
||
* models.todo.completed = true;
|
||
*
|
||
* // ...before 1 second has passed `models.todo` is deleted
|
||
* // which cancels the debounced `todoChanges` call
|
||
* delete models.todo;
|
||
*/
|
||
function debounce(func, wait, options) {
|
||
var args,
|
||
maxTimeoutId,
|
||
result,
|
||
stamp,
|
||
thisArg,
|
||
timeoutId,
|
||
trailingCall,
|
||
lastCalled = 0,
|
||
maxWait = false,
|
||
trailing = true;
|
||
|
||
if (typeof func != 'function') {
|
||
throw new TypeError(FUNC_ERROR_TEXT);
|
||
}
|
||
wait = wait < 0 ? 0 : (+wait || 0);
|
||
if (options === true) {
|
||
var leading = true;
|
||
trailing = false;
|
||
} else if (isObject(options)) {
|
||
leading = !!options.leading;
|
||
maxWait = 'maxWait' in options && nativeMax(+options.maxWait || 0, wait);
|
||
trailing = 'trailing' in options ? !!options.trailing : trailing;
|
||
}
|
||
|
||
function cancel() {
|
||
if (timeoutId) {
|
||
clearTimeout(timeoutId);
|
||
}
|
||
if (maxTimeoutId) {
|
||
clearTimeout(maxTimeoutId);
|
||
}
|
||
lastCalled = 0;
|
||
maxTimeoutId = timeoutId = trailingCall = undefined;
|
||
}
|
||
|
||
function complete(isCalled, id) {
|
||
if (id) {
|
||
clearTimeout(id);
|
||
}
|
||
maxTimeoutId = timeoutId = trailingCall = undefined;
|
||
if (isCalled) {
|
||
lastCalled = now();
|
||
result = func.apply(thisArg, args);
|
||
if (!timeoutId && !maxTimeoutId) {
|
||
args = thisArg = undefined;
|
||
}
|
||
}
|
||
}
|
||
|
||
function delayed() {
|
||
var remaining = wait - (now() - stamp);
|
||
if (remaining <= 0 || remaining > wait) {
|
||
complete(trailingCall, maxTimeoutId);
|
||
} else {
|
||
timeoutId = setTimeout(delayed, remaining);
|
||
}
|
||
}
|
||
|
||
function maxDelayed() {
|
||
complete(trailing, timeoutId);
|
||
}
|
||
|
||
function debounced() {
|
||
args = arguments;
|
||
stamp = now();
|
||
thisArg = this;
|
||
trailingCall = trailing && (timeoutId || !leading);
|
||
|
||
if (maxWait === false) {
|
||
var leadingCall = leading && !timeoutId;
|
||
} else {
|
||
if (!maxTimeoutId && !leading) {
|
||
lastCalled = stamp;
|
||
}
|
||
var remaining = maxWait - (stamp - lastCalled),
|
||
isCalled = remaining <= 0 || remaining > maxWait;
|
||
|
||
if (isCalled) {
|
||
if (maxTimeoutId) {
|
||
maxTimeoutId = clearTimeout(maxTimeoutId);
|
||
}
|
||
lastCalled = stamp;
|
||
result = func.apply(thisArg, args);
|
||
}
|
||
else if (!maxTimeoutId) {
|
||
maxTimeoutId = setTimeout(maxDelayed, remaining);
|
||
}
|
||
}
|
||
if (isCalled && timeoutId) {
|
||
timeoutId = clearTimeout(timeoutId);
|
||
}
|
||
else if (!timeoutId && wait !== maxWait) {
|
||
timeoutId = setTimeout(delayed, wait);
|
||
}
|
||
if (leadingCall) {
|
||
isCalled = true;
|
||
result = func.apply(thisArg, args);
|
||
}
|
||
if (isCalled && !timeoutId && !maxTimeoutId) {
|
||
args = thisArg = undefined;
|
||
}
|
||
return result;
|
||
}
|
||
debounced.cancel = cancel;
|
||
return debounced;
|
||
}
|
||
|
||
/**
|
||
* Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
|
||
* (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
|
||
*
|
||
* @static
|
||
* @memberOf _
|
||
* @category Lang
|
||
* @param {*} value The value to check.
|
||
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
|
||
* @example
|
||
*
|
||
* _.isObject({});
|
||
* // => true
|
||
*
|
||
* _.isObject([1, 2, 3]);
|
||
* // => true
|
||
*
|
||
* _.isObject(1);
|
||
* // => false
|
||
*/
|
||
function isObject(value) {
|
||
// Avoid a V8 JIT bug in Chrome 19-20.
|
||
// See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
|
||
var type = typeof value;
|
||
return !!value && (type == 'object' || type == 'function');
|
||
}
|
||
|
||
return debounce;
|
||
|
||
})();
|
||
|
||
module.exports = debounce;
|
||
},{}],4:[function(require,module,exports){
|
||
module.exports = function (opts, cy, $, debounce) {
|
||
|
||
var options = opts;
|
||
|
||
var changeOptions = function (opts) {
|
||
options = opts;
|
||
};
|
||
|
||
|
||
var $canvas = $( '<canvas></canvas>' );
|
||
var $container = $( cy.container() );
|
||
var ctx = $canvas[ 0 ].getContext( '2d' );
|
||
$container.append( $canvas );
|
||
|
||
var resetCanvas = function () {
|
||
$canvas
|
||
.attr('height', 0)
|
||
.attr('width', 0)
|
||
.css( {
|
||
'position': 'absolute',
|
||
'top': 0,
|
||
'left': 0,
|
||
'z-index': options.gridStackOrder
|
||
});
|
||
};
|
||
|
||
resetCanvas();
|
||
|
||
var drawGrid = function() {
|
||
var zoom = cy.zoom();
|
||
var canvasWidth = $container.width();
|
||
var canvasHeight = $container.height();
|
||
var increment = options.gridSpacing*zoom;
|
||
var pan = cy.pan();
|
||
var initialValueX = pan.x%increment;
|
||
var initialValueY = pan.y%increment;
|
||
|
||
ctx.strokeStyle = options.gridColor;
|
||
ctx.lineWidth = options.lineWidth;
|
||
|
||
var data = '\t<svg width="'+ canvasWidth + '" height="'+ canvasHeight + '" xmlns="http://www.w3.org/2000/svg">\n\
|
||
<defs>\n\
|
||
<pattern id="horizontalLines" width="' + increment + '" height="' + increment + '" patternUnits="userSpaceOnUse">\n\
|
||
<path d="M ' + increment + ' 0 L 0 0 0 ' + 0 + '" fill="none" stroke="' + options.gridColor + '" stroke-width="' + options.lineWidth + '" />\n\
|
||
</pattern>\n\
|
||
<pattern id="verticalLines" width="' + increment + '" height="' + increment + '" patternUnits="userSpaceOnUse">\n\
|
||
<path d="M ' + 0 + ' 0 L 0 0 0 ' + increment + '" fill="none" stroke="' + options.gridColor + '" stroke-width="' + options.lineWidth + '" />\n\
|
||
</pattern>\n\
|
||
</defs>\n\
|
||
<rect width="100%" height="100%" fill="url(#horizontalLines)" transform="translate('+ 0 + ', ' + initialValueY + ')" />\n\
|
||
<rect width="100%" height="100%" fill="url(#verticalLines)" transform="translate('+ initialValueX + ', ' + 0 + ')" />\n\
|
||
</svg>\n';
|
||
|
||
var img = new Image();
|
||
data = encodeURIComponent(data);
|
||
|
||
img.onload = function () {
|
||
clearDrawing();
|
||
ctx.drawImage(img, 0, 0);
|
||
};
|
||
|
||
img.src = "data:image/svg+xml," + data;
|
||
};
|
||
|
||
var clearDrawing = function() {
|
||
var width = $container.width();
|
||
var height = $container.height();
|
||
|
||
ctx.clearRect( 0, 0, width, height );
|
||
};
|
||
|
||
var resizeCanvas = debounce(function() {
|
||
$canvas
|
||
.attr( 'height', $container.height() )
|
||
.attr( 'width', $container.width() )
|
||
.css( {
|
||
'position': 'absolute',
|
||
'top': 0,
|
||
'left': 0,
|
||
'z-index': options.gridStackOrder
|
||
} );
|
||
|
||
setTimeout( function() {
|
||
var canvasBb = $canvas.offset();
|
||
var containerBb = $container.offset();
|
||
|
||
$canvas
|
||
.attr( 'height', $container.height() )
|
||
.attr( 'width', $container.width() )
|
||
.css( {
|
||
'top': -( canvasBb.top - containerBb.top ),
|
||
'left': -( canvasBb.left - containerBb.left )
|
||
} );
|
||
drawGrid();
|
||
}, 0 );
|
||
|
||
}, 250);
|
||
|
||
|
||
|
||
|
||
return {
|
||
initCanvas: resizeCanvas,
|
||
resizeCanvas: resizeCanvas,
|
||
resetCanvas: resetCanvas,
|
||
clearCanvas: clearDrawing,
|
||
drawGrid: drawGrid,
|
||
changeOptions: changeOptions,
|
||
sizeCanvas: drawGrid
|
||
};
|
||
};
|
||
|
||
},{}],5:[function(require,module,exports){
|
||
module.exports = function (cy, snap, resize, snapToGridDuringDrag, drawGrid, guidelines, parentPadding, $, opts) {
|
||
|
||
var feature = function (func) {
|
||
return function (enable) {
|
||
func(enable);
|
||
};
|
||
};
|
||
|
||
var controller = {
|
||
snapToGridDuringDrag: new feature(setDiscreteDrag),
|
||
resize: new feature(setResize),
|
||
snapToGridOnRelease: new feature(setSnapToGrid),
|
||
drawGrid: new feature(setDrawGrid),
|
||
guidelines: new feature(setGuidelines),
|
||
parentPadding: new feature(setParentPadding)
|
||
};
|
||
|
||
function applyToCyTarget(func, allowParent) {
|
||
return function (e) {
|
||
var cyTarget = e.target || e.cyTarget;
|
||
if (!cyTarget.is(":parent") || allowParent)
|
||
func(cyTarget);
|
||
}
|
||
}
|
||
|
||
function applyToAllNodesButNoParent(func) {
|
||
return function () {
|
||
cy.nodes().not(":parent").each(function (ele, i) {
|
||
if(typeof ele === "number") {
|
||
ele = i;
|
||
}
|
||
|
||
func(ele);
|
||
});
|
||
};
|
||
}
|
||
function applyToAllNodes(func) {
|
||
return function () {
|
||
cy.nodes().each(function (ele, i) {
|
||
if(typeof ele === "number") {
|
||
ele = i;
|
||
}
|
||
|
||
func(ele);
|
||
});
|
||
};
|
||
}
|
||
|
||
function eventStatus(enable) {
|
||
return enable ? "on" : "off";
|
||
}
|
||
|
||
|
||
// Discrete Drag
|
||
function setDiscreteDrag(enable) {
|
||
cy[eventStatus(enable)]("tapstart", "node", snapToGridDuringDrag.onTapStartNode);
|
||
}
|
||
|
||
// Resize
|
||
var resizeAllNodes = applyToAllNodesButNoParent(resize.resizeNode);
|
||
var resizeNode = applyToCyTarget(resize.resizeNode);
|
||
var recoverAllNodeDimensions = applyToAllNodesButNoParent(resize.recoverNodeDimensions);
|
||
|
||
function setResize(enable) {
|
||
cy[eventStatus(enable)]("ready", resizeAllNodes);
|
||
// cy[eventStatus(enable)]("style", "node", resizeNode);
|
||
enable ? resizeAllNodes() : recoverAllNodeDimensions();
|
||
}
|
||
|
||
// Snap To Grid
|
||
var snapAllNodes = applyToAllNodes(snap.snapNodesTopDown);
|
||
var recoverSnapAllNodes = applyToAllNodes(snap.recoverSnapNode);
|
||
var snapCyTarget = applyToCyTarget(snap.snapNode, true);
|
||
|
||
function setSnapToGrid(enable) {
|
||
cy[eventStatus(enable)]("add", "node", snapCyTarget);
|
||
cy[eventStatus(enable)]("ready", snapAllNodes);
|
||
|
||
cy[eventStatus(enable)]("free", "node", snap.onFreeNode);
|
||
|
||
if (enable) {
|
||
snapAllNodes();
|
||
} else {
|
||
recoverSnapAllNodes();
|
||
}
|
||
}
|
||
|
||
// Draw Grid
|
||
var drawGridOnZoom = function () {
|
||
if (currentOptions.zoomDash) drawGrid.drawGrid()
|
||
};
|
||
var drawGridOnPan = function () {
|
||
if (currentOptions.panGrid) drawGrid.drawGrid()
|
||
};
|
||
|
||
function setDrawGrid(enable) {
|
||
cy[eventStatus(enable)]('zoom', drawGridOnZoom);
|
||
cy[eventStatus(enable)]('pan', drawGridOnPan);
|
||
|
||
if (enable) {
|
||
drawGrid.initCanvas();
|
||
$(window).on('resize', drawGrid.resizeCanvas);
|
||
} else {
|
||
drawGrid.clearCanvas();
|
||
drawGrid.resetCanvas();
|
||
$(window).off('resize', drawGrid.resizeCanvas);
|
||
}
|
||
}
|
||
|
||
// Guidelines
|
||
var activeTopMostNodes = null;
|
||
var guidelinesGrabHandler = function(e){
|
||
var cyTarget = e.target || e.cyTarget;
|
||
var nodes = cyTarget.selected() ? e.cy.$(":selected") : cyTarget;
|
||
activeTopMostNodes = guidelines.getTopMostNodes(nodes.nodes());
|
||
guidelines.lines.init(activeTopMostNodes);
|
||
}
|
||
var guidelinesDragHandler = function(e){
|
||
if (this.id() == activeTopMostNodes.id()){
|
||
guidelines.lines.update(activeTopMostNodes);
|
||
|
||
if (currentOptions.snapToAlignmentLocationDuringDrag)
|
||
guidelines.lines.snapToAlignmentLocation(activeTopMostNodes);
|
||
}
|
||
};
|
||
var guidelinesFreeHandler = function(e){
|
||
if (currentOptions.snapToAlignmentLocationOnRelease)
|
||
guidelines.lines.snapToAlignmentLocation(activeTopMostNodes);
|
||
|
||
guidelines.lines.destroy();
|
||
activeTopMostNodes = null;
|
||
};
|
||
var guidelinesWindowResizeHandler = function(e){
|
||
guidelines.lines.resize();
|
||
};
|
||
var guidelinesTapHandler = function(e){
|
||
guidelines.getMousePos(e);
|
||
};
|
||
var guidelinesPanHandler = function(e){
|
||
if (activeTopMostNodes){
|
||
guidelines.setMousePos(cy.pan());
|
||
guidelines.lines.init(activeTopMostNodes);
|
||
}
|
||
}
|
||
function setGuidelines(enable) {
|
||
if (enable){
|
||
guidelines.resizeCanvas();
|
||
cy.on("tapstart", "node", guidelinesTapHandler);
|
||
cy.on("grab", guidelinesGrabHandler);
|
||
cy.on("pan", guidelinesPanHandler);
|
||
cy.on("drag", "node", guidelinesDragHandler);
|
||
cy.on("free", guidelinesFreeHandler);
|
||
$(window).on("resize", guidelinesWindowResizeHandler);
|
||
}
|
||
else{
|
||
cy.off("tapstart", "node", guidelinesTapHandler);
|
||
cy.off("grab", guidelinesGrabHandler);
|
||
cy.off("pan", guidelinesPanHandler);
|
||
cy.off("drag", "node", guidelinesDragHandler);
|
||
cy.off("free", guidelinesFreeHandler);
|
||
guidelines.resetCanvas();
|
||
$(window).off("resize", guidelinesWindowResizeHandler);
|
||
}
|
||
}
|
||
|
||
// Parent Padding
|
||
var setAllParentPaddings = function (enable) {
|
||
parentPadding.setPaddingOfParent(cy.nodes(":parent"), enable);
|
||
};
|
||
var enableParentPadding = function (node) {
|
||
parentPadding.setPaddingOfParent(node, true);
|
||
};
|
||
|
||
|
||
function setParentPadding(enable) {
|
||
|
||
setAllParentPaddings(enable);
|
||
|
||
cy[eventStatus(enable)]('ready', setAllParentPaddings);
|
||
cy[eventStatus(enable)]("add", "node:parent", applyToCyTarget(enableParentPadding, true));
|
||
}
|
||
|
||
// Sync with options: Enables/disables changed via options.
|
||
var latestOptions = {};
|
||
var currentOptions;
|
||
|
||
var specialOpts = {
|
||
drawGrid: ["gridSpacing", "zoomDash", "panGrid", "gridStackOrder", "gridColor", "lineWidth", "lineDash"],
|
||
guidelines: ["gridSpacing", "guidelinesStackOrder", "guidelinesTolerance", "guidelinesStyle", "distributionGuidelines", "range", "minDistRange", "geometricGuidelineRange"],
|
||
resize: ["gridSpacing"],
|
||
parentPadding: ["gridSpacing", "parentSpacing"],
|
||
snapToGridOnRelease: ["gridSpacing"]
|
||
};
|
||
|
||
function syncWithOptions(options) {
|
||
currentOptions = $.extend(true, {}, options);
|
||
options.guidelines = options.initPosAlignment || options.distributionGuidelines || options.geometricGuideline;
|
||
for (var key in options)
|
||
if (latestOptions[key] != options[key])
|
||
if (controller.hasOwnProperty(key)) {
|
||
controller[key](options[key]);
|
||
} else {
|
||
for (var optsKey in specialOpts) {
|
||
var opts = specialOpts[optsKey];
|
||
if (opts.indexOf(key) >= 0) {
|
||
if(optsKey == "drawGrid") {
|
||
drawGrid.changeOptions(options);
|
||
if (options.drawGrid)
|
||
drawGrid.resizeCanvas();
|
||
}
|
||
|
||
if (optsKey == "snapToGridOnRelease"){
|
||
snap.changeOptions(options);
|
||
if (options.snapToGridOnRelease)
|
||
snapAllNodes();
|
||
}
|
||
|
||
if(optsKey == "guidelines")
|
||
guidelines.changeOptions(options);
|
||
|
||
if (optsKey == "resize") {
|
||
resize.changeOptions(options);
|
||
if (options.resize)
|
||
resizeAllNodes();
|
||
}
|
||
|
||
if (optsKey == "parentPadding")
|
||
parentPadding.changeOptions(options);
|
||
|
||
|
||
}
|
||
}
|
||
}
|
||
latestOptions = $.extend(true, latestOptions, options);
|
||
}
|
||
|
||
return {
|
||
init: syncWithOptions,
|
||
syncWithOptions: syncWithOptions
|
||
};
|
||
|
||
};
|
||
|
||
},{}],6:[function(require,module,exports){
|
||
module.exports = function (opts, cy, $, debounce) {
|
||
|
||
|
||
var RBTree = require("functional-red-black-tree");
|
||
|
||
var options = opts;
|
||
|
||
var changeOptions = function (opts) {
|
||
options = opts;
|
||
|
||
// RBTree always returns null, when low == high
|
||
// to avoid this:
|
||
if (options.guidelinesTolerance == 0)
|
||
options.guidelinesTolerance = 0.001;
|
||
};
|
||
|
||
var getCyScratch = function () {
|
||
var sc = cy.scratch("_guidelines");
|
||
if (!sc)
|
||
sc = cy.scratch("_guidelines", {});
|
||
|
||
return sc;
|
||
};
|
||
|
||
/* Resize canvas */
|
||
var resizeCanvas = debounce(function () {
|
||
clearDrawing();
|
||
$canvas
|
||
.attr('height', $container.height())
|
||
.attr('width', $container.width())
|
||
.css({
|
||
'position': 'absolute',
|
||
'top': 0,
|
||
'left': 0,
|
||
'z-index': options.guidelinesStackOrder
|
||
});
|
||
setTimeout(function () {
|
||
var canvasBb = $canvas.offset();
|
||
var containerBb = $container.offset();
|
||
|
||
$canvas
|
||
.attr('height', $container.height())
|
||
.attr('width', $container.width())
|
||
.css({
|
||
'top': -( canvasBb.top - containerBb.top ),
|
||
'left': -( canvasBb.left - containerBb.left )
|
||
});
|
||
}, 0);
|
||
}, 250);
|
||
|
||
/* Clear canvas */
|
||
var clearDrawing = function () {
|
||
var width = $container.width();
|
||
var height = $container.height();
|
||
ctx.clearRect(0, 0, width, height);
|
||
};
|
||
|
||
/* Create a canvas */
|
||
var $canvas = $('<canvas></canvas>');
|
||
var $container = $(cy.container());
|
||
var ctx = $canvas[0].getContext('2d');
|
||
$container.append($canvas);
|
||
|
||
var resetCanvas = function () {
|
||
$canvas
|
||
.attr('height', 0)
|
||
.attr('width', 0)
|
||
.css( {
|
||
'position': 'absolute',
|
||
'top': 0,
|
||
'left': 0,
|
||
'z-index': options.gridStackOrder
|
||
});
|
||
};
|
||
|
||
resetCanvas();
|
||
|
||
/* Global variables */
|
||
var VTree = null;
|
||
var HTree = null;
|
||
var nodeInitPos;
|
||
var excludedNodes;
|
||
var lines = {};
|
||
var panInitPos = {};
|
||
var alignedLocations = {"h" : null, "v" : null};
|
||
|
||
/**
|
||
* Get positions of sides of a node
|
||
* @param node : a node
|
||
* @return : object of positions
|
||
*/
|
||
lines.getDims = function (node) {
|
||
var pos = node.renderedPosition();
|
||
var width = node.renderedWidth();
|
||
var height = node.renderedHeight();
|
||
var padding = {
|
||
left: Number(node.renderedStyle("padding-left").replace("px", "")),
|
||
right: Number(node.renderedStyle("padding-right").replace("px", "")),
|
||
top: Number(node.renderedStyle("padding-top").replace("px", "")),
|
||
bottom: Number(node.renderedStyle("padding-bottom").replace("px", ""))
|
||
};
|
||
|
||
return {
|
||
horizontal: {
|
||
center: (pos.x),
|
||
left: Math.round(pos.x - (padding.left + width / 2)),
|
||
right: Math.round(pos.x + (padding.right + width / 2))
|
||
},
|
||
vertical: {
|
||
center: (pos.y),
|
||
top: Math.round(pos.y - (padding.top + height / 2)),
|
||
bottom: Math.round(pos.y + (padding.bottom + height / 2))
|
||
}
|
||
};
|
||
};
|
||
|
||
/**
|
||
* Initialize trees and initial position of node
|
||
* @param activeNodes : top most active nodes
|
||
*/
|
||
lines.init = function (activeNodes) {
|
||
VTree = RBTree();
|
||
HTree = RBTree();
|
||
// TODO: seperate initialization of nodeInitPos
|
||
// not necessary to init trees when geometric and distribution alignments are disabled,
|
||
// but reference guideline is enables
|
||
if (!nodeInitPos){
|
||
panInitPos.x = cy.pan("x"); panInitPos.y = cy.pan("y");
|
||
nodeInitPos = activeNodes.renderedPosition();
|
||
}
|
||
|
||
var nodes = cy.nodes(":visible");
|
||
excludedNodes = activeNodes.union(activeNodes.ancestors());
|
||
excludedNodes = excludedNodes.union(activeNodes.descendants());
|
||
nodes.not(excludedNodes).each(function (node, i) {
|
||
if(typeof node === "number") {
|
||
node = i;
|
||
}
|
||
var dims = lines.getDims(node);
|
||
|
||
["left", "center", "right"].forEach(function (val) {
|
||
var hKey = dims.horizontal[val];
|
||
if (HTree.get(hKey))
|
||
HTree.get(hKey).push(node);
|
||
else
|
||
HTree = HTree.insert(hKey, [node]);
|
||
});
|
||
|
||
["top", "center", "bottom"].forEach(function (val) {
|
||
var vKey = dims.vertical[val];
|
||
if (VTree.get(vKey))
|
||
VTree.get(vKey).push(node);
|
||
else
|
||
VTree = VTree.insert(vKey, [node]);
|
||
});
|
||
|
||
});
|
||
ctx.lineWidth=options.lineWidth;
|
||
lines.update(activeNodes);
|
||
};
|
||
|
||
/* Destroy gobal variables */
|
||
lines.destroy = function () {
|
||
lines.clear();
|
||
VTree = null; HTree = null;
|
||
nodeInitPos = null;
|
||
mouseInitPos = {};
|
||
alignedLocations = {"h" : null, "v" : null};
|
||
if (nodeToAlign){
|
||
nodeToAlign.unlock();
|
||
nodeToAlign = undefined;
|
||
}
|
||
};
|
||
|
||
lines.clear = clearDrawing;
|
||
|
||
/**
|
||
* Draw straight line
|
||
* @param from : initial position
|
||
* @param to : final position
|
||
* @param color : color of the line
|
||
* @param lineStyle : whether line is solid or dashed
|
||
*/
|
||
lines.drawLine = function (from, to, color, lineStyle) {
|
||
ctx.setLineDash(lineStyle);
|
||
ctx.beginPath();
|
||
ctx.moveTo(from.x, from.y);
|
||
ctx.lineTo(to.x, to.y);
|
||
ctx.strokeStyle = color;
|
||
ctx.stroke();
|
||
};
|
||
|
||
/**
|
||
* Draw an arrow
|
||
* @param position : position of the arrow
|
||
* @param type : type/directşon of the arrow
|
||
*/
|
||
lines.drawArrow = function(position, type){
|
||
if (type == "right"){
|
||
// right arrow
|
||
ctx.setLineDash([]);
|
||
ctx.beginPath();
|
||
ctx.moveTo(position.x-5, position.y-5);
|
||
ctx.lineTo(position.x, position.y);
|
||
ctx.lineTo(position.x-5, position.y+5);
|
||
ctx.stroke();
|
||
}
|
||
else if (type == "left"){
|
||
// left arrow
|
||
ctx.setLineDash([]);
|
||
ctx.beginPath();
|
||
ctx.moveTo(position.x+5, position.y-5);
|
||
ctx.lineTo(position.x, position.y);
|
||
ctx.lineTo(position.x+5, position.y+5);
|
||
ctx.stroke();
|
||
}
|
||
else if (type == "top"){
|
||
// up arrow
|
||
ctx.setLineDash([]);
|
||
ctx.beginPath();
|
||
ctx.moveTo(position.x-5, position.y+5);
|
||
ctx.lineTo(position.x, position.y);
|
||
ctx.lineTo(position.x+5, position.y+5);
|
||
ctx.stroke();
|
||
}
|
||
else if (type == "bottom"){
|
||
// down arrow
|
||
ctx.setLineDash([]);
|
||
ctx.beginPath();
|
||
ctx.moveTo(position.x-5, position.y-5);
|
||
ctx.lineTo(position.x, position.y);
|
||
ctx.lineTo(position.x+5, position.y-5);
|
||
ctx.stroke();
|
||
}
|
||
|
||
}
|
||
|
||
/**
|
||
* Draw a cross - x
|
||
* @param position : position of the cross
|
||
*/
|
||
lines.drawCross = function(position){
|
||
ctx.setLineDash([]);
|
||
ctx.beginPath();
|
||
ctx.moveTo(position.x - 5, position.y + 5);
|
||
ctx.lineTo(position.x + 5, position.y - 5);
|
||
ctx.moveTo(position.x - 5, position.y - 5);
|
||
ctx.lineTo(position.x + 5, position.y + 5);
|
||
ctx.stroke();
|
||
};
|
||
|
||
/**
|
||
* Calculate the amount of offset for distribution guidelines
|
||
* @param nodes - list of nodes
|
||
* @param type - horizontal or vertical
|
||
*/
|
||
calculateOffset = function(nodes, type){
|
||
var minNode = nodes[0], min = lines.getDims(minNode)[type]["center"];
|
||
var maxNode = nodes[0], max = lines.getDims(maxNode)[type]["center"];
|
||
|
||
for (var i = 0; i < nodes.length; i++){
|
||
var node = nodes[i];
|
||
if (lines.getDims(node)[type]["center"] < min){
|
||
min = lines.getDims(node)[type]["center"]; minNode = node;
|
||
}
|
||
if (lines.getDims(node)[type]["center"] > max){
|
||
max = lines.getDims(node)[type]["center"]; maxNode = node;
|
||
}
|
||
}
|
||
|
||
if (type == "horizontal")
|
||
var offset = (min + max) / 2 < lines.getDims(nodes[1])[type]["center"] ? max + (0.5*maxNode.width() + options.guidelinesStyle.distGuidelineOffset)*cy.zoom() : min - (0.5*minNode.width() + options.guidelinesStyle.distGuidelineOffset)*cy.zoom();
|
||
else
|
||
var offset = (min + max) / 2 < lines.getDims(nodes[1])[type]["center"] ? max + (0.5*maxNode.height() + options.guidelinesStyle.distGuidelineOffset)*cy.zoom() : min - (0.5*minNode.height() + options.guidelinesStyle.distGuidelineOffset)*cy.zoom();
|
||
|
||
return offset;
|
||
}
|
||
/** Guidelines for horizontally distributed alignment
|
||
* @param: node the node to be aligned
|
||
*/
|
||
lines.horizontalDistribution = function(node){
|
||
// variables
|
||
var leftNode = null, rightNode = null;
|
||
var nodeDim = lines.getDims(node);
|
||
var Xcenter = nodeDim["horizontal"]["center"];
|
||
var Ycenter = nodeDim["vertical"]["center"];
|
||
// Find nodes in range and check if they align
|
||
HTree.forEach(function(key, nodes){
|
||
|
||
for (var i = 0; i < nodes.length; i++){
|
||
var left = nodes[i];
|
||
var leftDim = lines.getDims(left);
|
||
if (Math.abs(leftDim["vertical"]["center"] - nodeDim["vertical"]["center"]) < options.guidelinesStyle.range*cy.zoom()){
|
||
if ((leftDim["horizontal"]["right"]) == key &&
|
||
nodeDim["horizontal"]["left"] - leftDim["horizontal"]["right"] > options.guidelinesStyle.minDistRange){
|
||
var ripo = Math.round(2*Xcenter)-key;
|
||
HTree.forEach(function($, rightNodes){
|
||
for (var j = 0; j < rightNodes.length; j++){
|
||
var right = rightNodes[j];
|
||
if (Math.abs(lines.getDims(right)["vertical"]["center"] - Ycenter) < options.guidelinesStyle.range*cy.zoom()){
|
||
if (Math.abs(ripo - lines.getDims(right)["horizontal"]["left"]) < 2*options.guidelinesTolerance){
|
||
leftNode = left; rightNode = right;
|
||
}
|
||
}
|
||
}
|
||
}, ripo - options.guidelinesTolerance, ripo + options.guidelinesTolerance);
|
||
}
|
||
}
|
||
}
|
||
}, Xcenter - options.guidelinesStyle.range*cy.zoom(), Xcenter);
|
||
|
||
// Draw the lines
|
||
if (leftNode){
|
||
alignedLocations.hd = Xcenter - (lines.getDims(rightNode)["horizontal"]["left"] + lines.getDims(leftNode)["horizontal"]["right"]) / 2.0;
|
||
if (!options.geometricGuideline || alignedLocations.h == null || Math.abs(alignedLocations.h) > Math.abs(alignedLocations.hd)){
|
||
alignedLocations.h = alignedLocations.hd;
|
||
}
|
||
var offset = calculateOffset([leftNode, node, rightNode], "vertical");
|
||
|
||
lines.drawLine({
|
||
x: lines.getDims(leftNode)["horizontal"]["right"],
|
||
y: offset
|
||
}, {
|
||
x: nodeDim["horizontal"]["left"],
|
||
y: offset
|
||
}, options.guidelinesStyle.horizontalDistColor, options.guidelinesStyle.horizontalDistLine);
|
||
|
||
lines.drawLine({
|
||
x: lines.getDims(rightNode)["horizontal"]["left"],
|
||
y: offset
|
||
}, {
|
||
x: nodeDim["horizontal"]["right"],
|
||
y: offset
|
||
}, options.guidelinesStyle.horizontalDistColor, options.guidelinesStyle.horizontalDistLine);
|
||
|
||
lines.drawLine({
|
||
x: lines.getDims(leftNode)["horizontal"]["right"],
|
||
y: offset
|
||
}, {
|
||
x: lines.getDims(leftNode)["horizontal"]["right"],
|
||
y: lines.getDims(leftNode)["vertical"]["center"]
|
||
}, options.guidelinesStyle.horizontalDistColor, options.guidelinesStyle.horizontalDistLine);
|
||
|
||
lines.drawLine({
|
||
x: lines.getDims(rightNode)["horizontal"]["left"],
|
||
y: offset
|
||
}, {
|
||
x: lines.getDims(rightNode)["horizontal"]["left"],
|
||
y: lines.getDims(rightNode)["vertical"]["center"]
|
||
}, options.guidelinesStyle.horizontalDistColor, options.guidelinesStyle.horizontalDistLine);
|
||
|
||
lines.drawLine({
|
||
x: nodeDim["horizontal"]["left"],
|
||
y: offset
|
||
}, {
|
||
x: nodeDim["horizontal"]["left"],
|
||
y: Ycenter
|
||
}, options.guidelinesStyle.horizontalDistColor, options.guidelinesStyle.horizontalDistLine);
|
||
|
||
lines.drawLine({
|
||
x: nodeDim["horizontal"]["right"],
|
||
y: offset
|
||
}, {
|
||
x: nodeDim["horizontal"]["right"],
|
||
y: Ycenter
|
||
}, options.guidelinesStyle.horizontalDistColor, options.guidelinesStyle.horizontalDistLine);
|
||
|
||
lines.drawArrow({
|
||
x: lines.getDims(leftNode)["horizontal"]["right"],
|
||
y: offset}, "left");
|
||
|
||
lines.drawArrow({
|
||
x: nodeDim["horizontal"]["left"],
|
||
y: offset}, "right");
|
||
|
||
lines.drawArrow({
|
||
x: nodeDim["horizontal"]["right"],
|
||
y: offset}, "left");
|
||
|
||
lines.drawArrow({
|
||
x: lines.getDims(rightNode)["horizontal"]["left"],
|
||
y: offset}, "right");
|
||
|
||
}
|
||
else{
|
||
var state = lines.horizontalDistributionNext(node,"left" );
|
||
|
||
if (!state)
|
||
lines.horizontalDistributionNext(node,"right" );
|
||
}
|
||
}
|
||
|
||
/** Guidelines for horizontally distributed alignment
|
||
* @param: node the node to be aligned
|
||
*/
|
||
lines.verticalDistribution = function(node){
|
||
// variables
|
||
var belowNode = null, aboveNode = null;
|
||
var nodeDim = lines.getDims(node);
|
||
var Xcenter = nodeDim["horizontal"]["center"];
|
||
var Ycenter = nodeDim["vertical"]["center"];
|
||
// Find nodes in range and check if they align
|
||
VTree.forEach(function(key, nodes){
|
||
|
||
for (var i = 0; i < nodes.length; i++){
|
||
var below = nodes[i];
|
||
var belowDim = lines.getDims(below);
|
||
if (Math.abs(belowDim["horizontal"]["center"] - nodeDim["horizontal"]["center"]) < options.guidelinesStyle.range*cy.zoom()){
|
||
if (belowDim["vertical"]["bottom"] == key &&
|
||
nodeDim["vertical"]["top"] - belowDim["vertical"]["bottom"] > options.guidelinesStyle.minDistRange){
|
||
var abpo = Math.round((2*Ycenter)-key);
|
||
VTree.forEach(function($, aboveNodes){
|
||
//if (aboveNodes){
|
||
for (var j = 0; j < aboveNodes.length; j++){
|
||
var above = aboveNodes[j];
|
||
if (Math.abs(lines.getDims(above)["horizontal"]["center"] - Xcenter) < options.guidelinesStyle.range*cy.zoom()){
|
||
if (Math.abs(abpo - lines.getDims(above)["vertical"]["top"]) < 2*options.guidelinesTolerance){
|
||
belowNode = below; aboveNode = above;
|
||
}
|
||
}
|
||
}
|
||
//}
|
||
}, abpo - options.guidelinesTolerance, abpo + options.guidelinesTolerance);
|
||
}
|
||
}
|
||
}
|
||
}, Ycenter - options.guidelinesStyle.range*cy.zoom(), Ycenter);
|
||
|
||
if (belowNode){
|
||
alignedLocations.vd = Ycenter - (lines.getDims(belowNode)["vertical"]["bottom"] + lines.getDims(aboveNode)["vertical"]["top"]) / 2.0;
|
||
if (!options.geometricGuideline || alignedLocations.v == null || Math.abs(alignedLocations.v) > Math.abs(alignedLocations.vd)){
|
||
alignedLocations.v = alignedLocations.vd;
|
||
}
|
||
var offset = calculateOffset([belowNode, node, aboveNode], "horizontal");
|
||
lines.drawLine({
|
||
y: lines.getDims(belowNode)["vertical"]["bottom"],//renderedPosition("x"),
|
||
x: offset
|
||
}, {
|
||
y: nodeDim["vertical"]["top"],
|
||
x: offset
|
||
}, options.guidelinesStyle.verticalDistColor, options.guidelinesStyle.verticalDistLine);
|
||
|
||
lines.drawLine({
|
||
y: lines.getDims(aboveNode)["vertical"]["top"],//renderedPosition("x"),
|
||
x: offset
|
||
}, {
|
||
y: nodeDim["vertical"]["bottom"],
|
||
x: offset
|
||
}, options.guidelinesStyle.verticalDistColor, options.guidelinesStyle.verticalDistLine);
|
||
|
||
lines.drawLine({
|
||
y: lines.getDims(belowNode)["vertical"]["bottom"],//renderedPosition("x"),
|
||
x: offset
|
||
}, {
|
||
y: lines.getDims(belowNode)["vertical"]["bottom"],
|
||
x: lines.getDims(belowNode)["horizontal"]["center"]
|
||
}, options.guidelinesStyle.verticalDistColor, options.guidelinesStyle.verticalDistLine);
|
||
|
||
lines.drawLine({
|
||
y: lines.getDims(aboveNode)["vertical"]["top"],//renderedPosition("x"),
|
||
x: offset
|
||
}, {
|
||
y: lines.getDims(aboveNode)["vertical"]["top"],
|
||
x: lines.getDims(aboveNode)["horizontal"]["center"]
|
||
}, options.guidelinesStyle.verticalDistColor, options.guidelinesStyle.verticalDistLine);
|
||
|
||
lines.drawLine({
|
||
y: nodeDim["vertical"]["bottom"],//renderedPosition("x"),
|
||
x: offset
|
||
}, {
|
||
y: nodeDim["vertical"]["bottom"],//renderedPosition("x"),
|
||
x: Xcenter
|
||
}, options.guidelinesStyle.verticalDistColor, options.guidelinesStyle.verticalDistLine);
|
||
|
||
lines.drawLine({
|
||
y: nodeDim["vertical"]["top"],//renderedPosition("x"),
|
||
x: offset
|
||
}, {
|
||
y: nodeDim["vertical"]["top"],//renderedPosition("x"),
|
||
x: Xcenter
|
||
}, options.guidelinesStyle.verticalDistColor, options.guidelinesStyle.verticalDistLine);
|
||
|
||
lines.drawArrow({
|
||
x: offset,
|
||
y: lines.getDims(belowNode)["vertical"]["bottom"]}, "top");
|
||
|
||
lines.drawArrow({
|
||
x: offset,
|
||
y: nodeDim["vertical"]["top"]}, "bottom");
|
||
|
||
lines.drawArrow({
|
||
x: offset,
|
||
y: lines.getDims(aboveNode)["vertical"]["top"]}, "bottom");
|
||
|
||
lines.drawArrow({
|
||
x: offset,
|
||
y: nodeDim["vertical"]["bottom"]}, "top");
|
||
}
|
||
else{
|
||
var state = lines.verticalDistributionNext(node,"below" );
|
||
|
||
if (!state)
|
||
lines.verticalDistributionNext(node,"above" );
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Find geometric alignment lines and draw them
|
||
* @param type: horizontal or vertical
|
||
* @param node: the node to be aligned
|
||
*/
|
||
lines.searchForLine = function (type, node) {
|
||
|
||
// variables
|
||
var position, target, center, axis, otherAxis, Tree, closestKey;
|
||
var dims = lines.getDims(node)[type];
|
||
var targetKey = Number.MAX_SAFE_INTEGER;
|
||
|
||
// initialize Tree
|
||
if ( type == "horizontal"){
|
||
Tree = HTree;
|
||
axis = "y";
|
||
otherAxis = "x";
|
||
alignedLocations.h = null;
|
||
} else{
|
||
Tree = VTree;
|
||
axis = "x";
|
||
otherAxis = "y";
|
||
alignedLocations.v = null;
|
||
}
|
||
|
||
center = node.renderedPosition(axis);
|
||
// check if node aligned in any dimension:
|
||
// {center, left, right} or {center, top, bottom}
|
||
for (var dimKey in dims) {
|
||
position = dims[dimKey];
|
||
|
||
// find the closest alignment in range of tolerance
|
||
Tree.forEach(function (exKey, nodes) {
|
||
for (var i = 0; i < nodes.length; i++){
|
||
var n = nodes[i];
|
||
if (options.centerToEdgeAlignment || (dimKey != "center" && n.renderedPosition(otherAxis) != exKey) || (dimKey == "center" && n.renderedPosition(otherAxis) == exKey)){
|
||
var dif = Math.abs(center - n.renderedPosition(axis));
|
||
if ( dif < targetKey && dif < options.guidelinesStyle.geometricGuidelineRange*cy.zoom()){
|
||
target = n;
|
||
targetKey = dif;
|
||
closestKey = exKey;
|
||
}
|
||
}
|
||
}
|
||
}, position - Number(options.guidelinesTolerance), position + Number(options.guidelinesTolerance));
|
||
|
||
// if alignment found, draw lines and break
|
||
if (target) {
|
||
targetKey = lines.getDims(node)[type][dimKey];
|
||
|
||
// Draw horizontal or vertical alignment line
|
||
if (type == "horizontal") {
|
||
alignedLocations.h = targetKey - closestKey;
|
||
lines.drawLine({
|
||
x: targetKey,
|
||
y: node.renderedPosition("y")
|
||
}, {
|
||
x: targetKey,
|
||
y: target.renderedPosition("y")
|
||
}, options.guidelinesStyle.strokeStyle, options.guidelinesStyle.lineDash);
|
||
} else {
|
||
alignedLocations.v = targetKey - closestKey;
|
||
lines.drawLine({
|
||
x: node.renderedPosition("x"),
|
||
y: targetKey
|
||
}, {
|
||
x: target.renderedPosition("x"),
|
||
y: targetKey
|
||
}, options.guidelinesStyle.strokeStyle, options.guidelinesStyle.lineDash);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
};
|
||
|
||
lines.horizontalDistributionNext = function(node, type){
|
||
|
||
// variables
|
||
var leftNode = null, rightNode = null;
|
||
var nodeDim = lines.getDims(node);
|
||
var Xcenter = nodeDim["horizontal"]["center"];
|
||
var Ycenter = nodeDim["vertical"]["center"];
|
||
var side = "right", otherSide = "left";
|
||
var lowerBound = Xcenter;
|
||
if (type == "left"){
|
||
side = "left"; otherSide = "right";
|
||
var lowerBound = Xcenter - options.guidelinesStyle.range*cy.zoom();
|
||
}
|
||
|
||
var compare = {
|
||
"left": function (x, y) { return y - x > options.guidelinesStyle.minDistRange},
|
||
"right": function (x, y) { return x - y > options.guidelinesStyle.minDistRange}
|
||
}
|
||
|
||
// Find nodes in range and check if they align
|
||
HTree.forEach(function(key, nodes){
|
||
for (var i = 0; i < nodes.length; i++){
|
||
var left = nodes[i];
|
||
var leftDim = lines.getDims(left);
|
||
if (Math.abs(leftDim["vertical"]["center"] - nodeDim["vertical"]["center"]) < options.guidelinesStyle.range*cy.zoom()){
|
||
if ((leftDim["horizontal"][otherSide]) == key &&
|
||
compare[type](leftDim["horizontal"][otherSide], nodeDim["horizontal"][side])){
|
||
var ll = leftDim["horizontal"][side]-(nodeDim["horizontal"][side] - key);
|
||
HTree.forEach(function($, rightNodes){
|
||
for (var j = 0; j < rightNodes.length; j++){
|
||
var right = rightNodes[j];
|
||
if (Math.abs(lines.getDims(right)["vertical"]["center"] - Ycenter) < options.guidelinesStyle.range*cy.zoom()){
|
||
if (Math.abs(ll - lines.getDims(right)["horizontal"][otherSide]) < 2*options.guidelinesTolerance){
|
||
leftNode = left; rightNode = right;
|
||
}
|
||
}
|
||
}
|
||
}, ll - options.guidelinesTolerance, ll + options.guidelinesTolerance);
|
||
}
|
||
}
|
||
}
|
||
}, lowerBound, lowerBound + options.guidelinesStyle.range*cy.zoom());
|
||
|
||
// Draw the lines
|
||
if (leftNode){
|
||
alignedLocations.hd =(lines.getDims(node)["horizontal"][side] - lines.getDims(leftNode)["horizontal"][otherSide]) - (lines.getDims(leftNode)["horizontal"][side] - lines.getDims(rightNode)["horizontal"][otherSide]);
|
||
if (!options.geometricGuideline || alignedLocations.h == null || Math.abs(alignedLocations.h) > Math.abs(alignedLocations.hd)){
|
||
alignedLocations.h = alignedLocations.hd;
|
||
}
|
||
|
||
lines.drawDH(node, leftNode, rightNode, type);
|
||
return true;
|
||
}
|
||
else if (!options.geometricGuideline){
|
||
alignedLocations.h = null;
|
||
}
|
||
return false;
|
||
|
||
}
|
||
|
||
lines.drawDH = function(node, leftNode, rightNode, type){
|
||
var Ycenter = lines.getDims(node)["vertical"]["center"];
|
||
var side = "right", otherSide = "left";
|
||
if (type == "left"){
|
||
side = "left"; otherSide = "right";
|
||
}
|
||
var offset = calculateOffset([leftNode, node, rightNode], "vertical");
|
||
|
||
lines.drawLine({
|
||
x: lines.getDims(leftNode)["horizontal"][otherSide],
|
||
y: offset
|
||
}, {
|
||
x: lines.getDims(node)["horizontal"][side],
|
||
y: offset
|
||
}, options.guidelinesStyle.horizontalDistColor, options.guidelinesStyle.horizontalDistLine);
|
||
|
||
lines.drawLine({
|
||
x: lines.getDims(node)["horizontal"][side],
|
||
y: offset
|
||
}, {
|
||
x: lines.getDims(node)["horizontal"][side],
|
||
y: Ycenter,//lines.getDims(leftNode)["vertical"]["center"]
|
||
}, options.guidelinesStyle.horizontalDistColor, options.guidelinesStyle.horizontalDistLine);
|
||
|
||
lines.drawLine({
|
||
x: lines.getDims(rightNode)["horizontal"][otherSide],
|
||
y: offset
|
||
}, {
|
||
x: lines.getDims(leftNode)["horizontal"][side],
|
||
y: offset
|
||
}, options.guidelinesStyle.horizontalDistColor, options.guidelinesStyle.horizontalDistLine);
|
||
lines.drawLine({
|
||
x: lines.getDims(rightNode)["horizontal"][otherSide],
|
||
y: offset
|
||
}, {
|
||
x: lines.getDims(rightNode)["horizontal"][otherSide],
|
||
y: lines.getDims(rightNode)["vertical"]["center"]
|
||
}, options.guidelinesStyle.horizontalDistColor, options.guidelinesStyle.horizontalDistLine);
|
||
|
||
lines.drawLine({
|
||
x: lines.getDims(leftNode)["horizontal"][otherSide],
|
||
y: offset
|
||
}, {
|
||
x: lines.getDims(leftNode)["horizontal"][otherSide],
|
||
y: lines.getDims(leftNode)["vertical"]["center"]
|
||
}, options.guidelinesStyle.horizontalDistColor, options.guidelinesStyle.horizontalDistLine);
|
||
|
||
lines.drawLine({
|
||
x: lines.getDims(leftNode)["horizontal"][side],
|
||
y: offset
|
||
}, {
|
||
x: lines.getDims(leftNode)["horizontal"][side],
|
||
y: lines.getDims(leftNode)["vertical"]["center"]
|
||
}, options.guidelinesStyle.horizontalDistColor, options.guidelinesStyle.horizontalDistLine);
|
||
|
||
|
||
lines.drawArrow({
|
||
x: lines.getDims(node)["horizontal"][side],
|
||
y: offset}, otherSide);
|
||
|
||
lines.drawArrow({
|
||
x: lines.getDims(leftNode)["horizontal"][otherSide],
|
||
y: offset}, side);
|
||
|
||
lines.drawArrow({
|
||
x: lines.getDims(leftNode)["horizontal"][side],
|
||
y: offset}, otherSide);
|
||
|
||
lines.drawArrow({
|
||
x: lines.getDims(rightNode)["horizontal"][otherSide],
|
||
y: offset}, side);
|
||
|
||
}
|
||
|
||
lines.verticalDistributionNext = function(node, type){
|
||
// variables
|
||
var belowNode = null, aboveNode = null;
|
||
var nodeDim = lines.getDims(node);
|
||
var Xcenter = nodeDim["horizontal"]["center"];
|
||
var Ycenter = nodeDim["vertical"]["center"];
|
||
var side = "top", otherSide = "bottom";
|
||
var lowerBound = Ycenter - options.guidelinesStyle.range*cy.zoom();
|
||
if (type == "above"){
|
||
side = "bottom"; otherSide = "top";
|
||
lowerBound = Ycenter;
|
||
}
|
||
|
||
var compare = {
|
||
"below": function (x, y) { return y - x > options.guidelinesStyle.minDistRange},
|
||
"above": function (x, y) { return x - y > options.guidelinesStyle.minDistRange}
|
||
}
|
||
// Find nodes in range and check if they align
|
||
VTree.forEach(function(key, nodes){
|
||
for (var i = 0; i < nodes.length; i++){
|
||
var below = nodes[i];
|
||
var belowDim = lines.getDims(below);
|
||
if (Math.abs(belowDim["horizontal"]["center"] - nodeDim["horizontal"]["center"]) < options.guidelinesStyle.range*cy.zoom()){
|
||
if (belowDim["vertical"][otherSide] == key &&
|
||
compare[type](belowDim["vertical"][otherSide], nodeDim["vertical"][side])){
|
||
var ll = belowDim["vertical"][side]-(nodeDim["vertical"][side]-key);
|
||
VTree.forEach(function($, aboveNodes){
|
||
for (var j = 0; j < aboveNodes.length; j++){
|
||
var above = aboveNodes[j];
|
||
if (Math.abs(lines.getDims(above)["horizontal"]["center"] - Xcenter) < options.guidelinesStyle.range*cy.zoom()){
|
||
if (Math.abs(ll - lines.getDims(above)["vertical"][otherSide]) < 2*options.guidelinesTolerance){
|
||
belowNode = below; aboveNode = above;
|
||
}
|
||
}
|
||
}
|
||
}, ll - options.guidelinesTolerance, ll + options.guidelinesTolerance);
|
||
}
|
||
}
|
||
}
|
||
}, lowerBound, lowerBound+options.guidelinesStyle.range*cy.zoom());
|
||
|
||
if (belowNode){
|
||
alignedLocations.vd =(lines.getDims(node)["vertical"][side] - lines.getDims(belowNode)["vertical"][otherSide]) - (lines.getDims(belowNode)["vertical"][side] - lines.getDims(aboveNode)["vertical"][otherSide]);
|
||
if (!options.geometricGuideline || alignedLocations.v == null || Math.abs(alignedLocations.v) > Math.abs(alignedLocations.vd)){
|
||
alignedLocations.v = alignedLocations.vd;
|
||
}
|
||
lines.drawDV(node, belowNode, aboveNode, type);
|
||
return true;
|
||
}
|
||
else if (!options.geometricGuideline){
|
||
alignedLocations.v = null;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
|
||
|
||
lines.drawDV = function(node, belowNode, aboveNode, type){
|
||
var nodeDim = lines.getDims(node);
|
||
var Xcenter = nodeDim["horizontal"]["center"];
|
||
var side = "top", otherSide = "bottom";
|
||
if (type == "above"){
|
||
side = "bottom"; otherSide = "top";
|
||
}
|
||
|
||
var offset = calculateOffset([belowNode, node, aboveNode], "horizontal");
|
||
lines.drawLine({
|
||
x: offset,
|
||
y: nodeDim["vertical"][side]
|
||
}, {
|
||
x: offset,
|
||
y: lines.getDims(belowNode)["vertical"][otherSide]
|
||
}, options.guidelinesStyle.verticalDistColor, options.guidelinesStyle.verticalDistLine);
|
||
|
||
lines.drawLine({
|
||
x: offset,
|
||
y: lines.getDims(belowNode)["vertical"][side]
|
||
}, {
|
||
x: offset,
|
||
y: lines.getDims(aboveNode)["vertical"][otherSide]
|
||
}, options.guidelinesStyle.verticalDistColor, options.guidelinesStyle.verticalDistLine);
|
||
|
||
lines.drawLine({
|
||
x: Xcenter,
|
||
y: nodeDim["vertical"][side]
|
||
}, {
|
||
x: offset,
|
||
y: nodeDim["vertical"][side]
|
||
}, options.guidelinesStyle.verticalDistColor, options.guidelinesStyle.verticalDistLine);
|
||
|
||
lines.drawLine({
|
||
x: lines.getDims(belowNode)["horizontal"]["center"],
|
||
y: lines.getDims(belowNode)["vertical"][otherSide]
|
||
}, {
|
||
x: offset,
|
||
y: lines.getDims(belowNode)["vertical"][otherSide]
|
||
}, options.guidelinesStyle.verticalDistColor, options.guidelinesStyle.verticalDistLine);
|
||
|
||
lines.drawLine({
|
||
x: lines.getDims(belowNode)["horizontal"]["center"],
|
||
y: lines.getDims(belowNode)["vertical"][side]
|
||
}, {
|
||
x: offset,
|
||
y: lines.getDims(belowNode)["vertical"][side]
|
||
}, options.guidelinesStyle.verticalDistColor, options.guidelinesStyle.verticalDistLine);
|
||
|
||
lines.drawLine({
|
||
x: offset,//lines.getDims(aboveNode)["horizontal"]["center"],
|
||
y: lines.getDims(aboveNode)["vertical"][otherSide]
|
||
}, {
|
||
x: lines.getDims(aboveNode)["horizontal"]["center"],
|
||
y: lines.getDims(aboveNode)["vertical"][otherSide]
|
||
}, options.guidelinesStyle.verticalDistColor, options.guidelinesStyle.verticalDistLine);
|
||
|
||
lines.drawArrow({
|
||
x: offset,
|
||
y: nodeDim["vertical"][side]}, otherSide);
|
||
|
||
lines.drawArrow({
|
||
x: offset,
|
||
y: lines.getDims(belowNode)["vertical"][otherSide]}, side);
|
||
|
||
lines.drawArrow({
|
||
x: offset,
|
||
y: lines.getDims(belowNode)["vertical"][side]}, otherSide);
|
||
|
||
lines.drawArrow({
|
||
x: offset,
|
||
y: lines.getDims(aboveNode)["vertical"][otherSide]}, side);
|
||
|
||
}
|
||
lines.update = function (activeNodes) {
|
||
lines.clear();
|
||
|
||
if (options.initPosAlignment){
|
||
mouseLine(activeNodes);
|
||
}
|
||
|
||
activeNodes.each(function (node, i) {
|
||
if(typeof node === "number") {
|
||
node = i;
|
||
}
|
||
if (options.geometricGuideline){
|
||
lines.searchForLine("horizontal", node);
|
||
lines.searchForLine("vertical", node);
|
||
}
|
||
|
||
if (options.distributionGuidelines){
|
||
lines.horizontalDistribution(node);
|
||
lines.verticalDistribution(node);
|
||
}
|
||
});
|
||
|
||
};
|
||
|
||
lines.resize = function () {
|
||
resizeCanvas();
|
||
};
|
||
|
||
function getTopMostNodes(nodes) {
|
||
var nodesMap = {};
|
||
|
||
for (var i = 0; i < nodes.length; i++) {
|
||
nodesMap[nodes[i].id()] = true;
|
||
}
|
||
|
||
var roots = nodes.filter(function (ele, i) {
|
||
if(typeof ele === "number") {
|
||
ele = i;
|
||
}
|
||
|
||
var parent = ele.parent()[0];
|
||
while (parent != null) {
|
||
if (nodesMap[parent.id()]) {
|
||
return false;
|
||
}
|
||
parent = parent.parent()[0];
|
||
}
|
||
return true;
|
||
});
|
||
|
||
return roots;
|
||
}
|
||
|
||
var mouseInitPos = {};
|
||
var mouseRelativePos = {};
|
||
var getMousePos = function(e){
|
||
mouseInitPos = e.renderedPosition || e.cyRenderedPosition;
|
||
mouseRelativePos.x = mouseInitPos.x;
|
||
mouseRelativePos.y = mouseInitPos.y;
|
||
}
|
||
var setMousePos = function(panCurrPos){
|
||
mouseRelativePos.x += (panCurrPos.x - panInitPos.x);
|
||
mouseRelativePos.y += (panCurrPos.y - panInitPos.y);
|
||
panInitPos.x = panCurrPos.x; panInitPos.y = panCurrPos.y;
|
||
};
|
||
var mouseLine = function(node){
|
||
var nodeCurrentPos = node.renderedPosition();
|
||
if (Math.abs(nodeInitPos.y - nodeCurrentPos.y) < options.guidelinesTolerance){
|
||
lines.drawLine({
|
||
"x" : mouseRelativePos.x,
|
||
"y" : mouseInitPos.y
|
||
}, {
|
||
"x" : nodeCurrentPos.x,
|
||
"y" : mouseInitPos.y
|
||
}, options.guidelinesStyle.initPosAlignmentColor, options.guidelinesStyle.initPosAlignmentLine);
|
||
if (mouseInitPos.y == mouseRelativePos.y){
|
||
lines.drawCross(mouseRelativePos);
|
||
}
|
||
else{
|
||
lines.drawCross(mouseInitPos);
|
||
}
|
||
}
|
||
else if (Math.abs(nodeInitPos.x - nodeCurrentPos.x) < options.guidelinesTolerance){
|
||
lines.drawLine({
|
||
"x" : mouseInitPos.x,
|
||
"y" : mouseRelativePos.y
|
||
}, {
|
||
"x" : mouseInitPos.x,
|
||
"y" : nodeCurrentPos.y
|
||
}, options.guidelinesStyle.initPosAlignmentColor, options.guidelinesStyle.initPosAlignmentLine);
|
||
if (mouseInitPos.x == mouseRelativePos.x){
|
||
lines.drawCross(mouseRelativePos);
|
||
}
|
||
else{
|
||
lines.drawCross(mouseInitPos);
|
||
}
|
||
}
|
||
}
|
||
|
||
function moveNodes(positionDiff, nodes) {
|
||
// Get the descendants of top most nodes. Note that node.position() can move just the simple nodes.
|
||
var topMostNodes = getTopMostNodes(nodes);
|
||
var nodesToMove = topMostNodes.union(topMostNodes.descendants());
|
||
|
||
nodesToMove.filter(":childless").forEach(function(node, i) {
|
||
if(typeof node === "number") {
|
||
node = i;
|
||
}
|
||
var newPos = {x: positionDiff.x + node.renderedPosition("x"),
|
||
y: positionDiff.y + node.renderedPosition("y")};
|
||
|
||
node.renderedPosition(newPos);
|
||
});
|
||
}
|
||
|
||
var tappedNode;
|
||
cy.on("tapstart", "node", function(){tappedNode = this});
|
||
|
||
var currMousePos, oldMousePos = {"x": 0, "y": 0};
|
||
cy.on("mousemove", function(e){
|
||
currMousePos = e.renderedPosition || e.cyRenderedPosition;
|
||
if (nodeToAlign)
|
||
nodeToAlign.each(function (node, i){
|
||
if(typeof node === "number") {
|
||
node = i;
|
||
}
|
||
if (node.locked() && (Math.abs(currMousePos.x - oldMousePos.x) > 2*options.guidelinesTolerance
|
||
|| Math.abs(currMousePos.y - oldMousePos.y) > 2*options.guidelinesTolerance)){
|
||
|
||
node.unlock();
|
||
var diff = {};
|
||
diff.x = currMousePos.x - tappedNode.renderedPosition("x");
|
||
diff.y = currMousePos.y - tappedNode.renderedPosition("y");;
|
||
moveNodes(diff, node);
|
||
};
|
||
});
|
||
|
||
});
|
||
var nodeToAlign;
|
||
lines.snapToAlignmentLocation = function(activeNodes){
|
||
nodeToAlign = activeNodes;
|
||
activeNodes.each(function (node, i){
|
||
if(typeof node === "number") {
|
||
node = i;
|
||
}
|
||
var newPos = node.renderedPosition();
|
||
if (alignedLocations.h){
|
||
oldMousePos = currMousePos;
|
||
newPos.x -= alignedLocations.h;
|
||
node.renderedPosition(newPos);
|
||
}
|
||
if (alignedLocations.v){
|
||
oldMousePos = currMousePos;
|
||
newPos.y -= alignedLocations.v;
|
||
node.renderedPosition(newPos);
|
||
};
|
||
if (alignedLocations.v || alignedLocations.h){
|
||
alignedLocations.h = null;
|
||
alignedLocations.v = null;
|
||
nodeToAlign.lock();
|
||
}
|
||
});
|
||
lines.update(activeNodes);
|
||
}
|
||
|
||
return {
|
||
changeOptions: changeOptions,
|
||
lines: lines,
|
||
getTopMostNodes: getTopMostNodes,
|
||
getMousePos: getMousePos,
|
||
setMousePos: setMousePos,
|
||
resizeCanvas: resizeCanvas,
|
||
resetCanvas: resetCanvas,
|
||
}
|
||
};
|
||
|
||
},{"functional-red-black-tree":1}],7:[function(require,module,exports){
|
||
;(function(){ 'use strict';
|
||
|
||
// registers the extension on a cytoscape lib ref
|
||
var register = function(cytoscape, $){
|
||
|
||
if(!cytoscape || !$){ return; } // can't register if cytoscape unspecified
|
||
|
||
// flag that indicates if extension api functions are registed to cytoscape
|
||
// note that ideally these functions should not be directly registered to core from cytoscape.js
|
||
// extensions
|
||
var apiRegistered = false;
|
||
|
||
var defaults = {
|
||
// On/Off Modules
|
||
/* From the following four snap options, at most one should be true at a given time */
|
||
snapToGridOnRelease: true, // Snap to grid on release
|
||
snapToGridDuringDrag: false, // Snap to grid during drag
|
||
snapToAlignmentLocationOnRelease: false, // Snap to alignment location on release
|
||
snapToAlignmentLocationDuringDrag: false, // Snap to alignment location during drag
|
||
distributionGuidelines: false, //Distribution guidelines
|
||
geometricGuideline: false, // Geometric guidelines
|
||
initPosAlignment: false, // Guideline to initial mouse position
|
||
centerToEdgeAlignment: false, // Center tı edge alignment
|
||
resize: false, // Adjust node sizes to cell sizes
|
||
parentPadding: false, // Adjust parent sizes to cell sizes by padding
|
||
drawGrid: true, // Draw grid background
|
||
|
||
// General
|
||
gridSpacing: 20, // Distance between the lines of the grid.
|
||
zoomDash: true, // Determines whether the size of the dashes should change when the drawing is zoomed in and out if grid is drawn.
|
||
panGrid: false, // Determines whether the grid should move then the user moves the graph if grid is drawn.
|
||
gridStackOrder: -1, // Namely z-index
|
||
gridColor: '#dedede', // Color of grid lines
|
||
lineWidth: 1.0, // Width of grid lines
|
||
guidelinesStackOrder: 4, // z-index of guidelines
|
||
guidelinesTolerance: 2.00, // Tolerance distance for rendered positions of nodes' interaction.
|
||
guidelinesStyle: { // Set ctx properties of line. Properties are here:
|
||
strokeStyle: "#8b7d6b", // color of geometric guidelines
|
||
geometricGuidelineRange: 400, // range of geometric guidelines
|
||
range: 100, // max range of distribution guidelines
|
||
minDistRange: 10, // min range for distribution guidelines
|
||
distGuidelineOffset: 10, // shift amount of distribution guidelines
|
||
horizontalDistColor: "#ff0000", // color of horizontal distribution alignment
|
||
verticalDistColor: "#00ff00", // color of vertical distribution alignment
|
||
initPosAlignmentColor: "#0000ff", // color of alignment to initial location
|
||
lineDash: [0, 0], // line style of geometric guidelines
|
||
horizontalDistLine: [0, 0], // line style of horizontal distribıtion guidelines
|
||
verticalDistLine: [0, 0], // line style of vertical distribıtion guidelines
|
||
initPosAlignmentLine: [0, 0], // line style of alignment to initial mouse position
|
||
},
|
||
|
||
// Parent Padding
|
||
parentSpacing: -1 // -1 to set paddings of parents to gridSpacing
|
||
};
|
||
var _snapOnRelease = require("./snap_on_release");
|
||
var _snapToGridDuringDrag = require("./snap_during_drag");
|
||
var _drawGrid = require("./draw_grid");
|
||
var _resize = require("./resize");
|
||
var _eventsController = require("./events_controller");
|
||
var _guidelines = require("./guidelines");
|
||
var _parentPadding = require("./parentPadding");
|
||
var _alignment = require("./alignment");
|
||
var debounce = require("./debounce");
|
||
|
||
function getScratch(cy) {
|
||
if (!cy.scratch("_gridGuide")) {
|
||
cy.scratch("_gridGuide", { });
|
||
}
|
||
|
||
return cy.scratch("_gridGuide");
|
||
}
|
||
|
||
cytoscape( 'core', 'gridGuide', function(opts){
|
||
var cy = this;
|
||
|
||
// access the scratch pad for cy
|
||
var scratchPad = getScratch(cy);
|
||
|
||
// extend the already existing options for the instance or the default options
|
||
var options = $.extend(true, {}, scratchPad.options || defaults, opts);
|
||
|
||
// reset the options for the instance
|
||
scratchPad.options = options;
|
||
|
||
if (!scratchPad.initialized) {
|
||
|
||
var snap, resize, snapToGridDuringDrag, drawGrid, eventsController, guidelines, parentPadding, alignment;
|
||
|
||
snap = _snapOnRelease(cy, options.gridSpacing);
|
||
resize = _resize(options.gridSpacing);
|
||
snapToGridDuringDrag = _snapToGridDuringDrag(cy, snap);
|
||
drawGrid = _drawGrid(options, cy, $, debounce);
|
||
guidelines = _guidelines(options, cy, $, debounce);
|
||
parentPadding = _parentPadding(options, cy);
|
||
|
||
eventsController = _eventsController(cy, snap, resize, snapToGridDuringDrag, drawGrid, guidelines, parentPadding, $, options);
|
||
|
||
alignment = _alignment(cytoscape, cy, $, apiRegistered);
|
||
|
||
// mark that api functions are registered to cytoscape
|
||
apiRegistered = true;
|
||
|
||
eventsController.init(options);
|
||
|
||
// init params in scratchPad
|
||
scratchPad.initialized = true;
|
||
scratchPad.eventsController = eventsController;
|
||
}
|
||
else {
|
||
var eventsController = scratchPad.eventsController;
|
||
eventsController.syncWithOptions(options);
|
||
}
|
||
|
||
return this; // chainability
|
||
} ) ;
|
||
|
||
};
|
||
|
||
if( typeof module !== 'undefined' && module.exports ){ // expose as a commonjs module
|
||
module.exports = register;
|
||
}
|
||
|
||
if( typeof define !== 'undefined' && define.amd ){ // expose as an amd/requirejs module
|
||
define('cytoscape-grid-guide', function(){
|
||
return register;
|
||
});
|
||
}
|
||
|
||
if( typeof cytoscape !== 'undefined' && $ ){ // expose to global cytoscape (i.e. window.cytoscape)
|
||
register( cytoscape, $ );
|
||
}
|
||
|
||
})();
|
||
|
||
},{"./alignment":2,"./debounce":3,"./draw_grid":4,"./events_controller":5,"./guidelines":6,"./parentPadding":8,"./resize":9,"./snap_during_drag":10,"./snap_on_release":11}],8:[function(require,module,exports){
|
||
module.exports = function (opts, cy) {
|
||
|
||
var options = opts;
|
||
var ppClass = "_gridParentPadding";
|
||
|
||
function initPadding() {
|
||
var padding = options.parentSpacing < 0 ? options.gridSpacing : options.parentSpacing;
|
||
cy.style()
|
||
.selector('.' + ppClass)
|
||
.style("compound-sizing-wrt-labels", "exclude")
|
||
.style("padding-left", padding)
|
||
.style("padding-right", padding)
|
||
.style("padding-top", padding)
|
||
.style("padding-bottom", padding)
|
||
.update();
|
||
|
||
}
|
||
|
||
function changeOptions(opts) {
|
||
options = opts;
|
||
padding = options.parentSpacing < 0 ? options.gridSpacing : options.parentSpacing;
|
||
initPadding();
|
||
}
|
||
|
||
function setPaddingOfParent(node, enable) {
|
||
if (enable)
|
||
node.addClass(ppClass);
|
||
else
|
||
node.removeClass(ppClass);
|
||
}
|
||
|
||
return {
|
||
changeOptions: changeOptions,
|
||
setPaddingOfParent: setPaddingOfParent
|
||
};
|
||
};
|
||
},{}],9:[function(require,module,exports){
|
||
module.exports = function (gridSpacing) {
|
||
|
||
|
||
var changeOptions = function (opts) {
|
||
gridSpacing = Number(opts.gridSpacing);
|
||
};
|
||
|
||
var getScratch = function (node) {
|
||
if (!node.scratch("_gridGuide"))
|
||
node.scratch("_gridGuide", {});
|
||
|
||
return node.scratch("_gridGuide");
|
||
};
|
||
|
||
function resizeNode(node) {
|
||
var width = node.width();
|
||
var height = node.height();
|
||
|
||
var newWidth = Math.round((width - gridSpacing) / (gridSpacing * 2)) * (gridSpacing * 2);
|
||
var newHeight = Math.round((height - gridSpacing) / (gridSpacing * 2)) * (gridSpacing * 2);
|
||
newWidth = newWidth > 0 ? newWidth + gridSpacing : gridSpacing;
|
||
newHeight = newHeight > 0 ? newHeight + gridSpacing : gridSpacing;
|
||
|
||
if (width != newWidth || height != newHeight) {
|
||
node.style({
|
||
"width": newWidth,
|
||
"height": newHeight
|
||
});
|
||
getScratch(node).resize = {
|
||
oldWidth: width,
|
||
oldHeight: height
|
||
};
|
||
}
|
||
}
|
||
|
||
function recoverNodeDimensions(node) {
|
||
var oldSizes = getScratch(node).resize;
|
||
if (oldSizes)
|
||
node.style({
|
||
"width": oldSizes.oldWidth,
|
||
"height": oldSizes.oldHeight
|
||
});
|
||
|
||
|
||
}
|
||
|
||
|
||
return {
|
||
resizeNode: resizeNode,
|
||
recoverNodeDimensions: recoverNodeDimensions,
|
||
changeOptions: changeOptions
|
||
};
|
||
|
||
};
|
||
},{}],10:[function(require,module,exports){
|
||
module.exports = function (cy, snap) {
|
||
|
||
var snapToGridDuringDrag = {};
|
||
|
||
var attachedNode;
|
||
var draggedNodes;
|
||
|
||
var startPos;
|
||
var endPos;
|
||
|
||
snapToGridDuringDrag.onTapStartNode = function (e) {
|
||
// If user intends to do box selection, then return. Related issue #28
|
||
if (e.originalEvent.altKey || e.originalEvent.ctrlKey
|
||
|| e.originalEvent.metaKey || e.originalEvent.shiftKey){
|
||
return;
|
||
}
|
||
|
||
var cyTarget = e.target || e.cyTarget;
|
||
if (cyTarget.selected())
|
||
draggedNodes = e.cy.$(":selected");
|
||
else
|
||
draggedNodes = cyTarget;
|
||
|
||
startPos = e.position || e.cyPosition;
|
||
|
||
if (cyTarget.grabbable() && !cyTarget.locked()){
|
||
attachedNode = cyTarget;
|
||
attachedNode.lock();
|
||
//attachedNode.trigger("grab");
|
||
cy.on("tapdrag", onTapDrag);
|
||
cy.on("tapend", onTapEndNode);
|
||
}
|
||
};
|
||
|
||
var onTapEndNode = function (e) {
|
||
//attachedNode.trigger("free");
|
||
cy.off("tapdrag", onTapDrag);
|
||
cy.off("tapend", onTapEndNode);
|
||
attachedNode.unlock();
|
||
e.preventDefault();
|
||
};
|
||
|
||
var getDist = function () {
|
||
return {
|
||
x: endPos.x - startPos.x,
|
||
y: endPos.y - startPos.y
|
||
}
|
||
};
|
||
|
||
var onTapDrag = function (e) {
|
||
|
||
var nodePos = attachedNode.position();
|
||
endPos = e.position || e.cyPosition;
|
||
endPos = snap.snapPos(endPos);
|
||
var dist = getDist();
|
||
if (dist.x != 0 || dist.y != 0) {
|
||
attachedNode.unlock();
|
||
var nodes = draggedNodes.union(draggedNodes.descendants());
|
||
|
||
nodes.filter(":childless").positions(function (node, i) {
|
||
if(typeof node === "number") {
|
||
node = i;
|
||
}
|
||
var pos = node.position();
|
||
return snap.snapPos({
|
||
x: pos.x + dist.x,
|
||
y: pos.y + dist.y
|
||
});
|
||
});
|
||
|
||
startPos = endPos;
|
||
attachedNode.lock();
|
||
attachedNode.trigger("drag");
|
||
}
|
||
|
||
};
|
||
|
||
return snapToGridDuringDrag;
|
||
|
||
|
||
};
|
||
|
||
},{}],11:[function(require,module,exports){
|
||
module.exports = function (cy, gridSpacing) {
|
||
|
||
var snap = { };
|
||
|
||
snap.changeOptions = function (opts) {
|
||
gridSpacing = opts.gridSpacing;
|
||
};
|
||
|
||
var getScratch = function (node) {
|
||
if (!node.scratch("_gridGuide"))
|
||
node.scratch("_gridGuide", {});
|
||
|
||
return node.scratch("_gridGuide");
|
||
};
|
||
|
||
snap.snapPos = function (pos) {
|
||
var newPos = {
|
||
x: (Math.floor(pos.x / gridSpacing) + 0.5) * gridSpacing,
|
||
y: (Math.floor(pos.y / gridSpacing) + 0.5) * gridSpacing
|
||
};
|
||
|
||
return newPos;
|
||
};
|
||
|
||
snap.snapNode = function (node) {
|
||
|
||
var pos = node.position();
|
||
var newPos = snap.snapPos(pos);
|
||
|
||
node.position(newPos);
|
||
};
|
||
|
||
snap.snapNodesTopDown = function (nodes) {
|
||
// getTOpMostNodes -> nodes
|
||
cy.startBatch();
|
||
nodes.union(nodes.descendants()).filter(":childless").positions(function (node, i) {
|
||
if(typeof node === "number") {
|
||
node = i;
|
||
}
|
||
var pos = node.position();
|
||
return snap.snapPos(pos);
|
||
});
|
||
cy.endBatch();
|
||
};
|
||
|
||
snap.onFreeNode = function (e) {
|
||
var nodes;
|
||
var cyTarget = e.target || e.cyTarget;
|
||
if (cyTarget.selected())
|
||
nodes = e.cy.$(":selected");
|
||
else
|
||
nodes = cyTarget;
|
||
|
||
snap.snapNodesTopDown(nodes);
|
||
|
||
};
|
||
|
||
|
||
snap.recoverSnapNode = function (node) {
|
||
var snapScratch = getScratch(node).snap;
|
||
if (snapScratch) {
|
||
node.position(snapScratch.oldPos);
|
||
}
|
||
};
|
||
|
||
return snap;
|
||
|
||
|
||
|
||
|
||
|
||
};
|
||
|
||
},{}]},{},[7]);
|