mirror of
				https://git.code.sf.net/p/seeddms/code
				synced 2025-10-30 12:41:20 +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]);
 | 
