@ -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 | Fully random nodes clustering</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 | |||
graph.on('select', function(params) { | |||
document.getElementById('selection').innerHTML = 'Selection: ' + params.nodes; | |||
}); | |||
} | |||
</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,141 @@ | |||
<!doctype html> | |||
<html> | |||
<head> | |||
<title>Graph | Scale free graph clustering</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 | |||
graph.on('select', function(params) { | |||
document.getElementById('selection').innerHTML = 'Selection: ' + params.nodes; | |||
}); | |||
} | |||
</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,181 @@ | |||
<!doctype html> | |||
<html> | |||
<head> | |||
<title>Graph | Navigation</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, | |||
navigation: true, | |||
keyboard: true | |||
}; | |||
graph = new vis.Graph(container, data, options); | |||
// add event listeners | |||
graph.on('select', function(params) { | |||
document.getElementById('selection').innerHTML = 'Selection: ' + params.nodes; | |||
}); | |||
} | |||
</script> | |||
</head> | |||
<body onload="draw();"> | |||
<h2>Navigation controls and keyboad navigation</h2> | |||
<div style="width: 700px; font-size:14px;"> | |||
This example is the same as example 2, except for the navigation controls that has been activated. The navigation controls are described below. <br /><br /> | |||
<table class="legend_table"> | |||
<tr> | |||
<td>Icons: </td> | |||
<td><div class="table_content"><img src="../../dist/img/uparrow.png" /> </div></td> | |||
<td><div class="table_content"><img src="../../dist/img/downarrow.png" /> </div></td> | |||
<td><div class="table_content"><img src="../../dist/img/leftarrow.png" /> </div></td> | |||
<td><div class="table_content"><img src="../../dist/img/rightarrow.png" /> </div></td> | |||
<td><div class="table_content"><img src="../../dist/img/plus.png" /> </div></td> | |||
<td><div class="table_content"><img src="../../dist/img/minus.png" /> </div></td> | |||
<td><div class="table_content"><img src="../../dist/img/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> | |||
<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. | |||
</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,53 @@ | |||
<!DOCTYPE HTML> | |||
<html> | |||
<head> | |||
<title>Timeline | Event listeners</title> | |||
<style type="text/css"> | |||
body, html { | |||
font-family: sans-serif; | |||
} | |||
</style> | |||
<script src="../../dist/vis.js"></script> | |||
<link href="../../dist/vis.css" rel="stylesheet" type="text/css" /> | |||
</head> | |||
<body> | |||
<div id="visualization"></div> | |||
<p></p> | |||
<div id="log"></div> | |||
<script type="text/javascript"> | |||
var container = document.getElementById('visualization'); | |||
var items = [ | |||
{id: 1, content: 'item 1', start: '2013-04-20'}, | |||
{id: 2, content: 'item 2', start: '2013-04-14'}, | |||
{id: 3, content: 'item 3', start: '2013-04-18'}, | |||
{id: 4, content: 'item 4', start: '2013-04-16', end: '2013-04-19'}, | |||
{id: 5, content: 'item 5', start: '2013-04-25'}, | |||
{id: 6, content: 'item 6', start: '2013-04-27'} | |||
]; | |||
var options = {}; | |||
var timeline = new vis.Timeline(container, items, options); | |||
timeline.on('rangechange', function (properties) { | |||
logEvent('rangechange', properties); | |||
}); | |||
timeline.on('rangechanged', function (properties) { | |||
logEvent('rangechanged', properties); | |||
}); | |||
timeline.on('select', function (properties) { | |||
logEvent('select', properties); | |||
}); | |||
function logEvent(event, properties) { | |||
var log = document.getElementById('log'); | |||
var msg = document.createElement('div'); | |||
msg.innerHTML = 'event=' + JSON.stringify(event) + ', ' + | |||
'properties=' + JSON.stringify(properties); | |||
log.firstChild ? log.insertBefore(msg, log.firstChild) : log.appendChild(msg); | |||
} | |||
</script> | |||
</body> | |||
</html> |
@ -0,0 +1,245 @@ | |||
/** | |||
* Created by Alex on 1/22/14. | |||
*/ | |||
var NavigationMixin = { | |||
/** | |||
* This function moves the navigation controls if the canvas size has been changed. If the arugments | |||
* verticaAlignTop and horizontalAlignLeft are false, the correction will be made | |||
* | |||
* @private | |||
*/ | |||
_relocateNavigation : function() { | |||
if (this.sectors !== undefined) { | |||
var xOffset = this.navigationClientWidth - this.frame.canvas.clientWidth; | |||
var yOffset = this.navigationClientHeight - this.frame.canvas.clientHeight; | |||
this.navigationClientWidth = this.frame.canvas.clientWidth; | |||
this.navigationClientHeight = this.frame.canvas.clientHeight; | |||
var node = null; | |||
for (var nodeId in this.sectors["navigation"]["nodes"]) { | |||
if (this.sectors["navigation"]["nodes"].hasOwnProperty(nodeId)) { | |||
node = this.sectors["navigation"]["nodes"][nodeId]; | |||
if (!node.horizontalAlignLeft) { | |||
node.x -= xOffset; | |||
} | |||
if (!node.verticalAlignTop) { | |||
node.y -= yOffset; | |||
} | |||
} | |||
} | |||
} | |||
}, | |||
/** | |||
* Creation of the navigation controls 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 navigation controls 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 _relocateNavigation function on a size change of the canvas. | |||
* | |||
* @private | |||
*/ | |||
_loadNavigationElements : function() { | |||
var DIR = this.constants.navigation.iconPath; | |||
this.navigationClientWidth = this.frame.canvas.clientWidth; | |||
this.navigationClientHeight = this.frame.canvas.clientHeight; | |||
if (this.navigationClientWidth === undefined) { | |||
this.navigationClientWidth = 0; | |||
this.navigationClientHeight = 0; | |||
} | |||
var offset = 15; | |||
var intermediateOffset = 7; | |||
var navigationNodes = [ | |||
{id: 'navigation_up', shape: 'image', image: DIR + '/uparrow.png', triggerFunction: "_moveUp", | |||
verticalAlignTop: false, x: 45 + offset + intermediateOffset, y: this.navigationClientHeight - 45 - offset - intermediateOffset}, | |||
{id: 'navigation_down', shape: 'image', image: DIR + '/downarrow.png', triggerFunction: "_moveDown", | |||
verticalAlignTop: false, x: 45 + offset + intermediateOffset, y: this.navigationClientHeight - 15 - offset}, | |||
{id: 'navigation_left', shape: 'image', image: DIR + '/leftarrow.png', triggerFunction: "_moveLeft", | |||
verticalAlignTop: false, x: 15 + offset, y: this.navigationClientHeight - 15 - offset}, | |||
{id: 'navigation_right', shape: 'image', image: DIR + '/rightarrow.png',triggerFunction: "_moveRight", | |||
verticalAlignTop: false, x: 75 + offset + 2 * intermediateOffset, y: this.navigationClientHeight - 15 - offset}, | |||
{id: 'navigation_plus', shape: 'image', image: DIR + '/plus.png', triggerFunction: "_zoomIn", | |||
verticalAlignTop: false, horizontalAlignLeft: false, | |||
x: this.navigationClientWidth - 45 - offset - intermediateOffset, y: this.navigationClientHeight - 15 - offset}, | |||
{id: 'navigation_min', shape: 'image', image: DIR + '/minus.png', triggerFunction: "_zoomOut", | |||
verticalAlignTop: false, horizontalAlignLeft: false, | |||
x: this.navigationClientWidth - 15 - offset, y: this.navigationClientHeight - 15 - offset}, | |||
{id: 'navigation_zoomExtends', shape: 'image', image: DIR + '/zoomExtends.png', triggerFunction: "zoomToFit", | |||
verticalAlignTop: false, horizontalAlignLeft: false, | |||
x: this.navigationClientWidth - 15 - offset, y: this.navigationClientHeight - 45 - offset - intermediateOffset} | |||
]; | |||
var nodeObj = null; | |||
for (var i = 0; i < navigationNodes.length; i++) { | |||
nodeObj = this.sectors["navigation"]['nodes']; | |||
nodeObj[navigationNodes[i]['id']] = new Node(navigationNodes[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 | |||
*/ | |||
_highlightNavigationElement : function(elementId) { | |||
if (this.sectors["navigation"]["nodes"].hasOwnProperty(elementId)) { | |||
this.sectors["navigation"]["nodes"][elementId].clusterSize = 2; | |||
} | |||
}, | |||
/** | |||
* Reverting back to a normal button | |||
* | |||
* @param {String} elementId | |||
* @private | |||
*/ | |||
_unHighlightNavigationElement : function(elementId) { | |||
if (this.sectors["navigation"]["nodes"].hasOwnProperty(elementId)) { | |||
this.sectors["navigation"]["nodes"][elementId].clusterSize = 1; | |||
} | |||
}, | |||
/** | |||
* un-highlight (for lack of a better term) all navigation controls elements | |||
* @private | |||
*/ | |||
_unHighlightAll : function() { | |||
for (var nodeId in this.sectors['navigation']['nodes']) { | |||
if (this.sectors['navigation']['nodes'].hasOwnProperty(nodeId)) { | |||
this._unHighlightNavigationElement(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._highlightNavigationElement("navigation_up"); | |||
this.yIncrement = this.constants.keyboard.speed.y; | |||
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._highlightNavigationElement("navigation_down"); | |||
this.yIncrement = -this.constants.keyboard.speed.y; | |||
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._highlightNavigationElement("navigation_left"); | |||
this.xIncrement = this.constants.keyboard.speed.x; | |||
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._highlightNavigationElement("navigation_right"); | |||
this.xIncrement = -this.constants.keyboard.speed.y; | |||
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._highlightNavigationElement("navigation_plus"); | |||
this.zoomIncrement = this.constants.keyboard.speed.zoom; | |||
this.start(); // if there is no node movement, the calculation wont be done | |||
this._preventDefault(event); | |||
}, | |||
/** | |||
* Zoom out | |||
* @private | |||
*/ | |||
_zoomOut : function() { | |||
this._highlightNavigationElement("navigation_min"); | |||
this.zoomIncrement = -this.constants.keyboard.speed.zoom; | |||
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._unHighlightNavigationElement("navigation_plus"); | |||
this._unHighlightNavigationElement("navigation_min"); | |||
this.zoomIncrement = 0; | |||
}, | |||
/** | |||
* Stop moving in the Y direction and unHighlight the up and down | |||
* @private | |||
*/ | |||
_yStopMoving : function() { | |||
this._unHighlightNavigationElement("navigation_up"); | |||
this._unHighlightNavigationElement("navigation_down"); | |||
this.yIncrement = 0; | |||
}, | |||
/** | |||
* Stop moving in the X direction and unHighlight left and right. | |||
* @private | |||
*/ | |||
_xStopMoving : function() { | |||
this._unHighlightNavigationElement("navigation_left"); | |||
this._unHighlightNavigationElement("navigation_right"); | |||
this.xIncrement = 0; | |||
} | |||
}; |
@ -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 navigation controls sector. | |||
* | |||
* @private | |||
*/ | |||
_switchToNavigationSector : function() { | |||
this.nodeIndices = this.sectors["navigation"]["nodeIndices"]; | |||
this.nodes = this.sectors["navigation"]["nodes"]; | |||
this.edges = this.sectors["navigation"]["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 navigation controls 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 | |||
*/ | |||
_doInNavigationSector : function(runFunction,argument) { | |||
this._switchToNavigationSector(); | |||
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,515 @@ | |||
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 navigation controls 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 | |||
*/ | |||
_getAllNavigationNodesOverlappingWith : function (object) { | |||
var overlappingNodes = []; | |||
this._doInNavigationSector("_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 navigation controls node at the a specific point (like a click) | |||
* | |||
* @param {{x: Number, y: Number}} pointer | |||
* @return {Node | null} node | |||
* @private | |||
*/ | |||
_getNavigationNodeAt : function (pointer) { | |||
var screenPositionObject = this._pointerToScreenPositionObject(pointer); | |||
var overlappingNodes = this._getAllNavigationNodesOverlappingWith(screenPositionObject); | |||
if (overlappingNodes.length > 0) { | |||
return this.sectors["navigation"]["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 navigation controls 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. | |||
* | |||
* @param {Boolean} [doNotTrigger] | ignore trigger | |||
* @private | |||
*/ | |||
_unselectAll : function(doNotTrigger) { | |||
if (doNotTrigger === undefined) { | |||
doNotTrigger = false; | |||
} | |||
this.selection = []; | |||
for (var objId in this.selectionObj) { | |||
if (this.selectionObj.hasOwnProperty(objId)) { | |||
this.selectionObj[objId].unselect(); | |||
} | |||
} | |||
this.selectionObj = {}; | |||
if (doNotTrigger == false) { | |||
this._trigger('select', { | |||
nodes: this.getSelection() | |||
}); | |||
} | |||
}, | |||
/** | |||
* 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 | |||
* @param {Boolean} [doNotTrigger] | ignore trigger | |||
* @private | |||
*/ | |||
_selectNode : function(node, append, doNotTrigger) { | |||
if (doNotTrigger === undefined) { | |||
doNotTrigger = false; | |||
} | |||
if (this._selectionIsEmpty() == false && append == false) { | |||
this._unselectAll(true); | |||
} | |||
if (node.selected == false) { | |||
node.select(); | |||
this._addToSelection(node); | |||
} | |||
else { | |||
node.unselect(); | |||
this._removeFromSelection(node); | |||
} | |||
if (doNotTrigger == false) { | |||
this._trigger('select', { | |||
nodes: this.getSelection() | |||
}); | |||
} | |||
}, | |||
/** | |||
* handles the selection part of the touch, only for navigation controls 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) { | |||
if (this.constants.navigation.enabled == true) { | |||
var node = this._getNavigationNodeAt(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 navigation controls module. | |||
* | |||
* @private | |||
*/ | |||
_handleOnRelease : function() { | |||
this.xIncrement = 0; | |||
this.yIncrement = 0; | |||
this.zoomIncrement = 0; | |||
this._unHighlightAll(); | |||
}, | |||
/** | |||
* | |||
* retrieve the currently selected nodes | |||
* @return {Number[] | String[]} selection An array with the ids of the | |||
* selected nodes. | |||
*/ | |||
getSelection : function() { | |||
return this.selection.concat([]); | |||
}, | |||
/** | |||
* | |||
* retrieve the currently selected nodes as objects | |||
* @return {Objects} selection An array with the ids of the | |||
* selected nodes. | |||
*/ | |||
getSelectionObjects : function() { | |||
return this.selectionObj; | |||
}, | |||
/** | |||
* // 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 | |||
this._unselectAll(true); | |||
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'); | |||
} | |||
this._selectNode(node,true,true); | |||
} | |||
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', { | |||
nodes: this.getSelection() | |||
}); | |||
} | |||
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', { | |||
nodes: this.getSelection() | |||
}); | |||
} | |||
return changed; | |||
}, | |||
*/ | |||
}; | |||