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