vis-rails 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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