vis-rails 2.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/lib/vis/rails/version.rb +1 -1
  3. data/vendor/assets/javascripts/vis.js +26 -26
  4. metadata +16 -85
  5. data/vendor/assets/vis/DataSet.js +0 -926
  6. data/vendor/assets/vis/DataView.js +0 -283
  7. data/vendor/assets/vis/graph/Edge.js +0 -957
  8. data/vendor/assets/vis/graph/Graph.js +0 -2291
  9. data/vendor/assets/vis/graph/Groups.js +0 -80
  10. data/vendor/assets/vis/graph/Images.js +0 -41
  11. data/vendor/assets/vis/graph/Node.js +0 -966
  12. data/vendor/assets/vis/graph/Popup.js +0 -132
  13. data/vendor/assets/vis/graph/css/graph-manipulation.css +0 -128
  14. data/vendor/assets/vis/graph/css/graph-navigation.css +0 -66
  15. data/vendor/assets/vis/graph/dotparser.js +0 -829
  16. data/vendor/assets/vis/graph/graphMixins/ClusterMixin.js +0 -1143
  17. data/vendor/assets/vis/graph/graphMixins/HierarchicalLayoutMixin.js +0 -311
  18. data/vendor/assets/vis/graph/graphMixins/ManipulationMixin.js +0 -576
  19. data/vendor/assets/vis/graph/graphMixins/MixinLoader.js +0 -199
  20. data/vendor/assets/vis/graph/graphMixins/NavigationMixin.js +0 -205
  21. data/vendor/assets/vis/graph/graphMixins/SectorsMixin.js +0 -552
  22. data/vendor/assets/vis/graph/graphMixins/SelectionMixin.js +0 -648
  23. data/vendor/assets/vis/graph/graphMixins/physics/BarnesHut.js +0 -398
  24. data/vendor/assets/vis/graph/graphMixins/physics/HierarchialRepulsion.js +0 -64
  25. data/vendor/assets/vis/graph/graphMixins/physics/PhysicsMixin.js +0 -697
  26. data/vendor/assets/vis/graph/graphMixins/physics/Repulsion.js +0 -66
  27. data/vendor/assets/vis/graph/img/acceptDeleteIcon.png +0 -0
  28. data/vendor/assets/vis/graph/img/addNodeIcon.png +0 -0
  29. data/vendor/assets/vis/graph/img/backIcon.png +0 -0
  30. data/vendor/assets/vis/graph/img/connectIcon.png +0 -0
  31. data/vendor/assets/vis/graph/img/cross.png +0 -0
  32. data/vendor/assets/vis/graph/img/cross2.png +0 -0
  33. data/vendor/assets/vis/graph/img/deleteIcon.png +0 -0
  34. data/vendor/assets/vis/graph/img/downArrow.png +0 -0
  35. data/vendor/assets/vis/graph/img/editIcon.png +0 -0
  36. data/vendor/assets/vis/graph/img/leftArrow.png +0 -0
  37. data/vendor/assets/vis/graph/img/minus.png +0 -0
  38. data/vendor/assets/vis/graph/img/plus.png +0 -0
  39. data/vendor/assets/vis/graph/img/rightArrow.png +0 -0
  40. data/vendor/assets/vis/graph/img/upArrow.png +0 -0
  41. data/vendor/assets/vis/graph/img/zoomExtends.png +0 -0
  42. data/vendor/assets/vis/graph/shapes.js +0 -225
  43. data/vendor/assets/vis/graph3d/Graph3d.js +0 -3306
  44. data/vendor/assets/vis/module/exports.js +0 -65
  45. data/vendor/assets/vis/module/header.js +0 -24
  46. data/vendor/assets/vis/module/imports.js +0 -31
  47. data/vendor/assets/vis/shim.js +0 -252
  48. data/vendor/assets/vis/timeline/Range.js +0 -532
  49. data/vendor/assets/vis/timeline/TimeStep.js +0 -466
  50. data/vendor/assets/vis/timeline/Timeline.js +0 -851
  51. data/vendor/assets/vis/timeline/component/Component.js +0 -52
  52. data/vendor/assets/vis/timeline/component/CurrentTime.js +0 -128
  53. data/vendor/assets/vis/timeline/component/CustomTime.js +0 -182
  54. data/vendor/assets/vis/timeline/component/Group.js +0 -470
  55. data/vendor/assets/vis/timeline/component/ItemSet.js +0 -1332
  56. data/vendor/assets/vis/timeline/component/TimeAxis.js +0 -389
  57. data/vendor/assets/vis/timeline/component/css/animation.css +0 -33
  58. data/vendor/assets/vis/timeline/component/css/currenttime.css +0 -5
  59. data/vendor/assets/vis/timeline/component/css/customtime.css +0 -6
  60. data/vendor/assets/vis/timeline/component/css/item.css +0 -107
  61. data/vendor/assets/vis/timeline/component/css/itemset.css +0 -33
  62. data/vendor/assets/vis/timeline/component/css/labelset.css +0 -36
  63. data/vendor/assets/vis/timeline/component/css/panel.css +0 -71
  64. data/vendor/assets/vis/timeline/component/css/timeaxis.css +0 -48
  65. data/vendor/assets/vis/timeline/component/css/timeline.css +0 -2
  66. data/vendor/assets/vis/timeline/component/item/Item.js +0 -139
  67. data/vendor/assets/vis/timeline/component/item/ItemBox.js +0 -230
  68. data/vendor/assets/vis/timeline/component/item/ItemPoint.js +0 -190
  69. data/vendor/assets/vis/timeline/component/item/ItemRange.js +0 -262
  70. data/vendor/assets/vis/timeline/component/item/ItemRangeOverflow.js +0 -57
  71. data/vendor/assets/vis/timeline/img/delete.png +0 -0
  72. data/vendor/assets/vis/timeline/stack.js +0 -112
  73. data/vendor/assets/vis/util.js +0 -990
