merge alex_dev with developcss_transitions
| @ -1,2 +1,7 @@ | |||
| .idea | |||
| node_modules | |||
| .project | |||
| .settings/.jsdtscope | |||
| .settings/org.eclipse.wst.jsdt.ui.superType.container | |||
| .settings/org.eclipse.wst.jsdt.ui.superType.name | |||
| npm-debug.log | |||
| @ -0,0 +1,102 @@ | |||
| <!doctype html> | |||
| <html> | |||
| <head> | |||
| <title>Graph | Really Random nodes</title> | |||
| <style type="text/css"> | |||
| body { | |||
| font: 10pt sans; | |||
| } | |||
| #mygraph { | |||
| width: 600px; | |||
| height: 600px; | |||
| border: 1px solid lightgray; | |||
| } | |||
| </style> | |||
| <script type="text/javascript" src="../../dist/vis.js"></script> | |||
| <script type="text/javascript"> | |||
| var nodes = null; | |||
| var edges = null; | |||
| var graph = null; | |||
| function draw() { | |||
| nodes = []; | |||
| edges = []; | |||
| // randomly create some nodes and edges | |||
| var nodeCount = parseInt(document.getElementById('nodeCount').value); | |||
| for (var i = 0; i < nodeCount; i++) { | |||
| nodes.push({ | |||
| id: i, | |||
| label: String(i) | |||
| }); | |||
| } | |||
| for (var i = 0; i < nodeCount; i++) { | |||
| var from = i; | |||
| var to = i; | |||
| to = i; | |||
| while (to == i) { | |||
| to = Math.floor(Math.random() * (nodeCount)); | |||
| } | |||
| edges.push({ | |||
| from: from, | |||
| to: to | |||
| }); | |||
| } | |||
| // create a graph | |||
| var clusteringOn = document.getElementById('clustering').checked; | |||
| var container = document.getElementById('mygraph'); | |||
| var data = { | |||
| nodes: nodes, | |||
| edges: edges | |||
| }; | |||
| var options = { | |||
| edges: { | |||
| length: 80 | |||
| }, | |||
| clustering: { | |||
| enabled: clusteringOn | |||
| }, | |||
| stabilize: false | |||
| }; | |||
| graph = new vis.Graph(container, data, options); | |||
| // add event listeners | |||
| vis.events.addListener(graph, 'select', function(params) { | |||
| document.getElementById('selection').innerHTML = | |||
| 'Selection: ' + graph.getSelection(); | |||
| }); | |||
| } | |||
| </script> | |||
| </head> | |||
| <body onload="draw();"> | |||
| <h2>Clustering - Fully random graph</h2> | |||
| <div style="width:700px; font-size:14px;"> | |||
| This example shows a fully randomly generated set of nodes and connected edges. | |||
| By clicking the checkbox you can turn clustering on and off. If you increase the number of nodes to | |||
| a value higher than 100, automatic clustering is used before the initial draw (assuming the checkbox is checked). | |||
| <br /> | |||
| <br /> | |||
| Clustering is done automatically when zooming out. When zooming in over the cluster, the cluster pops open. When the cluster is very big, a special instance | |||
| will be created and the cluster contents will only be simulated in there. Double click will also open a cluster. | |||
| <br /> | |||
| <br /> | |||
| Try values of 500 and 5000 with and without clustering. All thresholds can be changed to suit your dataset. | |||
| </div> | |||
| <br /> | |||
| <form onsubmit="draw(); return false;"> | |||
| <label for="nodeCount">Number of nodes:</label> | |||
| <input id="nodeCount" type="text" value="50" style="width: 50px;"> | |||
| <label for="clustering">Enable Clustering:</label> | |||
| <input id="clustering" type="checkbox" onChange="draw()" checked="true"> | |||
| <input type="submit" value="Go"> | |||
| </form> | |||
| <br> | |||
| <div id="mygraph"></div> | |||
| <p id="selection"></p> | |||
| </body> | |||
| </html> | |||
| @ -0,0 +1,142 @@ | |||
| <!doctype html> | |||
| <html> | |||
| <head> | |||
| <title>Graph | Random nodes</title> | |||
| <style type="text/css"> | |||
| body { | |||
| font: 10pt sans; | |||
| } | |||
| #mygraph { | |||
| width: 600px; | |||
| height: 600px; | |||
| border: 1px solid lightgray; | |||
| } | |||
| </style> | |||
| <script type="text/javascript" src="../../dist/vis.js"></script> | |||
| <script type="text/javascript"> | |||
| var nodes = null; | |||
| var edges = null; | |||
| var graph = null; | |||
| function draw() { | |||
| nodes = []; | |||
| edges = []; | |||
| var connectionCount = []; | |||
| // randomly create some nodes and edges | |||
| var nodeCount = document.getElementById('nodeCount').value; | |||
| for (var i = 0; i < nodeCount; i++) { | |||
| nodes.push({ | |||
| id: i, | |||
| label: String(i) | |||
| }); | |||
| connectionCount[i] = 0; | |||
| // create edges in a scale-free-graph way | |||
| if (i == 1) { | |||
| var from = i; | |||
| var to = 0; | |||
| edges.push({ | |||
| from: from, | |||
| to: to | |||
| }); | |||
| connectionCount[from]++; | |||
| connectionCount[to]++; | |||
| } | |||
| else if (i > 1) { | |||
| var conn = edges.length * 2; | |||
| var rand = Math.floor(Math.random() * conn); | |||
| var cum = 0; | |||
| var j = 0; | |||
| while (j < connectionCount.length && cum < rand) { | |||
| cum += connectionCount[j]; | |||
| j++; | |||
| } | |||
| var from = i; | |||
| var to = j; | |||
| edges.push({ | |||
| from: from, | |||
| to: to | |||
| }); | |||
| connectionCount[from]++; | |||
| connectionCount[to]++; | |||
| } | |||
| } | |||
| // create a graph | |||
| var clusteringOn = document.getElementById('clustering').checked; | |||
| var clusterEdgeThreshold = parseInt(document.getElementById('clusterEdgeThreshold').value); | |||
| var container = document.getElementById('mygraph'); | |||
| var data = { | |||
| nodes: nodes, | |||
| edges: edges | |||
| }; | |||
| /* | |||
| var options = { | |||
| nodes: { | |||
| shape: 'circle' | |||
| }, | |||
| edges: { | |||
| length: 50 | |||
| }, | |||
| stabilize: false | |||
| }; | |||
| */ | |||
| var options = { | |||
| edges: { | |||
| length: 50 | |||
| }, | |||
| clustering: { | |||
| enabled: clusteringOn, | |||
| clusterEdgeThreshold: clusterEdgeThreshold | |||
| }, | |||
| stabilize: false | |||
| }; | |||
| graph = new vis.Graph(container, data, options); | |||
| // add event listeners | |||
| vis.events.addListener(graph, 'select', function(params) { | |||
| document.getElementById('selection').innerHTML = | |||
| 'Selection: ' + graph.getSelection(); | |||
| }); | |||
| } | |||
| </script> | |||
| </head> | |||
| <body onload="draw();"> | |||
| <h2>Clustering - Scale-Free-Graph</h2> | |||
| <div style="width:700px; font-size:14px;"> | |||
| This example shows therandomly generated <b>scale-free-graph</b> set of nodes and connected edges from example 2. | |||
| By clicking the checkbox you can turn clustering on and off. If you increase the number of nodes to | |||
| a value higher than 100, automatic clustering is used before the initial draw (assuming the checkbox is checked). | |||
| <br /> | |||
| <br /> | |||
| Clustering is done automatically when zooming out. When zooming in over the cluster, the cluster pops open. When the cluster is very big, a special instance | |||
| will be created and the cluster contents will only be simulated in there. Double click will also open a cluster. | |||
| <br /> | |||
| <br /> | |||
| Try values of 500 and 5000 with and without clustering. All thresholds can be changed to suit your dataset. | |||
| Experiment with the clusterEdgeThreshold, which increases the formation of clusters when zoomed out (assuming the checkbox is checked). | |||
| </div> | |||
| <br /> | |||
| <form onsubmit="draw(); return false;"> | |||
| <label for="nodeCount">Number of nodes:</label> | |||
| <input id="nodeCount" type="text" value="125" style="width: 50px;"> | |||
| <label for="clustering">Enable Clustering:</label> | |||
| <input id="clustering" type="checkbox" onChange="draw()" checked="true"> | |||
| <label for="clusterEdgeThreshold">clusterEdgeThreshold:</label> | |||
| <input id="clusterEdgeThreshold" type="text" value="20" style="width: 50px;"> | |||
| <input type="submit" value="Go"> | |||
| </form> | |||
| <br> | |||
| <div id="mygraph"></div> | |||
| <p id="selection"></p> | |||
| </body> | |||
| </html> | |||
| @ -0,0 +1,186 @@ | |||
| <!doctype html> | |||
| <html> | |||
| <head> | |||
| <title>Graph | Random nodes</title> | |||
| <style type="text/css"> | |||
| body { | |||
| font: 10pt sans; | |||
| } | |||
| #mygraph { | |||
| width: 600px; | |||
| height: 600px; | |||
| border: 1px solid lightgray; | |||
| } | |||
| table.legend_table { | |||
| font-size: 11px; | |||
| border-width:1px; | |||
| border-color:#d3d3d3; | |||
| border-style:solid; | |||
| } | |||
| table.legend_table,td { | |||
| border-width:1px; | |||
| border-color:#d3d3d3; | |||
| border-style:solid; | |||
| padding: 2px; | |||
| } | |||
| div.table_content { | |||
| width:80px; | |||
| text-align:center; | |||
| } | |||
| div.table_description { | |||
| width:100px; | |||
| } | |||
| </style> | |||
| <script type="text/javascript" src="../../dist/vis.js"></script> | |||
| <script type="text/javascript"> | |||
| var nodes = null; | |||
| var edges = null; | |||
| var graph = null; | |||
| function draw() { | |||
| nodes = []; | |||
| edges = []; | |||
| var connectionCount = []; | |||
| // randomly create some nodes and edges | |||
| var nodeCount = document.getElementById('nodeCount').value; | |||
| for (var i = 0; i < nodeCount; i++) { | |||
| nodes.push({ | |||
| id: i, | |||
| label: String(i) | |||
| }); | |||
| connectionCount[i] = 0; | |||
| // create edges in a scale-free-graph way | |||
| if (i == 1) { | |||
| var from = i; | |||
| var to = 0; | |||
| edges.push({ | |||
| from: from, | |||
| to: to | |||
| }); | |||
| connectionCount[from]++; | |||
| connectionCount[to]++; | |||
| } | |||
| else if (i > 1) { | |||
| var conn = edges.length * 2; | |||
| var rand = Math.floor(Math.random() * conn); | |||
| var cum = 0; | |||
| var j = 0; | |||
| while (j < connectionCount.length && cum < rand) { | |||
| cum += connectionCount[j]; | |||
| j++; | |||
| } | |||
| var from = i; | |||
| var to = j; | |||
| edges.push({ | |||
| from: from, | |||
| to: to | |||
| }); | |||
| connectionCount[from]++; | |||
| connectionCount[to]++; | |||
| } | |||
| } | |||
| // create a graph | |||
| var container = document.getElementById('mygraph'); | |||
| var data = { | |||
| nodes: nodes, | |||
| edges: edges | |||
| }; | |||
| /* | |||
| var options = { | |||
| nodes: { | |||
| shape: 'circle' | |||
| }, | |||
| edges: { | |||
| length: 50 | |||
| }, | |||
| stabilize: false | |||
| }; | |||
| */ | |||
| var options = { | |||
| edges: { | |||
| length: 50 | |||
| }, | |||
| stabilize: false, | |||
| navigationUI: { | |||
| enabled: true | |||
| }, | |||
| keyboardNavigation: { | |||
| enabled: true | |||
| } | |||
| }; | |||
| graph = new vis.Graph(container, data, options); | |||
| // add event listeners | |||
| vis.events.addListener(graph, 'select', function(params) { | |||
| document.getElementById('selection').innerHTML = | |||
| 'Selection: ' + graph.getSelection(); | |||
| }); | |||
| } | |||
| </script> | |||
| </head> | |||
| <body onload="draw();"> | |||
| <h2>UI - User Interface and Keyboad Navigation</h2> | |||
| <div style="width: 700px; font-size:14px;"> | |||
| This example is the same as example 2, except for the UI that has been activated. The UI icons are described below. <br /><br /> | |||
| <table class="legend_table"> | |||
| <tr> | |||
| <td>Icons: </td> | |||
| <td><div class="table_content"><img src="img/UI_icons/uparrow.png" /> </div></td> | |||
| <td><div class="table_content"><img src="img/UI_icons/downarrow.png" /> </div></td> | |||
| <td><div class="table_content"><img src="img/UI_icons/leftarrow.png" /> </div></td> | |||
| <td><div class="table_content"><img src="img/UI_icons/rightarrow.png" /> </div></td> | |||
| <td><div class="table_content"><img src="img/UI_icons/plus.png" /> </div></td> | |||
| <td><div class="table_content"><img src="img/UI_icons/minus.png" /> </div></td> | |||
| <td><div class="table_content"><img src="img/UI_icons/zoomExtends.png" /> </div></td> | |||
| </tr> | |||
| <tr> | |||
| <td><div class="table_description">Keyboard shortcuts:</div></td> | |||
| <td><div class="table_content">Up arrow</div></td> | |||
| <td><div class="table_content">Down arrow</div></td> | |||
| <td><div class="table_content">Left arrow</div></td> | |||
| <td><div class="table_content">Right arrow</div></td> | |||
| <td><div class="table_content">=<br />[<br />Page up</div></td> | |||
| <td><div class="table_content">-<br />]<br />Page down</div></td> | |||
| <td><div class="table_content">None</div></td> | |||
| </tr> | |||
| <td><div class="table_description">Description:</div></td> | |||
| <td><div class="table_content">Move up</div></td> | |||
| <td><div class="table_content">Move down</div></td> | |||
| <td><div class="table_content">Move left</div></td> | |||
| <td><div class="table_content">Move right</div></td> | |||
| <td><div class="table_content">Zoom in</div></td> | |||
| <td><div class="table_content">Zoom out</div></td> | |||
| <td><div class="table_content">Zoom extends</div></td> | |||
| </tr> | |||
| </table> | |||
| <br /> | |||
| Apart from clicking the icons, you can also navigate using the keyboard. The buttons are in table above. | |||
| Zoom Extends changes the zoom and position of the camera to encompass all visible nodes. The UI buttons can be toggled on or off | |||
| by pressing the U button on the keyboard. | |||
| </div> | |||
| <br /> | |||
| <form onsubmit="draw(); return false;"> | |||
| <label for="nodeCount">Number of nodes:</label> | |||
| <input id="nodeCount" type="text" value="25" style="width: 50px;"> | |||
| <input type="submit" value="Go"> | |||
| </form> | |||
| <br> | |||
| <div id="mygraph"></div> | |||
| <p id="selection"></p> | |||
| </body> | |||
| </html> | |||
| @ -0,0 +1,547 @@ | |||
| /** | |||
| * Creation of the SectorMixin var. | |||
| * | |||
| * This contains all the functions the Graph object can use to employ the sector system. | |||
| * The sector system is always used by Graph, though the benefits only apply to the use of clustering. | |||
| * If clustering is not used, there is no overhead except for a duplicate object with references to nodes and edges. | |||
| * | |||
| * Alex de Mulder | |||
| * 21-01-2013 | |||
| */ | |||
| var SectorMixin = { | |||
| /** | |||
| * This function is only called by the setData function of the Graph object. | |||
| * This loads the global references into the active sector. This initializes the sector. | |||
| * | |||
| * @private | |||
| */ | |||
| _putDataInSector : function() { | |||
| this.sectors["active"][this._sector()].nodes = this.nodes; | |||
| this.sectors["active"][this._sector()].edges = this.edges; | |||
| this.sectors["active"][this._sector()].nodeIndices = this.nodeIndices; | |||
| }, | |||
| /** | |||
| * /** | |||
| * This function sets the global references to nodes, edges and nodeIndices back to | |||
| * those of the supplied (active) sector. If a type is defined, do the specific type | |||
| * | |||
| * @param {String} sectorId | |||
| * @param {String} [sectorType] | "active" or "frozen" | |||
| * @private | |||
| */ | |||
| _switchToSector : function(sectorId, sectorType) { | |||
| if (sectorType === undefined || sectorType == "active") { | |||
| this._switchToActiveSector(sectorId); | |||
| } | |||
| else { | |||
| this._switchToFrozenSector(sectorId); | |||
| } | |||
| }, | |||
| /** | |||
| * This function sets the global references to nodes, edges and nodeIndices back to | |||
| * those of the supplied active sector. | |||
| * | |||
| * @param sectorId | |||
| * @private | |||
| */ | |||
| _switchToActiveSector : function(sectorId) { | |||
| this.nodeIndices = this.sectors["active"][sectorId]["nodeIndices"]; | |||
| this.nodes = this.sectors["active"][sectorId]["nodes"]; | |||
| this.edges = this.sectors["active"][sectorId]["edges"]; | |||
| }, | |||
| /** | |||
| * This function sets the global references to nodes, edges and nodeIndices back to | |||
| * those of the supplied frozen sector. | |||
| * | |||
| * @param sectorId | |||
| * @private | |||
| */ | |||
| _switchToFrozenSector : function(sectorId) { | |||
| this.nodeIndices = this.sectors["frozen"][sectorId]["nodeIndices"]; | |||
| this.nodes = this.sectors["frozen"][sectorId]["nodes"]; | |||
| this.edges = this.sectors["frozen"][sectorId]["edges"]; | |||
| }, | |||
| /** | |||
| * This function sets the global references to nodes, edges and nodeIndices to | |||
| * those of the navigationUI sector. | |||
| * | |||
| * @private | |||
| */ | |||
| _switchToUISector : function() { | |||
| this.nodeIndices = this.sectors["navigationUI"]["nodeIndices"]; | |||
| this.nodes = this.sectors["navigationUI"]["nodes"]; | |||
| this.edges = this.sectors["navigationUI"]["edges"]; | |||
| }, | |||
| /** | |||
| * This function sets the global references to nodes, edges and nodeIndices back to | |||
| * those of the currently active sector. | |||
| * | |||
| * @private | |||
| */ | |||
| _loadLatestSector : function() { | |||
| this._switchToSector(this._sector()); | |||
| }, | |||
| /** | |||
| * This function returns the currently active sector Id | |||
| * | |||
| * @returns {String} | |||
| * @private | |||
| */ | |||
| _sector : function() { | |||
| return this.activeSector[this.activeSector.length-1]; | |||
| }, | |||
| /** | |||
| * This function returns the previously active sector Id | |||
| * | |||
| * @returns {String} | |||
| * @private | |||
| */ | |||
| _previousSector : function() { | |||
| if (this.activeSector.length > 1) { | |||
| return this.activeSector[this.activeSector.length-2]; | |||
| } | |||
| else { | |||
| throw new TypeError('there are not enough sectors in the this.activeSector array.'); | |||
| } | |||
| }, | |||
| /** | |||
| * We add the active sector at the end of the this.activeSector array | |||
| * This ensures it is the currently active sector returned by _sector() and it reaches the top | |||
| * of the activeSector stack. When we reverse our steps we move from the end to the beginning of this stack. | |||
| * | |||
| * @param newId | |||
| * @private | |||
| */ | |||
| _setActiveSector : function(newId) { | |||
| this.activeSector.push(newId); | |||
| }, | |||
| /** | |||
| * We remove the currently active sector id from the active sector stack. This happens when | |||
| * we reactivate the previously active sector | |||
| * | |||
| * @private | |||
| */ | |||
| _forgetLastSector : function() { | |||
| this.activeSector.pop(); | |||
| }, | |||
| /** | |||
| * This function creates a new active sector with the supplied newId. This newId | |||
| * is the expanding node id. | |||
| * | |||
| * @param {String} newId | Id of the new active sector | |||
| * @private | |||
| */ | |||
| _createNewSector : function(newId) { | |||
| // create the new sector | |||
| this.sectors["active"][newId] = {"nodes":{}, | |||
| "edges":{}, | |||
| "nodeIndices":[], | |||
| "formationScale": this.scale, | |||
| "drawingNode": undefined}; | |||
| // create the new sector render node. This gives visual feedback that you are in a new sector. | |||
| this.sectors["active"][newId]['drawingNode'] = new Node( | |||
| {id:newId, | |||
| color: { | |||
| background: "#eaefef", | |||
| border: "495c5e" | |||
| } | |||
| },{},{},this.constants); | |||
| this.sectors["active"][newId]['drawingNode'].clusterSize = 2; | |||
| }, | |||
| /** | |||
| * This function removes the currently active sector. This is called when we create a new | |||
| * active sector. | |||
| * | |||
| * @param {String} sectorId | Id of the active sector that will be removed | |||
| * @private | |||
| */ | |||
| _deleteActiveSector : function(sectorId) { | |||
| delete this.sectors["active"][sectorId]; | |||
| }, | |||
| /** | |||
| * This function removes the currently active sector. This is called when we reactivate | |||
| * the previously active sector. | |||
| * | |||
| * @param {String} sectorId | Id of the active sector that will be removed | |||
| * @private | |||
| */ | |||
| _deleteFrozenSector : function(sectorId) { | |||
| delete this.sectors["frozen"][sectorId]; | |||
| }, | |||
| /** | |||
| * Freezing an active sector means moving it from the "active" object to the "frozen" object. | |||
| * We copy the references, then delete the active entree. | |||
| * | |||
| * @param sectorId | |||
| * @private | |||
| */ | |||
| _freezeSector : function(sectorId) { | |||
| // we move the set references from the active to the frozen stack. | |||
| this.sectors["frozen"][sectorId] = this.sectors["active"][sectorId]; | |||
| // we have moved the sector data into the frozen set, we now remove it from the active set | |||
| this._deleteActiveSector(sectorId); | |||
| }, | |||
| /** | |||
| * This is the reverse operation of _freezeSector. Activating means moving the sector from the "frozen" | |||
| * object to the "active" object. | |||
| * | |||
| * @param sectorId | |||
| * @private | |||
| */ | |||
| _activateSector : function(sectorId) { | |||
| // we move the set references from the frozen to the active stack. | |||
| this.sectors["active"][sectorId] = this.sectors["frozen"][sectorId]; | |||
| // we have moved the sector data into the active set, we now remove it from the frozen stack | |||
| this._deleteFrozenSector(sectorId); | |||
| }, | |||
| /** | |||
| * This function merges the data from the currently active sector with a frozen sector. This is used | |||
| * in the process of reverting back to the previously active sector. | |||
| * The data that is placed in the frozen (the previously active) sector is the node that has been removed from it | |||
| * upon the creation of a new active sector. | |||
| * | |||
| * @param sectorId | |||
| * @private | |||
| */ | |||
| _mergeThisWithFrozen : function(sectorId) { | |||
| // copy all nodes | |||
| for (var nodeId in this.nodes) { | |||
| if (this.nodes.hasOwnProperty(nodeId)) { | |||
| this.sectors["frozen"][sectorId]["nodes"][nodeId] = this.nodes[nodeId]; | |||
| } | |||
| } | |||
| // copy all edges (if not fully clustered, else there are no edges) | |||
| for (var edgeId in this.edges) { | |||
| if (this.edges.hasOwnProperty(edgeId)) { | |||
| this.sectors["frozen"][sectorId]["edges"][edgeId] = this.edges[edgeId]; | |||
| } | |||
| } | |||
| // merge the nodeIndices | |||
| for (var i = 0; i < this.nodeIndices.length; i++) { | |||
| this.sectors["frozen"][sectorId]["nodeIndices"].push(this.nodeIndices[i]); | |||
| } | |||
| }, | |||
| /** | |||
| * This clusters the sector to one cluster. It was a single cluster before this process started so | |||
| * we revert to that state. The clusterToFit function with a maximum size of 1 node does this. | |||
| * | |||
| * @private | |||
| */ | |||
| _collapseThisToSingleCluster : function() { | |||
| this.clusterToFit(1,false); | |||
| }, | |||
| /** | |||
| * We create a new active sector from the node that we want to open. | |||
| * | |||
| * @param node | |||
| * @private | |||
| */ | |||
| _addSector : function(node) { | |||
| // this is the currently active sector | |||
| var sector = this._sector(); | |||
| // // this should allow me to select nodes from a frozen set. | |||
| // if (this.sectors['active'][sector]["nodes"].hasOwnProperty(node.id)) { | |||
| // console.log("the node is part of the active sector"); | |||
| // } | |||
| // else { | |||
| // console.log("I dont know what the fuck happened!!"); | |||
| // } | |||
| // when we switch to a new sector, we remove the node that will be expanded from the current nodes list. | |||
| delete this.nodes[node.id]; | |||
| var unqiueIdentifier = util.randomUUID(); | |||
| // we fully freeze the currently active sector | |||
| this._freezeSector(sector); | |||
| // we create a new active sector. This sector has the Id of the node to ensure uniqueness | |||
| this._createNewSector(unqiueIdentifier); | |||
| // we add the active sector to the sectors array to be able to revert these steps later on | |||
| this._setActiveSector(unqiueIdentifier); | |||
| // we redirect the global references to the new sector's references. this._sector() now returns unqiueIdentifier | |||
| this._switchToSector(this._sector()); | |||
| // finally we add the node we removed from our previous active sector to the new active sector | |||
| this.nodes[node.id] = node; | |||
| }, | |||
| /** | |||
| * We close the sector that is currently open and revert back to the one before. | |||
| * If the active sector is the "default" sector, nothing happens. | |||
| * | |||
| * @private | |||
| */ | |||
| _collapseSector : function() { | |||
| // the currently active sector | |||
| var sector = this._sector(); | |||
| // we cannot collapse the default sector | |||
| if (sector != "default") { | |||
| if ((this.nodeIndices.length == 1) || | |||
| (this.sectors["active"][sector]["drawingNode"].width*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) || | |||
| (this.sectors["active"][sector]["drawingNode"].height*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) { | |||
| var previousSector = this._previousSector(); | |||
| // we collapse the sector back to a single cluster | |||
| this._collapseThisToSingleCluster(); | |||
| // we move the remaining nodes, edges and nodeIndices to the previous sector. | |||
| // This previous sector is the one we will reactivate | |||
| this._mergeThisWithFrozen(previousSector); | |||
| // the previously active (frozen) sector now has all the data from the currently active sector. | |||
| // we can now delete the active sector. | |||
| this._deleteActiveSector(sector); | |||
| // we activate the previously active (and currently frozen) sector. | |||
| this._activateSector(previousSector); | |||
| // we load the references from the newly active sector into the global references | |||
| this._switchToSector(previousSector); | |||
| // we forget the previously active sector because we reverted to the one before | |||
| this._forgetLastSector(); | |||
| // finally, we update the node index list. | |||
| this._updateNodeIndexList(); | |||
| } | |||
| } | |||
| }, | |||
| /** | |||
| * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation(). | |||
| * | |||
| * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors | |||
| * | we dont pass the function itself because then the "this" is the window object | |||
| * | instead of the Graph object | |||
| * @param {*} [argument] | Optional: arguments to pass to the runFunction | |||
| * @private | |||
| */ | |||
| _doInAllActiveSectors : function(runFunction,argument) { | |||
| if (argument === undefined) { | |||
| for (var sector in this.sectors["active"]) { | |||
| if (this.sectors["active"].hasOwnProperty(sector)) { | |||
| // switch the global references to those of this sector | |||
| this._switchToActiveSector(sector); | |||
| this[runFunction](); | |||
| } | |||
| } | |||
| } | |||
| else { | |||
| for (var sector in this.sectors["active"]) { | |||
| if (this.sectors["active"].hasOwnProperty(sector)) { | |||
| // switch the global references to those of this sector | |||
| this._switchToActiveSector(sector); | |||
| var args = Array.prototype.splice.call(arguments, 1); | |||
| if (args.length > 1) { | |||
| this[runFunction](args[0],args[1]); | |||
| } | |||
| else { | |||
| this[runFunction](argument); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| // we revert the global references back to our active sector | |||
| this._loadLatestSector(); | |||
| }, | |||
| /** | |||
| * This runs a function in all frozen sectors. This is used in the _redraw(). | |||
| * | |||
| * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors | |||
| * | we don't pass the function itself because then the "this" is the window object | |||
| * | instead of the Graph object | |||
| * @param {*} [argument] | Optional: arguments to pass to the runFunction | |||
| * @private | |||
| */ | |||
| _doInAllFrozenSectors : function(runFunction,argument) { | |||
| if (argument === undefined) { | |||
| for (var sector in this.sectors["frozen"]) { | |||
| if (this.sectors["frozen"].hasOwnProperty(sector)) { | |||
| // switch the global references to those of this sector | |||
| this._switchToFrozenSector(sector); | |||
| this[runFunction](); | |||
| } | |||
| } | |||
| } | |||
| else { | |||
| for (var sector in this.sectors["frozen"]) { | |||
| if (this.sectors["frozen"].hasOwnProperty(sector)) { | |||
| // switch the global references to those of this sector | |||
| this._switchToFrozenSector(sector); | |||
| var args = Array.prototype.splice.call(arguments, 1); | |||
| if (args.length > 1) { | |||
| this[runFunction](args[0],args[1]); | |||
| } | |||
| else { | |||
| this[runFunction](argument); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| this._loadLatestSector(); | |||
| }, | |||
| /** | |||
| * This runs a function in the navigationUI sector. | |||
| * | |||
| * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors | |||
| * | we don't pass the function itself because then the "this" is the window object | |||
| * | instead of the Graph object | |||
| * @param {*} [argument] | Optional: arguments to pass to the runFunction | |||
| * @private | |||
| */ | |||
| _doInUISector : function(runFunction,argument) { | |||
| this._switchToUISector(); | |||
| if (argument === undefined) { | |||
| this[runFunction](); | |||
| } | |||
| else { | |||
| var args = Array.prototype.splice.call(arguments, 1); | |||
| if (args.length > 1) { | |||
| this[runFunction](args[0],args[1]); | |||
| } | |||
| else { | |||
| this[runFunction](argument); | |||
| } | |||
| } | |||
| this._loadLatestSector(); | |||
| }, | |||
| /** | |||
| * This runs a function in all sectors. This is used in the _redraw(). | |||
| * | |||
| * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors | |||
| * | we don't pass the function itself because then the "this" is the window object | |||
| * | instead of the Graph object | |||
| * @param {*} [argument] | Optional: arguments to pass to the runFunction | |||
| * @private | |||
| */ | |||
| _doInAllSectors : function(runFunction,argument) { | |||
| var args = Array.prototype.splice.call(arguments, 1); | |||
| if (argument === undefined) { | |||
| this._doInAllActiveSectors(runFunction); | |||
| this._doInAllFrozenSectors(runFunction); | |||
| } | |||
| else { | |||
| if (args.length > 1) { | |||
| this._doInAllActiveSectors(runFunction,args[0],args[1]); | |||
| this._doInAllFrozenSectors(runFunction,args[0],args[1]); | |||
| } | |||
| else { | |||
| this._doInAllActiveSectors(runFunction,argument); | |||
| this._doInAllFrozenSectors(runFunction,argument); | |||
| } | |||
| } | |||
| }, | |||
| /** | |||
| * This clears the nodeIndices list. We cannot use this.nodeIndices = [] because we would break the link with the | |||
| * active sector. Thus we clear the nodeIndices in the active sector, then reconnect the this.nodeIndices to it. | |||
| * | |||
| * @private | |||
| */ | |||
| _clearNodeIndexList : function() { | |||
| var sector = this._sector(); | |||
| this.sectors["active"][sector]["nodeIndices"] = []; | |||
| this.nodeIndices = this.sectors["active"][sector]["nodeIndices"]; | |||
| }, | |||
| /** | |||
| * Draw the encompassing sector node | |||
| * | |||
| * @param ctx | |||
| * @param sectorType | |||
| * @private | |||
| */ | |||
| _drawSectorNodes : function(ctx,sectorType) { | |||
| var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node; | |||
| for (var sector in this.sectors[sectorType]) { | |||
| if (this.sectors[sectorType].hasOwnProperty(sector)) { | |||
| if (this.sectors[sectorType][sector]["drawingNode"] !== undefined) { | |||
| this._switchToSector(sector,sectorType); | |||
| minY = 1e9; maxY = -1e9; minX = 1e9; maxX = -1e9; | |||
| for (var nodeId in this.nodes) { | |||
| if (this.nodes.hasOwnProperty(nodeId)) { | |||
| node = this.nodes[nodeId]; | |||
| node.resize(ctx); | |||
| if (minX > node.x - 0.5 * node.width) {minX = node.x - 0.5 * node.width;} | |||
| if (maxX < node.x + 0.5 * node.width) {maxX = node.x + 0.5 * node.width;} | |||
| if (minY > node.y - 0.5 * node.height) {minY = node.y - 0.5 * node.height;} | |||
| if (maxY < node.y + 0.5 * node.height) {maxY = node.y + 0.5 * node.height;} | |||
| } | |||
| } | |||
| node = this.sectors[sectorType][sector]["drawingNode"]; | |||
| node.x = 0.5 * (maxX + minX); | |||
| node.y = 0.5 * (maxY + minY); | |||
| node.width = 2 * (node.x - minX); | |||
| node.height = 2 * (node.y - minY); | |||
| node.radius = Math.sqrt(Math.pow(0.5*node.width,2) + Math.pow(0.5*node.height,2)); | |||
| node.setScale(this.scale); | |||
| node._drawCircle(ctx); | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| _drawAllSectorNodes : function(ctx) { | |||
| this._drawSectorNodes(ctx,"frozen"); | |||
| this._drawSectorNodes(ctx,"active"); | |||
| this._loadLatestSector(); | |||
| } | |||
| }; | |||
| @ -0,0 +1,487 @@ | |||
| var SelectionMixin = { | |||
| /** | |||
| * This function can be called from the _doInAllSectors function | |||
| * | |||
| * @param object | |||
| * @param overlappingNodes | |||
| * @private | |||
| */ | |||
| _getNodesOverlappingWith : function(object, overlappingNodes) { | |||
| var nodes = this.nodes; | |||
| for (var nodeId in nodes) { | |||
| if (nodes.hasOwnProperty(nodeId)) { | |||
| if (nodes[nodeId].isOverlappingWith(object)) { | |||
| overlappingNodes.push(nodeId); | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| /** | |||
| * retrieve all nodes overlapping with given object | |||
| * @param {Object} object An object with parameters left, top, right, bottom | |||
| * @return {Number[]} An array with id's of the overlapping nodes | |||
| * @private | |||
| */ | |||
| _getAllNodesOverlappingWith : function (object) { | |||
| var overlappingNodes = []; | |||
| this._doInAllActiveSectors("_getNodesOverlappingWith",object,overlappingNodes); | |||
| return overlappingNodes; | |||
| }, | |||
| /** | |||
| * retrieve all nodes in the navigationUI overlapping with given object | |||
| * @param {Object} object An object with parameters left, top, right, bottom | |||
| * @return {Number[]} An array with id's of the overlapping nodes | |||
| * @private | |||
| */ | |||
| _getAllUINodesOverlappingWith : function (object) { | |||
| var overlappingNodes = []; | |||
| this._doInUISector("_getNodesOverlappingWith",object,overlappingNodes); | |||
| return overlappingNodes; | |||
| }, | |||
| /** | |||
| * Return a position object in canvasspace from a single point in screenspace | |||
| * | |||
| * @param pointer | |||
| * @returns {{left: number, top: number, right: number, bottom: number}} | |||
| * @private | |||
| */ | |||
| _pointerToPositionObject : function(pointer) { | |||
| var x = this._canvasToX(pointer.x); | |||
| var y = this._canvasToY(pointer.y); | |||
| return {left: x, | |||
| top: y, | |||
| right: x, | |||
| bottom: y}; | |||
| }, | |||
| /** | |||
| * Return a position object in canvasspace from a single point in screenspace | |||
| * | |||
| * @param pointer | |||
| * @returns {{left: number, top: number, right: number, bottom: number}} | |||
| * @private | |||
| */ | |||
| _pointerToScreenPositionObject : function(pointer) { | |||
| var x = pointer.x; | |||
| var y = pointer.y; | |||
| return {left: x, | |||
| top: y, | |||
| right: x, | |||
| bottom: y}; | |||
| }, | |||
| /** | |||
| * Get the top navigationUI node at the a specific point (like a click) | |||
| * | |||
| * @param {{x: Number, y: Number}} pointer | |||
| * @return {Node | null} node | |||
| * @private | |||
| */ | |||
| _getUINodeAt : function (pointer) { | |||
| var screenPositionObject = this._pointerToScreenPositionObject(pointer); | |||
| var overlappingNodes = this._getAllUINodesOverlappingWith(screenPositionObject); | |||
| if (this.UIvisible && overlappingNodes.length > 0) { | |||
| return this.sectors["navigationUI"]["nodes"][overlappingNodes[overlappingNodes.length - 1]]; | |||
| } | |||
| else { | |||
| return null; | |||
| } | |||
| }, | |||
| /** | |||
| * Get the top node at the a specific point (like a click) | |||
| * | |||
| * @param {{x: Number, y: Number}} pointer | |||
| * @return {Node | null} node | |||
| * @private | |||
| */ | |||
| _getNodeAt : function (pointer) { | |||
| // we first check if this is an navigationUI element | |||
| var positionObject = this._pointerToPositionObject(pointer); | |||
| overlappingNodes = this._getAllNodesOverlappingWith(positionObject); | |||
| // if there are overlapping nodes, select the last one, this is the | |||
| // one which is drawn on top of the others | |||
| if (overlappingNodes.length > 0) { | |||
| return this.nodes[overlappingNodes[overlappingNodes.length - 1]]; | |||
| } | |||
| else { | |||
| return null; | |||
| } | |||
| }, | |||
| /** | |||
| * Place holder. To implement change the _getNodeAt to a _getObjectAt. Have the _getObjectAt call | |||
| * _getNodeAt and _getEdgesAt, then priortize the selection to user preferences. | |||
| * | |||
| * @param pointer | |||
| * @returns {null} | |||
| * @private | |||
| */ | |||
| _getEdgeAt : function(pointer) { | |||
| return null; | |||
| }, | |||
| /** | |||
| * Add object to the selection array. The this.selection id array may not be needed. | |||
| * | |||
| * @param obj | |||
| * @private | |||
| */ | |||
| _addToSelection : function(obj) { | |||
| this.selection.push(obj.id); | |||
| this.selectionObj[obj.id] = obj; | |||
| }, | |||
| /** | |||
| * Remove a single option from selection. | |||
| * | |||
| * @param obj | |||
| * @private | |||
| */ | |||
| _removeFromSelection : function(obj) { | |||
| for (var i = 0; i < this.selection.length; i++) { | |||
| if (obj.id == this.selection[i]) { | |||
| this.selection.splice(i,1); | |||
| break; | |||
| } | |||
| } | |||
| delete this.selectionObj[obj.id]; | |||
| }, | |||
| /** | |||
| * Unselect all. The selectionObj is useful for this. | |||
| * | |||
| * @private | |||
| */ | |||
| _unselectAll : function() { | |||
| this.selection = []; | |||
| for (var objId in this.selectionObj) { | |||
| if (this.selectionObj.hasOwnProperty(objId)) { | |||
| this.selectionObj[objId].unselect(); | |||
| } | |||
| } | |||
| this.selectionObj = {}; | |||
| this._trigger('select'); | |||
| }, | |||
| /** | |||
| * Check if anything is selected | |||
| * | |||
| * @returns {boolean} | |||
| * @private | |||
| */ | |||
| _selectionIsEmpty : function() { | |||
| if (this.selection.length == 0) { | |||
| return true; | |||
| } | |||
| else { | |||
| return false; | |||
| } | |||
| }, | |||
| /** | |||
| * This is called when someone clicks on a node. either select or deselect it. | |||
| * If there is an existing selection and we don't want to append to it, clear the existing selection | |||
| * | |||
| * @param {Node} node | |||
| * @param {Boolean} append | |||
| * @private | |||
| */ | |||
| _selectNode : function(node, append) { | |||
| if (this._selectionIsEmpty() == false && append == false) { | |||
| this._unselectAll(); | |||
| } | |||
| if (node.selected == false) { | |||
| node.select(); | |||
| this._addToSelection(node); | |||
| } | |||
| else { | |||
| node.unselect(); | |||
| this._removeFromSelection(node); | |||
| } | |||
| this._trigger('select'); | |||
| }, | |||
| /** | |||
| * handles the selection part of the touch, only for navigationUI elements; | |||
| * Touch is triggered before tap, also before hold. Hold triggers after a while. | |||
| * This is the most responsive solution | |||
| * | |||
| * @param {Object} pointer | |||
| * @private | |||
| */ | |||
| _handleTouch : function(pointer) { | |||
| var node = this._getUINodeAt(pointer); | |||
| if (node != null) { | |||
| if (this[node.triggerFunction] !== undefined) { | |||
| this[node.triggerFunction](); | |||
| } | |||
| } | |||
| }, | |||
| /** | |||
| * handles the selection part of the tap; | |||
| * | |||
| * @param {Object} pointer | |||
| * @private | |||
| */ | |||
| _handleTap : function(pointer) { | |||
| var node = this._getNodeAt(pointer); | |||
| if (node != null) { | |||
| this._selectNode(node,false); | |||
| } | |||
| else { | |||
| this._unselectAll(); | |||
| } | |||
| this._redraw(); | |||
| }, | |||
| /** | |||
| * handles the selection part of the double tap and opens a cluster if needed | |||
| * | |||
| * @param {Object} pointer | |||
| * @private | |||
| */ | |||
| _handleDoubleTap : function(pointer) { | |||
| var node = this._getNodeAt(pointer); | |||
| if (node != null && node !== undefined) { | |||
| // we reset the areaCenter here so the opening of the node will occur | |||
| this.areaCenter = {"x" : this._canvasToX(pointer.x), | |||
| "y" : this._canvasToY(pointer.y)}; | |||
| this.openCluster(node); | |||
| } | |||
| }, | |||
| /** | |||
| * Handle the onHold selection part | |||
| * | |||
| * @param pointer | |||
| * @private | |||
| */ | |||
| _handleOnHold : function(pointer) { | |||
| var node = this._getNodeAt(pointer); | |||
| if (node != null) { | |||
| this._selectNode(node,true); | |||
| } | |||
| this._redraw(); | |||
| }, | |||
| /** | |||
| * handle the onRelease event. These functions are here for the navigationUI module. | |||
| * | |||
| * @private | |||
| */ | |||
| _handleOnRelease : function() { | |||
| this.xIncrement = 0; | |||
| this.yIncrement = 0; | |||
| this.zoomIncrement = 0; | |||
| this._unHighlightAll(); | |||
| }, | |||
| /** | |||
| * * // TODO: rework this function, it is from the old system | |||
| * | |||
| * retrieve the currently selected nodes | |||
| * @return {Number[] | String[]} selection An array with the ids of the | |||
| * selected nodes. | |||
| */ | |||
| getSelection : function() { | |||
| return this.selection.concat([]); | |||
| }, | |||
| /** | |||
| * // TODO: rework this function, it is from the old system | |||
| * | |||
| * select zero or more nodes | |||
| * @param {Number[] | String[]} selection An array with the ids of the | |||
| * selected nodes. | |||
| */ | |||
| setSelection : function(selection) { | |||
| var i, iMax, id; | |||
| if (!selection || (selection.length == undefined)) | |||
| throw 'Selection must be an array with ids'; | |||
| // first unselect any selected node | |||
| for (i = 0, iMax = this.selection.length; i < iMax; i++) { | |||
| id = this.selection[i]; | |||
| this.nodes[id].unselect(); | |||
| } | |||
| this.selection = []; | |||
| for (i = 0, iMax = selection.length; i < iMax; i++) { | |||
| id = selection[i]; | |||
| var node = this.nodes[id]; | |||
| if (!node) { | |||
| throw new RangeError('Node with id "' + id + '" not found'); | |||
| } | |||
| node.select(); | |||
| this.selection.push(id); | |||
| } | |||
| this.redraw(); | |||
| }, | |||
| /** | |||
| * TODO: rework this function, it is from the old system | |||
| * | |||
| * Validate the selection: remove ids of nodes which no longer exist | |||
| * @private | |||
| */ | |||
| _updateSelection : function () { | |||
| var i = 0; | |||
| while (i < this.selection.length) { | |||
| var nodeId = this.selection[i]; | |||
| if (!this.nodes.hasOwnProperty(nodeId)) { | |||
| this.selection.splice(i, 1); | |||
| delete this.selectionObj[nodeId]; | |||
| } | |||
| else { | |||
| i++; | |||
| } | |||
| } | |||
| } | |||
| /** | |||
| * Unselect selected nodes. If no selection array is provided, all nodes | |||
| * are unselected | |||
| * @param {Object[]} selection Array with selection objects, each selection | |||
| * object has a parameter row. Optional | |||
| * @param {Boolean} triggerSelect If true (default), the select event | |||
| * is triggered when nodes are unselected | |||
| * @return {Boolean} changed True if the selection is changed | |||
| * @private | |||
| */ | |||
| /* _unselectNodes : function(selection, triggerSelect) { | |||
| var changed = false; | |||
| var i, iMax, id; | |||
| if (selection) { | |||
| // remove provided selections | |||
| for (i = 0, iMax = selection.length; i < iMax; i++) { | |||
| id = selection[i]; | |||
| if (this.nodes.hasOwnProperty(id)) { | |||
| this.nodes[id].unselect(); | |||
| } | |||
| var j = 0; | |||
| while (j < this.selection.length) { | |||
| if (this.selection[j] == id) { | |||
| this.selection.splice(j, 1); | |||
| changed = true; | |||
| } | |||
| else { | |||
| j++; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| else if (this.selection && this.selection.length) { | |||
| // remove all selections | |||
| for (i = 0, iMax = this.selection.length; i < iMax; i++) { | |||
| id = this.selection[i]; | |||
| if (this.nodes.hasOwnProperty(id)) { | |||
| this.nodes[id].unselect(); | |||
| } | |||
| changed = true; | |||
| } | |||
| this.selection = []; | |||
| } | |||
| if (changed && (triggerSelect == true || triggerSelect == undefined)) { | |||
| // fire the select event | |||
| this._trigger('select'); | |||
| } | |||
| return changed; | |||
| }, | |||
| */ | |||
| /** | |||
| * select all nodes on given location x, y | |||
| * @param {Array} selection an array with node ids | |||
| * @param {boolean} append If true, the new selection will be appended to the | |||
| * current selection (except for duplicate entries) | |||
| * @return {Boolean} changed True if the selection is changed | |||
| * @private | |||
| */ | |||
| /* _selectNodes : function(selection, append) { | |||
| var changed = false; | |||
| var i, iMax; | |||
| // TODO: the selectNodes method is a little messy, rework this | |||
| // check if the current selection equals the desired selection | |||
| var selectionAlreadyThere = true; | |||
| if (selection.length != this.selection.length) { | |||
| selectionAlreadyThere = false; | |||
| } | |||
| else { | |||
| for (i = 0, iMax = Math.min(selection.length, this.selection.length); i < iMax; i++) { | |||
| if (selection[i] != this.selection[i]) { | |||
| selectionAlreadyThere = false; | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| if (selectionAlreadyThere) { | |||
| return changed; | |||
| } | |||
| if (append == undefined || append == false) { | |||
| // first deselect any selected node | |||
| var triggerSelect = false; | |||
| changed = this._unselectNodes(undefined, triggerSelect); | |||
| } | |||
| for (i = 0, iMax = selection.length; i < iMax; i++) { | |||
| // add each of the new selections, but only when they are not duplicate | |||
| var id = selection[i]; | |||
| var isDuplicate = (this.selection.indexOf(id) != -1); | |||
| if (!isDuplicate) { | |||
| this.nodes[id].select(); | |||
| this.selection.push(id); | |||
| changed = true; | |||
| } | |||
| } | |||
| if (changed) { | |||
| // fire the select event | |||
| this._trigger('select'); | |||
| } | |||
| return changed; | |||
| }, | |||
| */ | |||
| }; | |||
| @ -0,0 +1,258 @@ | |||
| /** | |||
| * Created by Alex on 1/22/14. | |||
| */ | |||
| var UIMixin = { | |||
| /** | |||
| * This function moves the navigationUI if the canvas size has been changed. If the arugments | |||
| * verticaAlignTop and horizontalAlignLeft are false, the correction will be made | |||
| * | |||
| * @private | |||
| */ | |||
| _relocateUI : function() { | |||
| if (this.sectors !== undefined) { | |||
| var xOffset = this.UIclientWidth - this.frame.canvas.clientWidth; | |||
| var yOffset = this.UIclientHeight - this.frame.canvas.clientHeight; | |||
| this.UIclientWidth = this.frame.canvas.clientWidth; | |||
| this.UIclientHeight = this.frame.canvas.clientHeight; | |||
| var node = null; | |||
| for (var nodeId in this.sectors["navigationUI"]["nodes"]) { | |||
| if (this.sectors["navigationUI"]["nodes"].hasOwnProperty(nodeId)) { | |||
| node = this.sectors["navigationUI"]["nodes"][nodeId]; | |||
| if (!node.horizontalAlignLeft) { | |||
| node.x -= xOffset; | |||
| } | |||
| if (!node.verticalAlignTop) { | |||
| node.y -= yOffset; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| /** | |||
| * Creation of the navigationUI nodes. They are drawn over the rest of the nodes and are not affected by scale and translation | |||
| * they have a triggerFunction which is called on click. If the position of the navigationUI is dependent | |||
| * on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false. | |||
| * This means that the location will be corrected by the _relocateUI function on a size change of the canvas. | |||
| * | |||
| * @private | |||
| */ | |||
| _loadUIElements : function() { | |||
| var DIR = this.constants.navigationUI.iconPath; | |||
| this.UIclientWidth = this.frame.canvas.clientWidth; | |||
| this.UIclientHeight = this.frame.canvas.clientHeight; | |||
| if (this.UIclientWidth === undefined) { | |||
| this.UIclientWidth = 0; | |||
| this.UIclientHeight = 0; | |||
| } | |||
| var offset = 15; | |||
| var intermediateOffset = 7; | |||
| var UINodes = [ | |||
| {id: 'UI_up', shape: 'image', image: DIR + 'uparrow.png', triggerFunction: "_moveUp", | |||
| verticalAlignTop: false, x: 45 + offset + intermediateOffset, y: this.UIclientHeight - 45 - offset - intermediateOffset}, | |||
| {id: 'UI_down', shape: 'image', image: DIR + 'downarrow.png', triggerFunction: "_moveDown", | |||
| verticalAlignTop: false, x: 45 + offset + intermediateOffset, y: this.UIclientHeight - 15 - offset}, | |||
| {id: 'UI_left', shape: 'image', image: DIR + 'leftarrow.png', triggerFunction: "_moveLeft", | |||
| verticalAlignTop: false, x: 15 + offset, y: this.UIclientHeight - 15 - offset}, | |||
| {id: 'UI_right', shape: 'image', image: DIR + 'rightarrow.png',triggerFunction: "_moveRight", | |||
| verticalAlignTop: false, x: 75 + offset + 2 * intermediateOffset, y: this.UIclientHeight - 15 - offset}, | |||
| {id: 'UI_plus', shape: 'image', image: DIR + 'plus.png', triggerFunction: "_zoomIn", | |||
| verticalAlignTop: false, horizontalAlignLeft: false, | |||
| x: this.UIclientWidth - 45 - offset - intermediateOffset, y: this.UIclientHeight - 15 - offset}, | |||
| {id: 'UI_min', shape: 'image', image: DIR + 'minus.png', triggerFunction: "_zoomOut", | |||
| verticalAlignTop: false, horizontalAlignLeft: false, | |||
| x: this.UIclientWidth - 15 - offset, y: this.UIclientHeight - 15 - offset}, | |||
| {id: 'UI_zoomExtends', shape: 'image', image: DIR + 'zoomExtends.png', triggerFunction: "zoomToFit", | |||
| verticalAlignTop: false, horizontalAlignLeft: false, | |||
| x: this.UIclientWidth - 15 - offset, y: this.UIclientHeight - 45 - offset - intermediateOffset} | |||
| ]; | |||
| var nodeObj = null; | |||
| for (var i = 0; i < UINodes.length; i++) { | |||
| nodeObj = this.sectors["navigationUI"]['nodes']; | |||
| nodeObj[UINodes[i]['id']] = new Node(UINodes[i], this.images, this.groups, this.constants); | |||
| } | |||
| }, | |||
| /** | |||
| * By setting the clustersize to be larger than 1, we use the clustering drawing method | |||
| * to illustrate the buttons are presed. We call this highlighting. | |||
| * | |||
| * @param {String} elementId | |||
| * @private | |||
| */ | |||
| _highlightUIElement : function(elementId) { | |||
| if (this.sectors["navigationUI"]["nodes"].hasOwnProperty(elementId)) { | |||
| this.sectors["navigationUI"]["nodes"][elementId].clusterSize = 2; | |||
| } | |||
| }, | |||
| /** | |||
| * Reverting back to a normal button | |||
| * | |||
| * @param {String} elementId | |||
| * @private | |||
| */ | |||
| _unHighlightUIElement : function(elementId) { | |||
| if (this.sectors["navigationUI"]["nodes"].hasOwnProperty(elementId)) { | |||
| this.sectors["navigationUI"]["nodes"][elementId].clusterSize = 1; | |||
| } | |||
| }, | |||
| /** | |||
| * toggle the visibility of the navigationUI | |||
| * | |||
| * @private | |||
| */ | |||
| _toggleUI : function() { | |||
| if (this.UIvisible === undefined) { | |||
| this.UIvisible = false; | |||
| } | |||
| this.UIvisible = !this.UIvisible; | |||
| this._redraw(); | |||
| }, | |||
| /** | |||
| * un-highlight (for lack of a better term) all navigationUI elements | |||
| * @private | |||
| */ | |||
| _unHighlightAll : function() { | |||
| for (var nodeId in this.sectors['navigationUI']['nodes']) { | |||
| this._unHighlightUIElement(nodeId); | |||
| } | |||
| }, | |||
| _preventDefault : function(event) { | |||
| if (event !== undefined) { | |||
| if (event.preventDefault) { | |||
| event.preventDefault(); | |||
| } else { | |||
| event.returnValue = false; | |||
| } | |||
| } | |||
| }, | |||
| /** | |||
| * move the screen up | |||
| * By using the increments, instead of adding a fixed number to the translation, we keep fluent and | |||
| * instant movement. The onKeypress event triggers immediately, then pauses, then triggers frequently | |||
| * To avoid this behaviour, we do the translation in the start loop. | |||
| * | |||
| * @private | |||
| */ | |||
| _moveUp : function(event) { | |||
| this._highlightUIElement("UI_up"); | |||
| this.yIncrement = this.constants.keyboardNavigation.yMovementSpeed; | |||
| this.start(); // if there is no node movement, the calculation wont be done | |||
| this._preventDefault(event); | |||
| }, | |||
| /** | |||
| * move the screen down | |||
| * @private | |||
| */ | |||
| _moveDown : function(event) { | |||
| this._highlightUIElement("UI_down"); | |||
| this.yIncrement = -this.constants.keyboardNavigation.yMovementSpeed; | |||
| this.start(); // if there is no node movement, the calculation wont be done | |||
| this._preventDefault(event); | |||
| }, | |||
| /** | |||
| * move the screen left | |||
| * @private | |||
| */ | |||
| _moveLeft : function(event) { | |||
| this._highlightUIElement("UI_left"); | |||
| this.xIncrement = this.constants.keyboardNavigation.xMovementSpeed; | |||
| this.start(); // if there is no node movement, the calculation wont be done | |||
| this._preventDefault(event); | |||
| }, | |||
| /** | |||
| * move the screen right | |||
| * @private | |||
| */ | |||
| _moveRight : function(event) { | |||
| this._highlightUIElement("UI_right"); | |||
| this.xIncrement = -this.constants.keyboardNavigation.xMovementSpeed; | |||
| this.start(); // if there is no node movement, the calculation wont be done | |||
| this._preventDefault(event); | |||
| }, | |||
| /** | |||
| * Zoom in, using the same method as the movement. | |||
| * @private | |||
| */ | |||
| _zoomIn : function(event) { | |||
| this._highlightUIElement("UI_plus"); | |||
| this.zoomIncrement = this.constants.keyboardNavigation.zoomMovementSpeed; | |||
| this.start(); // if there is no node movement, the calculation wont be done | |||
| this._preventDefault(event); | |||
| }, | |||
| /** | |||
| * Zoom out | |||
| * @private | |||
| */ | |||
| _zoomOut : function() { | |||
| this._highlightUIElement("UI_min"); | |||
| this.zoomIncrement = -this.constants.keyboardNavigation.zoomMovementSpeed; | |||
| this.start(); // if there is no node movement, the calculation wont be done | |||
| this._preventDefault(event); | |||
| }, | |||
| /** | |||
| * Stop zooming and unhighlight the zoom controls | |||
| * @private | |||
| */ | |||
| _stopZoom : function() { | |||
| this._unHighlightUIElement("UI_plus"); | |||
| this._unHighlightUIElement("UI_min"); | |||
| this.zoomIncrement = 0; | |||
| }, | |||
| /** | |||
| * Stop moving in the Y direction and unHighlight the up and down | |||
| * @private | |||
| */ | |||
| _yStopMoving : function() { | |||
| this._unHighlightUIElement("UI_up"); | |||
| this._unHighlightUIElement("UI_down"); | |||
| this.yIncrement = 0; | |||
| }, | |||
| /** | |||
| * Stop moving in the X direction and unHighlight left and right. | |||
| * @private | |||
| */ | |||
| _xStopMoving : function() { | |||
| this._unHighlightUIElement("UI_left"); | |||
| this._unHighlightUIElement("UI_right"); | |||
| this.xIncrement = 0; | |||
| } | |||
| }; | |||