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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.gitmodules +3 -0
- data/.project +11 -0
- data/Gemfile +4 -0
- data/LICENSE +202 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/vis/rails/engine.rb +6 -0
- data/lib/vis/rails/version.rb +5 -0
- data/lib/vis/rails.rb +7 -0
- data/vendor/assets/javascripts/vis.js +1 -0
- data/vendor/assets/stylesheets/vis.css +3 -0
- data/vendor/assets/vis/DataSet.js +936 -0
- data/vendor/assets/vis/DataView.js +281 -0
- data/vendor/assets/vis/EventBus.js +89 -0
- data/vendor/assets/vis/events.js +116 -0
- data/vendor/assets/vis/graph/ClusterMixin.js +1019 -0
- data/vendor/assets/vis/graph/Edge.js +620 -0
- data/vendor/assets/vis/graph/Graph.js +2111 -0
- data/vendor/assets/vis/graph/Groups.js +80 -0
- data/vendor/assets/vis/graph/Images.js +41 -0
- data/vendor/assets/vis/graph/NavigationMixin.js +245 -0
- data/vendor/assets/vis/graph/Node.js +978 -0
- data/vendor/assets/vis/graph/Popup.js +105 -0
- data/vendor/assets/vis/graph/SectorsMixin.js +547 -0
- data/vendor/assets/vis/graph/SelectionMixin.js +515 -0
- data/vendor/assets/vis/graph/dotparser.js +829 -0
- data/vendor/assets/vis/graph/img/downarrow.png +0 -0
- data/vendor/assets/vis/graph/img/leftarrow.png +0 -0
- data/vendor/assets/vis/graph/img/minus.png +0 -0
- data/vendor/assets/vis/graph/img/plus.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/graph/img/zoomExtends.png +0 -0
- data/vendor/assets/vis/graph/shapes.js +225 -0
- data/vendor/assets/vis/module/exports.js +68 -0
- data/vendor/assets/vis/module/header.js +24 -0
- data/vendor/assets/vis/module/imports.js +32 -0
- data/vendor/assets/vis/shim.js +252 -0
- data/vendor/assets/vis/timeline/Controller.js +172 -0
- data/vendor/assets/vis/timeline/Range.js +553 -0
- data/vendor/assets/vis/timeline/Stack.js +192 -0
- data/vendor/assets/vis/timeline/TimeStep.js +449 -0
- data/vendor/assets/vis/timeline/Timeline.js +476 -0
- data/vendor/assets/vis/timeline/component/Component.js +148 -0
- data/vendor/assets/vis/timeline/component/ContentPanel.js +113 -0
- data/vendor/assets/vis/timeline/component/CurrentTime.js +101 -0
- data/vendor/assets/vis/timeline/component/CustomTime.js +255 -0
- data/vendor/assets/vis/timeline/component/Group.js +129 -0
- data/vendor/assets/vis/timeline/component/GroupSet.js +546 -0
- data/vendor/assets/vis/timeline/component/ItemSet.js +612 -0
- data/vendor/assets/vis/timeline/component/Panel.js +112 -0
- data/vendor/assets/vis/timeline/component/RootPanel.js +215 -0
- data/vendor/assets/vis/timeline/component/TimeAxis.js +522 -0
- data/vendor/assets/vis/timeline/component/css/currenttime.css +5 -0
- data/vendor/assets/vis/timeline/component/css/customtime.css +6 -0
- data/vendor/assets/vis/timeline/component/css/groupset.css +59 -0
- data/vendor/assets/vis/timeline/component/css/item.css +93 -0
- data/vendor/assets/vis/timeline/component/css/itemset.css +17 -0
- data/vendor/assets/vis/timeline/component/css/panel.css +14 -0
- data/vendor/assets/vis/timeline/component/css/timeaxis.css +41 -0
- data/vendor/assets/vis/timeline/component/css/timeline.css +2 -0
- data/vendor/assets/vis/timeline/component/item/Item.js +81 -0
- data/vendor/assets/vis/timeline/component/item/ItemBox.js +302 -0
- data/vendor/assets/vis/timeline/component/item/ItemPoint.js +237 -0
- data/vendor/assets/vis/timeline/component/item/ItemRange.js +251 -0
- data/vendor/assets/vis/timeline/component/item/ItemRangeOverflow.js +91 -0
- data/vendor/assets/vis/util.js +673 -0
- data/vis-rails.gemspec +47 -0
- 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
|
+
|