Advertisement
Guest User

Untitled

a guest
Aug 18th, 2013
155
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. //
  2. //  barnes-hut.js
  3. //
  4. //  implementation of the barnes-hut quadtree algorithm for n-body repulsion
  5. //  http://www.cs.princeton.edu/courses/archive/fall03/cs126/assignments/barnes-hut.html
  6. //
  7. //  Created by Christian Swinehart on 2011-01-14.
  8. //  Copyright (c) 2011 Samizdat Drafting Co. All rights reserved.
  9. //
  10.  
  11.   var BarnesHutTree = function(){
  12.     var _branches = []
  13.     var _branchCtr = 0
  14.     var _root = null
  15.     var _theta = .5
  16.    
  17.     var that = {
  18.       init:function(topleft, bottomright, theta){
  19.         _theta = theta
  20.  
  21.         // create a fresh root node for these spatial bounds
  22.         _branchCtr = 0
  23.         _root = that._newBranch()
  24.         _root.origin = topleft
  25.         _root.size = bottomright.subtract(topleft)
  26.       },
  27.      
  28.       insert:function(newParticle){
  29.         // add a particle to the tree, starting at the current _root and working down
  30.         var node = _root
  31.         var queue = [newParticle]
  32.  
  33.         while (queue.length){
  34.           var particle = queue.shift()
  35.           var p_mass = particle._m || particle.m
  36.           var p_quad = that._whichQuad(particle, node)
  37.  
  38.           if (node[p_quad]===undefined){
  39.             // slot is empty, just drop this node in and update the mass/c.o.m.
  40.             node[p_quad] = particle
  41.             node.mass += p_mass
  42.             if (node.p){
  43.               node.p = node.p.add(particle.p.multiply(p_mass))
  44.             }else{
  45.               node.p = particle.p.multiply(p_mass)
  46.             }
  47.            
  48.           }else if ('origin' in node[p_quad]){
  49.             // slot conatins a branch node, keep iterating with the branch
  50.             // as our new root
  51.             node.mass += (p_mass)
  52.             if (node.p) node.p = node.p.add(particle.p.multiply(p_mass))
  53.             else node.p = particle.p.multiply(p_mass)
  54.            
  55.             node = node[p_quad]
  56.             queue.unshift(particle)
  57.           }else{
  58.             // slot contains a particle, create a new branch and recurse with
  59.             // both points in the queue now
  60.             var branch_size = node.size.divide(2)
  61.             var branch_origin = new Point(node.origin)
  62.             if (p_quad[0]=='s') branch_origin.y += branch_size.y
  63.             if (p_quad[1]=='e') branch_origin.x += branch_size.x
  64.  
  65.             // replace the previously particle-occupied quad with a new internal branch node
  66.             var oldParticle = node[p_quad]
  67.             node[p_quad] = that._newBranch()
  68.             node[p_quad].origin = branch_origin
  69.             node[p_quad].size = branch_size
  70.             node.mass = p_mass
  71.             node.p = particle.p.multiply(p_mass)
  72.             node = node[p_quad]
  73.  
  74.             if (oldParticle.p.x===particle.p.x && oldParticle.p.y===particle.p.y){
  75.               // prevent infinite bisection in the case where two particles
  76.               // have identical coordinates by jostling one of them slightly
  77.               var x_spread = branch_size.x*.08
  78.               var y_spread = branch_size.y*.08
  79.               oldParticle.p.x = Math.min(branch_origin.x+branch_size.x,  
  80.                                          Math.max(branch_origin.x,  
  81.                                                   oldParticle.p.x - x_spread/2 +
  82.                                                   Math.random()*x_spread))
  83.               oldParticle.p.y = Math.min(branch_origin.y+branch_size.y,  
  84.                                          Math.max(branch_origin.y,  
  85.                                                   oldParticle.p.y - y_spread/2 +
  86.                                                   Math.random()*y_spread))
  87.             }
  88.  
  89.             // keep iterating but now having to place both the current particle and the
  90.             // one we just replaced with the branch node
  91.             queue.push(oldParticle)
  92.             queue.unshift(particle)
  93.           }
  94.  
  95.         }
  96.  
  97.       },
  98.  
  99.       applyForces:function(particle, repulsion){
  100.         // find all particles/branch nodes this particle interacts with and apply
  101.         // the specified repulsion to the particle
  102.         var queue = [_root]
  103.         while (queue.length){
  104.           node = queue.shift()
  105.           if (node===undefined) continue
  106.           if (particle===node) continue
  107.          
  108.           if ('f' in node){
  109.             // this is a particle leafnode, so just apply the force directly
  110.             var d = particle.p.subtract(node.p);
  111.             var distance = Math.max(1.0, d.magnitude());
  112.             var direction = ((d.magnitude()>0) ? d : Point.random(1)).normalize()
  113.             particle.applyForce(direction.multiply(repulsion*(node._m||node.m))
  114.                                       .divide(distance * distance) );
  115.           }else{
  116.             // it's a branch node so decide if it's cluster-y and distant enough
  117.             // to summarize as a single point. if it's too complex, open it and deal
  118.             // with its quadrants in turn
  119.             var dist = particle.p.subtract(node.p.divide(node.mass)).magnitude()
  120.             var size = Math.sqrt(node.size.x * node.size.y)
  121.             if (size/dist > _theta){ // i.e., s/d > Θ
  122.               // open the quad and recurse
  123.               queue.push(node.ne)
  124.               queue.push(node.nw)
  125.               queue.push(node.se)
  126.               queue.push(node.sw)
  127.             }else{
  128.               // treat the quad as a single body
  129.               var d = particle.p.subtract(node.p.divide(node.mass));
  130.               var distance = Math.max(1.0, d.magnitude());
  131.               var direction = ((d.magnitude()>0) ? d : Point.random(1)).normalize()
  132.               particle.applyForce(direction.multiply(repulsion*(node.mass))
  133.                                            .divide(distance * distance) );
  134.             }
  135.           }
  136.         }
  137.       },
  138.      
  139.       _whichQuad:function(particle, node){
  140.         // sort the particle into one of the quadrants of this node
  141.         if (particle.p.exploded()) return null
  142.         var particle_p = particle.p.subtract(node.origin)
  143.         var halfsize = node.size.divide(2)
  144.         if (particle_p.y < halfsize.y){
  145.           if (particle_p.x < halfsize.x) return 'nw'
  146.           else return 'ne'
  147.         }else{
  148.           if (particle_p.x < halfsize.x) return 'sw'
  149.           else return 'se'
  150.         }
  151.       },
  152.      
  153.       _newBranch:function(){
  154.         // to prevent a gc horrorshow, recycle the tree nodes between iterations
  155.         if (_branches[_branchCtr]){
  156.           var branch = _branches[_branchCtr]
  157.           branch.ne = branch.nw = branch.se = branch.sw = undefined
  158.           branch.mass = 0
  159.           delete branch.p
  160.         }else{
  161.           branch = {origin:null, size:null,
  162.                     nw:undefined, ne:undefined, sw:undefined, se:undefined, mass:0}
  163.           _branches[_branchCtr] = branch
  164.         }
  165.  
  166.         _branchCtr++
  167.         return branch
  168.       }
  169.     }
  170.    
  171.     return that
  172.   }
  173.  
  174.  
  175. //
  176. // etc.js
  177. //
  178. // misc utilities
  179. //
  180.  
  181.   var trace = function(msg){
  182.     if (typeof(window)=='undefined' || !window.console) return
  183.     var len = arguments.length
  184.     var args = []
  185.     for (var i=0; i<len; i++) args.push("arguments["+i+"]")
  186.     eval("console.log("+args.join(",")+")")
  187.   }  
  188.  
  189.   var dirname = function(path){
  190.     var pth = path.replace(/^\/?(.*?)\/?$/,"$1").split('/')
  191.     pth.pop()
  192.     return "/"+pth.join("/")
  193.   }
  194.   var basename = function(path){
  195.     // var pth = path.replace(/^\//,'').split('/')
  196.     var pth = path.replace(/^\/?(.*?)\/?$/,"$1").split('/')
  197.    
  198.     var base = pth.pop()
  199.     if (base=="") return null
  200.     else return base
  201.   }
  202.  
  203.   var _ordinalize_re = /(\d)(?=(\d\d\d)+(?!\d))/g
  204.   var ordinalize = function(num){
  205.     var norm = ""+num
  206.     if (num < 11000){
  207.       norm = (""+num).replace(_ordinalize_re, "$1,")
  208.     } else if (num < 1000000){
  209.       norm = Math.floor(num/1000)+"k"
  210.     } else if (num < 1000000000){
  211.       norm = (""+Math.floor(num/1000)).replace(_ordinalize_re, "$1,")+"m"
  212.     }
  213.     return norm
  214.   }
  215.  
  216.   /* Nano Templates (Tomasz Mazur, Jacek Becela) */
  217.   var nano = function(template, data){
  218.     return template.replace(/\{([\w\-\.]*)}/g, function(str, key){
  219.       var keys = key.split("."), value = data[keys.shift()]
  220.       $.each(keys, function(){
  221.         if (value.hasOwnProperty(this)) value = value[this]
  222.         else value = str
  223.       })
  224.       return value
  225.     })
  226.   }
  227.  
  228.   var objcopy = function(old){
  229.     if (old===undefined) return undefined
  230.     if (old===null) return null
  231.    
  232.     if (old.parentNode) return old
  233.     switch (typeof old){
  234.       case "string":
  235.       return old.substring(0)
  236.       break
  237.      
  238.       case "number":
  239.       return old + 0
  240.       break
  241.      
  242.       case "boolean":
  243.       return old === true
  244.       break
  245.     }
  246.  
  247.     var newObj = ($.isArray(old)) ? [] : {}
  248.     $.each(old, function(ik, v){
  249.       newObj[ik] = objcopy(v)
  250.     })
  251.     return newObj
  252.   }
  253.  
  254.   var objmerge = function(dst, src){
  255.     dst = dst || {}
  256.     src = src || {}
  257.     var merge = objcopy(dst)
  258.     for (var k in src) merge[k] = src[k]
  259.     return merge
  260.   }
  261.  
  262.   var objcmp = function(a, b, strict_ordering){
  263.     if (!a || !b) return a===b // handle null+undef
  264.     if (typeof a != typeof b) return false // handle type mismatch
  265.     if (typeof a != 'object'){
  266.       // an atomic type
  267.       return a===b
  268.     }else{
  269.       // a collection type
  270.      
  271.       // first compare buckets
  272.       if ($.isArray(a)){
  273.         if (!($.isArray(b))) return false
  274.         if (a.length != b.length) return false
  275.       }else{
  276.         var a_keys = []; for (var k in a) if (a.hasOwnProperty(k)) a_keys.push(k)
  277.         var b_keys = []; for (var k in b) if (b.hasOwnProperty(k)) b_keys.push(k)
  278.         if (!strict_ordering){
  279.           a_keys.sort()
  280.           b_keys.sort()
  281.         }
  282.         if (a_keys.join(',') !== b_keys.join(',')) return false
  283.       }
  284.      
  285.       // then compare contents
  286.       var same = true
  287.       $.each(a, function(ik){
  288.         var diff = objcmp(a[ik], b[ik])
  289.         same = same && diff
  290.         if (!same) return false
  291.       })
  292.       return same
  293.     }
  294.   }
  295.  
  296.   var objkeys = function(obj){
  297.     var keys = []
  298.     $.each(obj, function(k,v){ if (obj.hasOwnProperty(k)) keys.push(k) })
  299.     return keys
  300.   }
  301.  
  302.   var objcontains = function(obj){
  303.     if (!obj || typeof obj!='object') return false
  304.     for (var i=1, j=arguments.length; i<j; i++){
  305.       if (obj.hasOwnProperty(arguments[i])) return true
  306.     }
  307.     return false
  308.   }
  309.  
  310.   var uniq = function(arr){
  311.     // keep in mind that this is only sensible with a list of strings
  312.     // anything else, objkey type coercion will turn it into one anyway
  313.     var len = arr.length
  314.     var set = {}
  315.     for (var i=0; i<len; i++){
  316.       set[arr[i]] = true
  317.     }
  318.  
  319.     return objkeys(set)
  320.   }
  321.  
  322.   var arbor_path = function(){
  323.     var candidates = $("script").map(function(elt){
  324.       var src = $(this).attr('src')
  325.       if (!src) return
  326.       if (src.match(/arbor[^\/\.]*.js|dev.js/)){
  327.         return src.match(/.*\//) || "/"
  328.       }
  329.     })
  330.  
  331.     if (candidates.length>0) return candidates[0]
  332.     else return null
  333.   }
  334.  
  335.  
  336.  
  337.  
  338. //
  339. // kernel.js
  340. //
  341. // run-loop manager for physics and tween updates
  342. //
  343.    
  344.   var Kernel = function(pSystem){
  345.     // in chrome, web workers aren't available to pages with file:// urls
  346.     var chrome_local_file = window.location.protocol == "file:" &&
  347.                             navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
  348.     var USE_WORKER = (window.Worker !== undefined && !chrome_local_file)    
  349.  
  350.     var _physics = null
  351.     var _tween = null
  352.     var _fpsWindow = [] // for keeping track of the actual frame rate
  353.     _fpsWindow.last = new Date()
  354.     var _screenInterval = null
  355.     var _attached = null
  356.  
  357.     var _tickInterval = null
  358.     var _lastTick = null
  359.     var _paused = false
  360.    
  361.     var that = {
  362.       system:pSystem,
  363.       tween:null,
  364.       nodes:{},
  365.  
  366.       init:function(){
  367.         if (typeof(Tween)!='undefined') _tween = Tween()
  368.         else if (typeof(arbor.Tween)!='undefined') _tween = arbor.Tween()
  369.         else _tween = {busy:function(){return false},
  370.                        tick:function(){return true},
  371.                        to:function(){ trace('Please include arbor-tween.js to enable tweens'); _tween.to=function(){}; return} }
  372.         that.tween = _tween
  373.         var params = pSystem.parameters()
  374.                
  375.         if(USE_WORKER){
  376.           trace('using web workers')
  377.           _screenInterval = setInterval(that.screenUpdate, params.timeout)
  378.  
  379.           _physics = new Worker(arbor_path()+'physics/worker.js')
  380.           _physics.onmessage = that.workerMsg
  381.           _physics.onerror = function(e){ trace('physics:',e) }
  382.           _physics.postMessage({type:"physics",
  383.                                 physics:objmerge(params,
  384.                                                 {timeout:Math.ceil(params.timeout)}) })
  385.         }else{
  386.           trace("couldn't use web workers, be careful...")
  387.           _physics = Physics(params.dt, params.stiffness, params.repulsion, params.friction, that.system._updateGeometry)
  388.           that.start()
  389.         }
  390.  
  391.         return that
  392.       },
  393.  
  394.       //
  395.       // updates from the ParticleSystem
  396.       graphChanged:function(changes){
  397.         // a node or edge was added or deleted
  398.         if (USE_WORKER) _physics.postMessage({type:"changes","changes":changes})
  399.         else _physics._update(changes)
  400.         that.start() // <- is this just to kick things off in the non-worker mode? (yes)
  401.       },
  402.  
  403.       particleModified:function(id, mods){
  404.         // a particle's position or mass is changed
  405.         // trace('mod',objkeys(mods))
  406.         if (USE_WORKER) _physics.postMessage({type:"modify", id:id, mods:mods})
  407.         else _physics.modifyNode(id, mods)
  408.         that.start() // <- is this just to kick things off in the non-worker mode? (yes)
  409.       },
  410.  
  411.       physicsModified:function(param){
  412.  
  413.         // intercept changes to the framerate in case we're using a worker and
  414.         // managing our own draw timer
  415.         if (!isNaN(param.timeout)){
  416.           if (USE_WORKER){
  417.             clearInterval(_screenInterval)
  418.             _screenInterval = setInterval(that.screenUpdate, param.timeout)
  419.           }else{
  420.             // clear the old interval then let the call to .start set the new one
  421.             clearInterval(_tickInterval)
  422.             _tickInterval=null
  423.           }
  424.         }
  425.  
  426.         // a change to the physics parameters
  427.         if (USE_WORKER) _physics.postMessage({type:'sys',param:param})
  428.         else _physics.modifyPhysics(param)
  429.         that.start() // <- is this just to kick things off in the non-worker mode? (yes)
  430.       },
  431.      
  432.       workerMsg:function(e){
  433.         var type = e.data.type
  434.         if (type=='geometry'){
  435.           that.workerUpdate(e.data)
  436.         }else{
  437.           trace('physics:',e.data)
  438.         }
  439.       },
  440.       _lastPositions:null,
  441.       workerUpdate:function(data){
  442.         that._lastPositions = data
  443.         that._lastBounds = data.bounds
  444.       },
  445.      
  446.  
  447.       //
  448.       // the main render loop when running in web worker mode
  449.       _lastFrametime:new Date().valueOf(),
  450.       _lastBounds:null,
  451.       _currentRenderer:null,
  452.       screenUpdate:function(){        
  453.         var now = new Date().valueOf()
  454.        
  455.         var shouldRedraw = false
  456.         if (that._lastPositions!==null){
  457.           that.system._updateGeometry(that._lastPositions)
  458.           that._lastPositions = null
  459.           shouldRedraw = true
  460.         }
  461.        
  462.         if (_tween && _tween.busy()) shouldRedraw = true
  463.  
  464.         if (that.system._updateBounds(that._lastBounds)) shouldRedraw=true
  465.        
  466.  
  467.         if (shouldRedraw){
  468.           var render = that.system.renderer
  469.           if (render!==undefined){
  470.             if (render !== _attached){
  471.                render.init(that.system)
  472.                _attached = render
  473.             }          
  474.            
  475.             if (_tween) _tween.tick()
  476.             render.redraw()
  477.  
  478.             var prevFrame = _fpsWindow.last
  479.             _fpsWindow.last = new Date()
  480.             _fpsWindow.push(_fpsWindow.last-prevFrame)
  481.             if (_fpsWindow.length>50) _fpsWindow.shift()
  482.           }
  483.         }
  484.       },
  485.  
  486.       //
  487.       // the main render loop when running in non-worker mode
  488.       physicsUpdate:function(){
  489.         if (_tween) _tween.tick()
  490.         _physics.tick()
  491.  
  492.         var stillActive = that.system._updateBounds()
  493.         if (_tween && _tween.busy()) stillActive = true
  494.  
  495.         var render = that.system.renderer
  496.         var now = new Date()        
  497.         var render = that.system.renderer
  498.         if (render!==undefined){
  499.           if (render !== _attached){
  500.             render.init(that.system)
  501.             _attached = render
  502.           }          
  503.           render.redraw({timestamp:now})
  504.         }
  505.  
  506.         var prevFrame = _fpsWindow.last
  507.         _fpsWindow.last = now
  508.         _fpsWindow.push(_fpsWindow.last-prevFrame)
  509.         if (_fpsWindow.length>50) _fpsWindow.shift()
  510.  
  511.         // but stop the simulation when energy of the system goes below a threshold
  512.         var sysEnergy = _physics.systemEnergy()
  513.         if ((sysEnergy.mean + sysEnergy.max)/2 < 0.05){
  514.           if (_lastTick===null) _lastTick=new Date().valueOf()
  515.           if (new Date().valueOf()-_lastTick>1000){
  516.             // trace('stopping')
  517.             clearInterval(_tickInterval)
  518.             _tickInterval = null
  519.           }else{
  520.             // trace('pausing')
  521.           }
  522.         }else{
  523.           // trace('continuing')
  524.           _lastTick = null
  525.         }
  526.       },
  527.  
  528.  
  529.       fps:function(newTargetFPS){
  530.         if (newTargetFPS!==undefined){
  531.           var timeout = 1000/Math.max(1,targetFps)
  532.           that.physicsModified({timeout:timeout})
  533.         }
  534.        
  535.         var totInterv = 0
  536.         for (var i=0, j=_fpsWindow.length; i<j; i++) totInterv+=_fpsWindow[i]
  537.         var meanIntev = totInterv/Math.max(1,_fpsWindow.length)
  538.         if (!isNaN(meanIntev)) return Math.round(1000/meanIntev)
  539.         else return 0
  540.       },
  541.  
  542.       //
  543.       // start/stop simulation
  544.       //
  545.       start:function(unpause){
  546.         if (_tickInterval !== null) return; // already running
  547.         if (_paused && !unpause) return; // we've been .stopped before, wait for unpause
  548.         _paused = false
  549.        
  550.         if (USE_WORKER){
  551.            _physics.postMessage({type:"start"})
  552.         }else{
  553.           _lastTick = null
  554.           _tickInterval = setInterval(that.physicsUpdate,
  555.                                       that.system.parameters().timeout)
  556.         }
  557.       },
  558.       stop:function(){
  559.         _paused = true
  560.         if (USE_WORKER){
  561.            _physics.postMessage({type:"stop"})
  562.         }else{
  563.           if (_tickInterval!==null){
  564.             clearInterval(_tickInterval)
  565.             _tickInterval = null
  566.           }
  567.         }
  568.      
  569.       }
  570.     }
  571.    
  572.     return that.init()    
  573.   }
  574.  
  575.  
  576. var Colors = (function(){
  577.   var iscolor_re = /#[0-9a-f]{6}/i
  578.   var hexrgb_re = /#(..)(..)(..)/
  579.  
  580.   var d2h = function(d){
  581.     // decimal to hex
  582.     var s=d.toString(16);
  583.     return (s.length==2) ? s : '0'+s
  584.   }
  585.  
  586.   var h2d = function(h){
  587.     // hex to decimal
  588.     return parseInt(h,16);
  589.   }
  590.  
  591.   var _isRGB = function(color){
  592.     if (!color || typeof color!='object') return false
  593.     var components = objkeys(color).sort().join("")
  594.     if (components == 'abgr') return true
  595.   }
  596.  
  597.   // var _isHSB = function(color){
  598.   //   if (!color || typeof cssOrHex!='object') return false
  599.   //   var components = objkeys(color).sort().join("")
  600.   //   if (components == 'hsb') return true
  601.   // }
  602.  
  603.  
  604.   var that = {
  605.     CSS:{aliceblue:"#f0f8ff", antiquewhite:"#faebd7", aqua:"#00ffff", aquamarine:"#7fffd4", azure:"#f0ffff", beige:"#f5f5dc", bisque:"#ffe4c4", black:"#000000", blanchedalmond:"#ffebcd", blue:"#0000ff", blueviolet:"#8a2be2", brown:"#a52a2a", burlywood:"#deb887", cadetblue:"#5f9ea0", chartreuse:"#7fff00", chocolate:"#d2691e", coral:"#ff7f50", cornflowerblue:"#6495ed", cornsilk:"#fff8dc", crimson:"#dc143c", cyan:"#00ffff", darkblue:"#00008b", darkcyan:"#008b8b", darkgoldenrod:"#b8860b", darkgray:"#a9a9a9", darkgrey:"#a9a9a9", darkgreen:"#006400", darkkhaki:"#bdb76b", darkmagenta:"#8b008b", darkolivegreen:"#556b2f", darkorange:"#ff8c00", darkorchid:"#9932cc", darkred:"#8b0000", darksalmon:"#e9967a", darkseagreen:"#8fbc8f", darkslateblue:"#483d8b", darkslategray:"#2f4f4f", darkslategrey:"#2f4f4f", darkturquoise:"#00ced1", darkviolet:"#9400d3", deeppink:"#ff1493", deepskyblue:"#00bfff", dimgray:"#696969", dimgrey:"#696969", dodgerblue:"#1e90ff", firebrick:"#b22222", floralwhite:"#fffaf0", forestgreen:"#228b22", fuchsia:"#ff00ff", gainsboro:"#dcdcdc", ghostwhite:"#f8f8ff", gold:"#ffd700", goldenrod:"#daa520", gray:"#808080", grey:"#808080", green:"#008000", greenyellow:"#adff2f", honeydew:"#f0fff0", hotpink:"#ff69b4", indianred:"#cd5c5c", indigo:"#4b0082", ivory:"#fffff0", khaki:"#f0e68c", lavender:"#e6e6fa", lavenderblush:"#fff0f5", lawngreen:"#7cfc00", lemonchiffon:"#fffacd", lightblue:"#add8e6", lightcoral:"#f08080", lightcyan:"#e0ffff", lightgoldenrodyellow:"#fafad2", lightgray:"#d3d3d3", lightgrey:"#d3d3d3", lightgreen:"#90ee90", lightpink:"#ffb6c1", lightsalmon:"#ffa07a", lightseagreen:"#20b2aa", lightskyblue:"#87cefa", lightslategray:"#778899", lightslategrey:"#778899", lightsteelblue:"#b0c4de", lightyellow:"#ffffe0", lime:"#00ff00", limegreen:"#32cd32", linen:"#faf0e6", magenta:"#ff00ff", maroon:"#800000", mediumaquamarine:"#66cdaa", mediumblue:"#0000cd", mediumorchid:"#ba55d3", mediumpurple:"#9370d8", mediumseagreen:"#3cb371", mediumslateblue:"#7b68ee", mediumspringgreen:"#00fa9a", mediumturquoise:"#48d1cc", mediumvioletred:"#c71585", midnightblue:"#191970", mintcream:"#f5fffa", mistyrose:"#ffe4e1", moccasin:"#ffe4b5", navajowhite:"#ffdead", navy:"#000080", oldlace:"#fdf5e6", olive:"#808000", olivedrab:"#6b8e23", orange:"#ffa500", orangered:"#ff4500", orchid:"#da70d6", palegoldenrod:"#eee8aa", palegreen:"#98fb98", paleturquoise:"#afeeee", palevioletred:"#d87093", papayawhip:"#ffefd5", peachpuff:"#ffdab9", peru:"#cd853f", pink:"#ffc0cb", plum:"#dda0dd", powderblue:"#b0e0e6", purple:"#800080", red:"#ff0000", rosybrown:"#bc8f8f", royalblue:"#4169e1", saddlebrown:"#8b4513", salmon:"#fa8072", sandybrown:"#f4a460", seagreen:"#2e8b57", seashell:"#fff5ee", sienna:"#a0522d", silver:"#c0c0c0", skyblue:"#87ceeb", slateblue:"#6a5acd", slategray:"#708090", slategrey:"#708090", snow:"#fffafa", springgreen:"#00ff7f", steelblue:"#4682b4", tan:"#d2b48c", teal:"#008080", thistle:"#d8bfd8", tomato:"#ff6347", turquoise:"#40e0d0", violet:"#ee82ee", wheat:"#f5deb3", white:"#ffffff", whitesmoke:"#f5f5f5", yellow:"#ffff00", yellowgreen:"#9acd32"},
  606.  
  607.     // possible invocations:
  608.     //    decode(1,2,3,.4)      -> {r:1,   g:2,   b:3,   a:0.4}
  609.     //    decode(128, .7)       -> {r:128, g:128, b:128, a:0.7}    
  610.     //    decode("#ff0000")     -> {r:255, g:0,   b:0,   a:1}
  611.     //    decode("#ff0000",.5)  -> {r:255, g:0,   b:0,   a:0.5}
  612.     //    decode("white")       -> {r:255, g:255, b:255, a:1}
  613.     //    decode({r:0,g:0,b:0}) -> {r:0,   g:0,   b:0,   a:1}
  614.     decode:function(clr){
  615.       var argLen = arguments.length
  616.       for (var i=argLen-1; i>=0; i--) if (arguments[i]===undefined) argLen--
  617.       var args = arguments
  618.       if (!clr) return null
  619.       if (argLen==1 && _isRGB(clr)) return clr
  620.  
  621.       var rgb = null
  622.  
  623.       if (typeof clr=='string'){
  624.         var alpha = 1
  625.         if (argLen==2) alpha = args[1]
  626.        
  627.         var nameMatch = that.CSS[clr.toLowerCase()]
  628.         if (nameMatch!==undefined){
  629.            clr = nameMatch
  630.         }
  631.         var hexMatch = clr.match(iscolor_re)
  632.         if (hexMatch){
  633.           vals = clr.match(hexrgb_re)
  634.           // trace(vals)
  635.           if (!vals || !vals.length || vals.length!=4) return null    
  636.           rgb = {r:h2d(vals[1]), g:h2d(vals[2]), b:h2d(vals[3]), a:alpha}
  637.         }
  638.       }else if (typeof clr=='number'){
  639.         if (argLen>=3){
  640.           rgb = {r:args[0], g:args[1], b:args[2], a:1}
  641.           if (argLen>=4) rgb.a *= args[3]
  642.         }else if(argLen>=1){
  643.           rgb = {r:args[0], g:args[0], b:args[0], a:1}
  644.           if (argLen==2) rgb.a *= args[1]
  645.         }
  646.       }
  647.  
  648.  
  649.       // if (!rgb) trace("<null color>")
  650.       // else trace(nano("<r:{r} g:{g} b:{b} a:{a}>",rgb))
  651.       //
  652.       // if (arguments.length==1){        
  653.       //   if (_isRGB(clr)) return clr
  654.       //   if (!clr || typeof clr!='string') return null
  655.       //
  656.       //   var nameMatch = that.CSS[clr.toLowerCase()]
  657.       //   if (nameMatch!==undefined){
  658.       //      clr = nameMatch
  659.       //   }
  660.       //   var hexMatch = clr.match(iscolor_re)
  661.       //   if (hexMatch){
  662.       //     vals = clr.match(hexrgb_re)
  663.       //     if (!vals || !vals.length || vals.length!=4) return null    
  664.       //     var rgb = {r:h2d(vals[1]), g:h2d(vals[2]), b:h2d(vals[3])}
  665.       //     return rgb
  666.       //   }
  667.       // }
  668.      
  669.       return rgb
  670.     },
  671.     validate:function(str){
  672.       if (!str || typeof str!='string') return false
  673.      
  674.       if (that.CSS[str.toLowerCase()] !== undefined) return true
  675.       if (str.match(iscolor_re)) return true
  676.       return false
  677.     },
  678.    
  679.     // transform
  680.     mix:function(color1, color2, proportion){
  681.       var c1 = that.decode(color1)
  682.       var c2 = that.decode(color2)
  683.      
  684.       // var mixed = ... should this be a triplet or a string?
  685.     },
  686.     blend:function(rgbOrHex, alpha){
  687.       alpha = (alpha!==undefined) ? Math.max(0,Math.min(1,alpha)) : 1
  688.      
  689.       var rgb = that.decode(rgbOrHex)
  690.       if (!rgb) return null
  691.      
  692.       if (alpha==1) return rgbOrHex
  693.       var rgb = rgbOrHex
  694.       if (typeof rgbOrHex=='string') rgb = that.decode(rgbOrHex)
  695.      
  696.       var blended = objcopy(rgb)
  697.       blended.a *= alpha
  698.      
  699.       return nano("rgba({r},{g},{b},{a})", blended)
  700.     },
  701.    
  702.     // output
  703.     encode:function(rgb){
  704.       if (!_isRGB(rgb)){
  705.         rgb = that.decode(rgb)
  706.         if (!_isRGB(rgb)) return null
  707.       }
  708.       if (rgb.a==1){
  709.         return nano("#{r}{g}{b}", {r:d2h(rgb.r), g:d2h(rgb.g), b:d2h(rgb.b)} )        
  710.       }else{
  711.         return nano("rgba({r},{g},{b},{a})", rgb)
  712.       }
  713.  
  714.       // encoding = encoding || "hex"
  715.       // if (!_isRGB(rgb)) return null
  716.       // switch(encoding){
  717.       // case "hex":
  718.       //   return nano("#{r}{g}{b}", {r:d2h(rgb.r), g:d2h(rgb.g), b:d2h(rgb.b)} )
  719.       //   break
  720.       //  
  721.       // case "rgba":
  722.       //   return nano("rgba({r},{g},{b},{alpha})", rgb)
  723.       //   break
  724.       // }
  725.       // // if (rgb===undefined || !rgb.length || rgb.length!=3) return null
  726.       // // return '#'+$.map(rgb, function(c){return d2h(c)}).join("")
  727.     }
  728.   }
  729.  
  730.   return that
  731. })();
  732.  
  733. //
  734. //  primitives
  735. //
  736. //  Created by Christian Swinehart on 2010-12-08.
  737. //  Copyright (c) 2011 Samizdat Drafting Co. All rights reserved.
  738. //
  739.  
  740.  
  741. var Primitives = function(ctx, _drawStyle, _fontStyle){
  742.  
  743.     ///MACRO:primitives-start
  744.     var _Oval = function(x,y,w,h,style){
  745.       this.x = x
  746.       this.y = y
  747.       this.w = w
  748.       this.h = h
  749.       this.style = (style!==undefined) ? style : {}
  750.     }
  751.     _Oval.prototype = {
  752.       draw:function(overrideStyle){
  753.         this._draw(overrideStyle)
  754.       },
  755.  
  756.       _draw:function(x,y,w,h, style){
  757.         if (objcontains(x, 'stroke', 'fill', 'width')) style = x
  758.         if (this.x!==undefined){
  759.           x=this.x, y=this.y, w=this.w, h=this.h;
  760.           style = objmerge(this.style, style)
  761.         }
  762.         style = objmerge(_drawStyle, style)
  763.         if (!style.stroke && !style.fill) return
  764.  
  765.         var kappa = .5522848;
  766.             ox = (w / 2) * kappa, // control point offset horizontal
  767.             oy = (h / 2) * kappa, // control point offset vertical
  768.             xe = x + w,           // x-end
  769.             ye = y + h,           // y-end
  770.             xm = x + w / 2,       // x-middle
  771.             ym = y + h / 2;       // y-middle
  772.  
  773.         ctx.save()
  774.           ctx.beginPath();
  775.           ctx.moveTo(x, ym);
  776.           ctx.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  777.           ctx.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  778.           ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  779.           ctx.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  780.           ctx.closePath();
  781.  
  782.           // trace(style.fill, style.stroke)
  783.           if (style.fill!==null){
  784.             // trace("fill",fillColor, Colors.encode(fillColor))
  785.             if (style.alpha!==undefined) ctx.fillStyle = Colors.blend(style.fill, style.alpha)
  786.             else ctx.fillStyle = Colors.encode(style.fill)
  787.             ctx.fill()
  788.           }
  789.  
  790.           if (style.stroke!==null){
  791.             ctx.strokeStyle = Colors.encode(style.stroke)
  792.             if (!isNaN(style.width)) ctx.lineWidth = style.width
  793.             ctx.stroke()
  794.           }      
  795.         ctx.restore()
  796.       }
  797.  
  798.     }
  799.  
  800.     var _Rect = function(x,y,w,h,r,style){
  801.       if (objcontains(r, 'stroke', 'fill', 'width')){
  802.          style = r
  803.          r = 0
  804.       }
  805.       this.x = x
  806.       this.y = y
  807.       this.w = w
  808.       this.h = h
  809.       this.r = (r!==undefined) ? r : 0
  810.       this.style = (style!==undefined) ? style : {}
  811.     }
  812.     _Rect.prototype = {
  813.       draw:function(overrideStyle){
  814.         this._draw(overrideStyle)
  815.       },
  816.  
  817.       _draw:function(x,y,w,h,r, style){
  818.         if (objcontains(r, 'stroke', 'fill', 'width', 'alpha')){
  819.           style = r; r=0;
  820.         }else if (objcontains(x, 'stroke', 'fill', 'width', 'alpha')){
  821.           style = x
  822.         }
  823.         if (this.x!==undefined){
  824.           x=this.x, y=this.y, w=this.w, h=this.h;
  825.           style = objmerge(this.style, style)
  826.         }
  827.         style = objmerge(_drawStyle, style)
  828.         if (!style.stroke && !style.fill) return
  829.  
  830.         var rounded = (r>0)
  831.         ctx.save()
  832.         ctx.beginPath();
  833.         ctx.moveTo(x+r, y);
  834.         ctx.lineTo(x+w-r, y);
  835.         if (rounded) ctx.quadraticCurveTo(x+w, y, x+w, y+r);
  836.         ctx.lineTo(x+w, y+h-r);
  837.         if (rounded) ctx.quadraticCurveTo(x+w, y+h, x+w-r, y+h);
  838.         ctx.lineTo(x+r, y+h);
  839.         if (rounded) ctx.quadraticCurveTo(x, y+h, x, y+h-r);
  840.         ctx.lineTo(x, y+r);
  841.         if (rounded) ctx.quadraticCurveTo(x, y, x+r, y);      
  842.  
  843.  
  844.         if (style.fill!==null){
  845.           if (style.alpha!==undefined) ctx.fillStyle = Colors.blend(style.fill, style.alpha)
  846.           else ctx.fillStyle = Colors.encode(style.fill)
  847.           ctx.fill()
  848.         }
  849.  
  850.         if (style.stroke!==null){
  851.           ctx.strokeStyle = Colors.encode(style.stroke)
  852.           if (!isNaN(style.width)) ctx.lineWidth = style.width
  853.           ctx.stroke()
  854.         }      
  855.         ctx.restore()
  856.       }
  857.     }
  858.  
  859.     var _Path = function(x1, y1, x2, y2, style){
  860.       // calling patterns:
  861.       // ƒ( x1, y1, x2, y2, <style> )
  862.       // ƒ( {x:1, y:1}, {x:2, y:2}, <style> )
  863.       // ƒ( [ {x:1, y:1}, {x:2, y:2}, ...], <style> ) one continuous line
  864.       // ƒ( [ [{x,y}, {x,y}], [{x,y}, {x,y}], ...], <style> ) separate lines
  865.  
  866.       if (style!==undefined || typeof y2=='number'){
  867.         // ƒ( x1, y1, x2, y2, <style> )
  868.         this.points = [ {x:x1,y:y1}, {x:x2,y:y2} ]
  869.         this.style = style || {}
  870.       }else if ($.isArray(x1)){
  871.         // ƒ( [ {x:1, y:1}, {x:2, y:2}, ...], <style> )
  872.         this.points = x1
  873.         this.style = y1 || {}
  874.       }else{
  875.         // ƒ( {x:1, y:1}, {x:2, y:2}, <style> )
  876.         this.points = [ x1, y1 ]
  877.         this.style = x2 || {}
  878.       }
  879.     }
  880.     _Path.prototype = {
  881.       draw:function(overrideStyle){
  882.         if (this.points.length<2) return
  883.  
  884.         var sublines = []
  885.         if (!$.isArray(this.points[0])) sublines.push(this.points)
  886.         else sublines = this.points
  887.        
  888.         ctx.save()
  889.           ctx.beginPath();
  890.           $.each(sublines, function(i, lineseg){
  891.             ctx.moveTo(lineseg[0].x+.5, lineseg[0].y+.5);
  892.             $.each(lineseg, function(i, pt){
  893.               if (i==0) return
  894.               ctx.lineTo(pt.x+.5, pt.y+.5);
  895.             })
  896.           })
  897.  
  898.           var style = $.extend(objmerge(_drawStyle, this.style), overrideStyle)
  899.           if (style.closed) ctx.closePath()
  900.  
  901.           if (style.fill!==undefined){
  902.             var fillColor = Colors.decode(style.fill, (style.alpha!==undefined) ? style.alpha : 1)
  903.             if (fillColor) ctx.fillStyle = Colors.encode(fillColor)
  904.               ctx.fill()
  905.           }
  906.  
  907.           if (style.stroke!==undefined){
  908.             var strokeColor = Colors.decode(style.stroke, (style.alpha!==undefined) ? style.alpha : 1)
  909.             if (strokeColor) ctx.strokeStyle = Colors.encode(strokeColor)
  910.             if (!isNaN(style.width)) ctx.lineWidth = style.width
  911.             ctx.stroke()
  912.           }
  913.             ctx.restore()
  914.       }
  915.     }
  916.    
  917.  
  918.     var _Color = function(a,b,c,d){
  919.       var rgba = Colors.decode(a,b,c,d)
  920.       if (rgba){
  921.         this.r = rgba.r
  922.         this.g = rgba.g
  923.         this.b = rgba.b
  924.         this.a = rgba.a
  925.       }
  926.     }
  927.  
  928.     _Color.prototype = {
  929.       toString:function(){
  930.         return Colors.encode(this)
  931.       },
  932.       blend:function(){
  933.         trace("blend",this.r,this.g,this.b,this.a)
  934.       }
  935.     }
  936.  
  937.     // var _Font = function(face, size){
  938.     //   this.face = (face!=undefined) ? face : "sans-serif"
  939.     //   this.size = (size!=undefined) ? size : 12
  940.     //   // this.alignment = (opts.alignment!=undefined) ? alignment : "left"
  941.     //   // this.baseline = (opts.baseline!=undefined) ? baseline : "ideographic"
  942.     //   // this.color = (opts.color!=undefined) ? Colors.decode(opts.color) : Colors.decode("black")
  943.     // }
  944.     // _Font.prototype = {
  945.     //   _use:function(face, size){
  946.     //     // var params = $.extend({face:face, size:size}, opts)
  947.     //     // $.each('face size alignment baseline color'.split(" "), function(i, param){
  948.     //     //   if (params[param]!==undefined){
  949.     //     //     if (param=='color') _fontStyle[param] = Colors.decode(params[param])
  950.     //     //     else _fontStyle[param] = params[param]
  951.     //     //   }
  952.     //     // })
  953.     //
  954.     //     // ctx.textAlign = _fontStyle.alignment
  955.     //     // ctx.textBaseline = _fontStyle.baseline
  956.     //     ctx.font = nano("{size}px {face}", {face:face, size:size})
  957.     //     // trace(ctx.font,face,size)      
  958.     //     // ctx.fillStyle = Colors.encode(_fontStyle)
  959.     //     // _fontStyle = {face:face, size:size, alignment:opts.alignment, baseline:opts.baseline, color:opts.color}
  960.     //   },
  961.     //   use:function(){
  962.     //     ctx.font = nano("{size}px {face}", this)
  963.     //   }
  964.     // }
  965.     //
  966.     //
  967.  
  968.   ///MACRO:primitives-end
  969.  
  970.  
  971.  
  972.  
  973.  
  974.  
  975.  
  976.  
  977.   return {
  978.     _Oval:_Oval,
  979.     _Rect:_Rect,
  980.     _Color:_Color,
  981.     _Path:_Path
  982.     // _Frame:Frame
  983.   }
  984. }
  985.  
  986. //
  987. //  graphics.js
  988. //
  989. //  Created by Christian Swinehart on 2010-12-07.
  990. //  Copyright (c) 2011 Samizdat Drafting Co. All rights reserved.
  991. //
  992.  
  993. var Graphics = function(canvas){
  994.   var dom = $(canvas)
  995.   var ctx = $(dom).get(0).getContext('2d')
  996.  
  997.   var _bounds = null
  998.  
  999.   var _colorMode = "rgb" // vs hsb
  1000.   var _coordMode = "origin" // vs "center"
  1001.  
  1002.   var _drawLibrary = {}
  1003.   var _drawStyle = {background:null,
  1004.                     fill:null,
  1005.                     stroke:null,
  1006.                     width:0}
  1007.  
  1008.   var _fontLibrary = {}
  1009.   var _fontStyle = {font:"sans-serif",
  1010.                    size:12,
  1011.                    align:"left",
  1012.                    color:Colors.decode("black"),
  1013.                    alpha:1,
  1014.                    baseline:"ideographic"}
  1015.  
  1016.   var _lineBuffer = [] // calls to .lines sit here until flushed by .drawlines
  1017.  
  1018.   ///MACRO:primitives-start
  1019.   var primitives = Primitives(ctx, _drawStyle, _fontStyle)
  1020.   var _Oval = primitives._Oval
  1021.   var _Rect = primitives._Rect
  1022.   var _Color = primitives._Color
  1023.   var _Path = primitives._Path
  1024.   ///MACRO:primitives-end    
  1025.  
  1026.  
  1027.   // drawStyle({background:"color" or {r,g,b,a},
  1028.   //            fill:"color" or {r,g,b,a},
  1029.   //            stroke:"color" or {r,g,b,a},
  1030.   //            alpha:<number>,
  1031.   //            weight:<number>})
  1032.  
  1033.  
  1034.  
  1035.  
  1036.  
  1037.   var that = {
  1038.     init:function(){
  1039.       if (!ctx) return null
  1040.       return that
  1041.     },
  1042.  
  1043.     // canvas-wide settings
  1044.     size:function(width,height){
  1045.       if (!isNaN(width) && !isNaN(height)){
  1046.         dom.attr({width:width,height:height})
  1047.        
  1048.         // if (_drawStyle.fill!==null) that.fill(_drawStyle.fill)
  1049.         // if (_drawStyle.stroke!==null) that.stroke(_drawStyle.stroke)
  1050.         // that.textStyle(_fontStyle)
  1051.        
  1052.         // trace(_drawStyle,_fontStyle)
  1053.       }
  1054.       return {width:dom.attr('width'), height:dom.attr('height')}
  1055.     },
  1056.  
  1057.     clear:function(x,y,w,h){
  1058.       if (arguments.length<4){
  1059.         x=0; y=0
  1060.         w=dom.attr('width')
  1061.         h=dom.attr('height')
  1062.       }
  1063.      
  1064.       ctx.clearRect(x,y,w,h)
  1065.       if (_drawStyle.background!==null){
  1066.         ctx.save()
  1067.         ctx.fillStyle = Colors.encode(_drawStyle.background)
  1068.         ctx.fillRect(x,y,w,h)
  1069.         ctx.restore()
  1070.       }
  1071.     },
  1072.  
  1073.     background:function(a,b,c,d){
  1074.       if (a==null){
  1075.         _drawStyle.background = null
  1076.         return null
  1077.       }
  1078.      
  1079.       var fillColor = Colors.decode(a,b,c,d)
  1080.       if (fillColor){
  1081.         _drawStyle.background = fillColor
  1082.         that.clear()
  1083.       }
  1084.     },
  1085.  
  1086.  
  1087.     // drawing to screen
  1088.     noFill:function(){
  1089.       _drawStyle.fill = null
  1090.     },
  1091.     fill:function(a,b,c,d){
  1092.       if (arguments.length==0){
  1093.         return _drawStyle.fill
  1094.       }else if (arguments.length>0){
  1095.         var fillColor = Colors.decode(a,b,c,d)
  1096.         _drawStyle.fill = fillColor
  1097.         ctx.fillStyle = Colors.encode(fillColor)
  1098.       }
  1099.     },
  1100.    
  1101.     noStroke:function(){
  1102.       _drawStyle.stroke = null
  1103.       ctx.strokeStyle = null
  1104.     },
  1105.     stroke:function(a,b,c,d){
  1106.       if (arguments.length==0 && _drawStyle.stroke!==null){
  1107.         return _drawStyle.stroke
  1108.       }else if (arguments.length>0){
  1109.         var strokeColor = Colors.decode(a,b,c,d)
  1110.         _drawStyle.stroke = strokeColor
  1111.         ctx.strokeStyle = Colors.encode(strokeColor)
  1112.       }
  1113.     },
  1114.     strokeWidth:function(ptsize){
  1115.       if (ptsize===undefined) return ctx.lineWidth
  1116.       ctx.lineWidth = _drawStyle.width = ptsize
  1117.     },
  1118.    
  1119.    
  1120.    
  1121.     Color:function(clr){
  1122.       return new _Color(clr)
  1123.     },
  1124.  
  1125.  
  1126.     // Font:function(fontName, pointSize){
  1127.     //   return new _Font(fontName, pointSize)
  1128.     // },
  1129.     // font:function(fontName, pointSize){
  1130.     //   if (fontName!==undefined) _fontStyle.font = fontName
  1131.     //   if (pointSize!==undefined) _fontStyle.size = pointSize
  1132.     //   ctx.font = nano("{size}px {font}", _fontStyle)
  1133.     // },
  1134.  
  1135.  
  1136.     drawStyle:function(style){
  1137.       // without arguments, show the current state
  1138.       if (arguments.length==0) return objcopy(_drawStyle)
  1139.      
  1140.       // if this is a ("stylename", {style}) invocation, don't change the current
  1141.       // state but add it to the library
  1142.       if (arguments.length==2){
  1143.         var styleName = arguments[0]
  1144.         var styleDef = arguments[1]
  1145.         if (typeof styleName=='string' && typeof styleDef=='object'){
  1146.           var newStyle = {}
  1147.           if (styleDef.color!==undefined){
  1148.             var textColor = Colors.decode(styleDef.color)
  1149.             if (textColor) newStyle.color = textColor
  1150.           }
  1151.           $.each('background fill stroke width'.split(' '), function(i, param){
  1152.             if (styleDef[param]!==undefined) newStyle[param] = styleDef[param]
  1153.           })
  1154.           if (!$.isEmptyObject(newStyle)) _drawLibrary[styleName] = newStyle
  1155.         }
  1156.         return
  1157.       }
  1158.      
  1159.       // if a ("stylename") invocation, load up the selected style
  1160.       if (arguments.length==1 && _drawLibrary[arguments[0]]!==undefined){
  1161.         style = _drawLibrary[arguments[0]]
  1162.       }
  1163.            
  1164.       // for each of the properties specified, update the canvas state
  1165.       if (style.width!==undefined) _drawStyle.width = style.width
  1166.       ctx.lineWidth = _drawStyle.width
  1167.      
  1168.       $.each('background fill stroke',function(i, color){
  1169.         if (style[color]!==undefined){
  1170.           if (style[color]===null) _drawStyle[color] = null
  1171.           else{
  1172.             var useColor = Colors.decode(style[color])
  1173.             if (useColor) _drawStyle[color] = useColor
  1174.           }
  1175.         }
  1176.       })
  1177.       ctx.fillStyle = _drawStyle.fill
  1178.       ctx.strokeStyle = _drawStyle.stroke
  1179.     },
  1180.  
  1181.     textStyle:function(style){
  1182.       // without arguments, show the current state
  1183.       if (arguments.length==0) return objcopy(_fontStyle)
  1184.      
  1185.       // if this is a ("name", {style}) invocation, don't change the current
  1186.       // state but add it to the library
  1187.       if (arguments.length==2){
  1188.         var styleName = arguments[0]
  1189.         var styleDef = arguments[1]
  1190.         if (typeof styleName=='string' && typeof styleDef=='object'){
  1191.           var newStyle = {}
  1192.           if (styleDef.color!==undefined){
  1193.             var textColor = Colors.decode(styleDef.color)
  1194.             if (textColor) newStyle.color = textColor
  1195.           }
  1196.           $.each('font size align baseline alpha'.split(' '), function(i, param){
  1197.             if (styleDef[param]!==undefined) newStyle[param] = styleDef[param]
  1198.           })
  1199.           if (!$.isEmptyObject(newStyle)) _fontLibrary[styleName] = newStyle
  1200.         }
  1201.         return
  1202.       }
  1203.      
  1204.       if (arguments.length==1 && _fontLibrary[arguments[0]]!==undefined){
  1205.         style = _fontLibrary[arguments[0]]
  1206.       }
  1207.            
  1208.       if (style.font!==undefined) _fontStyle.font = style.font
  1209.       if (style.size!==undefined) _fontStyle.size = style.size
  1210.       ctx.font = nano("{size}px {font}", _fontStyle)
  1211.  
  1212.       if (style.align!==undefined){
  1213.          ctx.textAlign = _fontStyle.align = style.align
  1214.       }
  1215.       if (style.baseline!==undefined){
  1216.          ctx.textBaseline = _fontStyle.baseline = style.baseline
  1217.       }
  1218.  
  1219.       if (style.alpha!==undefined) _fontStyle.alpha = style.alpha
  1220.       if (style.color!==undefined){
  1221.         var textColor = Colors.decode(style.color)
  1222.         if (textColor) _fontStyle.color = textColor
  1223.       }
  1224.       if (_fontStyle.color){
  1225.         var textColor = Colors.blend(_fontStyle.color, _fontStyle.alpha)
  1226.         if (textColor) ctx.fillStyle = textColor
  1227.       }
  1228.       // trace(_fontStyle,opts)
  1229.     },
  1230.  
  1231.     text:function(textStr, x, y, opts){ // opts: x,y, color, font, align, baseline, width
  1232.       if (arguments.length>=3 && !isNaN(x)){
  1233.         opts = opts || {}
  1234.         opts.x = x
  1235.         opts.y = y
  1236.       }else if (arguments.length==2 && typeof(x)=='object'){
  1237.         opts = x
  1238.       }else{
  1239.         opts = opts || {}
  1240.       }
  1241.  
  1242.       var style = objmerge(_fontStyle, opts)
  1243.       ctx.save()
  1244.         if (style.align!==undefined) ctx.textAlign = style.align
  1245.         if (style.baseline!==undefined) ctx.textBaseline = style.baseline
  1246.         if (style.font!==undefined && !isNaN(style.size)){
  1247.           ctx.font = nano("{size}px {font}", style)
  1248.         }
  1249.  
  1250.         var alpha = (style.alpha!==undefined) ? style.alpha : _fontStyle.alpha
  1251.         var color = (style.color!==undefined) ? style.color : _fontStyle.color
  1252.         ctx.fillStyle = Colors.blend(color, alpha)
  1253.        
  1254.         // if (alpha>0) ctx.fillText(textStr, style.x, style.y);        
  1255.         if (alpha>0) ctx.fillText(textStr, Math.round(style.x), style.y);        
  1256.       ctx.restore()
  1257.     },
  1258.  
  1259.     textWidth:function(textStr, style){ // style: x,y, color, font, align, baseline, width
  1260.       style = objmerge(_fontStyle, style||{})
  1261.       ctx.save()
  1262.         ctx.font = nano("{size}px {font}", style)
  1263.         var width = ctx.measureText(textStr).width           
  1264.       ctx.restore()
  1265.       return width
  1266.     },
  1267.    
  1268.     // hasFont:function(fontName){
  1269.     //   var testTxt = 'H h H a H m H b H u H r H g H e H r H f H o H n H s H t H i H v H'
  1270.     //   ctx.save()
  1271.     //   ctx.font = '10px sans-serif'
  1272.     //   var defaultWidth = ctx.measureText(testTxt).width
  1273.     //
  1274.     //   ctx.font = '10px "'+fontName+'"'
  1275.     //   var putativeWidth = ctx.measureText(testTxt).width
  1276.     //   ctx.restore()
  1277.     //  
  1278.     //   // var defaultWidth = that.textWidth(testTxt, {font:"Times New Roman", size:120})
  1279.     //   // var putativeWidth = that.textWidth(testTxt, {font:fontName, size:120})
  1280.     //   trace(defaultWidth,putativeWidth,ctx.font)
  1281.     //   // return (putativeWidth!=defaultWidth || fontName=="Times New Roman")
  1282.     //   return putativeWidth!=defaultWidth
  1283.     // },
  1284.    
  1285.    
  1286.     // shape primitives.
  1287.     // classes will return an {x,y,w,h, fill(), stroke()} object without drawing
  1288.     // functions will draw the shape based on current stroke/fill state
  1289.     Rect:function(x,y,w,h,r,style){
  1290.       return new _Rect(x,y,w,h,r,style)
  1291.     },
  1292.     rect:function(x, y, w, h, r, style){
  1293.       _Rect.prototype._draw(x,y,w,h,r,style)
  1294.     },
  1295.    
  1296.     Oval:function(x, y, w, h, style) {
  1297.       return new _Oval(x,y,w,h, style)
  1298.     },
  1299.     oval:function(x, y, w, h, style) {
  1300.       style = style || {}
  1301.       _Oval.prototype._draw(x,y,w,h, style)
  1302.     },
  1303.    
  1304.     // draw a line immediately
  1305.     line:function(x1, y1, x2, y2, style){
  1306.       var p = new _Path(x1,y1,x2,y2)
  1307.       p.draw(style)
  1308.     },
  1309.    
  1310.     // queue up a line segment to be drawn in a batch by .drawLines
  1311.     lines:function(x1, y1, x2, y2){
  1312.       if (typeof y2=='number'){
  1313.         // ƒ( x1, y1, x2, y2)
  1314.         _lineBuffer.push( [ {x:x1,y:y1}, {x:x2,y:y2} ] )
  1315.       }else{
  1316.         // ƒ( {x:1, y:1}, {x:2, y:2} )
  1317.         _lineBuffer.push( [ x1,y1 ] )
  1318.       }
  1319.     },
  1320.    
  1321.     // flush the buffered .lines to screen
  1322.     drawLines:function(style){
  1323.       var p = new _Path(_lineBuffer)
  1324.       p.draw(style)
  1325.       _lineBuffer = []
  1326.     }
  1327.    
  1328.  
  1329.   }
  1330.  
  1331.   return that.init()    
  1332. }
  1333.  
  1334.  
  1335. // // helpers for figuring out where to draw arrows
  1336. // var intersect_line_line = function(p1, p2, p3, p4)
  1337. // {
  1338. //  var denom = ((p4.y - p3.y)*(p2.x - p1.x) - (p4.x - p3.x)*(p2.y - p1.y));
  1339. //
  1340. //  // lines are parallel
  1341. //  if (denom === 0) {
  1342. //    return false;
  1343. //  }
  1344. //
  1345. //  var ua = ((p4.x - p3.x)*(p1.y - p3.y) - (p4.y - p3.y)*(p1.x - p3.x)) / denom;
  1346. //  var ub = ((p2.x - p1.x)*(p1.y - p3.y) - (p2.y - p1.y)*(p1.x - p3.x)) / denom;
  1347. //
  1348. //  if (ua < 0 || ua > 1 || ub < 0 || ub > 1) {
  1349. //    return false;
  1350. //  }
  1351. //
  1352. //  return arbor.Point(p1.x + ua * (p2.x - p1.x), p1.y + ua * (p2.y - p1.y));
  1353. // }
  1354. //
  1355. // var intersect_line_box = function(p1, p2, p3, w, h)
  1356. // {
  1357. //  var tl = {x: p3.x, y: p3.y};
  1358. //  var tr = {x: p3.x + w, y: p3.y};
  1359. //  var bl = {x: p3.x, y: p3.y + h};
  1360. //  var br = {x: p3.x + w, y: p3.y + h};
  1361. //
  1362. //  var result;
  1363. //  if (result = intersect_line_line(p1, p2, tl, tr)) { return result; } // top
  1364. //  if (result = intersect_line_line(p1, p2, tr, br)) { return result; } // right
  1365. //  if (result = intersect_line_line(p1, p2, br, bl)) { return result; } // bottom
  1366. //  if (result = intersect_line_line(p1, p2, bl, tl)) { return result; } // left
  1367. //
  1368. //  return false;
  1369. // }
  1370.  
  1371. //
  1372. // easing.js
  1373. // the world-famous penner easing equations
  1374. //
  1375.  
  1376. /*
  1377.  *
  1378.  * TERMS OF USE - EASING EQUATIONS
  1379.  *
  1380.  * Open source under the BSD License.
  1381.  *
  1382.  * Copyright © 2001 Robert Penner
  1383.  * All rights reserved.
  1384.  *
  1385.  * Redistribution and use in source and binary forms, with or without modification,
  1386.  * are permitted provided that the following conditions are met:
  1387.  *
  1388.  * Redistributions of source code must retain the above copyright notice, this list of
  1389.  * conditions and the following disclaimer.
  1390.  * Redistributions in binary form must reproduce the above copyright notice, this list
  1391.  * of conditions and the following disclaimer in the documentation and/or other materials
  1392.  * provided with the distribution.
  1393.  *
  1394.  * Neither the name of the author nor the names of contributors may be used to endorse
  1395.  * or promote products derived from this software without specific prior written permission.
  1396.  *
  1397.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
  1398.  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  1399.  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  1400.  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  1401.  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
  1402.  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
  1403.  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  1404.  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
  1405.  * OF THE POSSIBILITY OF SUCH DAMAGE.
  1406.  *
  1407.  */
  1408.  
  1409.  var Easing = (function(){
  1410.   var that = {
  1411.     // t: current time, b: beginning value, c: change in value, d: duration  
  1412.     linear: function(t, b, c, d){
  1413.       return c*(t/d) + b
  1414.     },
  1415.     quadin: function (t, b, c, d) {
  1416.         return c*(t/=d)*t + b;
  1417.     },
  1418.     quadout: function (t, b, c, d) {
  1419.         return -c *(t/=d)*(t-2) + b;
  1420.     },
  1421.     quadinout: function (t, b, c, d) {
  1422.         if ((t/=d/2) < 1) return c/2*t*t + b;
  1423.         return -c/2 * ((--t)*(t-2) - 1) + b;
  1424.     },
  1425.     cubicin: function (t, b, c, d) {
  1426.         return c*(t/=d)*t*t + b;
  1427.     },
  1428.     cubicout: function (t, b, c, d) {
  1429.         return c*((t=t/d-1)*t*t + 1) + b;
  1430.     },
  1431.     cubicinout: function (t, b, c, d) {
  1432.         if ((t/=d/2) < 1) return c/2*t*t*t + b;
  1433.         return c/2*((t-=2)*t*t + 2) + b;
  1434.     },
  1435.     quartin: function (t, b, c, d) {
  1436.         return c*(t/=d)*t*t*t + b;
  1437.     },
  1438.     quartout: function (t, b, c, d) {
  1439.         return -c * ((t=t/d-1)*t*t*t - 1) + b;
  1440.     },
  1441.     quartinout: function (t, b, c, d) {
  1442.         if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
  1443.         return -c/2 * ((t-=2)*t*t*t - 2) + b;
  1444.     },
  1445.     quintin: function (t, b, c, d) {
  1446.         return c*(t/=d)*t*t*t*t + b;
  1447.     },
  1448.     quintout: function (t, b, c, d) {
  1449.         return c*((t=t/d-1)*t*t*t*t + 1) + b;
  1450.     },
  1451.     quintinout: function (t, b, c, d) {
  1452.         if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
  1453.         return c/2*((t-=2)*t*t*t*t + 2) + b;
  1454.     },
  1455.     sinein: function (t, b, c, d) {
  1456.         return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
  1457.     },
  1458.     sineout: function (t, b, c, d) {
  1459.         return c * Math.sin(t/d * (Math.PI/2)) + b;
  1460.     },
  1461.     sineinout: function (t, b, c, d) {
  1462.         return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
  1463.     },
  1464.     expoin: function (t, b, c, d) {
  1465.         return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
  1466.     },
  1467.     expoout: function (t, b, c, d) {
  1468.         return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
  1469.     },
  1470.     expoinout: function (t, b, c, d) {
  1471.         if (t==0) return b;
  1472.         if (t==d) return b+c;
  1473.         if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
  1474.         return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
  1475.     },
  1476.     circin: function (t, b, c, d) {
  1477.         return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
  1478.     },
  1479.     circout: function (t, b, c, d) {
  1480.         return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
  1481.     },
  1482.     circinout: function (t, b, c, d) {
  1483.         if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
  1484.         return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
  1485.     },
  1486.     elasticin: function (t, b, c, d) {
  1487.         var s=1.70158;var p=0;var a=c;
  1488.         if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
  1489.         if (a < Math.abs(c)) { a=c; var s=p/4; }
  1490.         else var s = p/(2*Math.PI) * Math.asin (c/a);
  1491.         return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
  1492.     },
  1493.     elasticout: function (t, b, c, d) {
  1494.         var s=1.70158;var p=0;var a=c;
  1495.         if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
  1496.         if (a < Math.abs(c)) { a=c; var s=p/4; }
  1497.         else var s = p/(2*Math.PI) * Math.asin (c/a);
  1498.         return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
  1499.     },
  1500.     elasticinout: function (t, b, c, d) {
  1501.         var s=1.70158;var p=0;var a=c;
  1502.         if (t==0) return b;  if ((t/=d/2)==2) return b+c;  if (!p) p=d*(.3*1.5);
  1503.         if (a < Math.abs(c)) { a=c; var s=p/4; }
  1504.         else var s = p/(2*Math.PI) * Math.asin (c/a);
  1505.         if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
  1506.         return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
  1507.     },
  1508.     backin: function (t, b, c, d, s) {
  1509.         if (s == undefined) s = 1.70158;
  1510.         return c*(t/=d)*t*((s+1)*t - s) + b;
  1511.     },
  1512.     backout: function (t, b, c, d, s) {
  1513.         if (s == undefined) s = 1.70158;
  1514.         return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
  1515.     },
  1516.     backinout: function (t, b, c, d, s) {
  1517.         if (s == undefined) s = 1.70158;
  1518.         if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
  1519.         return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
  1520.     },
  1521.     bouncein: function (t, b, c, d) {
  1522.         return c - that.bounceOut (d-t, 0, c, d) + b;
  1523.     },
  1524.     bounceout: function (t, b, c, d) {
  1525.         if ((t/=d) < (1/2.75)) {
  1526.             return c*(7.5625*t*t) + b;
  1527.         } else if (t < (2/2.75)) {
  1528.             return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
  1529.         } else if (t < (2.5/2.75)) {
  1530.             return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
  1531.         } else {
  1532.             return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
  1533.         }
  1534.     },
  1535.     bounceinout: function (t, b, c, d) {
  1536.         if (t < d/2) return that.bounceIn (t*2, 0, c, d) * .5 + b;
  1537.         return that.bounceOut(t*2-d, 0, c, d) * .5 + c*.5 + b;
  1538.     }
  1539.     }
  1540.     return that
  1541. })();
  1542.  
  1543. //
  1544. // tween.js
  1545. //
  1546. // interpolator of .data field members for nodes and edges
  1547. //
  1548.  
  1549.   var Tween = function(){
  1550.     var _tweens = {}
  1551.     var _done = true
  1552.    
  1553.     var that = {
  1554.       init:function(){
  1555.         return that
  1556.       },
  1557.      
  1558.       busy:function(){
  1559.         var busy = false
  1560.         for (var k in _tweens){ busy=true; break}
  1561.         return busy
  1562.       },
  1563.      
  1564.       to:function(node, dur, to){
  1565.         var now = new Date().valueOf()
  1566.         var seenFields = {}
  1567.  
  1568.         var tween = {from:{}, to:{}, colors:{}, node:node, t0:now, t1:now+dur*1000, dur:dur*1000}
  1569.         var easing_fn = "linear"
  1570.         for (var k in to){
  1571.           if (k=='easing'){
  1572.             // need to do better here. case insensitive and default to linear
  1573.             // also be okay with functions getting passed in
  1574.             var ease = to[k].toLowerCase()
  1575.             if (ease in Easing) easing_fn = ease
  1576.             continue
  1577.           }else if (k=='delay'){
  1578.             var delay = (to[k]||0) * 1000
  1579.             tween.t0 += delay
  1580.             tween.t1 += delay
  1581.             continue
  1582.           }
  1583.          
  1584.           if (Colors.validate(to[k])){
  1585.             // it's a hex color string value
  1586.             tween.colors[k] = [Colors.decode(node.data[k]), Colors.decode(to[k]), to[k]]
  1587.             seenFields[k] = true
  1588.           }else{
  1589.             tween.from[k] = (node.data[k]!=undefined) ? node.data[k] : to[k]
  1590.             tween.to[k] = to[k]
  1591.             seenFields[k] = true
  1592.           }
  1593.         }
  1594.         tween.ease = Easing[easing_fn]
  1595.  
  1596.         if (_tweens[node._id]===undefined) _tweens[node._id] = []
  1597.         _tweens[node._id].push(tween)
  1598.        
  1599.         // look through queued prunes for any redundancies
  1600.         if (_tweens.length>1){
  1601.           for (var i=_tweens.length-2; i>=0; i++){
  1602.             var tw = _tweens[i]
  1603.  
  1604.             for (var k in tw.to){
  1605.               if (k in seenFields) delete tw.to[k]
  1606.               else seenFields[k] = true
  1607.             }
  1608.  
  1609.             for (var k in tw.colors){
  1610.               if (k in seenFields) delete tw.colors[k]
  1611.               else seenFields[k] = true
  1612.             }
  1613.  
  1614.             if ($.isEmptyObject(tw.colors) && $.isEmptyObject(tw.to)){
  1615.               _tweens.splice(i,1)
  1616.             }
  1617.  
  1618.           }
  1619.         }
  1620.        
  1621.         _done = false
  1622.       },
  1623.  
  1624.       interpolate:function(pct, src, dst, ease){
  1625.         ease = (ease||"").toLowerCase()
  1626.         var easing_fn = Easing.linear
  1627.         if (ease in Easing) easing_fn = Easing[ease]
  1628.  
  1629.         var proportion = easing_fn( pct, 0,1, 1 )
  1630.         if (Colors.validate(src) && Colors.validate(dst)){
  1631.           return lerpRGB(proportion, src,dst)
  1632.         }else if (!isNaN(src)){
  1633.           return lerpNumber(proportion, src,dst)
  1634.         }else if (typeof src=='string'){
  1635.           return (proportion<.5) ? src : dst
  1636.         }
  1637.        
  1638.       },
  1639.  
  1640.       tick:function(){
  1641.         var empty = true
  1642.         for (var k in _tweens){ empty=false; break}
  1643.         if (empty) return
  1644.        
  1645.         var now = new Date().valueOf()
  1646.        
  1647.         $.each(_tweens, function(id, tweens){
  1648.           var unprunedTweens = false
  1649.          
  1650.           $.each(tweens, function(i, tween){
  1651.             var proportion = tween.ease( (now-tween.t0), 0,1, tween.dur )
  1652.             proportion = Math.min(1.0, proportion)
  1653.             var from = tween.from
  1654.             var to = tween.to
  1655.             var colors = tween.colors
  1656.             var nodeData = tween.node.data
  1657.  
  1658.             var lastTick = (proportion==1.0)
  1659.  
  1660.             for (var k in to){
  1661.               switch (typeof to[k]){
  1662.                 case "number":
  1663.                   nodeData[k] = lerpNumber(proportion, from[k], to[k])
  1664.                   if (k=='alpha') nodeData[k] = Math.max(0,Math.min(1, nodeData[k]))
  1665.                   break
  1666.                 case "string":
  1667.                   if (lastTick){
  1668.                     nodeData[k] = to[k]
  1669.                   }
  1670.                   break
  1671.               }
  1672.             }
  1673.            
  1674.             for (var k in colors){
  1675.               if (lastTick){
  1676.                 nodeData[k] = colors[k][2]
  1677.               }else{
  1678.                 var rgb = lerpRGB(proportion, colors[k][0], colors[k][1])
  1679.                 nodeData[k] = Colors.encode(rgb)
  1680.               }
  1681.             }
  1682.  
  1683.             if (lastTick){
  1684.                tween.completed = true
  1685.                unprunedTweens = true
  1686.             }
  1687.           })
  1688.          
  1689.           if (unprunedTweens){
  1690.             _tweens[id] = $.map(tweens, function(t){ if (!t.completed) return t})
  1691.             if (_tweens[id].length==0) delete _tweens[id]
  1692.           }
  1693.         })
  1694.        
  1695.         _done = $.isEmptyObject(_tweens)
  1696.         return _done
  1697.       }
  1698.     }
  1699.     return that.init()
  1700.   }
  1701.  
  1702.   var lerpNumber = function(proportion,from,to){
  1703.     return from + proportion*(to-from)
  1704.   }
  1705.  
  1706.   var lerpRGB = function(proportion,from,to){
  1707.     proportion = Math.max(Math.min(proportion,1),0)
  1708.     var mixture = {}
  1709.    
  1710.     $.each('rgba'.split(""), function(i, c){
  1711.       mixture[c] = Math.round( from[c] + proportion*(to[c]-from[c]) )
  1712.     })
  1713.     return mixture
  1714.   }
  1715.  
  1716.  
  1717. // })()
  1718.  
  1719. //
  1720. // atoms.js
  1721. //
  1722. // particle system- or physics-related datatypes
  1723. //
  1724.  
  1725. var Node = function(data){
  1726.     this._id = _nextNodeId++; // simple ints to allow the Kernel & ParticleSystem to chat
  1727.     this.data = data || {};  // the user-serviceable parts
  1728.     this._mass = (data.mass!==undefined) ? data.mass : 1
  1729.     this._fixed = (data.fixed===true) ? true : false
  1730.     this._p = new Point((typeof(data.x)=='number') ? data.x : null,
  1731.                      (typeof(data.y)=='number') ? data.y : null)
  1732.   delete this.data.x
  1733.   delete this.data.y
  1734.   delete this.data.mass
  1735.   delete this.data.fixed
  1736. };
  1737. var _nextNodeId = 1
  1738.  
  1739. var Edge = function(source, target, data){
  1740.     this._id = _nextEdgeId--;
  1741.     this.source = source;
  1742.     this.target = target;
  1743.     this.length = (data.length!==undefined) ? data.length : 1
  1744.     this.data = (data!==undefined) ? data : {};
  1745.     delete this.data.length
  1746. };
  1747. var _nextEdgeId = -1
  1748.  
  1749. var Particle = function(position, mass){
  1750.   this.p = position;
  1751.   this.m = mass;
  1752.     this.v = new Point(0, 0); // velocity
  1753.     this.f = new Point(0, 0); // force
  1754. };
  1755. Particle.prototype.applyForce = function(force){
  1756.     this.f = this.f.add(force.divide(this.m));
  1757. };
  1758.  
  1759. var Spring = function(point1, point2, length, k)
  1760. {
  1761.     this.point1 = point1; // a particle
  1762.     this.point2 = point2; // another particle
  1763.     this.length = length; // spring length at rest
  1764.     this.k = k;           // stiffness
  1765. };
  1766. Spring.prototype.distanceToParticle = function(point)
  1767. {
  1768.   // see http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment/865080#865080
  1769.   var n = that.point2.p.subtract(that.point1.p).normalize().normal();
  1770.   var ac = point.p.subtract(that.point1.p);
  1771.   return Math.abs(ac.x * n.x + ac.y * n.y);
  1772. };
  1773.  
  1774. var Point = function(x, y){
  1775.   if (x && x.hasOwnProperty('y')){
  1776.     y = x.y; x=x.x;
  1777.   }
  1778.   this.x = x;
  1779.   this.y = y;  
  1780. }
  1781.  
  1782. Point.random = function(radius){
  1783.   radius = (radius!==undefined) ? radius : 5
  1784.     return new Point(2*radius * (Math.random() - 0.5), 2*radius* (Math.random() - 0.5));
  1785. }
  1786.  
  1787. Point.prototype = {
  1788.   exploded:function(){
  1789.     return ( isNaN(this.x) || isNaN(this.y) )
  1790.   },
  1791.   add:function(v2){
  1792.     return new Point(this.x + v2.x, this.y + v2.y);
  1793.   },
  1794.   subtract:function(v2){
  1795.     return new Point(this.x - v2.x, this.y - v2.y);
  1796.   },
  1797.   multiply:function(n){
  1798.     return new Point(this.x * n, this.y * n);
  1799.   },
  1800.   divide:function(n){
  1801.     return new Point(this.x / n, this.y / n);
  1802.   },
  1803.   magnitude:function(){
  1804.     return Math.sqrt(this.x*this.x + this.y*this.y);
  1805.   },
  1806.   normal:function(){
  1807.     return new Point(-this.y, this.x);
  1808.   },
  1809.   normalize:function(){
  1810.     return this.divide(this.magnitude());
  1811.   }
  1812. }
  1813.  
  1814. //
  1815. // physics.js
  1816. //
  1817. // the particle system itself. either run inline or in a worker (see worker.js)
  1818. //
  1819.  
  1820.   var Physics = function(dt, stiffness, repulsion, friction, updateFn){
  1821.     var bhTree = BarnesHutTree() // for computing particle repulsion
  1822.     var active = {particles:{}, springs:{}}
  1823.     var free = {particles:{}}
  1824.     var particles = []
  1825.     var springs = []
  1826.     var _epoch=0
  1827.     var _energy = {sum:0, max:0, mean:0}
  1828.     var _bounds = {topleft:new Point(-1,-1), bottomright:new Point(1,1)}
  1829.  
  1830.     var SPEED_LIMIT = 1000 // the max particle velocity per tick
  1831.    
  1832.     var that = {
  1833.       stiffness:(stiffness!==undefined) ? stiffness : 1000,
  1834.       repulsion:(repulsion!==undefined)? repulsion : 600,
  1835.       friction:(friction!==undefined)? friction : .3,
  1836.       gravity:false,
  1837.       dt:(dt!==undefined)? dt : 0.02,
  1838.       theta:.4, // the criterion value for the barnes-hut s/d calculation
  1839.      
  1840.       init:function(){
  1841.         return that
  1842.       },
  1843.  
  1844.       modifyPhysics:function(param){
  1845.         $.each(['stiffness','repulsion','friction','gravity','dt','precision'], function(i, p){
  1846.           if (param[p]!==undefined){
  1847.             if (p=='precision'){
  1848.               that.theta = 1-param[p]
  1849.               return
  1850.             }
  1851.             that[p] = param[p]
  1852.              
  1853.             if (p=='stiffness'){
  1854.               var stiff=param[p]
  1855.               $.each(active.springs, function(id, spring){
  1856.                 spring.k = stiff
  1857.               })            
  1858.             }
  1859.           }
  1860.         })
  1861.       },
  1862.  
  1863.       addNode:function(c){
  1864.         var id = c.id
  1865.         var mass = c.m
  1866.  
  1867.         var w = _bounds.bottomright.x - _bounds.topleft.x
  1868.         var h = _bounds.bottomright.y - _bounds.topleft.y
  1869.         var randomish_pt = new Point((c.x != null) ? c.x: _bounds.topleft.x + w*Math.random(),
  1870.                                      (c.y != null) ? c.y: _bounds.topleft.y + h*Math.random())
  1871.  
  1872.        
  1873.         active.particles[id] = new Particle(randomish_pt, mass);
  1874.         active.particles[id].connections = 0
  1875.         active.particles[id].fixed = (c.f===1)
  1876.         free.particles[id] = active.particles[id]
  1877.         particles.push(active.particles[id])        
  1878.       },
  1879.  
  1880.       dropNode:function(c){
  1881.         var id = c.id
  1882.         var dropping = active.particles[id]
  1883.         var idx = $.inArray(dropping, particles)
  1884.         if (idx>-1) particles.splice(idx,1)
  1885.         delete active.particles[id]
  1886.         delete free.particles[id]
  1887.       },
  1888.  
  1889.       modifyNode:function(id, mods){
  1890.         if (id in active.particles){
  1891.           var pt = active.particles[id]
  1892.           if ('x' in mods) pt.p.x = mods.x
  1893.           if ('y' in mods) pt.p.y = mods.y
  1894.           if ('m' in mods) pt.m = mods.m
  1895.           if ('f' in mods) pt.fixed = (mods.f===1)
  1896.           if ('_m' in mods){
  1897.             if (pt._m===undefined) pt._m = pt.m
  1898.             pt.m = mods._m            
  1899.           }
  1900.         }
  1901.       },
  1902.  
  1903.       addSpring:function(c){
  1904.         var id = c.id
  1905.         var length = c.l
  1906.         var from = active.particles[c.fm]
  1907.         var to = active.particles[c.to]
  1908.        
  1909.         if (from!==undefined && to!==undefined){
  1910.           active.springs[id] = new Spring(from, to, length, that.stiffness)
  1911.           springs.push(active.springs[id])
  1912.          
  1913.           from.connections++
  1914.           to.connections++
  1915.          
  1916.           delete free.particles[c.fm]
  1917.           delete free.particles[c.to]
  1918.         }
  1919.       },
  1920.  
  1921.       dropSpring:function(c){
  1922.         var id = c.id
  1923.         var dropping = active.springs[id]
  1924.        
  1925.         dropping.point1.connections--
  1926.         dropping.point2.connections--
  1927.        
  1928.         var idx = $.inArray(dropping, springs)
  1929.         if (idx>-1){
  1930.            springs.splice(idx,1)
  1931.         }
  1932.         delete active.springs[id]
  1933.       },
  1934.  
  1935.       _update:function(changes){
  1936.         // batch changes phoned in (automatically) by a ParticleSystem
  1937.         _epoch++
  1938.        
  1939.         $.each(changes, function(i, c){
  1940.           if (c.t in that) that[c.t](c)
  1941.         })
  1942.         return _epoch
  1943.       },
  1944.  
  1945.  
  1946.       tick:function(){
  1947.         that.tendParticles()
  1948.         that.eulerIntegrator(that.dt)
  1949.         that.tock()
  1950.       },
  1951.  
  1952.       tock:function(){
  1953.         var coords = []
  1954.         $.each(active.particles, function(id, pt){
  1955.           coords.push(id)
  1956.           coords.push(pt.p.x)
  1957.           coords.push(pt.p.y)
  1958.         })
  1959.  
  1960.         if (updateFn) updateFn({geometry:coords, epoch:_epoch, energy:_energy, bounds:_bounds})
  1961.       },
  1962.  
  1963.       tendParticles:function(){
  1964.         $.each(active.particles, function(id, pt){
  1965.           // decay down any of the temporary mass increases that were passed along
  1966.           // by using an {_m:} instead of an {m:} (which is to say via a Node having
  1967.           // its .tempMass attr set)
  1968.           if (pt._m!==undefined){
  1969.             if (Math.abs(pt.m-pt._m)<1){
  1970.               pt.m = pt._m
  1971.               delete pt._m
  1972.             }else{
  1973.               pt.m *= .98
  1974.             }
  1975.           }
  1976.  
  1977.           // zero out the velocity from one tick to the next
  1978.           pt.v.x = pt.v.y = 0          
  1979.         })
  1980.  
  1981.       },
  1982.      
  1983.      
  1984.       // Physics stuff
  1985.       eulerIntegrator:function(dt){
  1986.         if (that.repulsion>0){
  1987.           if (that.theta>0) that.applyBarnesHutRepulsion()
  1988.           else that.applyBruteForceRepulsion()
  1989.         }
  1990.         if (that.stiffness>0) that.applySprings()
  1991.         that.applyCenterDrift()
  1992.         if (that.gravity) that.applyCenterGravity()
  1993.         that.updateVelocity(dt)
  1994.         that.updatePosition(dt)
  1995.       },
  1996.  
  1997.       applyBruteForceRepulsion:function(){
  1998.         $.each(active.particles, function(id1, point1){
  1999.           $.each(active.particles, function(id2, point2){
  2000.             if (point1 !== point2){
  2001.               var d = point1.p.subtract(point2.p);
  2002.               var distance = Math.max(1.0, d.magnitude());
  2003.               var direction = ((d.magnitude()>0) ? d : Point.random(1)).normalize()
  2004.  
  2005.               // apply force to each end point
  2006.               // (consult the cached `real' mass value if the mass is being poked to allow
  2007.               // for repositioning. the poked mass will still be used in .applyforce() so
  2008.               // all should be well)
  2009.               point1.applyForce(direction.multiply(that.repulsion*(point2._m||point2.m)*.5)
  2010.                                          .divide(distance * distance * 0.5) );
  2011.               point2.applyForce(direction.multiply(that.repulsion*(point1._m||point1.m)*.5)
  2012.                                          .divide(distance * distance * -0.5) );
  2013.  
  2014.             }
  2015.           })          
  2016.         })
  2017.       },
  2018.      
  2019.       applyBarnesHutRepulsion:function(){
  2020.         if (!_bounds.topleft || !_bounds.bottomright) return
  2021.         var bottomright = new Point(_bounds.bottomright)
  2022.         var topleft = new Point(_bounds.topleft)
  2023.  
  2024.         // build a barnes-hut tree...
  2025.         bhTree.init(topleft, bottomright, that.theta)        
  2026.         $.each(active.particles, function(id, particle){
  2027.           bhTree.insert(particle)
  2028.         })
  2029.        
  2030.         // ...and use it to approximate the repulsion forces
  2031.         $.each(active.particles, function(id, particle){
  2032.           bhTree.applyForces(particle, that.repulsion)
  2033.         })
  2034.       },
  2035.      
  2036.       applySprings:function(){
  2037.         $.each(active.springs, function(id, spring){
  2038.           var d = spring.point2.p.subtract(spring.point1.p); // the direction of the spring
  2039.           var displacement = spring.length - d.magnitude()//Math.max(.1, d.magnitude());
  2040.           var direction = ( (d.magnitude()>0) ? d : Point.random(1) ).normalize()
  2041.  
  2042.           // BUG:
  2043.           // since things oscillate wildly for hub nodes, should probably normalize spring
  2044.           // forces by the number of incoming edges for each node. naive normalization
  2045.           // doesn't work very well though. what's the `right' way to do it?
  2046.  
  2047.           // apply force to each end point
  2048.           spring.point1.applyForce(direction.multiply(spring.k * displacement * -0.5))
  2049.           spring.point2.applyForce(direction.multiply(spring.k * displacement * 0.5))
  2050.         });
  2051.       },
  2052.  
  2053.  
  2054.       applyCenterDrift:function(){
  2055.         // find the centroid of all the particles in the system and shift everything
  2056.         // so the cloud is centered over the origin
  2057.         var numParticles = 0
  2058.         var centroid = new Point(0,0)
  2059.         $.each(active.particles, function(id, point) {
  2060.           centroid.add(point.p)
  2061.           numParticles++
  2062.         });
  2063.  
  2064.         if (numParticles==0) return
  2065.        
  2066.         var correction = centroid.divide(-numParticles)
  2067.         $.each(active.particles, function(id, point) {
  2068.           point.applyForce(correction)
  2069.         })
  2070.        
  2071.       },
  2072.       applyCenterGravity:function(){
  2073.         // attract each node to the origin
  2074.         $.each(active.particles, function(id, point) {
  2075.           var direction = point.p.multiply(-1.0);
  2076.           point.applyForce(direction.multiply(that.repulsion / 100.0));
  2077.         });
  2078.       },
  2079.      
  2080.       updateVelocity:function(timestep){
  2081.         // translate forces to a new velocity for this particle
  2082.         $.each(active.particles, function(id, point) {
  2083.           if (point.fixed){
  2084.              point.v = new Point(0,0)
  2085.              point.f = new Point(0,0)
  2086.              return
  2087.           }
  2088.  
  2089.           var was = point.v.magnitude()
  2090.           point.v = point.v.add(point.f.multiply(timestep)).multiply(1-that.friction);
  2091.           point.f.x = point.f.y = 0
  2092.  
  2093.           var speed = point.v.magnitude()          
  2094.           if (speed>SPEED_LIMIT) point.v = point.v.divide(speed*speed)
  2095.         });
  2096.       },
  2097.  
  2098.       updatePosition:function(timestep){
  2099.         // translate velocity to a position delta
  2100.         var sum=0, max=0, n = 0;
  2101.         var bottomright = null
  2102.         var topleft = null
  2103.  
  2104.         $.each(active.particles, function(i, point) {
  2105.           // move the node to its new position
  2106.           point.p = point.p.add(point.v.multiply(timestep));
  2107.          
  2108.           // keep stats to report in systemEnergy
  2109.           var speed = point.v.magnitude();
  2110.           var e = speed*speed
  2111.           sum += e
  2112.           max = Math.max(e,max)
  2113.           n++
  2114.  
  2115.           if (!bottomright){
  2116.             bottomright = new Point(point.p.x, point.p.y)
  2117.             topleft = new Point(point.p.x, point.p.y)
  2118.             return
  2119.           }
  2120.        
  2121.           var pt = point.p
  2122.           if (pt.x===null || pt.y===null) return
  2123.           if (pt.x > bottomright.x) bottomright.x = pt.x;
  2124.           if (pt.y > bottomright.y) bottomright.y = pt.y;          
  2125.           if   (pt.x < topleft.x)   topleft.x = pt.x;
  2126.           if   (pt.y < topleft.y)   topleft.y = pt.y;
  2127.         });
  2128.        
  2129.         _energy = {sum:sum, max:max, mean:sum/n, n:n}
  2130.         _bounds = {topleft:topleft||new Point(-1,-1), bottomright:bottomright||new Point(1,1)}
  2131.       },
  2132.  
  2133.       systemEnergy:function(timestep){
  2134.         // system stats
  2135.         return _energy
  2136.       }
  2137.  
  2138.      
  2139.     }
  2140.     return that.init()
  2141.   }
  2142.  
  2143.   var _nearParticle = function(center_pt, r){
  2144.       var r = r || .0
  2145.       var x = center_pt.x
  2146.       var y = center_pt.y
  2147.       var d = r*2
  2148.       return new Point(x-r+Math.random()*d, y-r+Math.random()*d)
  2149.   }
  2150.  
  2151. //
  2152. // system.js
  2153. //
  2154. // the main controller object for creating/modifying graphs
  2155. //
  2156.  
  2157.   var ParticleSystem = function(repulsion, stiffness, friction, centerGravity, targetFps, dt, precision){
  2158.   // also callable with ({stiffness:, repulsion:, friction:, timestep:, fps:, dt:, gravity:})
  2159.    
  2160.     var _changes=[]
  2161.     var _notification=null
  2162.     var _epoch = 0
  2163.  
  2164.     var _screenSize = null
  2165.     var _screenStep = .04
  2166.     var _screenPadding = [20,20,20,20]
  2167.     var _bounds = null
  2168.     var _boundsTarget = null
  2169.  
  2170.     if (typeof stiffness=='object'){
  2171.       var _p = stiffness
  2172.       friction = _p.friction
  2173.       repulsion = _p.repulsion
  2174.       targetFps = _p.fps
  2175.       dt = _p.dt
  2176.       stiffness = _p.stiffness
  2177.       centerGravity = _p.gravity
  2178.       precision = _p.precision
  2179.     }
  2180.  
  2181.     friction = isNaN(friction) ? .5 : friction
  2182.     repulsion = isNaN(repulsion) ? 1000 : repulsion
  2183.     targetFps = isNaN(targetFps) ? 55 : targetFps
  2184.     stiffness = isNaN(stiffness) ? 600 : stiffness
  2185.     dt = isNaN(dt) ? 0.02 : dt
  2186.     precision = isNaN(precision) ? .6 : precision
  2187.     centerGravity = (centerGravity===true)
  2188.     var _systemTimeout = (targetFps!==undefined) ? 1000/targetFps : 1000/50
  2189.     var _parameters = {repulsion:repulsion, stiffness:stiffness, friction:friction, dt:dt, gravity:centerGravity, precision:precision, timeout:_systemTimeout}
  2190.     var _energy
  2191.  
  2192.     var state = {
  2193.       renderer:null, // this is set by the library user
  2194.       tween:null, // gets filled in by the Kernel
  2195.       nodes:{}, // lookup based on node _id's from the worker
  2196.       edges:{}, // likewise
  2197.       adjacency:{}, // {name1:{name2:{}, name3:{}}}
  2198.       names:{}, // lookup table based on 'name' field in data objects
  2199.       kernel: null
  2200.     }
  2201.  
  2202.     var that={
  2203.       parameters:function(newParams){
  2204.         if (newParams!==undefined){
  2205.           if (!isNaN(newParams.precision)){
  2206.             newParams.precision = Math.max(0, Math.min(1, newParams.precision))
  2207.           }
  2208.           $.each(_parameters, function(p, v){
  2209.             if (newParams[p]!==undefined) _parameters[p] = newParams[p]
  2210.           })
  2211.           state.kernel.physicsModified(newParams)
  2212.         }
  2213.         return _parameters
  2214.       },
  2215.  
  2216.       fps:function(newFPS){
  2217.         if (newFPS===undefined) return state.kernel.fps()
  2218.         else that.parameters({timeout:1000/(newFPS||50)})
  2219.       },
  2220.  
  2221.  
  2222.       start:function(){
  2223.         state.kernel.start()
  2224.       },
  2225.       stop:function(){
  2226.         state.kernel.stop()
  2227.       },
  2228.  
  2229.       addNode:function(name, data){
  2230.         data = data || {}
  2231.         var priorNode = state.names[name]
  2232.         if (priorNode){
  2233.           priorNode.data = data
  2234.           return priorNode
  2235.         }else if (name!=undefined){
  2236.           // the data object has a few magic fields that are actually used
  2237.           // by the simulation:
  2238.           //   'mass' overrides the default of 1
  2239.           //   'fixed' overrides the default of false
  2240.           //   'x' & 'y' will set a starting position rather than
  2241.           //             defaulting to random placement
  2242.           var x = (data.x!=undefined) ? data.x : null
  2243.           var y = (data.y!=undefined) ? data.y : null
  2244.           var fixed = (data.fixed) ? 1 : 0
  2245.  
  2246.           var node = new Node(data)
  2247.           node.name = name
  2248.           state.names[name] = node
  2249.           state.nodes[node._id] = node;
  2250.  
  2251.           _changes.push({t:"addNode", id:node._id, m:node.mass, x:x, y:y, f:fixed})
  2252.           that._notify();
  2253.           return node;
  2254.  
  2255.         }
  2256.       },
  2257.  
  2258.       // remove a node and its associated edges from the graph
  2259.       pruneNode:function(nodeOrName) {
  2260.         var node = that.getNode(nodeOrName)
  2261.        
  2262.         if (typeof(state.nodes[node._id]) !== 'undefined'){
  2263.           delete state.nodes[node._id]
  2264.           delete state.names[node.name]
  2265.         }
  2266.  
  2267.  
  2268.         $.each(state.edges, function(id, e){
  2269.           if (e.source._id === node._id || e.target._id === node._id){
  2270.             that.pruneEdge(e);
  2271.           }
  2272.         })
  2273.  
  2274.         _changes.push({t:"dropNode", id:node._id})
  2275.         that._notify();
  2276.       },
  2277.  
  2278.       getNode:function(nodeOrName){
  2279.         if (nodeOrName._id!==undefined){
  2280.           return nodeOrName
  2281.         }else if (typeof nodeOrName=='string' || typeof nodeOrName=='number'){
  2282.           return state.names[nodeOrName]
  2283.         }
  2284.         // otherwise let it return undefined
  2285.       },
  2286.  
  2287.       eachNode:function(callback){
  2288.         // callback should accept two arguments: Node, Point
  2289.         $.each(state.nodes, function(id, n){
  2290.           if (n._p.x==null || n._p.y==null) return
  2291.           var pt = (_screenSize!==null) ? that.toScreen(n._p) : n._p
  2292.           callback.call(that, n, pt);
  2293.         })
  2294.       },
  2295.  
  2296.       addEdge:function(source, target, data){
  2297.         source = that.getNode(source) || that.addNode(source)
  2298.         target = that.getNode(target) || that.addNode(target)
  2299.         data = data || {}
  2300.         var edge = new Edge(source, target, data);
  2301.  
  2302.         var src = source._id
  2303.         var dst = target._id
  2304.         state.adjacency[src] = state.adjacency[src] || {}
  2305.         state.adjacency[src][dst] = state.adjacency[src][dst] || []
  2306.  
  2307.         var exists = (state.adjacency[src][dst].length > 0)
  2308.         if (exists){
  2309.           // probably shouldn't allow multiple edges in same direction
  2310.           // between same nodes? for now just overwriting the data...
  2311.           $.extend(state.adjacency[src][dst].data, edge.data)
  2312.           return
  2313.         }else{
  2314.           state.edges[edge._id] = edge
  2315.           state.adjacency[src][dst].push(edge)
  2316.           var len = (edge.length!==undefined) ? edge.length : 1
  2317.           _changes.push({t:"addSpring", id:edge._id, fm:src, to:dst, l:len})
  2318.           that._notify()
  2319.         }
  2320.  
  2321.         return edge;
  2322.  
  2323.       },
  2324.  
  2325.       // remove an edge and its associated lookup entries
  2326.       pruneEdge:function(edge) {
  2327.  
  2328.         _changes.push({t:"dropSpring", id:edge._id})
  2329.         delete state.edges[edge._id]
  2330.        
  2331.         for (var x in state.adjacency){
  2332.           for (var y in state.adjacency[x]){
  2333.             var edges = state.adjacency[x][y];
  2334.  
  2335.             for (var j=edges.length - 1; j>=0; j--)  {
  2336.               if (state.adjacency[x][y][j]._id === edge._id){
  2337.                 state.adjacency[x][y].splice(j, 1);
  2338.               }
  2339.             }
  2340.           }
  2341.         }
  2342.  
  2343.         that._notify();
  2344.       },
  2345.  
  2346.       // find the edges from node1 to node2
  2347.       getEdges:function(node1, node2) {
  2348.         node1 = that.getNode(node1)
  2349.         node2 = that.getNode(node2)
  2350.         if (!node1 || !node2) return []
  2351.        
  2352.         if (typeof(state.adjacency[node1._id]) !== 'undefined'
  2353.           && typeof(state.adjacency[node1._id][node2._id]) !== 'undefined'){
  2354.           return state.adjacency[node1._id][node2._id];
  2355.         }
  2356.  
  2357.         return [];
  2358.       },
  2359.  
  2360.       getEdgesFrom:function(node) {
  2361.         node = that.getNode(node)
  2362.         if (!node) return []
  2363.        
  2364.         if (typeof(state.adjacency[node._id]) !== 'undefined'){
  2365.           var nodeEdges = []
  2366.           $.each(state.adjacency[node._id], function(id, subEdges){
  2367.             nodeEdges = nodeEdges.concat(subEdges)
  2368.           })
  2369.           return nodeEdges
  2370.         }
  2371.  
  2372.         return [];
  2373.       },
  2374.  
  2375.       getEdgesTo:function(node) {
  2376.         node = that.getNode(node)
  2377.         if (!node) return []
  2378.  
  2379.         var nodeEdges = []
  2380.         $.each(state.edges, function(edgeId, edge){
  2381.           if (edge.target == node) nodeEdges.push(edge)
  2382.         })
  2383.        
  2384.         return nodeEdges;
  2385.       },
  2386.  
  2387.       eachEdge:function(callback){
  2388.         // callback should accept two arguments: Edge, Point
  2389.         $.each(state.edges, function(id, e){
  2390.           var p1 = state.nodes[e.source._id]._p
  2391.           var p2 = state.nodes[e.target._id]._p
  2392.  
  2393.  
  2394.           if (p1.x==null || p2.x==null) return
  2395.          
  2396.           p1 = (_screenSize!==null) ? that.toScreen(p1) : p1
  2397.           p2 = (_screenSize!==null) ? that.toScreen(p2) : p2
  2398.          
  2399.           if (p1 && p2) callback.call(that, e, p1, p2);
  2400.         })
  2401.       },
  2402.  
  2403.  
  2404.       prune:function(callback){
  2405.         // callback should be of the form ƒ(node, {from:[],to:[]})
  2406.  
  2407.         var changes = {dropped:{nodes:[], edges:[]}}
  2408.         if (callback===undefined){
  2409.           $.each(state.nodes, function(id, node){
  2410.             changes.dropped.nodes.push(node)
  2411.             that.pruneNode(node)
  2412.           })
  2413.         }else{
  2414.           that.eachNode(function(node){
  2415.             var drop = callback.call(that, node, {from:that.getEdgesFrom(node), to:that.getEdgesTo(node)})
  2416.             if (drop){
  2417.               changes.dropped.nodes.push(node)
  2418.               that.pruneNode(node)
  2419.             }
  2420.           })
  2421.         }
  2422.         // trace('prune', changes.dropped)
  2423.         return changes
  2424.       },
  2425.      
  2426.       graft:function(branch){
  2427.         // branch is of the form: { nodes:{name1:{d}, name2:{d},...},
  2428.         //                          edges:{fromNm:{toNm1:{d}, toNm2:{d}}, ...} }
  2429.  
  2430.         var changes = {added:{nodes:[], edges:[]}}
  2431.         if (branch.nodes) $.each(branch.nodes, function(name, nodeData){
  2432.           var oldNode = that.getNode(name)
  2433.           // should probably merge any x/y/m data as well...
  2434.           // if (oldNode) $.extend(oldNode.data, nodeData)
  2435.          
  2436.           if (oldNode) oldNode.data = nodeData
  2437.           else changes.added.nodes.push( that.addNode(name, nodeData) )
  2438.          
  2439.           state.kernel.start()
  2440.         })
  2441.        
  2442.         if (branch.edges) $.each(branch.edges, function(src, dsts){
  2443.           var srcNode = that.getNode(src)
  2444.           if (!srcNode) changes.added.nodes.push( that.addNode(src, {}) )
  2445.  
  2446.           $.each(dsts, function(dst, edgeData){
  2447.  
  2448.             // should probably merge any x/y/m data as well...
  2449.             // if (srcNode) $.extend(srcNode.data, nodeData)
  2450.  
  2451.  
  2452.             // i wonder if it should spawn any non-existant nodes that are part
  2453.             // of one of these edge requests...
  2454.             var dstNode = that.getNode(dst)
  2455.             if (!dstNode) changes.added.nodes.push( that.addNode(dst, {}) )
  2456.  
  2457.             var oldEdges = that.getEdges(src, dst)
  2458.             if (oldEdges.length>0){
  2459.               // trace("update",src,dst)
  2460.               oldEdges[0].data = edgeData
  2461.             }else{
  2462.             // trace("new ->",src,dst)
  2463.               changes.added.edges.push( that.addEdge(src, dst, edgeData) )
  2464.             }
  2465.           })
  2466.         })
  2467.  
  2468.         // trace('graft', changes.added)
  2469.         return changes
  2470.       },
  2471.  
  2472.       merge:function(branch){
  2473.         var changes = {added:{nodes:[], edges:[]}, dropped:{nodes:[], edges:[]}}
  2474.  
  2475.         $.each(state.edges, function(id, edge){
  2476.           // if ((branch.edges[edge.source.name]===undefined || branch.edges[edge.source.name][edge.target.name]===undefined) &&
  2477.           //     (branch.edges[edge.target.name]===undefined || branch.edges[edge.target.name][edge.source.name]===undefined)){
  2478.           if ((branch.edges[edge.source.name]===undefined || branch.edges[edge.source.name][edge.target.name]===undefined)){
  2479.                 that.pruneEdge(edge)
  2480.                 changes.dropped.edges.push(edge)
  2481.               }
  2482.         })
  2483.        
  2484.         var prune_changes = that.prune(function(node, edges){
  2485.           if (branch.nodes[node.name] === undefined){
  2486.             changes.dropped.nodes.push(node)
  2487.             return true
  2488.           }
  2489.         })
  2490.         var graft_changes = that.graft(branch)        
  2491.         changes.added.nodes = changes.added.nodes.concat(graft_changes.added.nodes)
  2492.         changes.added.edges = changes.added.edges.concat(graft_changes.added.edges)
  2493.         changes.dropped.nodes = changes.dropped.nodes.concat(prune_changes.dropped.nodes)
  2494.         changes.dropped.edges = changes.dropped.edges.concat(prune_changes.dropped.edges)
  2495.        
  2496.         // trace('changes', changes)
  2497.         return changes
  2498.       },
  2499.  
  2500.      
  2501.       tweenNode:function(nodeOrName, dur, to){
  2502.         var node = that.getNode(nodeOrName)
  2503.         if (node) state.tween.to(node, dur, to)
  2504.       },
  2505.  
  2506.       tweenEdge:function(a,b,c,d){
  2507.         if (d===undefined){
  2508.           // called with (edge, dur, to)
  2509.           that._tweenEdge(a,b,c)
  2510.         }else{
  2511.           // called with (node1, node2, dur, to)
  2512.           var edges = that.getEdges(a,b)
  2513.           $.each(edges, function(i, edge){
  2514.             that._tweenEdge(edge, c, d)    
  2515.           })
  2516.         }
  2517.       },
  2518.  
  2519.       _tweenEdge:function(edge, dur, to){
  2520.         if (edge && edge._id!==undefined) state.tween.to(edge, dur, to)
  2521.       },
  2522.  
  2523.       _updateGeometry:function(e){
  2524.         if (e != undefined){          
  2525.           var stale = (e.epoch<_epoch)
  2526.  
  2527.           _energy = e.energy
  2528.           var pts = e.geometry // an array of the form [id1,x1,y1, id2,x2,y2, ...]
  2529.           if (pts!==undefined){
  2530.             for (var i=0, j=pts.length/3; i<j; i++){
  2531.               var id = pts[3*i]
  2532.                            
  2533.               // canary silencer...
  2534.               if (stale && state.nodes[id]==undefined) continue;
  2535.              
  2536.               state.nodes[id]._p.x = pts[3*i + 1]
  2537.               state.nodes[id]._p.y = pts[3*i + 2]
  2538.             }
  2539.           }          
  2540.         }
  2541.       },
  2542.      
  2543.       // convert to/from screen coordinates
  2544.       screen:function(opts){
  2545.         if (opts == undefined) return {size:(_screenSize)? objcopy(_screenSize) : undefined,
  2546.                                        padding:_screenPadding.concat(),
  2547.                                        step:_screenStep}
  2548.         if (opts.size!==undefined) that.screenSize(opts.size.width, opts.size.height)
  2549.         if (!isNaN(opts.step)) that.screenStep(opts.step)
  2550.         if (opts.padding!==undefined) that.screenPadding(opts.padding)
  2551.       },
  2552.      
  2553.       screenSize:function(canvasWidth, canvasHeight){
  2554.         _screenSize = {width:canvasWidth,height:canvasHeight}
  2555.         that._updateBounds()
  2556.       },
  2557.  
  2558.       screenPadding:function(t,r,b,l){
  2559.         if ($.isArray(t)) trbl = t
  2560.         else trbl = [t,r,b,l]
  2561.  
  2562.         var top = trbl[0]
  2563.         var right = trbl[1]
  2564.         var bot = trbl[2]
  2565.         if (right===undefined) trbl = [top,top,top,top]
  2566.         else if (bot==undefined) trbl = [top,right,top,right]
  2567.        
  2568.         _screenPadding = trbl
  2569.       },
  2570.  
  2571.       screenStep:function(stepsize){
  2572.         _screenStep = stepsize
  2573.       },
  2574.  
  2575.       toScreen:function(p) {
  2576.         if (!_bounds || !_screenSize) return
  2577.         // trace(p.x, p.y)
  2578.  
  2579.         var _padding = _screenPadding || [0,0,0,0]
  2580.         var size = _bounds.bottomright.subtract(_bounds.topleft)
  2581.         var sx = _padding[3] + p.subtract(_bounds.topleft).divide(size.x).x * (_screenSize.width - (_padding[1] + _padding[3]))
  2582.         var sy = _padding[0] + p.subtract(_bounds.topleft).divide(size.y).y * (_screenSize.height - (_padding[0] + _padding[2]))
  2583.  
  2584.         // return arbor.Point(Math.floor(sx), Math.floor(sy))
  2585.         return arbor.Point(sx, sy)
  2586.       },
  2587.      
  2588.       fromScreen:function(s) {
  2589.         if (!_bounds || !_screenSize) return
  2590.  
  2591.         var _padding = _screenPadding || [0,0,0,0]
  2592.         var size = _bounds.bottomright.subtract(_bounds.topleft)
  2593.         var px = (s.x-_padding[3]) / (_screenSize.width-(_padding[1]+_padding[3]))  * size.x + _bounds.topleft.x
  2594.         var py = (s.y-_padding[0]) / (_screenSize.height-(_padding[0]+_padding[2])) * size.y + _bounds.topleft.y
  2595.  
  2596.         return arbor.Point(px, py);
  2597.       },
  2598.  
  2599.       _updateBounds:function(newBounds){
  2600.         // step the renderer's current bounding box closer to the true box containing all
  2601.         // the nodes. if _screenStep is set to 1 there will be no lag. if _screenStep is
  2602.         // set to 0 the bounding box will remain stationary after being initially set
  2603.         if (_screenSize===null) return
  2604.        
  2605.         if (newBounds) _boundsTarget = newBounds
  2606.         else _boundsTarget = that.bounds()
  2607.        
  2608.         // _boundsTarget = newBounds || that.bounds()
  2609.         // _boundsTarget.topleft = new Point(_boundsTarget.topleft.x,_boundsTarget.topleft.y)
  2610.         // _boundsTarget.bottomright = new Point(_boundsTarget.bottomright.x,_boundsTarget.bottomright.y)
  2611.  
  2612.         var bottomright = new Point(_boundsTarget.bottomright.x, _boundsTarget.bottomright.y)
  2613.         var topleft = new Point(_boundsTarget.topleft.x, _boundsTarget.topleft.y)
  2614.         var dims = bottomright.subtract(topleft)
  2615.         var center = topleft.add(dims.divide(2))
  2616.  
  2617.  
  2618.         var MINSIZE = 4                                   // perfect-fit scaling
  2619.         // MINSIZE = Math.max(Math.max(MINSIZE,dims.y), dims.x) // proportional scaling
  2620.  
  2621.         var size = new Point(Math.max(dims.x,MINSIZE), Math.max(dims.y,MINSIZE))
  2622.         _boundsTarget.topleft = center.subtract(size.divide(2))
  2623.         _boundsTarget.bottomright = center.add(size.divide(2))
  2624.  
  2625.         if (!_bounds){
  2626.           if ($.isEmptyObject(state.nodes)) return false
  2627.           _bounds = _boundsTarget
  2628.           return true
  2629.         }
  2630.        
  2631.         // var stepSize = (Math.max(dims.x,dims.y)<MINSIZE) ? .2 : _screenStep
  2632.         var stepSize = _screenStep
  2633.         _newBounds = {
  2634.           bottomright: _bounds.bottomright.add( _boundsTarget.bottomright.subtract(_bounds.bottomright).multiply(stepSize) ),
  2635.           topleft: _bounds.topleft.add( _boundsTarget.topleft.subtract(_bounds.topleft).multiply(stepSize) )
  2636.         }
  2637.        
  2638.         // return true if we're still approaching the target, false if we're ‘close enough’
  2639.         var diff = new Point(_bounds.topleft.subtract(_newBounds.topleft).magnitude(), _bounds.bottomright.subtract(_newBounds.bottomright).magnitude())        
  2640.         if (diff.x*_screenSize.width>1 || diff.y*_screenSize.height>1){
  2641.           _bounds = _newBounds
  2642.           return true
  2643.         }else{
  2644.          return false        
  2645.         }
  2646.       },
  2647.  
  2648.       energy:function(){
  2649.         return _energy
  2650.       },
  2651.  
  2652.       bounds:function(){
  2653.         //  TL   -1
  2654.         //     -1   1
  2655.         //        1   BR
  2656.         var bottomright = null
  2657.         var topleft = null
  2658.  
  2659.         // find the true x/y range of the nodes
  2660.         $.each(state.nodes, function(id, node){
  2661.           if (!bottomright){
  2662.             bottomright = new Point(node._p)
  2663.             topleft = new Point(node._p)
  2664.             return
  2665.           }
  2666.        
  2667.           var point = node._p
  2668.           if (point.x===null || point.y===null) return
  2669.           if (point.x > bottomright.x) bottomright.x = point.x;
  2670.           if (point.y > bottomright.y) bottomright.y = point.y;          
  2671.           if   (point.x < topleft.x)   topleft.x = point.x;
  2672.           if   (point.y < topleft.y)   topleft.y = point.y;
  2673.         })
  2674.  
  2675.  
  2676.         // return the true range then let to/fromScreen handle the padding
  2677.         if (bottomright && topleft){
  2678.           return {bottomright: bottomright, topleft: topleft}
  2679.         }else{
  2680.           return {topleft: new Point(-1,-1), bottomright: new Point(1,1)};
  2681.         }
  2682.       },
  2683.  
  2684.       // Find the nearest node to a particular position
  2685.       nearest:function(pos){
  2686.         if (_screenSize!==null) pos = that.fromScreen(pos)
  2687.         // if screen size has been specified, presume pos is in screen pixel
  2688.         // units and convert it back to the particle system coordinates
  2689.        
  2690.         var min = {node: null, point: null, distance: null};
  2691.         var t = that;
  2692.        
  2693.         $.each(state.nodes, function(id, node){
  2694.           var pt = node._p
  2695.           if (pt.x===null || pt.y===null) return
  2696.           var distance = pt.subtract(pos).magnitude();
  2697.           if (min.distance === null || distance < min.distance){
  2698.             min = {node: node, point: pt, distance: distance};
  2699.             if (_screenSize!==null) min.screenPoint = that.toScreen(pt)
  2700.           }
  2701.         })
  2702.        
  2703.         if (min.node){
  2704.           if (_screenSize!==null) min.distance = that.toScreen(min.node.p).subtract(that.toScreen(pos)).magnitude()
  2705.            return min
  2706.         }else{
  2707.            return null
  2708.         }
  2709.       },
  2710.  
  2711.       _notify:function() {
  2712.         // pass on graph changes to the physics object in the worker thread
  2713.         // (using a short timeout to batch changes)
  2714.         if (_notification===null) _epoch++
  2715.         else clearTimeout(_notification)
  2716.        
  2717.         _notification = setTimeout(that._synchronize,20)
  2718.         // that._synchronize()
  2719.       },
  2720.       _synchronize:function(){
  2721.         if (_changes.length>0){
  2722.           state.kernel.graphChanged(_changes)
  2723.           _changes = []
  2724.           _notification = null
  2725.         }
  2726.       }
  2727.     }    
  2728.    
  2729.     state.kernel = Kernel(that)
  2730.     state.tween = state.kernel.tween || null
  2731.  
  2732.  
  2733.  
  2734.     // some magic attrs to make the Node objects phone-home their physics-relevant changes
  2735.  
  2736.     var defineProperty = Object.defineProperty ||
  2737.       function (obj, name, desc) {
  2738.         if (desc.get)
  2739.           obj.__defineGetter__(name, desc.get)
  2740.         if (desc.set)
  2741.           obj.__defineSetter__(name, desc.set)
  2742.       }
  2743.  
  2744.     var RoboPoint = function (n) {
  2745.       this._n = n;
  2746.     }
  2747.     RoboPoint.prototype = new Point();
  2748.     defineProperty(RoboPoint.prototype, "x", {
  2749.       get: function(){ return this._n._p.x; },
  2750.       set: function(newX){ state.kernel.particleModified(this._n._id, {x:newX}) }
  2751.     })
  2752.     defineProperty(RoboPoint.prototype, "y", {
  2753.       get: function(){ return this._n._p.y; },
  2754.       set: function(newY){ state.kernel.particleModified(this._n._id, {y:newY}) }
  2755.     })
  2756.  
  2757.     defineProperty(Node.prototype, "p", {
  2758.       get: function() {
  2759.         return new RoboPoint(this)
  2760.       },
  2761.       set: function(newP) {
  2762.         this._p.x = newP.x
  2763.         this._p.y = newP.y
  2764.         state.kernel.particleModified(this._id, {x:newP.x, y:newP.y})
  2765.       }
  2766.     })
  2767.  
  2768.     defineProperty(Node.prototype, "mass", {
  2769.       get: function() { return this._mass; },
  2770.       set: function(newM) {
  2771.         this._mass = newM
  2772.         state.kernel.particleModified(this._id, {m:newM})
  2773.       }
  2774.     })
  2775.  
  2776.     defineProperty(Node.prototype, "tempMass", {
  2777.       set: function(newM) {
  2778.         state.kernel.particleModified(this._id, {_m:newM})
  2779.       }
  2780.     })
  2781.  
  2782.     defineProperty(Node.prototype, "fixed", {
  2783.       get: function() { return this._fixed; },
  2784.       set:function(isFixed) {
  2785.         this._fixed = isFixed
  2786.         state.kernel.particleModified(this._id, {f:isFixed?1:0})
  2787.       }
  2788.     })
  2789.    
  2790.     return that
  2791.   };
  2792.  
  2793.  
  2794. //
  2795. // dev.js
  2796. //
  2797. // module wrapper for running from the un-minified src files
  2798. //
  2799. //
  2800. // to run from src, make sure your html includes look like:
  2801. //   <script src="js/src/etc.js"></script>
  2802. //   <script src="js/src/kernel.js"></script>
  2803. //   <script src="js/src/graphics/colors.js"></script>
  2804. //   <script src="js/src/graphics/primitives.js"></script>
  2805. //   <script src="js/src/graphics/graphics.js"></script>
  2806. //   <script src="js/src/tween/easing.js"></script>
  2807. //   <script src="js/src/tween/tween.js"></script>
  2808. //   <script src="js/src/physics/atoms.js"></script>
  2809. //   <script src="js/src/physics/physics.js"></script>
  2810. //   <script src="js/src/physics/system.js"></script>
  2811. //   <script src="js/src/dev.js"></script>
  2812.  
  2813.  
  2814. (function(){
  2815.  
  2816.   arbor = (typeof(arbor)!=='undefined') ? arbor : {}
  2817.   $.extend(arbor, {
  2818.     // object constructors (don't use ‘new’, just call them)
  2819.     ParticleSystem:ParticleSystem,
  2820.     Tween:Tween,
  2821.     Point:function(x, y){ return new Point(x, y) },
  2822.     Graphics:function(canvas){ return Graphics(canvas) },
  2823.  
  2824.     // immutable objects with useful methods
  2825.     colors:{
  2826.       CSS:Colors.CSS,           // {colorname:#fef2e2,...}
  2827.       validate:Colors.validate, // ƒ(str) -> t/f
  2828.       decode:Colors.decode,     // ƒ(hexString_or_cssColor) -> {r,g,b,a}
  2829.       encode:Colors.encode,     // ƒ({r,g,b,a}) -> hexOrRgbaString
  2830.       blend:Colors.blend        // ƒ(color, opacity) -> rgbaString
  2831.     },
  2832.     etc:{      
  2833.       trace:trace,              // ƒ(msg) -> safe console logging
  2834.       dirname:dirname,          // ƒ(path) -> leading part of path
  2835.       basename:basename,        // ƒ(path) -> trailing part of path
  2836.       ordinalize:ordinalize,    // ƒ(num) -> abbrev integers (and add commas)
  2837.       objcopy:objcopy,          // ƒ(old) -> clone an object
  2838.       objcmp:objcmp,            // ƒ(a, b, strict_ordering) -> t/f comparison
  2839.       objkeys:objkeys,          // ƒ(obj) -> array of all keys in obj
  2840.       objmerge:objmerge,        // ƒ(dst, src) -> like $.extend but non-destructive
  2841.       uniq:uniq,                // ƒ(arr) -> array of unique items in arr
  2842.       arbor_path:arbor_path    // ƒ() -> guess the directory of the lib code
  2843.     }
  2844.   })
  2845.  
  2846.  
  2847. })();
  2848.  
  2849. //
  2850. // site.js
  2851. //
  2852. // the arbor.js website
  2853. //
  2854. //(function($){
  2855.   // var trace = function(msg){
  2856.   //   if (typeof(window)=='undefined' || !window.console) return
  2857.   //   var len = arguments.length, args = [];
  2858.   //   for (var i=0; i<len; i++) args.push("arguments["+i+"]")
  2859.   //   eval("console.log("+args.join(",")+")")
  2860.   // }  
  2861.  
  2862.   var Renderer = function(elt){
  2863.     var dom = $(elt)
  2864.     var canvas = dom.get(0)
  2865.     var ctx = canvas.getContext("2d");
  2866.     var gfx = arbor.Graphics(canvas)
  2867.     var sys = null
  2868.  
  2869.     var _vignette = null
  2870.     var selected = null,
  2871.         nearest = null,
  2872.         _mouseP = null;
  2873.  
  2874.    
  2875.     var that = {
  2876.       init:function(pSystem){
  2877.         sys = pSystem
  2878.         sys.screen({size:{width:dom.width(), height:dom.height()},
  2879.                     padding:[36,60,36,60]})
  2880.  
  2881.         $(window).resize(that.resize)
  2882.         that.resize()
  2883.         that._initMouseHandling()
  2884.       },
  2885.       resize:function(){
  2886.         canvas.width = $(window).width()
  2887.         canvas.height = .75* $(window).height()
  2888.         sys.screen({size:{width:canvas.width, height:canvas.height}})
  2889.         _vignette = null
  2890.         that.redraw()
  2891.       },
  2892.       redraw:function(){
  2893.         gfx.clear()
  2894.         sys.eachEdge(function(edge, p1, p2){
  2895.           if (edge.source.data.alpha * edge.target.data.alpha == 0) return
  2896.           gfx.line(p1, p2, {stroke:"#b2b19d", width:2, alpha:edge.target.data.alpha})
  2897.         })
  2898.         sys.eachNode(function(node, pt){
  2899.           var w = Math.max(20, 20+gfx.textWidth(node.name) )
  2900.           if (node.data.alpha===0) return
  2901.           if (node.data.shape=='dot'){
  2902.             gfx.oval(pt.x-w/2, pt.y-w/2, w, w, {fill:node.data.color, alpha:node.data.alpha})
  2903.             gfx.text(node.name, pt.x, pt.y+7, {color:"white", align:"center", font:"Arial", size:12})
  2904.             gfx.text(node.name, pt.x, pt.y+7, {color:"white", align:"center", font:"Arial", size:12})
  2905.           }else{
  2906.             gfx.rect(pt.x-w/2, pt.y-8, w, 20, 4, {fill:node.data.color, alpha:node.data.alpha})
  2907.             gfx.text(node.name, pt.x, pt.y+9, {color:"white", align:"center", font:"Arial", size:12})
  2908.             gfx.text(node.name, pt.x, pt.y+9, {color:"white", align:"center", font:"Arial", size:12})
  2909.           }
  2910.         })
  2911.         //that._drawVignette()
  2912.       },
  2913.      
  2914.       /*_drawVignette:function(){
  2915.         var w = canvas.width
  2916.         var h = canvas.height
  2917.         var r = 20
  2918.  
  2919.         if (!_vignette){
  2920.           var top = ctx.createLinearGradient(0,0,0,r)
  2921.           top.addColorStop(0, "#e0e0e0")
  2922.           top.addColorStop(.7, "rgba(255,255,255,0)")
  2923.  
  2924.           var bot = ctx.createLinearGradient(0,h-r,0,h)
  2925.           bot.addColorStop(0, "rgba(255,255,255,0)")
  2926.           bot.addColorStop(1, "white")
  2927.  
  2928.           _vignette = {top:top, bot:bot}
  2929.         }
  2930.        
  2931.         // top
  2932.         ctx.fillStyle = _vignette.top
  2933.         ctx.fillRect(0,0, w,r)
  2934.  
  2935.         // bot
  2936.         ctx.fillStyle = _vignette.bot
  2937.         ctx.fillRect(0,h-r, w,r)
  2938.       },*/
  2939.  
  2940.       switchMode:function(e){
  2941.         if (e.mode=='hidden'){
  2942.           dom.stop(true).fadeTo(e.dt,0, function(){
  2943.             if (sys) sys.stop()
  2944.             $(this).hide()
  2945.           })
  2946.         }else if (e.mode=='visible'){
  2947.           dom.stop(true).css('opacity',0).show().fadeTo(e.dt,1,function(){
  2948.             that.resize()
  2949.           })
  2950.           if (sys) sys.start()
  2951.         }
  2952.       },
  2953.      
  2954.       switchSection:function(newSection){
  2955.         var parent = sys.getEdgesFrom(newSection)[0].source
  2956.         var children = $.map(sys.getEdgesFrom(newSection), function(edge){
  2957.           return edge.target
  2958.         })
  2959.        
  2960.         sys.eachNode(function(node){
  2961.           if (node.data.shape=='dot') return // skip all but leafnodes
  2962.  
  2963.           var nowVisible = ($.inArray(node, children)>=0)
  2964.           var newAlpha = (nowVisible) ? 1 : 0
  2965.           var dt = (nowVisible) ? .5 : .5
  2966.           sys.tweenNode(node, dt, {alpha:newAlpha})
  2967.  
  2968.           if (newAlpha==1){
  2969.             node.p.x = parent.p.x + .05*Math.random() - .025
  2970.             node.p.y = parent.p.y + .05*Math.random() - .025
  2971.             node.tempMass = .001
  2972.           }
  2973.         })
  2974.       },
  2975.      
  2976.      
  2977.       _initMouseHandling:function(){
  2978.         // no-nonsense drag and drop (thanks springy.js)
  2979.         selected = null;
  2980.         nearest = null;
  2981.         var dragged = null;
  2982.         var oldmass = 1
  2983.  
  2984.         var _section = null
  2985.  
  2986.         var handler = {
  2987.           moved:function(e){
  2988.             var pos = $(canvas).offset();
  2989.             _mouseP = arbor.Point(e.pageX-pos.left, e.pageY-pos.top)
  2990.             nearest = sys.nearest(_mouseP);
  2991.  
  2992.             if (!nearest.node) return false
  2993.  
  2994.             if (nearest.node.data.shape!='dot'){
  2995.               selected = (nearest.distance < 50) ? nearest : null
  2996.               if (selected){
  2997.                  dom.addClass('linkable')
  2998.                  //window.status = selected.node.data.link.replace(/^\//,"http://"+window.location.host+"/").replace(/^#/,'')
  2999.               }
  3000.               else{
  3001.                  dom.removeClass('linkable')
  3002.                  window.status = ''
  3003.               }
  3004.             }else if (!isNaN(parseInt(nearest.node.name))){
  3005.               if (nearest.node.name!=_section){
  3006.                 _section = nearest.node.name
  3007.                 that.switchSection(_section)
  3008.               }
  3009.               dom.removeClass('linkable')
  3010.               window.status = ''
  3011.             }
  3012.            
  3013.             return false
  3014.           },
  3015.           clicked:function(e){
  3016.             var pos = $(canvas).offset();
  3017.             _mouseP = arbor.Point(e.pageX-pos.left, e.pageY-pos.top)
  3018.             nearest = dragged = sys.nearest(_mouseP);
  3019.            
  3020.             if (nearest && selected && nearest.node===selected.node){
  3021.               var link = selected.node.data.link
  3022.               if (link.match(/^#/)){
  3023.                  $(that).trigger({type:"navigate", path:link.substr(1)})
  3024.               }else{
  3025.                  window.location = link
  3026.               }
  3027.               return false
  3028.             }
  3029.            
  3030.            
  3031.             if (dragged && dragged.node !== null) dragged.node.fixed = true
  3032.  
  3033.             //$(canvas).unbind('mousemove', handler.moved);
  3034.             $(canvas).bind('mousemove', handler.dragged)
  3035.             $(window).bind('mouseup', handler.dropped)
  3036.  
  3037.             return false
  3038.           },
  3039.           dragged:function(e){
  3040.             var old_nearest = nearest && nearest.node._id
  3041.             var pos = $(canvas).offset();
  3042.        
  3043. //alert(e.pageY);
  3044.             var s = arbor.Point(e.pageX-pos.left, e.pageY-pos.top)
  3045.  
  3046.             if (!nearest) return
  3047.             if (dragged !== null && dragged.node !== null){
  3048.               var p = sys.fromScreen(s)
  3049.               dragged.node.p = p
  3050.             }
  3051.  
  3052.             return false
  3053.           },
  3054.  
  3055.           dropped:function(e){
  3056.             if (dragged===null || dragged.node===undefined) return
  3057.             if (dragged.node !== null) dragged.node.fixed = false
  3058.             dragged.node.tempMass = 1000
  3059.             dragged = null;
  3060.             // selected = null
  3061.             $(canvas).unbind('mousemove', handler.dragged)
  3062.             $(window).unbind('mouseup', handler.dropped)
  3063.             $(canvas).bind('mousemove', handler.moved);
  3064.             _mouseP = null
  3065.             return false
  3066.           }
  3067.  
  3068.  
  3069.         }
  3070.  
  3071.         $(canvas).mousedown(handler.clicked);
  3072.         $(canvas).mousemove(handler.moved);
  3073.  
  3074.       }
  3075.     }
  3076.    
  3077.     return that
  3078.   }
  3079. //})(this.jQuery)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement