vis-rails 0.0.6 → 1.0.0

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