Browse Source

Added data manipulation functionality

css_transitions
Alex de Mulder 10 years ago
parent
commit
12669bcc54
20 changed files with 1516 additions and 679 deletions
  1. BIN
      dist/img/acceptDeleteIcon.png
  2. BIN
      dist/img/editIcon.png
  3. +743
    -475
      dist/vis.js
  4. +8
    -23
      dist/vis.min.js
  5. +0
    -11
      examples/graph/02_random_nodes.html
  6. +0
    -11
      examples/graph/18_fully_random_nodes_clustering.html
  7. +0
    -11
      examples/graph/19_scale_free_graph_clustering.html
  8. +0
    -1
      examples/graph/20_navigation.html
  9. +98
    -73
      examples/graph/21_data_manipulation.html
  10. +1
    -0
      examples/graph/index.html
  11. +52
    -28
      src/graph/Graph.js
  12. +94
    -15
      src/graph/SelectionMixin.js
  13. BIN
      src/graph/img/acceptDeleteIcon.png
  14. BIN
      src/graph/img/addNodeIcon.png
  15. BIN
      src/graph/img/backIcon.png
  16. BIN
      src/graph/img/connectIcon.png
  17. BIN
      src/graph/img/deleteIcon.png
  18. BIN
      src/graph/img/editIcon.png
  19. +366
    -31
      src/graph/manipulationMixin.js
  20. +154
    -0
      src/util.js

BIN
dist/img/acceptDeleteIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
dist/img/editIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

+ 743
- 475
dist/vis.js
File diff suppressed because it is too large
View File


+ 8
- 23
dist/vis.min.js
File diff suppressed because it is too large
View File


+ 0
- 11
examples/graph/02_random_nodes.html View File

