vis-rails 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +5 -13
  2. data/lib/vis/rails/version.rb +1 -1
  3. data/vendor/assets/component/emitter.js +162 -0
  4. data/vendor/assets/javascripts/vis.js +1 -0
  5. data/vendor/assets/vis/DataSet.js +8 -2
  6. data/vendor/assets/vis/DataView.js +8 -4
  7. data/vendor/assets/vis/graph/Edge.js +210 -78
  8. data/vendor/assets/vis/graph/Graph.js +474 -652
  9. data/vendor/assets/vis/graph/Node.js +119 -82
  10. data/vendor/assets/vis/graph/css/graph-manipulation.css +128 -0
  11. data/vendor/assets/vis/graph/css/graph-navigation.css +62 -0
  12. data/vendor/assets/vis/graph/graphMixins/ClusterMixin.js +1141 -0
  13. data/vendor/assets/vis/graph/graphMixins/HierarchicalLayoutMixin.js +296 -0
  14. data/vendor/assets/vis/graph/graphMixins/ManipulationMixin.js +433 -0
  15. data/vendor/assets/vis/graph/graphMixins/MixinLoader.js +201 -0
  16. data/vendor/assets/vis/graph/graphMixins/NavigationMixin.js +173 -0
  17. data/vendor/assets/vis/graph/graphMixins/SectorsMixin.js +552 -0
  18. data/vendor/assets/vis/graph/graphMixins/SelectionMixin.js +558 -0
  19. data/vendor/assets/vis/graph/graphMixins/physics/BarnesHut.js +373 -0
  20. data/vendor/assets/vis/graph/graphMixins/physics/HierarchialRepulsion.js +64 -0
  21. data/vendor/assets/vis/graph/graphMixins/physics/PhysicsMixin.js +513 -0
  22. data/vendor/assets/vis/graph/graphMixins/physics/Repulsion.js +66 -0
  23. data/vendor/assets/vis/graph/img/acceptDeleteIcon.png +0 -0
  24. data/vendor/assets/vis/graph/img/addNodeIcon.png +0 -0
  25. data/vendor/assets/vis/graph/img/backIcon.png +0 -0
  26. data/vendor/assets/vis/graph/img/connectIcon.png +0 -0
  27. data/vendor/assets/vis/graph/img/cross.png +0 -0
  28. data/vendor/assets/vis/graph/img/cross2.png +0 -0
  29. data/vendor/assets/vis/graph/img/deleteIcon.png +0 -0
  30. data/vendor/assets/vis/graph/img/downArrow.png +0 -0
  31. data/vendor/assets/vis/graph/img/editIcon.png +0 -0
  32. data/vendor/assets/vis/graph/img/leftArrow.png +0 -0
  33. data/vendor/assets/vis/graph/img/rightArrow.png +0 -0
  34. data/vendor/assets/vis/graph/img/upArrow.png +0 -0
  35. data/vendor/assets/vis/module/exports.js +0 -2
  36. data/vendor/assets/vis/module/header.js +2 -2
  37. data/vendor/assets/vis/module/imports.js +1 -2
  38. data/vendor/assets/vis/timeline/Controller.js +56 -45
  39. data/vendor/assets/vis/timeline/Range.js +68 -62
  40. data/vendor/assets/vis/timeline/Stack.js +11 -13
  41. data/vendor/assets/vis/timeline/TimeStep.js +43 -38
  42. data/vendor/assets/vis/timeline/Timeline.js +215 -93
  43. data/vendor/assets/vis/timeline/component/Component.js +19 -3
  44. data/vendor/assets/vis/timeline/component/CurrentTime.js +1 -1
  45. data/vendor/assets/vis/timeline/component/CustomTime.js +39 -120
  46. data/vendor/assets/vis/timeline/component/GroupSet.js +35 -1
  47. data/vendor/assets/vis/timeline/component/ItemSet.js +272 -9
  48. data/vendor/assets/vis/timeline/component/RootPanel.js +59 -47
  49. data/vendor/assets/vis/timeline/component/TimeAxis.js +10 -0
  50. data/vendor/assets/vis/timeline/component/css/item.css +53 -22
  51. data/vendor/assets/vis/timeline/component/item/Item.js +40 -5
  52. data/vendor/assets/vis/timeline/component/item/ItemBox.js +3 -1
  53. data/vendor/assets/vis/timeline/component/item/ItemPoint.js +3 -1
  54. data/vendor/assets/vis/timeline/component/item/ItemRange.js +67 -3
  55. data/vendor/assets/vis/timeline/component/item/ItemRangeOverflow.js +37 -9
  56. data/vendor/assets/vis/timeline/img/delete.png +0 -0
  57. data/vendor/assets/vis/util.js +169 -30
  58. metadata +39 -12
@@ -10,17 +10,25 @@
10
10
  * @param {Object} options Options
11
11
  */
