vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1250 lines
44 KiB

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