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.
- checksums.yaml +5 -13
- data/lib/vis/rails/version.rb +1 -1
- data/vendor/assets/component/emitter.js +162 -0
- data/vendor/assets/javascripts/vis.js +1 -0
- data/vendor/assets/vis/DataSet.js +8 -2
- data/vendor/assets/vis/DataView.js +8 -4
- data/vendor/assets/vis/graph/Edge.js +210 -78
- data/vendor/assets/vis/graph/Graph.js +474 -652
- data/vendor/assets/vis/graph/Node.js +119 -82
- data/vendor/assets/vis/graph/css/graph-manipulation.css +128 -0
- data/vendor/assets/vis/graph/css/graph-navigation.css +62 -0
- data/vendor/assets/vis/graph/graphMixins/ClusterMixin.js +1141 -0
- data/vendor/assets/vis/graph/graphMixins/HierarchicalLayoutMixin.js +296 -0
- data/vendor/assets/vis/graph/graphMixins/ManipulationMixin.js +433 -0
- data/vendor/assets/vis/graph/graphMixins/MixinLoader.js +201 -0
- data/vendor/assets/vis/graph/graphMixins/NavigationMixin.js +173 -0
- data/vendor/assets/vis/graph/graphMixins/SectorsMixin.js +552 -0
- data/vendor/assets/vis/graph/graphMixins/SelectionMixin.js +558 -0
- data/vendor/assets/vis/graph/graphMixins/physics/BarnesHut.js +373 -0
- data/vendor/assets/vis/graph/graphMixins/physics/HierarchialRepulsion.js +64 -0
- data/vendor/assets/vis/graph/graphMixins/physics/PhysicsMixin.js +513 -0
- data/vendor/assets/vis/graph/graphMixins/physics/Repulsion.js +66 -0
- data/vendor/assets/vis/graph/img/acceptDeleteIcon.png +0 -0
- data/vendor/assets/vis/graph/img/addNodeIcon.png +0 -0
- data/vendor/assets/vis/graph/img/backIcon.png +0 -0
- data/vendor/assets/vis/graph/img/connectIcon.png +0 -0
- data/vendor/assets/vis/graph/img/cross.png +0 -0
- data/vendor/assets/vis/graph/img/cross2.png +0 -0
- data/vendor/assets/vis/graph/img/deleteIcon.png +0 -0
- data/vendor/assets/vis/graph/img/downArrow.png +0 -0
- data/vendor/assets/vis/graph/img/editIcon.png +0 -0
- data/vendor/assets/vis/graph/img/leftArrow.png +0 -0
- data/vendor/assets/vis/graph/img/rightArrow.png +0 -0
- data/vendor/assets/vis/graph/img/upArrow.png +0 -0
- data/vendor/assets/vis/module/exports.js +0 -2
- data/vendor/assets/vis/module/header.js +2 -2
- data/vendor/assets/vis/module/imports.js +1 -2
- data/vendor/assets/vis/timeline/Controller.js +56 -45
- data/vendor/assets/vis/timeline/Range.js +68 -62
- data/vendor/assets/vis/timeline/Stack.js +11 -13
- data/vendor/assets/vis/timeline/TimeStep.js +43 -38
- data/vendor/assets/vis/timeline/Timeline.js +215 -93
- data/vendor/assets/vis/timeline/component/Component.js +19 -3
- data/vendor/assets/vis/timeline/component/CurrentTime.js +1 -1
- data/vendor/assets/vis/timeline/component/CustomTime.js +39 -120
- data/vendor/assets/vis/timeline/component/GroupSet.js +35 -1
- data/vendor/assets/vis/timeline/component/ItemSet.js +272 -9
- data/vendor/assets/vis/timeline/component/RootPanel.js +59 -47
- data/vendor/assets/vis/timeline/component/TimeAxis.js +10 -0
- data/vendor/assets/vis/timeline/component/css/item.css +53 -22
- data/vendor/assets/vis/timeline/component/item/Item.js +40 -5
- data/vendor/assets/vis/timeline/component/item/ItemBox.js +3 -1
- data/vendor/assets/vis/timeline/component/item/ItemPoint.js +3 -1
- data/vendor/assets/vis/timeline/component/item/ItemRange.js +67 -3
- data/vendor/assets/vis/timeline/component/item/ItemRangeOverflow.js +37 -9
- data/vendor/assets/vis/timeline/img/delete.png +0 -0
- data/vendor/assets/vis/util.js +169 -30
- 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
|
-
|
18
|
-
|
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.
|
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
|
-
|
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
|
-
|
39
|
-
|
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: '#
|
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:
|
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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
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
|
-
|
154
|
-
|
208
|
+
graph._addNodes(params.items);
|
209
|
+
graph.start();
|
155
210
|
},
|
156
211
|
'update': function (event, params) {
|
157
|
-
|
158
|
-
|
212
|
+
graph._updateNodes(params.items);
|
213
|
+
graph.start();
|
159
214
|
},
|
160
215
|
'remove': function (event, params) {
|
161
|
-
|
162
|
-
|
216
|
+
graph._removeNodes(params.items);
|
217
|
+
graph.start();
|
163
218
|
}
|
164
219
|
};
|
165
220
|
this.edgesListeners = {
|
166
221
|
'add': function (event, params) {
|
167
|
-
|
168
|
-
|
222
|
+
graph._addEdges(params.items);
|
223
|
+
graph.start();
|
169
224
|
},
|
170
225
|
'update': function (event, params) {
|
171
|
-
|
172
|
-
|
226
|
+
graph._updateEdges(params.items);
|
227
|
+
graph.start();
|
173
228
|
},
|
174
229
|
'remove': function (event, params) {
|
175
|
-
|
176
|
-
|
230
|
+
graph._removeEdges(params.items);
|
231
|
+
graph.start();
|
177
232
|
}
|
178
233
|
};
|
179
234
|
|
180
|
-
// properties
|
181
|
-
this.moving =
|
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
|
-
//
|
188
|
-
this.
|
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
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
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
|
-
|
244
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
388
|
+
|
303
389
|
this._setScale(zoomLevel);
|
304
390
|
this._centerGraph(range);
|
305
|
-
|
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 (
|
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 (
|
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 (
|
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
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
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.
|
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.
|
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
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
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.
|
847
|
+
var pointer = this._getPointer(event.gesture.center);
|
711
848
|
|
712
849
|
var me = this,
|
713
|
-
|
714
|
-
|
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
|
-
|
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
|
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
|
-
|
746
|
-
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
-
|
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.
|
1124
|
-
this.
|
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.
|
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.
|
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 (
|
1188
|
-
|
1189
|
-
var
|
1190
|
-
|
1191
|
-
|
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.
|
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.
|
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
|
-
|
1640
|
-
|
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
|
-
|
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(
|
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.
|
1733
|
+
var interval = 0.65;
|
1902
1734
|
var nodes = this.nodes;
|
1903
|
-
|
1904
|
-
|
1905
|
-
|
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
|
-
|
1910
|
-
|
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
|
-
|
1970
|
-
|
1971
|
-
|
1972
|
-
|
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
|
-
|
1984
|
-
|
1985
|
-
|
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
|
-
|
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
|
-
*
|
1814
|
+
* Schedule a animation step with the refreshrate interval.
|
2013
1815
|
*
|
2014
|
-
* @
|
1816
|
+
* @poram {Boolean} runCalculationStep
|
2015
1817
|
*/
|
2016
|
-
Graph.prototype.
|
2017
|
-
this.
|
2018
|
-
|
2019
|
-
|
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
|
-
*
|
1831
|
+
* Move the graph according to the keyboard presses.
|
2043
1832
|
*
|
2044
1833
|
* @private
|
2045
1834
|
*/
|
2046
|
-
Graph.prototype.
|
2047
|
-
this.
|
2048
|
-
|
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
|
-
*
|
2060
|
-
*
|
2061
|
-
* @private
|
1851
|
+
* Freeze the _animationStep
|
2062
1852
|
*/
|
2063
|
-
Graph.prototype.
|
2064
|
-
|
2065
|
-
|
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
|
-
|
2071
|
-
this.
|
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
|
|