Author: marekz
Date: 2011-07-27 06:00:28 -0700 (Wed, 27 Jul 2011)
New Revision: 26280
Added:
cytoscapeweb/branches/gsoc2011/cytoscapeweb-svg/springy.js
Modified:
cytoscapeweb/branches/gsoc2011/cytoscapeweb-svg/cytoscapeweb-svg.js
cytoscapeweb/branches/gsoc2011/cytoscapeweb-svg/main.html
Log:
Refactored arrowheads and added arrow color styles. Introduced springy.js to
test FDL stability and FF/IE9 compatibility. Cleaned up demo code.
Modified: cytoscapeweb/branches/gsoc2011/cytoscapeweb-svg/cytoscapeweb-svg.js
===================================================================
--- cytoscapeweb/branches/gsoc2011/cytoscapeweb-svg/cytoscapeweb-svg.js
2011-07-26 23:40:38 UTC (rev 26279)
+++ cytoscapeweb/branches/gsoc2011/cytoscapeweb-svg/cytoscapeweb-svg.js
2011-07-27 13:00:28 UTC (rev 26280)
@@ -5,7 +5,11 @@
* Google Summer of Code 2011
*/
-
+/* TODO
+ * - Move SVG DOM functions into a separate class
+ * - Move create*Element methods into the objects that use them
+ * - Switch some hand-built paths to generic ones with SVG transforms applied
+ */
/**
* Network visualization instance.
* Represents an instance of the network visualization.
@@ -30,7 +34,9 @@
this._setElementAttributes = function(elem, attributeMap) {
for (var attribute in attributeMap) {
- elem.setAttribute(attribute, attributeMap[attribute]);
+ var value = attributeMap[attribute];
+ if (value != null)
+ elem.setAttribute(attribute, value);
}
};
@@ -45,29 +51,33 @@
this._svgNodeGroup = this._createSvgElement("g", this._svgElem);
this._svgLabelGroup = this._createSvgElement("g", this._svgElem);
- this._createNodeGroup = function() {
-
- }
-
this._createLabelElement = function () {
return this._createSvgElement("text", this._svgLabelGroup);
};
this._createEdgePathElement = function() {
return this._createSvgElement("path", this._svgEdgeGroup);
- }
+ };
this._createNodePathElement = function() {
return this._createSvgElement("path", this._svgNodeGroup);
- }
+ };
this._removeNodePathElement = function(elem) {
this._svgNodeGroup.removeChild(elem);
- }
+ };
this._removeEdgePathElement = function(elem) {
this._svgEdgeGroup.removeChild(elem);
- }
+ };
+
+ this._createLabelElement = function() {
+ return this._createSvgElement("text", this._svgLabelGroup);
+ };
+
+ this._removeLabelElement = function(elem) {
+ this._svgLabelGroup.removeChild(elem);
+ };
// --------
this._edges = {};
@@ -80,6 +90,13 @@
this._offsetX = 186;
this._offsetY = 100;
+
+ this._styleDefaults = {
+ global: {},
+ node: {},
+ edge: {}
+ };
+
// Style defaults
this._style = {
global: {
@@ -114,8 +131,10 @@
"style": "SOLID",
"forwardArrowShape": "",
"forwardArrowSize": 10,
+ "forwardArrowColor": "red",
"backwardArrowShape": "",
- "backwardArrowSize": 10
+ "backwardArrowSize": 10,
+ "backwardArrowColor": "black"
}
};
@@ -616,7 +635,7 @@
this._visualization = vis;
this._elem = null;
- this._elemLabel = null;
+ this._labelElem = null;
this._group = "node";
this._listeners = {};
this._eventTypes = ["dragStart", "dragEnd", "hoverStart", "hoverEnd",
"click", "select", "deselect"];
@@ -901,6 +920,7 @@
this.remove = function() {
if (this._elem) {
this._visualization._removeNodePathElement(this._elem);
+ if (this._labelElem)
this._visualization._removeLabelElement(this._labelElem);
}
//if (this._elemLabel) this._elemLabel.remove();
@@ -919,8 +939,8 @@
this._visualization = vis;
this._elem = null;
this._elemLabel = null;
- this._elemArrowForward = null;
- this._elemArrowBackward = null;
+ this._forwardArrowElem = null;
+ this._backwardArrowElem = null;
this._group = "edge";
this._offset = 0; // Offset used for drawing multiple edges between two
nodes
this._label = "";
@@ -940,6 +960,12 @@
if (this._elem) {
this._visualization._removeEdgePathElement(this._elem);
}
+ if (this._forwardArrowElem) {
+
this._visualization._removeEdgePathElement(this._forwardArrowElem);
+ }
+ if (this._backwardArrowElem) {
+
this._visualization._removeEdgePathElement(this._backwardArrowElem);
+ }
this._source = null;
this._target = null;
};
@@ -960,6 +986,8 @@
if (this._elem == null) {
this._elem =
this._visualization._createEdgePathElement();
//this._elem =
this._visualization._canvas.path(this._getSvgPath()).toBack().click(Util.delegate(this,
"_onClick")).attr("cursor", "pointer");
+ this._forwardArrowElem =
this._visualization._createEdgePathElement();
+ this._backwardArrowElem =
this._visualization._createEdgePathElement();
}
var dashArray = {
@@ -975,16 +1003,14 @@
// Update position
"d": paths[0],
"stroke-dasharray" : dashArray,
- "fill": "none"
+ "fill": "none",
+ "stroke-linecap": "square"
};
// TODO: render paths[1] and paths[2], which are the arrows.
var attrMap = {
"stroke": "color",
- //"fill": "color", // this is not applicable for curved
paths
-
-
"stroke-width": "width",
"stroke-opacity": "opacity"
};
@@ -997,7 +1023,35 @@
}
this._visualization._setElementAttributes(this._elem, attr);
- //this._elem.attr(attr);
+
+ // Arrow head attributes
+ // 1. Forward arrow
+ var forwardArrowColor =
this.getRenderedStyle("forwardArrowColor") || attr["stroke"];
+ var forwardArrowAttr = {
+ "fill": forwardArrowColor,
+ "stroke": forwardArrowColor,
+ "stroke-width": attr["stroke-width"],
+ "stroke-opacity": attr["stroke-opacity"], // TODO:
change to rely only on fill, rather than on stroke
+ // because opacity
+ "opacity": attr["stroke-opacity"],
+ "d": paths[1] || null
+ };
+
+
this._visualization._setElementAttributes(this._forwardArrowElem,
forwardArrowAttr);
+
+ // 2. Backward arrow
+ var backwardArrowColor =
this.getRenderedStyle("backwardArrowColor") || attr["stroke"];
+ var backwardArrowAttr = {
+ "fill": backwardArrowColor,
+ "stroke": backwardArrowColor,
+ "stroke-width": attr["stroke-width"],
+ "stroke-opacity": attr["stroke-opacity"],
+ "opacity": attr["stroke-opacity"],
+ "d": paths[2] || null
+ };
+
this._visualization._setElementAttributes(this._backwardArrowElem,
backwardArrowAttr);
+
+
};
this._onClick = function() {
@@ -1104,11 +1158,11 @@
var bpy = b.y + bcy;
- var path = [];
- path.push(populateString("M %,% Q %,% %,%",
[apx, apy, cx, cy, bpx, bpy]));
+ var path = new Array(3);
+ path[0] = (populateString("M %,% Q %,% %,%",
[apx, apy, cx, cy, bpx, bpy]));
- if (this.getRenderedStyle("forwardArrowShape")
== "DELTA") path.push(Shapes.buildArrow(apx, apy, 6, 10, Math.atan2(-acy,
-acx), 1));
- if (this.getRenderedStyle("backwardArrowShape")
== "DELTA") path.push(Shapes.buildArrow(bpx, bpy, 6, 10, Math.atan2(-bcy,
-bcx), 1));
+ if (this.getRenderedStyle("forwardArrowShape")
== "DELTA") path[1] = (Shapes.buildArrow(apx, apy, 6, 10, Math.atan2(-acy,
-acx), 1));
+ if (this.getRenderedStyle("backwardArrowShape")
== "DELTA") path[2] = (Shapes.buildArrow(bpx, bpy, 6, 10, Math.atan2(-bcy,
-bcx), 1));
return path;
@@ -1200,3 +1254,76 @@
});
}
+
+/**
+ * Arbor.js Renderer
+ */
+
+function ArborRenderer(vis) {
+ this.vis = vis;
+
+ this.init = function(ps) {
+ this.ps = ps;
+ ps.screenSize(320, 320);
+ ps.screenPadding(10);
+ }
+
+ this.redraw = function() {
+ this.ps.eachNode(function(node, pt) {
+ vis.getNode(node.name).setPosition(pt.x, pt.y);
+ });
+ }
+
+ this.die = function() {
+ this.vis = null;
+ this.ps = null;
+ }
+}
+
+/**
+ * Springy.js renderer
+ */
+
+var SpringyRenderer = function() {
+ var vis;
+ var graph;
+ var layout;
+
+ this.stop = function() {
+ if (layout.intervalId) {
+ layout.stop();
+ }
+ }
+
+ this.render = function(v) {
+ vis = v;
+ graph = new Graph();
+ var nodes = vis.getNodes();
+ for (var i = 0; i < nodes.length; i++) {
+ graph.newNodeM(nodes[i]._id);
+ }
+ var edges = vis.getEdges();
+ for (var i = 0; i < edges.length; i++) {
+ graph.newEdgeM(edges[i]._source._id,
+ edges[i]._target._id);
+ }
+ if (layout) layout.stop();
+ layout = new Layout.ForceDirected(graph, 200, 200, 0.3);
+
+
+
+ renderer = new Renderer(10, layout,
+ function clear() {
+
+ },
+ function drawEdge(edge, p1, p2) {
+
+ },
+ function drawNode(node, p) {
+ //console.log(p);
+ //alert(JSON.stringify(p));
+ vis.getNode(node.data.label).setPosition(p.x *
100 + 200, p.y * 100 +200);
+ });
+ renderer.start();
+ }
+};
Modified: cytoscapeweb/branches/gsoc2011/cytoscapeweb-svg/main.html
===================================================================
--- cytoscapeweb/branches/gsoc2011/cytoscapeweb-svg/main.html 2011-07-26
23:40:38 UTC (rev 26279)
+++ cytoscapeweb/branches/gsoc2011/cytoscapeweb-svg/main.html 2011-07-27
13:00:28 UTC (rev 26280)
@@ -10,12 +10,14 @@
margin: 30px auto;
}
</style>
+ <script type="text/javascript" src="springy.js"></script>
<script type="text/javascript"
src="cytoscapeweb-svg.js"></script>
<script type="text/javascript" src="jquery-1.6.2.js"></script>
- <script type="text/javascript" src="arbor.js"></script>
+ <!-- <script type="text/javascript" src="arbor.js"></script> -->
+
<script type="text/javascript">
- var graphs = {};
+
var styles = {};
styles.basicStyle = {
@@ -28,7 +30,6 @@
borderColor: "#666666",
borderWidth: 3,
opacity: 1,
-
},
edges: {
color: "#999999",
@@ -36,12 +37,13 @@
opacity: 0.8
}
}
-
+
+
styles.demoStyle = {
global: {
backgroundColor: "#fcfcfc"
},
- nodes: {
+ nodes: {
color: "#cccccc",
size: 14,
borderWidth: 2,
@@ -54,6 +56,7 @@
}
}
+
styles.darkStyle = {
global: {
backgroundColor: "#202020"
@@ -71,92 +74,377 @@
}
}
+
+ var graphs = {};
+
graphs.simpleGraph = {
- nodes: [
- {id: "n1", x: 220, y: 30},
- {id: "n2", x: 0, y: 97},
- {id: "n3", x: 54, y: 185},
- {id: "n4", x: 200, y: 191},
- {id: "n5", x: 210, y: 254},
- {id: "n6", x: 60, y: -5},
- {id: "n7", x: 280, y: 170}
- ],
- edges: [
- {id: "e1", source: "n1", target: "n2",
style: {forwardArrowShape: "DELTA"}},
- {id: "e2", source: "n2", target: "n1",
style: {forwardArrowShape: "DELTA"}},
- {id: "e3", source: "n1", target: "n3",
style: {forwardArrowShape: "DELTA"}},
- {id: "e4", source: "n2", target: "n3",
style: {forwardArrowShape: "DELTA"}},
- {id: "e5", source: "n4", target: "n5",
style: {forwardArrowShape: "DELTA"}},
- {id: "e6", source: "n5", target: "n2",
style: {forwardArrowShape: "DELTA"}},
- {id: "e7", source: "n3", target: "n5",
style: {forwardArrowShape: "DELTA"}},
- {id: "e8", source: "n1", target: "n4",
style: {forwardArrowShape: "DELTA"}},
- {id: "e9", source: "n3", target: "n4",
style: {forwardArrowShape: "DELTA"}},
- {id: "e10", source: "n4", target: "n6",
style: {forwardArrowShape: "DELTA"}},
- {id: "e11", source: "n6", target: "n4",
style: {forwardArrowShape: "DELTA"}},
- {id: "e12", source: "n3", target: "n6",
style: {forwardArrowShape: "DELTA"}},
- {id: "e14", source: "n4", target: "n7",
style: {forwardArrowShape: "DELTA", backwardArrowShape: "DELTA"}},
- {id: "e13", source: "n4", target: "n7",
style: {forwardArrowShape: "DELTA"}},
- {id: "e15", source: "n7", target: "n4",
style: {forwardArrowShape: "DELTA"}},
-
-
- ]
+ nodes: [{
+ id: "n1",
+ x: 220,
+ y: 30
+ }, {
+ id: "n2",
+ x: 0,
+ y: 97
+ }, {
+ id: "n3",
+ x: 54,
+ y: 185
+ }, {
+ id: "n4",
+ x: 200,
+ y: 191
+ }, {
+ id: "n5",
+ x: 210,
+ y: 254
+ }, {
+ id: "n6",
+ x: 60,
+ y: -5
+ }, {
+ id: "n7",
+ x: 280,
+ y: 170
+ }],
+ edges: [{
+ id: "e1",
+ source: "n1",
+ target: "n2",
+ style: {
+ forwardArrowShape: "DELTA"
+ }
+ }, {
+ id: "e2",
+ source: "n2",
+ target: "n1",
+ style: {
+ forwardArrowShape: "DELTA"
+ }
+ }, {
+ id: "e3",
+ source: "n1",
+ target: "n3",
+ style: {
+ forwardArrowShape: "DELTA"
+ }
+ }, {
+ id: "e4",
+ source: "n2",
+ target: "n3",
+ style: {
+ forwardArrowShape: "DELTA"
+ }
+ }, {
+ id: "e5",
+ source: "n4",
+ target: "n5",
+ style: {
+ forwardArrowShape: "DELTA"
+ }
+ }, {
+ id: "e6",
+ source: "n5",
+ target: "n2",
+ style: {
+ forwardArrowShape: "DELTA"
+ }
+ }, {
+ id: "e7",
+ source: "n3",
+ target: "n5",
+ style: {
+ forwardArrowShape: "DELTA"
+ }
+ }, {
+ id: "e8",
+ source: "n1",
+ target: "n4",
+ style: {
+ forwardArrowShape: "DELTA"
+ }
+ }, {
+ id: "e9",
+ source: "n3",
+ target: "n4",
+ style: {
+ forwardArrowShape: "DELTA"
+ }
+ }, {
+ id: "e10",
+ source: "n4",
+ target: "n6",
+ style: {
+ forwardArrowShape: "DELTA"
+ }
+ }, {
+ id: "e11",
+ source: "n6",
+ target: "n4",
+ style: {
+ forwardArrowShape: "DELTA"
+ }
+ }, {
+ id: "e12",
+ source: "n3",
+ target: "n6",
+ style: {
+ forwardArrowShape: "DELTA"
+ }
+ }, {
+ id: "e14",
+ source: "n4",
+ target: "n7",
+ style: {
+ forwardArrowShape: "DELTA",
+ backwardArrowShape: "DELTA"
+ }
+ }, {
+ id: "e13",
+ source: "n4",
+ target: "n7",
+ style: {
+ forwardArrowShape: "DELTA"
+ }
+ }, {
+ id: "e15",
+ source: "n7",
+ target: "n4",
+ style: {
+ forwardArrowShape: "DELTA"
+ }
+ }, ]
}
-
graphs.petersenGraph = {
- nodes: [
- {id: "nw2", x: -27, y: 97},
- {id: "sw2", x: 24, y: 255},
- {id: "se2", x: 210, y: 254},
- {id: "ne2", x: 254, y: 90},
- {id: "n2", x: 109, y: -10},
- {id: "sw", x: 73, y: 190},
- {id: "se", x: 154, y: 190},
- {id: "nw", x: 46, y: 115},
- {id: "ne", x: 176, y: 113},
- {id: "n", x: 109, y: 65}
- ],
- edges: [
- {id: "e1", source: "nw2", target: "n2"},
- {id: "e2", source: "nw2", target: "nw"},
- {id: "e4", source: "sw2", target:
"nw2"},
- {id: "e5", source: "sw2", target: "sw"},
- {id: "e6", source: "se2", target:
"sw2"},
- {id: "e7", source: "se2", target: "se"},
- {id: "e8", source: "ne2", target:
"se2"},
- {id: "e9", source: "ne2", target: "ne"},
- {id: "e10", source: "n2", target:
"ne2"},
- {id: "e11", source: "n2", target: "n"},
- {id: "e12", source: "sw", target: "se"},
- {id: "e13", source: "nw", target: "sw"},
- {id: "e14", source: "nw", target: "se"},
- {id: "e15", source: "nw", target: "ne"},
- {id: "e16", source: "ne", target: "sw"},
- {id: "e17", source: "ne", target: "se"},
- {id: "e18", source: "n", target: "sw"},
- {id: "e19", source: "n", target: "se"},
- {id: "e20", source: "n", target: "nw"},
- {id: "e21", source: "n", target: "ne"},
- ]
+ nodes: [{
+ id: "nw2",
+ x: -27,
+ y: 97
+ }, {
+ id: "sw2",
+ x: 24,
+ y: 255
+ }, {
+ id: "se2",
+ x: 210,
+ y: 254
+ }, {
+ id: "ne2",
+ x: 254,
+ y: 90
+ }, {
+ id: "n2",
+ x: 109,
+ y: -10
+ }, {
+ id: "sw",
+ x: 73,
+ y: 190
+ }, {
+ id: "se",
+ x: 154,
+ y: 190
+ }, {
+ id: "nw",
+ x: 46,
+ y: 115
+ }, {
+ id: "ne",
+ x: 176,
+ y: 113
+ }, {
+ id: "n",
+ x: 109,
+ y: 65
+ }],
+ edges: [{
+ id: "e1",
+ source: "nw2",
+ target: "n2"
+ }, {
+ id: "e2",
+ source: "nw2",
+ target: "nw"
+ }, {
+ id: "e4",
+ source: "sw2",
+ target: "nw2"
+ }, {
+ id: "e5",
+ source: "sw2",
+ target: "sw"
+ }, {
+ id: "e6",
+ source: "se2",
+ target: "sw2"
+ }, {
+ id: "e7",
+ source: "se2",
+ target: "se"
+ }, {
+ id: "e8",
+ source: "ne2",
+ target: "se2"
+ }, {
+ id: "e9",
+ source: "ne2",
+ target: "ne"
+ }, {
+ id: "e10",
+ source: "n2",
+ target: "ne2"
+ }, {
+ id: "e11",
+ source: "n2",
+ target: "n"
+ }, {
+ id: "e12",
+ source: "sw",
+ target: "se"
+ }, {
+ id: "e13",
+ source: "nw",
+ target: "sw"
+ }, {
+ id: "e14",
+ source: "nw",
+ target: "se"
+ }, {
+ id: "e15",
+ source: "nw",
+ target: "ne"
+ }, {
+ id: "e16",
+ source: "ne",
+ target: "sw"
+ }, {
+ id: "e17",
+ source: "ne",
+ target: "se"
+ }, {
+ id: "e18",
+ source: "n",
+ target: "sw"
+ }, {
+ id: "e19",
+ source: "n",
+ target: "se"
+ }, {
+ id: "e20",
+ source: "n",
+ target: "nw"
+ }, {
+ id: "e21",
+ source: "n",
+ target: "ne"
+ }, ]
}
+
graphs.ringGraph = {
- "nodes": [
-
{"id":"n1","x":38,"y":219,"style":{size: 32, borderColor: "black", color:
"teal", shape: "DIAMOND", borderWidth: 4}},
- {"id":"n2","x":57,"y":44,"style":{size:
26, color: "chocolate", borderWidth: 6}},
-
{"id":"n3","x":171,"y":28,"style":{size: 16, borderColor: "green", shape:
"RECTANGLE", borderWidth: 2}},
-
{"id":"n4","x":251,"y":132,"style":{size: 12, borderColor: "blue", shape:
"DIAMOND", borderWidth:4}},
-
{"id":"n5","x":156,"y":220,"style":{borderColor: "red"}}
- ],
- "edges": [
-
{"id":"e1","source":"n1","target":"n2","style":{forwardArrowShape: "DELTA",
color: "#394", width: 3}},
-
{"id":"e2","source":"n2","target":"n3","style":{forwardArrowShape: "DELTA",
backwardArrowShape: "DELTA", width: 3}},
-
{"id":"e3","source":"n3","target":"n4","style":{color: "red", width: 6, style:
"DASH"}},
-
{"id":"e4","source":"n4","target":"n5","style":{forwardArrowShape: "DELTA",
color: "blue", width: 5}},
-
{"id":"e5","source":"n5","target":"n4","style":{forwardArrowShape: "DELTA",
width: 5}},
-
{"id":"e6","source":"n5","target":"n1","style":{style: "DOT"}}
- ]
+ "nodes": [{
+ "id": "n1",
+ "x": 38,
+ "y": 219,
+ "style": {
+ size: 32,
+ borderColor: "black",
+ color: "teal",
+ shape: "DIAMOND",
+ borderWidth: 4
+ }
+ }, {
+ "id": "n2",
+ "x": 57,
+ "y": 44,
+ "style": {
+ size: 26,
+ color: "chocolate",
+ borderWidth: 6
+ }
+ }, {
+ "id": "n3",
+ "x": 171,
+ "y": 28,
+ "style": {
+ size: 16,
+ borderColor: "green",
+ shape: "RECTANGLE",
+ borderWidth: 2
+ }
+ }, {
+ "id": "n4",
+ "x": 251,
+ "y": 132,
+ "style": {
+ size: 12,
+ borderColor: "blue",
+ shape: "DIAMOND",
+ borderWidth: 4
+ }
+ }, {
+ "id": "n5",
+ "x": 156,
+ "y": 220,
+ "style": {
+ borderColor: "red"
+ }
+ }],
+ "edges": [{
+ "id": "e1",
+ "source": "n1",
+ "target": "n2",
+ "style": {
+ forwardArrowShape: "DELTA",
+ color: "#394",
+ width: 3
+ }
+ }, {
+ "id": "e2",
+ "source": "n2",
+ "target": "n3",
+ "style": {
+ forwardArrowShape: "DELTA",
+ backwardArrowShape: "DELTA",
+ width: 3
+ }
+ }, {
+ "id": "e3",
+ "source": "n3",
+ "target": "n4",
+ "style": {
+ color: "red",
+ width: 6,
+ style: "DASH"
+ }
+ }, {
+ "id": "e4",
+ "source": "n4",
+ "target": "n5",
+ "style": {
+ forwardArrowShape: "DELTA",
+ color: "blue",
+ width: 5
+ }
+ }, {
+ "id": "e5",
+ "source": "n5",
+ "target": "n4",
+ "style": {
+ forwardArrowShape: "DELTA",
+ width: 5
+ }
+ }, {
+ "id": "e6",
+ "source": "n5",
+ "target": "n1",
+ "style": {
+ style: "DOT"
+ }
+ }]
}
+ // Create a populated graph for stress testing
var pg = {nodes:[], edges:[]};
for (var x = 0; x < 100; x++) {
@@ -168,24 +456,29 @@
}
graphs.pg = pg;
+ // Initialize
var vis;
window.onload = function() {
vis = new Visualization("container-div", 640,
640);
+ sr = new SpringyRenderer();
load("simpleGraph");
setstyle("basicStyle");
}
- var ps;
+
+ // Load a graph
+ var ps; // arbor.ParticleSystem
+ var sr; // springyrenderer
function load(graph) {
vis.loadGraph(graphs[graph]);
-
+ /*
if (ps) {
- ps.renderer.delete();
+ ps.renderer.die();
ps.stop();
ps = null;
}
ps = arbor.ParticleSystem(800, 600, 0.5);
ps.parameters({gravity: true});
- ps.renderer = new Renderer(vis);
+ ps.renderer = new ArborRenderer(vis);
var nodes = vis.getNodes();
for (var i = 0; i < nodes.length; i++) {
@@ -199,8 +492,8 @@
ps.addEdge(edges[i]._source._id,
edges[i]._target._id);
}
-
-
+ */
+ sr.render(vis);
/*
var clickEvent = function() {
alert("Clicked node '" + this._id + "'
at " + this._x + ", " + this._y);
@@ -218,28 +511,9 @@
vis.draw();
}
- function Renderer(vis) {
- this.vis = vis;
-
- this.init = function(ps) {
- this.ps = ps;
- ps.screenSize(320, 320);
- ps.screenPadding(10);
- }
-
- this.redraw = function() {
- this.ps.eachNode(function(node, pt) {
-
vis.getNode(node.name).setPosition(pt.x, pt.y);
- });
- }
-
- this.delete = function() {
- this.vis = null;
- this.ps = null;
- }
- }
+
Added: cytoscapeweb/branches/gsoc2011/cytoscapeweb-svg/springy.js
===================================================================
--- cytoscapeweb/branches/gsoc2011/cytoscapeweb-svg/springy.js
(rev 0)
+++ cytoscapeweb/branches/gsoc2011/cytoscapeweb-svg/springy.js 2011-07-27
13:00:28 UTC (rev 26280)
@@ -0,0 +1,621 @@
+/**
+Copyright (c) 2010 Dennis Hotson
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the "Software"), to deal in the Software without
+ restriction, including without limitation the rights to use,
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following
+ conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+var Graph = function()
+{
+ this.nodeCache = {}; // Added mz
+ this.nodeSet = {};
+ this.nodes = [];
+ this.edges = [];
+ this.adjacency = {};
+
+ this.nextNodeId = 0;
+ this.nextEdgeId = 0;
+ this.eventListeners = [];
+};
+
+
+
+SpringyNode = function(id, data)
+{
+ this.id = id;
+ this.data = typeof(data) !== 'undefined' ? data : {};
+};
+
+SpringyEdge = function(id, source, target, data)
+{
+ this.id = id;
+ this.source = source;
+ this.target = target;
+ this.data = typeof(data) !== 'undefined' ? data : {};
+};
+
+Graph.prototype.addNode = function(node)
+{
+ if (typeof(this.nodeSet[node.id]) === 'undefined')
+ {
+ this.nodes.push(node);
+ }
+
+ this.nodeSet[node.id] = node;
+
+ this.notify();
+ return node;
+};
+
+Graph.prototype.addEdge = function(edge)
+{
+ var exists = false;
+ this.edges.forEach(function(e){
+ if (edge.id === e.id) { exists = true; }
+ });
+
+ if (!exists)
+ {
+ this.edges.push(edge);
+ }
+
+ if (typeof(this.adjacency[edge.source.id]) === 'undefined')
+ {
+ this.adjacency[edge.source.id] = {};
+ }
+ if (typeof(this.adjacency[edge.source.id][edge.target.id]) ===
'undefined')
+ {
+ this.adjacency[edge.source.id][edge.target.id] = [];
+ }
+
+ exists = false;
+ this.adjacency[edge.source.id][edge.target.id].forEach(function(e){
+ if (edge.id === e.id) { exists = true; }
+ });
+
+ if (!exists)
+ {
+ this.adjacency[edge.source.id][edge.target.id].push(edge);
+ }
+
+ this.notify();
+ return edge;
+};
+
+Graph.prototype.newNode = function(data)
+{
+ var node = new SpringyNode(this.nextNodeId++, data);
+ this.addNode(node);
+ return node;
+};
+
+Graph.prototype.newEdge = function(source, target, data)
+{
+ var edge = new SpringyEdge(this.nextEdgeId++, source, target, data);
+ this.addEdge(edge);
+ return edge;
+};
+
+
+// find the edges from node1 to node2
+Graph.prototype.getEdges = function(node1, node2)
+{
+ if (typeof(this.adjacency[node1.id]) !== 'undefined'
+ && typeof(this.adjacency[node1.id][node2.id]) !== 'undefined')
+ {
+ return this.adjacency[node1.id][node2.id];
+ }
+
+ return [];
+};
+
+// remove a node and it's associated edges from the graph
+Graph.prototype.removeNode = function(node)
+{
+ if (typeof(this.nodeSet[node.id]) !== 'undefined')
+ {
+ delete this.nodeSet[node.id];
+ }
+
+ for (var i = this.nodes.length - 1; i >= 0; i--)
+ {
+ if (this.nodes[i].id === node.id)
+ {
+ this.nodes.splice(i, 1);
+ }
+ }
+
+ var tmpEdges = this.edges.slice();
+ tmpEdges.forEach(function(e) {
+ if (e.source.id === node.id || e.target.id === node.id)
+ {
+ this.removeEdge(e);
+ }
+ }, this);
+
+ this.notify();
+};
+
+
+// remove a node and it's associated edges from the graph
+Graph.prototype.removeEdge = function(edge)
+{
+ for (var i = this.edges.length - 1; i >= 0; i--)
+ {
+ if (this.edges[i].id === edge.id)
+ {
+ this.edges.splice(i, 1);
+ }
+ }
+
+ for (var x in this.adjacency)
+ {
+ for (var y in this.adjacency[x])
+ {
+ var edges = this.adjacency[x][y];
+
+ 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);
+ }
+ }
+ }
+ }
+
+ this.notify();
+};
+
+/* Merge a list of nodes and edges into the current graph. eg.
+var o = {
+ nodes: [
+ {id: 123, data: {type: 'user', userid: 123, displayname:
'aaa'}},
+ {id: 234, data: {type: 'user', userid: 234, displayname: 'bbb'}}
+ ],
+ edges: [
+ {from: 0, to: 1, type: 'submitted_design', directed: true,
data: {weight: }}
+ ]
+}
+*/
+Graph.prototype.merge = function(data)
+{
+ var nodes = [];
+ data.nodes.forEach(function(n) {
+ nodes.push(this.addNode(new SpringyNode(n.id, n.data)));
+ }, this);
+
+ data.edges.forEach(function(e) {
+ 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 edge = this.addEdge(new SpringyEdge(id, from, to, e.data));
+ edge.data.type = e.type;
+ }, this);
+};
+
+Graph.prototype.filterNodes = function(fn)
+{
+ var tmpNodes = this.nodes.slice();
+ tmpNodes.forEach(function(n) {
+ if (!fn(n))
+ {
+ this.removeNode(n);
+ }
+ }, this);
+};
+
+Graph.prototype.filterEdges = function(fn)
+{
+ var tmpEdges = this.edges.slice();
+ tmpEdges.forEach(function(e) {
+ if (!fn(e))
+ {
+ this.removeEdge(e);
+ }
+ }, this);
+};
+
+
+Graph.prototype.addGraphListener = function(obj)
+{
+ this.eventListeners.push(obj);
+};
+
+Graph.prototype.notify = function()
+{
+ this.eventListeners.forEach(function(obj){
+ obj.graphChanged();
+ });
+};
+
+// -----------
+var Layout = {};
+Layout.ForceDirected = function(graph, stiffness, repulsion, damping)
+{
+ this.graph = graph;
+ this.stiffness = stiffness; // spring stiffness constant
+ this.repulsion = repulsion; // repulsion constant
+ this.damping = damping; // velocity damping factor
+
+ this.nodePoints = {}; // keep track of points associated with nodes
+ this.edgeSprings = {}; // keep track of springs associated with edges
+
+ this.intervalId = null;
+};
+
+Layout.ForceDirected.prototype.point = function(node)
+{
+ if (typeof(this.nodePoints[node.id]) === 'undefined')
+ {
+ var mass = typeof(node.data.mass) !== 'undefined' ?
node.data.mass : 1.0;
+ this.nodePoints[node.id] = new
Layout.ForceDirected.Point(Vector.random(), mass);
+ }
+
+ return this.nodePoints[node.id];
+};
+
+Layout.ForceDirected.prototype.spring = function(edge)
+{
+ if (typeof(this.edgeSprings[edge.id]) === 'undefined')
+ {
+ var length = typeof(edge.data.length) !== 'undefined' ?
edge.data.length : 1.0;
+
+ var existingSpring = false;
+
+ var from = this.graph.getEdges(edge.source, edge.target);
+ from.forEach(function(e){
+ if (existingSpring === false &&
typeof(this.edgeSprings[e.id]) !== 'undefined') {
+ existingSpring = this.edgeSprings[e.id];
+ }
+ }, this);
+
+ if (existingSpring !== false) {
+ 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){
+ if (existingSpring === false &&
typeof(this.edgeSprings[e.id]) !== 'undefined') {
+ existingSpring = this.edgeSprings[e.id];
+ }
+ }, this);
+
+ if (existingSpring !== false) {
+ return new
Layout.ForceDirected.Spring(existingSpring.point2, existingSpring.point1, 0.0,
0.0);
+ }
+
+ this.edgeSprings[edge.id] = new Layout.ForceDirected.Spring(
+ this.point(edge.source), this.point(edge.target),
length, this.stiffness
+ );
+ }
+
+ return this.edgeSprings[edge.id];
+};
+
+// callback should accept two arguments: SpringyNode, Point
+Layout.ForceDirected.prototype.eachNode = function(callback)
+{
+ var t = this;
+ this.graph.nodes.forEach(function(n){
+ callback.call(t, n, t.point(n));
+ });
+};
+
+// callback should accept two arguments: SpringyEdge, Spring
+Layout.ForceDirected.prototype.eachEdge = function(callback)
+{
+ var t = this;
+ this.graph.edges.forEach(function(e){
+ callback.call(t, e, t.spring(e));
+ });
+};
+
+// callback should accept one argument: Spring
+Layout.ForceDirected.prototype.eachSpring = function(callback)
+{
+ var t = this;
+ this.graph.edges.forEach(function(e){
+ callback.call(t, t.spring(e));
+ });
+};
+
+
+// Physics stuff
+Layout.ForceDirected.prototype.applyCoulombsLaw = function()
+{
+ this.eachNode(function(n1, point1) {
+ this.eachNode(function(n2, point2) {
+ if (point1 !== point2)
+ {
+ var d = point1.p.subtract(point2.p);
+ var distance = d.magnitude() + 1.0;
+ 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));
+ }
+ });
+ });
+};
+
+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();
+
+ // 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));
+ });
+};
+
+Layout.ForceDirected.prototype.attractToCentre = function()
+{
+ this.eachNode(function(node, point) {
+ var direction = point.p.multiply(-1.0);
+ point.applyForce(direction.multiply(this.repulsion / 50.0));
+ });
+};
+
+
+Layout.ForceDirected.prototype.updateVelocity = function(timestep)
+{
+ this.eachNode(function(node, point) {
+ point.v =
point.v.add(point.f.multiply(timestep)).multiply(this.damping);
+ point.f = new Vector(0,0);
+ });
+};
+
+Layout.ForceDirected.prototype.updatePosition = function(timestep)
+{
+ this.eachNode(function(node, point) {
+ point.p = point.p.add(point.v.multiply(timestep));
+ });
+};
+
+Layout.ForceDirected.prototype.totalEnergy = function(timestep)
+{
+ var energy = 0.0;
+ this.eachNode(function(node, point) {
+ var speed = point.v.magnitude();
+ energy += speed * speed;
+ });
+
+ return energy;
+};
+
+
+// start simulation
+Layout.ForceDirected.prototype.start = function(interval, render, done)
+{
+ var t = this;
+
+ if (this.intervalId !== null) {
+ return; // already running
+ }
+
+ this.intervalId = setInterval(function() {
+ t.applyCoulombsLaw();
+ t.applyHookesLaw();
+ t.attractToCentre();
+ t.updateVelocity(0.03);
+ t.updatePosition(0.03);
+
+ if (typeof(render) !== 'undefined') { render(); }
+
+ // stop simulation when energy of the system goes below a
threshold
+ if (t.totalEnergy() < 0.1)
+ {
+ t.stop();
+ if (typeof(done) !== 'undefined') { done(); }
+ }
+ }, interval);
+};
+
+// added mz
+Layout.ForceDirected.prototype.stop = function() {
+ clearInterval(this.intervalId);
+ this.intervalId = null;
+};
+
+// Find the nearest point to a particular position
+Layout.ForceDirected.prototype.nearest = function(pos)
+{
+ var min = {node: null, point: null, distance: null};
+ var t = this;
+ this.graph.nodes.forEach(function(n){
+ var point = t.point(n);
+ var distance = point.p.subtract(pos).magnitude();
+
+ if (min.distance === null || distance < min.distance)
+ {
+ min = {node: n, point: point, distance: distance};
+ }
+ });
+
+ return min;
+};
+
+// returns [bottomleft, topright]
+Layout.ForceDirected.prototype.getBoundingBox = function()
+{
+ var bottomleft = new Vector(-2,-2);
+ var topright = new Vector(2,2);
+
+ this.eachNode(function(n, point) {
+ if (point.p.x < bottomleft.x) {
+ bottomleft.x = point.p.x;
+ }
+ if (point.p.y < bottomleft.y) {
+ bottomleft.y = point.p.y;
+ }
+ if (point.p.x > topright.x) {
+ topright.x = point.p.x;
+ }
+ if (point.p.y > topright.y) {
+ topright.y = point.p.y;
+ }
+ });
+
+ var padding = topright.subtract(bottomleft).multiply(0.07); // 5%
padding
+
+ return {bottomleft: bottomleft.subtract(padding), topright:
topright.add(padding)};
+};
+
+
+// Vector
+Vector = function(x, y)
+{
+ this.x = x;
+ this.y = y;
+};
+
+Vector.random = function()
+{
+ return new Vector(10.0 * (Math.random() - 0.5), 10.0 * (Math.random() -
0.5));
+};
+
+Vector.prototype.add = function(v2)
+{
+ return new Vector(this.x + v2.x, this.y + v2.y);
+};
+
+Vector.prototype.subtract = function(v2)
+{
+ return new Vector(this.x - v2.x, this.y - v2.y);
+};
+
+Vector.prototype.multiply = function(n)
+{
+ return new Vector(this.x * n, this.y * n);
+};
+
+Vector.prototype.divide = function(n)
+{
+ return new Vector(this.x / n, this.y / n);
+};
+
+Vector.prototype.magnitude = function()
+{
+ return Math.sqrt(this.x*this.x + this.y*this.y);
+};
+
+Vector.prototype.normal = function()
+{
+ return new Vector(-this.y, this.x);
+};
+
+Vector.prototype.normalise = function()
+{
+ return this.divide(this.magnitude());
+};
+
+// Point
+Layout.ForceDirected.Point = function(position, mass)
+{
+ this.p = position; // position
+ this.m = mass; // mass
+ this.v = new Vector(0, 0); // velocity
+ this.f = new Vector(0, 0); // force
+};
+
+Layout.ForceDirected.Point.prototype.applyForce = function(force)
+{
+ this.f = this.f.add(force.divide(this.m));
+};
+
+// Spring
+Layout.ForceDirected.Spring = function(point1, point2, length, k)
+{
+ this.point1 = point1;
+ this.point2 = point2;
+ this.length = length; // spring length at rest
+ 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(interval, layout, clear, drawEdge, drawNode)
+{
+ this.interval = interval;
+ this.layout = layout;
+ this.clear = clear;
+ this.drawEdge = drawEdge;
+ this.drawNode = drawNode;
+
+ this.layout.graph.addGraphListener(this);
+}
+
+Renderer.prototype.graphChanged = function(e)
+{
+ this.start();
+};
+
+Renderer.prototype.start = function()
+{
+ var t = this;
+ this.layout.start(50, function render() {
+ t.clear();
+
+ t.layout.eachEdge(function(edge, spring) {
+ t.drawEdge(edge, spring.point1.p, spring.point2.p);
+ });
+
+ t.layout.eachNode(function(node, point) {
+ t.drawNode(node, point.p);
+ });
+ });
+};
+
+
+/**
+ * Extension by MZ
+ * Allow adding nodes and edges by string id only
+ */
+Graph.prototype.newNodeM = function(id, data) {
+ data = data || {};
+ data.label = id;
+ this.nodeCache[id] = this.newNode(data);
+
+}
+
+Graph.prototype.newEdgeM = function(sourceId, targetId, data)
+{
+ this.newEdge(this.nodeCache[sourceId], this.nodeCache[targetId], data);
+};
--
You received this message because you are subscribed to the Google Groups
"cytoscape-cvs" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/cytoscape-cvs?hl=en.