http://git-wip-us.apache.org/repos/asf/giraph/blob/8675c84a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/editor.core.js
----------------------------------------------------------------------
diff --git 
a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/editor.core.js
 
b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/editor.core.js
new file mode 100644
index 0000000..2536e3b
--- /dev/null
+++ 
b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/editor.core.js
@@ -0,0 +1,634 @@
+/*
+ * Graph Editor is based on Directed Graph Editor by rkirsling 
http://bl.ocks.org/rkirsling/5001347.
+ */
+
+/*
+ * Editor is a class that encapsulates the graph editing window
+ * @param {container, [undirected]} options - Initialize editor with these 
options.
+ * {options.container} - HTML element that contains the editor svg.
+ * {options.undirected} - Indicate whether the graph is directed/undirected.
+ * @constructor
+ */
+function Editor(options) {
+    this.container = 'body';
+    this.undirected = false;
+    // Readonly editor does not let users
+    // add new nodes/links.
+    this.readonly = false;
+    // Data for the graph nodes and edges.
+    this.defaultColor = '#FFFDDB'
+    // Useful options. Not required by the editor class itself.
+    this.errorColor = '#FF9494';
+    // Maximum number of nodes for which the graph view would be constructed 
and maintained.
+    this.graphViewNodeLimit = 2000;
+    // Graph members
+    this.nodes = [];
+    this.links = [];
+    this.messages = [];
+    this.currentZoom = { translate : [0,0], scale : 1 };
+    // Table members
+    // Current scenario (adjList) object as received from the server.
+    this.currentScenario = {};
+    // aggregators is a collecton of key-value pairs displayed in the 
top-right corner.
+    this.aggregators = {};
+    // set graph as the default view
+    this.view = Editor.ViewEnum.GRAPH;
+    // linkDistance controls the distance between two nodes in the graph.
+    this.linkDistance = 150;
+    if (options) {
+        this.container = options['container'] ? options['container'] : 
this.container;
+        this.undirected = options['undirected'] === true;
+        if (options.onOpenNode) {
+            this.onOpenNode = options.onOpenNode;
+        }
+        if (options.onOpenEdge) {
+            this.onOpenEdge = options.onOpenEdge;
+        }
+    }
+    this.setSize();
+    this.lastKeyDown = -1;
+    this.init();
+    this.buildSample();
+    return this;
+}
+
+/*
+ * Represents the two views of the editor - tabular and graph
+ */
+Editor.ViewEnum = {
+    TABLET : 'tablet',
+    GRAPH : 'graph'
+}
+
+Editor.prototype.onToggleView = function(toggleViewHandler) {
+    this.onToggleView.done = toggleViewHandler;
+}
+
+/*
+ * Build a sample graph with three nodes and two edges.
+ */
+Editor.prototype.buildSample = function() {
+    this.empty();
+    // Start with a sample graph.
+    for(var i = 0; i < 3; i++) {
+        this.addNode();
+    }
+    this.addEdge('1', '2');
+    this.addEdge('2', '3');
+    this.restartGraph();
+}
+
+/*
+ * Empties the graph by deleting all nodes and links.
+ */
+Editor.prototype.empty = function() {
+    // NOTE : Don't use this.nodes = [] to empty the array
+    // This creates a new reference and messes up this.force.nodes
+    this.nodes.length = 0;
+    this.links.length = 0;
+    this.messages.length = 0;
+    this.numNodes = 0;
+    this.restartGraph();
+}
+
+/*
+ * Initializes the SVG elements, force layout and event bindings.
+ */
+Editor.prototype.init = function() {
+    // Initializes the SVG elements.
+    this.initElements();
+    // Binds events and initializes variables used to track selected 
nodes/links.
+    this.initEvents();
+    // Line displayed when dragging an edge off a node
+    this.drag_line = this.svg.append('svg:path')
+                                 .attr('class', 'link dragline hidden')
+                                 .attr('d', 'M0,0L0,0');
+    // Handles to link and node element groups.
+    var pathContainer = this.svg.append('svg:g')
+    this.path = pathContainer.selectAll('path');
+    this.pathLabels = pathContainer.selectAll('text');
+    this.circle = this.svg.append('svg:g').selectAll('g');
+    // Initializes the force layout.
+    this.initForce();
+    this.restartGraph();
+}
+
+/* 
+ * Wrapper for restarting both graph and table. Automatically switches to table
+ * view if the number of nodes is too large.
+ */
+Editor.prototype.restart = function() {
+    // If numNodes > graphViewLimit, empty the graph and switch
+    // to table view.
+    if (this.numNodes > this.graphViewNodeLimit) {
+        this.empty();
+        if (this.view != Editor.ViewEnum.TABLET) {
+            this.toggleView();
+        }
+    }
+    this.restartGraph();
+    this.restartTable();
+}
+
+/*
+ * Updates the graph. Called internally on various events.
+ * May be called from the client after updating graph properties.
+ */
+Editor.prototype.restartGraph = function() {
+    this.setSize();
+    this.restartNodes();
+    this.restartLinks();
+    this.resizeForce();
+    this.restartAggregators();
+
+    // Set the background to light gray if editor is readonly.
+    d3.select('.editor').style('background-color', this.readonly ? '#f9f9f9' : 
'white');
+    this.svgRect.attr('fill', this.readonly ? '#f9f9f9' : 'white')
+                    .attr('width', this.width)
+                    .attr('height', this.height);
+
+    // Set the graph in motion
+    this.force.start();
+}
+
+/*
+ * Handles mousedown event.
+ * Insert a new node if Shift key is not pressed. Otherwise, drag the graph.
+ */
+Editor.prototype.mousedown = function() {
+    if (this.readonly === true) {
+        return;
+    }
+    this.svg.classed('active', true);
+    if (d3.event.shiftKey || this.mousedown_node || this.mousedown_link) {
+        return;
+    }
+    // Insert new node at point.
+    var point = d3.mouse(d3.event.target),
+        node =  this.addNode();
+    node.x = point[0];
+    node.y = point[1];
+    this.restartGraph();
+}
+
+/*
+ * Returns all the messages sent by node with the given id.
+ * Output format: {receiverId: message}
+ * @param {string} id
+ */
+Editor.prototype.getMessagesSentByNode = function(id) {
+    var messagesSent = {};
+    for (var i = 0; i < this.messages.length; i++) {
+        var messageObj = this.messages[i];
+        if (messageObj.outgoing === true && messageObj.sender.id === id) {
+            messagesSent[messageObj.receiver.id] = messageObj.message;
+        }
+    }
+    return messagesSent;
+}
+
+/*
+ * Returns all the edge values for this node's neighbor in a JSON object.
+ * Note that if an edge value is not present, still returns that neighborId 
with null/undefined value.
+ * Output format: {neighborId: edgeValue}
+ * @param {string} id
+ */
+Editor.prototype.getEdgeValuesForNode = function(id) {
+    var edgeValues = {};
+    var outgoingEdges = this.getEdgesWithSourceId(id);
+    $.each(outgoingEdges, function(i, edge) {
+        edgeValues[edge.target.id] = edge;
+    });
+    return edgeValues;
+}
+
+/*
+ * Returns all the messages received by node with the given id.
+ * Output format: {senderId: message}
+ * @param {string} id
+ */
+Editor.prototype.getMessagesReceivedByNode = function(id) {
+    var messagesReceived = {};
+
+    for (var i = 0; i < this.messages.length; i++) {
+        var messageObj = this.messages[i];
+        if (messageObj.incoming === true && messageObj.receiver.id === id) {
+            // Note: This is required because incoming messages do not have a 
sender as of now.
+            var senderId = '<i data-id="' + i + '"></i>';
+            messagesReceived[senderId] = messageObj.message;
+        }
+    }
+    return messagesReceived;
+}
+
+/*
+ * Returns the edge list.
+ * Edge list is the representation of the graph as a list of edges.
+ * An edge is represented as a vertex pair (u,v).
+ */
+Editor.prototype.getEdgeList = function() {
+    edgeList = '';
+
+    for (var i = 0; i < this.links.length; i++) {
+        var sourceId = this.links[i].source.id;
+        var targetId = this.links[i].target.id;
+
+        // Right links are source->target.
+        // Left links are target->source.
+        if (this.links[i].right) {
+            edgeList += sourceId + '\t' + targetId + '\n';
+        } else {
+            edgeList += targetId + '\t' + sourceId + '\n';
+        }
+    }
+    return edgeList;
+}
+
+/*
+ * Returns the adjacency list.
+ * Adj list is the representation of the graph as a list of nodes adjacent to
+ * each node.
+ */
+Editor.prototype.getAdjList = function() {
+    adjList = {}
+    $.each(this.nodes, (function(i, node) {
+        var id = node.id;
+        var edges = this.getEdgesWithSourceId(id);
+        adjList[id] = {adj : edges, vertexValue : node.attrs}
+    }).bind(this));
+    return adjList;
+}
+
+/*
+ * Returns the list of nodes along with their attributes.
+ */
+Editor.prototype.getNodeList  = function() {
+    nodeList = '';
+    for (var i = 0; i < this.nodes.length; i++){
+        nodeList += this.nodes[i].id + '\t' + this.nodes[i].attrs;
+        nodeList += (i != this.nodes.length - 1 ? '\n' : '');
+    }
+    return nodeList;
+}
+
+/*
+ * Handle the mousemove event.
+ * Updates the drag line if mouse is pressed at present.
+ * Ignores otherwise.
+ */
+Editor.prototype.mousemove = function() {
+    if (this.readonly) {
+        return;
+    }
+    // This indicates if the mouse is pressed at present.
+    if (!this.mousedown_node) {
+        return;
+    }
+    // Update drag line.
+    this.drag_line.attr('d', 'M' + this.mousedown_node.x + ',' +
+        this.mousedown_node.y + 'L' + d3.mouse(this.svg[0][0])[0] + ',' +
+        d3.mouse(this.svg[0][0])[1]
+    );
+    this.restartGraph();
+}
+
+/*
+ * Handles the mouseup event.
+ */
+Editor.prototype.mouseup = function() {
+    if (this.mousedown_node) {
+        // hide drag line
+        this.drag_line
+            .classed('hidden', true)
+            .style('marker-end', '');
+    }
+    this.svg.classed('active', false);
+    // Clear mouse event vars
+    this.resetMouseVars();
+}
+
+/*
+ * Handles keydown event.
+ * If Key is Shift, drags the graph using the force layout.
+ * If Key is 'L' or 'R' and link is selected, orients the link likewise.
+ * If Key is 'R' and node is selected, marks the node as reflexive.
+ * If Key is 'Delete', deletes the selected node or edge.
+ */
+Editor.prototype.keydown = function() {
+    if (this.lastKeyDown !== -1) {
+        return;
+    }
+    this.lastKeyDown = d3.event.keyCode;
+
+    // Shift key was pressed
+    if (d3.event.shiftKey) {
+        this.circle.call(this.force.drag);
+        this.svg.classed('ctrl', true);
+    }
+
+    if (!this.selected_node && !this.selected_link || this.readonly) {
+        return;
+    }
+
+    switch (d3.event.keyCode) {
+        case 46: // delete
+            if (this.selected_node) {
+                this.nodes.splice(this.nodes.indexOf(this.selected_node), 1);
+                this.spliceLinksForNode(this.selected_node);
+            } else if (this.selected_link) {
+                this.links.splice(this.links.indexOf(this.selected_link), 1);
+            }
+            this.selected_link = null;
+            this.selected_node = null;
+            this.restartGraph();
+            break;
+        case 66: // B
+            if (this.selected_link) {
+                // set link direction to both left and right
+                this.selected_link.left = true;
+                this.selected_link.right = true;
+            }
+
+            this.restartGraph();
+            break;
+        case 76: // L
+            if (this.selected_link) {
+                // set link direction to left only
+                this.selected_link.left = true;
+                this.selected_link.right = false;
+            }
+
+            this.restartGraph();
+            break;
+        case 82: // R
+            if (this.selected_node) {
+                // toggle node reflexivity
+                this.selected_node.reflexive = !this.selected_node.reflexive;
+            } else if (this.selected_link) {
+                // set link direction to right only
+                this.selected_link.left = false;
+                this.selected_link.right = true;
+            }
+
+            this.restartGraph();
+            break;
+    }
+}
+
+/*
+ * Handles the keyup event.
+ * Resets lastKeyDown to -1.
+ * Also resets the drag event binding to null if the key released was Shift.
+ */
+Editor.prototype.keyup = function() {
+    this.lastKeyDown = -1;
+
+    // Shift
+    if (d3.event.keyCode === 16) {
+        this.circle
+            .on('mousedown.drag', null)
+            .on('touchstart.drag', null);
+        this.svg.classed('ctrl', false);
+    }
+}
+
+/*
+ * Builds the graph from adj list by constructing the nodes and links arrays.
+ * @param {object} adjList - Adjacency list of the graph. attrs and msgs are 
optional.
+ * Format:
+ * {
+ *  nodeId: {
+ *            neighbors : [{ 
+ *                      neighborId: "neighborId1",
+ *                      edgeValue: "edgeValue1"
+ *                  },
+ *                  {
+ *                      neighborId: "neighborId2",
+ *                      edgeValue: "edgeValue2"
+ *                  }],
+ *            vertexValue : attrs,
+ *            outgoingMessages : {
+ *                    receiverId1: "message1",
+ *                    receiverId2: "message2",
+ *                    ...
+ *                  },
+ *            incomingMessages : [ "message1", "message2" ]
+ *            enabled : true/false
+ *          }
+ * }
+ */
+Editor.prototype.buildGraphFromAdjList = function(adjList) {
+    this.empty();
+
+    // Scan every node in adj list to build the nodes array.
+    for (var nodeId in adjList) {
+        var node = this.getNodeWithId(nodeId);
+        if (!node) {
+            node = this.addNode(nodeId);
+        }
+        var adj = adjList[nodeId]['neighbors'];
+        // For every node in the adj list of this node,
+        // add the node to this.nodes and add the edge to this.links
+        for (var i = 0; adj && i < adj.length; i++) {
+            var adjId = adj[i]['neighborId'].toString();
+            var edgeValue = adj[i]['edgeValue'];
+            var adjNode = this.getNodeWithId(adjId);
+            if (!adjNode) {
+                adjNode = this.addNode(adjId);
+            }
+            // Add the edge.
+            this.addEdge(nodeId, adjId, edgeValue);
+        }
+    }
+    this.updateGraphData(adjList);
+}
+
+/*
+ * Updates scenario properties - node attributes and messages from adj list.
+ * @param {object} scenario - scenario has the same format as adjList above,
+ * but with 'adj' ignored.
+ * **NOTE**: This method assumes the same scenario structure,
+ * only updates the node attributes and messages exchanged.
+ */
+Editor.prototype.updateGraphData = function(scenario) {
+    // Cache the scenario object. Used by tabular view.
+    this.currentScenario = scenario;
+    // Clear the messages array. Unlike other fields, messages is cleared and 
reloaded for every scenario.
+    this.messages.length = 0;
+
+    // Scan every node in adj list to build the nodes array.
+    for (var nodeId in scenario) {
+        var node = this.getNodeWithId(nodeId);
+        if (scenario[nodeId]['vertexValue']) {
+            node.attrs = scenario[nodeId]['vertexValue'];
+        }
+        if (scenario[nodeId].enabled != undefined) {
+            node.enabled = scenario[nodeId].enabled;
+        }
+
+        var outgoingMessages = scenario[nodeId]['outgoingMessages'];
+        var incomingMessages = scenario[nodeId]['incomingMessages'];
+
+        // Build this.messages
+        if (outgoingMessages) {
+            for(var receiverId in outgoingMessages) {
+                this.messages.push({ 
+                    sender: node,
+                    receiver: this.getNodeWithId(receiverId),
+                    message: outgoingMessages[receiverId],
+                    outgoing : true
+                });
+            }
+        }
+
+        if (incomingMessages) {
+            for (var i = 0; i < incomingMessages.length; i++) {
+              var incomingMessage = incomingMessages[i];
+              this.messages.push({ 
+                  // TODO: sender is not supplied by the server as of now.
+                  sender : null, 
+                  receiver: node,
+                  message: incomingMessage,
+                  incoming : true
+              });
+            }
+          }
+
+        // Update aggregators
+        // NOTE: Later vertices ovewrite value for a given key
+        var aggregators = scenario[nodeId]['aggregators'];
+        for (var key in aggregators) {
+            this.aggregators[key] = aggregators[key];
+        }
+    }
+    // Restart the graph and table to show new values.
+    this.restart();
+}
+
+/*
+ * Adds new nodes and links to the graph without changing the existing 
structure.
+ * @param {object} - scenario has the same format as above.
+ * **NOTE** - This method will add news nodes and links without modifying
+ * the existing structure. For instance, if the passed graph object does
+ * not have a link, but it already exists in the graph, it will stay.
+ */
+Editor.prototype.addToGraph = function(scenario) {
+    for (var nodeId in scenario) {
+        // If this node is not present in the graph. Add it.
+        this.addNode(nodeId);
+        var neighbors = scenario[nodeId]['neighbors'];
+        // For each neighbor, add the edge.
+        for (var i = 0 ; i < neighbors.length; i++) {
+            var neighborId = neighbors[i]['neighborId'];
+            var edgeValue = neighbors[i]['edgeValue'];
+            // Add neighbor node if it doesn't exist.
+            this.addNode(neighborId);
+            // Addes edge, or ignores if already exists.
+            this.addEdge(nodeId, neighborId, edgeValue);
+        }
+    }
+}
+
+/*
+ * Shows the preloader and hides all other elements.
+ */
+Editor.prototype.showPreloader = function() {
+    this.svg.selectAll('g').transition().style('opacity', 0);
+    this.preloader.transition().style('opacity', 1);
+}
+
+/*
+ * Hides the preloader and shows all other elements.
+ */
+Editor.prototype.hidePreloader = function() {
+    this.svg.selectAll('g').transition().style('opacity', 1);
+    this.preloader.transition().style('opacity', 0);
+    this.restartGraph();
+}
+
+/*
+ * Enables the given node. Enabled nodes are shown as opaque.
+ */
+Editor.prototype.enableNode = function(nodeId) {
+    this.getNodeWithId(nodeId).enabled = true;
+}
+
+/*
+ * Disables the given node. 
+ * Disabled nodes are shown as slightly transparent with outgoing messages 
removed.
+ */
+Editor.prototype.disableNode = function(nodeId) {
+    this.getNodeWithId(nodeId).enabled = false;
+    // Remove the outgoing Messages for this node.
+    var toSplice = this.messages.filter(function(message) {
+        return (message.outgoing === true && message.sender.id === nodeId);
+    });
+
+    toSplice.map((function(message) {
+        this.messages.splice(this.messages.indexOf(message), 1);
+    }).bind(this));
+}
+
+/*
+ * Colors the given node ids with the given color. Use this method to uncolor 
+ * all the nodes (reset to default color) by calling colorNodes([], 'random', 
true);
+ * @param {array} nodeIds - List of node ids.
+ * @param {color} color - Color of these nodes.
+ * @param {bool} [uncolorRest] - Optional parameter to reset the color of 
other nodes to default.
+ */
+Editor.prototype.colorNodes = function(nodeIds, color, uncolorRest) {
+    // Set the color property of each node in this array. restart will reflect 
changes.
+    for(var i = 0; i < nodeIds.length; i++) {
+        var node = this.getNodeWithId(nodeIds[i]);
+        if (node) {
+            node.color = color;
+        }
+    }
+    // If uncolorRest is specified
+    if (uncolorRest) {
+        for (var i = 0; i < this.nodes.length; i++) {
+            // Not in nodeIds, uncolor it.
+            if ($.inArray(this.nodes[i].id, nodeIds) === -1) {
+                this.nodes[i].color = this.defaultColor;
+            }
+        }
+    }
+    this.restartGraph();
+}
+
+/* 
+ * Toggles the two views of the editor by sliding up/down the tablet.
+ */
+Editor.prototype.toggleView = function() { 
+    if (this.view === Editor.ViewEnum.GRAPH) {
+        this.view = Editor.ViewEnum.TABLET;
+        $(this.tablet[0]).slideDown('slow');
+    } else {
+        this.view = Editor.ViewEnum.GRAPH;
+        $(this.tablet[0]).slideUp('slow');
+    }
+    // Call the handlers registered for toggleView
+    this.onToggleView.done(this.view);
+}
+
+/*
+ * Creates graph from a simple adj list of the format given below.
+ * @param {object} simpleAdjList : A simple adjacency list.
+ * Format:
+ * {
+ *     "vertexId1" : [ "neighborId1", "neighborId2" ...],
+ *     "vertexId2" : [ "neighborId1", "neighborId2" ...],
+ *     ...
+ * }
+ */
+Editor.prototype.buildGraphFromSimpleAdjList = function(simpleAdjList) {
+    var scenario = {};
+    $.each(simpleAdjList, function(vertexId, neighbors) {
+        scenario[vertexId] = {}
+        scenario[vertexId].neighbors = [];
+        $.each(neighbors, function(index, neighborId) {
+            scenario[vertexId].neighbors.push({ neighborId : neighborId });
+        });
+    });
+    this.buildGraphFromAdjList(scenario);
+}