@ -94,19 +94,8 @@
graph = new vis.Graph(container, data, options);
// add event listeners
<<<<<<< HEAD
<<<<<<< HEAD
vis.events.addListener(graph, 'select', function(params) {
document.getElementById('selection').innerHTML =
'Selection: ' + JSON.stringify(graph.getSelection());
=======
graph.on('select', function(params) {
document.getElementById('selection').innerHTML = 'Selection: ' + params.nodes;
>>>>>>> develop
=======
graph.on('select', function(params) {
document.getElementById('selection').innerHTML = 'Selection: ' + params.nodes;
>>>>>>> origin/gh-pages
});
}
</script>

+ 0
- 11
examples/graph/18_fully_random_nodes_clustering.html View File

@ -64,19 +64,8 @@
graph = new vis.Graph(container, data, options);
// add event listeners
<<<<<<< HEAD
<<<<<<< HEAD
vis.events.addListener(graph, 'select', function(params) {
document.getElementById('selection').innerHTML =
'Selection: ' + JSON.stringify(graph.getSelection());
=======
graph.on('select', function(params) {
document.getElementById('selection').innerHTML = 'Selection: ' + params.nodes;
>>>>>>> develop
=======
graph.on('select', function(params) {
document.getElementById('selection').innerHTML = 'Selection: ' + params.nodes;
>>>>>>> origin/gh-pages
});
}
</script>

+ 0
- 11
examples/graph/19_scale_free_graph_clustering.html View File

@ -100,19 +100,8 @@
graph = new vis.Graph(container, data, options);
// add event listeners
<<<<<<< HEAD
<<<<<<< HEAD
vis.events.addListener(graph, 'select', function(params) {
document.getElementById('selection').innerHTML =
'Selection: ' + JSON.stringify(graph.getSelection());
=======
graph.on('select', function(params) {
document.getElementById('selection').innerHTML = 'Selection: ' + params.nodes;
>>>>>>> develop
=======
graph.on('select', function(params) {
document.getElementById('selection').innerHTML = 'Selection: ' + params.nodes;
>>>>>>> origin/gh-pages
});
}
</script>

+ 0
- 1
examples/graph/20_navigation.html View File

@ -31,7 +31,6 @@
div.table_description {
width:100px;
}
</style>
<script type="text/javascript" src="../../dist/vis.js"></script>

examples/graph/20_UI_example.html → examples/graph/21_data_manipulation.html View File

@ -1,7 +1,7 @@
<!doctype html>
<html>
<head>
<title>Graph | Random nodes</title>
<title>Graph | Navigation</title>
<style type="text/css">
body {
@ -13,24 +13,25 @@
border: 1px solid lightgray;
}
table.legend_table {
font-size: 11px;
border-width:1px;
border-color:#d3d3d3;
border-style:solid;
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;
border-width:1px;
border-color:#d3d3d3;
border-style:solid;
padding: 2px;
}
div.table_content {
width:80px;
text-align:center;
width:80px;
text-align:center;
}
div.table_description {
width:100px;
width:100px;
}
div.graph-manipulationDiv {
border-width:0px;
border-bottom: 1px;
@ -51,10 +52,12 @@
}
span.manipulationUI {
font-family: verdana;
font-size: 12px;
-moz-border-radius: 15px;
border-radius: 15px;
display:inline-block;
background-position: 4px 0px;
background-position: 0px 0px;
background-repeat:no-repeat;
height:24px;
margin: -14px 0px 0px 10px;
@ -88,11 +91,22 @@
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");
}
@ -100,19 +114,32 @@
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 25px;
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>
@ -177,78 +204,76 @@
nodes: nodes,
edges: edges
};
/*
/*
var options = {
nodes: {
shape: 'circle'
},
edges: {
length: 50
},
stabilize: false
};
*/
var options = {
nodes: {
shape: 'circle'
},
edges: {
length: 50
},
stabilize: false
stabilize: false,
clustering:true,
navigation: true,
keyboard: true,
dataManipulationToolbar: true
};
*/
var options = {
edges: {
length: 50
},
stabilize: false,
navigationUI: {
enabled: true
},
keyboardNavigation: {
enabled: true
}
};
graph = new vis.Graph(container, data, options);
// add event listeners
vis.events.addListener(graph, 'select', function(params) {
document.getElementById('selection').innerHTML =
'Selection: ' + JSON.stringify(graph.getSelection());
graph.on('select', function(params) {
document.getElementById('selection').innerHTML = 'Selection: ' + params.nodes;
});
}
</script>
</head>
<body onload="draw();">
<h2>UI - User Interface and Keyboad Navigation</h2>
<h2>Navigation controls and keyboad navigation</h2>
<div style="width: 700px; font-size:14px;">
This example is the same as example 2, except for the UI that has been activated. The UI icons are described below. <br /><br />
<table class="legend_table">
<tr>
<td>Icons: </td>
<td><div class="table_content"><img src="../../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>
<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.
This example is the same as example 2, except for the navigation controls that has been activated. The navigation controls are described below. <br /><br />
<table class="legend_table">
<tr>
<td>Icons: </td>
<td><div class="table_content"><img src="../../dist/img/uparrow.png" /> </div></td>
<td><div class="table_content"><img src="../../dist/img/downarrow.png" /> </div></td>
<td><div class="table_content"><img src="../../dist/img/leftarrow.png" /> </div></td>
<td><div class="table_content"><img src="../../dist/img/rightarrow.png" /> </div></td>
<td><div class="table_content"><img src="../../dist/img/plus.png" /> </div></td>
<td><div class="table_content"><img src="../../dist/img/minus.png" /> </div></td>
<td><div class="table_content"><img src="../../dist/img/zoomExtends.png" /> </div></td>
</tr>
<tr>
<td><div class="table_description">Keyboard shortcuts:</div></td>
<td><div class="table_content">Up arrow</div></td>
<td><div class="table_content">Down arrow</div></td>
<td><div class="table_content">Left arrow</div></td>
<td><div class="table_content">Right arrow</div></td>
<td><div class="table_content">=<br />[<br />Page up</div></td>
<td><div class="table_content">-<br />]<br />Page down</div></td>
<td><div class="table_content">None</div></td>
</tr>
<tr>
<td><div class="table_description">Description:</div></td>
<td><div class="table_content">Move up</div></td>
<td><div class="table_content">Move down</div></td>
<td><div class="table_content">Move left</div></td>
<td><div class="table_content">Move right</div></td>
<td><div class="table_content">Zoom in</div></td>
<td><div class="table_content">Zoom out</div></td>
<td><div class="table_content">Zoom extends</div></td>
</tr>
</table>
<br />
Apart from clicking the icons, you can also navigate using the keyboard. The buttons are in table above.
Zoom Extends changes the zoom and position of the camera to encompass all visible nodes.
</div>

+ 1
- 0
examples/graph/index.html View File

@ -32,6 +32,7 @@
<p><a href="18_fully_random_nodes_clustering.html">18_fully_random_nodes_clustering.html</a></p>
<p><a href="19_scale_free_graph_clustering.html">19_scale_free_graph_clustering.html</a></p>
<p><a href="20_navigation.html">20_navigation.html</a></p>
<p><a href="21_data_manipulation.html">21_data_manipulation.html</a></p>
<p><a href="graphviz/graphviz_gallery.html">graphviz_gallery.html</a></p>
</div>

+ 52
- 28
src/graph/Graph.js View File

@ -94,6 +94,9 @@ function Graph (container, data, options) {
enabled: false,
speed: {x: 10, y: 10, zoom: 0.02}
},
dataManipulationToolbar: {
enabled: false
},
minVelocity: 2, // px/s
maxIterations: 1000 // maximum number of iteration to stabilize
};
@ -110,7 +113,6 @@ function Graph (container, data, options) {
this.yIncrement = 0;
this.zoomIncrement = 0;
// create a frame and canvas
this._create();
@ -123,18 +125,9 @@ function Graph (container, data, options) {
// load the selection system. (mandatory, required by Graph)
this._loadSelectionSystem();
// load the data manipulation system
this._loadManipulationSystem();
// apply options
this.setOptions(options);
// other vars
var graph = this;
this.freezeSimulation = false;// freeze the simulation
@ -145,6 +138,7 @@ function Graph (container, data, options) {
this.canvasTopLeft = {"x": 0,"y": 0}; // coordinates of the top left of the canvas. they will be set during _redraw.
this.canvasBottomRight = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
this.pointerPosition = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
this.areaCenter = {}; // object with x and y elements used for determining the center of the zoom action
this.scale = 1; // defining the global scale variable in the constructor
@ -427,6 +421,17 @@ Graph.prototype.setOptions = function (options) {
this.constants.keyboard.enabled = false;
}
if (options.dataManipulationToolbar) {
this.constants.dataManipulationToolbar.enabled = true;
for (var prop in options.dataManipulationToolbar) {
if (options.dataManipulationToolbar.hasOwnProperty(prop)) {
this.constants.dataManipulationToolbar[prop] = options.dataManipulationToolbar[prop];
}
}
}
else if (options.dataManipulationToolbar !== undefined) {
this.constants.dataManipulationToolbar.enabled = false;
}
// TODO: work out these options and document them
if (options.edges) {
@ -488,16 +493,18 @@ Graph.prototype.setOptions = function (options) {
}
}
this.setSize(this.width, this.height);
this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
this._setScale(1);
// load the navigation system.
this._loadNavigationControls();
// load the data manipulation system
this._loadManipulationSystem();
// bind keys. If disabled, this will not do anything;
this._createKeyBinds();
this.setSize(this.width, this.height);
this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
this._setScale(1);
this._redraw();
};
@ -628,6 +635,10 @@ Graph.prototype._createKeyBinds = function() {
this.mousetrap.bind("pagedown",this._zoomOut.bind(me),"keydown");
this.mousetrap.bind("pagedown",this._stopZoom.bind(me), "keyup");
}
if (this.constants.dataManipulationToolbar.enabled == true) {
this.mousetrap.bind("escape",this._createManipulatorBar.bind(me));
}
}
/**
@ -776,6 +787,7 @@ Graph.prototype._onDragEnd = function () {
*/
Graph.prototype._onTap = function (event) {
var pointer = this._getPointer(event.gesture.touches[0]);
this.pointerPosition = pointer;
this._handleTap(pointer);
};
@ -798,6 +810,7 @@ Graph.prototype._onDoubleTap = function (event) {
*/
Graph.prototype._onHold = function (event) {
var pointer = this._getPointer(event.gesture.touches[0]);
this.pointerPosition = pointer;
this._handleOnHold(pointer);
};
@ -1126,7 +1139,9 @@ Graph.prototype.setSize = function(width, height) {
this.frame.canvas.width = this.frame.canvas.clientWidth;
this.frame.canvas.height = this.frame.canvas.clientHeight;
this.manipulationDiv.style.width = this.frame.canvas.clientWidth;
if (this.manipulationDiv !== undefined) {
this.manipulationDiv.style.width = this.frame.canvas.clientWidth;
}
if (this.constants.navigation.enabled == true) {
this._relocateNavigation();
@ -1731,6 +1746,7 @@ Graph.prototype._calculateForces = function() {
// 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
var a_base = (-2/3); var b = 4/3;
for (i = 0; i < this.nodeIndices.length-1; i++) {
node1 = nodes[this.nodeIndices[i]];
for (j = i+1; j < this.nodeIndices.length; j++) {
@ -1743,6 +1759,7 @@ Graph.prototype._calculateForces = function() {
// clusters have a larger region of influence
minimumDistance = (clusterSize == 0) ? this.constants.nodes.distance : (this.constants.nodes.distance * (1 + clusterSize * this.constants.clustering.distanceAmplification));
var a = a_base / minimumDistance;
if (distance < 2*minimumDistance) { // at 2.0 * the minimum distance, the force is 0.000045
angle = Math.atan2(dy, dx);
@ -1753,13 +1770,13 @@ Graph.prototype._calculateForces = function() {
// TODO: correct factor for repulsing force
//repulsingForce = 2 * Math.exp(-5 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force
//repulsingForce = Math.exp(-1 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force
repulsingForce = 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness)); // TODO: customize the repulsing force
//repulsingForce = 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness)); // TODO: customize the repulsing force
repulsingForce = a * distance + b; // TODO: test the approximation of the function above
}
// amplify the repulsion for clusters.
repulsingForce *= (clusterSize == 0) ? 1 : 1 + clusterSize * this.constants.clustering.forceAmplification;
repulsingForce *= this.forceFactor;
fx = Math.cos(angle) * repulsingForce;
fy = Math.sin(angle) * repulsingForce ;
@ -2070,21 +2087,28 @@ Graph.prototype._loadSelectionSystem = function() {
* @private
*/
Graph.prototype._loadManipulationSystem = function() {
// load the manipulator HTML elements. All styling done in css.
this.manipulationDiv = document.createElement('div');
this.manipulationDiv.className = 'graph-manipulationDiv';
this.containerElement.insertBefore(this.manipulationDiv, this.frame);
// reset global variables -- these are used by the selection of nodes and edges.
this.blockConnectingEdgeSelection = false;
this.forceAppendSelection = false
// load the manipulation functions
for (var mixinFunction in manipulationMixin) {
if (manipulationMixin.hasOwnProperty(mixinFunction)) {
Graph.prototype[mixinFunction] = manipulationMixin[mixinFunction];
if (this.constants.dataManipulationToolbar.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.containerElement.insertBefore(this.manipulationDiv, this.frame);
}
// load the manipulation functions
for (var mixinFunction in manipulationMixin) {
if (manipulationMixin.hasOwnProperty(mixinFunction)) {
Graph.prototype[mixinFunction] = manipulationMixin[mixinFunction];
}
}
}
this._createManipulatorBar();
// create the manipulator toolbar
this._createManipulatorBar();
}
}
/**

+ 94
- 15
src/graph/SelectionMixin.js View File

@ -186,7 +186,7 @@ var SelectionMixin = {
/**
* Remove a single option from selection.
*
* @param obj
* @param {Object} obj
* @private
*/
_removeFromSelection : function(obj) {
@ -219,6 +219,89 @@ var SelectionMixin = {
}
},
/**
* Unselect all clusters. The selectionObj is useful for this.
*
* @param {Boolean} [doNotTrigger] | ignore trigger
* @private
*/
_unselectClusters : function(doNotTrigger) {
if (doNotTrigger === undefined) {
doNotTrigger = false;
}
for (var objectId in this.selectionObj) {
if (this.selectionObj.hasOwnProperty(objectId)) {
if (this.selectionObj[objectId] instanceof Node) {
if (this.selectionObj[objectId].clusterSize > 1) {
this.selectionObj[objectId].unselect();
this._removeFromSelection(this.selectionObj[objectId]);
}
}
}
}
if (doNotTrigger == false) {
this._trigger('select', {
nodes: this.getSelection()
});
}
},
/**
* return the number of selected nodes
*
* @returns {number}
* @private
*/
_getSelectedNodeCount : function() {
var count = 0;
for (var objectId in this.selectionObj) {
if (this.selectionObj.hasOwnProperty(objectId)) {
if (this.selectionObj[objectId] instanceof Node) {
count += 1;
}
}
}
return count;
},
/**
* return the number of selected edges
*
* @returns {number}
* @private
*/
_getSelectedEdgeCount : function() {
var count = 0;
for (var objectId in this.selectionObj) {
if (this.selectionObj.hasOwnProperty(objectId)) {
if (this.selectionObj[objectId] instanceof Edge) {
count += 1;
}
}
}
return count;
},
/**
* return the number of selected objects.
*
* @returns {number}
* @private
*/
_getSelectedObjectCount : function() {
var count = 0;
for (var objectId in this.selectionObj) {
if (this.selectionObj.hasOwnProperty(objectId)) {
count += 1;
}
}
return count;
},
/**
* Check if anything is selected
@ -235,6 +318,13 @@ var SelectionMixin = {
return true;
},
/**
* check if one of the selected nodes is a cluster.
*
* @returns {boolean}
* @private
*/
_clusterInSelection : function() {
for(var objectId in this.selectionObj) {
if(this.selectionObj.hasOwnProperty(objectId)) {
@ -293,14 +383,14 @@ var SelectionMixin = {
doNotTrigger = false;
}
if (this._selectionIsEmpty() == false && append == false) {
if (this._selectionIsEmpty() == false && append == false && this.forceAppendSelection == false) {
this._unselectAll(true);
}
if (object.selected == false) {
object.select();
this._addToSelection(object);
if (object instanceof Node) {
if (object instanceof Node && this.blockConnectingEdgeSelection == false) {
this._selectConnectedEdges(object);
}
}
@ -326,6 +416,7 @@ var SelectionMixin = {
*/
_handleTouch : function(pointer) {
if (this.constants.navigation.enabled == true) {
this.pointerPosition = pointer;
var node = this._getNavigationNodeAt(pointer);
if (node != null) {
if (this[node.triggerFunction] !== undefined) {
@ -484,7 +575,6 @@ var SelectionMixin = {
}
this._selectObject(node,true,true);
}
this.redraw();
},
@ -505,20 +595,9 @@ var SelectionMixin = {
if (!this.edges.hasOwnProperty(objectId)) {
delete this.selectionObj[objectId];
}
changed = true;
}
this.selection = [];
}
}
if (changed && (triggerSelect == true || triggerSelect == undefined)) {
// fire the select event
this._trigger('select', {
nodes: this.getSelection()
});
}
return changed;
}
}

BIN
src/graph/img/acceptDeleteIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
src/graph/img/addNodeIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
src/graph/img/backIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
src/graph/img/connectIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
src/graph/img/deleteIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

BIN
src/graph/img/editIcon.png View File

Before After
Width: 24  |  Height: 24  |  Size: 20 KiB

+ 366
- 31
src/graph/manipulationMixin.js View File

@ -4,7 +4,31 @@
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);
}
@ -12,6 +36,8 @@ var manipulationMixin = {
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>";
@ -19,17 +45,26 @@ var manipulationMixin = {
// 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._deleteSelected.bind(this);
deleteButton.onclick = this._createDeletionToolbar.bind(this);
},
/**
* Create the toolbar for adding Nodes
*
* @private
*/
_createAddToolbar : function() {
while (this.manipulationDiv.hasChildNodes()) {
this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
}
// 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>" +
@ -39,69 +74,369 @@ var manipulationMixin = {
var backButton = document.getElementById("manipulate-back");
backButton.onclick = this._createManipulatorBar.bind(this);
var me = this;
events.addListener(me, 'select', me._addNode.bind(me));
// 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);
},
_createConnectToolbar : function() {
while (this.manipulationDiv.hasChildNodes()) {
this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
/**
* 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.";
}
var message = "hello";
if (!this._selectionIsEmpty()) {
message = "Select the node you want to connect to other nodes";
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'>"+message+"</span></span>";
"<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);
var self = this;
events.addListener(self, 'select', function(params) {alert(self.selectForConnect)});
// 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);
}
},
_continueConnect : function() {
/**
* 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._unselectAll();
this._createConnectToolbar("Select the node you want to connect (Clusters are not allowed).");
return true;
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()) {
this._connectNodes();
return true;
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 {
var manipulatorLabel = document.getElementById['manipolatorLabel'];
manipulatorLabel
return false;
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(pointer) {
console.log("HERE",this)
_addNode : function() {
if (this._selectionIsEmpty()) {
var positionObject = this._pointerToPositionObject(pointer);
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() {
console.log(this.selectionObj)
var targetNode = this._getTargetNode();
var sourceNode = this.manipulationSourceNode;
this.edgesData.add({from:sourceNode.id, to:targetNode.id})
this.moving = true;
this.start();
},
@ -116,7 +451,8 @@ var manipulationMixin = {
var selectedEdges = this.getSelectedEdges();
this._removeEdges(selectedEdges);
this._removeNodes(selectedNodes);
this._redraw();
this.moving = true;
this.start();
}
else {
alert("Clusters cannot be deleted.")
@ -124,5 +460,4 @@ var manipulationMixin = {
}
}

+ 154
- 0
src/util.js View File

@ -671,3 +671,157 @@ util.option.asElement = function (value, defaultValue) {
return value || defaultValue || null;
};
util.GiveDec = function GiveDec(Hex)
{
if(Hex == "A")
Value = 10;
else
if(Hex == "B")
Value = 11;
else
if(Hex == "C")
Value = 12;
else
if(Hex == "D")
Value = 13;
else
if(Hex == "E")
Value = 14;
else
if(Hex == "F")
Value = 15;
else
Value = eval(Hex)
return Value;
}
util.GiveHex = function GiveHex(Dec)
{
if(Dec == 10)
Value = "A";
else
if(Dec == 11)
Value = "B";
else
if(Dec == 12)
Value = "C";
else
if(Dec == 13)
Value = "D";
else
if(Dec == 14)
Value = "E";
else
if(Dec == 15)
Value = "F";
else
Value = "" + Dec;
return Value;
}
/**
* http://www.yellowpipe.com/yis/tools/hex-to-rgb/color-converter.php
*
* @param {String} hex
* @returns {{r: *, g: *, b: *}}
*/
util.hexToRGB = function hexToRGB(hex) {
hex = hex.replace("#","").toUpperCase();
var a = util.GiveDec(hex.substring(0, 1));
var b = util.GiveDec(hex.substring(1, 2));
var c = util.GiveDec(hex.substring(2, 3));
var d = util.GiveDec(hex.substring(3, 4));
var e = util.GiveDec(hex.substring(4, 5));
var f = util.GiveDec(hex.substring(5, 6));
var r = (a * 16) + b;
var g = (c * 16) + d;
var b = (e * 16) + f;
return {r:r,g:g,b:b};
};
util.RGBToHex = function RGBToHex(red,green,blue) {
var a = util.GiveHex(Math.floor(red / 16));
var b = util.GiveHex(red % 16);
var c = util.GiveHex(Math.floor(green / 16));
var d = util.GiveHex(green % 16);
var e = util.GiveHex(Math.floor(blue / 16));
var f = util.GiveHex(blue % 16);
var hex = a + b + c + d + e + f;
return "#" + hex;
};
/**
* http://www.javascripter.net/faq/rgb2hsv.htm
*
* @param red
* @param green
* @param blue
* @returns {*}
* @constructor
*/
util.RGBToHSV = function RGBToHSV (red,green,blue) {
red=red/255; green=green/255; blue=blue/255;
var minRGB = Math.min(red,Math.min(green,blue));
var maxRGB = Math.max(red,Math.max(green,blue));
// Black-gray-white
if (minRGB == maxRGB) {
return {h:0,s:0,v:minRGB};
}
// Colors other than black-gray-white:
var d = (red==minRGB) ? green-blue : ((blue==minRGB) ? red-green : blue-red);
var h = (red==minRGB) ? 3 : ((blue==minRGB) ? 1 : 5);
var hue = 60*(h - d/(maxRGB - minRGB))/360;
var saturation = (maxRGB - minRGB)/maxRGB;
var value = maxRGB;
return {h:hue,s:saturation,v:value};
};
/**
* https://gist.github.com/mjijackson/5311256
* @param hue
* @param saturation
* @param value
* @returns {{r: number, g: number, b: number}}
* @constructor
*/
util.HSVToRGB = function HSVToRGB(h, s, v) {
var r, g, b;
var i = Math.floor(h * 6);
var f = h * 6 - i;
var p = v * (1 - s);
var q = v * (1 - f * s);
var t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break;
case 3: r = p, g = q, b = v; break;
case 4: r = t, g = p, b = v; break;
case 5: r = v, g = p, b = q; break;
}
return {r:Math.floor(r * 255), g:Math.floor(g * 255), b:Math.floor(b * 255) };
};
util.HSVToHex = function HSVToHex(h,s,v) {
var rgb = util.HSVToRGB(h,s,v);
return util.RGBToHex(rgb.r,rgb.g,rgb.b);
}
util.hexToHSV = function hexToHSV(hex) {
var rgb = util.hexToRGB(hex);
return util.RGBToHSV(rgb.r,rgb.g,rgb.b);
}

Loading…
Cancel
Save