vis-rails 0.0.1

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