Conflicts: Jakefile.js examples/graph/index.html src/graph/Edge.js src/graph/Graph.js src/graph/graphMixins/ClusterMixin.js src/graph/graphMixins/SelectionMixin.js src/timeline/Controller.js src/timeline/Range.js src/timeline/Timeline.js src/timeline/component/ItemSet.js src/timeline/component/RootPanel.js src/timeline/component/item/Item.js src/util.jscss_transitions
@ -0,0 +1 @@ | |||||
visjs.org |
@ -0,0 +1,94 @@ | |||||
#menu { | |||||
position: absolute; | |||||
left: -170px; | |||||
top: 35px; | |||||
background-color: #a7c8f9; | |||||
padding: 15px; | |||||
border-radius: 3px; | |||||
} | |||||
#forkme { | |||||
position: fixed; | |||||
top: 0; | |||||
right: 0; | |||||
border: 0; | |||||
} | |||||
div.nav { | |||||
text-align: center; | |||||
} | |||||
div.nav ul { | |||||
text-decoration: none; | |||||
text-transform: uppercase; | |||||
margin-bottom: 30px; | |||||
padding-left: 0; | |||||
} | |||||
li.nav { | |||||
} | |||||
div.nav ul li { | |||||
text-decoration: none; | |||||
text-transform: uppercase; | |||||
font-weight: normal; | |||||
font-size: 11pt; | |||||
list-style: none; | |||||
margin-top: 5px; | |||||
} | |||||
div.nav ul li ul li { | |||||
text-decoration: none; | |||||
text-transform: none; | |||||
font-weight: normal; | |||||
font-size: 11pt; | |||||
color: #4D4D4D; | |||||
list-style: none; | |||||
} | |||||
div.nav a { | |||||
color: #2B7CE9; | |||||
color: white; | |||||
font-weight: bold; | |||||
} | |||||
.subtitle { | |||||
color: gray; | |||||
text-transform: uppercase; | |||||
font-size: 11pt; | |||||
} | |||||
.download td { | |||||
border: none; | |||||
padding: 5px 20px 5px 0; | |||||
} | |||||
.gallery .thumb { | |||||
display: inline-block; | |||||
text-align: center; | |||||
margin-right: 10px; | |||||
margin-bottom: 20px; | |||||
} | |||||
.gallery .thumb img { | |||||
border: 1px solid white; | |||||
border-radius: 5px; | |||||
height: 90px; | |||||
margin: 0; | |||||
} | |||||
.gallery .thumb a:hover img { | |||||
border-color: lightgray; | |||||
} | |||||
.gallery .thumb div { | |||||
margin: 0; | |||||
} | |||||
img { | |||||
border: 0; | |||||
} |
@ -0,0 +1,214 @@ | |||||
<!doctype html> | |||||
<html> | |||||
<head> | |||||
<title>Graph | Navigation</title> | |||||
<style type="text/css"> | |||||
body { | |||||
font: 10pt sans; | |||||
} | |||||
#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; | |||||
} | |||||
#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>Editing the dataset</h2> | |||||
<div style="width: 700px; font-size:14px;"> | |||||
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 /> | |||||
<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> |
@ -0,0 +1,40 @@ | |||||
/**************************************\ | |||||
Shapes4FREE License | |||||
http://www.shapes4free.com/ - visit us to get free photoshop shapes, read our easy-to-understand shapes tutorials and tips, and view beautiful examples of using shapes in all kinds of design | |||||
More about the license: http://www.shapes4free.com/license/ | |||||
\**************************************/ | |||||
This resource was created by Oksana Khristenko | |||||
This resource has been downloaded from Shapes4FREE.com and is free for personal or commercial projects. You may use it for web and print design. | |||||
No attribution or backlinks are required, but we would certainly appreciate it if you bookmarked www.shapes4free.com and shared the link to it with your friends: | |||||
www.shapes4free.com - free photoshop shapes | |||||
You may not resell or distribute this resource. Uploading it to another website | |||||
or offering them for download on another website is not allowed. If you would like to feature this resource on | |||||
your website or share them with friends, do not link directly to the resource files, | |||||
please link to the appropriate page on Shapes4FREE.com where it is possible to download the freebie. | |||||
/**************************************\ | |||||
Shapes4FREE Ëèöåíçèÿ | |||||
http://www.shapes4free.com/ - áåñïëàòíûå ôèãóðû äëÿ Ôîòîøîïà, óðîêè è ïîäñêàçêè, à òàêæå êðàñèâûå ïðèìåðû èñïîëüçîâàíèÿ ôèãóð âî âñåõ âèäàõ äèçàéíà | |||||
Ëèöåíçèÿ: http://www.shapes4free.com/license-ru/ | |||||
\**************************************/ | |||||
Àâòîð: Îêñàíà Õðèñòåíêî | |||||
Âñå áåñïëàòíûå ðåñóðñû êîòîðûå ìîæíî ñêà÷àòü íà Shapes4FREE.com, âêëþ÷àÿ ïðîèçâîëüíûå ôèãóðû äëÿ Ôîòîøîïà | |||||
(photoshop custom shapes) áåñïëàòíû äëÿ èñïîëüçîâàíèÿ â ëè÷íûõ è êîììåð÷åñêèõ ïðîåêòàõ. Ðàçðåøåíî èñïîëüçîâàòü | |||||
áåñïëàòíûå ðåñóðñû Shapes4FREE â âåá äèçàéíå è ïå÷àòíûõ ìàòåðèàëàõ. | |||||
Ññûëêà íà ñàéò Shapes4FREE.com íå òðåáóåòñÿ íî ïðèâåòñòâóåòñÿ. Ìû áóäåì ðàäû åñëè âû ðàññêàæåòå î íàñ äðóçüÿì: | |||||
www.shapes4free.com - áåñïëàòíûå ôèãóðû äëÿ Ôîòîøîïà | |||||
Çàïðåùåíî ïðîäàâàòü èëè ðàñïðîñòðàíÿòü áåñïëàòíûå ðåñóðñû ñîçäàííûå Shapes4FREE. | |||||
Çàïðåùåíî çàãðóæàòü èõ íà äðóãèå ñàéòû è ïîçâîëÿòü ïîëüçîâàòåëÿì èõ ñêà÷èâàòü. Åñëè âû õîòèòå ðàññêàçàòü î íàøåì | |||||
áåñïëàòíîì ðåñóðñå íà ñàéòå èëè ïîäåëèòüñÿ ñ äðóçüÿìè, íå ñîçäàâàéòå ïðÿìûõ ññûëîê íà ôàéë, ñîçäàéòå ññûëêó íà | |||||
ñîîòâåòñòâóþùóþ ñòðàíèöó ñàéòà Shapes4FREE.com ãäå ìîæíî áóäåò ñêà÷àòü ýòîò ðåñóðñ. | |||||
@ -0,0 +1,169 @@ | |||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||||
<!-- Created with Inkscape (http://www.inkscape.org/) --> | |||||
<svg | |||||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||||
xmlns:cc="http://creativecommons.org/ns#" | |||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||||
xmlns:svg="http://www.w3.org/2000/svg" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||||
width="128" | |||||
height="128" | |||||
id="svg2" | |||||
version="1.1" | |||||
inkscape:version="0.48.3.1 r9886" | |||||
sodipodi:docname="vis.svg" | |||||
inkscape:export-filename="/home/jos/projects/vis-pages/img/logo/vis256.png" | |||||
inkscape:export-xdpi="180" | |||||
inkscape:export-ydpi="180"> | |||||
<defs | |||||
id="defs4"> | |||||
<filter | |||||
inkscape:collect="always" | |||||
id="filter3765"> | |||||
<feGaussianBlur | |||||
inkscape:collect="always" | |||||
stdDeviation="0.098994946" | |||||
id="feGaussianBlur3767" /> | |||||
</filter> | |||||
<filter | |||||
color-interpolation-filters="sRGB" | |||||
inkscape:collect="always" | |||||
id="filter3765-8"> | |||||
<feGaussianBlur | |||||
inkscape:collect="always" | |||||
stdDeviation="0.098994946" | |||||
id="feGaussianBlur3767-2" /> | |||||
</filter> | |||||
<filter | |||||
color-interpolation-filters="sRGB" | |||||
inkscape:collect="always" | |||||
id="filter3765-8-3"> | |||||
<feGaussianBlur | |||||
inkscape:collect="always" | |||||
stdDeviation="0.098994946" | |||||
id="feGaussianBlur3767-2-9" /> | |||||
</filter> | |||||
<filter | |||||
color-interpolation-filters="sRGB" | |||||
inkscape:collect="always" | |||||
id="filter3765-3"> | |||||
<feGaussianBlur | |||||
inkscape:collect="always" | |||||
stdDeviation="0.098994946" | |||||
id="feGaussianBlur3767-3" /> | |||||
</filter> | |||||
</defs> | |||||
<sodipodi:namedview | |||||
id="base" | |||||
pagecolor="#ffffff" | |||||
bordercolor="#666666" | |||||
borderopacity="1.0" | |||||
inkscape:pageopacity="0.0" | |||||
inkscape:pageshadow="2" | |||||
inkscape:zoom="2.8284271" | |||||
inkscape:cx="13.788283" | |||||
inkscape:cy="60.335199" | |||||
inkscape:document-units="px" | |||||
inkscape:current-layer="layer1" | |||||
showgrid="false" | |||||
inkscape:snap-global="true" | |||||
inkscape:window-width="1600" | |||||
inkscape:window-height="850" | |||||
inkscape:window-x="0" | |||||
inkscape:window-y="0" | |||||
inkscape:window-maximized="1" | |||||
inkscape:showpageshadow="false" | |||||
borderlayer="false" | |||||
showborder="true"> | |||||
<inkscape:grid | |||||
type="xygrid" | |||||
id="grid2987" | |||||
empspacing="5" | |||||
visible="true" | |||||
enabled="true" | |||||
snapvisiblegridlinesonly="true" /> | |||||
</sodipodi:namedview> | |||||
<metadata | |||||
id="metadata7"> | |||||
<rdf:RDF> | |||||
<cc:Work | |||||
rdf:about=""> | |||||
<dc:format>image/svg+xml</dc:format> | |||||
<dc:type | |||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||||
<dc:title></dc:title> | |||||
</cc:Work> | |||||
</rdf:RDF> | |||||
</metadata> | |||||
<g | |||||
inkscape:label="Layer 1" | |||||
inkscape:groupmode="layer" | |||||
id="layer1" | |||||
transform="translate(0,-924.36217)"> | |||||
<path | |||||
sodipodi:type="arc" | |||||
style="fill:#109618;fill-opacity:1;stroke:none;opacity:0.01587302" | |||||
id="path4104" | |||||
sodipodi:cx="64.25" | |||||
sodipodi:cy="64.5" | |||||
sodipodi:rx="63.75" | |||||
sodipodi:ry="64" | |||||
d="m 128,64.5 a 63.75,64 0 1 1 -127.5,0 63.75,64 0 1 1 127.5,0 z" | |||||
transform="matrix(0.93139646,0,0,0.83984375,-189.81956,958.94835)" /> | |||||
<path | |||||
style="fill:#ffcf00;fill-opacity:1;stroke:#ffcf00;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" | |||||
d="M 3.3795626,993.2579 C 22.563164,969.55671 64.941101,938.83212 101.8906,932.98778 c 15.01437,32.97153 15.80482,65.24066 11.89745,97.27812 -53.04371,-1.7716 -84.489701,-14.912 -110.4084874,-37.008 z" | |||||
id="path2991" | |||||
inkscape:connector-curvature="0" | |||||
sodipodi:nodetypes="cccc" /> | |||||
<path | |||||
style="fill:#109618;fill-opacity:1;stroke:#109618;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" | |||||
d="m 4.586161,1010.1966 c 29.519439,19.0948 61.669847,32.8593 114.396079,29.9232 6.98138,-4.9614 5.91066,-11.2031 3.61936,-17.6388 -37.656342,4.9772 -79.514689,1.6063 -118.015439,-12.2844 z" | |||||
id="path3761" | |||||
inkscape:connector-curvature="0" | |||||
sodipodi:nodetypes="cccc" /> | |||||
<path | |||||
sodipodi:type="arc" | |||||
style="fill:#ff9900;fill-opacity:1;stroke:none;filter:url(#filter3765-8)" | |||||
id="path3763-6" | |||||
sodipodi:cx="66.998367" | |||||
sodipodi:cy="63.476505" | |||||
sodipodi:rx="10.429825" | |||||
sodipodi:ry="9.3691645" | |||||
d="m 77.428192,63.476505 a 10.429825,9.3691645 0 1 1 -20.85965,0 10.429825,9.3691645 0 1 1 20.85965,0 z" | |||||
transform="matrix(3.7791753,0,0,4.159068,-180.52628,715.8869)" /> | |||||
<path | |||||
sodipodi:type="arc" | |||||
style="fill:#dc3912;fill-opacity:1;stroke:none;filter:url(#filter3765-8-3)" | |||||
id="path3763-6-3" | |||||
sodipodi:cx="66.998367" | |||||
sodipodi:cy="63.476505" | |||||
sodipodi:rx="10.429825" | |||||
sodipodi:ry="9.3691645" | |||||
d="m 77.428192,63.476505 a 10.429825,9.3691645 0 1 1 -20.85965,0 10.429825,9.3691645 0 1 1 20.85965,0 z" | |||||
transform="matrix(2.1560756,0,0,2.3930971,-65.92105,839.4416)" /> | |||||
<path | |||||
sodipodi:type="arc" | |||||
style="fill:#ffffff;fill-opacity:1;stroke:none;filter:url(#filter3765)" | |||||
id="path3763" | |||||
sodipodi:cx="66.998367" | |||||
sodipodi:cy="63.476505" | |||||
sodipodi:rx="10.429825" | |||||
sodipodi:ry="9.3691645" | |||||
d="m 77.428192,63.476505 a 10.429825,9.3691645 0 1 1 -20.85965,0 10.429825,9.3691645 0 1 1 20.85965,0 z" | |||||
transform="matrix(1.369772,0,0,1.5215787,-11.897888,900.22297)" /> | |||||
<path | |||||
sodipodi:type="arc" | |||||
style="fill:#4d4d4d;fill-opacity:1;stroke:none;filter:url(#filter3765-3)" | |||||
id="path3763-9" | |||||
sodipodi:cx="66.998367" | |||||
sodipodi:cy="63.476505" | |||||
sodipodi:rx="10.429825" | |||||
sodipodi:ry="9.3691645" | |||||
d="m 77.428192,63.476505 a 10.429825,9.3691645 0 1 1 -20.85965,0 10.429825,9.3691645 0 1 1 20.85965,0 z" | |||||
transform="matrix(0.68506744,0,0,0.76063592,38.273417,952.729)" /> | |||||
</g> | |||||
</svg> |
@ -0,0 +1,317 @@ | |||||
<!doctype html> | |||||
<html> | |||||
<head> | |||||
<title>vis.js | a dynamic, browser-based visualization library</title> | |||||
<meta charset='utf-8' /> | |||||
<meta name="title" content="vis.js"> | |||||
<meta name="description" content="vis.js is a dynamic, browser-based visualization library" /> | |||||
<meta name="keywords" content="vis, visualization, javascript, browser based, web based, chart, linechart, timeline, graph, network, browser" /> | |||||
<meta name="author" content="Almende B.V."> | |||||
<link href="docs/css/prettify.css" type="text/css" rel="stylesheet" /> | |||||
<link href='docs/css/style.css' type='text/css' rel='stylesheet'> | |||||
<link href="css/style.css" type="text/css" rel="stylesheet" > | |||||
<script type="text/javascript" src="docs/lib/prettify/prettify.js"></script> | |||||
</head> | |||||
<body onload="prettyPrint();"> | |||||
<div id="container"> | |||||
<div id="menu"> | |||||
<a href="http://visjs.org/"><img src="img/logo/vis128.png" alt="logo"></a> | |||||
<div class="nav"> | |||||
<ul> | |||||
<li><a href="#install">Install</a></li> | |||||
<li><a href="#example">Example</a></li> | |||||
<li><a href="#gallery">Gallery</a></li> | |||||
<li> | |||||
<a href="docs/index.html" target="_blank"> | |||||
Docs | |||||
<img src="img/external-link-icons/external-link-icon.png" style="vertical-align: text-top;" title="Docs will open in a new window"> | |||||
</a> | |||||
</li> | |||||
<li><a href="#license">License</a></li> | |||||
</ul> | |||||
</div> | |||||
</div> | |||||
<h1> | |||||
vis.js<br> | |||||
<span class="subtitle">a visual interaction system</span> | |||||
</h1> | |||||
<p> | |||||
Vis.js is a dynamic, browser based visualization library. | |||||
The library is designed to be easy to use, to handle large amounts | |||||
of dynamic data, and to enable manipulation of and interaction with the data. | |||||
The library consists of the components DataSet, Timeline, and Graph. | |||||
</p> | |||||
<p> | |||||
The vis.js library is developed by <a href="http://almende.com" target="_blank">Almende B.V</a>, | |||||
as part of the <a href="http://chap.almende.com/" target="_blank">CHAP</a>. | |||||
</p> | |||||
<h2 id="install">Install</h2> | |||||
<h3>npm</h3> | |||||
<pre class="prettyprint"> | |||||
npm install vis | |||||
</pre> | |||||
<h3>bower</h3> | |||||
<pre class="prettyprint"> | |||||
bower install vis | |||||
</pre> | |||||
<h3>download</h3> | |||||
<a href="download/vis.zip">Click here to download vis.js</a> | |||||
(version <span class="version">0.4.0</span>) | |||||
<h2 id="example">Example</h2> | |||||
<p> | |||||
A basic example demonstrating how to use the vis.js timeline is shown below. | |||||
See the <a href="#gallery">gallery</a> below for more examples. | |||||
</p> | |||||
<pre class="prettyprint lang-html"><!doctype html> | |||||
<html> | |||||
<head> | |||||
<title>Timeline | Basic demo</title> | |||||
<script src="http://visjs.org/dist/vis.js"></script> | |||||
<link href="http://visjs.org/dist/vis.css" rel="stylesheet" type="text/css" /> | |||||
<style type="text/css"> | |||||
body, html { | |||||
font-family: sans-serif; | |||||
} | |||||
</style> | |||||
</head> | |||||
<body> | |||||
<div id="mytimeline"></div> | |||||
<script type="text/javascript"> | |||||
var container = document.getElementById('mytimeline'); | |||||
var data = [ | |||||
{id: 1, content: 'item 1', start: '2013-04-20'}, | |||||
{id: 2, content: 'item 2', start: '2013-04-14'}, | |||||
{id: 3, content: 'item 3', start: '2013-04-18'}, | |||||
{id: 4, content: 'item 4', start: '2013-04-16', end: '2013-04-19'}, | |||||
{id: 5, content: 'item 5', start: '2013-04-25'}, | |||||
{id: 6, content: 'item 6', start: '2013-04-27'} | |||||
]; | |||||
var options = {}; | |||||
var timeline = new vis.Timeline(container, data, options); | |||||
</script> | |||||
</body> | |||||
</html> | |||||
</pre> | |||||
<h2 id="gallery">Gallery</h2> | |||||
This gallery gives an idea of the features and possibilities of the library. | |||||
The source code of the examples can be found in the | |||||
<a href="https://github.com/almende/vis/tree/master/examples" target="_blank">examples directory</a>. | |||||
<h3 id="timeline">Timeline</h3> | |||||
<p> | |||||
The timeline from vis.js displays different types of data on a timeline. | |||||
</p> | |||||
<div class="gallery"> | |||||
<div class="thumb"> | |||||
<a href="examples/timeline/01_basic.html"> | |||||
<img src="img/gallery/timeline/01_basic.png"> | |||||
<div>basic usage</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/timeline/02_dataset.html"> | |||||
<img src="img/gallery/timeline/02_dataset.png"> | |||||
<div>dataset</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/timeline/03_much_data.html"> | |||||
<img src="img/gallery/timeline/03_much_data.png"> | |||||
<div>much data</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/timeline/04_html_data.html"> | |||||
<img src="img/gallery/timeline/04_html_data.png"> | |||||
<div>html data</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/timeline/05_groups.html"> | |||||
<img src="img/gallery/timeline/05_groups.png"> | |||||
<div>groups</div> | |||||
</a> | |||||
</div> | |||||
</div> | |||||
<h3 id="graph">Graph</h3> | |||||
<p> | |||||
The graph from vis.js visualizes graphs and networks with | |||||
customizable styles. | |||||
</p> | |||||
<div class="gallery"> | |||||
<div class="thumb"> | |||||
<a href="examples/graph/01_basic_usage.html"> | |||||
<img src="img/gallery/graph/01_basic_usage.png"> | |||||
<div>basic usage</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/graph/02_random_nodes.html"> | |||||
<img src="img/gallery/graph/02_random_nodes.png"> | |||||
<div>random nodes</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/graph/03_images.html"> | |||||
<img src="img/gallery/graph/03_images.png"> | |||||
<div>images</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/graph/04_shapes.html"> | |||||
<img src="img/gallery/graph/04_shapes.png"> | |||||
<div>shapes</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/graph/05_social_network.html"> | |||||
<img src="img/gallery/graph/05_social_network.png"> | |||||
<div>social network</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/graph/06_groups.html"> | |||||
<img src="img/gallery/graph/06_groups.png"> | |||||
<div>groups</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/graph/07_selections.html"> | |||||
<img src="img/gallery/graph/07_selections.png"> | |||||
<div>selections</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/graph/08_mobile_friendly.html"> | |||||
<img src="img/gallery/graph/08_mobile_friendly.png"> | |||||
<div>mobile friendly</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/graph/09_sizing.html"> | |||||
<img src="img/gallery/graph/09_sizing.png"> | |||||
<div>sizing</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/graph/10_multiline_text.html"> | |||||
<img src="img/gallery/graph/10_multiline_text.png"> | |||||
<div>multiline text</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/graph/11_custom_style.html"> | |||||
<img src="img/gallery/graph/11_custom_style.png"> | |||||
<div>custom style</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/graph/12_scalable_images.html"> | |||||
<img src="img/gallery/graph/12_scalable_images.png"> | |||||
<div>scalable images</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/graph/13_dashed_lines.html"> | |||||
<img src="img/gallery/graph/13_dashed_lines.png"> | |||||
<div>dashed lines</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/graph/14_dot_language.html"> | |||||
<img src="img/gallery/graph/14_dot_language.png"> | |||||
<div>dot language</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/graph/15_dot_language_playground.html"> | |||||
<img src="img/gallery/graph/15_dot_language_playground.png"> | |||||
<div>playground</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/graph/16_dynamic_data.html"> | |||||
<img src="img/gallery/graph/16_dynamic_data.png"> | |||||
<div>dynamic data</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/graph/17_network_info.html"> | |||||
<img src="img/gallery/graph/17_network_info.png"> | |||||
<div>network info</div> | |||||
</a> | |||||
</div> | |||||
<div class="thumb"> | |||||
<a href="examples/graph/graphviz/graphviz_gallery.html"> | |||||
<img src="img/gallery/graph/graphviz_gallery.png"> | |||||
<div>graphviz gallery</div> | |||||
</a> | |||||
</div> | |||||
</div> | |||||
<h2 id="docs">Docs</h2> | |||||
<p> | |||||
Documentation is available here: | |||||
<a href="docs/index.html" target="_blank">Documentation</a> | |||||
</p> | |||||
<h2 id="license">License</h2> | |||||
<p> | |||||
Copyright (C) 2010-2014 Almende B.V. | |||||
</p> | |||||
<p> | |||||
Licensed under the Apache License, Version 2.0 (the "License"); | |||||
you may not use this file except in compliance with the License. | |||||
You may obtain a copy of the License at | |||||
</p> | |||||
<p> | |||||
http://www.apache.org/licenses/LICENSE-2.0 | |||||
</p> | |||||
<p> | |||||
Unless required by applicable law or agreed to in writing, software | |||||
distributed under the License is distributed on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
See the License for the specific language governing permissions and | |||||
limitations under the License. | |||||
</p> | |||||
<a id="forkme" href="https://github.com/almende/vis/" target="_blank"> | |||||
<img src="img/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" > | |||||
</a> | |||||
</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,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,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,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; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,58 @@ | |||||
// Update the version numbers and library sizes in index.html | |||||
var fs = require('fs'), | |||||
zlib = require('zlib'); | |||||
var VIS_ZIP = './dist/vis.js', | |||||
INDEX = 'index.html'; | |||||
// read version from dist/vis.js | |||||
function version(callback) { | |||||
fs.readFile(VIS_ZIP, function (err, data) { | |||||
if (!err) { | |||||
var match = /@version\s*([\w\.-]*)/i.exec(data); | |||||
var version = undefined; | |||||
if (match) { | |||||
version = match[1]; | |||||
} | |||||
callback(null, version); | |||||
} | |||||
else { | |||||
callback(err); | |||||
} | |||||
}); | |||||
} | |||||
// update version and library sizes in index.md | |||||
function updateVersion(version, callback) { | |||||
fs.readFile(INDEX, function (err, data) { | |||||
if (!err) { | |||||
data = String(data); | |||||
data = data.replace(/<span class="version">([\w\.-]*)<\/span>/g, | |||||
'<span class="version">' + version + '</span>'); | |||||
fs.writeFile(INDEX, data, callback); | |||||
} | |||||
else { | |||||
callback(err); | |||||
} | |||||
}); | |||||
} | |||||
version(function (err, version) { | |||||
console.log('version: ' + version); | |||||
if (version) { | |||||
updateVersion(version, function (err, res) { | |||||
if (err) { | |||||
console.log(err); | |||||
} | |||||
else { | |||||
console.log('done'); | |||||
} | |||||
}); | |||||
} | |||||
else { | |||||
} | |||||
}); |