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
10 years ago
10 years ago
10 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 textSize =
  855. {
  856. width: 1,
  857. height: Number(this.options.iconSize) + 4
  858. };
  859. this.width = textSize.width + 2 * margin;
  860. this.height = textSize.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 - (textSize.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.options.iconSize = this.options.iconSize || 50;
  872. this.left = this.x - this.width / 2;
  873. this.top = this.y - this.height / 2;
  874. this._icon(ctx, this.options.icon, this.x, this.y);
  875. this.boundingBox.top = this.y - this.options.iconSize/2;
  876. this.boundingBox.left = this.x - this.options.iconSize/2;
  877. this.boundingBox.right = this.x + this.options.iconSize/2;
  878. this.boundingBox.bottom = this.y + this.options.iconSize/2;
  879. if (this.label) {
  880. this._label(ctx, this.label, this.x, this.y + this.height / 2, '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, icon, x, y) {
  887. var relativeIconSize = Number(this.options.iconSize) * this.networkScale;
  888. if (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(icon, x, 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;