12
12
  function Graph (container, data, options) {
13
+
14
+ this._initializeMixinLoaders();
15
+
13
16
  // create variables and set default values
14
17
  this.containerElement = container;
15
18
  this.width = '100%';
16
19
  this.height = '100%';
17
- // to give everything a nice fluidity, we seperate the rendering and calculating of the forces
18
- this.renderRefreshRate = 60; // hz (fps)
20
+
21
+ // render and calculation settings
22
+ this.renderRefreshRate = 60; // hz (fps)
19
23
  this.renderTimestep = 1000 / this.renderRefreshRate; // ms -- saves calculation later on
20
- this.stabilize = true; // stabilize before displaying the graph
24
+ this.renderTime = 0.5 * this.renderTimestep; // measured time it takes to render a frame
25
+ this.maxRenderSteps = 3; // max amount of physics ticks per render step.
26
+
27
+ this.stabilize = true; // stabilize before displaying the graph
21
28
  this.selectable = true;
22
29
 
23
- this.forceFactor = 50000;
30
+ // these functions are triggered when the dataset is edited
31
+ this.triggerFunctions = {add:null,edit:null,connect:null,delete:null};
24
32
 
25
33
  // set constant values
26
34
  this.constants = {
@@ -28,15 +36,15 @@ function Graph (container, data, options) {
28
36
  radiusMin: 5,
29
37
  radiusMax: 20,
30
38
  radius: 5,
31
- distance: 100, // px
32
39
  shape: 'ellipse',
33
40
  image: undefined,
34
41
  widthMin: 16, // px
35
42
  widthMax: 64, // px
43
+ fixed: false,
36
44
  fontColor: 'black',
37
45
  fontSize: 14, // px
38
- //fontFace: verdana,
39
- fontFace: 'arial',
46
+ fontFace: 'verdana',
47
+ level: -1,
40
48
  color: {
41
49
  border: '#2B7CE9',
42
50
  background: '#97C2FC',
@@ -55,18 +63,47 @@ function Graph (container, data, options) {
55
63
  widthMax: 15,
56
64
  width: 1,
57
65
  style: 'line',
58
- color: '#343434',
66
+ color: '#848484',
59
67
  fontColor: '#343434',
60
68
  fontSize: 14, // px
61
69
  fontFace: 'arial',
62
- //distance: 100, //px
63
- length: 100, // px
64
70
  dash: {
65
71
  length: 10,
66
72
  gap: 5,
67
73
  altLength: undefined
68
74
  }
69
75
  },
76
+ configurePhysics:false,
77
+ physics: {
78
+ barnesHut: {
79
+ enabled: true,
80
+ theta: 1 / 0.6, // inverted to save time during calculation
81
+ gravitationalConstant: -2000,
82
+ centralGravity: 0.3,
83
+ springLength: 100,
84
+ springConstant: 0.05,
85
+ damping: 0.09
86
+ },
87
+ repulsion: {
88
+ centralGravity: 0.1,
89
+ springLength: 200,
90
+ springConstant: 0.05,
91
+ nodeDistance: 100,
92
+ damping: 0.09
93
+ },
94
+ hierarchicalRepulsion: {
95
+ enabled: false,
96
+ centralGravity: 0.0,
97
+ springLength: 100,
98
+ springConstant: 0.01,
99
+ nodeDistance: 60,
100
+ damping: 0.09
101
+ },
102
+ damping: null,
103
+ centralGravity: null,
104
+ springLength: null,
105
+ springConstant: null
106
+ },
70
107
  clustering: { // Per Node in Cluster = PNiC
71
108
  enabled: false, // (Boolean) | global on/off switch for clustering.
72
109
  initialMaxNodes: 100, // (# nodes) | if the initial amount of nodes is larger than this, we cluster until the total number is less than this threshold.
@@ -74,118 +111,144 @@ function Graph (container, data, options) {
74
111
  reduceToNodes:300, // (# nodes) | during calculate forces, we check if the total number of nodes is larger than clusterThreshold. If it is, cluster until reduced to this
75
112
  chainThreshold: 0.4, // (% of all drawn nodes)| maximum percentage of allowed chainnodes (long strings of connected nodes) within all nodes. (lower means less chains).
76
113
  clusterEdgeThreshold: 20, // (px) | edge length threshold. if smaller, this node is clustered.
77
- sectorThreshold: 50, // (# nodes in cluster) | cluster size threshold. If larger, expanding in own sector.
114
+ sectorThreshold: 100, // (# nodes in cluster) | cluster size threshold. If larger, expanding in own sector.
78
115
  screenSizeThreshold: 0.2, // (% of canvas) | relative size threshold. If the width or height of a clusternode takes up this much of the screen, decluster node.
79
116
  fontSizeMultiplier: 4.0, // (px PNiC) | how much the cluster font size grows per node in cluster (in px).
80
- forceAmplification: 0.6, // (multiplier PNiC) | factor of increase fo the repulsion force of a cluster (per node in cluster).
81
- distanceAmplification: 0.2, // (multiplier PNiC) | factor how much the repulsion distance of a cluster increases (per node in cluster).
82
- edgeGrowth: 11, // (px PNiC) | amount of clusterSize connected to the edge is multiplied with this and added to edgeLength.
83
- nodeScaling: {width: 10, // (px PNiC) | growth of the width per node in cluster.
84
- height: 10, // (px PNiC) | growth of the height per node in cluster.
85
- radius: 10}, // (px PNiC) | growth of the radius per node in cluster.
86
- activeAreaBoxSize: 100, // (px) | box area around the curser where clusters are popped open.
87
- massTransferCoefficient: 1 // (multiplier) | parent.mass += massTransferCoefficient * child.mass
117
+ maxFontSize: 1000,
118
+ forceAmplification: 0.1, // (multiplier PNiC) | factor of increase fo the repulsion force of a cluster (per node in cluster).
119
+ distanceAmplification: 0.1, // (multiplier PNiC) | factor how much the repulsion distance of a cluster increases (per node in cluster).
120
+ edgeGrowth: 20, // (px PNiC) | amount of clusterSize connected to the edge is multiplied with this and added to edgeLength.
121
+ nodeScaling: {width: 1, // (px PNiC) | growth of the width per node in cluster.
122
+ height: 1, // (px PNiC) | growth of the height per node in cluster.
123
+ radius: 1}, // (px PNiC) | growth of the radius per node in cluster.
124
+ maxNodeSizeIncrements: 600, // (# increments) | max growth of the width per node in cluster.
125
+ activeAreaBoxSize: 80, // (px) | box area around the curser where clusters are popped open.
126
+ clusterLevelDifference: 2
88
127
  },
89
128
  navigation: {
90
- enabled: false,
91
- iconPath: this._getScriptPath() + '/img'
129
+ enabled: false
92
130
  },
93
131
  keyboard: {
94
132
  enabled: false,
95
133
  speed: {x: 10, y: 10, zoom: 0.02}
96
134
  },
97
- minVelocity: 2, // px/s
135
+ dataManipulation: {
136
+ enabled: false,
137
+ initiallyVisible: false
138
+ },
139
+ hierarchicalLayout: {
140
+ enabled:false,
141
+ levelSeparation: 150,
142
+ nodeSpacing: 100,
143
+ direction: "UD" // UD, DU, LR, RL
144
+ },
145
+ smoothCurves: true,
146
+ maxVelocity: 10,
147
+ minVelocity: 0.1, // px/s
98
148
  maxIterations: 1000 // maximum number of iteration to stabilize
99
149
  };
150
+ this.editMode = this.constants.dataManipulation.initiallyVisible;
100
151
 
101
152
  // Node variables
153
+ var graph = this;
102
154
  this.groups = new Groups(); // object with groups
103
155
  this.images = new Images(); // object with images
104
156
  this.images.setOnloadCallback(function () {
105
157
  graph._redraw();
106
158
  });
107
159
 
108
- // navigation variables
160
+ // keyboard navigation variables
109
161
  this.xIncrement = 0;
110
162
  this.yIncrement = 0;
111
163
  this.zoomIncrement = 0;
112
164
 
165
+ // loading all the mixins:
166
+ // load the force calculation functions, grouped under the physics system.
167
+ this._loadPhysicsSystem();
113
168
  // create a frame and canvas
114
169
  this._create();
115
-
116
170
  // load the sector system. (mandatory, fully integrated with Graph)
117
171
  this._loadSectorSystem();
118
-
119
- // apply options
120
- this.setOptions(options);
121
-
122
172
  // load the cluster system. (mandatory, even when not using the cluster system, there are function calls to it)
123
173
  this._loadClusterSystem();
124
-
125
174
  // load the selection system. (mandatory, required by Graph)
126
175
  this._loadSelectionSystem();
176
+ // load the selection system. (mandatory, required by Graph)
177
+ this._loadHierarchySystem();
178
+
179
+ // apply options
180
+ this.setOptions(options);
127
181
 
128
182
  // other vars
129
- var graph = this;
130
183
  this.freezeSimulation = false;// freeze the simulation
184
+ this.cachedFunctions = {};
131
185
 
186
+ // containers for nodes and edges
187
+ this.calculationNodes = {};
188
+ this.calculationNodeIndices = [];
132
189
  this.nodeIndices = []; // array with all the indices of the nodes. Used to speed up forces calculation
133
190
  this.nodes = {}; // object with Node objects
134
191
  this.edges = {}; // object with Edge objects
135
192
 
193
+ // position and scale variables and objects
136
194
  this.canvasTopLeft = {"x": 0,"y": 0}; // coordinates of the top left of the canvas. they will be set during _redraw.
137
195
  this.canvasBottomRight = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
138
-
196
+ this.pointerPosition = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
139
197
  this.areaCenter = {}; // object with x and y elements used for determining the center of the zoom action
140
198
  this.scale = 1; // defining the global scale variable in the constructor
141
199
  this.previousScale = this.scale; // this is used to check if the zoom operation is zooming in or out
142
- // TODO: create a counter to keep track on the number of nodes having values
143
- // TODO: create a counter to keep track on the number of nodes currently moving
144
- // TODO: create a counter to keep track on the number of edges having values
145
200
 
201
+ // datasets or dataviews
146
202
  this.nodesData = null; // A DataSet or DataView
147
203
  this.edgesData = null; // A DataSet or DataView
148
204
 
149
205
  // create event listeners used to subscribe on the DataSets of the nodes and edges
150
- var me = this;
151
206
  this.nodesListeners = {
152
207
  'add': function (event, params) {
153
- me._addNodes(params.items);
154
- me.start();
208
+ graph._addNodes(params.items);
209
+ graph.start();
155
210
  },
156
211
  'update': function (event, params) {
157
- me._updateNodes(params.items);
158
- me.start();
212
+ graph._updateNodes(params.items);
213
+ graph.start();
159
214
  },
160
215
  'remove': function (event, params) {
161
- me._removeNodes(params.items);
162
- me.start();
216
+ graph._removeNodes(params.items);
217
+ graph.start();
163
218
  }
164
219
  };
165
220
  this.edgesListeners = {
166
221
  'add': function (event, params) {
167
- me._addEdges(params.items);
168
- me.start();
222
+ graph._addEdges(params.items);
223
+ graph.start();
169
224
  },
170
225
  'update': function (event, params) {
171
- me._updateEdges(params.items);
172
- me.start();
226
+ graph._updateEdges(params.items);
227
+ graph.start();
173
228
  },
174
229
  'remove': function (event, params) {
175
- me._removeEdges(params.items);
176
- me.start();
230
+ graph._removeEdges(params.items);
231
+ graph.start();
177
232
  }
178
233
  };
179
234
 
180
- // properties of the data
181
- this.moving = false; // True if any of the nodes have an undefined position
182
- this.timer = undefined;
235
+ // properties for the animation
236
+ this.moving = true;
237
+ this.timer = undefined; // Scheduling function. Is definded in this.start();
183
238
 
184
239
  // load data (the disable start variable will be the same as the enabled clustering)
185
- this.setData(data,this.constants.clustering.enabled);
240
+ this.setData(data,this.constants.clustering.enabled || this.constants.hierarchicalLayout.enabled);
186
241
 
187
- // zoom so all data will fit on the screen
188
- this.zoomToFit(true);
242
+ // hierarchical layout
243
+ if (this.constants.hierarchicalLayout.enabled == true) {
244
+ this._setupHierarchicalLayout();
245
+ }
246
+ else {
247
+ // zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here.
248
+ if (this.stabilize == false) {
249
+ this.zoomExtent(true,this.constants.clustering.enabled);
250
+ }
251
+ }
189
252
 
190
253
  // if clustering is disabled, the simulation will have started in the setData function
191
254
  if (this.constants.clustering.enabled) {
@@ -193,6 +256,9 @@ function Graph (container, data, options) {
193
256
  }
194
257
  }
195
258
 
259
+ // Extend Graph with an Emitter mixin
260
+ Emitter(Graph.prototype);
261
+
196
262
  /**
197
263
  * Get the script path where the vis.js library is located
198
264
  *
@@ -223,12 +289,14 @@ Graph.prototype._getScriptPath = function() {
223
289
  */
224
290
  Graph.prototype._getRange = function() {
225
291
  var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
226
- for (var i = 0; i < this.nodeIndices.length; i++) {
227
- node = this.nodes[this.nodeIndices[i]];
228
- if (minX > (node.x - node.width)) {minX = node.x - node.width;}
229
- if (maxX < (node.x + node.width)) {maxX = node.x + node.width;}
230
- if (minY > (node.y - node.height)) {minY = node.y - node.height;}
231
- if (maxY < (node.y + node.height)) {maxY = node.y + node.height;}
292
+ for (var nodeId in this.nodes) {
293
+ if (this.nodes.hasOwnProperty(nodeId)) {
294
+ node = this.nodes[nodeId];
295
+ if (minX > (node.x)) {minX = node.x;}
296
+ if (maxX < (node.x)) {maxX = node.x;}
297
+ if (minY > (node.y)) {minY = node.y;}
298
+ if (maxY < (node.y)) {maxY = node.y;}
299
+ }
232
300
  }
233
301
  return {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
234
302
  };
@@ -240,9 +308,8 @@ Graph.prototype._getRange = function() {
240
308
  * @private
241
309
  */
242
310
  Graph.prototype._findCenter = function(range) {
243
- var center = {x: (0.5 * (range.maxX + range.minX)),
244
- y: (0.5 * (range.maxY + range.minY))};
245
- return center;
311
+ return {x: (0.5 * (range.maxX + range.minX)),
312
+ y: (0.5 * (range.maxY + range.minY))};
246
313
  };
247
314
 
248
315
 
@@ -268,22 +335,41 @@ Graph.prototype._centerGraph = function(range) {
268
335
  *
269
336
  * @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false;
270
337
  */
271
- Graph.prototype.zoomToFit = function(initialZoom) {
338
+ Graph.prototype.zoomExtent = function(initialZoom, disableStart) {
272
339
  if (initialZoom === undefined) {
273
340
  initialZoom = false;
274
341
  }
342
+ if (disableStart === undefined) {
343
+ disableStart = false;
344
+ }
275
345
 
276
- var numberOfNodes = this.nodeIndices.length;
277
346
  var range = this._getRange();
347
+ var zoomLevel;
278
348
 
279
349
  if (initialZoom == true) {
280
- if (this.constants.clustering.enabled == true &&
350
+ var numberOfNodes = this.nodeIndices.length;
351
+ if (this.constants.smoothCurves == true) {
352
+ if (this.constants.clustering.enabled == true &&
281
353
  numberOfNodes >= this.constants.clustering.initialMaxNodes) {
282
- var zoomLevel = 38.8467 / (numberOfNodes - 14.50184) + 0.0116; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
354
+ zoomLevel = 49.07548 / (numberOfNodes + 142.05338) + 9.1444e-04; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
355
+ }
356
+ else {
357
+ zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
358
+ }
283
359
  }
284
360
  else {
285
- var zoomLevel = 42.54117319 / (numberOfNodes + 39.31966387) + 0.1944405; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
361
+ if (this.constants.clustering.enabled == true &&
362
+ numberOfNodes >= this.constants.clustering.initialMaxNodes) {
363
+ zoomLevel = 77.5271985 / (numberOfNodes + 187.266146) + 4.76710517e-05; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
364
+ }
365
+ else {
366
+ zoomLevel = 30.5062972 / (numberOfNodes + 19.93597763) + 0.08413486; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
367
+ }
286
368
  }
369
+
370
+ // correct for larger canvasses.
371
+ var factor = Math.min(this.frame.canvas.clientWidth / 600, this.frame.canvas.clientHeight / 600);
372
+ zoomLevel *= factor;
287
373
  }
288
374
  else {
289
375
  var xDistance = (Math.abs(range.minX) + Math.abs(range.maxX)) * 1.1;
@@ -299,10 +385,13 @@ Graph.prototype.zoomToFit = function(initialZoom) {
299
385
  zoomLevel = 1.0;
300
386
  }
301
387
 
302
- this.pinch.mousewheelScale = zoomLevel;
388
+
303
389
  this._setScale(zoomLevel);
304
390
  this._centerGraph(range);
305
- this.start();
391
+ if (disableStart == false) {
392
+ this.moving = true;
393
+ this.start();
394
+ }
306
395
  };
307
396
 
308
397
 
@@ -364,7 +453,6 @@ Graph.prototype.setData = function(data, disableStart) {
364
453
  if (this.stabilize) {
365
454
  this._doStabilize();
366
455
  }
367
- this.moving = true;
368
456
  this.start();
369
457
  }
370
458
  };
@@ -375,15 +463,66 @@ Graph.prototype.setData = function(data, disableStart) {
375
463
  */
376
464
  Graph.prototype.setOptions = function (options) {
377
465
  if (options) {
466
+ var prop;
378
467
  // retrieve parameter values
379
468
  if (options.width !== undefined) {this.width = options.width;}
380
469
  if (options.height !== undefined) {this.height = options.height;}
381
470
  if (options.stabilize !== undefined) {this.stabilize = options.stabilize;}
382
471
  if (options.selectable !== undefined) {this.selectable = options.selectable;}
472
+ if (options.smoothCurves !== undefined) {this.constants.smoothCurves = options.smoothCurves;}
473
+ if (options.configurePhysics !== undefined){this.constants.configurePhysics = options.configurePhysics;}
474
+
475
+ if (options.onAdd) {
476
+ this.triggerFunctions.add = options.onAdd;
477
+ }
478
+
479
+ if (options.onEdit) {
480
+ this.triggerFunctions.edit = options.onEdit;
481
+ }
482
+
483
+ if (options.onConnect) {
484
+ this.triggerFunctions.connect = options.onConnect;
485
+ }
486
+
487
+ if (options.onDelete) {
488
+ this.triggerFunctions.delete = options.onDelete;
489
+ }
490
+
491
+ if (options.physics) {
492
+ if (options.physics.barnesHut) {
493
+ this.constants.physics.barnesHut.enabled = true;
494
+ for (prop in options.physics.barnesHut) {
495
+ if (options.physics.barnesHut.hasOwnProperty(prop)) {
496
+ this.constants.physics.barnesHut[prop] = options.physics.barnesHut[prop];
497
+ }
498
+ }
499
+ }
500
+
501
+ if (options.physics.repulsion) {
502
+ this.constants.physics.barnesHut.enabled = false;
503
+ for (prop in options.physics.repulsion) {
504
+ if (options.physics.repulsion.hasOwnProperty(prop)) {
505
+ this.constants.physics.repulsion[prop] = options.physics.repulsion[prop];
506
+ }
507
+ }
508
+ }
509
+ }
510
+
511
+ if (options.hierarchicalLayout) {
512
+ this.constants.hierarchicalLayout.enabled = true;
513
+ for (prop in options.hierarchicalLayout) {
514
+ if (options.hierarchicalLayout.hasOwnProperty(prop)) {
515
+ this.constants.hierarchicalLayout[prop] = options.hierarchicalLayout[prop];
516
+ }
517
+ }
518
+ }
519
+ else if (options.hierarchicalLayout !== undefined) {
520
+ this.constants.hierarchicalLayout.enabled = false;
521
+ }
383
522
 
384
523
  if (options.clustering) {
385
524
  this.constants.clustering.enabled = true;
386
- for (var prop in options.clustering) {
525
+ for (prop in options.clustering) {
387
526
  if (options.clustering.hasOwnProperty(prop)) {
388
527
  this.constants.clustering[prop] = options.clustering[prop];
389
528
  }
@@ -395,7 +534,7 @@ Graph.prototype.setOptions = function (options) {
395
534
 
396
535
  if (options.navigation) {
397
536
  this.constants.navigation.enabled = true;
398
- for (var prop in options.navigation) {
537
+ for (prop in options.navigation) {
399
538
  if (options.navigation.hasOwnProperty(prop)) {
400
539
  this.constants.navigation[prop] = options.navigation[prop];
401
540
  }
@@ -407,7 +546,7 @@ Graph.prototype.setOptions = function (options) {
407
546
 
408
547
  if (options.keyboard) {
409
548
  this.constants.keyboard.enabled = true;
410
- for (var prop in options.keyboard) {
549
+ for (prop in options.keyboard) {
411
550
  if (options.keyboard.hasOwnProperty(prop)) {
412
551
  this.constants.keyboard[prop] = options.keyboard[prop];
413
552
  }
@@ -417,6 +556,17 @@ Graph.prototype.setOptions = function (options) {
417
556
  this.constants.keyboard.enabled = false;
418
557
  }
419
558
 
559
+ if (options.dataManipulation) {
560
+ this.constants.dataManipulation.enabled = true;
561
+ for (prop in options.dataManipulation) {
562
+ if (options.dataManipulation.hasOwnProperty(prop)) {
563
+ this.constants.dataManipulation[prop] = options.dataManipulation[prop];
564
+ }
565
+ }
566
+ }
567
+ else if (options.dataManipulation !== undefined) {
568
+ this.constants.dataManipulation.enabled = false;
569
+ }
420
570
 
421
571
  // TODO: work out these options and document them
422
572
  if (options.edges) {
@@ -426,12 +576,6 @@ Graph.prototype.setOptions = function (options) {
426
576
  }
427
577
  }
428
578
 
429
- if (options.edges.length !== undefined &&
430
- options.nodes && options.nodes.distance === undefined) {
431
- this.constants.edges.length = options.edges.length;
432
- this.constants.nodes.distance = options.edges.length * 1.25;
433
- }
434
-
435
579
  if (!options.edges.fontColor) {
436
580
  this.constants.edges.fontColor = options.edges.color;
437
581
  }
@@ -478,57 +622,27 @@ Graph.prototype.setOptions = function (options) {
478
622
  }
479
623
  }
480
624
 
481
- this.setSize(this.width, this.height);
482
- this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
483
- this._setScale(1);
484
625
 
626
+ // (Re)loading the mixins that can be enabled or disabled in the options.
627
+ // load the force calculation functions, grouped under the physics system.
628
+ this._loadPhysicsSystem();
485
629
  // load the navigation system.
486
630
  this._loadNavigationControls();
631
+ // load the data manipulation system
632
+ this._loadManipulationSystem();
633
+ // configure the smooth curves
634
+ this._configureSmoothCurves();
635
+
487
636
 
488
637
  // bind keys. If disabled, this will not do anything;
489
638
  this._createKeyBinds();
490
639
 
640
+ this.setSize(this.width, this.height);
641
+ this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
642
+ this._setScale(1);
491
643
  this._redraw();
492
644
  };
493
645
 
494
- /**
495
- * Add event listener
496
- * @param {String} event Event name. Available events:
497
- * 'select'
498
- * @param {function} callback Callback function, invoked as callback(properties)
499
- * where properties is an optional object containing
500
- * event specific properties.
501
- */
502
- Graph.prototype.on = function on (event, callback) {
503
- var available = ['select'];
504
-
505
- if (available.indexOf(event) == -1) {
506
- throw new Error('Unknown event "' + event + '". Choose from ' + available.join());
507
- }
508
-
509
- events.addListener(this, event, callback);
510
- };
511
-
512
- /**
513
- * Remove an event listener
514
- * @param {String} event Event name
515
- * @param {function} callback Callback function
516
- */
517
- Graph.prototype.off = function off (event, callback) {
518
- events.removeListener(this, event, callback);
519
- };
520
-
521
- /**
522
- * fire an event
523
- * @param {String} event The name of an event, for example 'select'
524
- * @param {Object} params Optional object with event parameters
525
- * @private
526
- */
527
- Graph.prototype._trigger = function (event, params) {
528
- events.trigger(this, event, params);
529
- };
530
-
531
-
532
646
  /**
533
647
  * Create the main frame for the Graph.
534
648
  * This function is executed once when a Graph object is created. The frame
@@ -546,6 +660,7 @@ Graph.prototype._create = function () {
546
660
  this.frame.className = 'graph-frame';
547
661
  this.frame.style.position = 'relative';
548
662
  this.frame.style.overflow = 'hidden';
663
+ this.frame.style.zIndex = "1";
549
664
 
550
665
  // create the graph canvas (HTML canvas element)
551
666
  this.frame.canvas = document.createElement( 'canvas' );
@@ -581,6 +696,7 @@ Graph.prototype._create = function () {
581
696
 
582
697
  // add the frame to the container element
583
698
  this.containerElement.appendChild(this.frame);
699
+
584
700
  };
585
701
 
586
702
 
@@ -616,15 +732,12 @@ Graph.prototype._createKeyBinds = function() {
616
732
  this.mousetrap.bind("pagedown",this._zoomOut.bind(me),"keydown");
617
733
  this.mousetrap.bind("pagedown",this._stopZoom.bind(me), "keyup");
618
734
  }
619
- /*
620
- this.mousetrap.bind("=",this.decreaseClusterLevel.bind(me));
621
- this.mousetrap.bind("-",this.increaseClusterLevel.bind(me));
622
- this.mousetrap.bind("s",this.singleStep.bind(me));
623
- this.mousetrap.bind("h",this.updateClustersDefault.bind(me));
624
- this.mousetrap.bind("c",this._collapseSector.bind(me));
625
- this.mousetrap.bind("f",this.toggleFreeze.bind(me));
626
- */
627
- }
735
+
736
+ if (this.constants.dataManipulation.enabled == true) {
737
+ this.mousetrap.bind("escape",this._createManipulatorBar.bind(me));
738
+ this.mousetrap.bind("del",this._deleteSelected.bind(me));
739
+ }
740
+ };
628
741
 
629
742
  /**
630
743
  * Get the pointer location from a touch location
@@ -645,7 +758,7 @@ Graph.prototype._getPointer = function (touch) {
645
758
  * @private
646
759
  */
647
760
  Graph.prototype._onTouch = function (event) {
648
- this.drag.pointer = this._getPointer(event.gesture.touches[0]);
761
+ this.drag.pointer = this._getPointer(event.gesture.center);
649
762
  this.drag.pinched = false;
650
763
  this.pinch.scale = this._getScale();
651
764
 
@@ -657,6 +770,17 @@ Graph.prototype._onTouch = function (event) {
657
770
  * @private
658
771
  */
659
772
  Graph.prototype._onDragStart = function () {
773
+ this._handleDragStart();
774
+ };
775
+
776
+
777
+ /**
778
+ * This function is called by _onDragStart.
779
+ * It is separated out because we can then overload it for the datamanipulation system.
780
+ *
781
+ * @private
782
+ */
783
+ Graph.prototype._handleDragStart = function() {
660
784
  var drag = this.drag;
661
785
  var node = this._getNodeAt(drag.pointer);
662
786
  // note: drag.pointer is set in _onTouch to get the initial touch location
@@ -670,52 +794,65 @@ Graph.prototype._onDragStart = function () {
670
794
  drag.nodeId = node.id;
671
795
  // select the clicked node if not yet selected
672
796
  if (!node.isSelected()) {
673
- this._selectNode(node,false);
797
+ this._selectObject(node,false);
674
798
  }
675
799
 
676
800
  // create an array with the selected nodes and their original location and status
677
- var me = this;
678
- this.selection.forEach(function (id) {
679
- var node = me.nodes[id];
680
- if (node) {
681
- var s = {
682
- id: id,
683
- node: node,
684
-
685
- // store original x, y, xFixed and yFixed, make the node temporarily Fixed
686
- x: node.x,
687
- y: node.y,
688
- xFixed: node.xFixed,
689
- yFixed: node.yFixed
690
- };
691
-
692
- node.xFixed = true;
693
- node.yFixed = true;
694
-
695
- drag.selection.push(s);
801
+ for (var objectId in this.selectionObj) {
802
+ if (this.selectionObj.hasOwnProperty(objectId)) {
803
+ var object = this.selectionObj[objectId];
804
+ if (object instanceof Node) {
805
+ var s = {
806
+ id: object.id,
807
+ node: object,
808
+
809
+ // store original x, y, xFixed and yFixed, make the node temporarily Fixed
810
+ x: object.x,
811
+ y: object.y,
812
+ xFixed: object.xFixed,
813
+ yFixed: object.yFixed
814
+ };
815
+
816
+ object.xFixed = true;
817
+ object.yFixed = true;
818
+
819
+ drag.selection.push(s);
820
+ }
696
821
  }
697
- });
822
+ }
698
823
  }
699
824
  };
700
825
 
826
+
701
827
  /**
702
828
  * handle drag event
703
829
  * @private
704
830
  */
705
831
  Graph.prototype._onDrag = function (event) {
832
+ this._handleOnDrag(event)
833
+ };
834
+
835
+
836
+ /**
837
+ * This function is called by _onDrag.
838
+ * It is separated out because we can then overload it for the datamanipulation system.
839
+ *
840
+ * @private
841
+ */
842
+ Graph.prototype._handleOnDrag = function(event) {
706
843
  if (this.drag.pinched) {
707
844
  return;
708
845
  }
709
846
 
710
- var pointer = this._getPointer(event.gesture.touches[0]);
847
+ var pointer = this._getPointer(event.gesture.center);
711
848
 
712
849
  var me = this,
713
- drag = this.drag,
714
- selection = drag.selection;
850
+ drag = this.drag,
851
+ selection = drag.selection;
715
852
  if (selection && selection.length) {
716
853
  // calculate delta's and new location
717
854
  var deltaX = pointer.x - drag.pointer.x,
718
- deltaY = pointer.y - drag.pointer.y;
855
+ deltaY = pointer.y - drag.pointer.y;
719
856
 
720
857
  // update position of all selected nodes
721
858
  selection.forEach(function (s) {
@@ -730,7 +867,7 @@ Graph.prototype._onDrag = function (event) {
730
867
  }
731
868
  });
732
869
 
733
- // start animation if not yet running
870
+ // start _animationStep if not yet running
734
871
  if (!this.moving) {
735
872
  this.moving = true;
736
873
  this.start();
@@ -742,8 +879,8 @@ Graph.prototype._onDrag = function (event) {
742
879
  var diffY = pointer.y - this.drag.pointer.y;
743
880
 
744
881
  this._setTranslation(
745
- this.drag.translation.x + diffX,
746
- this.drag.translation.y + diffY);
882
+ this.drag.translation.x + diffX,
883
+ this.drag.translation.y + diffY);
747
884
  this._redraw();
748
885
  this.moved = true;
749
886
  }
@@ -770,8 +907,10 @@ Graph.prototype._onDragEnd = function () {
770
907
  * @private
771
908
  */
772
909
  Graph.prototype._onTap = function (event) {
773
- var pointer = this._getPointer(event.gesture.touches[0]);
910
+ var pointer = this._getPointer(event.gesture.center);
911
+ this.pointerPosition = pointer;
774
912
  this._handleTap(pointer);
913
+
775
914
  };
776
915
 
777
916
 
@@ -780,9 +919,8 @@ Graph.prototype._onTap = function (event) {
780
919
  * @private
781
920
  */
782
921
  Graph.prototype._onDoubleTap = function (event) {
783
- var pointer = this._getPointer(event.gesture.touches[0]);
922
+ var pointer = this._getPointer(event.gesture.center);
784
923
  this._handleDoubleTap(pointer);
785
-
786
924
  };
787
925
 
788
926
 
@@ -791,18 +929,19 @@ Graph.prototype._onDoubleTap = function (event) {
791
929
  * @private
792
930
  */
793
931
  Graph.prototype._onHold = function (event) {
794
- var pointer = this._getPointer(event.gesture.touches[0]);
932
+ var pointer = this._getPointer(event.gesture.center);
933
+ this.pointerPosition = pointer;
795
934
  this._handleOnHold(pointer);
796
935
  };
797
936
 
798
937
  /**
799
938
  * handle the release of the screen
800
939
  *
801
- * @param event
802
940
  * @private
803
941
  */
804
942
  Graph.prototype._onRelease = function (event) {
805
- this._handleOnRelease();
943
+ var pointer = this._getPointer(event.gesture.center);
944
+ this._handleOnRelease(pointer);
806
945
  };
807
946
 
808
947
  /**
@@ -848,9 +987,6 @@ Graph.prototype._zoom = function(scale, pointer) {
848
987
  this.areaCenter = {"x" : this._canvasToX(pointer.x),
849
988
  "y" : this._canvasToY(pointer.y)};
850
989
 
851
- // this.areaCenter = {"x" : pointer.x,"y" : pointer.y };
852
- // console.log(translation.x,translation.y,pointer.x,pointer.y,scale);
853
- this.pinch.mousewheelScale = scale;
854
990
  this._setScale(scale);
855
991
  this._setTranslation(tx, ty);
856
992
  this.updateClustersDefault();
@@ -859,6 +995,7 @@ Graph.prototype._zoom = function(scale, pointer) {
859
995
  return scale;
860
996
  };
861
997
 
998
+
862
999
  /**
863
1000
  * Event handler for mouse wheel event, used to zoom the timeline
864
1001
  * See http://adomas.org/javascript-mouse-wheel/
@@ -881,12 +1018,9 @@ Graph.prototype._onMouseWheel = function(event) {
881
1018
  // Basically, delta is now positive if wheel was scrolled up,
882
1019
  // and negative, if wheel was scrolled down.
883
1020
  if (delta) {
884
- if (!('mousewheelScale' in this.pinch)) {
885
- this.pinch.mousewheelScale = 1;
886
- }
887
1021
 
888
1022
  // calculate the new scale
889
- var scale = this.pinch.mousewheelScale;
1023
+ var scale = this._getScale();
890
1024
  var zoom = delta / 10;
891
1025
  if (delta < 0) {
892
1026
  zoom = zoom / (1 - zoom);
@@ -898,10 +1032,7 @@ Graph.prototype._onMouseWheel = function(event) {
898
1032
  var pointer = this._getPointer(gesture.center);
899
1033
 
900
1034
  // apply the new scale
901
- scale = this._zoom(scale, pointer);
902
-
903
- // store the new, applied scale -- this is now done in _zoom
904
- // this.pinch.mousewheelScale = scale;
1035
+ this._zoom(scale, pointer);
905
1036
  }
906
1037
 
907
1038
  // Prevent default actions caused by mouse wheel.
@@ -1008,6 +1139,7 @@ Graph.prototype._checkShowPopup = function (pointer) {
1008
1139
  }
1009
1140
  };
1010
1141
 
1142
+
1011
1143
  /**
1012
1144
  * Check if the popup must be hided, which is the case when the mouse is no
1013
1145
  * longer hovering on the object
@@ -1024,85 +1156,6 @@ Graph.prototype._checkHidePopup = function (pointer) {
1024
1156
  };
1025
1157
 
1026
1158
 
1027
- /**
1028
- * Temporary method to test calculating a hub value for the nodes
1029
- * @param {number} level Maximum number edges between two nodes in order
1030
- * to call them connected. Optional, 1 by default
1031
- * @return {Number[]} connectioncount array with the connection count
1032
- * for each node
1033
- * @private
1034
- */
1035
- Graph.prototype._getConnectionCount = function(level) {
1036
- if (level == undefined) {
1037
- level = 1;
1038
- }
1039
-
1040
- // get the nodes connected to given nodes
1041
- function getConnectedNodes(nodes) {
1042
- var connectedNodes = [];
1043
-
1044
- for (var j = 0, jMax = nodes.length; j < jMax; j++) {
1045
- var node = nodes[j];
1046
-
1047
- // find all nodes connected to this node
1048
- var edges = node.edges;
1049
- for (var i = 0, iMax = edges.length; i < iMax; i++) {
1050
- var edge = edges[i];
1051
- var other = null;
1052
-
1053
- // check if connected
1054
- if (edge.from == node)
1055
- other = edge.to;
1056
- else if (edge.to == node)
1057
- other = edge.from;
1058
-
1059
- // check if the other node is not already in the list with nodes
1060
- var k, kMax;
1061
- if (other) {
1062
- for (k = 0, kMax = nodes.length; k < kMax; k++) {
1063
- if (nodes[k] == other) {
1064
- other = null;
1065
- break;
1066
- }
1067
- }
1068
- }
1069
- if (other) {
1070
- for (k = 0, kMax = connectedNodes.length; k < kMax; k++) {
1071
- if (connectedNodes[k] == other) {
1072
- other = null;
1073
- break;
1074
- }
1075
- }
1076
- }
1077
-
1078
- if (other)
1079
- connectedNodes.push(other);
1080
- }
1081
- }
1082
-
1083
- return connectedNodes;
1084
- }
1085
-
1086
- var connections = [];
1087
- var nodes = this.nodes;
1088
- for (var id in nodes) {
1089
- if (nodes.hasOwnProperty(id)) {
1090
- var c = [nodes[id]];
1091
- for (var l = 0; l < level; l++) {
1092
- c = c.concat(getConnectedNodes(c));
1093
- }
1094
- connections.push(c);
1095
- }
1096
- }
1097
-
1098
- var hubs = [];
1099
- for (var i = 0, len = connections.length; i < len; i++) {
1100
- hubs.push(connections[i].length);
1101
- }
1102
-
1103
- return hubs;
1104
- };
1105
-
1106
1159
  /**
1107
1160
  * Set a new size for the graph
1108
1161
  * @param {string} width Width in pixels or percentage (for example '800px'
@@ -1120,9 +1173,11 @@ Graph.prototype.setSize = function(width, height) {
1120
1173
  this.frame.canvas.width = this.frame.canvas.clientWidth;
1121
1174
  this.frame.canvas.height = this.frame.canvas.clientHeight;
1122
1175
 
1123
- if (this.constants.navigation.enabled == true) {
1124
- this._relocateNavigation();
1176
+ if (this.manipulationDiv !== undefined) {
1177
+ this.manipulationDiv.style.width = this.frame.canvas.clientWidth;
1125
1178
  }
1179
+
1180
+ this.emit('resize', {width:this.frame.canvas.width,height:this.frame.canvas.height});
1126
1181
  };
1127
1182
 
1128
1183
  /**
@@ -1150,7 +1205,7 @@ Graph.prototype._setNodes = function(nodes) {
1150
1205
  if (oldNodesData) {
1151
1206
  // unsubscribe from old dataset
1152
1207
  util.forEach(this.nodesListeners, function (callback, event) {
1153
- oldNodesData.unsubscribe(event, callback);
1208
+ oldNodesData.off(event, callback);
1154
1209
  });
1155
1210
  }
1156
1211
 
@@ -1161,7 +1216,7 @@ Graph.prototype._setNodes = function(nodes) {
1161
1216
  // subscribe to new dataset
1162
1217
  var me = this;
1163
1218
  util.forEach(this.nodesListeners, function (callback, event) {
1164
- me.nodesData.subscribe(event, callback);
1219
+ me.nodesData.on(event, callback);
1165
1220
  });
1166
1221
 
1167
1222
  // draw all new nodes
@@ -1184,13 +1239,11 @@ Graph.prototype._addNodes = function(ids) {
1184
1239
  var node = new Node(data, this.images, this.groups, this.constants);
1185
1240
  this.nodes[id] = node; // note: this may replace an existing node
1186
1241
 
1187
- if (!node.isFixed()) {
1188
- // TODO: position new nodes in a smarter way!
1189
- var radius = this.constants.edges.length * 2;
1190
- var count = ids.length;
1191
- var angle = 2 * Math.PI * (i / count);
1192
- node.x = radius * Math.cos(angle);
1193
- node.y = radius * Math.sin(angle);
1242
+ if ((node.xFixed == false || node.yFixed == false) && this.createNodeOnClick != true) {
1243
+ var radius = 10 * 0.1*ids.length;
1244
+ var angle = 2 * Math.PI * Math.random();
1245
+ if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
1246
+ if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
1194
1247
 
1195
1248
  // note: no not use node.isMoving() here, as that gives the current
1196
1249
  // velocity of the node, which is zero after creation of the node.
@@ -1198,8 +1251,10 @@ Graph.prototype._addNodes = function(ids) {
1198
1251
  }
1199
1252
  }
1200
1253
  this._updateNodeIndexList();
1254
+ this._updateCalculationNodes();
1201
1255
  this._reconnectEdges();
1202
1256
  this._updateValueRange(this.nodes);
1257
+ this.updateLabels();
1203
1258
  };
1204
1259
 
1205
1260
  /**
@@ -1276,7 +1331,7 @@ Graph.prototype._setEdges = function(edges) {
1276
1331
  if (oldEdgesData) {
1277
1332
  // unsubscribe from old dataset
1278
1333
  util.forEach(this.edgesListeners, function (callback, event) {
1279
- oldEdgesData.unsubscribe(event, callback);
1334
+ oldEdgesData.off(event, callback);
1280
1335
  });
1281
1336
  }
1282
1337
 
@@ -1287,7 +1342,7 @@ Graph.prototype._setEdges = function(edges) {
1287
1342
  // subscribe to new dataset
1288
1343
  var me = this;
1289
1344
  util.forEach(this.edgesListeners, function (callback, event) {
1290
- me.edgesData.subscribe(event, callback);
1345
+ me.edgesData.on(event, callback);
1291
1346
  });
1292
1347
 
1293
1348
  // draw all new nodes
@@ -1321,6 +1376,8 @@ Graph.prototype._addEdges = function (ids) {
1321
1376
 
1322
1377
  this.moving = true;
1323
1378
  this._updateValueRange(edges);
1379
+ this._createBezierNodes();
1380
+ this._updateCalculationNodes();
1324
1381
  };
1325
1382
 
1326
1383
  /**
@@ -1349,6 +1406,7 @@ Graph.prototype._updateEdges = function (ids) {
1349
1406
  }
1350
1407
  }
1351
1408
 
1409
+ this._createBezierNodes();
1352
1410
  this.moving = true;
1353
1411
  this._updateValueRange(edges);
1354
1412
  };
@@ -1364,6 +1422,9 @@ Graph.prototype._removeEdges = function (ids) {
1364
1422
  var id = ids[i];
1365
1423
  var edge = edges[id];
1366
1424
  if (edge) {
1425
+ if (edge.via != null) {
1426
+ delete this.sectors['support']['nodes'][edge.via.id];
1427
+ }
1367
1428
  edge.disconnect();
1368
1429
  delete edges[id];
1369
1430
  }
@@ -1371,6 +1432,7 @@ Graph.prototype._removeEdges = function (ids) {
1371
1432
 
1372
1433
  this.moving = true;
1373
1434
  this._updateValueRange(edges);
1435
+ this._updateCalculationNodes();
1374
1436
  };
1375
1437
 
1376
1438
  /**
@@ -1468,14 +1530,13 @@ Graph.prototype._redraw = function() {
1468
1530
 
1469
1531
  this._doInAllSectors("_drawAllSectorNodes",ctx);
1470
1532
  this._doInAllSectors("_drawEdges",ctx);
1471
- this._doInAllSectors("_drawNodes",ctx);
1533
+ this._doInAllSectors("_drawNodes",ctx,false);
1534
+
1535
+ // this._doInSupportSector("_drawNodes",ctx,true);
1536
+ // this._drawTree(ctx,"#F00F0F");
1472
1537
 
1473
1538
  // restore original scaling and translation
1474
1539
  ctx.restore();
1475
-
1476
- if (this.constants.navigation.enabled == true) {
1477
- this._doInNavigationSector("_drawNodes",ctx,true);
1478
- }
1479
1540
  };
1480
1541
 
1481
1542
  /**
@@ -1632,244 +1693,17 @@ Graph.prototype._drawEdges = function(ctx) {
1632
1693
  * @private
1633
1694
  */
1634
1695
  Graph.prototype._doStabilize = function() {
1635
- //var start = new Date();
1636
-
1637
1696
  // find stable position
1638
1697
  var count = 0;
1639
- var vmin = this.constants.minVelocity;
1640
- var stable = false;
1641
- while (!stable && count < this.constants.maxIterations) {
1642
- this._initializeForceCalculation();
1643
- this._discreteStepNodes();
1644
- stable = !this._isMoving(vmin);
1698
+ while (this.moving && count < this.constants.maxIterations) {
1699
+ this._physicsTick();
1645
1700
  count++;
1646
1701
  }
1647
- this.zoomToFit();
1648
-
1649
- // var end = new Date();
1650
1702
 
1651
- // console.log('Stabilized in ' + (end-start) + ' ms, ' + count + ' iterations' ); // TODO: cleanup
1703
+ this.zoomExtent(false,true);
1652
1704
  };
1653
1705
 
1654
1706
 
1655
- /**
1656
- * Before calculating the forces, we check if we need to cluster to keep up performance and we check
1657
- * if there is more than one node. If it is just one node, we dont calculate anything.
1658
- *
1659
- * @private
1660
- */
1661
- Graph.prototype._initializeForceCalculation = function() {
1662
- // stop calculation if there is only one node
1663
- if (this.nodeIndices.length == 1) {
1664
- this.nodes[this.nodeIndices[0]]._setForce(0,0);
1665
- }
1666
- else {
1667
- // if there are too many nodes on screen, we cluster without repositioning
1668
- if (this.nodeIndices.length > this.constants.clustering.clusterThreshold && this.constants.clustering.enabled == true) {
1669
- this.clusterToFit(this.constants.clustering.reduceToNodes, false);
1670
- }
1671
-
1672
- // we now start the force calculation
1673
- this._calculateForces();
1674
- }
1675
- };
1676
-
1677
-
1678
- /**
1679
- * Calculate the external forces acting on the nodes
1680
- * Forces are caused by: edges, repulsing forces between nodes, gravity
1681
- * @private
1682
- */
1683
- Graph.prototype._calculateForces = function() {
1684
- // var screenCenterPos = {"x":(0.5*(this.canvasTopLeft.x + this.canvasBottomRight.x)),
1685
- // "y":(0.5*(this.canvasTopLeft.y + this.canvasBottomRight.y))}
1686
- // create a local edge to the nodes and edges, that is faster
1687
- var dx, dy, angle, distance, fx, fy,
1688
- repulsingForce, springForce, length, edgeLength,
1689
- node, node1, node2, edge, edgeId, i, j, nodeId, xCenter, yCenter;
1690
- var clusterSize;
1691
- var nodes = this.nodes;
1692
- var edges = this.edges;
1693
-
1694
- // Gravity is required to keep separated groups from floating off
1695
- // the forces are reset to zero in this loop by using _setForce instead
1696
- // of _addForce
1697
- var gravity = 0.08 * this.forceFactor;
1698
- for (i = 0; i < this.nodeIndices.length; i++) {
1699
- node = nodes[this.nodeIndices[i]];
1700
- // gravity does not apply when we are in a pocket sector
1701
- if (this._sector() == "default") {
1702
- dx = -node.x;// + screenCenterPos.x;
1703
- dy = -node.y;// + screenCenterPos.y;
1704
-
1705
- angle = Math.atan2(dy, dx);
1706
- fx = Math.cos(angle) * gravity;
1707
- fy = Math.sin(angle) * gravity;
1708
- }
1709
- else {
1710
- fx = 0;
1711
- fy = 0;
1712
- }
1713
- node._setForce(fx, fy);
1714
-
1715
- node.updateDamping(this.nodeIndices.length);
1716
- }
1717
-
1718
- // repulsing forces between nodes
1719
- var minimumDistance = this.constants.nodes.distance,
1720
- steepness = 10; // higher value gives steeper slope of the force around the given minimumDistance
1721
-
1722
-
1723
- // we loop from i over all but the last entree in the array
1724
- // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
1725
- for (i = 0; i < this.nodeIndices.length-1; i++) {
1726
- node1 = nodes[this.nodeIndices[i]];
1727
- for (j = i+1; j < this.nodeIndices.length; j++) {
1728
- node2 = nodes[this.nodeIndices[j]];
1729
- clusterSize = (node1.clusterSize + node2.clusterSize - 2);
1730
- dx = node2.x - node1.x;
1731
- dy = node2.y - node1.y;
1732
- distance = Math.sqrt(dx * dx + dy * dy);
1733
-
1734
-
1735
- // clusters have a larger region of influence
1736
- minimumDistance = (clusterSize == 0) ? this.constants.nodes.distance : (this.constants.nodes.distance * (1 + clusterSize * this.constants.clustering.distanceAmplification));
1737
- if (distance < 2*minimumDistance) { // at 2.0 * the minimum distance, the force is 0.000045
1738
- angle = Math.atan2(dy, dx);
1739
-
1740
- if (distance < 0.5*minimumDistance) { // at 0.5 * the minimum distance, the force is 0.993307
1741
- repulsingForce = 1.0;
1742
- }
1743
- else {
1744
- // TODO: correct factor for repulsing force
1745
- //repulsingForce = 2 * Math.exp(-5 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force
1746
- //repulsingForce = Math.exp(-1 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force
1747
- repulsingForce = 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness)); // TODO: customize the repulsing force
1748
- }
1749
- // amplify the repulsion for clusters.
1750
- repulsingForce *= (clusterSize == 0) ? 1 : 1 + clusterSize * this.constants.clustering.forceAmplification;
1751
- repulsingForce *= this.forceFactor;
1752
-
1753
-
1754
- fx = Math.cos(angle) * repulsingForce;
1755
- fy = Math.sin(angle) * repulsingForce ;
1756
-
1757
- node1._addForce(-fx, -fy);
1758
- node2._addForce(fx, fy);
1759
- }
1760
- }
1761
- }
1762
-
1763
- /*
1764
- // repulsion of the edges on the nodes and
1765
- for (var nodeId in nodes) {
1766
- if (nodes.hasOwnProperty(nodeId)) {
1767
- node = nodes[nodeId];
1768
- for(var edgeId in edges) {
1769
- if (edges.hasOwnProperty(edgeId)) {
1770
- edge = edges[edgeId];
1771
-
1772
- // get the center of the edge
1773
- xCenter = edge.from.x+(edge.to.x - edge.from.x)/2;
1774
- yCenter = edge.from.y+(edge.to.y - edge.from.y)/2;
1775
-
1776
- // calculate normally distributed force
1777
- dx = node.x - xCenter;
1778
- dy = node.y - yCenter;
1779
- distance = Math.sqrt(dx * dx + dy * dy);
1780
- if (distance < 2*minimumDistance) { // at 2.0 * the minimum distance, the force is 0.000045
1781
- angle = Math.atan2(dy, dx);
1782
-
1783
- if (distance < 0.5*minimumDistance) { // at 0.5 * the minimum distance, the force is 0.993307
1784
- repulsingForce = 1.0;
1785
- }
1786
- else {
1787
- // TODO: correct factor for repulsing force
1788
- //var repulsingforce = 2 * Math.exp(-5 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force
1789
- //repulsingforce = Math.exp(-1 * (distance * distance) / (dmin * dmin) ), // TODO: customize the repulsing force
1790
- repulsingForce = 1 / (1 + Math.exp((distance / (minimumDistance / 2) - 1) * steepness)); // TODO: customize the repulsing force
1791
- }
1792
- fx = Math.cos(angle) * repulsingForce;
1793
- fy = Math.sin(angle) * repulsingForce;
1794
- node._addForce(fx, fy);
1795
- edge.from._addForce(-fx/2,-fy/2);
1796
- edge.to._addForce(-fx/2,-fy/2);
1797
- }
1798
- }
1799
- }
1800
- }
1801
- }
1802
- */
1803
-
1804
- // forces caused by the edges, modelled as springs
1805
- for (edgeId in edges) {
1806
- if (edges.hasOwnProperty(edgeId)) {
1807
- edge = edges[edgeId];
1808
- if (edge.connected) {
1809
- // only calculate forces if nodes are in the same sector
1810
- if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
1811
- clusterSize = (edge.to.clusterSize + edge.from.clusterSize - 2);
1812
- dx = (edge.to.x - edge.from.x);
1813
- dy = (edge.to.y - edge.from.y);
1814
- //edgeLength = (edge.from.width + edge.from.height + edge.to.width + edge.to.height)/2 || edge.length; // TODO: dmin
1815
- //edgeLength = (edge.from.width + edge.to.width)/2 || edge.length; // TODO: dmin
1816
- //edgeLength = 20 + ((edge.from.width + edge.to.width) || 0) / 2;
1817
- edgeLength = edge.length;
1818
- // this implies that the edges between big clusters are longer
1819
- edgeLength += clusterSize * this.constants.clustering.edgeGrowth;
1820
- length = Math.sqrt(dx * dx + dy * dy);
1821
- angle = Math.atan2(dy, dx);
1822
-
1823
- springForce = edge.stiffness * (edgeLength - length) * this.forceFactor;
1824
-
1825
- fx = Math.cos(angle) * springForce;
1826
- fy = Math.sin(angle) * springForce;
1827
-
1828
- edge.from._addForce(-fx, -fy);
1829
- edge.to._addForce(fx, fy);
1830
- }
1831
- }
1832
- }
1833
- }
1834
- /*
1835
- // TODO: re-implement repulsion of edges
1836
-
1837
- // repulsing forces between edges
1838
- var minimumDistance = this.constants.edges.distance,
1839
- steepness = 10; // higher value gives steeper slope of the force around the given minimumDistance
1840
- for (var l = 0; l < edges.length; l++) {
1841
- //Keep distance from other edge centers
1842
- for (var l2 = l + 1; l2 < this.edges.length; l2++) {
1843
- //var dmin = (nodes[n].width + nodes[n].height + nodes[n2].width + nodes[n2].height) / 1 || minimumDistance, // TODO: dmin
1844
- //var dmin = (nodes[n].width + nodes[n2].width)/2 || minimumDistance, // TODO: dmin
1845
- //dmin = 40 + ((nodes[n].width/2 + nodes[n2].width/2) || 0),
1846
- var lx = edges[l].from.x+(edges[l].to.x - edges[l].from.x)/2,
1847
- ly = edges[l].from.y+(edges[l].to.y - edges[l].from.y)/2,
1848
- l2x = edges[l2].from.x+(edges[l2].to.x - edges[l2].from.x)/2,
1849
- l2y = edges[l2].from.y+(edges[l2].to.y - edges[l2].from.y)/2,
1850
-
1851
- // calculate normally distributed force
1852
- dx = l2x - lx,
1853
- dy = l2y - ly,
1854
- distance = Math.sqrt(dx * dx + dy * dy),
1855
- angle = Math.atan2(dy, dx),
1856
-
1857
-
1858
- // TODO: correct factor for repulsing force
1859
- //var repulsingforce = 2 * Math.exp(-5 * (distance * distance) / (dmin * dmin) ); // TODO: customize the repulsing force
1860
- //repulsingforce = Math.exp(-1 * (distance * distance) / (dmin * dmin) ), // TODO: customize the repulsing force
1861
- repulsingforce = 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness)), // TODO: customize the repulsing force
1862
- fx = Math.cos(angle) * repulsingforce,
1863
- fy = Math.sin(angle) * repulsingforce;
1864
-
1865
- edges[l].from._addForce(-fx, -fy);
1866
- edges[l].to._addForce(-fx, -fy);
1867
- edges[l2].from._addForce(fx, fy);
1868
- edges[l2].to._addForce(fx, fy);
1869
- }
1870
- }
1871
- */
1872
- };
1873
1707
 
1874
1708
 
1875
1709
  /**
@@ -1879,10 +1713,9 @@ Graph.prototype._calculateForces = function() {
1879
1713
  * @private
1880
1714
  */
1881
1715
  Graph.prototype._isMoving = function(vmin) {
1882
- var vminCorrected = vmin / this.scale;
1883
1716
  var nodes = this.nodes;
1884
1717
  for (var id in nodes) {
1885
- if (nodes.hasOwnProperty(id) && nodes[id].isMoving(vminCorrected)) {
1718
+ if (nodes.hasOwnProperty(id) && nodes[id].isMoving(vmin)) {
1886
1719
  return true;
1887
1720
  }
1888
1721
  }
@@ -1894,208 +1727,197 @@ Graph.prototype._isMoving = function(vmin) {
1894
1727
  * /**
1895
1728
  * Perform one discrete step for all nodes
1896
1729
  *
1897
- * @param interval
1898
1730
  * @private
1899
1731
  */
1900
1732
  Graph.prototype._discreteStepNodes = function() {
1901
- var interval = 0.01;
1733
+ var interval = 0.65;
1902
1734
  var nodes = this.nodes;
1903
- for (var id in nodes) {
1904
- if (nodes.hasOwnProperty(id)) {
1905
- nodes[id].discreteStep(interval);
1735
+ var nodeId;
1736
+
1737
+ if (this.constants.maxVelocity > 0) {
1738
+ for (nodeId in nodes) {
1739
+ if (nodes.hasOwnProperty(nodeId)) {
1740
+ nodes[nodeId].discreteStepLimited(interval, this.constants.maxVelocity);
1741
+ }
1906
1742
  }
1907
1743
  }
1908
-
1909
- var vmin = this.constants.minVelocity;
1910
- this.moving = this._isMoving(vmin);
1744
+ else {
1745
+ for (nodeId in nodes) {
1746
+ if (nodes.hasOwnProperty(nodeId)) {
1747
+ nodes[nodeId].discreteStep(interval);
1748
+ }
1749
+ }
1750
+ }
1751
+ var vminCorrected = this.constants.minVelocity / Math.max(this.scale,0.05);
1752
+ if (vminCorrected > 0.5*this.constants.maxVelocity) {
1753
+ this.moving = true;
1754
+ }
1755
+ else {
1756
+ this.moving = this._isMoving(vminCorrected);
1757
+ }
1911
1758
  };
1912
1759
 
1913
1760
 
1914
-
1915
- /**
1916
- * Start animating nodes and edges
1917
- *
1918
- * @poram {Boolean} runCalculationStep
1919
- */
1920
- Graph.prototype.start = function() {
1761
+ Graph.prototype._physicsTick = function() {
1921
1762
  if (!this.freezeSimulation) {
1922
-
1923
1763
  if (this.moving) {
1924
1764
  this._doInAllActiveSectors("_initializeForceCalculation");
1765
+ if (this.constants.smoothCurves) {
1766
+ this._doInSupportSector("_discreteStepNodes");
1767
+ }
1925
1768
  this._doInAllActiveSectors("_discreteStepNodes");
1926
1769
  this._findCenter(this._getRange())
1927
1770
  }
1928
-
1929
- if (this.moving || this.xIncrement != 0 || this.yIncrement != 0 || this.zoomIncrement != 0) {
1930
- // start animation. only start calculationTimer if it is not already running
1931
- if (!this.timer) {
1932
- var graph = this;
1933
- this.timer = window.setTimeout(function () {
1934
- graph.timer = undefined;
1935
-
1936
- // keyboad movement
1937
- if (graph.xIncrement != 0 || graph.yIncrement != 0) {
1938
- var translation = graph._getTranslation();
1939
- graph._setTranslation(translation.x+graph.xIncrement, translation.y+graph.yIncrement);
1940
- }
1941
- if (graph.zoomIncrement != 0) {
1942
- var center = {
1943
- x: graph.frame.canvas.clientWidth / 2,
1944
- y: graph.frame.canvas.clientHeight / 2
1945
- };
1946
- graph._zoom(graph.scale*(1 + graph.zoomIncrement), center);
1947
- }
1948
-
1949
- graph.start();
1950
- graph._redraw();
1951
-
1952
- //this.end = window.performance.now();
1953
- //this.time = this.end - this.startTime;
1954
- //console.log('refresh time: ' + this.time);
1955
- //this.startTime = window.performance.now();
1956
-
1957
- }, this.renderTimestep);
1958
- }
1959
- }
1960
- else {
1961
- this._redraw();
1962
- }
1963
1771
  }
1964
1772
  };
1965
1773
 
1966
1774
 
1775
+ /**
1776
+ * This function runs one step of the animation. It calls an x amount of physics ticks and one render tick.
1777
+ * It reschedules itself at the beginning of the function
1778
+ *
1779
+ * @private
1780
+ */
1781
+ Graph.prototype._animationStep = function() {
1782
+ // reset the timer so a new scheduled animation step can be set
1783
+ this.timer = undefined;
1784
+ // handle the keyboad movement
1785
+ this._handleNavigation();
1967
1786
 
1787
+ // this schedules a new animation step
1788
+ this.start();
1968
1789
 
1969
- Graph.prototype.singleStep = function() {
1970
- if (this.moving) {
1971
- this._initializeForceCalculation();
1972
- this._discreteStepNodes();
1790
+ // start the physics simulation
1791
+ var calculationTime = Date.now();
1792
+ var maxSteps = 1;
1793
+ this._physicsTick();
1794
+ var timeRequired = Date.now() - calculationTime;
1795
+ while (timeRequired < (this.renderTimestep - this.renderTime) && maxSteps < this.maxRenderSteps) {
1796
+ this._physicsTick();
1797
+ timeRequired = Date.now() - calculationTime;
1798
+ maxSteps++;
1973
1799
 
1974
- var vmin = this.constants.minVelocity;
1975
- this.moving = this._isMoving(vmin);
1976
- this._redraw();
1977
1800
  }
1978
- };
1979
-
1980
-
1981
1801
 
1982
- /**
1983
- * Freeze the animation
1984
- */
1985
- Graph.prototype.toggleFreeze = function() {
1986
- if (this.freezeSimulation == false) {
1987
- this.freezeSimulation = true;
1988
- }
1989
- else {
1990
- this.freezeSimulation = false;
1991
- this.start();
1992
- }
1802
+ // start the rendering process
1803
+ var renderTime = Date.now();
1804
+ this._redraw();
1805
+ this.renderTime = Date.now() - renderTime;
1993
1806
  };
1994
1807
 
1995
- /**
1996
- * Mixin the cluster system and initialize the parameters required.
1997
- *
1998
- * @private
1999
- */
2000
- Graph.prototype._loadClusterSystem = function() {
2001
- this.clusterSession = 0;
2002
- this.hubThreshold = 5;
2003
-
2004
- for (var mixinFunction in ClusterMixin) {
2005
- if (ClusterMixin.hasOwnProperty(mixinFunction)) {
2006
- Graph.prototype[mixinFunction] = ClusterMixin[mixinFunction];
2007
- }
2008
- }
1808
+ if (typeof window !== 'undefined') {
1809
+ window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
1810
+ window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
2009
1811
  }
2010
1812
 
2011
1813
  /**
2012
- * Mixin the sector system and initialize the parameters required
1814
+ * Schedule a animation step with the refreshrate interval.
2013
1815
  *
2014
- * @private
1816
+ * @poram {Boolean} runCalculationStep
2015
1817
  */
2016
- Graph.prototype._loadSectorSystem = function() {
2017
- this.sectors = {};
2018
- this.activeSector = ["default"];
2019
- this.sectors["active"] = {};
2020
- this.sectors["active"]["default"] = {"nodes":{},
2021
- "edges":{},
2022
- "nodeIndices":[],
2023
- "formationScale": 1.0,
2024
- "drawingNode": undefined};
2025
- this.sectors["frozen"] = {};
2026
- this.sectors["navigation"] = {"nodes":{},
2027
- "edges":{},
2028
- "nodeIndices":[],
2029
- "formationScale": 1.0,
2030
- "drawingNode": undefined};
2031
-
2032
- this.nodeIndices = this.sectors["active"]["default"]["nodeIndices"]; // the node indices list is used to speed up the computation of the repulsion fields
2033
- for (var mixinFunction in SectorMixin) {
2034
- if (SectorMixin.hasOwnProperty(mixinFunction)) {
2035
- Graph.prototype[mixinFunction] = SectorMixin[mixinFunction];
1818
+ Graph.prototype.start = function() {
1819
+ if (this.moving || this.xIncrement != 0 || this.yIncrement != 0 || this.zoomIncrement != 0) {
1820
+ if (!this.timer) {
1821
+ this.timer = window.requestAnimationFrame(this._animationStep.bind(this), this.renderTimestep); // wait this.renderTimeStep milliseconds and perform the animation step function
2036
1822
  }
2037
1823
  }
1824
+ else {
1825
+ this._redraw();
1826
+ }
2038
1827
  };
2039
1828
 
2040
1829
 
2041
1830
  /**
2042
- * Mixin the selection system and initialize the parameters required
1831
+ * Move the graph according to the keyboard presses.
2043
1832
  *
2044
1833
  * @private
2045
1834
  */
2046
- Graph.prototype._loadSelectionSystem = function() {
2047
- this.selection = [];
2048
- this.selectionObj = {};
2049
-
2050
- for (var mixinFunction in SelectionMixin) {
2051
- if (SelectionMixin.hasOwnProperty(mixinFunction)) {
2052
- Graph.prototype[mixinFunction] = SelectionMixin[mixinFunction];
2053
- }
1835
+ Graph.prototype._handleNavigation = function() {
1836
+ if (this.xIncrement != 0 || this.yIncrement != 0) {
1837
+ var translation = this._getTranslation();
1838
+ this._setTranslation(translation.x+this.xIncrement, translation.y+this.yIncrement);
2054
1839
  }
2055
- }
1840
+ if (this.zoomIncrement != 0) {
1841
+ var center = {
1842
+ x: this.frame.canvas.clientWidth / 2,
1843
+ y: this.frame.canvas.clientHeight / 2
1844
+ };
1845
+ this._zoom(this.scale*(1 + this.zoomIncrement), center);
1846
+ }
1847
+ };
2056
1848
 
2057
1849
 
2058
1850
  /**
2059
- * Mixin the navigation (User Interface) system and initialize the parameters required
2060
- *
2061
- * @private
1851
+ * Freeze the _animationStep
2062
1852
  */
2063
- Graph.prototype._loadNavigationControls = function() {
2064
- for (var mixinFunction in NavigationMixin) {
2065
- if (NavigationMixin.hasOwnProperty(mixinFunction)) {
2066
- Graph.prototype[mixinFunction] = NavigationMixin[mixinFunction];
2067
- }
1853
+ Graph.prototype.toggleFreeze = function() {
1854
+ if (this.freezeSimulation == false) {
1855
+ this.freezeSimulation = true;
2068
1856
  }
2069
-
2070
- if (this.constants.navigation.enabled == true) {
2071
- this._loadNavigationElements();
1857
+ else {
1858
+ this.freezeSimulation = false;
1859
+ this.start();
2072
1860
  }
2073
- }
2074
-
2075
- /**
2076
- * this function exists to avoid errors when not loading the navigation system
2077
- */
2078
- Graph.prototype._relocateNavigation = function() {
2079
- // empty, is overloaded by navigation system
2080
- }
2081
-
2082
- /**
2083
- * * this function exists to avoid errors when not loading the navigation system
2084
- */
2085
- Graph.prototype._unHighlightAll = function() {
2086
- // empty, is overloaded by the navigation system
2087
- }
2088
-
2089
-
2090
-
2091
-
1861
+ };
2092
1862
 
2093
1863
 
2094
1864
 
1865
+ Graph.prototype._configureSmoothCurves = function(disableStart) {
1866
+ if (disableStart === undefined) {
1867
+ disableStart = true;
1868
+ }
2095
1869
 
1870
+ if (this.constants.smoothCurves == true) {
1871
+ this._createBezierNodes();
1872
+ }
1873
+ else {
1874
+ // delete the support nodes
1875
+ this.sectors['support']['nodes'] = {};
1876
+ for (var edgeId in this.edges) {
1877
+ if (this.edges.hasOwnProperty(edgeId)) {
1878
+ this.edges[edgeId].smooth = false;
1879
+ this.edges[edgeId].via = null;
1880
+ }
1881
+ }
1882
+ }
1883
+ this._updateCalculationNodes();
1884
+ if (!disableStart) {
1885
+ this.moving = true;
1886
+ this.start();
1887
+ }
1888
+ };
2096
1889
 
1890
+ Graph.prototype._createBezierNodes = function() {
1891
+ if (this.constants.smoothCurves == true) {
1892
+ for (var edgeId in this.edges) {
1893
+ if (this.edges.hasOwnProperty(edgeId)) {
1894
+ var edge = this.edges[edgeId];
1895
+ if (edge.via == null) {
1896
+ edge.smooth = true;
1897
+ var nodeId = "edgeId:".concat(edge.id);
1898
+ this.sectors['support']['nodes'][nodeId] = new Node(
1899
+ {id:nodeId,
1900
+ mass:1,
1901
+ shape:'circle',
1902
+ internalMultiplier:1
1903
+ },{},{},this.constants);
1904
+ edge.via = this.sectors['support']['nodes'][nodeId];
1905
+ edge.via.parentEdgeId = edge.id;
1906
+ edge.positionBezierNode();
1907
+ }
1908
+ }
1909
+ }
1910
+ }
1911
+ };
2097
1912
 
2098
1913
 
1914
+ Graph.prototype._initializeMixinLoaders = function () {
1915
+ for (var mixinFunction in graphMixinLoaders) {
1916
+ if (graphMixinLoaders.hasOwnProperty(mixinFunction)) {
1917
+ Graph.prototype[mixinFunction] = graphMixinLoaders[mixinFunction];
1918
+ }
1919
+ }
1920
+ };
2099
1921
 
2100
1922
 
2101
1923