diff --git a/springy.js b/springy.js index 768c53f..359da7b 100644 --- a/springy.js +++ b/springy.js @@ -25,8 +25,10 @@ * OTHER DEALINGS IN THE SOFTWARE. */ +/*jshint globalstrict:true, browser:true */ + // Enable strict mode for EC5 compatible browsers -"use strict"; +'use strict'; var Graph = function() { this.nodeSet = {}; @@ -44,21 +46,20 @@ var Node = function(id, data) { this.data = (data !== undefined) ? data : {}; // Data fields used by layout algorithm in this file: -// this.data.mass +// this.data.mass // Data used by default renderer in springyui.js -// this.data.label +// this.data.label }; var Edge = function(id, source, target, data) { this.id = id; - /** @type {Node} */ this.source = source; this.target = target; this.data = (data !== undefined) ? data : {}; // Edge data field used by layout alorithm -// this.data.length -// this.data.type +// this.data.length +// this.data.type }; Graph.prototype.addNode = function(node) { @@ -73,13 +74,13 @@ Graph.prototype.addNode = function(node) { }; Graph.prototype.addNodes = function() { - // accepts variable number of arguments, where each argument - // is a string that becomes both node identifier and label - for (var i = 0; i < arguments.length; i++) { - var name = arguments[i]; - var node = new Node(name, {label:name}); - this.addNode(node); - } + // accepts variable number of arguments, where each argument + // is a string that becomes both node identifier and label + for (var i = 0; i < arguments.length; i++) { + var name = arguments[i]; + var node = new Node(name, {label: name}); + this.addNode(node); + } }; Graph.prototype.addEdge = function(edge) { @@ -113,22 +114,22 @@ Graph.prototype.addEdge = function(edge) { }; Graph.prototype.addEdges = function() { - // accepts variable number of arguments, where each argument - // is a triple [nodeid1, nodeid2, attributes] - for (var i = 0; i < arguments.length; i++) { - var e = arguments[i]; - var node1 = this.nodeSet[e[0]]; - if (node1 == undefined) { - throw new TypeError("invalid node name: " + e[0]); - } - var node2 = this.nodeSet[e[1]]; - if (node2 == undefined) { - throw new TypeError("invalid node name: " + e[1]); - } - var attr = e[2]; + // accepts variable number of arguments, where each argument + // is a triple [nodeid1, nodeid2, attributes] + for (var i = 0; i < arguments.length; i++) { + var e = arguments[i]; + var node1 = this.nodeSet[e[0]]; + if (node1 === undefined) { + throw new TypeError('invalid node name: ' + e[0]); + } + var node2 = this.nodeSet[e[1]]; + if (node2 === undefined) { + throw new TypeError('invalid node name: ' + e[1]); + } + var attr = e[2]; - this.newEdge(node1, node2, attr); - } + this.newEdge(node1, node2, attr); + } }; Graph.prototype.newNode = function(data) { @@ -145,8 +146,7 @@ Graph.prototype.newEdge = function(source, target, data) { // find the edges from node1 to node2 Graph.prototype.getEdges = function(node1, node2) { - if (node1.id in this.adjacency - && node2.id in this.adjacency[node1.id]) { + if (node1.id in this.adjacency && node2.id in this.adjacency[node1.id]) { return this.adjacency[node1.id][node2.id]; } @@ -193,7 +193,7 @@ Graph.prototype.removeEdge = function(edge) { for (var y in this.adjacency[x]) { var edges = this.adjacency[x][y]; - for (var j=edges.length - 1; j>=0; j--) { + for (var j = edges.length - 1; j >= 0; j--) { if (this.adjacency[x][y][j].id === edge.id) { this.adjacency[x][y].splice(j, 1); } @@ -225,11 +225,11 @@ Graph.prototype.merge = function(data) { var from = nodes[e.from]; var to = nodes[e.to]; - var id = (e.directed) - ? (id = e.type + "-" + from.id + "-" + to.id) - : (from.id < to.id) // normalise id for non-directed edges - ? e.type + "-" + from.id + "-" + to.id - : e.type + "-" + to.id + "-" + from.id; + var id = (e.directed) ? + (id = e.type + '-' + from.id + '-' + to.id) : + (from.id < to.id) ? // normalise id for non-directed edges + (e.type + '-' + from.id + '-' + to.id) : + (e.type + '-' + to.id + '-' + from.id); var edge = this.addEdge(new Edge(id, from, to, e.data)); edge.data.type = e.type; @@ -260,7 +260,7 @@ Graph.prototype.addGraphListener = function(obj) { }; Graph.prototype.notify = function() { - this.eventListeners.forEach(function(obj){ + this.eventListeners.forEach(function(obj) { obj.graphChanged(); }); }; @@ -280,7 +280,8 @@ Layout.ForceDirected = function(graph, stiffness, repulsion, damping) { Layout.ForceDirected.prototype.point = function(node) { if (!(node.id in this.nodePoints)) { var mass = (node.data.mass !== undefined) ? node.data.mass : 1.0; - this.nodePoints[node.id] = new Layout.ForceDirected.Point(Vector.random(), mass); + this.nodePoints[node.id] = + new Layout.ForceDirected.Point(Vector.random(), mass); } return this.nodePoints[node.id]; @@ -300,18 +301,22 @@ Layout.ForceDirected.prototype.spring = function(edge) { }, this); if (existingSpring !== false) { - return new Layout.ForceDirected.Spring(existingSpring.point1, existingSpring.point2, 0.0, 0.0); + return new Layout.ForceDirected.Spring( + existingSpring.point1, + existingSpring.point2, 0.0, 0.0); } var to = this.graph.getEdges(edge.target, edge.source); - from.forEach(function(e){ + from.forEach(function(e) { if (existingSpring === false && e.id in this.edgeSprings) { existingSpring = this.edgeSprings[e.id]; } }, this); if (existingSpring !== false) { - return new Layout.ForceDirected.Spring(existingSpring.point2, existingSpring.point1, 0.0, 0.0); + return new Layout.ForceDirected.Spring( + existingSpring.point2, + existingSpring.point1, 0.0, 0.0); } this.edgeSprings[edge.id] = new Layout.ForceDirected.Spring( @@ -325,7 +330,7 @@ Layout.ForceDirected.prototype.spring = function(edge) { // callback should accept two arguments: Node, Point Layout.ForceDirected.prototype.eachNode = function(callback) { var t = this; - this.graph.nodes.forEach(function(n){ + this.graph.nodes.forEach(function(n) { callback.call(t, n, t.point(n)); }); }; @@ -333,7 +338,7 @@ Layout.ForceDirected.prototype.eachNode = function(callback) { // callback should accept two arguments: Edge, Spring Layout.ForceDirected.prototype.eachEdge = function(callback) { var t = this; - this.graph.edges.forEach(function(e){ + this.graph.edges.forEach(function(e) { callback.call(t, e, t.spring(e)); }); }; @@ -341,7 +346,7 @@ Layout.ForceDirected.prototype.eachEdge = function(callback) { // callback should accept one argument: Spring Layout.ForceDirected.prototype.eachSpring = function(callback) { var t = this; - this.graph.edges.forEach(function(e){ + this.graph.edges.forEach(function(e) { callback.call(t, t.spring(e)); }); }; @@ -354,26 +359,39 @@ Layout.ForceDirected.prototype.applyCoulombsLaw = function() { if (point1 !== point2) { var d = point1.p.subtract(point2.p); - var distance = d.magnitude() + 0.1; // avoid massive forces at small distances (and divide by zero) + // avoid massive forces at small distances (and divide by zero) + var distance = d.magnitude() + 0.1; var direction = d.normalise(); // apply force to each end point - point1.applyForce(direction.multiply(this.repulsion).divide(distance * distance * 0.5)); - point2.applyForce(direction.multiply(this.repulsion).divide(distance * distance * -0.5)); + point1.applyForce( + direction + .multiply(this.repulsion) + .divide(distance * distance * 0.5)); + + point2.applyForce( + direction + .multiply(this.repulsion) + .divide(distance * distance * -0.5)); } }); }); }; Layout.ForceDirected.prototype.applyHookesLaw = function() { - this.eachSpring(function(spring){ - var d = spring.point2.p.subtract(spring.point1.p); // the direction of the spring - var displacement = spring.length - d.magnitude(); - var direction = d.normalise(); + this.eachSpring(function(spring) { + var dir = spring.point2.p.subtract(spring.point1.p); + var displacement = spring.length - dir.magnitude(); + var direction = dir.normalise(); // apply force to each end point - spring.point1.applyForce(direction.multiply(spring.k * displacement * -0.5)); - spring.point2.applyForce(direction.multiply(spring.k * displacement * 0.5)); + spring.point1.applyForce( + direction + .multiply(spring.k * displacement * -0.5)); + + spring.point2.applyForce( + direction + .multiply(spring.k * displacement * 0.5)); }); }; @@ -387,17 +405,13 @@ Layout.ForceDirected.prototype.attractToCentre = function() { Layout.ForceDirected.prototype.updateVelocity = function(timestep) { this.eachNode(function(node, point) { - // Is this, along with updatePosition below, the only places that your - // integration code exist? point.v = point.v.add(point.a.multiply(timestep)).multiply(this.damping); - point.a = new Vector(0,0); + point.a = new Vector(0, 0); }); }; Layout.ForceDirected.prototype.updatePosition = function(timestep) { this.eachNode(function(node, point) { - // Same question as above; along with updateVelocity, is this all of - // your integration code? point.p = point.p.add(point.v.multiply(timestep)); }); }; @@ -413,16 +427,19 @@ Layout.ForceDirected.prototype.totalEnergy = function(timestep) { return energy; }; -var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; // stolen from coffeescript, thanks jashkenas! ;-) +// stolen from coffeescript, thanks jashkenas! ;-) +var __bind = function(fn, me) { return function() { + return fn.apply(me, arguments); +}; }; Layout.requestAnimationFrame = __bind(window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || - function(callback, element) { + (function(callback, element) { window.setTimeout(callback, 10); - }, window); + }), window); // start simulation @@ -441,7 +458,7 @@ Layout.ForceDirected.prototype.start = function(render, done) { if (render !== undefined) { render(); - } + } // stop simulation when energy of the system goes below a threshold if (t.totalEnergy() < 0.01) { @@ -457,7 +474,7 @@ Layout.ForceDirected.prototype.start = function(render, done) { Layout.ForceDirected.prototype.nearest = function(pos) { var min = {node: null, point: null, distance: null}; var t = this; - this.graph.nodes.forEach(function(n){ + this.graph.nodes.forEach(function(n) { var point = t.point(n); var distance = point.p.subtract(pos).magnitude(); @@ -471,8 +488,8 @@ Layout.ForceDirected.prototype.nearest = function(pos) { // returns [bottomleft, topright] Layout.ForceDirected.prototype.getBoundingBox = function() { - var bottomleft = new Vector(-2,-2); - var topright = new Vector(2,2); + var bottomleft = new Vector(-2, -2); + var topright = new Vector(2, 2); this.eachNode(function(n, point) { if (point.p.x < bottomleft.x) { @@ -491,7 +508,10 @@ Layout.ForceDirected.prototype.getBoundingBox = function() { var padding = topright.subtract(bottomleft).multiply(0.07); // ~5% padding - return {bottomleft: bottomleft.subtract(padding), topright: topright.add(padding)}; + return { + bottomleft: bottomleft.subtract(padding), + topright: topright.add(padding) + }; }; @@ -518,11 +538,13 @@ Vector.prototype.multiply = function(n) { }; Vector.prototype.divide = function(n) { - return new Vector((this.x / n) || 0, (this.y / n) || 0); // Avoid divide by zero errors.. + return new Vector( + (this.x / n) || 0, + (this.y / n) || 0); // Avoid divide by zero errors.. }; Vector.prototype.magnitude = function() { - return Math.sqrt(this.x*this.x + this.y*this.y); + return Math.sqrt(this.x * this.x + this.y * this.y); }; Vector.prototype.normal = function() { @@ -553,15 +575,6 @@ Layout.ForceDirected.Spring = function(point1, point2, length, k) { this.k = k; // spring constant (See Hooke's law) .. how stiff the spring is }; -// Layout.ForceDirected.Spring.prototype.distanceToPoint = function(point) -// { -// // hardcore vector arithmetic.. ohh yeah! -// // .. see http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment/865080#865080 -// var n = this.point2.p.subtract(this.point1.p).normalise().normal(); -// var ac = point.p.subtract(this.point1.p); -// return Math.abs(ac.x * n.x + ac.y * n.y); -// }; - // Renderer handles the layout rendering loop function Renderer(layout, clear, drawEdge, drawNode) { this.layout = layout; @@ -593,29 +606,29 @@ Renderer.prototype.start = function() { // Array.forEach implementation for IE support.. //https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach -if ( !Array.prototype.forEach ) { - Array.prototype.forEach = function( callback, thisArg ) { - var T, k; - if ( this == null ) { - throw new TypeError( " this is null or not defined" ); - } - var O = Object(this); - var len = O.length >>> 0; // Hack to convert O.length to a UInt32 - if ( {}.toString.call(callback) != "[object Function]" ) { - throw new TypeError( callback + " is not a function" ); - } - if ( thisArg ) { - T = thisArg; - } - k = 0; - while( k < len ) { - var kValue; - if ( k in O ) { - kValue = O[ k ]; - callback.call( T, kValue, k, O ); - } - k++; - } - }; +if (!Array.prototype.forEach) { + Array.prototype.forEach = function(callback, thisArg) { + var T, k; + if (this === null) { + throw new TypeError(' this is null or not defined'); + } + var O = Object(this); + var len = O.length >>> 0; // Hack to convert O.length to a UInt32 + if ({}.toString.call(callback) != '[object Function]') { + throw new TypeError(callback + ' is not a function'); + } + if (thisArg) { + T = thisArg; + } + k = 0; + while (k < len) { + var kValue; + if (k in O) { + kValue = O[k]; + callback.call(T, kValue, k, O); + } + k++; + } + }; } diff --git a/springyui.js b/springyui.js index 771dec9..92c3cae 100755 --- a/springyui.js +++ b/springyui.js @@ -34,7 +34,7 @@ jQuery.fn.springy = function(params) { var nodeSelected = params.nodeSelected || null; var canvas = this[0]; - var ctx = canvas.getContext("2d"); + var ctx = canvas.getContext('2d'); var layout = this.layout = new Layout.ForceDirected(graph, stiffness, repulsion, damping); @@ -47,9 +47,9 @@ jQuery.fn.springy = function(params) { targetBB = layout.getBoundingBox(); // current gets 20% closer to target every iteration currentBB = { - bottomleft: currentBB.bottomleft.add( targetBB.bottomleft.subtract(currentBB.bottomleft) + bottomleft: currentBB.bottomleft.add(targetBB.bottomleft.subtract(currentBB.bottomleft) .divide(10)), - topright: currentBB.topright.add( targetBB.topright.subtract(currentBB.topright) + topright: currentBB.topright.add(targetBB.topright.subtract(currentBB.topright) .divide(10)) }; @@ -107,7 +107,7 @@ jQuery.fn.springy = function(params) { renderer.start(); }); - jQuery(window).bind('mouseup',function(e) { + jQuery(window).bind('mouseup', function(e) { dragged = null; }); @@ -117,7 +117,7 @@ jQuery.fn.springy = function(params) { return this._width[text]; ctx.save(); - ctx.font = "16px Verdana, sans-serif"; + ctx.font = '16px Verdana, sans-serif'; var width = ctx.measureText(text).width + 10; ctx.restore(); @@ -133,7 +133,7 @@ jQuery.fn.springy = function(params) { var renderer = new Renderer(layout, function clear() { - ctx.clearRect(0,0,canvas.width,canvas.height); + ctx.clearRect(0, 0, canvas.width, canvas.height); }, function drawEdge(edge, p1, p2) { var x1 = toScreen(p1).x; @@ -141,7 +141,7 @@ jQuery.fn.springy = function(params) { var x2 = toScreen(p2).x; var y2 = toScreen(p2).y; - var direction = new Vector(x2-x1, y2-y1); + var direction = new Vector(x2 - x1, y2 - y1); var normal = direction.normal().normalise(); var from = graph.getEdges(edge.source, edge.target); @@ -151,7 +151,7 @@ jQuery.fn.springy = function(params) { // Figure out edge's position in relation to other edges between the same nodes var n = 0; - for (var i=0; i 1 || ub < 0 || ub > 1) { return false; @@ -300,6 +300,6 @@ jQuery.fn.springy = function(params) { } return this; -} +}; })();