vis-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.gitmodules +3 -0
  4. data/.project +11 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +202 -0
  7. data/README.md +29 -0
  8. data/Rakefile +1 -0
  9. data/lib/vis/rails/engine.rb +6 -0
  10. data/lib/vis/rails/version.rb +5 -0
  11. data/lib/vis/rails.rb +7 -0
  12. data/vendor/assets/javascripts/vis.js +1 -0
  13. data/vendor/assets/stylesheets/vis.css +3 -0
  14. data/vendor/assets/vis/DataSet.js +936 -0
  15. data/vendor/assets/vis/DataView.js +281 -0
  16. data/vendor/assets/vis/EventBus.js +89 -0
  17. data/vendor/assets/vis/events.js +116 -0
  18. data/vendor/assets/vis/graph/ClusterMixin.js +1019 -0
  19. data/vendor/assets/vis/graph/Edge.js +620 -0
  20. data/vendor/assets/vis/graph/Graph.js +2111 -0
  21. data/vendor/assets/vis/graph/Groups.js +80 -0
  22. data/vendor/assets/vis/graph/Images.js +41 -0
  23. data/vendor/assets/vis/graph/NavigationMixin.js +245 -0
  24. data/vendor/assets/vis/graph/Node.js +978 -0
  25. data/vendor/assets/vis/graph/Popup.js +105 -0
  26. data/vendor/assets/vis/graph/SectorsMixin.js +547 -0
  27. data/vendor/assets/vis/graph/SelectionMixin.js +515 -0
  28. data/vendor/assets/vis/graph/dotparser.js +829 -0
  29. data/vendor/assets/vis/graph/img/downarrow.png +0 -0
  30. data/vendor/assets/vis/graph/img/leftarrow.png +0 -0
  31. data/vendor/assets/vis/graph/img/minus.png +0 -0
  32. data/vendor/assets/vis/graph/img/plus.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/graph/img/zoomExtends.png +0 -0
  36. data/vendor/assets/vis/graph/shapes.js +225 -0
  37. data/vendor/assets/vis/module/exports.js +68 -0
  38. data/vendor/assets/vis/module/header.js +24 -0
  39. data/vendor/assets/vis/module/imports.js +32 -0
  40. data/vendor/assets/vis/shim.js +252 -0
  41. data/vendor/assets/vis/timeline/Controller.js +172 -0
  42. data/vendor/assets/vis/timeline/Range.js +553 -0
  43. data/vendor/assets/vis/timeline/Stack.js +192 -0
  44. data/vendor/assets/vis/timeline/TimeStep.js +449 -0
  45. data/vendor/assets/vis/timeline/Timeline.js +476 -0
  46. data/vendor/assets/vis/timeline/component/Component.js +148 -0
  47. data/vendor/assets/vis/timeline/component/ContentPanel.js +113 -0
  48. data/vendor/assets/vis/timeline/component/CurrentTime.js +101 -0
  49. data/vendor/assets/vis/timeline/component/CustomTime.js +255 -0
  50. data/vendor/assets/vis/timeline/component/Group.js +129 -0
  51. data/vendor/assets/vis/timeline/component/GroupSet.js +546 -0
  52. data/vendor/assets/vis/timeline/component/ItemSet.js +612 -0
  53. data/vendor/assets/vis/timeline/component/Panel.js +112 -0
  54. data/vendor/assets/vis/timeline/component/RootPanel.js +215 -0
  55. data/vendor/assets/vis/timeline/component/TimeAxis.js +522 -0
  56. data/vendor/assets/vis/timeline/component/css/currenttime.css +5 -0
  57. data/vendor/assets/vis/timeline/component/css/customtime.css +6 -0
  58. data/vendor/assets/vis/timeline/component/css/groupset.css +59 -0
  59. data/vendor/assets/vis/timeline/component/css/item.css +93 -0
  60. data/vendor/assets/vis/timeline/component/css/itemset.css +17 -0
  61. data/vendor/assets/vis/timeline/component/css/panel.css +14 -0
  62. data/vendor/assets/vis/timeline/component/css/timeaxis.css +41 -0
  63. data/vendor/assets/vis/timeline/component/css/timeline.css +2 -0
  64. data/vendor/assets/vis/timeline/component/item/Item.js +81 -0
  65. data/vendor/assets/vis/timeline/component/item/ItemBox.js +302 -0
  66. data/vendor/assets/vis/timeline/component/item/ItemPoint.js +237 -0
  67. data/vendor/assets/vis/timeline/component/item/ItemRange.js +251 -0
  68. data/vendor/assets/vis/timeline/component/item/ItemRangeOverflow.js +91 -0
  69. data/vendor/assets/vis/util.js +673 -0
  70. data/vis-rails.gemspec +47 -0
  71. metadata +142 -0