http://git-wip-us.apache.org/repos/asf/giraph/blob/8675c84a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/editor.utils.js
----------------------------------------------------------------------
diff --git 
a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/editor.utils.js
 
b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/editor.utils.js
new file mode 100644
index 0000000..863a8d9
--- /dev/null
+++ 
b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/editor.utils.js
@@ -0,0 +1,791 @@
+/*
+ * Sets the size of the graph editing window.
+ * The graph is always centered in the container according to these dimensions.
+ */
+Editor.prototype.setSize = function() {
+    this.width = $(this.container).width();
+    this.height = $(this.container).height();
+}
+
+/*
+ * Resize the force layout. The D3 force layout controls the movement of the
+ * svg elements within the container.
+ */
+Editor.prototype.resizeForce = function() {
+    this.force.size([this.width, this.height])
+              .linkDistance(this.linkDistance)
+              .charge(-500 - (this.linkDistance - 150)*2);
+}
+
+/*
+ * Returns the detailed view of each row in the table view.
+ */
+Editor.prototype.getRowDetailsHtml = function(row) {
+    var outerContainer = $('<div />');
+    var navContainer = $('<ul />')
+        .attr('class', 'nav nav-tabs')
+        .attr('id', 'tablet-nav')
+        .appendTo(outerContainer);
+    navContainer.append($('<li class="active"><a data-toggle="tab" 
data-name="outgoingMessages">Outgoing Messages</a></li>'));
+    navContainer.append($('<li><a data-toggle="tab" 
data-name="incomingMessages">Incoming Messages</a></li>'));
+    navContainer.append($('<li><a data-toggle="tab" 
data-name="neighbors">Neighbors</a></li>'));
+
+    var dataContainer = $('<div />')
+        .attr('class', 'tablet-data-container')
+        .appendTo(outerContainer);
+
+    return {
+        'outerContainer' : outerContainer,
+        'dataContainer' : dataContainer,
+        'navContainer' : navContainer
+    };
+}
+
+Editor.prototype.initTable = function() {
+    var jqueryTableContainer = $(this.tablet[0]);
+    var jqueryTable = $('<table id="editor-tablet-table" 
class="editor-tablet-table table display">' + 
+            '<thead><tr><th></th><th>Vertex ID</th><th>Vertex 
Value</th><th>Outgoing Msgs</th>' + 
+            '<th>Incoming Msgs</th><th>Neighbors</th></tr></thead></table>');
+    jqueryTableContainer.append(jqueryTable);
+    // Define the table schema and initialize DataTable object.
+    this.dataTable = $("#editor-tablet-table").DataTable({
+        'columns' : [
+            {
+                'class' : 'tablet-details-control',
+                'orderable' : false,
+                'data' : null,
+                'defaultContent' : ''
+            },
+            { 'data' : 'vertexId' },
+            { 'data' : 'vertexValue' },
+            { 'data' : 'outgoingMessages.numOutgoingMessages' },
+            { 'data' : 'incomingMessages.numIncomingMessages' },
+            { 'data' : 'neighbors.numNeighbors'}
+        ]
+    });
+}
+
+/*
+ * Zooms the svg element with the given translate and scale factors.
+ * Use translate = [0,0] and scale = 1 for original zoom level (unzoomed).
+ */
+Editor.prototype.zoomSvg = function(translate, scale) {
+    this.currentZoom.translate = translate;
+    this.currentZoom.scale = scale;
+    this.svg.attr("transform", "translate(" + translate + ")"
+        + " scale(" + scale + ")");
+}
+
+Editor.prototype.redraw = function() {
+    this.zoomSvg(d3.event.translate, d3.event.scale);
+}
+
+/*
+ * Initializes the SVG element, along with marker and defs.
+ */
+Editor.prototype.initElements = function() {
+    // Create the tabular view and hide it for now.
+    this.tablet = d3.select(this.container)
+        .insert('div')
+        .attr('class', 'editor-tablet')
+        .style('display', 'none');
+
+    this.initTable();
+    // Creates the main SVG element and appends it to the container as the 
first child.
+    // Set the SVG class to 'editor'.
+    this.svgRoot = d3.select(this.container)
+                     .insert('svg')
+    this.zoomHolder = this.svgRoot
+                         .attr('class','editor')
+                         .attr('pointer-events', 'all')
+                         .append('svg:g');
+
+    this.svg = this.zoomHolder.append('svg:g');
+    this.svgRect = this.svg.append('svg:rect')
+
+    // Defines end arrow marker for graph links.
+    this.svg.append('svg:defs')
+                 .append('svg:marker')
+                     .attr('id', 'end-arrow')
+                     .attr('viewBox', '0 -5 10 10')
+                     .attr('refX', 6)
+                     .attr('markerWidth', 3)
+                     .attr('markerHeight', 3)
+                     .attr('orient', 'auto')
+                     .append('svg:path')
+                         .attr('d', 'M0,-5L10,0L0,5')
+                         .attr('fill', '#000');
+
+    // Defines start arrow marker for graph links.
+    this.svgRoot.append('svg:defs')
+                .append('svg:marker')
+                    .attr('id', 'start-arrow')
+                    .attr('viewBox', '0 -5 10 10')
+                    .attr('refX', 4)
+                    .attr('markerWidth', 3)
+                    .attr('markerHeight', 3)
+                    .attr('orient', 'auto')
+                    .append('svg:path')
+                        .attr('d', 'M10,-5L0,0L10,5')
+                        .attr('fill', '#000');
+    // Append the preloader
+    // Dimensions of the image are 128x128
+    var preloaderX = this.width / 2 - 64;
+    var preloaderY = this.height / 2 - 64;
+    this.preloader = this.svgRoot.append('svg:g')
+                                 .attr('transform', 'translate(' + preloaderX 
+ ',' + preloaderY + ')')
+                                 .attr('opacity', 0);
+
+    this.preloader.append('svg:image')
+                      .attr('xlink:href', 'img/preloader.gif')
+                      .attr('width', '128')
+                      .attr('height', '128');
+    this.preloader.append('svg:text')
+                      .text('Loading')
+                      .attr('x', '40')
+                      .attr('y', '128');
+    // Aggregators
+    this.aggregatorsContainer = this.svg.append('svg:g');
+    this.aggregatorsContainer.append('text')
+                       .attr('class', 'editor-aggregators-heading')
+                       .text('Aggregators');
+   // d3 selector for global key-value pairs
+   this.globs = this.aggregatorsContainer.append('text').selectAll('tspan');
+}
+
+/*
+ * Binds the mouse and key events to the appropriate methods.
+ */
+Editor.prototype.initEvents = function() {
+    // Mouse event vars - These variables are set (and reset) when the 
corresponding event occurs.
+    this.selected_node = null;
+    this.selected_link = null;
+    this.mousedown_link = null;
+    this.mousedown_node = null;
+    this.mouseup_node = null;
+
+    // Binds mouse down/up/move events on main SVG to appropriate methods.
+    // Used to create new nodes, create edges and dragging the graph.
+    this.svg.on('mousedown', this.mousedown.bind(this))
+            .on('mousemove', this.mousemove.bind(this))
+            .on('mouseup', this.mouseup.bind(this));
+
+    // Binds Key down/up events on the window to appropriate methods.
+    d3.select(window)
+          .on('keydown', this.keydown.bind(this))
+          .on('keyup', this.keyup.bind(this));
+}
+
+/*
+ * Initializes D3 force layout to update node/link location and orientation.
+ */
+Editor.prototype.initForce = function() {
+    this.force = d3.layout.force()
+                              .nodes(this.nodes)
+                              .links(this.links)
+                              .size([this.width, this.height])
+                              .linkDistance(this.linkDistance)
+                              .charge(-500 )
+                              .on('tick', this.tick.bind(this))
+}
+
+/*
+ * Reset the mouse event variables to null.
+ */
+Editor.prototype.resetMouseVars = function() {
+    this.mousedown_node = null;
+    this.mouseup_node = null;
+    this.mousedown_link = null;
+}
+
+/*
+ * Called at a fixed time interval to update the nodes and edge positions.
+ * Gives the fluid appearance to the editor.
+ */
+Editor.prototype.tick = function() {
+    // draw directed edges with proper padding from node centers
+    this.path.attr('d', function(d) {
+        var sourcePadding = getPadding(d.source);
+        var targetPadding = getPadding(d.target);
+
+        var deltaX = d.target.x - d.source.x,
+                deltaY = d.target.y - d.source.y,
+                dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
+                normX = deltaX / dist,
+                normY = deltaY / dist,
+                sourcePadding = d.left ? sourcePadding[0] : sourcePadding[1],
+                targetPadding = d.right ? targetPadding[0] : targetPadding[1],
+                sourceX = d.source.x + (sourcePadding * normX),
+                sourceY = d.source.y + (sourcePadding * normY),
+                targetX = d.target.x - (targetPadding * normX),
+                targetY = d.target.y - (targetPadding * normY);
+        return 'M' + sourceX + ',' + sourceY + 'L' + targetX + ',' + targetY;
+    });
+
+    this.circle.attr('transform', function(d) {
+        return 'translate(' + d.x + ',' + d.y + ')';
+    });
+}
+
+/*
+ * Returns the radius of the node.
+ * Radius is not fixed since nodes with longer identifiers need a bigger 
circle.
+ * @param {int} node - Node object whose radius is required.
+ */
+function getRadius(node) {
+    // Radius is detemined by multiplyiing the max of length of node ID
+    // and node value (first attribute) by a factor and adding a constant.
+    // If node value is not present, only node id length is used.
+    return 14 + Math.max(node.id.length, getAttrForDisplay(node.attrs).length) 
* 3;
+}
+
+/*
+ * Truncates the attribute value so that it fits propertly on the editor node 
+ * without exploding the circle.
+ */
+function getAttrForDisplay(attr) {
+    if (attr && attr.length > 11) {
+        return attr.slice(0, 4) + "..." + attr.slice(attr.length - 4);    
+    }
+    return attr ? attr : '';
+}
+
+/*
+ * Returns the padding of the node.
+ * Padding is used by edges as an offset from the node center.
+ * Padding is not fixed since nodes with longer identifiers need bigger circle.
+ * @param {int} node - Node object whose padding is required.
+ */
+function getPadding(node) {
+    // Offset is detemined by multiplyiing the max of length of node ID
+    // and node value (first attribute) by a factor and adding a constant.
+    // If node value is not present, only node id length is used.
+    var nodeOffset = Math.max(node.id.length, 
getAttrForDisplay(node.attrs).length) * 3;
+    return [19 + nodeOffset, 12  + nodeOffset];
+}
+
+/*
+ * Returns a new node object.
+ * @param {string} id - Identifier of the node.
+ */
+Editor.prototype.getNewNode = function(id) {
+    return {id : id, reflexive : false, attrs : null, x: Math.random(), y: 
Math.random(), enabled: true, color: this.defaultColor};
+}
+
+/* 
+ * Returns a new edge object. 
+ * @param {object} source - Object for the source node.
+ * @param {object) target - Object for the target node.
+ * @param {object} edgeVal - Any edge value object.
+ */
+Editor.prototype.getNewEdge = function(source, target, edgeValue) {
+    return {source: source, target: target, edgeValue: edgeValue};
+}
+
+/*
+ * Returns a new link (edge) object from the node IDs of the logical edge.
+ * @param {string} sourceNodeId - The ID of the source node in the logical 
edge.
+ * @param {string} targetNodeId - The ID of the target node in the logical 
edge.
+ * @param {string} [edgeValue] - Value associated with the edge. Optional 
parameter.
+ * @desc - Logical edge means, "Edge from node with ID x to node with ID y".
+ * It implicitly captures the direction. However, the link objects have
+ * the 'left' and 'right' properties to denote direction. Also, source 
strictly < target.
+ * Therefore, the source and target may not match that of the logical edge, 
but the
+ * direction will compensate for the mismatch.
+ */
+Editor.prototype.getNewLink = function(sourceNodeId, targetNodeId, edgeValue) {
+    var source, target, direction, leftValue = null, rightValue = null;
+    if (sourceNodeId < targetNodeId) {
+        source = sourceNodeId;
+        target = targetNodeId;
+        direction = 'right';
+        rightValue = edgeValue;
+    } else {
+        source = targetNodeId;
+        target = sourceNodeId;
+        direction = 'left';
+        leftValue = edgeValue;
+    }
+    // Every link has an ID - Added to the SVG element to show edge value as 
textPath
+    if (!this.maxLinkId) {
+        this.maxLinkId = 0;
+    }
+    link = {source : this.getNodeWithId(source), target : 
this.getNodeWithId(target), 
+        id : this.maxLinkId++, leftValue : leftValue, rightValue : rightValue, 
 left : false, right : false};
+    link[direction] = true;
+    return link;
+}
+
+/*
+ * Returns the logical edge(s) from a link object.
+ * @param {object} link - Link object.
+ * This method is required because a single link object may encode
+ * two edges using the left/right attributes.
+ */
+Editor.prototype.getEdges = function(link) {
+    var edges = [];
+    if (link.left || this.undirected) {
+        edges.push(this.getNewEdge(link.target, link.source, link.leftValue));
+    }
+    if (link.right || this.undirected) {
+        edges.push(this.getNewEdge(link.source, link.target, link.rightValue));
+    }
+    return edges;
+}
+
+/*
+ * Adds a new link object to the links array or updates an existing link.
+ * @param {string} sourceNodeId - Id of the source node in the logical edge.
+ * @param {string} targetNodeid - Id of the target node in the logical edge.
+ * @param {string} [edgeValue] - Value associated with the edge. Optional 
parameter.
+ */
+Editor.prototype.addEdge = function(sourceNodeId, targetNodeId, edgeValue) {
+    // console.log('Adding edge: ' + sourceNodeId + ' -> ' + targetNodeId);
+    // Get the new link object.
+    var newLink = this.getNewLink(sourceNodeId, targetNodeId, edgeValue);
+    // Check if a link with these source and target Ids already exists.
+    var existingLink = this.links.filter(function(l) {
+        return (l.source === newLink.source && l.target === newLink.target);
+    })[0];
+
+    // Add link to graph (update if exists).
+    if (existingLink) {
+        // Set the existingLink directions to true if either
+        // newLink or existingLink denote the edge.
+        existingLink.left = existingLink.left || newLink.left;
+        existingLink.right = existingLink.right || newLink.right;
+        if (edgeValue != undefined) {
+            if (sourceNodeId < targetNodeId) {
+                existingLink.rightValue = edgeValue;
+            } else {
+                existingLink.leftValue = edgeValue; 
+            }
+        }
+        return existingLink;
+    } else {
+        this.links.push(newLink);
+        return newLink;
+    }
+}
+
+/*
+ * Adds node with nodeId to the graph (or ignores if already exists).
+ * Returns the added (or already existing) node.
+ * @param [{string}] nodeId - ID of the node to add. If not provided, adds
+ * a new node with a new nodeId.
+ * TODO(vikesh): Incremental nodeIds are buggy. May cause conflict. Use unique 
identifiers.
+ */
+Editor.prototype.addNode = function(nodeId) {
+    if (!nodeId) {
+        nodeId = (this.numNodes + 1).toString();
+    }
+    var newNode = this.getNodeWithId(nodeId);
+    if (!newNode) {
+        newNode = this.getNewNode(nodeId);
+        this.nodes.push(newNode);
+        this.numNodes++;
+    }
+    return newNode;
+}
+
+/*
+ * Updates existing links and adds new links.
+ */
+Editor.prototype.restartLinks = function() {
+    // path (link) group
+    this.path = this.path.data(this.links);
+    this.pathLabels = this.pathLabels.data(this.links);
+
+    // Update existing links
+    this.path.classed('selected', (function(d) {
+        return d === this.selected_link;
+    }).bind(this))
+             .style('marker-start', (function(d) {
+                 return d.left && !this.undirected ? 'url(#start-arrow)' : '';
+             }).bind(this))
+             .style('marker-end', (function(d) {
+                 return d.right && !this.undirected ? 'url(#end-arrow)' : '';
+             }).bind(this));
+
+    // Add new links.
+    // For each link in the bound data but not in elements group, enter()
+    // selection calls everything that follows once.
+    // Note that links are stored as source, target where source < target.
+    // If the link is from source -> target, it's a 'right' link.
+    // If the link is from target -> source, it's a 'left' link.
+    // A right link has end marker at the target side.
+    // A left link has a start marker at the source side.
+    this.path.enter()
+                 .append('svg:path')
+                     .attr('class', 'link')
+                     .attr('id', function(d) { return d.id })
+                     .classed('selected', (function(d) {
+                         return d === this.selected_link;
+                     }).bind(this))
+                     .style('marker-start', (function(d) {
+                         if(d.left && !this.undirected) {
+                             return  'url(#start-arrow)';
+                         }
+                         return '';
+                     }).bind(this))
+                     .style('marker-end', (function(d) {
+                         if(d.right && !this.undirected) {
+                             return 'url(#end-arrow)';
+                         }
+                         return '';
+                     }).bind(this))
+                     .on('mousedown', (function(d) {
+                         // Select link
+                         this.mousedown_link = d;
+                         // If edge was selected with shift key, call the 
openEdge handler and return.
+                         if (d3.event.shiftKey) {
+                            this.onOpenEdge({ event: d3.event, link: d, 
editor: this });
+                            return; 
+                         }
+                         if (this.mousedown_link === this.selected_link) {
+                             this.selected_link = null;
+                         } else {
+                             this.selected_link = this.mousedown_link;
+                         }
+                         this.selected_node = null;
+                         this.restartGraph();
+                     }).bind(this));
+    // Add edge value labels for the new edges.
+    // Note that two tspans are required for 
+    // left and right links (represented by the same 'link' object)
+    var textPaths = this.pathLabels.enter()
+        .append('svg:text')
+        .append('svg:textPath')
+        .attr('xlink:href', function(d) { return '#' + d.id })
+        .attr('startOffset', '35%');
+
+    textPaths.append('tspan')
+        .attr('dy', -6)
+        .attr('data-orientation', 'right')
+    textPaths.append('tspan')
+        .attr('dy', 20)
+        .attr('x', 5)
+        .attr('data-orientation', 'left')
+
+    // Update the tspans with the edge value
+    this.pathLabels.selectAll('tspan').text(function(d) {
+        return $(this).attr('data-orientation') === 'right' 
+            ? ( d.right ? d.rightValue : null ) 
+            : ( d.left ? d.leftValue : null );
+    });
+    // Remove old links.
+    this.path.exit().remove();
+    this.pathLabels.exit().remove();
+}
+
+/*
+ * Adds new nodes to the graph and binds mouse events.
+ * Assumes that the data for this.circle is already set by the caller.
+ * Creates 'circle' elements for each new node in this.nodes
+ */
+Editor.prototype.addNodes = function() {
+    // Adds new nodes.
+    // The enter() call appends a 'g' element for each node in this.nodes.
+    // that is not present in this.circle already.
+    var g = this.circle.enter().append('svg:g');
+
+    g.attr('class', 'node-container')
+         .append('svg:circle')
+         .attr('class', 'node')
+         .attr('r', (function(d) {
+             return getRadius(d);
+         }).bind(this))
+         .style('fill', this.defaultColor)
+         .style('stroke', '#000000')
+         .classed('reflexive', function(d) { return d.reflexive; })
+         .on('mouseover', (function(d) {
+             if (!this.mousedown_node || d === this.mousedown_node) {
+                 return;
+             }
+             // Enlarge target node.
+             d3.select(d3.event.target).attr('transform', 'scale(1.1)');
+         }).bind(this))
+         .on('mouseout', (function(d) {
+             if (!this.mousedown_node || d === this.mousedown_node) {
+                 return;
+             }
+             // Unenlarge target node.
+             d3.select(d3.event.target).attr('transform', '');
+         }).bind(this))
+         .on('mousedown', (function(d) {
+             if (d3.event.shiftKey || this.readonly) {
+                 return;
+             }
+             // Select node.
+             this.mousedown_node = d;
+             if (this.mousedown_node === this.selected_node) {
+                 this.selected_node = null;
+             } else {
+                 this.selected_node = this.mousedown_node;
+             }
+             this.selected_link = null;
+             // Reposition drag line.
+             this.drag_line
+                    .style('marker-end', 'url(#end-arrow)')
+                    .classed('hidden', false)
+                    .attr('d', 'M' + this.mousedown_node.x + ',' + 
this.mousedown_node.y + 'L' + this.mousedown_node.x + ',' + 
this.mousedown_node.y);
+             this.restartGraph();
+         }).bind(this))
+         .on('mouseup', (function(d) {
+             if (!this.mousedown_node) {
+                 return;
+             }
+             this.drag_line
+                    .classed('hidden', true)
+                    .style('marker-end', '');
+
+             // Check for drag-to-self.
+             this.mouseup_node = d;
+             if (this.mouseup_node === this.mousedown_node) {
+                 this.resetMouseVars();
+                 return;
+             }
+
+             // Unenlarge target node to default size.
+             d3.select(d3.event.target).attr('transform', '');
+
+             // Add link to graph (update if exists).
+             var newLink = this.addEdge(this.mousedown_node.id, 
this.mouseup_node.id);
+             this.selected_link = newLink;
+             this.restartGraph();
+         }).bind(this))
+         .on('dblclick', (function(d) {
+             if (this.onOpenNode) {
+                 this.onOpenNode({ event: d3.event, node: d , editor: this });
+                 this.restartGraph();
+             }
+         }).bind(this));
+
+    // Show node IDs
+    g.append('svg:text')
+        .attr('x', 0)
+        .attr('y', 4)
+        .attr('class', 'tid')
+}
+
+/*
+ * Updates existing nodes and adds new nodes.
+ */
+Editor.prototype.restartNodes = function() {
+    // Set the circle group's data to this.nodes.
+    // Note that nodes are identified by id, not their index in the array.
+    this.circle = this.circle.data(this.nodes, function(d) { return d.id; });
+    // NOTE: addNodes must only be called after .data is set to the latest
+    // this.nodes. This is done at the beginning of this method.
+    this.addNodes();
+    // Update existing nodes (reflexive & selected visual states)
+    this.circle.selectAll('circle')
+        .style('fill', function(d) { return d.color; })
+        .classed('reflexive', function(d) { return d.reflexive; })
+        .classed('selected', (function(d) { return d === this.selected_node 
}).bind(this))
+        .attr('r', function(d) { return getRadius(d);  });
+    // If node is not enabled, set its opacity to 0.2    
+    this.circle.transition().style('opacity', function(d) { return d.enabled 
=== true ? 1 : 0.2; });
+    // Update node IDs
+    var el = this.circle.selectAll('text').text('');
+    el.append('tspan')
+          .text(function(d) {
+              return d.id;
+          })
+          .attr('x', 0)
+          .attr('dy', function(d) {
+              return d.attrs != null && d.attrs.trim() != '' ? '-8' : '0 ';
+          })
+          .attr('class', 'id');
+    // Node value (if present) is added/updated here
+    el.append('tspan')
+          .text(function(d) {
+              return getAttrForDisplay(d.attrs);
+          })
+          .attr('x', 0)
+          .attr('dy', function(d) {
+              return d.attrs != null && d.attrs.trim() != '' ? '18' : '0';
+          })
+          .attr('class', 'vval');
+    // remove old nodes
+    this.circle.exit().remove();
+}
+
+/* 
+ * Restarts (refreshes, just using 'restart' for consistency) the aggregators.
+ */
+Editor.prototype.restartAggregators = function() {
+    this.aggregatorsContainer.attr('transform', 'translate(' + (this.width - 
250) + ', 25)')
+    this.aggregatorsContainer.transition().style('opacity', 
Utils.count(this.aggregators) > 0 ? 1 : 0);
+    // Remove all values
+    this.globs = this.globs.data([]);
+    this.globs.exit().remove();
+    // Convert JSON to array of 2-length arrays for d3
+    var data = $.map(this.aggregators, function(value, key) { return [[key, 
value]]; });
+    // Set new values
+    this.globs = this.globs.data(data);
+    this.globs.enter().append('tspan').classed('editor-aggregators-value', 
true)
+        .attr('dy', '2.0em')
+        .attr('x', 0)
+        .text(function(d) { return "{0} -> {1}".format(d[0], d[1]); });
+}
+
+/*
+ * Restarts the table with the latest currentScenario.
+ */
+Editor.prototype.restartTable = function() { 
+    // Remove all rows of the table and add again.
+    this.dataTable.rows().remove();
+
+    // Modify the scenario object to suit dataTables format
+    for (var nodeId in this.currentScenario) {
+        var dataRow = {};
+        var scenario = this.currentScenario[nodeId];
+        dataRow.vertexId = nodeId;
+        dataRow.vertexValue = scenario.vertexValue ? scenario.vertexValue : 
'-',
+        dataRow.outgoingMessages = { 
+            numOutgoingMessages : Utils.count(scenario.outgoingMessages), 
+            data : scenario.outgoingMessages
+        },
+        dataRow.incomingMessages = { 
+            numIncomingMessages : Utils.count(scenario.incomingMessages), 
+            data : scenario.incomingMessages
+        },
+        dataRow.neighbors = {
+            numNeighbors : Utils.count(scenario.neighbors),
+            data : scenario.neighbors
+        }
+        this.dataTable.row.add(dataRow).draw();
+    }
+
+    // Bind click event for rows.
+    $('#editor-tablet-table td.tablet-details-control').click((function(event) 
{
+        var tr = $(event.target).parents('tr');
+        var row = this.dataTable.row(tr);
+        if ( row.child.isShown()) {
+            // This row is already open - close it.
+            row.child.hide();
+            tr.removeClass('shown');
+        } else {
+            // Open this row.
+            var rowData = row.data();
+            var rowHtml = this.getRowDetailsHtml(rowData);
+            var dataContainer = rowHtml.dataContainer;
+            row.child(rowHtml.outerContainer).show();
+            // Now attach events to the tabs
+            // NOTE: MUST attach events after the row.child call. 
+            $(rowHtml.navContainer).on('click', 'li a', (function(event) {
+                // Check which tab was clicked and populate data accordingly.
+                var dataContainer = rowHtml.dataContainer;
+                var tabName = $(event.target).data('name');
+                // Clear the data container
+                $(dataContainer).empty();
+                if (tabName === 'outgoingMessages') {
+                    var mainTable = $('<table><thead><th>Receiver 
ID</th><th>Outgoing Message</th></thead></table>')
+                        .attr('class', 'table')
+                        .appendTo(dataContainer)
+                    var outgoingMessages = rowData.outgoingMessages.data;
+                    for (var receiverId in outgoingMessages) {
+                        
$(mainTable).append("<tr><td>{0}</td><td>{1}</td></tr>".format(receiverId, 
outgoingMessages[receiverId]));
+                    }
+                    $(mainTable).DataTable();
+                } else if (tabName === 'incomingMessages') {
+                    var mainTable = $('<table><thead><th>Incoming 
Message</th></thead></table>')
+                        .attr('class', 'table')
+                        .appendTo(dataContainer);
+                    var incomingMessages = rowData.incomingMessages.data;
+                    for (var i = 0; i < incomingMessages.length; i++) {
+                        
$(mainTable).append("<tr><td>{0}</td></tr>".format(incomingMessages[i]));
+                    }
+                } else if (tabName === 'neighbors') {
+                    var mainTable = $('<table><thead><th>Neighbor 
ID</th><th>Edge Value</th></thead></table>')
+                        .attr('class', 'table')
+                        .appendTo(dataContainer)
+                    var neighbors = rowData.neighbors.data;
+                    console.log(neighbors);
+                    for (var i = 0 ; i < neighbors.length; i++) {
+                        $(mainTable).append("<tr><td>{0}</td><td>{1}</td></tr>"
+                            .format(neighbors[i].neighborId, 
neighbors[i].edgeValue ? neighbors[i].edgeValue : '-'));
+                    }
+                    $(mainTable).DataTable();
+                }
+            }).bind(this));
+            // Click the first tab of the navContainer - ul>li>a
+            
$(rowHtml.navContainer).children(':first').children(':first').click();
+            tr.addClass('shown');
+        }
+    }).bind(this));
+}
+
+/*
+ * Returns the index of the node with the given id in the nodes array.
+ * @param {string} id - The identifier of the node.
+ */
+Editor.prototype.getNodeIndex = function(id) {
+    return this.nodes.map(function(e) { return e.id }).indexOf(id);
+}
+
+/*
+ * Returns the node object with the given id, null if node is not present.
+ * @param {string} id - The identifier of the node.
+ */
+Editor.prototype.getNodeWithId = function(id) {
+    var index = this.getNodeIndex(id);
+    return index >= 0 ? this.nodes[index] : null;
+}
+
+/*
+ * Returns the link objeccts with the given id as the source.
+ * Note that source here implies that all these links are outgoing from this 
node.
+ * @param {string} sourceId - The identifier of the source node.
+ */
+Editor.prototype.getEdgesWithSourceId = function(sourceId) {
+   var edges = [];
+   $.each(this.links, (function(i, link) {
+       $.each(this.getEdges(link), function(index, edge) {
+           if (edge.source.id === sourceId) {
+               edges.push(edge);
+           }
+       });
+   }).bind(this));
+   return edges;
+}
+
+/*
+ * Returns true if the node with the given ID is present in the graph.
+ * @param {string} id - The identifier of the node.
+ */
+Editor.prototype.containsNode = function(id) {
+    return this.getNodeIndex(id) >= 0;
+}
+
+/*
+ * Removes the links associated with a given node.
+ * Used when a node is deleted.
+ */
+Editor.prototype.spliceLinksForNode = function(node) {
+    var toSplice = this.links.filter(function(l) {
+        return (l.source === node || l.target === node);
+    });
+
+    toSplice.map((function(l) {
+        this.links.splice(this.links.indexOf(l), 1);
+    }).bind(this));
+}
+
+/*
+ * Puts the graph in readonly state. 
+ */
+Editor.prototype.setReadonly = function(_readonly) {
+    this.readonly = _readonly;
+    if (this.readonly) {
+        // Support zooming in readonly mode.
+        this.zoomHolder.call(d3.behavior.zoom().on('zoom', 
this.redraw.bind(this)))
+        // Remove double click zoom since we display node attrs on double 
click.
+        this.zoomHolder.on('dblclick.zoom', null);
+    } else {
+        // Remove zooming in edit mode.
+        this.zoomHolder.on('.zoom', null);
+        this.zoomSvg([0,0], 1);
+    }
+}

http://git-wip-us.apache.org/repos/asf/giraph/blob/8675c84a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/slider/bootstrap-slider.js
----------------------------------------------------------------------
diff --git 
a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/slider/bootstrap-slider.js
 
b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/slider/bootstrap-slider.js
new file mode 100644
index 0000000..97e1738
--- /dev/null
+++ 
b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/slider/bootstrap-slider.js
@@ -0,0 +1,394 @@
+/* =========================================================
+ * bootstrap-slider.js v2.0.0
+ * http://www.eyecon.ro/bootstrap-slider
+ * =========================================================
+ * Copyright 2012 Stefan Petre
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================= 
+ *
+ * NOTE: We are using this code to show a slider for changing link distance
+ * between nodes, among other things. 
+ * USING A LOCAL COPY OF THIS LIBRARY. NO OFFICIAL CDN AVAILABLE.
+ *
+ */
+ 
+!function( $ ) {
+
+       var Slider = function(element, options) {
+               this.element = $(element);
+               this.picker = $('<div class="slider">'+
+                                                       '<div 
class="slider-track">'+
+                                                               '<div 
class="slider-selection"></div>'+
+                                                               '<div 
class="slider-handle"></div>'+
+                                                               '<div 
class="slider-handle"></div>'+
+                                                       '</div>'+
+                                                       '<div 
class="tooltip"><div class="tooltip-arrow"></div><div 
class="tooltip-inner"></div></div>'+
+                                               '</div>')
+                                                       
.insertBefore(this.element)
+                                                       .append(this.element);
+               this.id = this.element.data('slider-id')||options.id;
+               if (this.id) {
+                       this.picker[0].id = this.id;
+               }
+
+               if (typeof Modernizr !== 'undefined' && Modernizr.touch) {
+                       this.touchCapable = true;
+               }
+
+               var tooltip = 
this.element.data('slider-tooltip')||options.tooltip;
+
+               this.tooltip = this.picker.find('.tooltip');
+               this.tooltipInner = this.tooltip.find('div.tooltip-inner');
+
+               this.orientation = 
this.element.data('slider-orientation')||options.orientation;
+               switch(this.orientation) {
+                       case 'vertical':
+                               this.picker.addClass('slider-vertical');
+                               this.stylePos = 'top';
+                               this.mousePos = 'pageY';
+                               this.sizePos = 'offsetHeight';
+                               this.tooltip.addClass('right')[0].style.left = 
'100%';
+                               break;
+                       default:
+                               this.picker
+                                       .addClass('slider-horizontal')
+                                       .css('width', 
this.element.outerWidth());
+                               this.orientation = 'horizontal';
+                               this.stylePos = 'left';
+                               this.mousePos = 'pageX';
+                               this.sizePos = 'offsetWidth';
+                               this.tooltip.addClass('top')[0].style.top = 
-this.tooltip.outerHeight() - 14 + 'px';
+                               break;
+               }
+
+               this.min = this.element.data('slider-min')||options.min;
+               this.max = this.element.data('slider-max')||options.max;
+               this.step = this.element.data('slider-step')||options.step;
+               this.value = this.element.data('slider-value')||options.value;
+               if (this.value[1]) {
+                       this.range = true;
+               }
+
+               this.selection = 
this.element.data('slider-selection')||options.selection;
+               this.selectionEl = this.picker.find('.slider-selection');
+               if (this.selection === 'none') {
+                       this.selectionEl.addClass('hide');
+               }
+               this.selectionElStyle = this.selectionEl[0].style;
+
+
+               this.handle1 = this.picker.find('.slider-handle:first');
+               this.handle1Stype = this.handle1[0].style;
+               this.handle2 = this.picker.find('.slider-handle:last');
+               this.handle2Stype = this.handle2[0].style;
+
+               var handle = this.element.data('slider-handle')||options.handle;
+               switch(handle) {
+                       case 'round':
+                               this.handle1.addClass('round');
+                               this.handle2.addClass('round');
+                               break
+                       case 'triangle':
+                               this.handle1.addClass('triangle');
+                               this.handle2.addClass('triangle');
+                               break
+               }
+
+               if (this.range) {
+                       this.value[0] = Math.max(this.min, Math.min(this.max, 
this.value[0]));
+                       this.value[1] = Math.max(this.min, Math.min(this.max, 
this.value[1]));
+               } else {
+                       this.value = [ Math.max(this.min, Math.min(this.max, 
this.value))];
+                       this.handle2.addClass('hide');
+                       if (this.selection == 'after') {
+                               this.value[1] = this.max;
+                       } else {
+                               this.value[1] = this.min;
+                       }
+               }
+               this.diff = this.max - this.min;
+               this.percentage = [
+                       (this.value[0]-this.min)*100/this.diff,
+                       (this.value[1]-this.min)*100/this.diff,
+                       this.step*100/this.diff
+               ];
+
+               this.offset = this.picker.offset();
+               this.size = this.picker[0][this.sizePos];
+
+               this.formater = options.formater;
+
+               this.layout();
+
+               if (this.touchCapable) {
+                       // Touch: Bind touch events:
+                       this.picker.on({
+                               touchstart: $.proxy(this.mousedown, this)
+                       });
+               } else {
+                       this.picker.on({
+                               mousedown: $.proxy(this.mousedown, this)
+                       });
+               }
+
+               if (tooltip === 'show') {
+                       this.picker.on({
+                               mouseenter: $.proxy(this.showTooltip, this),
+                               mouseleave: $.proxy(this.hideTooltip, this)
+                       });
+               } else {
+                       this.tooltip.addClass('hide');
+               }
+       };
+
+       Slider.prototype = {
+               constructor: Slider,
+
+               over: false,
+               inDrag: false,
+               
+               showTooltip: function(){
+                       this.tooltip.addClass('in');
+                       //var left = Math.round(this.percent*this.width);
+                       //this.tooltip.css('left', left - 
this.tooltip.outerWidth()/2);
+                       this.over = true;
+               },
+               
+               hideTooltip: function(){
+                       if (this.inDrag === false) {
+                               this.tooltip.removeClass('in');
+                       }
+                       this.over = false;
+               },
+
+               layout: function(){
+                       this.handle1Stype[this.stylePos] = 
this.percentage[0]+'%';
+                       this.handle2Stype[this.stylePos] = 
this.percentage[1]+'%';
+                       if (this.orientation == 'vertical') {
+                               this.selectionElStyle.top = 
Math.min(this.percentage[0], this.percentage[1]) +'%';
+                               this.selectionElStyle.height = 
Math.abs(this.percentage[0] - this.percentage[1]) +'%';
+                       } else {
+                               this.selectionElStyle.left = 
Math.min(this.percentage[0], this.percentage[1]) +'%';
+                               this.selectionElStyle.width = 
Math.abs(this.percentage[0] - this.percentage[1]) +'%';
+                       }
+                       if (this.range) {
+                               this.tooltipInner.text(
+                                       this.formater(this.value[0]) + 
+                                       ' : ' + 
+                                       this.formater(this.value[1])
+                               );
+                               this.tooltip[0].style[this.stylePos] = 
this.size * (this.percentage[0] + (this.percentage[1] - 
this.percentage[0])/2)/100 - (this.orientation === 'vertical' ? 
this.tooltip.outerHeight()/2 : this.tooltip.outerWidth()/2) +'px';
+                       } else {
+                               this.tooltipInner.text(
+                                       this.formater(this.value[0])
+                               );
+                               this.tooltip[0].style[this.stylePos] = 
this.size * this.percentage[0]/100 - (this.orientation === 'vertical' ? 
this.tooltip.outerHeight()/2 : this.tooltip.outerWidth()/2) +'px';
+                       }
+               },
+
+               mousedown: function(ev) {
+
+                       // Touch: Get the original event:
+                       if (this.touchCapable && ev.type === 'touchstart') {
+                               ev = ev.originalEvent;
+                       }
+
+                       this.offset = this.picker.offset();
+                       this.size = this.picker[0][this.sizePos];
+
+                       var percentage = this.getPercentage(ev);
+
+                       if (this.range) {
+                               var diff1 = Math.abs(this.percentage[0] - 
percentage);
+                               var diff2 = Math.abs(this.percentage[1] - 
percentage);
+                               this.dragged = (diff1 < diff2) ? 0 : 1;
+                       } else {
+                               this.dragged = 0;
+                       }
+
+                       this.percentage[this.dragged] = percentage;
+                       this.layout();
+
+                       if (this.touchCapable) {
+                               // Touch: Bind touch events:
+                               $(document).on({
+                                       touchmove: $.proxy(this.mousemove, 
this),
+                                       touchend: $.proxy(this.mouseup, this)
+                               });
+                       } else {
+                               $(document).on({
+                                       mousemove: $.proxy(this.mousemove, 
this),
+                                       mouseup: $.proxy(this.mouseup, this)
+                               });
+                       }
+
+                       this.inDrag = true;
+                       var val = this.calculateValue();
+                       this.element.trigger({
+                                       type: 'slideStart',
+                                       value: val
+                               }).trigger({
+                                       type: 'slide',
+                                       value: val
+                               });
+                       return false;
+               },
+
+               mousemove: function(ev) {
+                       
+                       // Touch: Get the original event:
+                       if (this.touchCapable && ev.type === 'touchmove') {
+                               ev = ev.originalEvent;
+                       }
+
+                       var percentage = this.getPercentage(ev);
+                       if (this.range) {
+                               if (this.dragged === 0 && this.percentage[1] < 
percentage) {
+                                       this.percentage[0] = this.percentage[1];
+                                       this.dragged = 1;
+                               } else if (this.dragged === 1 && 
this.percentage[0] > percentage) {
+                                       this.percentage[1] = this.percentage[0];
+                                       this.dragged = 0;
+                               }
+                       }
+                       this.percentage[this.dragged] = percentage;
+                       this.layout();
+                       var val = this.calculateValue();
+                       this.element
+                               .trigger({
+                                       type: 'slide',
+                                       value: val
+                               })
+                               .data('value', val)
+                               .prop('value', val);
+                       return false;
+               },
+
+               mouseup: function(ev) {
+                       if (this.touchCapable) {
+                               // Touch: Bind touch events:
+                               $(document).off({
+                                       touchmove: this.mousemove,
+                                       touchend: this.mouseup
+                               });
+                       } else {
+                               $(document).off({
+                                       mousemove: this.mousemove,
+                                       mouseup: this.mouseup
+                               });
+                       }
+
+                       this.inDrag = false;
+                       if (this.over == false) {
+                               this.hideTooltip();
+                       }
+                       this.element;
+                       var val = this.calculateValue();
+                       this.element
+                               .trigger({
+                                       type: 'slideStop',
+                                       value: val
+                               })
+                               .data('value', val)
+                               .prop('value', val);
+                       return false;
+               },
+
+               calculateValue: function() {
+                       var val;
+                       if (this.range) {
+                               val = [
+                                       (this.min + Math.round((this.diff * 
this.percentage[0]/100)/this.step)*this.step),
+                                       (this.min + Math.round((this.diff * 
this.percentage[1]/100)/this.step)*this.step)
+                               ];
+                               this.value = val;
+                       } else {
+                               val = (this.min + Math.round((this.diff * 
this.percentage[0]/100)/this.step)*this.step);
+                               this.value = [val, this.value[1]];
+                       }
+                       return val;
+               },
+
+               getPercentage: function(ev) {
+                       if (this.touchCapable) {
+                               ev = ev.touches[0];
+                       }
+                       var percentage = (ev[this.mousePos] - 
this.offset[this.stylePos])*100/this.size;
+                       percentage = 
Math.round(percentage/this.percentage[2])*this.percentage[2];
+                       return Math.max(0, Math.min(100, percentage));
+               },
+
+               getValue: function() {
+                       if (this.range) {
+                               return this.value;
+                       }
+                       return this.value[0];
+               },
+
+               setValue: function(val) {
+                       this.value = val;
+
+                       if (this.range) {
+                               this.value[0] = Math.max(this.min, 
Math.min(this.max, this.value[0]));
+                               this.value[1] = Math.max(this.min, 
Math.min(this.max, this.value[1]));
+                       } else {
+                               this.value = [ Math.max(this.min, 
Math.min(this.max, this.value))];
+                               this.handle2.addClass('hide');
+                               if (this.selection == 'after') {
+                                       this.value[1] = this.max;
+                               } else {
+                                       this.value[1] = this.min;
+                               }
+                       }
+                       this.diff = this.max - this.min;
+                       this.percentage = [
+                               (this.value[0]-this.min)*100/this.diff,
+                               (this.value[1]-this.min)*100/this.diff,
+                               this.step*100/this.diff
+                       ];
+                       this.layout();
+               }
+       };
+
+       $.fn.slider = function ( option, val ) {
+               return this.each(function () {
+                       var $this = $(this),
+                               data = $this.data('slider'),
+                               options = typeof option === 'object' && option;
+                       if (!data)  {
+                               $this.data('slider', (data = new Slider(this, 
$.extend({}, $.fn.slider.defaults,options))));
+                       }
+                       if (typeof option == 'string') {
+                               data[option](val);
+                       }
+               })
+       };
+
+       $.fn.slider.defaults = {
+               min: 0,
+               max: 10,
+               step: 1,
+               orientation: 'horizontal',
+               value: 5,
+               selection: 'before',
+               tooltip: 'show',
+               handle: 'round',
+               formater: function(value) {
+                       return value;
+               }
+       };
+
+       $.fn.slider.Constructor = Slider;
+
+}( window.jQuery );

http://git-wip-us.apache.org/repos/asf/giraph/blob/8675c84a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/utils.js
----------------------------------------------------------------------
diff --git 
a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/utils.js 
b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/utils.js
new file mode 100644
index 0000000..a0b9167
--- /dev/null
+++ 
b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/utils.js
@@ -0,0 +1,256 @@
+/*
+ * Utility functions used in other JS files. 
+ * Parts of this file are borrowed from others. A comment is placed on top in 
such cases.
+ */
+function Utils() {}
+
+/*
+ * Counts the number of keys of a JSON object.
+ */
+Utils.count = function(obj) {
+   var count=0;
+   for(var prop in obj) {
+      if (obj.hasOwnProperty(prop)) {
+         ++count;
+      }
+   }
+   return count;
+}
+
+/*
+ * Format feature for JS strings. 
+ * Example - "Hello {0}, {1}".format("World", "Graph")
+ * = Hello World, Graph
+ */
+if (!String.prototype.format) {
+  String.prototype.format = function() {
+    var args = arguments;
+    return this.replace(/{(\d+)}/g, function(match, number) { 
+      return typeof args[number] != 'undefined'
+        ? args[number]
+        : match
+      ;
+    });
+  };
+}
+
+/*! 
+ *jQuery Ajax Retry - v0.2.4 - 2013-08-16
+ * https://github.com/johnkpaul/jquery-ajax-retry
+ * Copyright (c) 2013 John Paul; Licensed MIT 
+ *
+ * NOTE: We are using this code to retry AJAX calls to the debugger server.
+ */
+(function($) {
+  // enhance all ajax requests with our retry API
+  $.ajaxPrefilter(function(options, originalOptions, jqXHR){
+    jqXHR.retry = function(opts){
+      if(opts.timeout){
+        this.timeout = opts.timeout;
+      }
+      if (opts.statusCodes) {
+        this.statusCodes = opts.statusCodes;
+      }
+      if (opts.retryCallback) {
+        this.retryCallback = opts.retryCallback;
+      }
+      return this.pipe(null, pipeFailRetry(this, opts));
+    };
+  });
+
+  // Generates a fail pipe function that will retry `jqXHR` `times` more times.
+  function pipeFailRetry(jqXHR, opts){
+    var times = opts.times;
+    var timeout = opts.timeout;
+    var retryCallback = opts.retryCallback;
+
+    // takes failure data as input, returns a new deferred
+    return function(input, status, msg){
+      var ajaxOptions = this;
+      var output = new $.Deferred();
+
+      // whenever we do make this request, pipe its output to our deferred
+      function nextRequest() {
+        $.ajax(ajaxOptions)
+          .retry({times : times-1, timeout : timeout, retryCallback : 
retryCallback})
+          .pipe(output.resolve, output.reject);
+      }
+
+      if (times > 1 && (!jqXHR.statusCodes || $.inArray(input.status, 
jqXHR.statusCodes) > -1)) {
+        if (retryCallback) {
+          retryCallback(times - 1);
+        }
+        // time to make that next request...
+        if(jqXHR.timeout !== undefined){
+          setTimeout(nextRequest, jqXHR.timeout);
+        } else {
+          nextRequest();
+        }
+      } else {
+        // no times left, reject our deferred with the current arguments
+        output.rejectWith(this, arguments);
+      }
+      return output;
+    };
+  }
+}(jQuery));
+
+/* 
+ * Select all text in a given container.
+ * @param elementId : ID of the element with the text.
+ */
+function selectText(elementId) {
+    var doc = document
+        , text = doc.getElementById(elementId)
+        , range, selection
+    ;    
+    if (doc.body.createTextRange) { //ms
+        range = doc.body.createTextRange();
+        range.moveToElementText(text);
+        range.select();
+    } else if (window.getSelection) { //all others
+        selection = window.getSelection();        
+        range = doc.createRange();
+        range.selectNodeContents(text);
+        selection.removeAllRanges();
+        selection.addRange(range);
+    }
+}
+
+/*
+ * Hook to force a file download given the contents and the file name.
+ */
+Utils.downloadFile = function(contents, fileName) {
+    var pom = document.createElement('a');
+    pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + 
encodeURIComponent(contents));
+    pom.setAttribute('download', fileName);
+    pom.click();
+}
+
+/* 
+ * Utility method to fetch vertex sceario from server.
+ */
+Utils.fetchVertexTest = function(debuggerServerRoot, jobId, superstepId, 
vertexId, traceType) {
+    var url = debuggerServerRoot + '/test/vertex';
+    var params = { 
+        jobId : jobId,
+        superstepId : superstepId,
+        vertexId : vertexId,
+        traceType : traceType
+    };
+    return $.ajax({
+        url : url, 
+        data : params,
+        dataFilter : function(data) {
+            return {
+                code: data,
+                url : "{0}?{1}".format(url, $.param(params))
+            };
+        }
+    });
+}
+
+/* 
+ * Utility method to fetch master sceario from server.
+ */
+Utils.fetchMasterTest = function(debuggerServerRoot, jobId, superstepId) {
+    var url = debuggerServerRoot + '/test/master';
+    var params = {
+            jobId: jobId,
+            superstepId : superstepId
+    };
+    return $.ajax({
+        url : url,
+        data : params,
+        dataFilter : function(data) {
+            return {
+                code: data,
+                url : "{0}?{1}".format(url, $.param(params))
+            };
+        }
+    });
+}
+
+/*
+ * Utility method to fetch the test graph for an adjacency list.
+ */
+Utils.fetchTestGraph = function(debuggerServerRoot, adjList) {
+    var url = debuggerServerRoot + '/test/graph';
+    var params = { adjList : adjList };
+    return $.ajax({
+        url : debuggerServerRoot + '/test/graph',
+        data : params, 
+        dataFilter : function(data) {
+            return {
+                code: data,
+                url : "{0}?{1}".format(url, $.param(params))
+            };
+        }
+    });
+}
+
+/*
+ * Converts the adjList object returned by editor to a string representation 
+ *
+ */
+Utils.getAdjListStr = function(editorAdjList) {
+    adjList = '';
+    $.each(editorAdjList, function(vertexId, obj) {
+        adjList += vertexId + '\t';
+        $.each(obj.adj, function(i, edge) {
+            adjList += edge.target.id + '\t';
+        });
+        // Remove the last tab
+        adjList = adjList.slice(0, -1);
+        adjList += '\n';
+    });
+    // Remove the last newline
+    return adjList.slice(0, -1);
+}
+
+/*
+ * Converts the adjList object returned by editor to a string representation
+ * used by the Test Graph debugger server.
+ */
+Utils.getAdjListStrForTestGraph = function(editorAdjList) {
+    adjList = '';
+    $.each(editorAdjList, function(vertexId, obj) {
+        adjList += "{0}{1} ".format(vertexId, obj.vertexValue ? ":" + 
obj.vertexValue : "");
+        $.each(obj.adj, function(i, edge) {
+            var edgeValue = edge.edgeValue;
+            adjList += "{0}{1} ".format(edge.target.id, edgeValue ? ":" + 
edgeValue : "");
+        });
+        // Remove the last whitespace
+        adjList = adjList.slice(0, -1);
+        adjList += '\n';
+    });
+    // Remove the last newline
+    return adjList.slice(0, -1);
+}
+
+/*
+ * Creates and returns a submit button with an OK icon.
+ */
+Utils.getBtnSubmitSm = function() {
+    return $('<button />')
+        .attr('type', 'button')
+        .addClass('btn btn-primary btn-sm editable-submit')
+        .html('<i class="glyphicon glyphicon-ok"></i>')
+}
+
+/*
+ * Creates and returns a cancel button with REMOVE icon.
+ */
+Utils.getBtnCancelSm = function() {
+    return $('<button />')
+        .attr('type', 'button')
+        .addClass('btn btn-default btn-sm editable-cancel')
+        .html('<i class="glyphicon glyphicon-remove"></i>')
+}
+
+/*
+ * Returns the jQuery selector for element ID.
+ */
+Utils.getSelectorForId = function(id) {
+    return '#' + id;
+}

http://git-wip-us.apache.org/repos/asf/giraph/blob/8675c84a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/utils.sampleGraphs.js
----------------------------------------------------------------------
diff --git 
a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/utils.sampleGraphs.js
 
b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/utils.sampleGraphs.js
new file mode 100644
index 0000000..9f4f87d
--- /dev/null
+++ 
b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/utils.sampleGraphs.js
@@ -0,0 +1,25 @@
+/*
+ * Map of sample graphs and their corresponding init handlers.
+ * Format is "<human readable graph type>" : function() { }
+ * Attach the handler of the same name to Utils.SampleGraphs
+ */
+Utils.sampleGraphs = {
+    "Line Graph" : function(numNodes) {
+        var simpleAdjList = { 0 : {} };
+        for (var i = 0; i < numNodes - 1; i++) {
+            simpleAdjList[i] = [i+1];
+        }
+        return simpleAdjList;
+    },
+    "Cycle" : function(numNodes) {
+        var simpleAdjList = Utils.sampleGraphs["Line Graph"](numNodes);
+        if (numNodes > 1) {
+            simpleAdjList[numNodes-1] = [0];
+        }
+        return simpleAdjList;
+    },
+    "Vertex Clique" : function(numNodes) {},
+    "Tailed Cycle Graph" : function(numNodes) {},
+    "Star Graph" : function(numNodes) {},
+    "Disconnected Graphs" : function(numNodes) {}
+}

Reply via email to