diff --git a/Jakefile.js b/Jakefile.js index a4795eab..a4a9aa54 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -82,6 +82,7 @@ task('build', {async: true}, function () { './src/graph/Popup.js', './src/graph/Groups.js', './src/graph/Images.js', + './src/graph/manipulationMixin.js', './src/graph/SectorsMixin.js', './src/graph/ClusterMixin.js', './src/graph/SelectionMixin.js', diff --git a/dist/UI_icons/addNodeIcon.png b/dist/UI_icons/addNodeIcon.png new file mode 100644 index 00000000..6fa30613 Binary files /dev/null and b/dist/UI_icons/addNodeIcon.png differ diff --git a/dist/UI_icons/backIcon.png b/dist/UI_icons/backIcon.png new file mode 100644 index 00000000..e2f99126 Binary files /dev/null and b/dist/UI_icons/backIcon.png differ diff --git a/dist/UI_icons/connectIcon.png b/dist/UI_icons/connectIcon.png new file mode 100644 index 00000000..4164da1f Binary files /dev/null and b/dist/UI_icons/connectIcon.png differ diff --git a/dist/UI_icons/deleteIcon.png b/dist/UI_icons/deleteIcon.png new file mode 100644 index 00000000..54025647 Binary files /dev/null and b/dist/UI_icons/deleteIcon.png differ diff --git a/dist/vis.js b/dist/vis.js index 5c4efb78..5dc6d8e6 100644 --- a/dist/vis.js +++ b/dist/vis.js @@ -8956,8 +8956,8 @@ Node.prototype.setProperties = function(properties, constants) { } } - this.xFixed = this.xFixed || (properties.x !== undefined); - this.yFixed = this.yFixed || (properties.y !== undefined); + this.xFixed = this.xFixed || (properties.x !== undefined && properties.fixed); + this.yFixed = this.yFixed || (properties.y !== undefined && properties.fixed); this.radiusFixed = this.radiusFixed || (properties.radius !== undefined); if (this.shape == 'image') { @@ -10604,6 +10604,134 @@ Images.prototype.load = function(url) { return img; }; +/** + * Created by Alex on 2/4/14. + */ + +var manipulationMixin = { + + _createManipulatorBar : function() { + while (this.manipulationDiv.hasChildNodes()) { + this.manipulationDiv.removeChild(this.manipulationDiv.firstChild); + } + // add the icons to the manipulator div + this.manipulationDiv.innerHTML = "" + + "Add Node" + + "
" + + "Connect Node" + + "" + + "Delete selected"; + + // bind the icons + var addButton = document.getElementById("manipulate-addNode"); + addButton.onclick = this._createAddToolbar.bind(this); + var connectButton = document.getElementById("manipulate-connectNode"); + connectButton.onclick = this._createConnectToolbar.bind(this); + var deleteButton = document.getElementById("manipulate-delete"); + deleteButton.onclick = this._deleteSelected.bind(this); + }, + + _createAddToolbar : function() { + while (this.manipulationDiv.hasChildNodes()) { + this.manipulationDiv.removeChild(this.manipulationDiv.firstChild); + } + + this.manipulationDiv.innerHTML = "" + + "Back" + + "" + + "Click in an empty space to place a new node"; + + // bind the icon + var backButton = document.getElementById("manipulate-back"); + backButton.onclick = this._createManipulatorBar.bind(this); + + + var me = this; + events.addListener(me, 'select', me._addNode.bind(me)); + }, + + + _createConnectToolbar : function() { + while (this.manipulationDiv.hasChildNodes()) { + this.manipulationDiv.removeChild(this.manipulationDiv.firstChild); + } + var message = "hello"; + if (!this._selectionIsEmpty()) { + message = "Select the node you want to connect to other nodes"; + } + + this.manipulationDiv.innerHTML = "" + + "Back" + + "" + + ""+message+""; + + // bind the icon + var backButton = document.getElementById("manipulate-back"); + backButton.onclick = this._createManipulatorBar.bind(this); + + var self = this; + events.addListener(self, 'select', function(params) {alert(self.selectForConnect)}); + }, + + _continueConnect : function() { + if (this._clusterInSelection()) { + this._unselectAll(); + this._createConnectToolbar("Select the node you want to connect (Clusters are not allowed)."); + return true; + } + else if (!this._selectionIsEmpty()) { + this._connectNodes(); + return true; + } + else { + var manipulatorLabel = document.getElementById['manipolatorLabel']; + manipulatorLabel + return false; + } + }, + + + /** + * Adds a node on the specified location + * + * @param {Object} pointer + */ + _addNode : function(pointer) { + console.log("HERE",this) + if (this._selectionIsEmpty()) { + var positionObject = this._pointerToPositionObject(pointer); + this.nodesData.add({id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",fixed:false}); + this.moving = true; + this.start(); + } + }, + + _connectNodes : function() { + console.log(this.selectionObj) + }, + + + /** + * delete everything in the selection + * + * @private + */ + _deleteSelected : function() { + if (!this._clusterInSelection()) { + var selectedNodes = this.getSelectedNodes(); + var selectedEdges = this.getSelectedEdges(); + this._removeEdges(selectedEdges); + this._removeNodes(selectedNodes); + this._redraw(); + } + else { + alert("Clusters cannot be deleted.") + } + } + + + +} /** * Creation of the SectorMixin var. * @@ -12413,6 +12541,19 @@ var SelectionMixin = { return true; }, + _clusterInSelection : function() { + for(var objectId in this.selectionObj) { + if(this.selectionObj.hasOwnProperty(objectId)) { + if (this.selectionObj[objectId] instanceof Node) { + if (this.selectionObj[objectId].clusterSize > 1) { + return true; + } + } + } + } + return false; + }, + /** * select the edges connected to the node that is being selected * @@ -12583,9 +12724,7 @@ var SelectionMixin = { */ getSelection : function() { var nodeIds = this.getSelectedNodes(); - var edgeIds = this.getSelectedEdges(); - return {nodes:nodeIds, edges:edgeIds}; }, @@ -12625,15 +12764,6 @@ var SelectionMixin = { return idArray }, - /** - * - * retrieve the currently selected nodes as objects - * @return {Objects} selection An array with the ids of the - * selected nodes. - */ - getSelectionObjects : function() { - return this.selectionObj; - }, /** * select zero or more nodes @@ -13048,21 +13178,31 @@ function Graph (container, data, options) { this.yIncrement = 0; this.zoomIncrement = 0; + // create a frame and canvas this._create(); // load the sector system. (mandatory, fully integrated with Graph) this._loadSectorSystem(); - // apply options - this.setOptions(options); - // load the cluster system. (mandatory, even when not using the cluster system, there are function calls to it) this._loadClusterSystem(); // load the selection system. (mandatory, required by Graph) this._loadSelectionSystem(); + // load the data manipulation system + this._loadManipulationSystem(); + + // apply options + this.setOptions(options); + + + + + + + // other vars var graph = this; this.freezeSimulation = false;// freeze the simulation @@ -13457,6 +13597,7 @@ Graph.prototype._create = function () { this.frame.className = 'graph-frame'; this.frame.style.position = 'relative'; this.frame.style.overflow = 'hidden'; + this.frame.style.zIndex = "1"; // create the graph canvas (HTML canvas element) this.frame.canvas = document.createElement( 'canvas' ); @@ -13492,6 +13633,7 @@ Graph.prototype._create = function () { // add the frame to the container element this.containerElement.appendChild(this.frame); + }; @@ -13527,14 +13669,6 @@ Graph.prototype._createKeyBinds = function() { this.mousetrap.bind("pagedown",this._zoomOut.bind(me),"keydown"); this.mousetrap.bind("pagedown",this._stopZoom.bind(me), "keyup"); } - /* - this.mousetrap.bind("=",this.decreaseClusterLevel.bind(me)); - this.mousetrap.bind("-",this.increaseClusterLevel.bind(me)); - this.mousetrap.bind("s",this.singleStep.bind(me)); - this.mousetrap.bind("h",this.updateClustersDefault.bind(me)); - this.mousetrap.bind("c",this._collapseSector.bind(me)); - this.mousetrap.bind("f",this.toggleFreeze.bind(me)); - */ } /** @@ -13580,7 +13714,7 @@ Graph.prototype._onDragStart = function () { drag.nodeId = node.id; // select the clicked node if not yet selected if (!node.isSelected()) { - this._selectNode(node,false); + this._selectObject(node,false); } // create an array with the selected nodes and their original location and status @@ -13682,6 +13816,7 @@ Graph.prototype._onDragEnd = function () { Graph.prototype._onTap = function (event) { var pointer = this._getPointer(event.gesture.touches[0]); this._handleTap(pointer); + }; @@ -14032,6 +14167,8 @@ Graph.prototype.setSize = function(width, height) { this.frame.canvas.width = this.frame.canvas.clientWidth; this.frame.canvas.height = this.frame.canvas.clientHeight; + this.manipulationDiv.style.width = this.frame.canvas.clientWidth; + if (this.constants.navigationUI.enabled == true) { this._relocateUI(); } @@ -14096,7 +14233,7 @@ Graph.prototype._addNodes = function(ids) { var node = new Node(data, this.images, this.groups, this.constants); this.nodes[id] = node; // note: this may replace an existing node - if (!node.isFixed()) { + if (!node.isFixed() && this.createNodeOnClick != true) { // TODO: position new nodes in a smarter way! var radius = this.constants.edges.length * 2; var count = ids.length; @@ -14112,6 +14249,7 @@ Graph.prototype._addNodes = function(ids) { this._updateNodeIndexList(); this._reconnectEdges(); this._updateValueRange(this.nodes); + this.updateLabels(); }; /** @@ -14376,7 +14514,7 @@ Graph.prototype._redraw = function() { this._doInAllSectors("_drawAllSectorNodes",ctx); this._doInAllSectors("_drawEdges",ctx); - this._doInAllSectors("_drawNodes",ctx); + this._doInAllSectors("_drawNodes",ctx,true); // restore original scaling and translation ctx.restore(); @@ -14867,9 +15005,9 @@ Graph.prototype.start = function() { } }; - - - +/** + * Debug function, does one step of the graph + */ Graph.prototype.singleStep = function() { if (this.moving) { this._initializeForceCalculation(); @@ -14958,6 +15096,30 @@ Graph.prototype._loadSelectionSystem = function() { } + +/** + * Mixin the navigationUI (User Interface) system and initialize the parameters required + * + * @private + */ +Graph.prototype._loadManipulationSystem = function() { + // load the manipulator HTML elements. All styling done in css. + this.manipulationDiv = document.createElement('div'); + this.manipulationDiv.className = 'graph-manipulationDiv'; + this.containerElement.insertBefore(this.manipulationDiv, this.frame); + + + // load the manipulation functions + for (var mixinFunction in manipulationMixin) { + if (manipulationMixin.hasOwnProperty(mixinFunction)) { + Graph.prototype[mixinFunction] = manipulationMixin[mixinFunction]; + } + } + + this._createManipulatorBar(); + +} + /** * Mixin the navigationUI (User Interface) system and initialize the parameters required * diff --git a/examples/graph/20_UI_example.html b/examples/graph/20_UI_example.html index f3bdc458..89e620a6 100644 --- a/examples/graph/20_UI_example.html +++ b/examples/graph/20_UI_example.html @@ -31,6 +31,89 @@ div.table_description { width:100px; } + div.graph-manipulationDiv { + border-width:0px; + border-bottom: 1px; + border-style:solid; + border-color: #d6d9d8; + background: #ffffff; /* Old browsers */ + background: -moz-linear-gradient(top, #ffffff 0%, #f3f3f3 50%, #ededed 51%, #ffffff 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(50%,#f3f3f3), color-stop(51%,#ededed), color-stop(100%,#ffffff)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #ffffff 0%,#f3f3f3 50%,#ededed 51%,#ffffff 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, #ffffff 0%,#f3f3f3 50%,#ededed 51%,#ffffff 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(top, #ffffff 0%,#f3f3f3 50%,#ededed 51%,#ffffff 100%); /* IE10+ */ + background: linear-gradient(to bottom, #ffffff 0%,#f3f3f3 50%,#ededed 51%,#ffffff 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ffffff',GradientType=0 ); /* IE6-9 */ + width: 600px; + height:30px; + z-index:10; + position:absolute; + } + + span.manipulationUI { + -moz-border-radius: 15px; + border-radius: 15px; + display:inline-block; + background-position: 4px 0px; + background-repeat:no-repeat; + height:24px; + margin: -14px 0px 0px 10px; + vertical-align:middle; + cursor: pointer; + padding: 0px 8px 0px 8px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + + span.manipulationUI:hover { + box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.20); + } + + span.manipulationUI:active { + box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.50); + } + + span.manipulationUI.back { + background-image: url("../../dist/UI_icons/backIcon.png"); + } + + span.manipulationUI.none:hover { + box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0); + cursor: default; + } + span.manipulationUI.none:active { + box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0); + } + + span.manipulationUI.add { + background-image: url("../../dist/UI_icons/addNodeIcon.png"); + } + + span.manipulationUI.connect { + background-image: url("../../dist/UI_icons/connectIcon.png"); + } + + span.manipulationUI.delete { + background-image: url("../../dist/UI_icons/deleteIcon.png"); + } + /* top right bottom left */ + span.manipulationLabel { + margin: 0px 0px 0px 25px; + line-height: 25px; + } + + div.seperatorLine { + display:inline-block; + width:1px; + height:20px; + background-color: #bdbdbd; + margin: 5px 7px 0px 15px; + + } diff --git a/src/graph/Graph.js b/src/graph/Graph.js index e36e15d8..1f0c1ee0 100644 --- a/src/graph/Graph.js +++ b/src/graph/Graph.js @@ -110,21 +110,31 @@ function Graph (container, data, options) { this.yIncrement = 0; this.zoomIncrement = 0; + // create a frame and canvas this._create(); // load the sector system. (mandatory, fully integrated with Graph) this._loadSectorSystem(); - // apply options - this.setOptions(options); - // load the cluster system. (mandatory, even when not using the cluster system, there are function calls to it) this._loadClusterSystem(); // load the selection system. (mandatory, required by Graph) this._loadSelectionSystem(); + // load the data manipulation system + this._loadManipulationSystem(); + + // apply options + this.setOptions(options); + + + + + + + // other vars var graph = this; this.freezeSimulation = false;// freeze the simulation @@ -519,6 +529,7 @@ Graph.prototype._create = function () { this.frame.className = 'graph-frame'; this.frame.style.position = 'relative'; this.frame.style.overflow = 'hidden'; + this.frame.style.zIndex = "1"; // create the graph canvas (HTML canvas element) this.frame.canvas = document.createElement( 'canvas' ); @@ -554,6 +565,7 @@ Graph.prototype._create = function () { // add the frame to the container element this.containerElement.appendChild(this.frame); + }; @@ -589,14 +601,6 @@ Graph.prototype._createKeyBinds = function() { this.mousetrap.bind("pagedown",this._zoomOut.bind(me),"keydown"); this.mousetrap.bind("pagedown",this._stopZoom.bind(me), "keyup"); } - /* - this.mousetrap.bind("=",this.decreaseClusterLevel.bind(me)); - this.mousetrap.bind("-",this.increaseClusterLevel.bind(me)); - this.mousetrap.bind("s",this.singleStep.bind(me)); - this.mousetrap.bind("h",this.updateClustersDefault.bind(me)); - this.mousetrap.bind("c",this._collapseSector.bind(me)); - this.mousetrap.bind("f",this.toggleFreeze.bind(me)); - */ } /** @@ -642,7 +646,7 @@ Graph.prototype._onDragStart = function () { drag.nodeId = node.id; // select the clicked node if not yet selected if (!node.isSelected()) { - this._selectNode(node,false); + this._selectObject(node,false); } // create an array with the selected nodes and their original location and status @@ -744,6 +748,7 @@ Graph.prototype._onDragEnd = function () { Graph.prototype._onTap = function (event) { var pointer = this._getPointer(event.gesture.touches[0]); this._handleTap(pointer); + }; @@ -1094,6 +1099,8 @@ Graph.prototype.setSize = function(width, height) { this.frame.canvas.width = this.frame.canvas.clientWidth; this.frame.canvas.height = this.frame.canvas.clientHeight; + this.manipulationDiv.style.width = this.frame.canvas.clientWidth; + if (this.constants.navigationUI.enabled == true) { this._relocateUI(); } @@ -1158,7 +1165,7 @@ Graph.prototype._addNodes = function(ids) { var node = new Node(data, this.images, this.groups, this.constants); this.nodes[id] = node; // note: this may replace an existing node - if (!node.isFixed()) { + if (!node.isFixed() && this.createNodeOnClick != true) { // TODO: position new nodes in a smarter way! var radius = this.constants.edges.length * 2; var count = ids.length; @@ -1174,6 +1181,7 @@ Graph.prototype._addNodes = function(ids) { this._updateNodeIndexList(); this._reconnectEdges(); this._updateValueRange(this.nodes); + this.updateLabels(); }; /** @@ -1438,7 +1446,7 @@ Graph.prototype._redraw = function() { this._doInAllSectors("_drawAllSectorNodes",ctx); this._doInAllSectors("_drawEdges",ctx); - this._doInAllSectors("_drawNodes",ctx); + this._doInAllSectors("_drawNodes",ctx,true); // restore original scaling and translation ctx.restore(); @@ -1929,9 +1937,9 @@ Graph.prototype.start = function() { } }; - - - +/** + * Debug function, does one step of the graph + */ Graph.prototype.singleStep = function() { if (this.moving) { this._initializeForceCalculation(); @@ -2020,6 +2028,30 @@ Graph.prototype._loadSelectionSystem = function() { } + +/** + * Mixin the navigationUI (User Interface) system and initialize the parameters required + * + * @private + */ +Graph.prototype._loadManipulationSystem = function() { + // load the manipulator HTML elements. All styling done in css. + this.manipulationDiv = document.createElement('div'); + this.manipulationDiv.className = 'graph-manipulationDiv'; + this.containerElement.insertBefore(this.manipulationDiv, this.frame); + + + // load the manipulation functions + for (var mixinFunction in manipulationMixin) { + if (manipulationMixin.hasOwnProperty(mixinFunction)) { + Graph.prototype[mixinFunction] = manipulationMixin[mixinFunction]; + } + } + + this._createManipulatorBar(); + +} + /** * Mixin the navigationUI (User Interface) system and initialize the parameters required * diff --git a/src/graph/Node.js b/src/graph/Node.js index 3aea901d..924be40f 100644 --- a/src/graph/Node.js +++ b/src/graph/Node.js @@ -188,8 +188,8 @@ Node.prototype.setProperties = function(properties, constants) { } } - this.xFixed = this.xFixed || (properties.x !== undefined); - this.yFixed = this.yFixed || (properties.y !== undefined); + this.xFixed = this.xFixed || (properties.x !== undefined && properties.fixed); + this.yFixed = this.yFixed || (properties.y !== undefined && properties.fixed); this.radiusFixed = this.radiusFixed || (properties.radius !== undefined); if (this.shape == 'image') { diff --git a/src/graph/SelectionMixin.js b/src/graph/SelectionMixin.js index 4b6bebd5..42b58d56 100644 --- a/src/graph/SelectionMixin.js +++ b/src/graph/SelectionMixin.js @@ -233,6 +233,19 @@ var SelectionMixin = { return true; }, + _clusterInSelection : function() { + for(var objectId in this.selectionObj) { + if(this.selectionObj.hasOwnProperty(objectId)) { + if (this.selectionObj[objectId] instanceof Node) { + if (this.selectionObj[objectId].clusterSize > 1) { + return true; + } + } + } + } + return false; + }, + /** * select the edges connected to the node that is being selected * @@ -403,9 +416,7 @@ var SelectionMixin = { */ getSelection : function() { var nodeIds = this.getSelectedNodes(); - var edgeIds = this.getSelectedEdges(); - return {nodes:nodeIds, edges:edgeIds}; }, @@ -445,15 +456,6 @@ var SelectionMixin = { return idArray }, - /** - * - * retrieve the currently selected nodes as objects - * @return {Objects} selection An array with the ids of the - * selected nodes. - */ - getSelectionObjects : function() { - return this.selectionObj; - }, /** * select zero or more nodes diff --git a/src/graph/manipulationMixin.js b/src/graph/manipulationMixin.js new file mode 100644 index 00000000..9ba8f20f --- /dev/null +++ b/src/graph/manipulationMixin.js @@ -0,0 +1,128 @@ +/** + * Created by Alex on 2/4/14. + */ + +var manipulationMixin = { + + _createManipulatorBar : function() { + while (this.manipulationDiv.hasChildNodes()) { + this.manipulationDiv.removeChild(this.manipulationDiv.firstChild); + } + // add the icons to the manipulator div + this.manipulationDiv.innerHTML = "" + + "Add Node" + + "" + + "Connect Node" + + "" + + "Delete selected"; + + // bind the icons + var addButton = document.getElementById("manipulate-addNode"); + addButton.onclick = this._createAddToolbar.bind(this); + var connectButton = document.getElementById("manipulate-connectNode"); + connectButton.onclick = this._createConnectToolbar.bind(this); + var deleteButton = document.getElementById("manipulate-delete"); + deleteButton.onclick = this._deleteSelected.bind(this); + }, + + _createAddToolbar : function() { + while (this.manipulationDiv.hasChildNodes()) { + this.manipulationDiv.removeChild(this.manipulationDiv.firstChild); + } + + this.manipulationDiv.innerHTML = "" + + "Back" + + "" + + "Click in an empty space to place a new node"; + + // bind the icon + var backButton = document.getElementById("manipulate-back"); + backButton.onclick = this._createManipulatorBar.bind(this); + + + var me = this; + events.addListener(me, 'select', me._addNode.bind(me)); + }, + + + _createConnectToolbar : function() { + while (this.manipulationDiv.hasChildNodes()) { + this.manipulationDiv.removeChild(this.manipulationDiv.firstChild); + } + var message = "hello"; + if (!this._selectionIsEmpty()) { + message = "Select the node you want to connect to other nodes"; + } + + this.manipulationDiv.innerHTML = "" + + "Back" + + "" + + ""+message+""; + + // bind the icon + var backButton = document.getElementById("manipulate-back"); + backButton.onclick = this._createManipulatorBar.bind(this); + + var self = this; + events.addListener(self, 'select', function(params) {alert(self.selectForConnect)}); + }, + + _continueConnect : function() { + if (this._clusterInSelection()) { + this._unselectAll(); + this._createConnectToolbar("Select the node you want to connect (Clusters are not allowed)."); + return true; + } + else if (!this._selectionIsEmpty()) { + this._connectNodes(); + return true; + } + else { + var manipulatorLabel = document.getElementById['manipolatorLabel']; + manipulatorLabel + return false; + } + }, + + + /** + * Adds a node on the specified location + * + * @param {Object} pointer + */ + _addNode : function(pointer) { + console.log("HERE",this) + if (this._selectionIsEmpty()) { + var positionObject = this._pointerToPositionObject(pointer); + this.nodesData.add({id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",fixed:false}); + this.moving = true; + this.start(); + } + }, + + _connectNodes : function() { + console.log(this.selectionObj) + }, + + + /** + * delete everything in the selection + * + * @private + */ + _deleteSelected : function() { + if (!this._clusterInSelection()) { + var selectedNodes = this.getSelectedNodes(); + var selectedEdges = this.getSelectedEdges(); + this._removeEdges(selectedEdges); + this._removeNodes(selectedNodes); + this._redraw(); + } + else { + alert("Clusters cannot be deleted.") + } + } + + + +} \ No newline at end of file