@@ -0,0 +1,978 @@
1
+ /**
2
+ * @class Node
3
+ * A node. A node can be connected to other nodes via one or multiple edges.
4
+ * @param {object} properties An object containing properties for the node. All
5
+ * properties are optional, except for the id.
6
+ * {number} id Id of the node. Required
7
+ * {string} label Text label for the node
8
+ * {number} x Horizontal position of the node
9
+ * {number} y Vertical position of the node
10
+ * {string} shape Node shape, available:
11
+ * "database", "circle", "ellipse",
12
+ * "box", "image", "text", "dot",
13
+ * "star", "triangle", "triangleDown",
14
+ * "square"
15
+ * {string} image An image url
16
+ * {string} title An title text, can be HTML
17
+ * {anytype} group A group name or number
18
+ * @param {Graph.Images} imagelist A list with images. Only needed
19
+ * when the node has an image
20
+ * @param {Graph.Groups} grouplist A list with groups. Needed for
21
+ * retrieving group properties
22
+ * @param {Object} constants An object with default values for
23
+ * example for the color
24
+ */
25
+ function Node(properties, imagelist, grouplist, constants) {
26
+ this.selected = false;
27
+
28
+ this.edges = []; // all edges connected to this node
29
+ this.dynamicEdges = [];
30
+ this.reroutedEdges = {};
31
+ this.group = constants.nodes.group;
32
+
33
+ this.fontSize = constants.nodes.fontSize;
34
+ this.fontFace = constants.nodes.fontFace;
35
+ this.fontColor = constants.nodes.fontColor;
36
+
37
+ this.color = constants.nodes.color;
38
+
39
+ // set defaults for the properties
40
+ this.id = undefined;
41
+ this.shape = constants.nodes.shape;
42
+ this.image = constants.nodes.image;
43
+ this.x = 0;
44
+ this.y = 0;
45
+ this.xFixed = false;
46
+ this.yFixed = false;
47
+ this.horizontalAlignLeft = true; // these are for the navigation controls
48
+ this.verticalAlignTop = true; // these are for the navigation controls
49
+ this.radius = constants.nodes.radius;
50
+ this.baseRadiusValue = constants.nodes.radius;
51
+ this.radiusFixed = false;
52
+ this.radiusMin = constants.nodes.radiusMin;
53
+ this.radiusMax = constants.nodes.radiusMax;
54
+
55
+ this.imagelist = imagelist;
56
+
57
+ this.grouplist = grouplist;
58
+
59
+ this.setProperties(properties, constants);
60
+
61
+ // creating the variables for clustering
62
+ this.resetCluster();
63
+ this.dynamicEdgesLength = 0;
64
+ this.clusterSession = 0;
65
+ this.clusterSizeWidthFactor = constants.clustering.nodeScaling.width;
66
+ this.clusterSizeHeightFactor = constants.clustering.nodeScaling.height;
67
+ this.clusterSizeRadiusFactor = constants.clustering.nodeScaling.radius;
68
+
69
+ // mass, force, velocity
70
+ this.mass = 1; // kg (mass is adjusted for the number of connected edges)
71
+ this.fx = 0.0; // external force x
72
+ this.fy = 0.0; // external force y
73
+ this.vx = 0.0; // velocity x
74
+ this.vy = 0.0; // velocity y
75
+ this.minForce = constants.minForce;
76
+ this.damping = 0.9;
77
+ this.dampingFactor = 75;
78
+
79
+ this.graphScaleInv = 1;
80
+ this.canvasTopLeft = {"x": -300, "y": -300};
81
+ this.canvasBottomRight = {"x": 300, "y": 300};
82
+ }
83
+
84
+ /**
85
+ * (re)setting the clustering variables and objects
86
+ */
87
+ Node.prototype.resetCluster = function() {
88
+ // clustering variables
89
+ this.formationScale = undefined; // this is used to determine when to open the cluster
90
+ this.clusterSize = 1; // this signifies the total amount of nodes in this cluster
91
+ this.containedNodes = {};
92
+ this.containedEdges = {};
93
+ this.clusterSessions = [];
94
+ };
95
+
96
+ /**
97
+ * Attach a edge to the node
98
+ * @param {Edge} edge
99
+ */
100
+ Node.prototype.attachEdge = function(edge) {
101
+ if (this.edges.indexOf(edge) == -1) {
102
+ this.edges.push(edge);
103
+ }
104
+ if (this.dynamicEdges.indexOf(edge) == -1) {
105
+ this.dynamicEdges.push(edge);
106
+ }
107
+ this.dynamicEdgesLength = this.dynamicEdges.length;
108
+ this._updateMass();
109
+ };
110
+
111
+ /**
112
+ * Detach a edge from the node
113
+ * @param {Edge} edge
114
+ */
115
+ Node.prototype.detachEdge = function(edge) {
116
+ var index = this.edges.indexOf(edge);
117
+ if (index != -1) {
118
+ this.edges.splice(index, 1);
119
+ this.dynamicEdges.splice(index, 1);
120
+ }
121
+ this.dynamicEdgesLength = this.dynamicEdges.length;
122
+ this._updateMass();
123
+ };
124
+
125
+ /**
126
+ * Update the nodes mass, which is determined by the number of edges connecting
127
+ * to it (more edges -> heavier node).
128
+ * @private
129
+ */
130
+ Node.prototype._updateMass = function() {
131
+ this.mass = 1 + 0.6 * this.edges.length; // kg
132
+ };
133
+
134
+ /**
135
+ * Set or overwrite properties for the node
136
+ * @param {Object} properties an object with properties
137
+ * @param {Object} constants and object with default, global properties
138
+ */
139
+ Node.prototype.setProperties = function(properties, constants) {
140
+ if (!properties) {
141
+ return;
142
+ }
143
+ this.originalLabel = undefined;
144
+ // basic properties
145
+ if (properties.id !== undefined) {this.id = properties.id;}
146
+ if (properties.label !== undefined) {this.label = properties.label; this.originalLabel = properties.label;}
147
+ if (properties.title !== undefined) {this.title = properties.title;}
148
+ if (properties.group !== undefined) {this.group = properties.group;}
149
+ if (properties.x !== undefined) {this.x = properties.x;}
150
+ if (properties.y !== undefined) {this.y = properties.y;}
151
+ if (properties.value !== undefined) {this.value = properties.value;}
152
+
153
+ // navigation controls properties
154
+ if (properties.horizontalAlignLeft !== undefined) {this.horizontalAlignLeft = properties.horizontalAlignLeft;}
155
+ if (properties.verticalAlignTop !== undefined) {this.verticalAlignTop = properties.verticalAlignTop;}
156
+ if (properties.triggerFunction !== undefined) {this.triggerFunction = properties.triggerFunction;}
157
+
158
+ if (this.id === undefined) {
159
+ throw "Node must have an id";
160
+ }
161
+
162
+ // copy group properties
163
+ if (this.group) {
164
+ var groupObj = this.grouplist.get(this.group);
165
+ for (var prop in groupObj) {
166
+ if (groupObj.hasOwnProperty(prop)) {
167
+ this[prop] = groupObj[prop];
168
+ }
169
+ }
170
+ }
171
+
172
+ // individual shape properties
173
+ if (properties.shape !== undefined) {this.shape = properties.shape;}
174
+ if (properties.image !== undefined) {this.image = properties.image;}
175
+ if (properties.radius !== undefined) {this.radius = properties.radius;}
176
+ if (properties.color !== undefined) {this.color = Node.parseColor(properties.color);}
177
+
178
+ if (properties.fontColor !== undefined) {this.fontColor = properties.fontColor;}
179
+ if (properties.fontSize !== undefined) {this.fontSize = properties.fontSize;}
180
+ if (properties.fontFace !== undefined) {this.fontFace = properties.fontFace;}
181
+
182
+ if (this.image !== undefined) {
183
+ if (this.imagelist) {
184
+ this.imageObj = this.imagelist.load(this.image);
185
+ }
186
+ else {
187
+ throw "No imagelist provided";
188
+ }
189
+ }
190
+
191
+ this.xFixed = this.xFixed || (properties.x !== undefined);
192
+ this.yFixed = this.yFixed || (properties.y !== undefined);
193
+ this.radiusFixed = this.radiusFixed || (properties.radius !== undefined);
194
+
195
+ if (this.shape == 'image') {
196
+ this.radiusMin = constants.nodes.widthMin;
197
+ this.radiusMax = constants.nodes.widthMax;
198
+ }
199
+
200
+ // choose draw method depending on the shape
201
+ switch (this.shape) {
202
+ case 'database': this.draw = this._drawDatabase; this.resize = this._resizeDatabase; break;
203
+ case 'box': this.draw = this._drawBox; this.resize = this._resizeBox; break;
204
+ case 'circle': this.draw = this._drawCircle; this.resize = this._resizeCircle; break;
205
+ case 'ellipse': this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break;
206
+ // TODO: add diamond shape
207
+ case 'image': this.draw = this._drawImage; this.resize = this._resizeImage; break;
208
+ case 'text': this.draw = this._drawText; this.resize = this._resizeText; break;
209
+ case 'dot': this.draw = this._drawDot; this.resize = this._resizeShape; break;
210
+ case 'square': this.draw = this._drawSquare; this.resize = this._resizeShape; break;
211
+ case 'triangle': this.draw = this._drawTriangle; this.resize = this._resizeShape; break;
212
+ case 'triangleDown': this.draw = this._drawTriangleDown; this.resize = this._resizeShape; break;
213
+ case 'star': this.draw = this._drawStar; this.resize = this._resizeShape; break;
214
+ default: this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break;
215
+ }
216
+ // reset the size of the node, this can be changed
217
+ this._reset();
218
+ };
219
+
220
+ /**
221
+ * Parse a color property into an object with border, background, and
222
+ * hightlight colors
223
+ * @param {Object | String} color
224
+ * @return {Object} colorObject
225
+ */
226
+ Node.parseColor = function(color) {
227
+ var c;
228
+ if (util.isString(color)) {
229
+ c = {
230
+ border: color,
231
+ background: color,
232
+ highlight: {
233
+ border: color,
234
+ background: color
235
+ }
236
+ };
237
+ // TODO: automatically generate a nice highlight color
238
+ }
239
+ else {
240
+ c = {};
241
+ c.background = color.background || 'white';
242
+ c.border = color.border || c.background;
243
+
244
+ if (util.isString(color.highlight)) {
245
+ c.highlight = {
246
+ border: color.highlight,
247
+ background: color.highlight
248
+ }
249
+ }
250
+ else {
251
+ c.highlight = {};
252
+ c.highlight.background = color.highlight && color.highlight.background || c.background;
253
+ c.highlight.border = color.highlight && color.highlight.border || c.border;
254
+ }
255
+ }
256
+
257
+ return c;
258
+ };
259
+
260
+ /**
261
+ * select this node
262
+ */
263
+ Node.prototype.select = function() {
264
+ this.selected = true;
265
+ this._reset();
266
+ };
267
+
268
+ /**
269
+ * unselect this node
270
+ */
271
+ Node.prototype.unselect = function() {
272
+ this.selected = false;
273
+ this._reset();
274
+ };
275
+
276
+
277
+ /**
278
+ * Reset the calculated size of the node, forces it to recalculate its size
279
+ */
280
+ Node.prototype.clearSizeCache = function() {
281
+ this._reset();
282
+ };
283
+
284
+ /**
285
+ * Reset the calculated size of the node, forces it to recalculate its size
286
+ * @private
287
+ */
288
+ Node.prototype._reset = function() {
289
+ this.width = undefined;
290
+ this.height = undefined;
291
+ };
292
+
293
+ /**
294
+ * get the title of this node.
295
+ * @return {string} title The title of the node, or undefined when no title
296
+ * has been set.
297
+ */
298
+ Node.prototype.getTitle = function() {
299
+ return this.title;
300
+ };
301
+
302
+ /**
303
+ * Calculate the distance to the border of the Node
304
+ * @param {CanvasRenderingContext2D} ctx
305
+ * @param {Number} angle Angle in radians
306
+ * @returns {number} distance Distance to the border in pixels
307
+ */
308
+ Node.prototype.distanceToBorder = function (ctx, angle) {
309
+ var borderWidth = 1;
310
+
311
+ if (!this.width) {
312
+ this.resize(ctx);
313
+ }
314
+
315
+ //noinspection FallthroughInSwitchStatementJS
316
+ switch (this.shape) {
317
+ case 'circle':
318
+ case 'dot':
319
+ return this.radius + borderWidth;
320
+
321
+ case 'ellipse':
322
+ var a = this.width / 2;
323
+ var b = this.height / 2;
324
+ var w = (Math.sin(angle) * a);
325
+ var h = (Math.cos(angle) * b);
326
+ return a * b / Math.sqrt(w * w + h * h);
327
+
328
+ // TODO: implement distanceToBorder for database
329
+ // TODO: implement distanceToBorder for triangle
330
+ // TODO: implement distanceToBorder for triangleDown
331
+
332
+ case 'box':
333
+ case 'image':
334
+ case 'text':
335
+ default:
336
+ if (this.width) {
337
+ return Math.min(
338
+ Math.abs(this.width / 2 / Math.cos(angle)),
339
+ Math.abs(this.height / 2 / Math.sin(angle))) + borderWidth;
340
+ // TODO: reckon with border radius too in case of box
341
+ }
342
+ else {
343
+ return 0;
344
+ }
345
+
346
+ }
347
+
348
+ // TODO: implement calculation of distance to border for all shapes
349
+ };
350
+
351
+ /**
352
+ * Set forces acting on the node
353
+ * @param {number} fx Force in horizontal direction
354
+ * @param {number} fy Force in vertical direction
355
+ */
356
+ Node.prototype._setForce = function(fx, fy) {
357
+ this.fx = fx;
358
+ this.fy = fy;
359
+ };
360
+
361
+ /**
362
+ * Add forces acting on the node
363
+ * @param {number} fx Force in horizontal direction
364
+ * @param {number} fy Force in vertical direction
365
+ * @private
366
+ */
367
+ Node.prototype._addForce = function(fx, fy) {
368
+ this.fx += fx;
369
+ this.fy += fy;
370
+ };
371
+
372
+ /**
373
+ * Perform one discrete step for the node
374
+ * @param {number} interval Time interval in seconds
375
+ */
376
+ Node.prototype.discreteStep = function(interval) {
377
+ if (!this.xFixed) {
378
+ var dx = -this.damping * this.vx; // damping force
379
+ var ax = (this.fx + dx) / this.mass; // acceleration
380
+ this.vx += ax * interval; // velocity
381
+ this.x += this.vx * interval; // position
382
+ }
383
+
384
+ if (!this.yFixed) {
385
+ var dy = -this.damping * this.vy; // damping force
386
+ var ay = (this.fy + dy) / this.mass; // acceleration
387
+ this.vy += ay * interval; // velocity
388
+ this.y += this.vy * interval; // position
389
+ }
390
+ };
391
+
392
+
393
+ /**
394
+ * Check if this node has a fixed x and y position
395
+ * @return {boolean} true if fixed, false if not
396
+ */
397
+ Node.prototype.isFixed = function() {
398
+ return (this.xFixed && this.yFixed);
399
+ };
400
+
401
+ /**
402
+ * Check if this node is moving
403
+ * @param {number} vmin the minimum velocity considered as "moving"
404
+ * @return {boolean} true if moving, false if it has no velocity
405
+ */
406
+ // TODO: replace this method with calculating the kinetic energy
407
+ Node.prototype.isMoving = function(vmin) {
408
+
409
+ if (Math.abs(this.vx) > vmin || Math.abs(this.vy) > vmin) {
410
+ // console.log(vmin,this.vx,this.vy);
411
+ return true;
412
+ }
413
+ else {
414
+ this.vx = 0; this.vy = 0;
415
+ return false;
416
+ }
417
+ //return (Math.abs(this.vx) > vmin || Math.abs(this.vy) > vmin);
418
+ };
419
+
420
+ /**
421
+ * check if this node is selecte
422
+ * @return {boolean} selected True if node is selected, else false
423
+ */
424
+ Node.prototype.isSelected = function() {
425
+ return this.selected;
426
+ };
427
+
428
+ /**
429
+ * Retrieve the value of the node. Can be undefined
430
+ * @return {Number} value
431
+ */
432
+ Node.prototype.getValue = function() {
433
+ return this.value;
434
+ };
435
+
436
+ /**
437
+ * Calculate the distance from the nodes location to the given location (x,y)
438
+ * @param {Number} x
439
+ * @param {Number} y
440
+ * @return {Number} value
441
+ */
442
+ Node.prototype.getDistance = function(x, y) {
443
+ var dx = this.x - x,
444
+ dy = this.y - y;
445
+ return Math.sqrt(dx * dx + dy * dy);
446
+ };
447
+
448
+
449
+ /**
450
+ * Adjust the value range of the node. The node will adjust it's radius
451
+ * based on its value.
452
+ * @param {Number} min
453
+ * @param {Number} max
454
+ */
455
+ Node.prototype.setValueRange = function(min, max) {
456
+ if (!this.radiusFixed && this.value !== undefined) {
457
+ if (max == min) {
458
+ this.radius = (this.radiusMin + this.radiusMax) / 2;
459
+ }
460
+ else {
461
+ var scale = (this.radiusMax - this.radiusMin) / (max - min);
462
+ this.radius = (this.value - min) * scale + this.radiusMin;
463
+ }
464
+ }
465
+ this.baseRadiusValue = this.radius;
466
+ };
467
+
468
+ /**
469
+ * Draw this node in the given canvas
470
+ * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
471
+ * @param {CanvasRenderingContext2D} ctx
472
+ */
473
+ Node.prototype.draw = function(ctx) {
474
+ throw "Draw method not initialized for node";
475
+ };
476
+
477
+ /**
478
+ * Recalculate the size of this node in the given canvas
479
+ * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
480
+ * @param {CanvasRenderingContext2D} ctx
481
+ */
482
+ Node.prototype.resize = function(ctx) {
483
+ throw "Resize method not initialized for node";
484
+ };
485
+
486
+ /**
487
+ * Check if this object is overlapping with the provided object
488
+ * @param {Object} obj an object with parameters left, top, right, bottom
489
+ * @return {boolean} True if location is located on node
490
+ */
491
+ Node.prototype.isOverlappingWith = function(obj) {
492
+ return (this.left < obj.right &&
493
+ this.left + this.width > obj.left &&
494
+ this.top < obj.bottom &&
495
+ this.top + this.height > obj.top);
496
+ };
497
+
498
+ Node.prototype._resizeImage = function (ctx) {
499
+ // TODO: pre calculate the image size
500
+
501
+ if (!this.width || !this.height) { // undefined or 0
502
+ var width, height;
503
+ if (this.value) {
504
+ this.radius = this.baseRadiusValue;
505
+ var scale = this.imageObj.height / this.imageObj.width;
506
+ if (scale !== undefined) {
507
+ width = this.radius || this.imageObj.width;
508
+ height = this.radius * scale || this.imageObj.height;
509
+ }
510
+ else {
511
+ width = 0;
512
+ height = 0;
513
+ }
514
+ }
515
+ else {
516
+ width = this.imageObj.width;
517
+ height = this.imageObj.height;
518
+ }
519
+ this.width = width;
520
+ this.height = height;
521
+
522
+ if (this.width > 0 && this.height > 0) {
523
+ this.width += (this.clusterSize - 1) * this.clusterSizeWidthFactor;
524
+ this.height += (this.clusterSize - 1) * this.clusterSizeHeightFactor;
525
+ this.radius += (this.clusterSize - 1) * this.clusterSizeRadiusFactor;
526
+ }
527
+ }
528
+
529
+ };
530
+
531
+ Node.prototype._drawImage = function (ctx) {
532
+ this._resizeImage(ctx);
533
+
534
+ this.left = this.x - this.width / 2;
535
+ this.top = this.y - this.height / 2;
536
+
537
+ var yLabel;
538
+ if (this.imageObj.width != 0 ) {
539
+ // draw the shade
540
+ if (this.clusterSize > 1) {
541
+ var lineWidth = ((this.clusterSize > 1) ? 10 : 0.0);
542
+ lineWidth *= this.graphScaleInv;
543
+ lineWidth = Math.min(0.2 * this.width,lineWidth);
544
+
545
+ ctx.globalAlpha = 0.5;
546
+ ctx.drawImage(this.imageObj, this.left - lineWidth, this.top - lineWidth, this.width + 2*lineWidth, this.height + 2*lineWidth);
547
+ }
548
+
549
+ // draw the image
550
+ ctx.globalAlpha = 1.0;
551
+ ctx.drawImage(this.imageObj, this.left, this.top, this.width, this.height);
552
+ yLabel = this.y + this.height / 2;
553
+ }
554
+ else {
555
+ // image still loading... just draw the label for now
556
+ yLabel = this.y;
557
+ }
558
+
559
+ this._label(ctx, this.label, this.x, yLabel, undefined, "top");
560
+ };
561
+
562
+
563
+ Node.prototype._resizeBox = function (ctx) {
564
+ if (!this.width) {
565
+ var margin = 5;
566
+ var textSize = this.getTextSize(ctx);
567
+ this.width = textSize.width + 2 * margin;
568
+ this.height = textSize.height + 2 * margin;
569
+
570
+ this.width += (this.clusterSize - 1) * 0.5 * this.clusterSizeWidthFactor;
571
+ this.height += (this.clusterSize - 1) * 0.5 * this.clusterSizeHeightFactor;
572
+ // this.radius += (this.clusterSize - 1) * 0.5 * this.clusterSizeRadiusFactor;
573
+ }
574
+ };
575
+
576
+ Node.prototype._drawBox = function (ctx) {
577
+ this._resizeBox(ctx);
578
+
579
+ this.left = this.x - this.width / 2;
580
+ this.top = this.y - this.height / 2;
581
+
582
+ var clusterLineWidth = 2.5;
583
+ var selectionLineWidth = 2;
584
+
585
+ ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border;
586
+
587
+ // draw the outer border
588
+ if (this.clusterSize > 1) {
589
+ ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
590
+ ctx.lineWidth *= this.graphScaleInv;
591
+ ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth);
592
+
593
+ ctx.roundRect(this.left-2*ctx.lineWidth, this.top-2*ctx.lineWidth, this.width+4*ctx.lineWidth, this.height+4*ctx.lineWidth, this.radius);
594
+ ctx.stroke();
595
+ }
596
+ ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
597
+ ctx.lineWidth *= this.graphScaleInv;
598
+ ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth);
599
+
600
+ ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background;
601
+
602
+ ctx.roundRect(this.left, this.top, this.width, this.height, this.radius);
603
+ ctx.fill();
604
+ ctx.stroke();
605
+
606
+ this._label(ctx, this.label, this.x, this.y);
607
+ };
608
+
609
+
610
+ Node.prototype._resizeDatabase = function (ctx) {
611
+ if (!this.width) {
612
+ var margin = 5;
613
+ var textSize = this.getTextSize(ctx);
614
+ var size = textSize.width + 2 * margin;
615
+ this.width = size;
616
+ this.height = size;
617
+
618
+ // scaling used for clustering
619
+ this.width += (this.clusterSize - 1) * this.clusterSizeWidthFactor;
620
+ this.height += (this.clusterSize - 1) * this.clusterSizeHeightFactor;
621
+ this.radius += (this.clusterSize - 1) * this.clusterSizeRadiusFactor;
622
+ }
623
+ };
624
+
625
+ Node.prototype._drawDatabase = function (ctx) {
626
+ this._resizeDatabase(ctx);
627
+ this.left = this.x - this.width / 2;
628
+ this.top = this.y - this.height / 2;
629
+
630
+ var clusterLineWidth = 2.5;
631
+ var selectionLineWidth = 2;
632
+
633
+ ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border;
634
+
635
+ // draw the outer border
636
+ if (this.clusterSize > 1) {
637
+ ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
638
+ ctx.lineWidth *= this.graphScaleInv;
639
+ ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth);
640
+
641
+ ctx.database(this.x - this.width/2 - 2*ctx.lineWidth, this.y - this.height*0.5 - 2*ctx.lineWidth, this.width + 4*ctx.lineWidth, this.height + 4*ctx.lineWidth);
642
+ ctx.stroke();
643
+ }
644
+ ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
645
+ ctx.lineWidth *= this.graphScaleInv;
646
+ ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth);
647
+
648
+ ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background;
649
+ ctx.database(this.x - this.width/2, this.y - this.height*0.5, this.width, this.height);
650
+ ctx.fill();
651
+ ctx.stroke();
652
+
653
+ this._label(ctx, this.label, this.x, this.y);
654
+ };
655
+
656
+
657
+ Node.prototype._resizeCircle = function (ctx) {
658
+ if (!this.width) {
659
+ var margin = 5;
660
+ var textSize = this.getTextSize(ctx);
661
+ var diameter = Math.max(textSize.width, textSize.height) + 2 * margin;
662
+ this.radius = diameter / 2;
663
+
664
+ this.width = diameter;
665
+ this.height = diameter;
666
+
667
+ // scaling used for clustering
668
+ // this.width += (this.clusterSize - 1) * 0.5 * this.clusterSizeWidthFactor;
669
+ // this.height += (this.clusterSize - 1) * 0.5 * this.clusterSizeHeightFactor;
670
+ this.radius += (this.clusterSize - 1) * 0.5 * this.clusterSizeRadiusFactor;
671
+ }
672
+ };
673
+
674
+ Node.prototype._drawCircle = function (ctx) {
675
+ this._resizeCircle(ctx);
676
+ this.left = this.x - this.width / 2;
677
+ this.top = this.y - this.height / 2;
678
+
679
+ var clusterLineWidth = 2.5;
680
+ var selectionLineWidth = 2;
681
+
682
+ ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border;
683
+
684
+ // draw the outer border
685
+ if (this.clusterSize > 1) {
686
+ ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
687
+ ctx.lineWidth *= this.graphScaleInv;
688
+ ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth);
689
+
690
+ ctx.circle(this.x, this.y, this.radius+2*ctx.lineWidth);
691
+ ctx.stroke();
692
+ }
693
+ ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
694
+ ctx.lineWidth *= this.graphScaleInv;
695
+ ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth);
696
+
697
+ ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background;
698
+ ctx.circle(this.x, this.y, this.radius);
699
+ ctx.fill();
700
+ ctx.stroke();
701
+
702
+ this._label(ctx, this.label, this.x, this.y);
703
+ };
704
+
705
+ Node.prototype._resizeEllipse = function (ctx) {
706
+ if (!this.width) {
707
+ var textSize = this.getTextSize(ctx);
708
+
709
+ this.width = textSize.width * 1.5;
710
+ this.height = textSize.height * 2;
711
+ if (this.width < this.height) {
712
+ this.width = this.height;
713
+ }
714
+
715
+ // scaling used for clustering
716
+ this.width += (this.clusterSize - 1) * this.clusterSizeWidthFactor;
717
+ this.height += (this.clusterSize - 1) * this.clusterSizeHeightFactor;
718
+ this.radius += (this.clusterSize - 1) * this.clusterSizeRadiusFactor;
719
+ }
720
+ };
721
+
722
+ Node.prototype._drawEllipse = function (ctx) {
723
+ this._resizeEllipse(ctx);
724
+ this.left = this.x - this.width / 2;
725
+ this.top = this.y - this.height / 2;
726
+
727
+ var clusterLineWidth = 2.5;
728
+ var selectionLineWidth = 2;
729
+
730
+ ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border;
731
+
732
+ // draw the outer border
733
+ if (this.clusterSize > 1) {
734
+ ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
735
+ ctx.lineWidth *= this.graphScaleInv;
736
+ ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth);
737
+
738
+ ctx.ellipse(this.left-2*ctx.lineWidth, this.top-2*ctx.lineWidth, this.width+4*ctx.lineWidth, this.height+4*ctx.lineWidth);
739
+ ctx.stroke();
740
+ }
741
+ ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
742
+ ctx.lineWidth *= this.graphScaleInv;
743
+ ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth);
744
+
745
+ ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background;
746
+
747
+ ctx.ellipse(this.left, this.top, this.width, this.height);
748
+ ctx.fill();
749
+ ctx.stroke();
750
+ this._label(ctx, this.label, this.x, this.y);
751
+ };
752
+
753
+ Node.prototype._drawDot = function (ctx) {
754
+ this._drawShape(ctx, 'circle');
755
+ };
756
+
757
+ Node.prototype._drawTriangle = function (ctx) {
758
+ this._drawShape(ctx, 'triangle');
759
+ };
760
+
761
+ Node.prototype._drawTriangleDown = function (ctx) {
762
+ this._drawShape(ctx, 'triangleDown');
763
+ };
764
+
765
+ Node.prototype._drawSquare = function (ctx) {
766
+ this._drawShape(ctx, 'square');
767
+ };
768
+
769
+ Node.prototype._drawStar = function (ctx) {
770
+ this._drawShape(ctx, 'star');
771
+ };
772
+
773
+ Node.prototype._resizeShape = function (ctx) {
774
+ if (!this.width) {
775
+ this.radius = this.baseRadiusValue;
776
+ var size = 2 * this.radius;
777
+ this.width = size;
778
+ this.height = size;
779
+
780
+ // scaling used for clustering
781
+ this.width += (this.clusterSize - 1) * this.clusterSizeWidthFactor;
782
+ this.height += (this.clusterSize - 1) * this.clusterSizeHeightFactor;
783
+ this.radius += (this.clusterSize - 1) * 0.5 * this.clusterSizeRadiusFactor;
784
+ }
785
+ };
786
+
787
+ Node.prototype._drawShape = function (ctx, shape) {
788
+ this._resizeShape(ctx);
789
+
790
+ this.left = this.x - this.width / 2;
791
+ this.top = this.y - this.height / 2;
792
+
793
+ var clusterLineWidth = 2.5;
794
+ var selectionLineWidth = 2;
795
+ var radiusMultiplier = 2;
796
+
797
+ // choose draw method depending on the shape
798
+ switch (shape) {
799
+ case 'dot': radiusMultiplier = 2; break;
800
+ case 'square': radiusMultiplier = 2; break;
801
+ case 'triangle': radiusMultiplier = 3; break;
802
+ case 'triangleDown': radiusMultiplier = 3; break;
803
+ case 'star': radiusMultiplier = 4; break;
804
+ }
805
+
806
+ ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border;
807
+
808
+ // draw the outer border
809
+ if (this.clusterSize > 1) {
810
+ ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
811
+ ctx.lineWidth *= this.graphScaleInv;
812
+ ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth);
813
+
814
+ ctx[shape](this.x, this.y, this.radius + radiusMultiplier * ctx.lineWidth);
815
+ ctx.stroke();
816
+ }
817
+ ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
818
+ ctx.lineWidth *= this.graphScaleInv;
819
+ ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth);
820
+
821
+ ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background;
822
+
823
+ ctx[shape](this.x, this.y, this.radius);
824
+ ctx.fill();
825
+ ctx.stroke();
826
+
827
+ if (this.label) {
828
+ this._label(ctx, this.label, this.x, this.y + this.height / 2, undefined, 'top');
829
+ }
830
+ };
831
+
832
+ Node.prototype._resizeText = function (ctx) {
833
+ if (!this.width) {
834
+ var margin = 5;
835
+ var textSize = this.getTextSize(ctx);
836
+ this.width = textSize.width + 2 * margin;
837
+ this.height = textSize.height + 2 * margin;
838
+
839
+ // scaling used for clustering
840
+ this.width += (this.clusterSize - 1) * this.clusterSizeWidthFactor;
841
+ this.height += (this.clusterSize - 1) * this.clusterSizeHeightFactor;
842
+ this.radius += (this.clusterSize - 1) * this.clusterSizeRadiusFactor;
843
+ }
844
+ };
845
+
846
+ Node.prototype._drawText = function (ctx) {
847
+ this._resizeText(ctx);
848
+ this.left = this.x - this.width / 2;
849
+ this.top = this.y - this.height / 2;
850
+
851
+ this._label(ctx, this.label, this.x, this.y);
852
+ };
853
+
854
+
855
+ Node.prototype._label = function (ctx, text, x, y, align, baseline) {
856
+ if (text) {
857
+ ctx.font = (this.selected ? "bold " : "") + this.fontSize + "px " + this.fontFace;
858
+ ctx.fillStyle = this.fontColor || "black";
859
+ ctx.textAlign = align || "center";
860
+ ctx.textBaseline = baseline || "middle";
861
+
862
+ var lines = text.split('\n'),
863
+ lineCount = lines.length,
864
+ fontSize = (this.fontSize + 4),
865
+ yLine = y + (1 - lineCount) / 2 * fontSize;
866
+
867
+ for (var i = 0; i < lineCount; i++) {
868
+ ctx.fillText(lines[i], x, yLine);
869
+ yLine += fontSize;
870
+ }
871
+ }
872
+ };
873
+
874
+
875
+ Node.prototype.getTextSize = function(ctx) {
876
+ if (this.label !== undefined) {
877
+ ctx.font = (this.selected ? "bold " : "") + this.fontSize + "px " + this.fontFace;
878
+
879
+ var lines = this.label.split('\n'),
880
+ height = (this.fontSize + 4) * lines.length,
881
+ width = 0;
882
+
883
+ for (var i = 0, iMax = lines.length; i < iMax; i++) {
884
+ width = Math.max(width, ctx.measureText(lines[i]).width);
885
+ }
886
+
887
+ return {"width": width, "height": height};
888
+ }
889
+ else {
890
+ return {"width": 0, "height": 0};
891
+ }
892
+ };
893
+
894
+ /**
895
+ * this is used to determine if a node is visible at all. this is used to determine when it needs to be drawn.
896
+ * there is a safety margin of 0.3 * width;
897
+ *
898
+ * @returns {boolean}
899
+ */
900
+ Node.prototype.inArea = function() {
901
+ if (this.width !== undefined) {
902
+ return (this.x + this.width*this.graphScaleInv >= this.canvasTopLeft.x &&
903
+ this.x - this.width*this.graphScaleInv < this.canvasBottomRight.x &&
904
+ this.y + this.height*this.graphScaleInv >= this.canvasTopLeft.y &&
905
+ this.y - this.height*this.graphScaleInv < this.canvasBottomRight.y);
906
+ }
907
+ else {
908
+ return true;
909
+ }
910
+ }
911
+
912
+ /**
913
+ * checks if the core of the node is in the display area, this is used for opening clusters around zoom
914
+ * @returns {boolean}
915
+ */
916
+ Node.prototype.inView = function() {
917
+ return (this.x >= this.canvasTopLeft.x &&
918
+ this.x < this.canvasBottomRight.x &&
919
+ this.y >= this.canvasTopLeft.y &&
920
+ this.y < this.canvasBottomRight.y);
921
+ }
922
+
923
+ /**
924
+ * This allows the zoom level of the graph to influence the rendering
925
+ * We store the inverted scale and the coordinates of the top left, and bottom right points of the canvas
926
+ *
927
+ * @param scale
928
+ * @param canvasTopLeft
929
+ * @param canvasBottomRight
930
+ */
931
+ Node.prototype.setScaleAndPos = function(scale,canvasTopLeft,canvasBottomRight) {
932
+ this.graphScaleInv = 1.0/scale;
933
+ this.canvasTopLeft = canvasTopLeft;
934
+ this.canvasBottomRight = canvasBottomRight;
935
+ };
936
+
937
+
938
+ /**
939
+ * This allows the zoom level of the graph to influence the rendering
940
+ *
941
+ * @param scale
942
+ */
943
+ Node.prototype.setScale = function(scale) {
944
+ this.graphScaleInv = 1.0/scale;
945
+ };
946
+
947
+ /**
948
+ * This function updates the damping parameter for clusters, based ont he
949
+ *
950
+ * @param {Number} numberOfNodes
951
+ */
952
+ Node.prototype.updateDamping = function(numberOfNodes) {
953
+ this.damping = (0.8 + 0.1*this.clusterSize * (1 + Math.pow(numberOfNodes,-2)));
954
+ this.damping *= this.dampingFactor;
955
+ };
956
+
957
+
958
+ /**
959
+ * set the velocity at 0. Is called when this node is contained in another during clustering
960
+ */
961
+ Node.prototype.clearVelocity = function() {
962
+ this.vx = 0;
963
+ this.vy = 0;
964
+ };
965
+
966
+
967
+ /**
968
+ * Basic preservation of (kinectic) energy
969
+ *
970
+ * @param massBeforeClustering
971
+ */
972
+ Node.prototype.updateVelocity = function(massBeforeClustering) {
973
+ var energyBefore = this.vx * this.vx * massBeforeClustering;
974
+ this.vx = Math.sqrt(energyBefore/this.mass);
975
+ energyBefore = this.vy * this.vy * massBeforeClustering;
976
+ this.vy = Math.sqrt(energyBefore/this.mass);
977
+ };
978
+