Conflicts: dist/vis.jscss_transitions
@ -1,293 +1,214 @@ | |||
<!doctype html> | |||
<html> | |||
<head> | |||
<title>Graph | Navigation</title> | |||
<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; | |||
} | |||
div.graph-manipulationDiv { | |||
border-width:0px; | |||
border-bottom: 1px; | |||
border-style:solid; | |||
border-color: #d6d9d8; | |||
background: #ffffff; /* Old browsers */ | |||
background: -moz-linear-gradient(top, #ffffff 0%, #f3f3f3 50%, #ededed 51%, #ffffff 100%); /* FF3.6+ */ | |||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(50%,#f3f3f3), color-stop(51%,#ededed), color-stop(100%,#ffffff)); /* Chrome,Safari4+ */ | |||
background: -webkit-linear-gradient(top, #ffffff 0%,#f3f3f3 50%,#ededed 51%,#ffffff 100%); /* Chrome10+,Safari5.1+ */ | |||
background: -o-linear-gradient(top, #ffffff 0%,#f3f3f3 50%,#ededed 51%,#ffffff 100%); /* Opera 11.10+ */ | |||
background: -ms-linear-gradient(top, #ffffff 0%,#f3f3f3 50%,#ededed 51%,#ffffff 100%); /* IE10+ */ | |||
background: linear-gradient(to bottom, #ffffff 0%,#f3f3f3 50%,#ededed 51%,#ffffff 100%); /* W3C */ | |||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ffffff',GradientType=0 ); /* IE6-9 */ | |||
width: 600px; | |||
height:30px; | |||
z-index:10; | |||
position:absolute; | |||
} | |||
span.manipulationUI { | |||
font-family: verdana; | |||
font-size: 12px; | |||
-moz-border-radius: 15px; | |||
border-radius: 15px; | |||
display:inline-block; | |||
background-position: 0px 0px; | |||
background-repeat:no-repeat; | |||
height:24px; | |||
margin: -14px 0px 0px 10px; | |||
vertical-align:middle; | |||
cursor: pointer; | |||
padding: 0px 8px 0px 8px; | |||
-webkit-touch-callout: none; | |||
-webkit-user-select: none; | |||
-khtml-user-select: none; | |||
-moz-user-select: none; | |||
-ms-user-select: none; | |||
user-select: none; | |||
} | |||
span.manipulationUI:hover { | |||
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.20); | |||
} | |||
span.manipulationUI:active { | |||
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.50); | |||
} | |||
span.manipulationUI.back { | |||
background-image: url("../../dist/img/backIcon.png"); | |||
} | |||
span.manipulationUI.none:hover { | |||
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0); | |||
cursor: default; | |||
} | |||
span.manipulationUI.none:active { | |||
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0); | |||
} | |||
span.manipulationUI.none { | |||
padding: 0px 0px 0px 0px; | |||
} | |||
span.manipulationUI.notification{ | |||
margin: 2px; | |||
font-weight: bold; | |||
} | |||
span.manipulationUI.add { | |||
background-image: url("../../dist/img/addNodeIcon.png"); | |||
} | |||
span.manipulationUI.edit { | |||
background-image: url("../../dist/img/editIcon.png"); | |||
} | |||
span.manipulationUI.connect { | |||
background-image: url("../../dist/img/connectIcon.png"); | |||
} | |||
span.manipulationUI.delete { | |||
background-image: url("../../dist/img/deleteIcon.png"); | |||
} | |||
span.manipulationUI.acceptDelete { | |||
background-image: url("../../dist/img/acceptDeleteIcon.png"); | |||
} | |||
/* top right bottom left */ | |||
span.manipulationLabel { | |||
margin: 0px 0px 0px 23px; | |||
line-height: 25px; | |||
} | |||
div.seperatorLine { | |||
display:inline-block; | |||
width:1px; | |||
height:20px; | |||
background-color: #bdbdbd; | |||
margin: 5px 7px 0px 15px; | |||
} | |||
input.manipulatorInput[type="text"] { | |||
width:80px; | |||
height:15px; | |||
font-size:11px; | |||
margin: 2px 0px 0px 0px; | |||
} | |||
input.manipulatorInput[type="button"] { | |||
width:80px; | |||
height:22px; | |||
font-size:12px; | |||
margin: 2px 0px 0px 10px; | |||
} | |||
</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]++; | |||
<style type="text/css"> | |||
body { | |||
font: 10pt sans; | |||
} | |||
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]++; | |||
#mygraph { | |||
position:relative; | |||
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; | |||
} | |||
} | |||
// 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, | |||
clustering:true, | |||
navigation: true, | |||
keyboard: true, | |||
dataManipulationToolbar: true | |||
}; | |||
graph = new vis.Graph(container, data, options); | |||
// add event listeners | |||
graph.on('select', function(params) { | |||
document.getElementById('selection').innerHTML = 'Selection: ' + params.nodes; | |||
}); | |||
} | |||
</script> | |||
#operation { | |||
font-size:28px; | |||
} | |||
#graph-popUp { | |||
display:none; | |||
position:absolute; | |||
top:350px; | |||
left:170px; | |||
z-index:299; | |||
width:250px; | |||
height:120px; | |||
background-color: #f9f9f9; | |||
border-style:solid; | |||
border-width:3px; | |||
border-color: #5394ed; | |||
padding:10px; | |||
text-align: center; | |||
} | |||
</style> | |||
<link type="text/css" rel="stylesheet" charset="UTF-8" href="../../dist/css/graph-manipulation.css"> | |||
<link type="text/css" rel="stylesheet" charset="UTF-8" href="../../dist/css/graph-navigation.css"> | |||
<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 = 25; | |||
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 = { | |||
edges: { | |||
length: 50 | |||
}, | |||
stabilize: false, | |||
dataManipulation: true, | |||
onAdd: function(data,callback) { | |||
var span = document.getElementById('operation'); | |||
var idInput = document.getElementById('node-id'); | |||
var labelInput = document.getElementById('node-label'); | |||
var saveButton = document.getElementById('saveButton'); | |||
var cancelButton = document.getElementById('cancelButton'); | |||
var div = document.getElementById('graph-popUp'); | |||
span.innerHTML = "Add Node"; | |||
idInput.value = data.id; | |||
labelInput.value = data.label; | |||
saveButton.onclick = saveData.bind(this,data,callback); | |||
cancelButton.onclick = clearPopUp.bind(); | |||
div.style.display = 'block'; | |||
}, | |||
onEdit: function(data,callback) { | |||
var span = document.getElementById('operation'); | |||
var idInput = document.getElementById('node-id'); | |||
var labelInput = document.getElementById('node-label'); | |||
var saveButton = document.getElementById('saveButton'); | |||
var cancelButton = document.getElementById('cancelButton'); | |||
var div = document.getElementById('graph-popUp'); | |||
span.innerHTML = "Edit Node"; | |||
idInput.value = data.id; | |||
labelInput.value = data.label; | |||
saveButton.onclick = saveData.bind(this,data,callback); | |||
cancelButton.onclick = clearPopUp.bind(); | |||
div.style.display = 'block'; | |||
} | |||
}; | |||
graph = new vis.Graph(container, data, options); | |||
// add event listeners | |||
graph.on('select', function(params) { | |||
document.getElementById('selection').innerHTML = 'Selection: ' + params.nodes; | |||
}); | |||
graph.on("frameResize", function(params) {console.log(params.width,params.height)}); | |||
function clearPopUp() { | |||
var saveButton = document.getElementById('saveButton'); | |||
var cancelButton = document.getElementById('cancelButton'); | |||
saveButton.onclick = null; | |||
cancelButton.onclick = null; | |||
var div = document.getElementById('graph-popUp'); | |||
div.style.display = 'none'; | |||
} | |||
function saveData(data,callback) { | |||
var idInput = document.getElementById('node-id'); | |||
var labelInput = document.getElementById('node-label'); | |||
var div = document.getElementById('graph-popUp'); | |||
data.id = idInput.value; | |||
data.label = labelInput.value; | |||
clearPopUp(); | |||
callback(data); | |||
} | |||
} | |||
</script> | |||
</head> | |||
<body onload="draw();"> | |||
<h2>Navigation controls and keyboad navigation</h2> | |||
<h2>Editing the dataset</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. | |||
In this example we have enabled the data manipulation setting. If the dataManipulation option is set to true, the edit button will appear. | |||
If you prefer to have the toolbar visible initially, you can set the initiallyVisible option to true. The exact method is described in the docs. | |||
<br /><br /> | |||
The data manipulation allows the user to add nodes, connect them, edit them and delete any selected items. In this example we have created trigger functions | |||
for the add and edit operations. By settings these trigger functions the user can direct the way the data is manipulated. In this example we have created a simple | |||
pop-up that allows us to edit some of the properties. | |||
</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="graph-popUp"> | |||
<span id="operation">node</span> <br> | |||
<table style="margin:auto;"><tr> | |||
<td>id</td><td><input id="node-id" value="new value"></td> | |||
</tr> | |||
<tr> | |||
<td>label</td><td><input id="node-label" value="new value"> </td> | |||
</tr></table> | |||
<input type="button" value="save" id="saveButton"></button> | |||
<input type="button" value="cancel" id="cancelButton"></button> | |||
</div> | |||
<br /> | |||
<div id="mygraph"></div> | |||
<p id="selection"></p> | |||
</body> | |||
</html> | |||
@ -0,0 +1,373 @@ | |||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | |||
<html> | |||
<head> | |||
<title>Graph | Multiline text</title> | |||
<style type="text/css"> | |||
#mygraph { | |||
width: 900px; | |||
height: 900px; | |||
border: 1px solid lightgray; | |||
} | |||
</style> | |||
<script type="text/javascript" src="../../dist/vis.js"></script> | |||
<script type="text/javascript"> | |||
function draw() { | |||
// create some nodes | |||
var nodes = [ | |||
{id:0,"labelIGNORE":"Myriel","group":1}, | |||
{id:1,"labelIGNORE":"Napoleon","group":1}, | |||
{id:2,"labelIGNORE":"Mlle.Baptistine","group":1}, | |||
{id:3,"labelIGNORE":"Mme.Magloire","group":1}, | |||
{id:4,"labelIGNORE":"CountessdeLo","group":1}, | |||
{id:5,"labelIGNORE":"Geborand","group":1}, | |||
{id:6,"labelIGNORE":"Champtercier","group":1}, | |||
{id:7,"labelIGNORE":"Cravatte","group":1}, | |||
{id:8,"labelIGNORE":"Count","group":1}, | |||
{id:9,"labelIGNORE":"OldMan","group":1}, | |||
{id:10,"labelIGNORE":"Labarre","group":2}, | |||
{id:11,"labelIGNORE":"Valjean","group":2}, | |||
{id:12,"labelIGNORE":"Marguerite","group":3}, | |||
{id:13,"labelIGNORE":"Mme.deR","group":2}, | |||
{id:14,"labelIGNORE":"Isabeau","group":2}, | |||
{id:15,"labelIGNORE":"Gervais","group":2}, | |||
{id:16,"labelIGNORE":"Tholomyes","group":3}, | |||
{id:17,"labelIGNORE":"Listolier","group":3}, | |||
{id:18,"labelIGNORE":"Fameuil","group":3}, | |||
{id:19,"labelIGNORE":"Blacheville","group":3}, | |||
{id:20,"labelIGNORE":"Favourite","group":3}, | |||
{id:21,"labelIGNORE":"Dahlia","group":3}, | |||
{id:22,"labelIGNORE":"Zephine","group":3}, | |||
{id:23,"labelIGNORE":"Fantine","group":3}, | |||
{id:24,"labelIGNORE":"Mme.Thenardier","group":4}, | |||
{id:25,"labelIGNORE":"Thenardier","group":4}, | |||
{id:26,"labelIGNORE":"Cosette","group":5}, | |||
{id:27,"labelIGNORE":"Javert","group":4}, | |||
{id:28,"labelIGNORE":"Fauchelevent","group":0}, | |||
{id:29,"labelIGNORE":"Bamatabois","group":2}, | |||
{id:30,"labelIGNORE":"Perpetue","group":3}, | |||
{id:31,"labelIGNORE":"Simplice","group":2}, | |||
{id:32,"labelIGNORE":"Scaufflaire","group":2}, | |||
{id:33,"labelIGNORE":"Woman1","group":2}, | |||
{id:34,"labelIGNORE":"Judge","group":2}, | |||
{id:35,"labelIGNORE":"Champmathieu","group":2}, | |||
{id:36,"labelIGNORE":"Brevet","group":2}, | |||
{id:37,"labelIGNORE":"Chenildieu","group":2}, | |||
{id:38,"labelIGNORE":"Cochepaille","group":2}, | |||
{id:39,"labelIGNORE":"Pontmercy","group":4}, | |||
{id:40,"labelIGNORE":"Boulatruelle","group":6}, | |||
{id:41,"labelIGNORE":"Eponine","group":4}, | |||
{id:42,"labelIGNORE":"Anzelma","group":4}, | |||
{id:43,"labelIGNORE":"Woman2","group":5}, | |||
{id:44,"labelIGNORE":"MotherInnocent","group":0}, | |||
{id:45,"labelIGNORE":"Gribier","group":0}, | |||
{id:46,"labelIGNORE":"Jondrette","group":7}, | |||
{id:47,"labelIGNORE":"Mme.Burgon","group":7}, | |||
{id:48,"labelIGNORE":"Gavroche","group":8}, | |||
{id:49,"labelIGNORE":"Gillenormand","group":5}, | |||
{id:50,"labelIGNORE":"Magnon","group":5}, | |||
{id:51,"labelIGNORE":"Mlle.Gillenormand","group":5}, | |||
{id:52,"labelIGNORE":"Mme.Pontmercy","group":5}, | |||
{id:53,"labelIGNORE":"Mlle.Vaubois","group":5}, | |||
{id:54,"labelIGNORE":"Lt.Gillenormand","group":5}, | |||
{id:55,"labelIGNORE":"Marius","group":8}, | |||
{id:56,"labelIGNORE":"BaronessT","group":5}, | |||
{id:57,"labelIGNORE":"Mabeuf","group":8}, | |||
{id:58,"labelIGNORE":"Enjolras","group":8}, | |||
{id:59,"labelIGNORE":"Combeferre","group":8}, | |||
{id:60,"labelIGNORE":"Prouvaire","group":8}, | |||
{id:61,"labelIGNORE":"Feuilly","group":8}, | |||
{id:62,"labelIGNORE":"Courfeyrac","group":8}, | |||
{id:63,"labelIGNORE":"Bahorel","group":8}, | |||
{id:64,"labelIGNORE":"Bossuet","group":8}, | |||
{id:65,"labelIGNORE":"Joly","group":8}, | |||
{id:66,"labelIGNORE":"Grantaire","group":8}, | |||
{id:67,"labelIGNORE":"MotherPlutarch","group":9}, | |||
{id:68,"labelIGNORE":"Gueulemer","group":4}, | |||
{id:69,"labelIGNORE":"Babet","group":4}, | |||
{id:70,"labelIGNORE":"Claquesous","group":4}, | |||
{id:71,"labelIGNORE":"Montparnasse","group":4}, | |||
{id:72,"labelIGNORE":"Toussaint","group":5}, | |||
{id:73,"labelIGNORE":"Child1","group":10}, | |||
{id:74,"labelIGNORE":"Child2","group":10}, | |||
{id:75,"labelIGNORE":"Brujon","group":4}, | |||
{id:76,"labelIGNORE":"Mme.Hucheloup","group":8} | |||
]; | |||
// create some edges | |||
var edges = [ | |||
{"from":1,"to":0,"valueIGNORED":1}, | |||
{"from":2,"to":0,"valueIGNORED":8}, | |||
{"from":3,"to":0,"valueIGNORED":10}, | |||
{"from":3,"to":2,"valueIGNORED":6}, | |||
{"from":4,"to":0,"valueIGNORED":1}, | |||
{"from":5,"to":0,"valueIGNORED":1}, | |||
{"from":6,"to":0,"valueIGNORED":1}, | |||
{"from":7,"to":0,"valueIGNORED":1}, | |||
{"from":8,"to":0,"valueIGNORED":2}, | |||
{"from":9,"to":0,"valueIGNORED":1}, | |||
{"from":11,"to":10,"valueIGNORED":1}, | |||
{"from":11,"to":3,"valueIGNORED":3}, | |||
{"from":11,"to":2,"valueIGNORED":3}, | |||
{"from":11,"to":0,"valueIGNORED":5}, | |||
{"from":12,"to":11,"valueIGNORED":1}, | |||
{"from":13,"to":11,"valueIGNORED":1}, | |||
{"from":14,"to":11,"valueIGNORED":1}, | |||
{"from":15,"to":11,"valueIGNORED":1}, | |||
{"from":17,"to":16,"valueIGNORED":4}, | |||
{"from":18,"to":16,"valueIGNORED":4}, | |||
{"from":18,"to":17,"valueIGNORED":4}, | |||
{"from":19,"to":16,"valueIGNORED":4}, | |||
{"from":19,"to":17,"valueIGNORED":4}, | |||
{"from":19,"to":18,"valueIGNORED":4}, | |||
{"from":20,"to":16,"valueIGNORED":3}, | |||
{"from":20,"to":17,"valueIGNORED":3}, | |||
{"from":20,"to":18,"valueIGNORED":3}, | |||
{"from":20,"to":19,"valueIGNORED":4}, | |||
{"from":21,"to":16,"valueIGNORED":3}, | |||
{"from":21,"to":17,"valueIGNORED":3}, | |||
{"from":21,"to":18,"valueIGNORED":3}, | |||
{"from":21,"to":19,"valueIGNORED":3}, | |||
{"from":21,"to":20,"valueIGNORED":5}, | |||
{"from":22,"to":16,"valueIGNORED":3}, | |||
{"from":22,"to":17,"valueIGNORED":3}, | |||
{"from":22,"to":18,"valueIGNORED":3}, | |||
{"from":22,"to":19,"valueIGNORED":3}, | |||
{"from":22,"to":20,"valueIGNORED":4}, | |||
{"from":22,"to":21,"valueIGNORED":4}, | |||
{"from":23,"to":16,"valueIGNORED":3}, | |||
{"from":23,"to":17,"valueIGNORED":3}, | |||
{"from":23,"to":18,"valueIGNORED":3}, | |||
{"from":23,"to":19,"valueIGNORED":3}, | |||
{"from":23,"to":20,"valueIGNORED":4}, | |||
{"from":23,"to":21,"valueIGNORED":4}, | |||
{"from":23,"to":22,"valueIGNORED":4}, | |||
{"from":23,"to":12,"valueIGNORED":2}, | |||
{"from":23,"to":11,"valueIGNORED":9}, | |||
{"from":24,"to":23,"valueIGNORED":2}, | |||
{"from":24,"to":11,"valueIGNORED":7}, | |||
{"from":25,"to":24,"valueIGNORED":13}, | |||
{"from":25,"to":23,"valueIGNORED":1}, | |||
{"from":25,"to":11,"valueIGNORED":12}, | |||
{"from":26,"to":24,"valueIGNORED":4}, | |||
{"from":26,"to":11,"valueIGNORED":31}, | |||
{"from":26,"to":16,"valueIGNORED":1}, | |||
{"from":26,"to":25,"valueIGNORED":1}, | |||
{"from":27,"to":11,"valueIGNORED":17}, | |||
{"from":27,"to":23,"valueIGNORED":5}, | |||
{"from":27,"to":25,"valueIGNORED":5}, | |||
{"from":27,"to":24,"valueIGNORED":1}, | |||
{"from":27,"to":26,"valueIGNORED":1}, | |||
{"from":28,"to":11,"valueIGNORED":8}, | |||
{"from":28,"to":27,"valueIGNORED":1}, | |||
{"from":29,"to":23,"valueIGNORED":1}, | |||
{"from":29,"to":27,"valueIGNORED":1}, | |||
{"from":29,"to":11,"valueIGNORED":2}, | |||
{"from":30,"to":23,"valueIGNORED":1}, | |||
{"from":31,"to":30,"valueIGNORED":2}, | |||
{"from":31,"to":11,"valueIGNORED":3}, | |||
{"from":31,"to":23,"valueIGNORED":2}, | |||
{"from":31,"to":27,"valueIGNORED":1}, | |||
{"from":32,"to":11,"valueIGNORED":1}, | |||
{"from":33,"to":11,"valueIGNORED":2}, | |||
{"from":33,"to":27,"valueIGNORED":1}, | |||
{"from":34,"to":11,"valueIGNORED":3}, | |||
{"from":34,"to":29,"valueIGNORED":2}, | |||
{"from":35,"to":11,"valueIGNORED":3}, | |||
{"from":35,"to":34,"valueIGNORED":3}, | |||
{"from":35,"to":29,"valueIGNORED":2}, | |||
{"from":36,"to":34,"valueIGNORED":2}, | |||
{"from":36,"to":35,"valueIGNORED":2}, | |||
{"from":36,"to":11,"valueIGNORED":2}, | |||
{"from":36,"to":29,"valueIGNORED":1}, | |||
{"from":37,"to":34,"valueIGNORED":2}, | |||
{"from":37,"to":35,"valueIGNORED":2}, | |||
{"from":37,"to":36,"valueIGNORED":2}, | |||
{"from":37,"to":11,"valueIGNORED":2}, | |||
{"from":37,"to":29,"valueIGNORED":1}, | |||
{"from":38,"to":34,"valueIGNORED":2}, | |||
{"from":38,"to":35,"valueIGNORED":2}, | |||
{"from":38,"to":36,"valueIGNORED":2}, | |||
{"from":38,"to":37,"valueIGNORED":2}, | |||
{"from":38,"to":11,"valueIGNORED":2}, | |||
{"from":38,"to":29,"valueIGNORED":1}, | |||
{"from":39,"to":25,"valueIGNORED":1}, | |||
{"from":40,"to":25,"valueIGNORED":1}, | |||
{"from":41,"to":24,"valueIGNORED":2}, | |||
{"from":41,"to":25,"valueIGNORED":3}, | |||
{"from":42,"to":41,"valueIGNORED":2}, | |||
{"from":42,"to":25,"valueIGNORED":2}, | |||
{"from":42,"to":24,"valueIGNORED":1}, | |||
{"from":43,"to":11,"valueIGNORED":3}, | |||
{"from":43,"to":26,"valueIGNORED":1}, | |||
{"from":43,"to":27,"valueIGNORED":1}, | |||
{"from":44,"to":28,"valueIGNORED":3}, | |||
{"from":44,"to":11,"valueIGNORED":1}, | |||
{"from":45,"to":28,"valueIGNORED":2}, | |||
{"from":47,"to":46,"valueIGNORED":1}, | |||
{"from":48,"to":47,"valueIGNORED":2}, | |||
{"from":48,"to":25,"valueIGNORED":1}, | |||
{"from":48,"to":27,"valueIGNORED":1}, | |||
{"from":48,"to":11,"valueIGNORED":1}, | |||
{"from":49,"to":26,"valueIGNORED":3}, | |||
{"from":49,"to":11,"valueIGNORED":2}, | |||
{"from":50,"to":49,"valueIGNORED":1}, | |||
{"from":50,"to":24,"valueIGNORED":1}, | |||
{"from":51,"to":49,"valueIGNORED":9}, | |||
{"from":51,"to":26,"valueIGNORED":2}, | |||
{"from":51,"to":11,"valueIGNORED":2}, | |||
{"from":52,"to":51,"valueIGNORED":1}, | |||
{"from":52,"to":39,"valueIGNORED":1}, | |||
{"from":53,"to":51,"valueIGNORED":1}, | |||
{"from":54,"to":51,"valueIGNORED":2}, | |||
{"from":54,"to":49,"valueIGNORED":1}, | |||
{"from":54,"to":26,"valueIGNORED":1}, | |||
{"from":55,"to":51,"valueIGNORED":6}, | |||
{"from":55,"to":49,"valueIGNORED":12}, | |||
{"from":55,"to":39,"valueIGNORED":1}, | |||
{"from":55,"to":54,"valueIGNORED":1}, | |||
{"from":55,"to":26,"valueIGNORED":21}, | |||
{"from":55,"to":11,"valueIGNORED":19}, | |||
{"from":55,"to":16,"valueIGNORED":1}, | |||
{"from":55,"to":25,"valueIGNORED":2}, | |||
{"from":55,"to":41,"valueIGNORED":5}, | |||
{"from":55,"to":48,"valueIGNORED":4}, | |||
{"from":56,"to":49,"valueIGNORED":1}, | |||
{"from":56,"to":55,"valueIGNORED":1}, | |||
{"from":57,"to":55,"valueIGNORED":1}, | |||
{"from":57,"to":41,"valueIGNORED":1}, | |||
{"from":57,"to":48,"valueIGNORED":1}, | |||
{"from":58,"to":55,"valueIGNORED":7}, | |||
{"from":58,"to":48,"valueIGNORED":7}, | |||
{"from":58,"to":27,"valueIGNORED":6}, | |||
{"from":58,"to":57,"valueIGNORED":1}, | |||
{"from":58,"to":11,"valueIGNORED":4}, | |||
{"from":59,"to":58,"valueIGNORED":15}, | |||
{"from":59,"to":55,"valueIGNORED":5}, | |||
{"from":59,"to":48,"valueIGNORED":6}, | |||
{"from":59,"to":57,"valueIGNORED":2}, | |||
{"from":60,"to":48,"valueIGNORED":1}, | |||
{"from":60,"to":58,"valueIGNORED":4}, | |||
{"from":60,"to":59,"valueIGNORED":2}, | |||
{"from":61,"to":48,"valueIGNORED":2}, | |||
{"from":61,"to":58,"valueIGNORED":6}, | |||
{"from":61,"to":60,"valueIGNORED":2}, | |||
{"from":61,"to":59,"valueIGNORED":5}, | |||
{"from":61,"to":57,"valueIGNORED":1}, | |||
{"from":61,"to":55,"valueIGNORED":1}, | |||
{"from":62,"to":55,"valueIGNORED":9}, | |||
{"from":62,"to":58,"valueIGNORED":17}, | |||
{"from":62,"to":59,"valueIGNORED":13}, | |||
{"from":62,"to":48,"valueIGNORED":7}, | |||
{"from":62,"to":57,"valueIGNORED":2}, | |||
{"from":62,"to":41,"valueIGNORED":1}, | |||
{"from":62,"to":61,"valueIGNORED":6}, | |||
{"from":62,"to":60,"valueIGNORED":3}, | |||
{"from":63,"to":59,"valueIGNORED":5}, | |||
{"from":63,"to":48,"valueIGNORED":5}, | |||
{"from":63,"to":62,"valueIGNORED":6}, | |||
{"from":63,"to":57,"valueIGNORED":2}, | |||
{"from":63,"to":58,"valueIGNORED":4}, | |||
{"from":63,"to":61,"valueIGNORED":3}, | |||
{"from":63,"to":60,"valueIGNORED":2}, | |||
{"from":63,"to":55,"valueIGNORED":1}, | |||
{"from":64,"to":55,"valueIGNORED":5}, | |||
{"from":64,"to":62,"valueIGNORED":12}, | |||
{"from":64,"to":48,"valueIGNORED":5}, | |||
{"from":64,"to":63,"valueIGNORED":4}, | |||
{"from":64,"to":58,"valueIGNORED":10}, | |||
{"from":64,"to":61,"valueIGNORED":6}, | |||
{"from":64,"to":60,"valueIGNORED":2}, | |||
{"from":64,"to":59,"valueIGNORED":9}, | |||
{"from":64,"to":57,"valueIGNORED":1}, | |||
{"from":64,"to":11,"valueIGNORED":1}, | |||
{"from":65,"to":63,"valueIGNORED":5}, | |||
{"from":65,"to":64,"valueIGNORED":7}, | |||
{"from":65,"to":48,"valueIGNORED":3}, | |||
{"from":65,"to":62,"valueIGNORED":5}, | |||
{"from":65,"to":58,"valueIGNORED":5}, | |||
{"from":65,"to":61,"valueIGNORED":5}, | |||
{"from":65,"to":60,"valueIGNORED":2}, | |||
{"from":65,"to":59,"valueIGNORED":5}, | |||
{"from":65,"to":57,"valueIGNORED":1}, | |||
{"from":65,"to":55,"valueIGNORED":2}, | |||
{"from":66,"to":64,"valueIGNORED":3}, | |||
{"from":66,"to":58,"valueIGNORED":3}, | |||
{"from":66,"to":59,"valueIGNORED":1}, | |||
{"from":66,"to":62,"valueIGNORED":2}, | |||
{"from":66,"to":65,"valueIGNORED":2}, | |||
{"from":66,"to":48,"valueIGNORED":1}, | |||
{"from":66,"to":63,"valueIGNORED":1}, | |||
{"from":66,"to":61,"valueIGNORED":1}, | |||
{"from":66,"to":60,"valueIGNORED":1}, | |||
{"from":67,"to":57,"valueIGNORED":3}, | |||
{"from":68,"to":25,"valueIGNORED":5}, | |||
{"from":68,"to":11,"valueIGNORED":1}, | |||
{"from":68,"to":24,"valueIGNORED":1}, | |||
{"from":68,"to":27,"valueIGNORED":1}, | |||
{"from":68,"to":48,"valueIGNORED":1}, | |||
{"from":68,"to":41,"valueIGNORED":1}, | |||
{"from":69,"to":25,"valueIGNORED":6}, | |||
{"from":69,"to":68,"valueIGNORED":6}, | |||
{"from":69,"to":11,"valueIGNORED":1}, | |||
{"from":69,"to":24,"valueIGNORED":1}, | |||
{"from":69,"to":27,"valueIGNORED":2}, | |||
{"from":69,"to":48,"valueIGNORED":1}, | |||
{"from":69,"to":41,"valueIGNORED":1}, | |||
{"from":70,"to":25,"valueIGNORED":4}, | |||
{"from":70,"to":69,"valueIGNORED":4}, | |||
{"from":70,"to":68,"valueIGNORED":4}, | |||
{"from":70,"to":11,"valueIGNORED":1}, | |||
{"from":70,"to":24,"valueIGNORED":1}, | |||
{"from":70,"to":27,"valueIGNORED":1}, | |||
{"from":70,"to":41,"valueIGNORED":1}, | |||
{"from":70,"to":58,"valueIGNORED":1}, | |||
{"from":71,"to":27,"valueIGNORED":1}, | |||
{"from":71,"to":69,"valueIGNORED":2}, | |||
{"from":71,"to":68,"valueIGNORED":2}, | |||
{"from":71,"to":70,"valueIGNORED":2}, | |||
{"from":71,"to":11,"valueIGNORED":1}, | |||
{"from":71,"to":48,"valueIGNORED":1}, | |||
{"from":71,"to":41,"valueIGNORED":1}, | |||
{"from":71,"to":25,"valueIGNORED":1}, | |||
{"from":72,"to":26,"valueIGNORED":2}, | |||
{"from":72,"to":27,"valueIGNORED":1}, | |||
{"from":72,"to":11,"valueIGNORED":1}, | |||
{"from":73,"to":48,"valueIGNORED":2}, | |||
{"from":74,"to":48,"valueIGNORED":2}, | |||
{"from":74,"to":73,"valueIGNORED":3}, | |||
{"from":75,"to":69,"valueIGNORED":3}, | |||
{"from":75,"to":68,"valueIGNORED":3}, | |||
{"from":75,"to":25,"valueIGNORED":3}, | |||
{"from":75,"to":48,"valueIGNORED":1}, | |||
{"from":75,"to":41,"valueIGNORED":1}, | |||
{"from":75,"to":70,"valueIGNORED":1}, | |||
{"from":75,"to":71,"valueIGNORED":1}, | |||
{"from":76,"to":64,"valueIGNORED":1}, | |||
{"from":76,"to":65,"valueIGNORED":1}, | |||
{"from":76,"to":66,"valueIGNORED":1}, | |||
{"from":76,"to":63,"valueIGNORED":1}, | |||
{"from":76,"to":62,"valueIGNORED":1}, | |||
{"from":76,"to":48,"valueIGNORED":1}, | |||
{"from":76,"to":58,"valueIGNORED":1} | |||
]; | |||
// create a graph | |||
var container = document.getElementById('mygraph'); | |||
var data = { | |||
nodes: nodes, | |||
edges: edges | |||
}; | |||
var options = {nodes: {shape:'circle'},stabilize: false}; | |||
var graph = new vis.Graph(container, data, options); | |||
} | |||
</script> | |||
</head> | |||
<body onload="draw()"> | |||
<div id="mygraph"></div> | |||
</body> | |||
</html> |
@ -1,245 +0,0 @@ | |||
/** | |||
* 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,128 @@ | |||
div.graph-manipulationDiv { | |||
border-width:0px; | |||
border-bottom: 1px; | |||
border-style:solid; | |||
border-color: #d6d9d8; | |||
background: #ffffff; /* Old browsers */ | |||
background: -moz-linear-gradient(top, #ffffff 0%, #fcfcfc 48%, #fafafa 50%, #fcfcfc 100%); /* FF3.6+ */ | |||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(48%,#fcfcfc), color-stop(50%,#fafafa), color-stop(100%,#fcfcfc)); /* Chrome,Safari4+ */ | |||
background: -webkit-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* Chrome10+,Safari5.1+ */ | |||
background: -o-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* Opera 11.10+ */ | |||
background: -ms-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* IE10+ */ | |||
background: linear-gradient(to bottom, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* W3C */ | |||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#fcfcfc',GradientType=0 ); /* IE6-9 */ | |||
width: 600px; | |||
height:30px; | |||
z-index:10; | |||
position:absolute; | |||
} | |||
div.graph-manipulation-editMode { | |||
height:30px; | |||
z-index:10; | |||
position:absolute; | |||
margin-top:20px; | |||
} | |||
div.graph-manipulation-closeDiv { | |||
height:30px; | |||
width:30px; | |||
z-index:11; | |||
position:absolute; | |||
margin-top:3px; | |||
margin-left:590px; | |||
background-position: 0px 0px; | |||
background-repeat:no-repeat; | |||
background-image: url("../../dist/img/cross.png"); | |||
cursor: pointer; | |||
-webkit-touch-callout: none; | |||
-webkit-user-select: none; | |||
-khtml-user-select: none; | |||
-moz-user-select: none; | |||
-ms-user-select: none; | |||
user-select: none; | |||
} | |||
span.graph-manipulationUI { | |||
font-family: verdana; | |||
font-size: 12px; | |||
-moz-border-radius: 15px; | |||
border-radius: 15px; | |||
display:inline-block; | |||
background-position: 0px 0px; | |||
background-repeat:no-repeat; | |||
height:24px; | |||
margin: -14px 0px 0px 10px; | |||
vertical-align:middle; | |||
cursor: pointer; | |||
padding: 0px 8px 0px 8px; | |||
-webkit-touch-callout: none; | |||
-webkit-user-select: none; | |||
-khtml-user-select: none; | |||
-moz-user-select: none; | |||
-ms-user-select: none; | |||
user-select: none; | |||
} | |||
span.graph-manipulationUI:hover { | |||
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.20); | |||
} | |||
span.graph-manipulationUI:active { | |||
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.50); | |||
} | |||
span.graph-manipulationUI.back { | |||
background-image: url("../../dist/img/backIcon.png"); | |||
} | |||
span.graph-manipulationUI.none:hover { | |||
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0); | |||
cursor: default; | |||
} | |||
span.graph-manipulationUI.none:active { | |||
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0); | |||
} | |||
span.graph-manipulationUI.none { | |||
padding: 0px 0px 0px 0px; | |||
} | |||
span.graph-manipulationUI.notification{ | |||
margin: 2px; | |||
font-weight: bold; | |||
} | |||
span.graph-manipulationUI.add { | |||
background-image: url("../../dist/img/addNodeIcon.png"); | |||
} | |||
span.graph-manipulationUI.edit { | |||
background-image: url("../../dist/img/editIcon.png"); | |||
} | |||
span.graph-manipulationUI.edit.editmode { | |||
background-color: #fcfcfc; | |||
border-style:solid; | |||
border-width:1px; | |||
border-color: #cccccc; | |||
} | |||
span.graph-manipulationUI.connect { | |||
background-image: url("../../dist/img/connectIcon.png"); | |||
} | |||
span.graph-manipulationUI.delete { | |||
background-image: url("../../dist/img/deleteIcon.png"); | |||
} | |||
/* top right bottom left */ | |||
span.graph-manipulationLabel { | |||
margin: 0px 0px 0px 23px; | |||
line-height: 25px; | |||
} | |||
div.graph-seperatorLine { | |||
display:inline-block; | |||
width:1px; | |||
height:20px; | |||
background-color: #bdbdbd; | |||
margin: 5px 7px 0px 15px; | |||
} |
@ -0,0 +1,62 @@ | |||
div.graph-navigation { | |||
width:34px; | |||
height:34px; | |||
z-index:10; | |||
-moz-border-radius: 17px; | |||
border-radius: 17px; | |||
position:absolute; | |||
display:inline-block; | |||
background-position: 2px 2px; | |||
background-repeat:no-repeat; | |||
cursor: pointer; | |||
-webkit-touch-callout: none; | |||
-webkit-user-select: none; | |||
-khtml-user-select: none; | |||
-moz-user-select: none; | |||
-ms-user-select: none; | |||
user-select: none; | |||
} | |||
div.graph-navigation:hover { | |||
box-shadow: 0px 0px 3px 3px rgba(56, 207, 21, 0.30); | |||
} | |||
div.graph-navigation:active { | |||
box-shadow: 0px 0px 1px 3px rgba(56, 207, 21, 0.95); | |||
} | |||
div.graph-navigation.up { | |||
background-image: url("../../dist/img/upArrow.png"); | |||
margin-top:520px; | |||
margin-left:55px; | |||
} | |||
div.graph-navigation.down { | |||
background-image: url("../../dist/img/downArrow.png"); | |||
margin-top:560px; | |||
margin-left:55px; | |||
} | |||
div.graph-navigation.left { | |||
background-image: url("../../dist/img/leftArrow.png"); | |||
margin-top:560px; | |||
margin-left:15px; | |||
} | |||
div.graph-navigation.right { | |||
background-image: url("../../dist/img/rightArrow.png"); | |||
margin-top:560px; | |||
margin-left:95px; | |||
} | |||
div.graph-navigation.zoomIn { | |||
background-image: url("../../dist/img/plus.png"); | |||
margin-top:560px; | |||
margin-left:555px; | |||
} | |||
div.graph-navigation.zoomOut { | |||
background-image: url("../../dist/img/minus.png"); | |||
margin-top:560px; | |||
margin-left:515px; | |||
} | |||
div.graph-navigation.zoomExtends { | |||
background-image: url("../../dist/img/zoomExtends.png"); | |||
margin-top:520px; | |||
margin-left:555px; | |||
} |
@ -0,0 +1,433 @@ | |||
/** | |||
* Created by Alex on 2/4/14. | |||
*/ | |||
var manipulationMixin = { | |||
/** | |||
* clears the toolbar div element of children | |||
* | |||
* @private | |||
*/ | |||
_clearManipulatorBar : function() { | |||
while (this.manipulationDiv.hasChildNodes()) { | |||
this.manipulationDiv.removeChild(this.manipulationDiv.firstChild); | |||
} | |||
}, | |||
/** | |||
* Manipulation UI temporarily overloads certain functions to extend or replace them. To be able to restore | |||
* these functions to their original functionality, we saved them in this.cachedFunctions. | |||
* This function restores these functions to their original function. | |||
* | |||
* @private | |||
*/ | |||
_restoreOverloadedFunctions : function() { | |||
for (var functionName in this.cachedFunctions) { | |||
if (this.cachedFunctions.hasOwnProperty(functionName)) { | |||
this[functionName] = this.cachedFunctions[functionName]; | |||
} | |||
} | |||
}, | |||
/** | |||
* Enable or disable edit-mode. | |||
* | |||
* @private | |||
*/ | |||
_toggleEditMode : function() { | |||
this.editMode = !this.editMode; | |||
var toolbar = document.getElementById("graph-manipulationDiv"); | |||
var closeDiv = document.getElementById("graph-manipulation-closeDiv"); | |||
var editModeDiv = document.getElementById("graph-manipulation-editMode"); | |||
if (this.editMode == true) { | |||
toolbar.style.display="block"; | |||
closeDiv.style.display="block"; | |||
editModeDiv.style.display="none"; | |||
closeDiv.onclick = this._toggleEditMode.bind(this); | |||
} | |||
else { | |||
toolbar.style.display="none"; | |||
closeDiv.style.display="none"; | |||
editModeDiv.style.display="block"; | |||
closeDiv.onclick = null; | |||
} | |||
this._createManipulatorBar() | |||
}, | |||
/** | |||
* main function, creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar. | |||
* | |||
* @private | |||
*/ | |||
_createManipulatorBar : function() { | |||
// remove bound functions | |||
this.off('select', this.boundFunction); | |||
// restore overloaded functions | |||
this._restoreOverloadedFunctions(); | |||
// resume calculation | |||
this.freezeSimulation = false; | |||
// reset global variables | |||
this.blockConnectingEdgeSelection = false; | |||
this.forceAppendSelection = false | |||
if (this.editMode == true) { | |||
while (this.manipulationDiv.hasChildNodes()) { | |||
this.manipulationDiv.removeChild(this.manipulationDiv.firstChild); | |||
} | |||
// add the icons to the manipulator div | |||
this.manipulationDiv.innerHTML = "" + | |||
"<span class='graph-manipulationUI add' id='graph-manipulate-addNode'>" + | |||
"<span class='graph-manipulationLabel'>Add Node</span></span>" + | |||
"<div class='graph-seperatorLine'></div>" + | |||
"<span class='graph-manipulationUI connect' id='graph-manipulate-connectNode'>" + | |||
"<span class='graph-manipulationLabel'>Add Link</span></span>"; | |||
if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) { | |||
this.manipulationDiv.innerHTML += "" + | |||
"<div class='graph-seperatorLine'></div>" + | |||
"<span class='graph-manipulationUI edit' id='graph-manipulate-editNode'>" + | |||
"<span class='graph-manipulationLabel'>Edit Node</span></span>"; | |||
} | |||
if (this._selectionIsEmpty() == false) { | |||
this.manipulationDiv.innerHTML += "" + | |||
"<div class='graph-seperatorLine'></div>" + | |||
"<span class='graph-manipulationUI delete' id='graph-manipulate-delete'>" + | |||
"<span class='graph-manipulationLabel'>Delete selected</span></span>"; | |||
} | |||
// bind the icons | |||
var addNodeButton = document.getElementById("graph-manipulate-addNode"); | |||
addNodeButton.onclick = this._createAddNodeToolbar.bind(this); | |||
var addEdgeButton = document.getElementById("graph-manipulate-connectNode"); | |||
addEdgeButton.onclick = this._createAddEdgeToolbar.bind(this); | |||
if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) { | |||
var editButton = document.getElementById("graph-manipulate-editNode"); | |||
editButton.onclick = this._editNode.bind(this); | |||
} | |||
if (this._selectionIsEmpty() == false) { | |||
var deleteButton = document.getElementById("graph-manipulate-delete"); | |||
deleteButton.onclick = this._deleteSelected.bind(this); | |||
} | |||
var closeDiv = document.getElementById("graph-manipulation-closeDiv"); | |||
closeDiv.onclick = this._toggleEditMode.bind(this); | |||
this.boundFunction = this._createManipulatorBar.bind(this); | |||
this.on('select', this.boundFunction); | |||
} | |||
else { | |||
this.editModeDiv.innerHTML = "" + | |||
"<span class='graph-manipulationUI edit editmode' id='graph-manipulate-editModeButton'>" + | |||
"<span class='graph-manipulationLabel'>Edit</span></span>" | |||
var editModeButton = document.getElementById("graph-manipulate-editModeButton"); | |||
editModeButton.onclick = this._toggleEditMode.bind(this); | |||
} | |||
}, | |||
/** | |||
* Create the toolbar for adding Nodes | |||
* | |||
* @private | |||
*/ | |||
_createAddNodeToolbar : function() { | |||
// clear the toolbar | |||
this._clearManipulatorBar(); | |||
this.off('select', this.boundFunction); | |||
// create the toolbar contents | |||
this.manipulationDiv.innerHTML = "" + | |||
"<span class='graph-manipulationUI back' id='graph-manipulate-back'>" + | |||
"<span class='graph-manipulationLabel'>Back</span></span>" + | |||
"<div class='graph-seperatorLine'></div>" + | |||
"<span class='graph-manipulationUI none' id='graph-manipulate-back'>" + | |||
"<span class='graph-manipulationLabel'>Click in an empty space to place a new node</span></span>"; | |||
// bind the icon | |||
var backButton = document.getElementById("graph-manipulate-back"); | |||
backButton.onclick = this._createManipulatorBar.bind(this); | |||
// we use the boundFunction so we can reference it when we unbind it from the "select" event. | |||
this.boundFunction = this._addNode.bind(this); | |||
this.on('select', this.boundFunction); | |||
}, | |||
/** | |||
* create the toolbar to connect nodes | |||
* | |||
* @private | |||
*/ | |||
_createAddEdgeToolbar : function() { | |||
// clear the toolbar | |||
this._clearManipulatorBar(); | |||
this._unselectAll(true); | |||
this.freezeSimulation = true; | |||
this.off('select', this.boundFunction); | |||
this._unselectAll(); | |||
this.forceAppendSelection = false; | |||
this.blockConnectingEdgeSelection = true; | |||
this.manipulationDiv.innerHTML = "" + | |||
"<span class='graph-manipulationUI back' id='graph-manipulate-back'>" + | |||
"<span class='graph-manipulationLabel'>Back</span></span>" + | |||
"<div class='graph-seperatorLine'></div>" + | |||
"<span class='graph-manipulationUI none' id='graph-manipulate-back'>" + | |||
"<span id='graph-manipulatorLabel' class='graph-manipulationLabel'>Click on a node and drag the edge to another node to connect them.</span></span>"; | |||
// bind the icon | |||
var backButton = document.getElementById("graph-manipulate-back"); | |||
backButton.onclick = this._createManipulatorBar.bind(this); | |||
// we use the boundFunction so we can reference it when we unbind it from the "select" event. | |||
this.boundFunction = this._handleConnect.bind(this); | |||
this.on('select', this.boundFunction); | |||
// temporarily overload functions | |||
this.cachedFunctions["_handleTouch"] = this._handleTouch; | |||
this.cachedFunctions["_handleOnRelease"] = this._handleOnRelease; | |||
this._handleTouch = this._handleConnect; | |||
this._handleOnRelease = this._finishConnect; | |||
// redraw to show the unselect | |||
this._redraw(); | |||
}, | |||
/** | |||
* the function bound to the selection event. It checks if you want to connect a cluster and changes the description | |||
* to walk the user through the process. | |||
* | |||
* @private | |||
*/ | |||
_handleConnect : function(pointer) { | |||
if (this._getSelectedNodeCount() == 0) { | |||
var node = this._getNodeAt(pointer); | |||
if (node != null) { | |||
if (node.clusterSize > 1) { | |||
alert("Cannot create edges to a cluster.") | |||
} | |||
else { | |||
this._selectObject(node,false); | |||
// create a node the temporary line can look at | |||
this.sectors['support']['nodes']['targetNode'] = new Node({id:'targetNode'},{},{},this.constants); | |||
this.sectors['support']['nodes']['targetNode'].x = node.x; | |||
this.sectors['support']['nodes']['targetNode'].y = node.y; | |||
this.sectors['support']['nodes']['targetViaNode'] = new Node({id:'targetViaNode'},{},{},this.constants); | |||
this.sectors['support']['nodes']['targetViaNode'].x = node.x; | |||
this.sectors['support']['nodes']['targetViaNode'].y = node.y; | |||
this.sectors['support']['nodes']['targetViaNode'].parentEdgeId = "connectionEdge"; | |||
// create a temporary edge | |||
this.edges['connectionEdge'] = new Edge({id:"connectionEdge",from:node.id,to:this.sectors['support']['nodes']['targetNode'].id}, this, this.constants); | |||
this.edges['connectionEdge'].from = node; | |||
this.edges['connectionEdge'].connected = true; | |||
this.edges['connectionEdge'].smooth = true; | |||
this.edges['connectionEdge'].selected = true; | |||
this.edges['connectionEdge'].to = this.sectors['support']['nodes']['targetNode']; | |||
this.edges['connectionEdge'].via = this.sectors['support']['nodes']['targetViaNode']; | |||
this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag; | |||
this._handleOnDrag = function(event) { | |||
var pointer = this._getPointer(event.gesture.touches[0]); | |||
this.sectors['support']['nodes']['targetNode'].x = this._canvasToX(pointer.x); | |||
this.sectors['support']['nodes']['targetNode'].y = this._canvasToY(pointer.y); | |||
this.sectors['support']['nodes']['targetViaNode'].x = 0.5 * (this._canvasToX(pointer.x) + this.edges['connectionEdge'].from.x); | |||
this.sectors['support']['nodes']['targetViaNode'].y = this._canvasToY(pointer.y); | |||
}; | |||
this.moving = true; | |||
this.start(); | |||
} | |||
} | |||
} | |||
}, | |||
_finishConnect : function(pointer) { | |||
if (this._getSelectedNodeCount() == 1) { | |||
// restore the drag function | |||
this._handleOnDrag = this.cachedFunctions["_handleOnDrag"]; | |||
delete this.cachedFunctions["_handleOnDrag"]; | |||
// remember the edge id | |||
var connectFromId = this.edges['connectionEdge'].fromId; | |||
// remove the temporary nodes and edge | |||
delete this.edges['connectionEdge'] | |||
delete this.sectors['support']['nodes']['targetNode']; | |||
delete this.sectors['support']['nodes']['targetViaNode']; | |||
var node = this._getNodeAt(pointer); | |||
if (node != null) { | |||
if (node.clusterSize > 1) { | |||
alert("Cannot create edges to a cluster.") | |||
} | |||
else { | |||
this._createEdge(connectFromId,node.id); | |||
this._createManipulatorBar(); | |||
} | |||
} | |||
this._unselectAll(); | |||
} | |||
}, | |||
/** | |||
* Adds a node on the specified location | |||
* | |||
* @param {Object} pointer | |||
*/ | |||
_addNode : function() { | |||
if (this._selectionIsEmpty() && this.editMode == true) { | |||
var positionObject = this._pointerToPositionObject(this.pointerPosition); | |||
var defaultData = {id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",fixed:false}; | |||
if (this.triggerFunctions.add) { | |||
if (this.triggerFunctions.add.length == 2) { | |||
var me = this; | |||
this.triggerFunctions.add(defaultData, function(finalizedData) { | |||
me.createNodeOnClick = true; | |||
me.nodesData.add(finalizedData); | |||
me.createNodeOnClick = false; | |||
me._createManipulatorBar(); | |||
me.moving = true; | |||
me.start(); | |||
}); | |||
} | |||
else { | |||
alert("The function for add does not support two arguments (data,callback)."); | |||
this._createManipulatorBar(); | |||
this.moving = true; | |||
this.start(); | |||
} | |||
} | |||
else { | |||
this.createNodeOnClick = true; | |||
this.nodesData.add(defaultData); | |||
this.createNodeOnClick = false; | |||
this._createManipulatorBar(); | |||
this.moving = true; | |||
this.start(); | |||
} | |||
} | |||
}, | |||
/** | |||
* connect two nodes with a new edge. | |||
* | |||
* @private | |||
*/ | |||
_createEdge : function(sourceNodeId,targetNodeId) { | |||
if (this.editMode == true) { | |||
var defaultData = {from:sourceNodeId, to:targetNodeId}; | |||
if (this.triggerFunctions.connect) { | |||
if (this.triggerFunctions.connect.length == 2) { | |||
var me = this; | |||
this.triggerFunctions.connect(defaultData, function(finalizedData) { | |||
me.edgesData.add(finalizedData) | |||
me.moving = true; | |||
me.start(); | |||
}); | |||
} | |||
else { | |||
alert("The function for connect does not support two arguments (data,callback)."); | |||
this.moving = true; | |||
this.start(); | |||
} | |||
} | |||
else { | |||
this.edgesData.add(defaultData) | |||
this.moving = true; | |||
this.start(); | |||
} | |||
} | |||
}, | |||
/** | |||
* Create the toolbar to edit the selected node. The label and the color can be changed. Other colors are derived from the chosen color. | |||
* | |||
* @private | |||
*/ | |||
_editNode : function() { | |||
if (this.triggerFunctions.edit && this.editMode == true) { | |||
var node = this._getSelectedNode(); | |||
var data = {id:node.id, | |||
label: node.label, | |||
group: node.group, | |||
shape: node.shape, | |||
color: { | |||
background:node.color.background, | |||
border:node.color.border, | |||
highlight: { | |||
background:node.color.highlight.background, | |||
border:node.color.highlight.border | |||
} | |||
}}; | |||
if (this.triggerFunctions.edit.length == 2) { | |||
var me = this; | |||
this.triggerFunctions.edit(data, function (finalizedData) { | |||
me.nodesData.update(finalizedData); | |||
me._createManipulatorBar(); | |||
me.moving = true; | |||
me.start(); | |||
}); | |||
} | |||
else { | |||
alert("The function for edit does not support two arguments (data, callback).") | |||
} | |||
} | |||
else { | |||
alert("No edit function has been bound to this button.") | |||
} | |||
}, | |||
/** | |||
* delete everything in the selection | |||
* | |||
* @private | |||
*/ | |||
_deleteSelected : function() { | |||
if (!this._selectionIsEmpty() && this.editMode == true) { | |||
if (!this._clusterInSelection()) { | |||
var selectedNodes = this.getSelectedNodes(); | |||
var selectedEdges = this.getSelectedEdges(); | |||
if (this.triggerFunctions.delete) { | |||
var me = this; | |||
var data = {nodes: selectedNodes, edges: selectedEdges}; | |||
if (this.triggerFunctions.delete.length = 2) { | |||
this.triggerFunctions.delete(data, function (finalizedData) { | |||
me.edgesData.remove(finalizedData.edges); | |||
me.nodesData.remove(finalizedData.nodes); | |||
this._unselectAll(); | |||
me.moving = true; | |||
me.start(); | |||
}); | |||
} | |||
else { | |||
alert("The function for edit does not support two arguments (data, callback).") | |||
} | |||
} | |||
else { | |||
this.edgesData.remove(selectedEdges); | |||
this.nodesData.remove(selectedNodes); | |||
this._unselectAll(); | |||
this.moving = true; | |||
this.start(); | |||
} | |||
} | |||
else { | |||
alert("Clusters cannot be deleted."); | |||
} | |||
} | |||
} | |||
}; |
@ -0,0 +1,219 @@ | |||
/** | |||
* Created by Alex on 2/10/14. | |||
*/ | |||
var graphMixinLoaders = { | |||
/** | |||
* Load a mixin into the graph object | |||
* | |||
* @param {Object} sourceVariable | this object has to contain functions. | |||
* @private | |||
*/ | |||
_loadMixin : function(sourceVariable) { | |||
for (var mixinFunction in sourceVariable) { | |||
if (sourceVariable.hasOwnProperty(mixinFunction)) { | |||
Graph.prototype[mixinFunction] = sourceVariable[mixinFunction]; | |||
} | |||
} | |||
}, | |||
/** | |||
* removes a mixin from the graph object. | |||
* | |||
* @param {Object} sourceVariable | this object has to contain functions. | |||
* @private | |||
*/ | |||
_clearMixin : function(sourceVariable) { | |||
for (var mixinFunction in sourceVariable) { | |||
if (sourceVariable.hasOwnProperty(mixinFunction)) { | |||
Graph.prototype[mixinFunction] = undefined; | |||
} | |||
} | |||
}, | |||
/** | |||
* Mixin the physics system and initialize the parameters required. | |||
* | |||
* @private | |||
*/ | |||
_loadPhysicsSystem : function() { | |||
this._loadMixin(physicsMixin); | |||
this._loadSelectedForceSolver(); | |||
}, | |||
/** | |||
* This loads the node force solver based on the barnes hut or repulsion algorithm | |||
* | |||
* @private | |||
*/ | |||
_loadSelectedForceSolver : function() { | |||
// this overloads the this._calculateNodeForces | |||
if (this.constants.physics.barnesHut.enabled == true) { | |||
this._clearMixin(repulsionMixin); | |||
this.constants.physics.centralGravity = this.constants.physics.barnesHut.centralGravity; | |||
this.constants.physics.springLength = this.constants.physics.barnesHut.springLength; | |||
this.constants.physics.springConstant = this.constants.physics.barnesHut.springConstant; | |||
this.constants.physics.damping = this.constants.physics.barnesHut.damping; | |||
this.constants.physics.springGrowthPerMass = this.constants.physics.barnesHut.springGrowthPerMass; | |||
this._loadMixin(barnesHutMixin); | |||
} | |||
else { | |||
this._clearMixin(barnesHutMixin); | |||
this.barnesHutTree = undefined; | |||
this.constants.physics.centralGravity = this.constants.physics.repulsion.centralGravity; | |||
this.constants.physics.springLength = this.constants.physics.repulsion.springLength; | |||
this.constants.physics.springConstant = this.constants.physics.repulsion.springConstant; | |||
this.constants.physics.damping = this.constants.physics.repulsion.damping; | |||
this.constants.physics.springGrowthPerMass = this.constants.physics.repulsion.springGrowthPerMass; | |||
this._loadMixin(repulsionMixin); | |||
} | |||
}, | |||
/** | |||
* Mixin the cluster system and initialize the parameters required. | |||
* | |||
* @private | |||
*/ | |||
_loadClusterSystem : function() { | |||
this.clusterSession = 0; | |||
this.hubThreshold = 5; | |||
this._loadMixin(ClusterMixin); | |||
}, | |||
/** | |||
* Mixin the sector system and initialize the parameters required | |||
* | |||
* @private | |||
*/ | |||
_loadSectorSystem : function() { | |||
this.sectors = { }, | |||
this.activeSector = ["default"]; | |||
this.sectors["active"] = { }, | |||
this.sectors["active"]["default"] = {"nodes":{}, | |||
"edges":{}, | |||
"nodeIndices":[], | |||
"formationScale": 1.0, | |||
"drawingNode": undefined }; | |||
this.sectors["frozen"] = {}, | |||
this.sectors["support"] = {"nodes":{}, | |||
"edges":{}, | |||
"nodeIndices":[], | |||
"formationScale": 1.0, | |||
"drawingNode": undefined }; | |||
this.nodeIndices = this.sectors["active"]["default"]["nodeIndices"]; // the node indices list is used to speed up the computation of the repulsion fields | |||
this._loadMixin(SectorMixin); | |||
}, | |||
/** | |||
* Mixin the selection system and initialize the parameters required | |||
* | |||
* @private | |||
*/ | |||
_loadSelectionSystem : function() { | |||
this.selectionObj = { }; | |||
this._loadMixin(SelectionMixin); | |||
}, | |||
/** | |||
* Mixin the navigationUI (User Interface) system and initialize the parameters required | |||
* | |||
* @private | |||
*/ | |||
_loadManipulationSystem : function() { | |||
// reset global variables -- these are used by the selection of nodes and edges. | |||
this.blockConnectingEdgeSelection = false; | |||
this.forceAppendSelection = false | |||
if (this.constants.dataManipulation.enabled == true) { | |||
// load the manipulator HTML elements. All styling done in css. | |||
if (this.manipulationDiv === undefined) { | |||
this.manipulationDiv = document.createElement('div'); | |||
this.manipulationDiv.className = 'graph-manipulationDiv'; | |||
this.manipulationDiv.id = 'graph-manipulationDiv'; | |||
if (this.editMode == true) { | |||
this.manipulationDiv.style.display = "block"; | |||
} | |||
else { | |||
this.manipulationDiv.style.display = "none"; | |||
} | |||
this.containerElement.insertBefore(this.manipulationDiv, this.frame); | |||
} | |||
if (this.editModeDiv === undefined) { | |||
this.editModeDiv = document.createElement('div'); | |||
this.editModeDiv.className = 'graph-manipulation-editMode'; | |||
this.editModeDiv.id = 'graph-manipulation-editMode'; | |||
if (this.editMode == true) { | |||
this.editModeDiv.style.display = "none"; | |||
} | |||
else { | |||
this.editModeDiv.style.display = "block"; | |||
} | |||
this.containerElement.insertBefore(this.editModeDiv, this.frame); | |||
} | |||
if (this.closeDiv === undefined) { | |||
this.closeDiv = document.createElement('div'); | |||
this.closeDiv.className = 'graph-manipulation-closeDiv'; | |||
this.closeDiv.id = 'graph-manipulation-closeDiv'; | |||
this.closeDiv.style.display = this.manipulationDiv.style.display; | |||
this.containerElement.insertBefore(this.closeDiv, this.frame); | |||
} | |||
// load the manipulation functions | |||
this._loadMixin(manipulationMixin); | |||
// create the manipulator toolbar | |||
this._createManipulatorBar(); | |||
} | |||
else { | |||
if (this.manipulationDiv !== undefined) { | |||
// removes all the bindings and overloads | |||
this._createManipulatorBar(); | |||
// remove the manipulation divs | |||
this.containerElement.removeChild(this.manipulationDiv); | |||
this.containerElement.removeChild(this.editModeDiv); | |||
this.containerElement.removeChild(this.closeDiv); | |||
this.manipulationDiv = undefined; | |||
this.editModeDiv = undefined; | |||
this.closeDiv = undefined; | |||
// remove the mixin functions | |||
this._clearMixin(manipulationMixin); | |||
} | |||
} | |||
}, | |||
/** | |||
* Mixin the navigation (User Interface) system and initialize the parameters required | |||
* | |||
* @private | |||
*/ | |||
_loadNavigationControls : function() { | |||
this._loadMixin(NavigationMixin); | |||
// the clean function removes the button divs, this is done to remove the bindings. | |||
this._cleanNavigation(); | |||
if (this.constants.navigation.enabled == true) { | |||
this._loadNavigationElements(); | |||
} | |||
} | |||
} |
@ -0,0 +1,173 @@ | |||
/** | |||
* Created by Alex on 1/22/14. | |||
*/ | |||
var NavigationMixin = { | |||
_cleanNavigation : function() { | |||
// clean up previosu navigation items | |||
var wrapper = document.getElementById('graph-navigation_wrapper'); | |||
if (wrapper != null) { | |||
this.containerElement.removeChild(wrapper); | |||
} | |||
document.onmouseup = null; | |||
}, | |||
/** | |||
* 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() { | |||
this._cleanNavigation(); | |||
this.navigationDivs = {}; | |||
var navigationDivs = ['up','down','left','right','zoomIn','zoomOut','zoomExtends']; | |||
var navigationDivActions = ['_moveUp','_moveDown','_moveLeft','_moveRight','_zoomIn','_zoomOut','zoomToFit']; | |||
this.navigationDivs['wrapper'] = document.createElement('div'); | |||
this.navigationDivs['wrapper'].id = "graph-navigation_wrapper"; | |||
this.containerElement.insertBefore(this.navigationDivs['wrapper'],this.frame); | |||
for (var i = 0; i < navigationDivs.length; i++) { | |||
this.navigationDivs[navigationDivs[i]] = document.createElement('div'); | |||
this.navigationDivs[navigationDivs[i]].id = "graph-navigation_" + navigationDivs[i]; | |||
this.navigationDivs[navigationDivs[i]].className = "graph-navigation " + navigationDivs[i]; | |||
this.navigationDivs['wrapper'].appendChild(this.navigationDivs[navigationDivs[i]]); | |||
this.navigationDivs[navigationDivs[i]].onmousedown = this[navigationDivActions[i]].bind(this); | |||
} | |||
document.onmouseup = this._stopMovement.bind(this); | |||
}, | |||
/** | |||
* this stops all movement induced by the navigation buttons | |||
* | |||
* @private | |||
*/ | |||
_stopMovement : function() { | |||
this._xStopMoving(); | |||
this._yStopMoving(); | |||
this._stopZoom(); | |||
}, | |||
/** | |||
* stops the actions performed by page up and down etc. | |||
* | |||
* @param event | |||
* @private | |||
*/ | |||
_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) { | |||
console.log("here") | |||
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.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.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.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.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.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.zoomIncrement = 0; | |||
}, | |||
/** | |||
* Stop moving in the Y direction and unHighlight the up and down | |||
* @private | |||
*/ | |||
_yStopMoving : function() { | |||
this.yIncrement = 0; | |||
}, | |||
/** | |||
* Stop moving in the X direction and unHighlight left and right. | |||
* @private | |||
*/ | |||
_xStopMoving : function() { | |||
this.xIncrement = 0; | |||
} | |||
}; |
@ -0,0 +1,245 @@ | |||
/** | |||
* Created by Alex on 2/6/14. | |||
*/ | |||
var physicsMixin = { | |||
/** | |||
* Toggling barnes Hut calculation on and off. | |||
* | |||
* @private | |||
*/ | |||
_toggleBarnesHut : function() { | |||
this.constants.physics.barnesHut.enabled = !this.constants.physics.barnesHut.enabled; | |||
this._loadSelectedForceSolver(); | |||
this.moving = true; | |||
this.start(); | |||
}, | |||
/** | |||
* Before calculating the forces, we check if we need to cluster to keep up performance and we check | |||
* if there is more than one node. If it is just one node, we dont calculate anything. | |||
* | |||
* @private | |||
*/ | |||
_initializeForceCalculation : function() { | |||
// stop calculation if there is only one node | |||
if (this.nodeIndices.length == 1) { | |||
this.nodes[this.nodeIndices[0]]._setForce(0,0); | |||
} | |||
else { | |||
// if there are too many nodes on screen, we cluster without repositioning | |||
if (this.nodeIndices.length > this.constants.clustering.clusterThreshold && this.constants.clustering.enabled == true) { | |||
this.clusterToFit(this.constants.clustering.reduceToNodes, false); | |||
} | |||
// we now start the force calculation | |||
this._calculateForces(); | |||
} | |||
}, | |||
/** | |||
* Calculate the external forces acting on the nodes | |||
* Forces are caused by: edges, repulsing forces between nodes, gravity | |||
* @private | |||
*/ | |||
_calculateForces : function() { | |||
// Gravity is required to keep separated groups from floating off | |||
// the forces are reset to zero in this loop by using _setForce instead | |||
// of _addForce | |||
this._calculateGravitationalForces(); | |||
this._calculateNodeForces(); | |||
if (this.constants.smoothCurves == true) { | |||
this._calculateSpringForcesWithSupport(); | |||
} | |||
else { | |||
this._calculateSpringForces(); | |||
} | |||
}, | |||
/** | |||
* Smooth curves are created by adding invisible nodes in the center of the edges. These nodes are also | |||
* handled in the calculateForces function. We then use a quadratic curve with the center node as control. | |||
* This function joins the datanodes and invisible (called support) nodes into one object. | |||
* We do this so we do not contaminate this.nodes with the support nodes. | |||
* | |||
* @private | |||
*/ | |||
_updateCalculationNodes : function() { | |||
if (this.constants.smoothCurves == true) { | |||
this.calculationNodes = {}; | |||
this.calculationNodeIndices = []; | |||
for (var nodeId in this.nodes) { | |||
if (this.nodes.hasOwnProperty(nodeId)) { | |||
this.calculationNodes[nodeId] = this.nodes[nodeId]; | |||
} | |||
} | |||
var supportNodes = this.sectors['support']['nodes']; | |||
for (var supportNodeId in supportNodes) { | |||
if (supportNodes.hasOwnProperty(supportNodeId)) { | |||
if (this.edges.hasOwnProperty(supportNodes[supportNodeId].parentEdgeId)) { | |||
this.calculationNodes[supportNodeId] = supportNodes[supportNodeId]; | |||
} | |||
else { | |||
supportNodes[supportNodeId]._setForce(0,0); | |||
} | |||
} | |||
} | |||
for (var idx in this.calculationNodes) { | |||
if (this.calculationNodes.hasOwnProperty(idx)) { | |||
this.calculationNodeIndices.push(idx); | |||
} | |||
} | |||
} | |||
else { | |||
this.calculationNodes = this.nodes; | |||
this.calculationNodeIndices = this.nodeIndices; | |||
} | |||
}, | |||
/** | |||
* this function applies the central gravity effect to keep groups from floating off | |||
* | |||
* @private | |||
*/ | |||
_calculateGravitationalForces : function() { | |||
var dx, dy, distance, node, i; | |||
var nodes = this.calculationNodes; | |||
var gravity = this.constants.physics.centralGravity; | |||
var gravityForce = 0; | |||
for (i = 0; i < this.calculationNodeIndices.length; i++) { | |||
node = nodes[this.calculationNodeIndices[i]]; | |||
node.damping = this.constants.physics.damping; // possibly add function to alter damping properties of clusters. | |||
// gravity does not apply when we are in a pocket sector | |||
if (this._sector() == "default") { | |||
dx = -node.x; | |||
dy = -node.y; | |||
distance = Math.sqrt(dx*dx + dy*dy); | |||
gravityForce = gravity / distance; | |||
node.fx = dx * gravityForce; | |||
node.fy = dy * gravityForce; | |||
} | |||
else { | |||
node.fx = 0; | |||
node.fy = 0; | |||
} | |||
} | |||
}, | |||
/** | |||
* this function calculates the effects of the springs in the case of unsmooth curves. | |||
* | |||
* @private | |||
*/ | |||
_calculateSpringForces : function() { | |||
var edgeLength, edge, edgeId; | |||
var dx, dy, fx, fy, springForce, length; | |||
var edges = this.edges; | |||
// forces caused by the edges, modelled as springs | |||
for (edgeId in edges) { | |||
if (edges.hasOwnProperty(edgeId)) { | |||
edge = edges[edgeId]; | |||
if (edge.connected) { | |||
// only calculate forces if nodes are in the same sector | |||
if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) { | |||
edgeLength = edge.length; | |||
// this implies that the edges between big clusters are longer | |||
edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth; | |||
dx = (edge.from.x - edge.to.x); | |||
dy = (edge.from.y - edge.to.y); | |||
length = Math.sqrt(dx * dx + dy * dy); | |||
springForce = this.constants.physics.springConstant * (edgeLength - length) / length; | |||
fx = dx * springForce; | |||
fy = dy * springForce; | |||
edge.from.fx += fx; | |||
edge.from.fy += fy; | |||
edge.to.fx -= fx; | |||
edge.to.fy -= fy; | |||
} | |||
} | |||
} | |||
} | |||
}, | |||
/** | |||
* This function calculates the springforces on the nodes, accounting for the support nodes. | |||
* | |||
* @private | |||
*/ | |||
_calculateSpringForcesWithSupport : function() { | |||
var edgeLength, edge, edgeId, combinedClusterSize; | |||
var edges = this.edges; | |||
// forces caused by the edges, modelled as springs | |||
for (edgeId in edges) { | |||
if (edges.hasOwnProperty(edgeId)) { | |||
edge = edges[edgeId]; | |||
if (edge.connected) { | |||
// only calculate forces if nodes are in the same sector | |||
if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) { | |||
if (edge.via != null) { | |||
var node1 = edge.to; | |||
var node2 = edge.via; | |||
var node3 = edge.from; | |||
edgeLength = edge.length; | |||
combinedClusterSize = node1.clusterSize + node3.clusterSize - 2; | |||
// this implies that the edges between big clusters are longer | |||
edgeLength += combinedClusterSize * this.constants.clustering.edgeGrowth; | |||
this._calculateSpringForce(node1,node2,0.5*edgeLength); | |||
this._calculateSpringForce(node2,node3,0.5*edgeLength); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
}, | |||
/** | |||
* This is the code actually performing the calculation for the function above. It is split out to avoid repetition. | |||
* | |||
* @param node1 | |||
* @param node2 | |||
* @param edgeLength | |||
* @private | |||
*/ | |||
_calculateSpringForce : function(node1,node2,edgeLength) { | |||
var dx, dy, fx, fy, springForce, length; | |||
dx = (node1.x - node2.x); | |||
dy = (node1.y - node2.y); | |||
length = Math.sqrt(dx * dx + dy * dy); | |||
springForce = this.constants.physics.springConstant * (edgeLength - length) / length; | |||
fx = dx * springForce; | |||
fy = dy * springForce; | |||
node1.fx += fx; | |||
node1.fy += fy; | |||
node2.fx -= fx; | |||
node2.fy -= fy; | |||
} | |||
} |
@ -0,0 +1,373 @@ | |||
/** | |||
* Created by Alex on 2/10/14. | |||
*/ | |||
var barnesHutMixin = { | |||
/** | |||
* This function calculates the forces the nodes apply on eachother based on a gravitational model. | |||
* The Barnes Hut method is used to speed up this N-body simulation. | |||
* | |||
* @private | |||
*/ | |||
_calculateNodeForces : function() { | |||
var node; | |||
var nodes = this.calculationNodes; | |||
var nodeIndices = this.calculationNodeIndices; | |||
var nodeCount = nodeIndices.length; | |||
this._formBarnesHutTree(nodes,nodeIndices); | |||
var barnesHutTree = this.barnesHutTree; | |||
// place the nodes one by one recursively | |||
for (var i = 0; i < nodeCount; i++) { | |||
node = nodes[nodeIndices[i]]; | |||
// starting with root is irrelevant, it never passes the BarnesHut condition | |||
this._getForceContribution(barnesHutTree.root.children.NW,node); | |||
this._getForceContribution(barnesHutTree.root.children.NE,node); | |||
this._getForceContribution(barnesHutTree.root.children.SW,node); | |||
this._getForceContribution(barnesHutTree.root.children.SE,node); | |||
} | |||
}, | |||
/** | |||
* This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass. | |||
* If a region contains a single node, we check if it is not itself, then we apply the force. | |||
* | |||
* @param parentBranch | |||
* @param node | |||
* @private | |||
*/ | |||
_getForceContribution : function(parentBranch,node) { | |||
// we get no force contribution from an empty region | |||
if (parentBranch.childrenCount > 0) { | |||
var dx,dy,distance; | |||
// get the distance from the center of mass to the node. | |||
dx = parentBranch.centerOfMass.x - node.x; | |||
dy = parentBranch.centerOfMass.y - node.y; | |||
distance = Math.sqrt(dx * dx + dy * dy); | |||
// BarnesHut condition | |||
// original condition : s/d < theta = passed === d/s > 1/theta = passed | |||
// calcSize = 1/s --> d * 1/s > 1/theta = passed | |||
if (distance * parentBranch.calcSize > this.constants.physics.barnesHut.theta) { | |||
// duplicate code to reduce function calls to speed up program | |||
if (distance == 0) { | |||
distance = 0.5*Math.random(); | |||
dx = distance; | |||
} | |||
var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.mass / (distance * distance * distance); | |||
var fx = dx * gravityForce; | |||
var fy = dy * gravityForce; | |||
node.fx += fx; | |||
node.fy += fy; | |||
} | |||
else { | |||
// Did not pass the condition, go into children if available | |||
if (parentBranch.childrenCount == 4) { | |||
this._getForceContribution(parentBranch.children.NW,node); | |||
this._getForceContribution(parentBranch.children.NE,node); | |||
this._getForceContribution(parentBranch.children.SW,node); | |||
this._getForceContribution(parentBranch.children.SE,node); | |||
} | |||
else { // parentBranch must have only one node, if it was empty we wouldnt be here | |||
if (parentBranch.children.data.id != node.id) { // if it is not self | |||
// duplicate code to reduce function calls to speed up program | |||
if (distance == 0) { | |||
distance = 0.5*Math.random(); | |||
dx = distance; | |||
} | |||
var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.mass / (distance * distance * distance); | |||
var fx = dx * gravityForce; | |||
var fy = dy * gravityForce; | |||
node.fx += fx; | |||
node.fy += fy; | |||
} | |||
} | |||
} | |||
} | |||
}, | |||
/** | |||
* This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes. | |||
* | |||
* @param nodes | |||
* @param nodeIndices | |||
* @private | |||
*/ | |||
_formBarnesHutTree : function(nodes,nodeIndices) { | |||
var node; | |||
var nodeCount = nodeIndices.length; | |||
var minX = Number.MAX_VALUE, | |||
minY = Number.MAX_VALUE, | |||
maxX =-Number.MAX_VALUE, | |||
maxY =-Number.MAX_VALUE; | |||
// get the range of the nodes | |||
for (var i = 0; i < nodeCount; i++) { | |||
var x = nodes[nodeIndices[i]].x; | |||
var y = nodes[nodeIndices[i]].y; | |||
if (x < minX) { minX = x; } | |||
if (x > maxX) { maxX = x; } | |||
if (y < minY) { minY = y; } | |||
if (y > maxY) { maxY = y; } | |||
} | |||
// make the range a square | |||
var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y | |||
if (sizeDiff > 0) {minY -= 0.5 * sizeDiff; maxY += 0.5 * sizeDiff;} // xSize > ySize | |||
else {minX += 0.5 * sizeDiff; maxX -= 0.5 * sizeDiff;} // xSize < ySize | |||
var minimumTreeSize = 1e-5; | |||
var rootSize = Math.max(minimumTreeSize,Math.abs(maxX - minX)); | |||
var halfRootSize = 0.5 * rootSize; | |||
var centerX = 0.5 * (minX + maxX), centerY = 0.5 * (minY + maxY); | |||
// construct the barnesHutTree | |||
var barnesHutTree = {root:{ | |||
centerOfMass:{x:0,y:0}, // Center of Mass | |||
mass:0, | |||
range: {minX:centerX-halfRootSize,maxX:centerX+halfRootSize, | |||
minY:centerY-halfRootSize,maxY:centerY+halfRootSize}, | |||
size: rootSize, | |||
calcSize: 1 / rootSize, | |||
children: {data:null}, | |||
maxWidth: 0, | |||
level: 0, | |||
childrenCount: 4 | |||
}}; | |||
this._splitBranch(barnesHutTree.root); | |||
// place the nodes one by one recursively | |||
for (i = 0; i < nodeCount; i++) { | |||
node = nodes[nodeIndices[i]]; | |||
this._placeInTree(barnesHutTree.root,node); | |||
} | |||
// make global | |||
this.barnesHutTree = barnesHutTree | |||
}, | |||
_updateBranchMass : function(parentBranch, node) { | |||
var totalMass = parentBranch.mass + node.mass; | |||
var totalMassInv = 1/totalMass; | |||
parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.mass; | |||
parentBranch.centerOfMass.x *= totalMassInv; | |||
parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.mass; | |||
parentBranch.centerOfMass.y *= totalMassInv; | |||
parentBranch.mass = totalMass; | |||
var biggestSize = Math.max(Math.max(node.height,node.radius),node.width); | |||
parentBranch.maxWidth = (parentBranch.maxWidth < biggestSize) ? biggestSize : parentBranch.maxWidth; | |||
}, | |||
_placeInTree : function(parentBranch,node,skipMassUpdate) { | |||
if (skipMassUpdate != true || skipMassUpdate === undefined) { | |||
// update the mass of the branch. | |||
this._updateBranchMass(parentBranch,node); | |||
} | |||
if (parentBranch.children.NW.range.maxX > node.x) { // in NW or SW | |||
if (parentBranch.children.NW.range.maxY > node.y) { // in NW | |||
this._placeInRegion(parentBranch,node,"NW"); | |||
} | |||
else { // in SW | |||
this._placeInRegion(parentBranch,node,"SW"); | |||
} | |||
} | |||
else { // in NE or SE | |||
if (parentBranch.children.NW.range.maxY > node.y) { // in NE | |||
this._placeInRegion(parentBranch,node,"NE"); | |||
} | |||
else { // in SE | |||
this._placeInRegion(parentBranch,node,"SE"); | |||
} | |||
} | |||
}, | |||
_placeInRegion : function(parentBranch,node,region) { | |||
switch (parentBranch.children[region].childrenCount) { | |||
case 0: // place node here | |||
parentBranch.children[region].children.data = node; | |||
parentBranch.children[region].childrenCount = 1; | |||
this._updateBranchMass(parentBranch.children[region],node); | |||
break; | |||
case 1: // convert into children | |||
// if there are two nodes exactly overlapping (on init, on opening of cluster etc.) | |||
// we move one node a pixel and we do not put it in the tree. | |||
if (parentBranch.children[region].children.data.x == node.x && | |||
parentBranch.children[region].children.data.y == node.y) { | |||
node.x += Math.random(); | |||
node.y += Math.random(); | |||
this._placeInTree(parentBranch,node, true); | |||
} | |||
else { | |||
this._splitBranch(parentBranch.children[region]); | |||
this._placeInTree(parentBranch.children[region],node); | |||
} | |||
break; | |||
case 4: // place in branch | |||
this._placeInTree(parentBranch.children[region],node); | |||
break; | |||
} | |||
}, | |||
/** | |||
* this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch | |||
* after the split is complete. | |||
* | |||
* @param parentBranch | |||
* @private | |||
*/ | |||
_splitBranch : function(parentBranch) { | |||
// if the branch is filled with a node, replace the node in the new subset. | |||
var containedNode = null; | |||
if (parentBranch.childrenCount == 1) { | |||
containedNode = parentBranch.children.data; | |||
parentBranch.mass = 0; parentBranch.centerOfMass.x = 0; parentBranch.centerOfMass.y = 0; | |||
} | |||
parentBranch.childrenCount = 4; | |||
parentBranch.children.data = null; | |||
this._insertRegion(parentBranch,"NW"); | |||
this._insertRegion(parentBranch,"NE"); | |||
this._insertRegion(parentBranch,"SW"); | |||
this._insertRegion(parentBranch,"SE"); | |||
if (containedNode != null) { | |||
this._placeInTree(parentBranch,containedNode); | |||
} | |||
}, | |||
/** | |||
* This function subdivides the region into four new segments. | |||
* Specifically, this inserts a single new segment. | |||
* It fills the children section of the parentBranch | |||
* | |||
* @param parentBranch | |||
* @param region | |||
* @param parentRange | |||
* @private | |||
*/ | |||
_insertRegion : function(parentBranch, region) { | |||
var minX,maxX,minY,maxY; | |||
var childSize = 0.5 * parentBranch.size; | |||
switch (region) { | |||
case "NW": | |||
minX = parentBranch.range.minX; | |||
maxX = parentBranch.range.minX + childSize; | |||
minY = parentBranch.range.minY; | |||
maxY = parentBranch.range.minY + childSize; | |||
break; | |||
case "NE": | |||
minX = parentBranch.range.minX + childSize; | |||
maxX = parentBranch.range.maxX; | |||
minY = parentBranch.range.minY; | |||
maxY = parentBranch.range.minY + childSize; | |||
break; | |||
case "SW": | |||
minX = parentBranch.range.minX; | |||
maxX = parentBranch.range.minX + childSize; | |||
minY = parentBranch.range.minY + childSize; | |||
maxY = parentBranch.range.maxY; | |||
break; | |||
case "SE": | |||
minX = parentBranch.range.minX + childSize; | |||
maxX = parentBranch.range.maxX; | |||
minY = parentBranch.range.minY + childSize; | |||
maxY = parentBranch.range.maxY; | |||
break; | |||
} | |||
parentBranch.children[region] = { | |||
centerOfMass:{x:0,y:0}, | |||
mass:0, | |||
range:{minX:minX,maxX:maxX,minY:minY,maxY:maxY}, | |||
size: 0.5 * parentBranch.size, | |||
calcSize: 2 * parentBranch.calcSize, | |||
children: {data:null}, | |||
maxWidth: 0, | |||
level: parentBranch.level+1, | |||
childrenCount: 0 | |||
}; | |||
}, | |||
/** | |||
* This function is for debugging purposed, it draws the tree. | |||
* | |||
* @param ctx | |||
* @param color | |||
* @private | |||
*/ | |||
_drawTree : function(ctx,color) { | |||
if (this.barnesHutTree !== undefined) { | |||
ctx.lineWidth = 1; | |||
this._drawBranch(this.barnesHutTree.root,ctx,color); | |||
} | |||
}, | |||
/** | |||
* This function is for debugging purposes. It draws the branches recursively. | |||
* | |||
* @param branch | |||
* @param ctx | |||
* @param color | |||
* @private | |||
*/ | |||
_drawBranch : function(branch,ctx,color) { | |||
if (color === undefined) { | |||
color = "#FF0000"; | |||
} | |||
if (branch.childrenCount == 4) { | |||
this._drawBranch(branch.children.NW,ctx); | |||
this._drawBranch(branch.children.NE,ctx); | |||
this._drawBranch(branch.children.SE,ctx); | |||
this._drawBranch(branch.children.SW,ctx); | |||
} | |||
ctx.strokeStyle = color; | |||
ctx.beginPath(); | |||
ctx.moveTo(branch.range.minX,branch.range.minY); | |||
ctx.lineTo(branch.range.maxX,branch.range.minY); | |||
ctx.stroke(); | |||
ctx.beginPath(); | |||
ctx.moveTo(branch.range.maxX,branch.range.minY); | |||
ctx.lineTo(branch.range.maxX,branch.range.maxY); | |||
ctx.stroke(); | |||
ctx.beginPath(); | |||
ctx.moveTo(branch.range.maxX,branch.range.maxY); | |||
ctx.lineTo(branch.range.minX,branch.range.maxY); | |||
ctx.stroke(); | |||
ctx.beginPath(); | |||
ctx.moveTo(branch.range.minX,branch.range.maxY); | |||
ctx.lineTo(branch.range.minX,branch.range.minY); | |||
ctx.stroke(); | |||
/* | |||
if (branch.mass > 0) { | |||
ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass); | |||
ctx.stroke(); | |||
} | |||
*/ | |||
} | |||
}; |
@ -0,0 +1,66 @@ | |||
/** | |||
* Created by Alex on 2/10/14. | |||
*/ | |||
var repulsionMixin = { | |||
/** | |||
* Calculate the forces the nodes apply on eachother based on a repulsion field. | |||
* This field is linearly approximated. | |||
* | |||
* @private | |||
*/ | |||
_calculateNodeForces : function() { | |||
var dx, dy, angle, distance, fx, fy, combinedClusterSize, | |||
repulsingForce, node1, node2, i, j; | |||
var nodes = this.calculationNodes; | |||
var nodeIndices = this.calculationNodeIndices; | |||
// approximation constants | |||
var a_base = -2/3; | |||
var b = 4/3; | |||
// repulsing forces between nodes | |||
var nodeDistance = this.constants.physics.repulsion.nodeDistance; | |||
var minimumDistance = nodeDistance; | |||
// we loop from i over all but the last entree in the array | |||
// j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j | |||
for (i = 0; i < nodeIndices.length-1; i++) { | |||
node1 = nodes[nodeIndices[i]]; | |||
for (j = i+1; j < nodeIndices.length; j++) { | |||
node2 = nodes[nodeIndices[j]]; | |||
combinedClusterSize = node1.clusterSize + node2.clusterSize - 2; | |||
dx = node2.x - node1.x; | |||
dy = node2.y - node1.y; | |||
distance = Math.sqrt(dx * dx + dy * dy); | |||
minimumDistance = (combinedClusterSize == 0) ? nodeDistance : (nodeDistance * (1 + combinedClusterSize * this.constants.clustering.distanceAmplification)); | |||
var a = a_base / minimumDistance; | |||
if (distance < 2*minimumDistance) { | |||
if (distance < 0.5*minimumDistance) { | |||
repulsingForce = 1.0; | |||
} | |||
else { | |||
repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness)) | |||
} | |||
// amplify the repulsion for clusters. | |||
repulsingForce *= (combinedClusterSize == 0) ? 1 : 1 + combinedClusterSize * this.constants.clustering.forceAmplification; | |||
repulsingForce = repulsingForce/distance; | |||
fx = dx * repulsingForce; | |||
fy = dy * repulsingForce; | |||
node1.fx -= fx; | |||
node1.fy -= fy; | |||
node2.fx += fx; | |||
node2.fy += fy; | |||
} | |||
} | |||
} | |||
} | |||
} |
@ -1,463 +0,0 @@ | |||
/** | |||
* Created by Alex on 2/4/14. | |||
*/ | |||
var manipulationMixin = { | |||
/** | |||
* clears the toolbar div element of children | |||
* | |||
* @private | |||
*/ | |||
_clearManipulatorBar : function() { | |||
while (this.manipulationDiv.hasChildNodes()) { | |||
this.manipulationDiv.removeChild(this.manipulationDiv.firstChild); | |||
} | |||
}, | |||
/** | |||
* main function, creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar. | |||
* | |||
* @private | |||
*/ | |||
_createManipulatorBar : function() { | |||
// remove bound functions | |||
this.off('select', this.boundFunction); | |||
// reset global variables | |||
this.blockConnectingEdgeSelection = false; | |||
this.forceAppendSelection = false | |||
while (this.manipulationDiv.hasChildNodes()) { | |||
this.manipulationDiv.removeChild(this.manipulationDiv.firstChild); | |||
} | |||
// add the icons to the manipulator div | |||
this.manipulationDiv.innerHTML = "" + | |||
"<span class='manipulationUI add' id='manipulate-addNode'><span class='manipulationLabel'>Add Node</span></span>" + | |||
"<div class='seperatorLine'></div>" + | |||
"<span class='manipulationUI edit' id='manipulate-editNode'><span class='manipulationLabel'>Edit Selected</span></span>" + | |||
"<div class='seperatorLine'></div>" + | |||
"<span class='manipulationUI connect' id='manipulate-connectNode'><span class='manipulationLabel'>Connect Node</span></span>" + | |||
"<div class='seperatorLine'></div>" + | |||
"<span class='manipulationUI delete' id='manipulate-delete'><span class='manipulationLabel'>Delete selected</span></span>"; | |||
// bind the icons | |||
var addButton = document.getElementById("manipulate-addNode"); | |||
addButton.onclick = this._createAddToolbar.bind(this); | |||
var editButton = document.getElementById("manipulate-editNode"); | |||
editButton.onclick = this._createEditToolbar.bind(this); | |||
var connectButton = document.getElementById("manipulate-connectNode"); | |||
connectButton.onclick = this._createConnectToolbar.bind(this); | |||
var deleteButton = document.getElementById("manipulate-delete"); | |||
deleteButton.onclick = this._createDeletionToolbar.bind(this); | |||
}, | |||
/** | |||
* Create the toolbar for adding Nodes | |||
* | |||
* @private | |||
*/ | |||
_createAddToolbar : function() { | |||
// clear the toolbar | |||
this._clearManipulatorBar(); | |||
this.off('select', this.boundFunction); | |||
// create the toolbar contents | |||
this.manipulationDiv.innerHTML = "" + | |||
"<span class='manipulationUI back' id='manipulate-back'><span class='manipulationLabel'>Back</span></span>" + | |||
"<div class='seperatorLine'></div>" + | |||
"<span class='manipulationUI none' id='manipulate-back'><span class='manipulationLabel'>Click in an empty space to place a new node</span></span>"; | |||
// bind the icon | |||
var backButton = document.getElementById("manipulate-back"); | |||
backButton.onclick = this._createManipulatorBar.bind(this); | |||
// we use the boundFunction so we can reference it when we unbind it from the "select" event. | |||
this.boundFunction = this._addNode.bind(this); | |||
this.on('select', this.boundFunction); | |||
}, | |||
/** | |||
* Create the toolbar to edit nodes or edges. | |||
* TODO: edges not implemented yet, unsure what to edit. | |||
* | |||
* @private | |||
*/ | |||
_createEditToolbar : function() { | |||
// clear the toolbar | |||
this.blockConnectingEdgeSelection = false; | |||
this._clearManipulatorBar(); | |||
this.off('select', this.boundFunction); | |||
var message = ""; | |||
if (this._selectionIsEmpty()) { | |||
message = "Select a node or edge to edit."; | |||
} | |||
else { | |||
if (this._getSelectedObjectCount() > 1) { | |||
message = "Select a single node or edge to edit." | |||
this._unselectAll(true); | |||
} | |||
else { | |||
if (this._clusterInSelection()) { | |||
message = "You cannot edit a cluster." | |||
this._unselectAll(true); | |||
} | |||
else { | |||
if (this._getSelectedNodeCount() > 0) { // the selected item is a node | |||
this._createEditNodeToolbar(); | |||
} | |||
else { // the selected item is an edge | |||
this._createEditEdgeToolbar(); | |||
} | |||
} | |||
} | |||
} | |||
if (message != "") { | |||
this.blockConnectingEdgeSelection = true; | |||
// create the toolbar contents | |||
this.manipulationDiv.innerHTML = "" + | |||
"<span class='manipulationUI back' id='manipulate-back'><span class='manipulationLabel'>Back</span></span>" + | |||
"<div class='seperatorLine'></div>" + | |||
"<span class='manipulationUI none' id='manipulate-back'><span class='manipulationLabel'>"+message+"</span></span>"; | |||
// bind the icon | |||
var backButton = document.getElementById("manipulate-back"); | |||
backButton.onclick = this._createManipulatorBar.bind(this); | |||
// we use the boundFunction so we can reference it when we unbind it from the "select" event. | |||
this.boundFunction = this._createEditToolbar.bind(this); | |||
this.on('select', this.boundFunction); | |||
} | |||
}, | |||
/** | |||
* Create the toolbar to edit the selected node. The label and the color can be changed. Other colors are derived from the chosen color. | |||
* TODO: change shape or group? | |||
* | |||
* @private | |||
*/ | |||
_createEditNodeToolbar : function() { | |||
// clear the toolbar | |||
this.blockConnectingEdgeSelection = false; | |||
this._clearManipulatorBar(); | |||
this.off('select', this.boundFunction); | |||
var editObject = this._getEditObject(); | |||
// create the toolbar contents | |||
this.manipulationDiv.innerHTML = "" + | |||
"<span class='manipulationUI back' id='manipulate-back'><span class='manipulationLabel'>Cancel</span></span>" + | |||
"<div class='seperatorLine'></div>" + | |||
"<span class='manipulationUI none'>label: <input type='text' class='manipulatorInput' value='" + editObject.label + "' id='manipulator-obj-label' /></span>" + | |||
"<div class='seperatorLine'></div>" + | |||
"<span class='manipulationUI none'>color: <input type='text' class='manipulatorInput' value='" + editObject.color.background + "' id='manipulator-obj-color' /></span>" + | |||
"<div class='seperatorLine'></div>" + | |||
"<span class='manipulationUI none'><input type='button' class='manipulatorInput' value='save' id='manipulator-obj-save' /></span>" | |||
// bind the icon | |||
var backButton = document.getElementById("manipulate-back"); | |||
backButton.onclick = this._createManipulatorBar.bind(this); | |||
var saveButton = document.getElementById("manipulator-obj-save"); | |||
saveButton.onclick = this._saveNodeData.bind(this); | |||
// we use the boundFunction so we can reference it when we unbind it from the "select" event. | |||
this.boundFunction = this._createManipulatorBar.bind(this); | |||
this.on('select', this.boundFunction); | |||
}, | |||
/** | |||
* save the changes in the node data | |||
* | |||
* @private | |||
*/ | |||
_saveNodeData : function() { | |||
var editObjectId = this._getEditObject().id; | |||
var label = document.getElementById('manipulator-obj-label').value; | |||
var definedColor = document.getElementById('manipulator-obj-color').value; | |||
var hsv = util.hexToHSV(definedColor); | |||
var lighterColorHSV = {h:hsv.h,s:hsv.s * 0.45,v:Math.min(1,hsv.v * 1.05)}; | |||
var darkerColorHSV = {h:hsv.h,s:Math.min(1,hsv.v * 1.25),v:hsv.v*0.6}; | |||
var darkerColorHex = util.HSVToHex(darkerColorHSV.h ,darkerColorHSV.h ,darkerColorHSV.v); | |||
var lighterColorHex = util.HSVToHex(lighterColorHSV.h,lighterColorHSV.s,lighterColorHSV.v); | |||
var updatedSettings = {id:editObjectId, | |||
label: label, | |||
color: { | |||
background:definedColor, | |||
border:darkerColorHex, | |||
highlight: { | |||
background:lighterColorHex, | |||
border:darkerColorHex | |||
} | |||
}}; | |||
this.nodesData.update(updatedSettings); | |||
this._createManipulatorBar(); | |||
}, | |||
/** | |||
* creating the toolbar to edit edges. | |||
* | |||
* @private | |||
*/ | |||
_createEditEdgeToolbar : function() { | |||
// clear the toolbar | |||
this.blockConnectingEdgeSelection = false; | |||
this._clearManipulatorBar(); | |||
this.off('select', this.boundFunction); | |||
// create the toolbar contents | |||
this.manipulationDiv.innerHTML = "" + | |||
"<span class='manipulationUI back' id='manipulate-back'><span class='manipulationLabel'>Back</span></span>" + | |||
"<div class='seperatorLine'></div>" + | |||
"<span class='manipulationUI none' id='manipulate-back'><span class='manipulationLabel'>Currently only nodes can be edited.</span></span>"; | |||
// bind the icon | |||
var backButton = document.getElementById("manipulate-back"); | |||
backButton.onclick = this._createManipulatorBar.bind(this); | |||
// we use the boundFunction so we can reference it when we unbind it from the "select" event. | |||
this.boundFunction = this._createManipulatorBar.bind(this); | |||
this.on('select', this.boundFunction); | |||
}, | |||
/** | |||
* create the toolbar to connect nodes | |||
* | |||
* @private | |||
*/ | |||
_createConnectToolbar : function() { | |||
// clear the toolbar | |||
this._clearManipulatorBar(); | |||
this.off('select', this.boundFunction); | |||
this._unselectAll(); | |||
this.forceAppendSelection = false; | |||
this.blockConnectingEdgeSelection = true; | |||
this.manipulationDiv.innerHTML = "" + | |||
"<span class='manipulationUI back' id='manipulate-back'><span class='manipulationLabel'>Back</span></span>" + | |||
"<div class='seperatorLine'></div>" + | |||
"<span class='manipulationUI none' id='manipulate-back'><span id='manipulatorLabel' class='manipulationLabel'>Select the node you want to connect to other nodes.</span></span>"; | |||
// bind the icon | |||
var backButton = document.getElementById("manipulate-back"); | |||
backButton.onclick = this._createManipulatorBar.bind(this); | |||
// we use the boundFunction so we can reference it when we unbind it from the "select" event. | |||
this.boundFunction = this._handleConnect.bind(this); | |||
this.on('select', this.boundFunction); | |||
}, | |||
/** | |||
* create the toolbar for deleting selected objects. User has to be sure. | |||
* | |||
* @private | |||
*/ | |||
_createDeletionToolbar : function() { | |||
// clear the toolbar | |||
this._clearManipulatorBar(); | |||
this.off('select', this.boundFunction); | |||
if (this._selectionIsEmpty()) { | |||
this.manipulationDiv.innerHTML = "" + | |||
"<span class='manipulationUI none notification' id='manipulate-back'><span id='manipulatorLabel' class='manipulationLabel'>Cannot delete an empty selection.</span></span>"; | |||
var graph = this; | |||
window.setTimeout (function() {graph._createManipulatorBar()},1500); | |||
} | |||
else { | |||
this.manipulationDiv.innerHTML = "" + | |||
"<span class='manipulationUI back' id='manipulate-back'><span class='manipulationLabel'>Back</span></span>" + | |||
"<div class='seperatorLine'></div>" + | |||
"<span class='manipulationUI none' id='manipulate-back'><span id='manipulatorLabel' class='manipulationLabel'>Are you sure? This cannot be undone.</span></span>" + | |||
"<div class='seperatorLine'></div>" + | |||
"<span class='manipulationUI acceptDelete' id='manipulate-acceptDelete'><span class='manipulationLabel'>Yes.</span></span>"; | |||
// bind the buttons | |||
var backButton = document.getElementById("manipulate-back"); | |||
backButton.onclick = this._createManipulatorBar.bind(this); | |||
var acceptDeleteButton = document.getElementById("manipulate-acceptDelete"); | |||
acceptDeleteButton.onclick = this._deleteSelected.bind(this); | |||
// we use the boundFunction so we can reference it when we unbind it from the "select" event. | |||
this.boundFunction = this._createManipulatorBar.bind(this); | |||
this.on('select', this.boundFunction); | |||
} | |||
}, | |||
/** | |||
* the function bound to the selection event. It checks if you want to connect a cluster and changes the description | |||
* to walk the user through the process. | |||
* | |||
* @private | |||
*/ | |||
_handleConnect : function() { | |||
this.forceAppendSelection = false; | |||
if (this._clusterInSelection()) { | |||
this._unselectClusters(true); | |||
if (!this._selectionIsEmpty()) { | |||
this._setManipulationMessage("You cannot connect a node to a cluster."); | |||
this.forceAppendSelection = true; | |||
} | |||
else { | |||
this._setManipulationMessage("You cannot connect anything to a cluster."); | |||
} | |||
} | |||
else if (!this._selectionIsEmpty()) { | |||
if (this._getSelectedNodeCount() == 2) { | |||
this._connectNodes(); | |||
this._restoreSourceNode(); | |||
this._setManipulationMessage("Click on another node you want to connect this node to or go back."); | |||
} | |||
else { | |||
this._setManipulationMessage("Click on the node you want to connect this node."); | |||
this._setSourceNode(); | |||
this.forceAppendSelection = true; | |||
} | |||
} | |||
else { | |||
this._setManipulationMessage("Select the node you want to connect to other nodes."); | |||
} | |||
}, | |||
/** | |||
* returns the object that is selected | |||
* | |||
* @returns {*} | |||
* @private | |||
*/ | |||
_getEditObject : function() { | |||
for(var objectId in this.selectionObj) { | |||
if(this.selectionObj.hasOwnProperty(objectId)) { | |||
return this.selectionObj[objectId]; | |||
} | |||
} | |||
return null; | |||
}, | |||
/** | |||
* stores the first selected node for the connecting process as the source node. This allows us to remember the direction | |||
* | |||
* @private | |||
*/ | |||
_setSourceNode : function() { | |||
for(var objectId in this.selectionObj) { | |||
if(this.selectionObj.hasOwnProperty(objectId)) { | |||
if (this.selectionObj[objectId] instanceof Node) { | |||
this.manipulationSourceNode = this.selectionObj[objectId]; | |||
} | |||
} | |||
} | |||
}, | |||
/** | |||
* gets the node the source connects to. | |||
* | |||
* @returns {*} | |||
* @private | |||
*/ | |||
_getTargetNode : function() { | |||
for(var objectId in this.selectionObj) { | |||
if(this.selectionObj.hasOwnProperty(objectId)) { | |||
if (this.selectionObj[objectId] instanceof Node) { | |||
if (this.manipulationSourceNode.id != this.selectionObj[objectId].id) { | |||
return this.selectionObj[objectId]; | |||
} | |||
} | |||
} | |||
} | |||
return null; | |||
}, | |||
/** | |||
* restore the selection back to only the sourcenode | |||
* | |||
* @private | |||
*/ | |||
_restoreSourceNode : function() { | |||
this._unselectAll(true); | |||
this._selectObject(this.manipulationSourceNode); | |||
}, | |||
/** | |||
* change the description message on the toolbar | |||
* | |||
* @param message | |||
* @private | |||
*/ | |||
_setManipulationMessage : function(message) { | |||
var messageSpan = document.getElementById('manipulatorLabel'); | |||
messageSpan.innerHTML = message; | |||
}, | |||
/** | |||
* Adds a node on the specified location | |||
* | |||
* @param {Object} pointer | |||
*/ | |||
_addNode : function() { | |||
if (this._selectionIsEmpty()) { | |||
var positionObject = this._pointerToPositionObject(this.pointerPosition); | |||
this.createNodeOnClick = true; | |||
this.nodesData.add({id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",fixed:false}); | |||
this.createNodeOnClick = false; | |||
this.moving = true; | |||
this.start(); | |||
} | |||
}, | |||
/** | |||
* connect two nodes with a new edge. | |||
* | |||
* @private | |||
*/ | |||
_connectNodes : function() { | |||
var targetNode = this._getTargetNode(); | |||
var sourceNode = this.manipulationSourceNode; | |||
this.edgesData.add({from:sourceNode.id, to:targetNode.id}) | |||
this.moving = true; | |||
this.start(); | |||
}, | |||
/** | |||
* delete everything in the selection | |||
* | |||
* @private | |||
*/ | |||
_deleteSelected : function() { | |||
if (!this._clusterInSelection()) { | |||
var selectedNodes = this.getSelectedNodes(); | |||
var selectedEdges = this.getSelectedEdges(); | |||
this._removeEdges(selectedEdges); | |||
this._removeNodes(selectedNodes); | |||
this.moving = true; | |||
this.start(); | |||
} | |||
else { | |||
alert("Clusters cannot be deleted.") | |||
} | |||
} | |||
} |