diff --git a/html/admin/js/graphViewer/graph/edgeShaper.js b/html/admin/js/graphViewer/graph/edgeShaper.js
index 53daeede91..1bd0a48fd6 100644
--- a/html/admin/js/graphViewer/graph/edgeShaper.js
+++ b/html/admin/js/graphViewer/graph/edgeShaper.js
@@ -50,7 +50,8 @@ function EdgeShaper(parent, flags, idfunc) {
edges = [],
toplevelSVG,
visibleLabels = true,
-
+ followEdge = {},
+ followEdgeG,
idFunction = function(d) {
return d.source._id + "-" + d.target._id;
},
@@ -319,6 +320,8 @@ function EdgeShaper(parent, flags, idfunc) {
idFunction = idfunc;
}
+ followEdgeG = toplevelSVG.append("g");
+
/////////////////////////////////////////////////////////
/// Public functions
@@ -351,6 +354,24 @@ function EdgeShaper(parent, flags, idfunc) {
}
shapeEdges();
};
+
+ self.addAnEdgeFollowingTheCursor = function(x, y) {
+ followEdge = followEdgeG.append("line");
+ followEdge.attr("stroke", "black")
+ .attr("id", "connectionLine")
+ .attr("x1", x)
+ .attr("y1", y)
+ .attr("x2", x)
+ .attr("y2", y);
+ return function(x, y) {
+ followEdge.attr("x2", x).attr("y2", y);
+ };
+ };
+
+ self.removeCursorFollowingEdge = function() {
+ followEdge.remove();
+ followEdge = {};
+ };
}
EdgeShaper.shapes = Object.freeze({
diff --git a/html/admin/js/graphViewer/graph/eventDispatcher.js b/html/admin/js/graphViewer/graph/eventDispatcher.js
index e011efc277..818320008d 100644
--- a/html/admin/js/graphViewer/graph/eventDispatcher.js
+++ b/html/admin/js/graphViewer/graph/eventDispatcher.js
@@ -1,5 +1,5 @@
/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */
-/*global _, $*/
+/*global _, $, window*/
/*global EventLibrary*/
////////////////////////////////////////////////////////////////////////////////
/// @brief Graph functionality
@@ -80,11 +80,14 @@ function EventDispatcher(nodeShaper, edgeShaper, config) {
self.events.STARTCREATEEDGE = function(callback) {
return function(node) {
+ var e = d3.event || window.event;
edgeStart = node;
didInsert = false;
if (callback !== undefined) {
- callback();
+ callback(node, e);
}
+ // Necessary to omit dragging of the graph
+ e.stopPropagation();
};
};
diff --git a/html/admin/js/graphViewer/jasmine_test/specEdgeShaper/edgeShaperSpec.js b/html/admin/js/graphViewer/jasmine_test/specEdgeShaper/edgeShaperSpec.js
index e058f8e6be..2b01b81aec 100644
--- a/html/admin/js/graphViewer/jasmine_test/specEdgeShaper/edgeShaperSpec.js
+++ b/html/admin/js/graphViewer/jasmine_test/specEdgeShaper/edgeShaperSpec.js
@@ -42,6 +42,7 @@
beforeEach(function () {
svg = document.createElement("svg");
+ svg.id = "svg";
document.body.appendChild(svg);
});
@@ -274,6 +275,55 @@
expect($("#1-5 line").attr("x2")).toEqual("28.284271247461902");
});
+ it('should be able to draw an edge that follows the cursor', function() {
+ var line,
+ jqLine,
+ cursorX,
+ cursorY,
+ nodeX = 15,
+ nodeY = 20,
+ shaper = new EdgeShaper(d3.select("svg")),
+ moveCB = shaper.addAnEdgeFollowingTheCursor(nodeX, nodeY);
+
+ cursorX = 20;
+ cursorY = 30;
+ moveCB(cursorX, cursorY);
+
+ expect($("#connectionLine").length).toEqual(1);
+
+ jqLine = $("#connectionLine");
+ line = document.getElementById("connectionLine");
+ expect(line.tagName.toLowerCase()).toEqual("line");
+ expect(jqLine.attr("x1")).toEqual(String(nodeX));
+ expect(jqLine.attr("y1")).toEqual(String(nodeY));
+
+ expect(jqLine.attr("x2")).toEqual(String(cursorX));
+ expect(jqLine.attr("y2")).toEqual(String(cursorY));
+
+ cursorX = 45;
+ cursorY = 12;
+ moveCB(cursorX, cursorY);
+ expect(jqLine.attr("x2")).toEqual(String(cursorX));
+ expect(jqLine.attr("y2")).toEqual(String(cursorY));
+ });
+
+ it('should be able to remove the cursor-following edge on demand', function() {
+ var line,
+ cursorX,
+ cursorY,
+ nodeX = 15,
+ nodeY = 20,
+ shaper = new EdgeShaper(d3.select("svg")),
+ moveCB;
+
+ moveCB = shaper.addAnEdgeFollowingTheCursor(nodeX, nodeY);
+ cursorX = 20;
+ cursorY = 30;
+ moveCB(cursorX, cursorY);
+ shaper.removeCursorFollowingEdge();
+ expect($("#connectionLine").length).toEqual(0);
+ });
+
describe('testing for colours', function() {
it('should have a default colouring of no colour flag is given', function() {
diff --git a/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherUISpec.js b/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherUISpec.js
index 20a9e31a6a..f10ad66e0d 100644
--- a/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherUISpec.js
+++ b/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherUISpec.js
@@ -61,17 +61,21 @@
beforeEach(function () {
nodes = [{
_id: 1,
- _rev: 1,
- _key: 1,
+ x: 3,
+ y: 4,
_data: {
_id: 1,
+ _rev: 1,
+ _key: 1,
name: "Alice"
}
},{
_id: 2,
- _rev: 2,
- _key: 2,
+ x: 1,
+ y: 2,
_data: {
+ _rev: 2,
+ _key: 2,
_id: 2
}
}];
@@ -122,6 +126,7 @@
};
svg = document.createElement("svg");
+ svg.id = "svg";
document.body.appendChild(svg);
nodeShaper = new NodeShaper(d3.select("svg"));
edgeShaper = new EdgeShaper(d3.select("svg"));
@@ -265,7 +270,7 @@
helper.simulateMouseEvent("click", "control_event_node_edit_submit");
expect(adapter.patchNode).toHaveBeenCalledWith(
nodes[0],
- { _id: "1",
+ {
name: "Bob"
},
jasmine.any(Function));
@@ -286,9 +291,6 @@
expect(adapter.patchEdge).toHaveBeenCalledWith(
edges[0],
{
- _id: "12",
- _rev: "12",
- _key: "12",
_from: "1",
_to: "2",
label: "newLabel"
@@ -373,41 +375,95 @@
});
});
- it('should be able to add a connect control to the list', function() {
- runs(function() {
+ describe('the connect control', function() {
+
+ it('should be added to the list', function() {
+ runs(function() {
+ dispatcherUI.addControlConnect();
+
+ expect($("#control_event_list #control_event_connect").length).toEqual(1);
+
+ helper.simulateMouseEvent("click", "control_event_connect");
+
+ expect(nodeShaper.changeTo).toHaveBeenCalledWith({
+ actions: {
+ reset: true,
+ mousedown: jasmine.any(Function),
+ mouseup: jasmine.any(Function)
+ }
+ });
+
+ expect(edgeShaper.changeTo).toHaveBeenCalledWith({
+ actions: {
+ reset: true
+ }
+ });
+
+ expect(mousePointerbox.className).toEqual("mousepointer icon-resize-horizontal");
+
+ helper.simulateMouseEvent("mousedown", "2");
+
+ helper.simulateMouseEvent("mouseup", "1");
+
+ expect(adapter.createEdge).toHaveBeenCalledWith(
+ {source: nodes[1], target: nodes[0]},
+ jasmine.any(Function)
+ );
+
+ });
+ });
+
+ it('should draw a line from startNode following the cursor', function() {
+ var line,
+ cursorX,
+ cursorY;
+
+ spyOn(edgeShaper, "addAnEdgeFollowingTheCursor");
+
dispatcherUI.addControlConnect();
-
- expect($("#control_event_list #control_event_connect").length).toEqual(1);
-
helper.simulateMouseEvent("click", "control_event_connect");
-
- expect(nodeShaper.changeTo).toHaveBeenCalledWith({
- actions: {
- reset: true,
- mousedown: jasmine.any(Function),
- mouseup: jasmine.any(Function)
- }
- });
-
- expect(edgeShaper.changeTo).toHaveBeenCalledWith({
- actions: {
- reset: true
- }
- });
-
- expect(mousePointerbox.className).toEqual("mousepointer icon-resize-horizontal");
-
helper.simulateMouseEvent("mousedown", "2");
- helper.simulateMouseEvent("mouseup", "1");
-
- expect(adapter.createEdge).toHaveBeenCalledWith(
- {source: nodes[1], target: nodes[0]},
- jasmine.any(Function)
+ expect(edgeShaper.addAnEdgeFollowingTheCursor).toHaveBeenCalledWith(
+ 0, 0
);
+ });
+
+ it('the cursor-line should follow the cursor on mousemove over svg', function() {
+ dispatcherUI.addControlConnect();
+ helper.simulateMouseEvent("click", "control_event_connect");
+ helper.simulateMouseEvent("mousedown", "2");
- });
+ helper.simulateMouseMoveEvent("svg", 40, 50);
+
+ var line = $("#connectionLine");
+ expect(line.attr("x1")).toEqual(String(nodes[1].x));
+ expect(line.attr("y1")).toEqual(String(nodes[1].y));
+ expect(line.attr("x2")).toEqual("40");
+ expect(line.attr("y2")).toEqual("50");
+ });
+
+ it('the cursor-line should disappear on mouseup on svg', function() {
+ spyOn(edgeShaper, "removeCursorFollowingEdge");
+ dispatcherUI.addControlConnect();
+ helper.simulateMouseEvent("click", "control_event_connect");
+ helper.simulateMouseEvent("mousedown", "2");
+ helper.simulateMouseEvent("mouseup", "1-2");
+ expect(edgeShaper.removeCursorFollowingEdge).toHaveBeenCalled();
+ });
+
+ it('the cursor-line should disappear on mouseup on svg', function() {
+ spyOn(edgeShaper, "removeCursorFollowingEdge");
+ dispatcherUI.addControlConnect();
+ helper.simulateMouseEvent("click", "control_event_connect");
+ helper.simulateMouseEvent("mousedown", "2");
+ helper.simulateMouseEvent("mouseup", "1");
+ expect(edgeShaper.removeCursorFollowingEdge).toHaveBeenCalled();
+ });
+
});
+
+
it('should be able to add all controls to the list', function () {
dispatcherUI.addAll();
diff --git a/html/admin/js/graphViewer/ui/eventDispatcherControls.js b/html/admin/js/graphViewer/ui/eventDispatcherControls.js
index 45e43ee77c..a272432ed8 100644
--- a/html/admin/js/graphViewer/ui/eventDispatcherControls.js
+++ b/html/admin/js/graphViewer/ui/eventDispatcherControls.js
@@ -111,6 +111,13 @@ function EventDispatcherControls(list, cursorIconBox, nodeShaper, edgeShaper, di
return res;
},
+ getCursorPositionInSVG = function (ev) {
+ var pos = getCursorPosition(ev);
+ pos.x -= $('svg').offset().left;
+ pos.y -= $('svg').offset().top;
+ return pos;
+ },
+
moveCursorBox = function(ev) {
var pos = getCursorPosition(ev);
pos.x += 7;
@@ -217,14 +224,25 @@ function EventDispatcherControls(list, cursorIconBox, nodeShaper, edgeShaper, di
callback = function() {
setCursorIcon(icon);
rebindNodes({
- mousedown: dispatcher.events.STARTCREATEEDGE(),
+ mousedown: dispatcher.events.STARTCREATEEDGE(function(startNode, ev) {
+ var pos = getCursorPositionInSVG(ev);
+ var moveCB = edgeShaper.addAnEdgeFollowingTheCursor(pos.x, pos.y);
+ dispatcher.bind("svg", "mousemove", function(ev) {
+ var pos = getCursorPositionInSVG(ev);
+ moveCB(pos.x, pos.y);
+ });
+ }),
mouseup: dispatcher.events.FINISHCREATEEDGE(function(edge){
+ edgeShaper.removeCursorFollowingEdge();
+ dispatcher.bind("svg", "mousemove", function(){});
})
});
rebindEdges();
rebindSVG({
- mouseup: dispatcher.events.CANCELCREATEEDGE(),
- mouseout: dispatcher.events.CANCELCREATEEDGE()
+ mouseup: function() {
+ dispatcher.events.CANCELCREATEEDGE();
+ edgeShaper.removeCursorFollowingEdge();
+ }
});
};
createIcon(icon, "connect", callback);