Permalink
Browse files

Initial work on graph support

  • Loading branch information...
vkaravir committed Dec 7, 2012
1 parent 09a93b9 commit f19d52e056363aa17deb9f95e7a9d409ef2d2543
Showing with 247 additions and 3 deletions.
  1. +1 −1 Makefile
  2. +2 −2 css/JSAV.css
  3. +244 −0 src/graph.js
View
@@ -5,7 +5,7 @@ CAT = cat
SRC = src
TARGET = build
-SOURCES = $(SRC)/front.js $(SRC)/core.js $(SRC)/utils.js $(SRC)/anim.js $(SRC)/messages.js $(SRC)/effects.js $(SRC)/events.js $(SRC)/graphicals.js $(SRC)/datastructures.js $(SRC)/array.js $(SRC)/tree.js $(SRC)/list.js $(SRC)/code.js $(SRC)/settings.js $(SRC)/questions.js $(SRC)/exercise.js $(SRC)/version.js
+SOURCES = $(SRC)/front.js $(SRC)/core.js $(SRC)/utils.js $(SRC)/anim.js $(SRC)/messages.js $(SRC)/effects.js $(SRC)/events.js $(SRC)/graphicals.js $(SRC)/datastructures.js $(SRC)/array.js $(SRC)/tree.js $(SRC)/list.js $(SRC)/graph.js $(SRC)/code.js $(SRC)/settings.js $(SRC)/questions.js $(SRC)/exercise.js $(SRC)/version.js
# This only works right when this is a submodule
FETCH = ../.git/modules/JSAV/FETCH_HEAD
View
@@ -414,12 +414,12 @@ a.jsavdialogclose {
line-height: 1;
}
-/********* TREE DATA STRUCTURE *******/
+/********* TREE / GRAPH DATA STRUCTURE *******/
.jsavtree {
position: relative;
}
-.jsavtreenode {
+.jsavtreenode, .jsavgraphnode {
border-radius: 23px;
position: absolute;
}
View
@@ -0,0 +1,244 @@
+/**
+* Module that contains the graph data structure implementations.
+* Depends on core.js, datastructures.js, anim.js, utils.js
+*/
+(function($) {
+ "use strict";
+ if (typeof JSAV === "undefined") { return; }
+ var Edge = JSAV._types.ds.Edge; // shortcut to JSAV Edge
+
+ var Graph = function(jsav, options) {
+ this.nodes = [];
+ this.edges = [];
+ this.jsav = jsav;
+ this.options = $.extend({visible: true, nodegap: 40, autoresize: true}, options);
+ var el = this.options.element || $("<div/>");
+ el.addClass("jsavgraph");
+ for (var key in this.options) {
+ var val = this.options[key];
+ if (this.options.hasOwnProperty(key) && typeof(val) === "string" || typeof(val) === "number" || typeof(val) === "boolean") {
+ el.attr("data-" + key, val);
+ }
+ }
+ if (!this.options.element) {
+ $(jsav.canvas).append(el);
+ }
+ this.element = el;
+ this.element.attr({"id": this.id()});
+ if (this.options.autoresize) {
+ el.addClass("jsavautoresize");
+ }
+ JSAV.utils._helpers.handlePosition(this);
+ JSAV.utils._helpers.handleVisibility(this, this.options);
+ };
+ var graphproto = Graph.prototype;
+ $.extend(graphproto, JSAV._types.ds.common);
+ graphproto.css = JSAV.utils._helpers.css;
+ graphproto._setcss = JSAV.anim(JSAV.utils._helpers._setcss);
+ graphproto.clear = function() {};
+
+ // returns a new graph node
+ graphproto.newNode = function(value, options) {
+ var newNode = new GraphNode(this, value, options);
+ this.nodes.push(newNode);
+ return newNode;
+ };
+ graphproto.addNode = function(value, options) {
+ return this.newNode(value, options);
+ };
+
+ // adds an edge from fromNode to toNode
+ graphproto.addEdge = function(fromNode, toNode) {};
+
+ // removes an edge from fromNode to toNode
+ graphproto.removeEdge = function(fromNode, toNode) {};
+
+ // returns true/false whether an edge from fromNode to toNode exists
+ graphproto.hasEdge = function(fromNode, toNode) {};
+
+ // removes the given node
+ graphproto.removeNode = function(node) {};
+
+ // returns an array of nodes in the graph
+ graphproto.nodes = function() {};
+
+ // returns an array of edges in the graph
+ graphproto.edges = function() {};
+
+ graphproto.layout = function() {
+ var spring = new SpringLayout(this);
+ console.log(spring);
+ };
+
+var SpringLayout = function(graph) {
+ this.graph = graph;
+ this.iterations = 500;
+ this.maxRepulsiveForceDistance = 6;
+ this.k = 2;
+ this.c = 0.01;
+ this.maxVertexMovement = 0.5;
+ this.layout();
+};
+SpringLayout.prototype = {
+ layout: function() {
+ this.layoutPrepare();
+ for (var i = 0; i < this.iterations; i++) {
+ this.layoutIteration();
+ }
+ this.layoutCalcBounds();
+ },
+
+ layoutPrepare: function() {
+ for (var i in this.graph.nodes) {
+ var node = this.graph.nodes[i];
+ node.layoutPosX = 0;
+ node.layoutPosY = 0;
+ node.layoutForceX = 0;
+ node.layoutForceY = 0;
+ }
+
+ },
+
+ layoutCalcBounds: function() {
+ var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity;
+
+ for (var i in this.graph.nodes) {
+ var x = this.graph.nodes[i].layoutPosX;
+ var y = this.graph.nodes[i].layoutPosY;
+
+ if(x > maxx) maxx = x;
+ if(x < minx) minx = x;
+ if(y > maxy) maxy = y;
+ if(y < miny) miny = y;
+ }
+
+ this.graph.layoutMinX = minx;
+ this.graph.layoutMaxX = maxx;
+ this.graph.layoutMinY = miny;
+ this.graph.layoutMaxY = maxy;
+ },
+
+ layoutIteration: function() {
+ // Forces on nodes due to node-node repulsions
+
+ var prev = new Array();
+ for(var c in this.graph.nodes) {
+ var node1 = this.graph.nodes[c];
+ for (var d in prev) {
+ var node2 = this.graph.nodes[prev[d]];
+ this.layoutRepulsive(node1, node2);
+
+ }
+ prev.push(c);
+ }
+
+ // Forces on nodes due to edge attractions
+ for (var i = 0; i < this.graph.edges.length; i++) {
+ var edge = this.graph.edges[i];
+ this.layoutAttractive(edge);
+ }
+
+ // Move by the given force
+ for (i in this.graph.nodes) {
+ var node = this.graph.nodes[i];
+ var xmove = this.c * node.layoutForceX;
+ var ymove = this.c * node.layoutForceY;
+
+ var max = this.maxVertexMovement;
+ if(xmove > max) xmove = max;
+ if(xmove < -max) xmove = -max;
+ if(ymove > max) ymove = max;
+ if(ymove < -max) ymove = -max;
+
+ node.layoutPosX += xmove;
+ node.layoutPosY += ymove;
+ node.layoutForceX = 0;
+ node.layoutForceY = 0;
+ }
+ },
+
+ layoutRepulsive: function(node1, node2) {
+ if (typeof node1 == 'undefined' || typeof node2 == 'undefined')
+ return;
+ var dx = node2.layoutPosX - node1.layoutPosX;
+ var dy = node2.layoutPosY - node1.layoutPosY;
+ var d2 = dx * dx + dy * dy;
+ if(d2 < 0.01) {
+ dx = 0.1 * Math.random() + 0.1;
+ dy = 0.1 * Math.random() + 0.1;
+ var d2 = dx * dx + dy * dy;
+ }
+ var d = Math.sqrt(d2);
+ if(d < this.maxRepulsiveForceDistance) {
+ var repulsiveForce = this.k * this.k / d;
+ node2.layoutForceX += repulsiveForce * dx / d;
+ node2.layoutForceY += repulsiveForce * dy / d;
+ node1.layoutForceX -= repulsiveForce * dx / d;
+ node1.layoutForceY -= repulsiveForce * dy / d;
+ }
+ },
+
+ layoutAttractive: function(edge) {
+ var node1 = edge.source;
+ var node2 = edge.target;
+
+ var dx = node2.layoutPosX - node1.layoutPosX;
+ var dy = node2.layoutPosY - node1.layoutPosY;
+ var d2 = dx * dx + dy * dy;
+ if(d2 < 0.01) {
+ dx = 0.1 * Math.random() + 0.1;
+ dy = 0.1 * Math.random() + 0.1;
+ var d2 = dx * dx + dy * dy;
+ }
+ var d = Math.sqrt(d2);
+ if(d > this.maxRepulsiveForceDistance) {
+ d = this.maxRepulsiveForceDistance;
+ d2 = d * d;
+ }
+ var attractiveForce = (d2 - this.k * this.k) / this.k;
+ if(edge.attraction == undefined) edge.attraction = 1;
+ attractiveForce *= Math.log(edge.attraction) * 0.5 + 1;
+
+ node2.layoutForceX -= attractiveForce * dx / d;
+ node2.layoutForceY -= attractiveForce * dy / d;
+ node1.layoutForceX += attractiveForce * dx / d;
+ node1.layoutForceY += attractiveForce * dy / d;
+ }
+};
+
+
+ var GraphNode = function(container, value, options) {
+ this.jsav = container.jsav;
+ this.container = container;
+ this.options = $.extend(true, {visible: true}, options);
+ var el = this.options.nodeelement || $("<div><span class='jsavvalue'>" + this._valstring(value) + "</span></div>"),
+ valtype = typeof(value);
+ if (valtype === "object") { valtype = "string"; }
+ this.element = el;
+ el.addClass("jsavnode jsavgraphnode")
+ .attr({"data-value": value, "id": this.id(), "data-value-type": valtype })
+ .data("node", this);
+ if (this.options.autoResize) {
+ el.addClass("jsavautoresize");
+ }
+ this.container.element.append(el);
+
+ JSAV.utils._helpers.handleVisibility(this, this.options);
+ this._successors = [];
+ };
+ var nodeproto = GraphNode.prototype;
+ $.extend(nodeproto, JSAV._types.ds.Node.prototype);
+ nodeproto.css = JSAV.utils._helpers.css;
+ nodeproto._setcss = JSAV.anim(JSAV.utils._helpers._setcss);
+
+ // expose the types
+ var dstypes = JSAV._types.ds;
+ dstypes.Graph = Graph;
+ dstypes.GraphNode = GraphNode;
+
+ // add functions to jsav.ds to create graphs
+ JSAV.ext.ds.graph = function(options) {
+ return new Graph(this, $.extend(true, {visible: true, autoresize: true}, options));
+ };
+
+})(jQuery);

0 comments on commit f19d52e

Please sign in to comment.