@@ -1,1143 +0,0 @@
1
- /**
2
- * Creation of the ClusterMixin var.
3
- *
4
- * This contains all the functions the Graph object can use to employ clustering
5
- *
6
- * Alex de Mulder
7
- * 21-01-2013
8
- */
9
- var ClusterMixin = {
10
-
11
- /**
12
- * This is only called in the constructor of the graph object
13
- *
14
- */
15
- startWithClustering : function() {
16
- // cluster if the data set is big
17
- this.clusterToFit(this.constants.clustering.initialMaxNodes, true);
18
-
19
- // updates the lables after clustering
20
- this.updateLabels();
21
-
22
- // this is called here because if clusterin is disabled, the start and stabilize are called in
23
- // the setData function.
24
- if (this.stabilize) {
25
- this._stabilize();
26
- }
27
- this.start();
28
- },
29
-
30
- /**
31
- * This function clusters until the initialMaxNodes has been reached
32
- *
33
- * @param {Number} maxNumberOfNodes
34
- * @param {Boolean} reposition
35
- */
36
- clusterToFit : function(maxNumberOfNodes, reposition) {
37
- var numberOfNodes = this.nodeIndices.length;
38
-
39
- var maxLevels = 50;
40
- var level = 0;
41
-
42
- // we first cluster the hubs, then we pull in the outliers, repeat
43
- while (numberOfNodes > maxNumberOfNodes && level < maxLevels) {
44
- if (level % 3 == 0) {
45
- this.forceAggregateHubs(true);
46
- this.normalizeClusterLevels();
47
- }
48
- else {
49
- this.increaseClusterLevel(); // this also includes a cluster normalization
50
- }
51
-
52
- numberOfNodes = this.nodeIndices.length;
53
- level += 1;
54
- }
55
-
56
- // after the clustering we reposition the nodes to reduce the initial chaos
57
- if (level > 0 && reposition == true) {
58
- this.repositionNodes();
59
- }
60
- this._updateCalculationNodes();
61
- },
62
-
63
- /**
64
- * This function can be called to open up a specific cluster. It is only called by
65
- * It will unpack the cluster back one level.
66
- *
67
- * @param node | Node object: cluster to open.
68
- */
69
- openCluster : function(node) {
70
- var isMovingBeforeClustering = this.moving;
71
- if (node.clusterSize > this.constants.clustering.sectorThreshold && this._nodeInActiveArea(node) &&
72
- !(this._sector() == "default" && this.nodeIndices.length == 1)) {
73
- // this loads a new sector, loads the nodes and edges and nodeIndices of it.
74
- this._addSector(node);
75
- var level = 0;
76
-
77
- // we decluster until we reach a decent number of nodes
78
- while ((this.nodeIndices.length < this.constants.clustering.initialMaxNodes) && (level < 10)) {
79
- this.decreaseClusterLevel();
80
- level += 1;
81
- }
82
-
83
- }
84
- else {
85
- this._expandClusterNode(node,false,true);
86
-
87
- // update the index list, dynamic edges and labels
88
- this._updateNodeIndexList();
89
- this._updateDynamicEdges();
90
- this._updateCalculationNodes();
91
- this.updateLabels();
92
- }
93
-
94
- // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
95
- if (this.moving != isMovingBeforeClustering) {
96
- this.start();
97
- }
98
- },
99
-
100
-
101
- /**
102
- * This calls the updateClustes with default arguments
103
- */
104
- updateClustersDefault : function() {
105
- if (this.constants.clustering.enabled == true) {
106
- this.updateClusters(0,false,false);
107
- }
108
- },
109
-
110
-
111
- /**
112
- * This function can be called to increase the cluster level. This means that the nodes with only one edge connection will
113
- * be clustered with their connected node. This can be repeated as many times as needed.
114
- * This can be called externally (by a keybind for instance) to reduce the complexity of big datasets.
115
- */
116
- increaseClusterLevel : function() {
117
- this.updateClusters(-1,false,true);
118
- },
119
-
120
-
121
- /**
122
- * This function can be called to decrease the cluster level. This means that the nodes with only one edge connection will
123
- * be unpacked if they are a cluster. This can be repeated as many times as needed.
124
- * This can be called externally (by a key-bind for instance) to look into clusters without zooming.
125
- */
126
- decreaseClusterLevel : function() {
127
- this.updateClusters(1,false,true);
128
- },
129
-
130
-
131
- /**
132
- * This is the main clustering function. It clusters and declusters on zoom or forced
133
- * This function clusters on zoom, it can be called with a predefined zoom direction
134
- * If out, check if we can form clusters, if in, check if we can open clusters.
135
- * This function is only called from _zoom()
136
- *
137
- * @param {Number} zoomDirection | -1 / 0 / +1 for zoomOut / determineByZoom / zoomIn
138
- * @param {Boolean} recursive | enabled or disable recursive calling of the opening of clusters
139
- * @param {Boolean} force | enabled or disable forcing
140
- * @param {Boolean} doNotStart | if true do not call start
141
- *
142
- */
143
- updateClusters : function(zoomDirection,recursive,force,doNotStart) {
144
- var isMovingBeforeClustering = this.moving;
145
- var amountOfNodes = this.nodeIndices.length;
146
-
147
- // on zoom out collapse the sector if the scale is at the level the sector was made
148
- if (this.previousScale > this.scale && zoomDirection == 0) {
149
- this._collapseSector();
150
- }
151
-
152
- // check if we zoom in or out
153
- if (this.previousScale > this.scale || zoomDirection == -1) { // zoom out
154
- // forming clusters when forced pulls outliers in. When not forced, the edge length of the
155
- // outer nodes determines if it is being clustered
156
- this._formClusters(force);
157
- }
158
- else if (this.previousScale < this.scale || zoomDirection == 1) { // zoom in
159
- if (force == true) {
160
- // _openClusters checks for each node if the formationScale of the cluster is smaller than
161
- // the current scale and if so, declusters. When forced, all clusters are reduced by one step
162
- this._openClusters(recursive,force);
163
- }
164
- else {
165
- // if a cluster takes up a set percentage of the active window
166
- this._openClustersBySize();
167
- }
168
- }
169
- this._updateNodeIndexList();
170
-
171
- // if a cluster was NOT formed and the user zoomed out, we try clustering by hubs
172
- if (this.nodeIndices.length == amountOfNodes && (this.previousScale > this.scale || zoomDirection == -1)) {
173
- this._aggregateHubs(force);
174
- this._updateNodeIndexList();
175
- }
176
-
177
- // we now reduce chains.
178
- if (this.previousScale > this.scale || zoomDirection == -1) { // zoom out
179
- this.handleChains();
180
- this._updateNodeIndexList();
181
- }
182
-
183
- this.previousScale = this.scale;
184
-
185
- // rest of the update the index list, dynamic edges and labels
186
- this._updateDynamicEdges();
187
- this.updateLabels();
188
-
189
- // if a cluster was formed, we increase the clusterSession
190
- if (this.nodeIndices.length < amountOfNodes) { // this means a clustering operation has taken place
191
- this.clusterSession += 1;
192
- // if clusters have been made, we normalize the cluster level
193
- this.normalizeClusterLevels();
194
- }
195
-
196
- if (doNotStart == false || doNotStart === undefined) {
197
- // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
198
- if (this.moving != isMovingBeforeClustering) {
199
- this.start();
200
- }
201
- }
202
-
203
- this._updateCalculationNodes();
204
- },
205
-
206
- /**
207
- * This function handles the chains. It is called on every updateClusters().
208
- */
209
- handleChains : function() {
210
- // after clustering we check how many chains there are
211
- var chainPercentage = this._getChainFraction();
212
- if (chainPercentage > this.constants.clustering.chainThreshold) {
213
- this._reduceAmountOfChains(1 - this.constants.clustering.chainThreshold / chainPercentage)
214
-
215
- }
216
- },
217
-
218
- /**
219
- * this functions starts clustering by hubs
220
- * The minimum hub threshold is set globally
221
- *
222
- * @private
223
- */
224
- _aggregateHubs : function(force) {
225
- this._getHubSize();
226
- this._formClustersByHub(force,false);
227
- },
228
-
229
-
230
- /**
231
- * This function is fired by keypress. It forces hubs to form.
232
- *
233
- */
234
- forceAggregateHubs : function(doNotStart) {
235
- var isMovingBeforeClustering = this.moving;
236
- var amountOfNodes = this.nodeIndices.length;
237
-
238
- this._aggregateHubs(true);
239
-
240
- // update the index list, dynamic edges and labels
241
- this._updateNodeIndexList();
242
- this._updateDynamicEdges();
243
- this.updateLabels();
244
-
245
- // if a cluster was formed, we increase the clusterSession
246
- if (this.nodeIndices.length != amountOfNodes) {
247
- this.clusterSession += 1;
248
- }
249
-
250
- if (doNotStart == false || doNotStart === undefined) {
251
- // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
252
- if (this.moving != isMovingBeforeClustering) {
253
- this.start();
254
- }
255
- }
256
- },
257
-
258
- /**
259
- * If a cluster takes up more than a set percentage of the screen, open the cluster
260
- *
261
- * @private
262
- */
263
- _openClustersBySize : function() {
264
- for (var nodeId in this.nodes) {
265
- if (this.nodes.hasOwnProperty(nodeId)) {
266
- var node = this.nodes[nodeId];
267
- if (node.inView() == true) {
268
- if ((node.width*this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
269
- (node.height*this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
270
- this.openCluster(node);
271
- }
272
- }
273
- }
274
- }
275
- },
276
-
277
-
278
- /**
279
- * This function loops over all nodes in the nodeIndices list. For each node it checks if it is a cluster and if it
280
- * has to be opened based on the current zoom level.
281
- *
282
- * @private
283
- */
284
- _openClusters : function(recursive,force) {
285
- for (var i = 0; i < this.nodeIndices.length; i++) {
286
- var node = this.nodes[this.nodeIndices[i]];
287
- this._expandClusterNode(node,recursive,force);
288
- this._updateCalculationNodes();
289
- }
290
- },
291
-
292
- /**
293
- * This function checks if a node has to be opened. This is done by checking the zoom level.
294
- * If the node contains child nodes, this function is recursively called on the child nodes as well.
295
- * This recursive behaviour is optional and can be set by the recursive argument.
296
- *
297
- * @param {Node} parentNode | to check for cluster and expand
298
- * @param {Boolean} recursive | enabled or disable recursive calling
299
- * @param {Boolean} force | enabled or disable forcing
300
- * @param {Boolean} [openAll] | This will recursively force all nodes in the parent to be released
301
- * @private
302
- */
303
- _expandClusterNode : function(parentNode, recursive, force, openAll) {
304
- // first check if node is a cluster
305
- if (parentNode.clusterSize > 1) {
306
- // this means that on a double tap event or a zoom event, the cluster fully unpacks if it is smaller than 20
307
- if (parentNode.clusterSize < this.constants.clustering.sectorThreshold) {
308
- openAll = true;
309
- }
310
- recursive = openAll ? true : recursive;
311
-
312
- // if the last child has been added on a smaller scale than current scale decluster
313
- if (parentNode.formationScale < this.scale || force == true) {
314
- // we will check if any of the contained child nodes should be removed from the cluster
315
- for (var containedNodeId in parentNode.containedNodes) {
316
- if (parentNode.containedNodes.hasOwnProperty(containedNodeId)) {
317
- var childNode = parentNode.containedNodes[containedNodeId];
318
-
319
- // force expand will expand the largest cluster size clusters. Since we cluster from outside in, we assume that
320
- // the largest cluster is the one that comes from outside
321
- if (force == true) {
322
- if (childNode.clusterSession == parentNode.clusterSessions[parentNode.clusterSessions.length-1]
323
- || openAll) {
324
- this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll);
325
- }
326
- }
327
- else {
328
- if (this._nodeInActiveArea(parentNode)) {
329
- this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll);
330
- }
331
- }
332
- }
333
- }
334
- }
335
- }
336
- },
337
-
338
- /**
339
- * ONLY CALLED FROM _expandClusterNode
340
- *
341
- * This function will expel a child_node from a parent_node. This is to de-cluster the node. This function will remove
342
- * the child node from the parent contained_node object and put it back into the global nodes object.
343
- * The same holds for the edge that was connected to the child node. It is moved back into the global edges object.
344
- *
345
- * @param {Node} parentNode | the parent node
346
- * @param {String} containedNodeId | child_node id as it is contained in the containedNodes object of the parent node
347
- * @param {Boolean} recursive | This will also check if the child needs to be expanded.
348
- * With force and recursive both true, the entire cluster is unpacked
349
- * @param {Boolean} force | This will disregard the zoom level and will expel this child from the parent
350
- * @param {Boolean} openAll | This will recursively force all nodes in the parent to be released
351
- * @private
352
- */
353
- _expelChildFromParent : function(parentNode, containedNodeId, recursive, force, openAll) {
354
- var childNode = parentNode.containedNodes[containedNodeId];
355
-
356
- // if child node has been added on smaller scale than current, kick out
357
- if (childNode.formationScale < this.scale || force == true) {
358
- // unselect all selected items
359
- this._unselectAll();
360
-
361
- // put the child node back in the global nodes object
362
- this.nodes[containedNodeId] = childNode;
363
-
364
- // release the contained edges from this childNode back into the global edges
365
- this._releaseContainedEdges(parentNode,childNode);
366
-
367
- // reconnect rerouted edges to the childNode
368
- this._connectEdgeBackToChild(parentNode,childNode);
369
-
370
- // validate all edges in dynamicEdges
371
- this._validateEdges(parentNode);
372
-
373
- // undo the changes from the clustering operation on the parent node
374
- parentNode.mass -= childNode.mass;
375
- parentNode.clusterSize -= childNode.clusterSize;
376
- parentNode.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize);
377
- parentNode.dynamicEdgesLength = parentNode.dynamicEdges.length;
378
-
379
- // place the child node near the parent, not at the exact same location to avoid chaos in the system
380
- childNode.x = parentNode.x + parentNode.growthIndicator * (0.5 - Math.random());
381
- childNode.y = parentNode.y + parentNode.growthIndicator * (0.5 - Math.random());
382
-
383
- // remove node from the list
384
- delete parentNode.containedNodes[containedNodeId];
385
-
386
- // check if there are other childs with this clusterSession in the parent.
387
- var othersPresent = false;
388
- for (var childNodeId in parentNode.containedNodes) {
389
- if (parentNode.containedNodes.hasOwnProperty(childNodeId)) {
390
- if (parentNode.containedNodes[childNodeId].clusterSession == childNode.clusterSession) {
391
- othersPresent = true;
392
- break;
393
- }
394
- }
395
- }
396
- // if there are no others, remove the cluster session from the list
397
- if (othersPresent == false) {
398
- parentNode.clusterSessions.pop();
399
- }
400
-
401
- this._repositionBezierNodes(childNode);
402
- // this._repositionBezierNodes(parentNode);
403
-
404
- // remove the clusterSession from the child node
405
- childNode.clusterSession = 0;
406
-
407
- // recalculate the size of the node on the next time the node is rendered
408
- parentNode.clearSizeCache();
409
-
410
- // restart the simulation to reorganise all nodes
411
- this.moving = true;
412
- }
413
-
414
- // check if a further expansion step is possible if recursivity is enabled
415
- if (recursive == true) {
416
- this._expandClusterNode(childNode,recursive,force,openAll);
417
- }
418
- },
419
-
420
-
421
- /**
422
- * position the bezier nodes at the center of the edges
423
- *
424
- * @param node
425
- * @private
426
- */
427
- _repositionBezierNodes : function(node) {
428
- for (var i = 0; i < node.dynamicEdges.length; i++) {
429
- node.dynamicEdges[i].positionBezierNode();
430
- }
431
- },
432
-
433
-
434
- /**
435
- * This function checks if any nodes at the end of their trees have edges below a threshold length
436
- * This function is called only from updateClusters()
437
- * forceLevelCollapse ignores the length of the edge and collapses one level
438
- * This means that a node with only one edge will be clustered with its connected node
439
- *
440
- * @private
441
- * @param {Boolean} force
442
- */
443
- _formClusters : function(force) {
444
- if (force == false) {
445
- this._formClustersByZoom();
446
- }
447
- else {
448
- this._forceClustersByZoom();
449
- }
450
- },
451
-
452
-
453
- /**
454
- * This function handles the clustering by zooming out, this is based on a minimum edge distance
455
- *
456
- * @private
457
- */
458
- _formClustersByZoom : function() {
459
- var dx,dy,length,
460
- minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
461
-
462
- // check if any edges are shorter than minLength and start the clustering
463
- // the clustering favours the node with the larger mass
464
- for (var edgeId in this.edges) {
465
- if (this.edges.hasOwnProperty(edgeId)) {
466
- var edge = this.edges[edgeId];
467
- if (edge.connected) {
468
- if (edge.toId != edge.fromId) {
469
- dx = (edge.to.x - edge.from.x);
470
- dy = (edge.to.y - edge.from.y);
471
- length = Math.sqrt(dx * dx + dy * dy);
472
-
473
-
474
- if (length < minLength) {
475
- // first check which node is larger
476
- var parentNode = edge.from;
477
- var childNode = edge.to;
478
- if (edge.to.mass > edge.from.mass) {
479
- parentNode = edge.to;
480
- childNode = edge.from;
481
- }
482
-
483
- if (childNode.dynamicEdgesLength == 1) {
484
- this._addToCluster(parentNode,childNode,false);
485
- }
486
- else if (parentNode.dynamicEdgesLength == 1) {
487
- this._addToCluster(childNode,parentNode,false);
488
- }
489
- }
490
- }
491
- }
492
- }
493
- }
494
- },
495
-
496
- /**
497
- * This function forces the graph to cluster all nodes with only one connecting edge to their
498
- * connected node.
499
- *
500
- * @private
501
- */
502
- _forceClustersByZoom : function() {
503
- for (var nodeId in this.nodes) {
504
- // another node could have absorbed this child.
505
- if (this.nodes.hasOwnProperty(nodeId)) {
506
- var childNode = this.nodes[nodeId];
507
-
508
- // the edges can be swallowed by another decrease
509
- if (childNode.dynamicEdgesLength == 1 && childNode.dynamicEdges.length != 0) {
510
- var edge = childNode.dynamicEdges[0];
511
- var parentNode = (edge.toId == childNode.id) ? this.nodes[edge.fromId] : this.nodes[edge.toId];
512
-
513
- // group to the largest node
514
- if (childNode.id != parentNode.id) {
515
- if (parentNode.mass > childNode.mass) {
516
- this._addToCluster(parentNode,childNode,true);
517
- }
518
- else {
519
- this._addToCluster(childNode,parentNode,true);
520
- }
521
- }
522
- }
523
- }
524
- }
525
- },
526
-
527
-
528
- /**
529
- * To keep the nodes of roughly equal size we normalize the cluster levels.
530
- * This function clusters a node to its smallest connected neighbour.
531
- *
532
- * @param node
533
- * @private
534
- */
535
- _clusterToSmallestNeighbour : function(node) {
536
- var smallestNeighbour = -1;
537
- var smallestNeighbourNode = null;
538
- for (var i = 0; i < node.dynamicEdges.length; i++) {
539
- if (node.dynamicEdges[i] !== undefined) {
540
- var neighbour = null;
541
- if (node.dynamicEdges[i].fromId != node.id) {
542
- neighbour = node.dynamicEdges[i].from;
543
- }
544
- else if (node.dynamicEdges[i].toId != node.id) {
545
- neighbour = node.dynamicEdges[i].to;
546
- }
547
-
548
-
549
- if (neighbour != null && smallestNeighbour > neighbour.clusterSessions.length) {
550
- smallestNeighbour = neighbour.clusterSessions.length;
551
- smallestNeighbourNode = neighbour;
552
- }
553
- }
554
- }
555
-
556
- if (neighbour != null && this.nodes[neighbour.id] !== undefined) {
557
- this._addToCluster(neighbour, node, true);
558
- }
559
- },
560
-
561
-
562
- /**
563
- * This function forms clusters from hubs, it loops over all nodes
564
- *
565
- * @param {Boolean} force | Disregard zoom level
566
- * @param {Boolean} onlyEqual | This only clusters a hub with a specific number of edges
567
- * @private
568
- */
569
- _formClustersByHub : function(force, onlyEqual) {
570
- // we loop over all nodes in the list
571
- for (var nodeId in this.nodes) {
572
- // we check if it is still available since it can be used by the clustering in this loop
573
- if (this.nodes.hasOwnProperty(nodeId)) {
574
- this._formClusterFromHub(this.nodes[nodeId],force,onlyEqual);
575
- }
576
- }
577
- },
578
-
579
- /**
580
- * This function forms a cluster from a specific preselected hub node
581
- *
582
- * @param {Node} hubNode | the node we will cluster as a hub
583
- * @param {Boolean} force | Disregard zoom level
584
- * @param {Boolean} onlyEqual | This only clusters a hub with a specific number of edges
585
- * @param {Number} [absorptionSizeOffset] |
586
- * @private
587
- */
588
- _formClusterFromHub : function(hubNode, force, onlyEqual, absorptionSizeOffset) {
589
- if (absorptionSizeOffset === undefined) {
590
- absorptionSizeOffset = 0;
591
- }
592
- // we decide if the node is a hub
593
- if ((hubNode.dynamicEdgesLength >= this.hubThreshold && onlyEqual == false) ||
594
- (hubNode.dynamicEdgesLength == this.hubThreshold && onlyEqual == true)) {
595
- // initialize variables
596
- var dx,dy,length;
597
- var minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
598
- var allowCluster = false;
599
-
600
- // we create a list of edges because the dynamicEdges change over the course of this loop
601
- var edgesIdarray = [];
602
- var amountOfInitialEdges = hubNode.dynamicEdges.length;
603
- for (var j = 0; j < amountOfInitialEdges; j++) {
604
- edgesIdarray.push(hubNode.dynamicEdges[j].id);
605
- }
606
-
607
- // if the hub clustering is not forces, we check if one of the edges connected
608
- // to a cluster is small enough based on the constants.clustering.clusterEdgeThreshold
609
- if (force == false) {
610
- allowCluster = false;
611
- for (j = 0; j < amountOfInitialEdges; j++) {
612
- var edge = this.edges[edgesIdarray[j]];
613
- if (edge !== undefined) {
614
- if (edge.connected) {
615
- if (edge.toId != edge.fromId) {
616
- dx = (edge.to.x - edge.from.x);
617
- dy = (edge.to.y - edge.from.y);
618
- length = Math.sqrt(dx * dx + dy * dy);
619
-
620
- if (length < minLength) {
621
- allowCluster = true;
622
- break;
623
- }
624
- }
625
- }
626
- }
627
- }
628
- }
629
-
630
- // start the clustering if allowed
631
- if ((!force && allowCluster) || force) {
632
- // we loop over all edges INITIALLY connected to this hub
633
- for (j = 0; j < amountOfInitialEdges; j++) {
634
- edge = this.edges[edgesIdarray[j]];
635
- // the edge can be clustered by this function in a previous loop
636
- if (edge !== undefined) {
637
- var childNode = this.nodes[(edge.fromId == hubNode.id) ? edge.toId : edge.fromId];
638
- // we do not want hubs to merge with other hubs nor do we want to cluster itself.
639
- if ((childNode.dynamicEdges.length <= (this.hubThreshold + absorptionSizeOffset)) &&
640
- (childNode.id != hubNode.id)) {
641
- this._addToCluster(hubNode,childNode,force);
642
- }
643
- }
644
- }
645
- }
646
- }
647
- },
648
-
649
-
650
-
651
- /**
652
- * This function adds the child node to the parent node, creating a cluster if it is not already.
653
- *
654
- * @param {Node} parentNode | this is the node that will house the child node
655
- * @param {Node} childNode | this node will be deleted from the global this.nodes and stored in the parent node
656
- * @param {Boolean} force | true will only update the remainingEdges at the very end of the clustering, ensuring single level collapse
657
- * @private
658
- */
659
- _addToCluster : function(parentNode, childNode, force) {
660
- // join child node in the parent node
661
- parentNode.containedNodes[childNode.id] = childNode;
662
-
663
- // manage all the edges connected to the child and parent nodes
664
- for (var i = 0; i < childNode.dynamicEdges.length; i++) {
665
- var edge = childNode.dynamicEdges[i];
666
- if (edge.toId == parentNode.id || edge.fromId == parentNode.id) { // edge connected to parentNode
667
- this._addToContainedEdges(parentNode,childNode,edge);
668
- }
669
- else {
670
- this._connectEdgeToCluster(parentNode,childNode,edge);
671
- }
672
- }
673
- // a contained node has no dynamic edges.
674
- childNode.dynamicEdges = [];
675
-
676
- // remove circular edges from clusters
677
- this._containCircularEdgesFromNode(parentNode,childNode);
678
-
679
-
680
- // remove the childNode from the global nodes object
681
- delete this.nodes[childNode.id];
682
-
683
- // update the properties of the child and parent
684
- var massBefore = parentNode.mass;
685
- childNode.clusterSession = this.clusterSession;
686
- parentNode.mass += childNode.mass;
687
- parentNode.clusterSize += childNode.clusterSize;
688
- parentNode.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize);
689
-
690
- // keep track of the clustersessions so we can open the cluster up as it has been formed.
691
- if (parentNode.clusterSessions[parentNode.clusterSessions.length - 1] != this.clusterSession) {
692
- parentNode.clusterSessions.push(this.clusterSession);
693
- }
694
-
695
- // forced clusters only open from screen size and double tap
696
- if (force == true) {
697
- // parentNode.formationScale = Math.pow(1 - (1.0/11.0),this.clusterSession+3);
698
- parentNode.formationScale = 0;
699
- }
700
- else {
701
- parentNode.formationScale = this.scale; // The latest child has been added on this scale
702
- }
703
-
704
- // recalculate the size of the node on the next time the node is rendered
705
- parentNode.clearSizeCache();
706
-
707
- // set the pop-out scale for the childnode
708
- parentNode.containedNodes[childNode.id].formationScale = parentNode.formationScale;
709
-
710
- // nullify the movement velocity of the child, this is to avoid hectic behaviour
711
- childNode.clearVelocity();
712
-
713
- // the mass has altered, preservation of energy dictates the velocity to be updated
714
- parentNode.updateVelocity(massBefore);
715
-
716
- // restart the simulation to reorganise all nodes
717
- this.moving = true;
718
- },
719
-
720
-
721
- /**
722
- * This function will apply the changes made to the remainingEdges during the formation of the clusters.
723
- * This is a seperate function to allow for level-wise collapsing of the node barnesHutTree.
724
- * It has to be called if a level is collapsed. It is called by _formClusters().
725
- * @private
726
- */
727
- _updateDynamicEdges : function() {
728
- for (var i = 0; i < this.nodeIndices.length; i++) {
729
- var node = this.nodes[this.nodeIndices[i]];
730
- node.dynamicEdgesLength = node.dynamicEdges.length;
731
-
732
- // this corrects for multiple edges pointing at the same other node
733
- var correction = 0;
734
- if (node.dynamicEdgesLength > 1) {
735
- for (var j = 0; j < node.dynamicEdgesLength - 1; j++) {
736
- var edgeToId = node.dynamicEdges[j].toId;
737
- var edgeFromId = node.dynamicEdges[j].fromId;
738
- for (var k = j+1; k < node.dynamicEdgesLength; k++) {
739
- if ((node.dynamicEdges[k].toId == edgeToId && node.dynamicEdges[k].fromId == edgeFromId) ||
740
- (node.dynamicEdges[k].fromId == edgeToId && node.dynamicEdges[k].toId == edgeFromId)) {
741
- correction += 1;
742
- }
743
- }
744
- }
745
- }
746
- node.dynamicEdgesLength -= correction;
747
- }
748
- },
749
-
750
-
751
- /**
752
- * This adds an edge from the childNode to the contained edges of the parent node
753
- *
754
- * @param parentNode | Node object
755
- * @param childNode | Node object
756
- * @param edge | Edge object
757
- * @private
758
- */
759
- _addToContainedEdges : function(parentNode, childNode, edge) {
760
- // create an array object if it does not yet exist for this childNode
761
- if (!(parentNode.containedEdges.hasOwnProperty(childNode.id))) {
762
- parentNode.containedEdges[childNode.id] = []
763
- }
764
- // add this edge to the list
765
- parentNode.containedEdges[childNode.id].push(edge);
766
-
767
- // remove the edge from the global edges object
768
- delete this.edges[edge.id];
769
-
770
- // remove the edge from the parent object
771
- for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
772
- if (parentNode.dynamicEdges[i].id == edge.id) {
773
- parentNode.dynamicEdges.splice(i,1);
774
- break;
775
- }
776
- }
777
- },
778
-
779
- /**
780
- * This function connects an edge that was connected to a child node to the parent node.
781
- * It keeps track of which nodes it has been connected to with the originalId array.
782
- *
783
- * @param {Node} parentNode | Node object
784
- * @param {Node} childNode | Node object
785
- * @param {Edge} edge | Edge object
786
- * @private
787
- */
788
- _connectEdgeToCluster : function(parentNode, childNode, edge) {
789
- // handle circular edges
790
- if (edge.toId == edge.fromId) {
791
- this._addToContainedEdges(parentNode, childNode, edge);
792
- }
793
- else {
794
- if (edge.toId == childNode.id) { // edge connected to other node on the "to" side
795
- edge.originalToId.push(childNode.id);
796
- edge.to = parentNode;
797
- edge.toId = parentNode.id;
798
- }
799
- else { // edge connected to other node with the "from" side
800
-
801
- edge.originalFromId.push(childNode.id);
802
- edge.from = parentNode;
803
- edge.fromId = parentNode.id;
804
- }
805
-
806
- this._addToReroutedEdges(parentNode,childNode,edge);
807
- }
808
- },
809
-
810
-
811
- /**
812
- * If a node is connected to itself, a circular edge is drawn. When clustering we want to contain
813
- * these edges inside of the cluster.
814
- *
815
- * @param parentNode
816
- * @param childNode
817
- * @private
818
- */
819
- _containCircularEdgesFromNode : function(parentNode, childNode) {
820
- // manage all the edges connected to the child and parent nodes
821
- for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
822
- var edge = parentNode.dynamicEdges[i];
823
- // handle circular edges
824
- if (edge.toId == edge.fromId) {
825
- this._addToContainedEdges(parentNode, childNode, edge);
826
- }
827
- }
828
- },
829
-
830
-
831
- /**
832
- * This adds an edge from the childNode to the rerouted edges of the parent node
833
- *
834
- * @param parentNode | Node object
835
- * @param childNode | Node object
836
- * @param edge | Edge object
837
- * @private
838
- */
839
- _addToReroutedEdges : function(parentNode, childNode, edge) {
840
- // create an array object if it does not yet exist for this childNode
841
- // we store the edge in the rerouted edges so we can restore it when the cluster pops open
842
- if (!(parentNode.reroutedEdges.hasOwnProperty(childNode.id))) {
843
- parentNode.reroutedEdges[childNode.id] = [];
844
- }
845
- parentNode.reroutedEdges[childNode.id].push(edge);
846
-
847
- // this edge becomes part of the dynamicEdges of the cluster node
848
- parentNode.dynamicEdges.push(edge);
849
- },
850
-
851
-
852
-
853
- /**
854
- * This function connects an edge that was connected to a cluster node back to the child node.
855
- *
856
- * @param parentNode | Node object
857
- * @param childNode | Node object
858
- * @private
859
- */
860
- _connectEdgeBackToChild : function(parentNode, childNode) {
861
- if (parentNode.reroutedEdges.hasOwnProperty(childNode.id)) {
862
- for (var i = 0; i < parentNode.reroutedEdges[childNode.id].length; i++) {
863
- var edge = parentNode.reroutedEdges[childNode.id][i];
864
- if (edge.originalFromId[edge.originalFromId.length-1] == childNode.id) {
865
- edge.originalFromId.pop();
866
- edge.fromId = childNode.id;
867
- edge.from = childNode;
868
- }
869
- else {
870
- edge.originalToId.pop();
871
- edge.toId = childNode.id;
872
- edge.to = childNode;
873
- }
874
-
875
- // append this edge to the list of edges connecting to the childnode
876
- childNode.dynamicEdges.push(edge);
877
-
878
- // remove the edge from the parent object
879
- for (var j = 0; j < parentNode.dynamicEdges.length; j++) {
880
- if (parentNode.dynamicEdges[j].id == edge.id) {
881
- parentNode.dynamicEdges.splice(j,1);
882
- break;
883
- }
884
- }
885
- }
886
- // remove the entry from the rerouted edges
887
- delete parentNode.reroutedEdges[childNode.id];
888
- }
889
- },
890
-
891
-
892
- /**
893
- * When loops are clustered, an edge can be both in the rerouted array and the contained array.
894
- * This function is called last to verify that all edges in dynamicEdges are in fact connected to the
895
- * parentNode
896
- *
897
- * @param parentNode | Node object
898
- * @private
899
- */
900
- _validateEdges : function(parentNode) {
901
- for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
902
- var edge = parentNode.dynamicEdges[i];
903
- if (parentNode.id != edge.toId && parentNode.id != edge.fromId) {
904
- parentNode.dynamicEdges.splice(i,1);
905
- }
906
- }
907
- },
908
-
909
-
910
- /**
911
- * This function released the contained edges back into the global domain and puts them back into the
912
- * dynamic edges of both parent and child.
913
- *
914
- * @param {Node} parentNode |
915
- * @param {Node} childNode |
916
- * @private
917
- */
918
- _releaseContainedEdges : function(parentNode, childNode) {
919
- for (var i = 0; i < parentNode.containedEdges[childNode.id].length; i++) {
920
- var edge = parentNode.containedEdges[childNode.id][i];
921
-
922
- // put the edge back in the global edges object
923
- this.edges[edge.id] = edge;
924
-
925
- // put the edge back in the dynamic edges of the child and parent
926
- childNode.dynamicEdges.push(edge);
927
- parentNode.dynamicEdges.push(edge);
928
- }
929
- // remove the entry from the contained edges
930
- delete parentNode.containedEdges[childNode.id];
931
-
932
- },
933
-
934
-
935
-
936
-
937
- // ------------------- UTILITY FUNCTIONS ---------------------------- //
938
-
939
-
940
- /**
941
- * This updates the node labels for all nodes (for debugging purposes)
942
- */
943
- updateLabels : function() {
944
- var nodeId;
945
- // update node labels
946
- for (nodeId in this.nodes) {
947
- if (this.nodes.hasOwnProperty(nodeId)) {
948
- var node = this.nodes[nodeId];
949
- if (node.clusterSize > 1) {
950
- node.label = "[".concat(String(node.clusterSize),"]");
951
- }
952
- }
953
- }
954
-
955
- // update node labels
956
- for (nodeId in this.nodes) {
957
- if (this.nodes.hasOwnProperty(nodeId)) {
958
- node = this.nodes[nodeId];
959
- if (node.clusterSize == 1) {
960
- if (node.originalLabel !== undefined) {
961
- node.label = node.originalLabel;
962
- }
963
- else {
964
- node.label = String(node.id);
965
- }
966
- }
967
- }
968
- }
969
-
970
- // /* Debug Override */
971
- // for (nodeId in this.nodes) {
972
- // if (this.nodes.hasOwnProperty(nodeId)) {
973
- // node = this.nodes[nodeId];
974
- // node.label = String(node.level);
975
- // }
976
- // }
977
-
978
- },
979
-
980
-
981
- /**
982
- * We want to keep the cluster level distribution rather small. This means we do not want unclustered nodes
983
- * if the rest of the nodes are already a few cluster levels in.
984
- * To fix this we use this function. It determines the min and max cluster level and sends nodes that have not
985
- * clustered enough to the clusterToSmallestNeighbours function.
986
- */
987
- normalizeClusterLevels : function() {
988
- var maxLevel = 0;
989
- var minLevel = 1e9;
990
- var clusterLevel = 0;
991
- var nodeId;
992
-
993
- // we loop over all nodes in the list
994
- for (nodeId in this.nodes) {
995
- if (this.nodes.hasOwnProperty(nodeId)) {
996
- clusterLevel = this.nodes[nodeId].clusterSessions.length;
997
- if (maxLevel < clusterLevel) {maxLevel = clusterLevel;}
998
- if (minLevel > clusterLevel) {minLevel = clusterLevel;}
999
- }
1000
- }
1001
-
1002
- if (maxLevel - minLevel > this.constants.clustering.clusterLevelDifference) {
1003
- var amountOfNodes = this.nodeIndices.length;
1004
- var targetLevel = maxLevel - this.constants.clustering.clusterLevelDifference;
1005
- // we loop over all nodes in the list
1006
- for (nodeId in this.nodes) {
1007
- if (this.nodes.hasOwnProperty(nodeId)) {
1008
- if (this.nodes[nodeId].clusterSessions.length < targetLevel) {
1009
- this._clusterToSmallestNeighbour(this.nodes[nodeId]);
1010
- }
1011
- }
1012
- }
1013
- this._updateNodeIndexList();
1014
- this._updateDynamicEdges();
1015
- // if a cluster was formed, we increase the clusterSession
1016
- if (this.nodeIndices.length != amountOfNodes) {
1017
- this.clusterSession += 1;
1018
- }
1019
- }
1020
- },
1021
-
1022
-
1023
-
1024
- /**
1025
- * This function determines if the cluster we want to decluster is in the active area
1026
- * this means around the zoom center
1027
- *
1028
- * @param {Node} node
1029
- * @returns {boolean}
1030
- * @private
1031
- */
1032
- _nodeInActiveArea : function(node) {
1033
- return (
1034
- Math.abs(node.x - this.areaCenter.x) <= this.constants.clustering.activeAreaBoxSize/this.scale
1035
- &&
1036
- Math.abs(node.y - this.areaCenter.y) <= this.constants.clustering.activeAreaBoxSize/this.scale
1037
- )
1038
- },
1039
-
1040
-
1041
- /**
1042
- * This is an adaptation of the original repositioning function. This is called if the system is clustered initially
1043
- * It puts large clusters away from the center and randomizes the order.
1044
- *
1045
- */
1046
- repositionNodes : function() {
1047
- for (var i = 0; i < this.nodeIndices.length; i++) {
1048
- var node = this.nodes[this.nodeIndices[i]];
1049
- if ((node.xFixed == false || node.yFixed == false)) {
1050
- var radius = 10 * 0.1*this.nodeIndices.length * Math.min(100,node.mass);
1051
- var angle = 2 * Math.PI * Math.random();
1052
- if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
1053
- if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
1054
- this._repositionBezierNodes(node);
1055
- }
1056
- }
1057
- },
1058
-
1059
-
1060
- /**
1061
- * We determine how many connections denote an important hub.
1062
- * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%)
1063
- *
1064
- * @private
1065
- */
1066
- _getHubSize : function() {
1067
- var average = 0;
1068
- var averageSquared = 0;
1069
- var hubCounter = 0;
1070
- var largestHub = 0;
1071
-
1072
- for (var i = 0; i < this.nodeIndices.length; i++) {
1073
-
1074
- var node = this.nodes[this.nodeIndices[i]];
1075
- if (node.dynamicEdgesLength > largestHub) {
1076
- largestHub = node.dynamicEdgesLength;
1077
- }
1078
- average += node.dynamicEdgesLength;
1079
- averageSquared += Math.pow(node.dynamicEdgesLength,2);
1080
- hubCounter += 1;
1081
- }
1082
- average = average / hubCounter;
1083
- averageSquared = averageSquared / hubCounter;
1084
-
1085
- var variance = averageSquared - Math.pow(average,2);
1086
-
1087
- var standardDeviation = Math.sqrt(variance);
1088
-
1089
- this.hubThreshold = Math.floor(average + 2*standardDeviation);
1090
-
1091
- // always have at least one to cluster
1092
- if (this.hubThreshold > largestHub) {
1093
- this.hubThreshold = largestHub;
1094
- }
1095
-
1096
- // console.log("average",average,"averageSQ",averageSquared,"var",variance,"std",standardDeviation);
1097
- // console.log("hubThreshold:",this.hubThreshold);
1098
- },
1099
-
1100
-
1101
- /**
1102
- * We reduce the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
1103
- * with this amount we can cluster specifically on these chains.
1104
- *
1105
- * @param {Number} fraction | between 0 and 1, the percentage of chains to reduce
1106
- * @private
1107
- */
1108
- _reduceAmountOfChains : function(fraction) {
1109
- this.hubThreshold = 2;
1110
- var reduceAmount = Math.floor(this.nodeIndices.length * fraction);
1111
- for (var nodeId in this.nodes) {
1112
- if (this.nodes.hasOwnProperty(nodeId)) {
1113
- if (this.nodes[nodeId].dynamicEdgesLength == 2 && this.nodes[nodeId].dynamicEdges.length >= 2) {
1114
- if (reduceAmount > 0) {
1115
- this._formClusterFromHub(this.nodes[nodeId],true,true,1);
1116
- reduceAmount -= 1;
1117
- }
1118
- }
1119
- }
1120
- }
1121
- },
1122
-
1123
- /**
1124
- * We get the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
1125
- * with this amount we can cluster specifically on these chains.
1126
- *
1127
- * @private
1128
- */
1129
- _getChainFraction : function() {
1130
- var chains = 0;
1131
- var total = 0;
1132
- for (var nodeId in this.nodes) {
1133
- if (this.nodes.hasOwnProperty(nodeId)) {
1134
- if (this.nodes[nodeId].dynamicEdgesLength == 2 && this.nodes[nodeId].dynamicEdges.length >= 2) {
1135
- chains += 1;
1136
- }
1137
- total += 1;
1138
- }
1139
- }
1140
- return chains/total;
1141
- }
1142
-
1143
- };