vis-rails 2.0.0 → 2.0.1

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.
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
- };