vis-rails 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -13
- data/lib/vis/rails/version.rb +1 -1
- data/vendor/assets/component/emitter.js +162 -0
- data/vendor/assets/javascripts/vis.js +1 -0
- data/vendor/assets/vis/DataSet.js +8 -2
- data/vendor/assets/vis/DataView.js +8 -4
- data/vendor/assets/vis/graph/Edge.js +210 -78
- data/vendor/assets/vis/graph/Graph.js +474 -652
- data/vendor/assets/vis/graph/Node.js +119 -82
- data/vendor/assets/vis/graph/css/graph-manipulation.css +128 -0
- data/vendor/assets/vis/graph/css/graph-navigation.css +62 -0
- data/vendor/assets/vis/graph/graphMixins/ClusterMixin.js +1141 -0
- data/vendor/assets/vis/graph/graphMixins/HierarchicalLayoutMixin.js +296 -0
- data/vendor/assets/vis/graph/graphMixins/ManipulationMixin.js +433 -0
- data/vendor/assets/vis/graph/graphMixins/MixinLoader.js +201 -0
- data/vendor/assets/vis/graph/graphMixins/NavigationMixin.js +173 -0
- data/vendor/assets/vis/graph/graphMixins/SectorsMixin.js +552 -0
- data/vendor/assets/vis/graph/graphMixins/SelectionMixin.js +558 -0
- data/vendor/assets/vis/graph/graphMixins/physics/BarnesHut.js +373 -0
- data/vendor/assets/vis/graph/graphMixins/physics/HierarchialRepulsion.js +64 -0
- data/vendor/assets/vis/graph/graphMixins/physics/PhysicsMixin.js +513 -0
- data/vendor/assets/vis/graph/graphMixins/physics/Repulsion.js +66 -0
- data/vendor/assets/vis/graph/img/acceptDeleteIcon.png +0 -0
- data/vendor/assets/vis/graph/img/addNodeIcon.png +0 -0
- data/vendor/assets/vis/graph/img/backIcon.png +0 -0
- data/vendor/assets/vis/graph/img/connectIcon.png +0 -0
- data/vendor/assets/vis/graph/img/cross.png +0 -0
- data/vendor/assets/vis/graph/img/cross2.png +0 -0
- data/vendor/assets/vis/graph/img/deleteIcon.png +0 -0
- data/vendor/assets/vis/graph/img/downArrow.png +0 -0
- data/vendor/assets/vis/graph/img/editIcon.png +0 -0
- data/vendor/assets/vis/graph/img/leftArrow.png +0 -0
- data/vendor/assets/vis/graph/img/rightArrow.png +0 -0
- data/vendor/assets/vis/graph/img/upArrow.png +0 -0
- data/vendor/assets/vis/module/exports.js +0 -2
- data/vendor/assets/vis/module/header.js +2 -2
- data/vendor/assets/vis/module/imports.js +1 -2
- data/vendor/assets/vis/timeline/Controller.js +56 -45
- data/vendor/assets/vis/timeline/Range.js +68 -62
- data/vendor/assets/vis/timeline/Stack.js +11 -13
- data/vendor/assets/vis/timeline/TimeStep.js +43 -38
- data/vendor/assets/vis/timeline/Timeline.js +215 -93
- data/vendor/assets/vis/timeline/component/Component.js +19 -3
- data/vendor/assets/vis/timeline/component/CurrentTime.js +1 -1
- data/vendor/assets/vis/timeline/component/CustomTime.js +39 -120
- data/vendor/assets/vis/timeline/component/GroupSet.js +35 -1
- data/vendor/assets/vis/timeline/component/ItemSet.js +272 -9
- data/vendor/assets/vis/timeline/component/RootPanel.js +59 -47
- data/vendor/assets/vis/timeline/component/TimeAxis.js +10 -0
- data/vendor/assets/vis/timeline/component/css/item.css +53 -22
- data/vendor/assets/vis/timeline/component/item/Item.js +40 -5
- data/vendor/assets/vis/timeline/component/item/ItemBox.js +3 -1
- data/vendor/assets/vis/timeline/component/item/ItemPoint.js +3 -1
- data/vendor/assets/vis/timeline/component/item/ItemRange.js +67 -3
- data/vendor/assets/vis/timeline/component/item/ItemRangeOverflow.js +37 -9
- data/vendor/assets/vis/timeline/img/delete.png +0 -0
- data/vendor/assets/vis/util.js +169 -30
- metadata +39 -12
@@ -0,0 +1,296 @@
|
|
1
|
+
var HierarchicalLayoutMixin = {
|
2
|
+
|
3
|
+
|
4
|
+
/**
|
5
|
+
* This is the main function to layout the nodes in a hierarchical way.
|
6
|
+
* It checks if the node details are supplied correctly
|
7
|
+
*
|
8
|
+
* @private
|
9
|
+
*/
|
10
|
+
_setupHierarchicalLayout : function() {
|
11
|
+
if (this.constants.hierarchicalLayout.enabled == true) {
|
12
|
+
if (this.constants.hierarchicalLayout.direction == "RL" || this.constants.hierarchicalLayout.direction == "DU") {
|
13
|
+
this.constants.hierarchicalLayout.levelSeparation *= -1;
|
14
|
+
}
|
15
|
+
// get the size of the largest hubs and check if the user has defined a level for a node.
|
16
|
+
var hubsize = 0;
|
17
|
+
var node, nodeId;
|
18
|
+
var definedLevel = false;
|
19
|
+
var undefinedLevel = false;
|
20
|
+
|
21
|
+
for (nodeId in this.nodes) {
|
22
|
+
if (this.nodes.hasOwnProperty(nodeId)) {
|
23
|
+
node = this.nodes[nodeId];
|
24
|
+
if (node.level != -1) {
|
25
|
+
definedLevel = true;
|
26
|
+
}
|
27
|
+
else {
|
28
|
+
undefinedLevel = true;
|
29
|
+
}
|
30
|
+
if (hubsize < node.edges.length) {
|
31
|
+
hubsize = node.edges.length;
|
32
|
+
}
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
// if the user defined some levels but not all, alert and run without hierarchical layout
|
37
|
+
if (undefinedLevel == true && definedLevel == true) {
|
38
|
+
alert("To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.")
|
39
|
+
this.zoomExtent(true,this.constants.clustering.enabled);
|
40
|
+
if (!this.constants.clustering.enabled) {
|
41
|
+
this.start();
|
42
|
+
}
|
43
|
+
}
|
44
|
+
else {
|
45
|
+
// setup the system to use hierarchical method.
|
46
|
+
this._changeConstants();
|
47
|
+
|
48
|
+
// define levels if undefined by the users. Based on hubsize
|
49
|
+
if (undefinedLevel == true) {
|
50
|
+
this._determineLevels(hubsize);
|
51
|
+
}
|
52
|
+
// check the distribution of the nodes per level.
|
53
|
+
var distribution = this._getDistribution();
|
54
|
+
|
55
|
+
// place the nodes on the canvas. This also stablilizes the system.
|
56
|
+
this._placeNodesByHierarchy(distribution);
|
57
|
+
|
58
|
+
// start the simulation.
|
59
|
+
this.start();
|
60
|
+
}
|
61
|
+
}
|
62
|
+
},
|
63
|
+
|
64
|
+
|
65
|
+
/**
|
66
|
+
* This function places the nodes on the canvas based on the hierarchial distribution.
|
67
|
+
*
|
68
|
+
* @param {Object} distribution | obtained by the function this._getDistribution()
|
69
|
+
* @private
|
70
|
+
*/
|
71
|
+
_placeNodesByHierarchy : function(distribution) {
|
72
|
+
var nodeId, node;
|
73
|
+
|
74
|
+
// start placing all the level 0 nodes first. Then recursively position their branches.
|
75
|
+
for (nodeId in distribution[0].nodes) {
|
76
|
+
if (distribution[0].nodes.hasOwnProperty(nodeId)) {
|
77
|
+
node = distribution[0].nodes[nodeId];
|
78
|
+
if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
|
79
|
+
if (node.xFixed) {
|
80
|
+
node.x = distribution[0].minPos;
|
81
|
+
node.xFixed = false;
|
82
|
+
|
83
|
+
distribution[0].minPos += distribution[0].nodeSpacing;
|
84
|
+
}
|
85
|
+
}
|
86
|
+
else {
|
87
|
+
if (node.yFixed) {
|
88
|
+
node.y = distribution[0].minPos;
|
89
|
+
node.yFixed = false;
|
90
|
+
|
91
|
+
distribution[0].minPos += distribution[0].nodeSpacing;
|
92
|
+
}
|
93
|
+
}
|
94
|
+
this._placeBranchNodes(node.edges,node.id,distribution,node.level);
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
// stabilize the system after positioning. This function calls zoomExtent.
|
99
|
+
this._doStabilize();
|
100
|
+
},
|
101
|
+
|
102
|
+
|
103
|
+
/**
|
104
|
+
* This function get the distribution of levels based on hubsize
|
105
|
+
*
|
106
|
+
* @returns {Object}
|
107
|
+
* @private
|
108
|
+
*/
|
109
|
+
_getDistribution : function() {
|
110
|
+
var distribution = {};
|
111
|
+
var nodeId, node;
|
112
|
+
|
113
|
+
// we fix Y because the hierarchy is vertical, we fix X so we do not give a node an x position for a second time.
|
114
|
+
// the fix of X is removed after the x value has been set.
|
115
|
+
for (nodeId in this.nodes) {
|
116
|
+
if (this.nodes.hasOwnProperty(nodeId)) {
|
117
|
+
node = this.nodes[nodeId];
|
118
|
+
node.xFixed = true;
|
119
|
+
node.yFixed = true;
|
120
|
+
if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
|
121
|
+
node.y = this.constants.hierarchicalLayout.levelSeparation*node.level;
|
122
|
+
}
|
123
|
+
else {
|
124
|
+
node.x = this.constants.hierarchicalLayout.levelSeparation*node.level;
|
125
|
+
}
|
126
|
+
if (!distribution.hasOwnProperty(node.level)) {
|
127
|
+
distribution[node.level] = {amount: 0, nodes: {}, minPos:0, nodeSpacing:0};
|
128
|
+
}
|
129
|
+
distribution[node.level].amount += 1;
|
130
|
+
distribution[node.level].nodes[node.id] = node;
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
// determine the largest amount of nodes of all levels
|
135
|
+
var maxCount = 0;
|
136
|
+
for (var level in distribution) {
|
137
|
+
if (distribution.hasOwnProperty(level)) {
|
138
|
+
if (maxCount < distribution[level].amount) {
|
139
|
+
maxCount = distribution[level].amount;
|
140
|
+
}
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
// set the initial position and spacing of each nodes accordingly
|
145
|
+
for (var level in distribution) {
|
146
|
+
if (distribution.hasOwnProperty(level)) {
|
147
|
+
distribution[level].nodeSpacing = (maxCount + 1) * this.constants.hierarchicalLayout.nodeSpacing;
|
148
|
+
distribution[level].nodeSpacing /= (distribution[level].amount + 1);
|
149
|
+
distribution[level].minPos = distribution[level].nodeSpacing - (0.5 * (distribution[level].amount + 1) * distribution[level].nodeSpacing);
|
150
|
+
}
|
151
|
+
}
|
152
|
+
|
153
|
+
return distribution;
|
154
|
+
},
|
155
|
+
|
156
|
+
|
157
|
+
/**
|
158
|
+
* this function allocates nodes in levels based on the recursive branching from the largest hubs.
|
159
|
+
*
|
160
|
+
* @param hubsize
|
161
|
+
* @private
|
162
|
+
*/
|
163
|
+
_determineLevels : function(hubsize) {
|
164
|
+
var nodeId, node;
|
165
|
+
|
166
|
+
// determine hubs
|
167
|
+
for (nodeId in this.nodes) {
|
168
|
+
if (this.nodes.hasOwnProperty(nodeId)) {
|
169
|
+
node = this.nodes[nodeId];
|
170
|
+
if (node.edges.length == hubsize) {
|
171
|
+
node.level = 0;
|
172
|
+
}
|
173
|
+
}
|
174
|
+
}
|
175
|
+
|
176
|
+
// branch from hubs
|
177
|
+
for (nodeId in this.nodes) {
|
178
|
+
if (this.nodes.hasOwnProperty(nodeId)) {
|
179
|
+
node = this.nodes[nodeId];
|
180
|
+
if (node.level == 0) {
|
181
|
+
this._setLevel(1,node.edges,node.id);
|
182
|
+
}
|
183
|
+
}
|
184
|
+
}
|
185
|
+
},
|
186
|
+
|
187
|
+
|
188
|
+
/**
|
189
|
+
* Since hierarchical layout does not support:
|
190
|
+
* - smooth curves (based on the physics),
|
191
|
+
* - clustering (based on dynamic node counts)
|
192
|
+
*
|
193
|
+
* We disable both features so there will be no problems.
|
194
|
+
*
|
195
|
+
* @private
|
196
|
+
*/
|
197
|
+
_changeConstants : function() {
|
198
|
+
this.constants.clustering.enabled = false;
|
199
|
+
this.constants.physics.barnesHut.enabled = false;
|
200
|
+
this.constants.physics.hierarchicalRepulsion.enabled = true;
|
201
|
+
this._loadSelectedForceSolver();
|
202
|
+
this.constants.smoothCurves = false;
|
203
|
+
this._configureSmoothCurves();
|
204
|
+
},
|
205
|
+
|
206
|
+
|
207
|
+
/**
|
208
|
+
* This is a recursively called function to enumerate the branches from the largest hubs and place the nodes
|
209
|
+
* on a X position that ensures there will be no overlap.
|
210
|
+
*
|
211
|
+
* @param edges
|
212
|
+
* @param parentId
|
213
|
+
* @param distribution
|
214
|
+
* @param parentLevel
|
215
|
+
* @private
|
216
|
+
*/
|
217
|
+
_placeBranchNodes : function(edges, parentId, distribution, parentLevel) {
|
218
|
+
for (var i = 0; i < edges.length; i++) {
|
219
|
+
var childNode = null;
|
220
|
+
if (edges[i].toId == parentId) {
|
221
|
+
childNode = edges[i].from;
|
222
|
+
}
|
223
|
+
else {
|
224
|
+
childNode = edges[i].to;
|
225
|
+
}
|
226
|
+
|
227
|
+
// if a node is conneceted to another node on the same level (or higher (means lower level))!, this is not handled here.
|
228
|
+
var nodeMoved = false;
|
229
|
+
if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
|
230
|
+
if (childNode.xFixed && childNode.level > parentLevel) {
|
231
|
+
childNode.xFixed = false;
|
232
|
+
childNode.x = distribution[childNode.level].minPos;
|
233
|
+
nodeMoved = true;
|
234
|
+
}
|
235
|
+
}
|
236
|
+
else {
|
237
|
+
if (childNode.yFixed && childNode.level > parentLevel) {
|
238
|
+
childNode.yFixed = false;
|
239
|
+
childNode.y = distribution[childNode.level].minPos;
|
240
|
+
nodeMoved = true;
|
241
|
+
}
|
242
|
+
}
|
243
|
+
|
244
|
+
if (nodeMoved == true) {
|
245
|
+
distribution[childNode.level].minPos += distribution[childNode.level].nodeSpacing;
|
246
|
+
if (childNode.edges.length > 1) {
|
247
|
+
this._placeBranchNodes(childNode.edges,childNode.id,distribution,childNode.level);
|
248
|
+
}
|
249
|
+
}
|
250
|
+
}
|
251
|
+
},
|
252
|
+
|
253
|
+
|
254
|
+
/**
|
255
|
+
* this function is called recursively to enumerate the barnches of the largest hubs and give each node a level.
|
256
|
+
*
|
257
|
+
* @param level
|
258
|
+
* @param edges
|
259
|
+
* @param parentId
|
260
|
+
* @private
|
261
|
+
*/
|
262
|
+
_setLevel : function(level, edges, parentId) {
|
263
|
+
for (var i = 0; i < edges.length; i++) {
|
264
|
+
var childNode = null;
|
265
|
+
if (edges[i].toId == parentId) {
|
266
|
+
childNode = edges[i].from;
|
267
|
+
}
|
268
|
+
else {
|
269
|
+
childNode = edges[i].to;
|
270
|
+
}
|
271
|
+
if (childNode.level == -1 || childNode.level > level) {
|
272
|
+
childNode.level = level;
|
273
|
+
if (edges.length > 1) {
|
274
|
+
this._setLevel(level+1, childNode.edges, childNode.id);
|
275
|
+
}
|
276
|
+
}
|
277
|
+
}
|
278
|
+
},
|
279
|
+
|
280
|
+
|
281
|
+
/**
|
282
|
+
* Unfix nodes
|
283
|
+
*
|
284
|
+
* @private
|
285
|
+
*/
|
286
|
+
_restoreNodes : function() {
|
287
|
+
for (nodeId in this.nodes) {
|
288
|
+
if (this.nodes.hasOwnProperty(nodeId)) {
|
289
|
+
this.nodes[nodeId].xFixed = false;
|
290
|
+
this.nodes[nodeId].yFixed = false;
|
291
|
+
}
|
292
|
+
}
|
293
|
+
}
|
294
|
+
|
295
|
+
|
296
|
+
};
|
@@ -0,0 +1,433 @@
|
|
1
|
+
/**
|
2
|
+
* Created by Alex on 2/4/14.
|
3
|
+
*/
|
4
|
+
|
5
|
+
var manipulationMixin = {
|
6
|
+
|
7
|
+
/**
|
8
|
+
* clears the toolbar div element of children
|
9
|
+
*
|
10
|
+
* @private
|
11
|
+
*/
|
12
|
+
_clearManipulatorBar : function() {
|
13
|
+
while (this.manipulationDiv.hasChildNodes()) {
|
14
|
+
this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
|
15
|
+
}
|
16
|
+
},
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Manipulation UI temporarily overloads certain functions to extend or replace them. To be able to restore
|
20
|
+
* these functions to their original functionality, we saved them in this.cachedFunctions.
|
21
|
+
* This function restores these functions to their original function.
|
22
|
+
*
|
23
|
+
* @private
|
24
|
+
*/
|
25
|
+
_restoreOverloadedFunctions : function() {
|
26
|
+
for (var functionName in this.cachedFunctions) {
|
27
|
+
if (this.cachedFunctions.hasOwnProperty(functionName)) {
|
28
|
+
this[functionName] = this.cachedFunctions[functionName];
|
29
|
+
}
|
30
|
+
}
|
31
|
+
},
|
32
|
+
|
33
|
+
/**
|
34
|
+
* Enable or disable edit-mode.
|
35
|
+
*
|
36
|
+
* @private
|
37
|
+
*/
|
38
|
+
_toggleEditMode : function() {
|
39
|
+
this.editMode = !this.editMode;
|
40
|
+
var toolbar = document.getElementById("graph-manipulationDiv");
|
41
|
+
var closeDiv = document.getElementById("graph-manipulation-closeDiv");
|
42
|
+
var editModeDiv = document.getElementById("graph-manipulation-editMode");
|
43
|
+
if (this.editMode == true) {
|
44
|
+
toolbar.style.display="block";
|
45
|
+
closeDiv.style.display="block";
|
46
|
+
editModeDiv.style.display="none";
|
47
|
+
closeDiv.onclick = this._toggleEditMode.bind(this);
|
48
|
+
}
|
49
|
+
else {
|
50
|
+
toolbar.style.display="none";
|
51
|
+
closeDiv.style.display="none";
|
52
|
+
editModeDiv.style.display="block";
|
53
|
+
closeDiv.onclick = null;
|
54
|
+
}
|
55
|
+
this._createManipulatorBar()
|
56
|
+
},
|
57
|
+
|
58
|
+
/**
|
59
|
+
* main function, creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar.
|
60
|
+
*
|
61
|
+
* @private
|
62
|
+
*/
|
63
|
+
_createManipulatorBar : function() {
|
64
|
+
// remove bound functions
|
65
|
+
this.off('select', this.boundFunction);
|
66
|
+
|
67
|
+
// restore overloaded functions
|
68
|
+
this._restoreOverloadedFunctions();
|
69
|
+
|
70
|
+
// resume calculation
|
71
|
+
this.freezeSimulation = false;
|
72
|
+
|
73
|
+
// reset global variables
|
74
|
+
this.blockConnectingEdgeSelection = false;
|
75
|
+
this.forceAppendSelection = false
|
76
|
+
|
77
|
+
if (this.editMode == true) {
|
78
|
+
while (this.manipulationDiv.hasChildNodes()) {
|
79
|
+
this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
|
80
|
+
}
|
81
|
+
// add the icons to the manipulator div
|
82
|
+
this.manipulationDiv.innerHTML = "" +
|
83
|
+
"<span class='graph-manipulationUI add' id='graph-manipulate-addNode'>" +
|
84
|
+
"<span class='graph-manipulationLabel'>Add Node</span></span>" +
|
85
|
+
"<div class='graph-seperatorLine'></div>" +
|
86
|
+
"<span class='graph-manipulationUI connect' id='graph-manipulate-connectNode'>" +
|
87
|
+
"<span class='graph-manipulationLabel'>Add Link</span></span>";
|
88
|
+
if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
|
89
|
+
this.manipulationDiv.innerHTML += "" +
|
90
|
+
"<div class='graph-seperatorLine'></div>" +
|
91
|
+
"<span class='graph-manipulationUI edit' id='graph-manipulate-editNode'>" +
|
92
|
+
"<span class='graph-manipulationLabel'>Edit Node</span></span>";
|
93
|
+
}
|
94
|
+
if (this._selectionIsEmpty() == false) {
|
95
|
+
this.manipulationDiv.innerHTML += "" +
|
96
|
+
"<div class='graph-seperatorLine'></div>" +
|
97
|
+
"<span class='graph-manipulationUI delete' id='graph-manipulate-delete'>" +
|
98
|
+
"<span class='graph-manipulationLabel'>Delete selected</span></span>";
|
99
|
+
}
|
100
|
+
|
101
|
+
|
102
|
+
// bind the icons
|
103
|
+
var addNodeButton = document.getElementById("graph-manipulate-addNode");
|
104
|
+
addNodeButton.onclick = this._createAddNodeToolbar.bind(this);
|
105
|
+
var addEdgeButton = document.getElementById("graph-manipulate-connectNode");
|
106
|
+
addEdgeButton.onclick = this._createAddEdgeToolbar.bind(this);
|
107
|
+
if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
|
108
|
+
var editButton = document.getElementById("graph-manipulate-editNode");
|
109
|
+
editButton.onclick = this._editNode.bind(this);
|
110
|
+
}
|
111
|
+
if (this._selectionIsEmpty() == false) {
|
112
|
+
var deleteButton = document.getElementById("graph-manipulate-delete");
|
113
|
+
deleteButton.onclick = this._deleteSelected.bind(this);
|
114
|
+
}
|
115
|
+
var closeDiv = document.getElementById("graph-manipulation-closeDiv");
|
116
|
+
closeDiv.onclick = this._toggleEditMode.bind(this);
|
117
|
+
|
118
|
+
this.boundFunction = this._createManipulatorBar.bind(this);
|
119
|
+
this.on('select', this.boundFunction);
|
120
|
+
}
|
121
|
+
else {
|
122
|
+
this.editModeDiv.innerHTML = "" +
|
123
|
+
"<span class='graph-manipulationUI edit editmode' id='graph-manipulate-editModeButton'>" +
|
124
|
+
"<span class='graph-manipulationLabel'>Edit</span></span>"
|
125
|
+
var editModeButton = document.getElementById("graph-manipulate-editModeButton");
|
126
|
+
editModeButton.onclick = this._toggleEditMode.bind(this);
|
127
|
+
}
|
128
|
+
},
|
129
|
+
|
130
|
+
|
131
|
+
|
132
|
+
/**
|
133
|
+
* Create the toolbar for adding Nodes
|
134
|
+
*
|
135
|
+
* @private
|
136
|
+
*/
|
137
|
+
_createAddNodeToolbar : function() {
|
138
|
+
// clear the toolbar
|
139
|
+
this._clearManipulatorBar();
|
140
|
+
this.off('select', this.boundFunction);
|
141
|
+
|
142
|
+
// create the toolbar contents
|
143
|
+
this.manipulationDiv.innerHTML = "" +
|
144
|
+
"<span class='graph-manipulationUI back' id='graph-manipulate-back'>" +
|
145
|
+
"<span class='graph-manipulationLabel'>Back</span></span>" +
|
146
|
+
"<div class='graph-seperatorLine'></div>" +
|
147
|
+
"<span class='graph-manipulationUI none' id='graph-manipulate-back'>" +
|
148
|
+
"<span class='graph-manipulationLabel'>Click in an empty space to place a new node</span></span>";
|
149
|
+
|
150
|
+
// bind the icon
|
151
|
+
var backButton = document.getElementById("graph-manipulate-back");
|
152
|
+
backButton.onclick = this._createManipulatorBar.bind(this);
|
153
|
+
|
154
|
+
// we use the boundFunction so we can reference it when we unbind it from the "select" event.
|
155
|
+
this.boundFunction = this._addNode.bind(this);
|
156
|
+
this.on('select', this.boundFunction);
|
157
|
+
},
|
158
|
+
|
159
|
+
|
160
|
+
/**
|
161
|
+
* create the toolbar to connect nodes
|
162
|
+
*
|
163
|
+
* @private
|
164
|
+
*/
|
165
|
+
_createAddEdgeToolbar : function() {
|
166
|
+
// clear the toolbar
|
167
|
+
this._clearManipulatorBar();
|
168
|
+
this._unselectAll(true);
|
169
|
+
this.freezeSimulation = true;
|
170
|
+
|
171
|
+
this.off('select', this.boundFunction);
|
172
|
+
|
173
|
+
this._unselectAll();
|
174
|
+
this.forceAppendSelection = false;
|
175
|
+
this.blockConnectingEdgeSelection = true;
|
176
|
+
|
177
|
+
this.manipulationDiv.innerHTML = "" +
|
178
|
+
"<span class='graph-manipulationUI back' id='graph-manipulate-back'>" +
|
179
|
+
"<span class='graph-manipulationLabel'>Back</span></span>" +
|
180
|
+
"<div class='graph-seperatorLine'></div>" +
|
181
|
+
"<span class='graph-manipulationUI none' id='graph-manipulate-back'>" +
|
182
|
+
"<span id='graph-manipulatorLabel' class='graph-manipulationLabel'>Click on a node and drag the edge to another node to connect them.</span></span>";
|
183
|
+
|
184
|
+
// bind the icon
|
185
|
+
var backButton = document.getElementById("graph-manipulate-back");
|
186
|
+
backButton.onclick = this._createManipulatorBar.bind(this);
|
187
|
+
|
188
|
+
// we use the boundFunction so we can reference it when we unbind it from the "select" event.
|
189
|
+
this.boundFunction = this._handleConnect.bind(this);
|
190
|
+
this.on('select', this.boundFunction);
|
191
|
+
|
192
|
+
// temporarily overload functions
|
193
|
+
this.cachedFunctions["_handleTouch"] = this._handleTouch;
|
194
|
+
this.cachedFunctions["_handleOnRelease"] = this._handleOnRelease;
|
195
|
+
this._handleTouch = this._handleConnect;
|
196
|
+
this._handleOnRelease = this._finishConnect;
|
197
|
+
|
198
|
+
// redraw to show the unselect
|
199
|
+
this._redraw();
|
200
|
+
|
201
|
+
},
|
202
|
+
|
203
|
+
|
204
|
+
/**
|
205
|
+
* the function bound to the selection event. It checks if you want to connect a cluster and changes the description
|
206
|
+
* to walk the user through the process.
|
207
|
+
*
|
208
|
+
* @private
|
209
|
+
*/
|
210
|
+
_handleConnect : function(pointer) {
|
211
|
+
if (this._getSelectedNodeCount() == 0) {
|
212
|
+
var node = this._getNodeAt(pointer);
|
213
|
+
if (node != null) {
|
214
|
+
if (node.clusterSize > 1) {
|
215
|
+
alert("Cannot create edges to a cluster.")
|
216
|
+
}
|
217
|
+
else {
|
218
|
+
this._selectObject(node,false);
|
219
|
+
// create a node the temporary line can look at
|
220
|
+
this.sectors['support']['nodes']['targetNode'] = new Node({id:'targetNode'},{},{},this.constants);
|
221
|
+
this.sectors['support']['nodes']['targetNode'].x = node.x;
|
222
|
+
this.sectors['support']['nodes']['targetNode'].y = node.y;
|
223
|
+
this.sectors['support']['nodes']['targetViaNode'] = new Node({id:'targetViaNode'},{},{},this.constants);
|
224
|
+
this.sectors['support']['nodes']['targetViaNode'].x = node.x;
|
225
|
+
this.sectors['support']['nodes']['targetViaNode'].y = node.y;
|
226
|
+
this.sectors['support']['nodes']['targetViaNode'].parentEdgeId = "connectionEdge";
|
227
|
+
|
228
|
+
// create a temporary edge
|
229
|
+
this.edges['connectionEdge'] = new Edge({id:"connectionEdge",from:node.id,to:this.sectors['support']['nodes']['targetNode'].id}, this, this.constants);
|
230
|
+
this.edges['connectionEdge'].from = node;
|
231
|
+
this.edges['connectionEdge'].connected = true;
|
232
|
+
this.edges['connectionEdge'].smooth = true;
|
233
|
+
this.edges['connectionEdge'].selected = true;
|
234
|
+
this.edges['connectionEdge'].to = this.sectors['support']['nodes']['targetNode'];
|
235
|
+
this.edges['connectionEdge'].via = this.sectors['support']['nodes']['targetViaNode'];
|
236
|
+
|
237
|
+
this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
|
238
|
+
this._handleOnDrag = function(event) {
|
239
|
+
var pointer = this._getPointer(event.gesture.center);
|
240
|
+
this.sectors['support']['nodes']['targetNode'].x = this._canvasToX(pointer.x);
|
241
|
+
this.sectors['support']['nodes']['targetNode'].y = this._canvasToY(pointer.y);
|
242
|
+
this.sectors['support']['nodes']['targetViaNode'].x = 0.5 * (this._canvasToX(pointer.x) + this.edges['connectionEdge'].from.x);
|
243
|
+
this.sectors['support']['nodes']['targetViaNode'].y = this._canvasToY(pointer.y);
|
244
|
+
};
|
245
|
+
|
246
|
+
this.moving = true;
|
247
|
+
this.start();
|
248
|
+
}
|
249
|
+
}
|
250
|
+
}
|
251
|
+
},
|
252
|
+
|
253
|
+
_finishConnect : function(pointer) {
|
254
|
+
if (this._getSelectedNodeCount() == 1) {
|
255
|
+
|
256
|
+
// restore the drag function
|
257
|
+
this._handleOnDrag = this.cachedFunctions["_handleOnDrag"];
|
258
|
+
delete this.cachedFunctions["_handleOnDrag"];
|
259
|
+
|
260
|
+
// remember the edge id
|
261
|
+
var connectFromId = this.edges['connectionEdge'].fromId;
|
262
|
+
|
263
|
+
// remove the temporary nodes and edge
|
264
|
+
delete this.edges['connectionEdge']
|
265
|
+
delete this.sectors['support']['nodes']['targetNode'];
|
266
|
+
delete this.sectors['support']['nodes']['targetViaNode'];
|
267
|
+
|
268
|
+
var node = this._getNodeAt(pointer);
|
269
|
+
if (node != null) {
|
270
|
+
if (node.clusterSize > 1) {
|
271
|
+
alert("Cannot create edges to a cluster.")
|
272
|
+
}
|
273
|
+
else {
|
274
|
+
this._createEdge(connectFromId,node.id);
|
275
|
+
this._createManipulatorBar();
|
276
|
+
}
|
277
|
+
}
|
278
|
+
this._unselectAll();
|
279
|
+
}
|
280
|
+
},
|
281
|
+
|
282
|
+
|
283
|
+
/**
|
284
|
+
* Adds a node on the specified location
|
285
|
+
*
|
286
|
+
* @param {Object} pointer
|
287
|
+
*/
|
288
|
+
_addNode : function() {
|
289
|
+
if (this._selectionIsEmpty() && this.editMode == true) {
|
290
|
+
var positionObject = this._pointerToPositionObject(this.pointerPosition);
|
291
|
+
var defaultData = {id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",allowedToMove:true};
|
292
|
+
if (this.triggerFunctions.add) {
|
293
|
+
if (this.triggerFunctions.add.length == 2) {
|
294
|
+
var me = this;
|
295
|
+
this.triggerFunctions.add(defaultData, function(finalizedData) {
|
296
|
+
me.createNodeOnClick = true;
|
297
|
+
me.nodesData.add(finalizedData);
|
298
|
+
me.createNodeOnClick = false;
|
299
|
+
me._createManipulatorBar();
|
300
|
+
me.moving = true;
|
301
|
+
me.start();
|
302
|
+
});
|
303
|
+
}
|
304
|
+
else {
|
305
|
+
alert("The function for add does not support two arguments (data,callback).");
|
306
|
+
this._createManipulatorBar();
|
307
|
+
this.moving = true;
|
308
|
+
this.start();
|
309
|
+
}
|
310
|
+
}
|
311
|
+
else {
|
312
|
+
this.createNodeOnClick = true;
|
313
|
+
this.nodesData.add(defaultData);
|
314
|
+
this.createNodeOnClick = false;
|
315
|
+
this._createManipulatorBar();
|
316
|
+
this.moving = true;
|
317
|
+
this.start();
|
318
|
+
}
|
319
|
+
}
|
320
|
+
},
|
321
|
+
|
322
|
+
|
323
|
+
/**
|
324
|
+
* connect two nodes with a new edge.
|
325
|
+
*
|
326
|
+
* @private
|
327
|
+
*/
|
328
|
+
_createEdge : function(sourceNodeId,targetNodeId) {
|
329
|
+
if (this.editMode == true) {
|
330
|
+
var defaultData = {from:sourceNodeId, to:targetNodeId};
|
331
|
+
if (this.triggerFunctions.connect) {
|
332
|
+
if (this.triggerFunctions.connect.length == 2) {
|
333
|
+
var me = this;
|
334
|
+
this.triggerFunctions.connect(defaultData, function(finalizedData) {
|
335
|
+
me.edgesData.add(finalizedData)
|
336
|
+
me.moving = true;
|
337
|
+
me.start();
|
338
|
+
});
|
339
|
+
}
|
340
|
+
else {
|
341
|
+
alert("The function for connect does not support two arguments (data,callback).");
|
342
|
+
this.moving = true;
|
343
|
+
this.start();
|
344
|
+
}
|
345
|
+
}
|
346
|
+
else {
|
347
|
+
this.edgesData.add(defaultData)
|
348
|
+
this.moving = true;
|
349
|
+
this.start();
|
350
|
+
}
|
351
|
+
}
|
352
|
+
},
|
353
|
+
|
354
|
+
|
355
|
+
/**
|
356
|
+
* Create the toolbar to edit the selected node. The label and the color can be changed. Other colors are derived from the chosen color.
|
357
|
+
*
|
358
|
+
* @private
|
359
|
+
*/
|
360
|
+
_editNode : function() {
|
361
|
+
if (this.triggerFunctions.edit && this.editMode == true) {
|
362
|
+
var node = this._getSelectedNode();
|
363
|
+
var data = {id:node.id,
|
364
|
+
label: node.label,
|
365
|
+
group: node.group,
|
366
|
+
shape: node.shape,
|
367
|
+
color: {
|
368
|
+
background:node.color.background,
|
369
|
+
border:node.color.border,
|
370
|
+
highlight: {
|
371
|
+
background:node.color.highlight.background,
|
372
|
+
border:node.color.highlight.border
|
373
|
+
}
|
374
|
+
}};
|
375
|
+
if (this.triggerFunctions.edit.length == 2) {
|
376
|
+
var me = this;
|
377
|
+
this.triggerFunctions.edit(data, function (finalizedData) {
|
378
|
+
me.nodesData.update(finalizedData);
|
379
|
+
me._createManipulatorBar();
|
380
|
+
me.moving = true;
|
381
|
+
me.start();
|
382
|
+
});
|
383
|
+
}
|
384
|
+
else {
|
385
|
+
alert("The function for edit does not support two arguments (data, callback).")
|
386
|
+
}
|
387
|
+
}
|
388
|
+
else {
|
389
|
+
alert("No edit function has been bound to this button.")
|
390
|
+
}
|
391
|
+
},
|
392
|
+
|
393
|
+
|
394
|
+
/**
|
395
|
+
* delete everything in the selection
|
396
|
+
*
|
397
|
+
* @private
|
398
|
+
*/
|
399
|
+
_deleteSelected : function() {
|
400
|
+
if (!this._selectionIsEmpty() && this.editMode == true) {
|
401
|
+
if (!this._clusterInSelection()) {
|
402
|
+
var selectedNodes = this.getSelectedNodes();
|
403
|
+
var selectedEdges = this.getSelectedEdges();
|
404
|
+
if (this.triggerFunctions.delete) {
|
405
|
+
var me = this;
|
406
|
+
var data = {nodes: selectedNodes, edges: selectedEdges};
|
407
|
+
if (this.triggerFunctions.delete.length = 2) {
|
408
|
+
this.triggerFunctions.delete(data, function (finalizedData) {
|
409
|
+
me.edgesData.remove(finalizedData.edges);
|
410
|
+
me.nodesData.remove(finalizedData.nodes);
|
411
|
+
this._unselectAll();
|
412
|
+
me.moving = true;
|
413
|
+
me.start();
|
414
|
+
});
|
415
|
+
}
|
416
|
+
else {
|
417
|
+
alert("The function for edit does not support two arguments (data, callback).")
|
418
|
+
}
|
419
|
+
}
|
420
|
+
else {
|
421
|
+
this.edgesData.remove(selectedEdges);
|
422
|
+
this.nodesData.remove(selectedNodes);
|
423
|
+
this._unselectAll();
|
424
|
+
this.moving = true;
|
425
|
+
this.start();
|
426
|
+
}
|
427
|
+
}
|
428
|
+
else {
|
429
|
+
alert("Clusters cannot be deleted.");
|
430
|
+
}
|
431
|
+
}
|
432
|
+
}
|
433
|
+
};
|