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.

967 lines
29 KiB

10 years ago
10 years ago
10 years ago
  1. var util = require('../util');
  2. /**
  3. * @class Edge
  4. *
  5. * A edge connects two nodes
  6. * @param {Object} properties Object with properties. Must contain
  7. * At least properties from and to.
  8. * Available properties: from (number),
  9. * to (number), label (string, color (string),
  10. * width (number), style (string),
  11. * length (number), title (string)
  12. * @param {Network} network A Network object, used to find and edge to
  13. * nodes.
  14. * @param {Object} constants An object with default values for
  15. * example for the color
  16. */
  17. function Edge (properties, network, constants) {
  18. if (!network) {
  19. throw "No network provided";
  20. }
  21. this.network = network;
  22. // initialize constants
  23. this.widthMin = constants.edges.widthMin;
  24. this.widthMax = constants.edges.widthMax;
  25. // initialize variables
  26. this.id = undefined;
  27. this.fromId = undefined;
  28. this.toId = undefined;
  29. this.style = constants.edges.style;
  30. this.title = undefined;
  31. this.width = constants.edges.width;
  32. this.widthSelectionMultiplier = constants.edges.widthSelectionMultiplier;
  33. this.widthSelected = this.width * this.widthSelectionMultiplier;
  34. this.hoverWidth = constants.edges.hoverWidth;
  35. this.value = undefined;
  36. this.length = constants.physics.springLength;
  37. this.customLength = false;
  38. this.selected = false;
  39. this.hover = false;
  40. this.smooth = constants.smoothCurves;
  41. this.arrowScaleFactor = constants.edges.arrowScaleFactor;
  42. this.from = null; // a node
  43. this.to = null; // a node
  44. this.via = null; // a temp node
  45. // we use this to be able to reconnect the edge to a cluster if its node is put into a cluster
  46. // by storing the original information we can revert to the original connection when the cluser is opened.
  47. this.originalFromId = [];
  48. this.originalToId = [];
  49. this.connected = false;
  50. // Added to support dashed lines
  51. // David Jordan
  52. // 2012-08-08
  53. this.dash = util.extend({}, constants.edges.dash); // contains properties length, gap, altLength
  54. this.color = {color:constants.edges.color.color,
  55. highlight:constants.edges.color.highlight,
  56. hover:constants.edges.color.hover};
  57. this.widthFixed = false;
  58. this.lengthFixed = false;
  59. this.setProperties(properties, constants);
  60. this.controlNodesEnabled = false;
  61. this.controlNodes = {from:null, to:null, positions:{}};
  62. this.connectedNode = null;
  63. }
  64. /**
  65. * Set or overwrite properties for the edge
  66. * @param {Object} properties an object with properties
  67. * @param {Object} constants and object with default, global properties
  68. */
  69. Edge.prototype.setProperties = function(properties, constants) {
  70. if (!properties) {
  71. return;
  72. }
  73. if (properties.from !== undefined) {this.fromId = properties.from;}
  74. if (properties.to !== undefined) {this.toId = properties.to;}
  75. if (properties.id !== undefined) {this.id = properties.id;}
  76. if (properties.style !== undefined) {this.style = properties.style;}
  77. if (properties.label !== undefined) {this.label = properties.label;}
  78. if (this.label) {
  79. this.fontSize = constants.edges.fontSize;
  80. this.fontFace = constants.edges.fontFace;
  81. this.fontColor = constants.edges.fontColor;
  82. this.fontFill = constants.edges.fontFill;
  83. if (properties.fontColor !== undefined) {this.fontColor = properties.fontColor;}
  84. if (properties.fontSize !== undefined) {this.fontSize = properties.fontSize;}
  85. if (properties.fontFace !== undefined) {this.fontFace = properties.fontFace;}
  86. if (properties.fontFill !== undefined) {this.fontFill = properties.fontFill;}
  87. }
  88. if (properties.title !== undefined) {this.title = properties.title;}
  89. if (properties.width !== undefined) {this.width = properties.width;}
  90. if (properties.widthSelectionMultiplier !== undefined)
  91. {this.widthSelectionMultiplier = properties.widthSelectionMultiplier;}
  92. if (properties.hoverWidth !== undefined) {this.hoverWidth = properties.hoverWidth;}
  93. if (properties.value !== undefined) {this.value = properties.value;}
  94. if (properties.length !== undefined) {this.length = properties.length;
  95. this.customLength = true;}
  96. // scale the arrow
  97. if (properties.arrowScaleFactor !== undefined) {this.arrowScaleFactor = properties.arrowScaleFactor;}
  98. // Added to support dashed lines
  99. // David Jordan
  100. // 2012-08-08
  101. if (properties.dash) {
  102. if (properties.dash.length !== undefined) {this.dash.length = properties.dash.length;}
  103. if (properties.dash.gap !== undefined) {this.dash.gap = properties.dash.gap;}
  104. if (properties.dash.altLength !== undefined) {this.dash.altLength = properties.dash.altLength;}
  105. }
  106. if (properties.color !== undefined) {
  107. if (util.isString(properties.color)) {
  108. this.color.color = properties.color;
  109. this.color.highlight = properties.color;
  110. }
  111. else {
  112. if (properties.color.color !== undefined) {this.color.color = properties.color.color;}
  113. if (properties.color.highlight !== undefined) {this.color.highlight = properties.color.highlight;}
  114. if (properties.color.hover !== undefined) {this.color.hover = properties.color.hover;}
  115. }
  116. }
  117. // A node is connected when it has a from and to node.
  118. this.connect();
  119. this.widthFixed = this.widthFixed || (properties.width !== undefined);
  120. this.lengthFixed = this.lengthFixed || (properties.length !== undefined);
  121. this.widthSelected = this.width * this.widthSelectionMultiplier;
  122. // set draw method based on style
  123. switch (this.style) {
  124. case 'line': this.draw = this._drawLine; break;
  125. case 'arrow': this.draw = this._drawArrow; break;
  126. case 'arrow-center': this.draw = this._drawArrowCenter; break;
  127. case 'dash-line': this.draw = this._drawDashLine; break;
  128. default: this.draw = this._drawLine; break;
  129. }
  130. };
  131. /**
  132. * Connect an edge to its nodes
  133. */
  134. Edge.prototype.connect = function () {
  135. this.disconnect();
  136. this.from = this.network.nodes[this.fromId] || null;
  137. this.to = this.network.nodes[this.toId] || null;
  138. this.connected = (this.from && this.to);
  139. if (this.connected) {
  140. this.from.attachEdge(this);
  141. this.to.attachEdge(this);
  142. }
  143. else {
  144. if (this.from) {
  145. this.from.detachEdge(this);
  146. }
  147. if (this.to) {
  148. this.to.detachEdge(this);
  149. }
  150. }
  151. };
  152. /**
  153. * Disconnect an edge from its nodes
  154. */
  155. Edge.prototype.disconnect = function () {
  156. if (this.from) {
  157. this.from.detachEdge(this);
  158. this.from = null;
  159. }
  160. if (this.to) {
  161. this.to.detachEdge(this);
  162. this.to = null;
  163. }
  164. this.connected = false;
  165. };
  166. /**
  167. * get the title of this edge.
  168. * @return {string} title The title of the edge, or undefined when no title
  169. * has been set.
  170. */
  171. Edge.prototype.getTitle = function() {
  172. return typeof this.title === "function" ? this.title() : this.title;
  173. };
  174. /**
  175. * Retrieve the value of the edge. Can be undefined
  176. * @return {Number} value
  177. */
  178. Edge.prototype.getValue = function() {
  179. return this.value;
  180. };
  181. /**
  182. * Adjust the value range of the edge. The edge will adjust it's width
  183. * based on its value.
  184. * @param {Number} min
  185. * @param {Number} max
  186. */
  187. Edge.prototype.setValueRange = function(min, max) {
  188. if (!this.widthFixed && this.value !== undefined) {
  189. var scale = (this.widthMax - this.widthMin) / (max - min);
  190. this.width = (this.value - min) * scale + this.widthMin;
  191. }
  192. };
  193. /**
  194. * Redraw a edge
  195. * Draw this edge in the given canvas
  196. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  197. * @param {CanvasRenderingContext2D} ctx
  198. */
  199. Edge.prototype.draw = function(ctx) {
  200. throw "Method draw not initialized in edge";
  201. };
  202. /**
  203. * Check if this object is overlapping with the provided object
  204. * @param {Object} obj an object with parameters left, top
  205. * @return {boolean} True if location is located on the edge
  206. */
  207. Edge.prototype.isOverlappingWith = function(obj) {
  208. if (this.connected) {
  209. var distMax = 10;
  210. var xFrom = this.from.x;
  211. var yFrom = this.from.y;
  212. var xTo = this.to.x;
  213. var yTo = this.to.y;
  214. var xObj = obj.left;
  215. var yObj = obj.top;
  216. var dist = this._getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
  217. return (dist < distMax);
  218. }
  219. else {
  220. return false
  221. }
  222. };
  223. /**
  224. * Redraw a edge as a line
  225. * Draw this edge in the given canvas
  226. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  227. * @param {CanvasRenderingContext2D} ctx
  228. * @private
  229. */
  230. Edge.prototype._drawLine = function(ctx) {
  231. // set style
  232. if (this.selected == true) {ctx.strokeStyle = this.color.highlight;}
  233. else if (this.hover == true) {ctx.strokeStyle = this.color.hover;}
  234. else {ctx.strokeStyle = this.color.color;}
  235. ctx.lineWidth = this._getLineWidth();
  236. if (this.from != this.to) {
  237. // draw line
  238. this._line(ctx);
  239. // draw label
  240. var point;
  241. if (this.label) {
  242. if (this.smooth == true) {
  243. var midpointX = 0.5*(0.5*(this.from.x + this.via.x) + 0.5*(this.to.x + this.via.x));
  244. var midpointY = 0.5*(0.5*(this.from.y + this.via.y) + 0.5*(this.to.y + this.via.y));
  245. point = {x:midpointX, y:midpointY};
  246. }
  247. else {
  248. point = this._pointOnLine(0.5);
  249. }
  250. this._label(ctx, this.label, point.x, point.y);
  251. }
  252. }
  253. else {
  254. var x, y;
  255. var radius = this.length / 4;
  256. var node = this.from;
  257. if (!node.width) {
  258. node.resize(ctx);
  259. }
  260. if (node.width > node.height) {
  261. x = node.x + node.width / 2;
  262. y = node.y - radius;
  263. }
  264. else {
  265. x = node.x + radius;
  266. y = node.y - node.height / 2;
  267. }
  268. this._circle(ctx, x, y, radius);
  269. point = this._pointOnCircle(x, y, radius, 0.5);
  270. this._label(ctx, this.label, point.x, point.y);
  271. }
  272. };
  273. /**
  274. * Get the line width of the edge. Depends on width and whether one of the
  275. * connected nodes is selected.
  276. * @return {Number} width
  277. * @private
  278. */
  279. Edge.prototype._getLineWidth = function() {
  280. if (this.selected == true) {
  281. return Math.min(this.widthSelected, this.widthMax)*this.networkScaleInv;
  282. }
  283. else {
  284. if (this.hover == true) {
  285. return Math.min(this.hoverWidth, this.widthMax)*this.networkScaleInv;
  286. }
  287. else {
  288. return this.width*this.networkScaleInv;
  289. }
  290. }
  291. };
  292. /**
  293. * Draw a line between two nodes
  294. * @param {CanvasRenderingContext2D} ctx
  295. * @private
  296. */
  297. Edge.prototype._line = function (ctx) {
  298. // draw a straight line
  299. ctx.beginPath();
  300. ctx.moveTo(this.from.x, this.from.y);
  301. if (this.smooth == true) {
  302. ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y);
  303. }
  304. else {
  305. ctx.lineTo(this.to.x, this.to.y);
  306. }
  307. ctx.stroke();
  308. };
  309. /**
  310. * Draw a line from a node to itself, a circle
  311. * @param {CanvasRenderingContext2D} ctx
  312. * @param {Number} x
  313. * @param {Number} y
  314. * @param {Number} radius
  315. * @private
  316. */
  317. Edge.prototype._circle = function (ctx, x, y, radius) {
  318. // draw a circle
  319. ctx.beginPath();
  320. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  321. ctx.stroke();
  322. };
  323. /**
  324. * Draw label with white background and with the middle at (x, y)
  325. * @param {CanvasRenderingContext2D} ctx
  326. * @param {String} text
  327. * @param {Number} x
  328. * @param {Number} y
  329. * @private
  330. */
  331. Edge.prototype._label = function (ctx, text, x, y) {
  332. if (text) {
  333. // TODO: cache the calculated size
  334. ctx.font = ((this.from.selected || this.to.selected) ? "bold " : "") +
  335. this.fontSize + "px " + this.fontFace;
  336. ctx.fillStyle = this.fontFill;
  337. var width = ctx.measureText(text).width;
  338. var height = this.fontSize;
  339. var left = x - width / 2;
  340. var top = y - height / 2;
  341. ctx.fillRect(left, top, width, height);
  342. // draw text
  343. ctx.fillStyle = this.fontColor || "black";
  344. ctx.textAlign = "left";
  345. ctx.textBaseline = "top";
  346. ctx.fillText(text, left, top);
  347. }
  348. };
  349. /**
  350. * Redraw a edge as a dashed line
  351. * Draw this edge in the given canvas
  352. * @author David Jordan
  353. * @date 2012-08-08
  354. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  355. * @param {CanvasRenderingContext2D} ctx
  356. * @private
  357. */
  358. Edge.prototype._drawDashLine = function(ctx) {
  359. // set style
  360. if (this.selected == true) {ctx.strokeStyle = this.color.highlight;}
  361. else if (this.hover == true) {ctx.strokeStyle = this.color.hover;}
  362. else {ctx.strokeStyle = this.color.color;}
  363. ctx.lineWidth = this._getLineWidth();
  364. // only firefox and chrome support this method, else we use the legacy one.
  365. if (ctx.mozDash !== undefined || ctx.setLineDash !== undefined) {
  366. ctx.beginPath();
  367. ctx.moveTo(this.from.x, this.from.y);
  368. // configure the dash pattern
  369. var pattern = [0];
  370. if (this.dash.length !== undefined && this.dash.gap !== undefined) {
  371. pattern = [this.dash.length,this.dash.gap];
  372. }
  373. else {
  374. pattern = [5,5];
  375. }
  376. // set dash settings for chrome or firefox
  377. if (typeof ctx.setLineDash !== 'undefined') { //Chrome
  378. ctx.setLineDash(pattern);
  379. ctx.lineDashOffset = 0;
  380. } else { //Firefox
  381. ctx.mozDash = pattern;
  382. ctx.mozDashOffset = 0;
  383. }
  384. // draw the line
  385. if (this.smooth == true) {
  386. ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y);
  387. }
  388. else {
  389. ctx.lineTo(this.to.x, this.to.y);
  390. }
  391. ctx.stroke();
  392. // restore the dash settings.
  393. if (typeof ctx.setLineDash !== 'undefined') { //Chrome
  394. ctx.setLineDash([0]);
  395. ctx.lineDashOffset = 0;
  396. } else { //Firefox
  397. ctx.mozDash = [0];
  398. ctx.mozDashOffset = 0;
  399. }
  400. }
  401. else { // unsupporting smooth lines
  402. // draw dashed line
  403. ctx.beginPath();
  404. ctx.lineCap = 'round';
  405. if (this.dash.altLength !== undefined) //If an alt dash value has been set add to the array this value
  406. {
  407. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  408. [this.dash.length,this.dash.gap,this.dash.altLength,this.dash.gap]);
  409. }
  410. else if (this.dash.length !== undefined && this.dash.gap !== undefined) //If a dash and gap value has been set add to the array this value
  411. {
  412. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  413. [this.dash.length,this.dash.gap]);
  414. }
  415. else //If all else fails draw a line
  416. {
  417. ctx.moveTo(this.from.x, this.from.y);
  418. ctx.lineTo(this.to.x, this.to.y);
  419. }
  420. ctx.stroke();
  421. }
  422. // draw label
  423. if (this.label) {
  424. var point;
  425. if (this.smooth == true) {
  426. var midpointX = 0.5*(0.5*(this.from.x + this.via.x) + 0.5*(this.to.x + this.via.x));
  427. var midpointY = 0.5*(0.5*(this.from.y + this.via.y) + 0.5*(this.to.y + this.via.y));
  428. point = {x:midpointX, y:midpointY};
  429. }
  430. else {
  431. point = this._pointOnLine(0.5);
  432. }
  433. this._label(ctx, this.label, point.x, point.y);
  434. }
  435. };
  436. /**
  437. * Get a point on a line
  438. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  439. * @return {Object} point
  440. * @private
  441. */
  442. Edge.prototype._pointOnLine = function (percentage) {
  443. return {
  444. x: (1 - percentage) * this.from.x + percentage * this.to.x,
  445. y: (1 - percentage) * this.from.y + percentage * this.to.y
  446. }
  447. };
  448. /**
  449. * Get a point on a circle
  450. * @param {Number} x
  451. * @param {Number} y
  452. * @param {Number} radius
  453. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  454. * @return {Object} point
  455. * @private
  456. */
  457. Edge.prototype._pointOnCircle = function (x, y, radius, percentage) {
  458. var angle = (percentage - 3/8) * 2 * Math.PI;
  459. return {
  460. x: x + radius * Math.cos(angle),
  461. y: y - radius * Math.sin(angle)
  462. }
  463. };
  464. /**
  465. * Redraw a edge as a line with an arrow halfway the line
  466. * Draw this edge in the given canvas
  467. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  468. * @param {CanvasRenderingContext2D} ctx
  469. * @private
  470. */
  471. Edge.prototype._drawArrowCenter = function(ctx) {
  472. var point;
  473. // set style
  474. if (this.selected == true) {ctx.strokeStyle = this.color.highlight; ctx.fillStyle = this.color.highlight;}
  475. else if (this.hover == true) {ctx.strokeStyle = this.color.hover; ctx.fillStyle = this.color.hover;}
  476. else {ctx.strokeStyle = this.color.color; ctx.fillStyle = this.color.color;}
  477. ctx.lineWidth = this._getLineWidth();
  478. if (this.from != this.to) {
  479. // draw line
  480. this._line(ctx);
  481. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  482. var length = (10 + 5 * this.width) * this.arrowScaleFactor;
  483. // draw an arrow halfway the line
  484. if (this.smooth == true) {
  485. var midpointX = 0.5*(0.5*(this.from.x + this.via.x) + 0.5*(this.to.x + this.via.x));
  486. var midpointY = 0.5*(0.5*(this.from.y + this.via.y) + 0.5*(this.to.y + this.via.y));
  487. point = {x:midpointX, y:midpointY};
  488. }
  489. else {
  490. point = this._pointOnLine(0.5);
  491. }
  492. ctx.arrow(point.x, point.y, angle, length);
  493. ctx.fill();
  494. ctx.stroke();
  495. // draw label
  496. if (this.label) {
  497. this._label(ctx, this.label, point.x, point.y);
  498. }
  499. }
  500. else {
  501. // draw circle
  502. var x, y;
  503. var radius = 0.25 * Math.max(100,this.length);
  504. var node = this.from;
  505. if (!node.width) {
  506. node.resize(ctx);
  507. }
  508. if (node.width > node.height) {
  509. x = node.x + node.width * 0.5;
  510. y = node.y - radius;
  511. }
  512. else {
  513. x = node.x + radius;
  514. y = node.y - node.height * 0.5;
  515. }
  516. this._circle(ctx, x, y, radius);
  517. // draw all arrows
  518. var angle = 0.2 * Math.PI;
  519. var length = (10 + 5 * this.width) * this.arrowScaleFactor;
  520. point = this._pointOnCircle(x, y, radius, 0.5);
  521. ctx.arrow(point.x, point.y, angle, length);
  522. ctx.fill();
  523. ctx.stroke();
  524. // draw label
  525. if (this.label) {
  526. point = this._pointOnCircle(x, y, radius, 0.5);
  527. this._label(ctx, this.label, point.x, point.y);
  528. }
  529. }
  530. };
  531. /**
  532. * Redraw a edge as a line with an arrow
  533. * Draw this edge in the given canvas
  534. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  535. * @param {CanvasRenderingContext2D} ctx
  536. * @private
  537. */
  538. Edge.prototype._drawArrow = function(ctx) {
  539. // set style
  540. if (this.selected == true) {ctx.strokeStyle = this.color.highlight; ctx.fillStyle = this.color.highlight;}
  541. else if (this.hover == true) {ctx.strokeStyle = this.color.hover; ctx.fillStyle = this.color.hover;}
  542. else {ctx.strokeStyle = this.color.color; ctx.fillStyle = this.color.color;}
  543. ctx.lineWidth = this._getLineWidth();
  544. var angle, length;
  545. //draw a line
  546. if (this.from != this.to) {
  547. angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  548. var dx = (this.to.x - this.from.x);
  549. var dy = (this.to.y - this.from.y);
  550. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  551. var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
  552. var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
  553. var xFrom = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
  554. var yFrom = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
  555. if (this.smooth == true) {
  556. angle = Math.atan2((this.to.y - this.via.y), (this.to.x - this.via.x));
  557. dx = (this.to.x - this.via.x);
  558. dy = (this.to.y - this.via.y);
  559. edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  560. }
  561. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  562. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  563. var xTo,yTo;
  564. if (this.smooth == true) {
  565. xTo = (1 - toBorderPoint) * this.via.x + toBorderPoint * this.to.x;
  566. yTo = (1 - toBorderPoint) * this.via.y + toBorderPoint * this.to.y;
  567. }
  568. else {
  569. xTo = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  570. yTo = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  571. }
  572. ctx.beginPath();
  573. ctx.moveTo(xFrom,yFrom);
  574. if (this.smooth == true) {
  575. ctx.quadraticCurveTo(this.via.x,this.via.y,xTo, yTo);
  576. }
  577. else {
  578. ctx.lineTo(xTo, yTo);
  579. }
  580. ctx.stroke();
  581. // draw arrow at the end of the line
  582. length = (10 + 5 * this.width) * this.arrowScaleFactor;
  583. ctx.arrow(xTo, yTo, angle, length);
  584. ctx.fill();
  585. ctx.stroke();
  586. // draw label
  587. if (this.label) {
  588. var point;
  589. if (this.smooth == true) {
  590. var midpointX = 0.5*(0.5*(this.from.x + this.via.x) + 0.5*(this.to.x + this.via.x));
  591. var midpointY = 0.5*(0.5*(this.from.y + this.via.y) + 0.5*(this.to.y + this.via.y));
  592. point = {x:midpointX, y:midpointY};
  593. }
  594. else {
  595. point = this._pointOnLine(0.5);
  596. }
  597. this._label(ctx, this.label, point.x, point.y);
  598. }
  599. }
  600. else {
  601. // draw circle
  602. var node = this.from;
  603. var x, y, arrow;
  604. var radius = 0.25 * Math.max(100,this.length);
  605. if (!node.width) {
  606. node.resize(ctx);
  607. }
  608. if (node.width > node.height) {
  609. x = node.x + node.width * 0.5;
  610. y = node.y - radius;
  611. arrow = {
  612. x: x,
  613. y: node.y,
  614. angle: 0.9 * Math.PI
  615. };
  616. }
  617. else {
  618. x = node.x + radius;
  619. y = node.y - node.height * 0.5;
  620. arrow = {
  621. x: node.x,
  622. y: y,
  623. angle: 0.6 * Math.PI
  624. };
  625. }
  626. ctx.beginPath();
  627. // TODO: similarly, for a line without arrows, draw to the border of the nodes instead of the center
  628. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  629. ctx.stroke();
  630. // draw all arrows
  631. var length = (10 + 5 * this.width) * this.arrowScaleFactor;
  632. ctx.arrow(arrow.x, arrow.y, arrow.angle, length);
  633. ctx.fill();
  634. ctx.stroke();
  635. // draw label
  636. if (this.label) {
  637. point = this._pointOnCircle(x, y, radius, 0.5);
  638. this._label(ctx, this.label, point.x, point.y);
  639. }
  640. }
  641. };
  642. /**
  643. * Calculate the distance between a point (x3,y3) and a line segment from
  644. * (x1,y1) to (x2,y2).
  645. * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
  646. * @param {number} x1
  647. * @param {number} y1
  648. * @param {number} x2
  649. * @param {number} y2
  650. * @param {number} x3
  651. * @param {number} y3
  652. * @private
  653. */
  654. Edge.prototype._getDistanceToEdge = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is the point
  655. if (this.from != this.to) {
  656. if (this.smooth == true) {
  657. var minDistance = 1e9;
  658. var i,t,x,y,dx,dy;
  659. for (i = 0; i < 10; i++) {
  660. t = 0.1*i;
  661. x = Math.pow(1-t,2)*x1 + (2*t*(1 - t))*this.via.x + Math.pow(t,2)*x2;
  662. y = Math.pow(1-t,2)*y1 + (2*t*(1 - t))*this.via.y + Math.pow(t,2)*y2;
  663. dx = Math.abs(x3-x);
  664. dy = Math.abs(y3-y);
  665. minDistance = Math.min(minDistance,Math.sqrt(dx*dx + dy*dy));
  666. }
  667. return minDistance
  668. }
  669. else {
  670. var px = x2-x1,
  671. py = y2-y1,
  672. something = px*px + py*py,
  673. u = ((x3 - x1) * px + (y3 - y1) * py) / something;
  674. if (u > 1) {
  675. u = 1;
  676. }
  677. else if (u < 0) {
  678. u = 0;
  679. }
  680. var x = x1 + u * px,
  681. y = y1 + u * py,
  682. dx = x - x3,
  683. dy = y - y3;
  684. //# Note: If the actual distance does not matter,
  685. //# if you only want to compare what this function
  686. //# returns to other results of this function, you
  687. //# can just return the squared distance instead
  688. //# (i.e. remove the sqrt) to gain a little performance
  689. return Math.sqrt(dx*dx + dy*dy);
  690. }
  691. }
  692. else {
  693. var x, y, dx, dy;
  694. var radius = this.length / 4;
  695. var node = this.from;
  696. if (!node.width) {
  697. node.resize(ctx);
  698. }
  699. if (node.width > node.height) {
  700. x = node.x + node.width / 2;
  701. y = node.y - radius;
  702. }
  703. else {
  704. x = node.x + radius;
  705. y = node.y - node.height / 2;
  706. }
  707. dx = x - x3;
  708. dy = y - y3;
  709. return Math.abs(Math.sqrt(dx*dx + dy*dy) - radius);
  710. }
  711. };
  712. /**
  713. * This allows the zoom level of the network to influence the rendering
  714. *
  715. * @param scale
  716. */
  717. Edge.prototype.setScale = function(scale) {
  718. this.networkScaleInv = 1.0/scale;
  719. };
  720. Edge.prototype.select = function() {
  721. this.selected = true;
  722. };
  723. Edge.prototype.unselect = function() {
  724. this.selected = false;
  725. };
  726. Edge.prototype.positionBezierNode = function() {
  727. if (this.via !== null) {
  728. this.via.x = 0.5 * (this.from.x + this.to.x);
  729. this.via.y = 0.5 * (this.from.y + this.to.y);
  730. }
  731. };
  732. /**
  733. * This function draws the control nodes for the manipulator. In order to enable this, only set the this.controlNodesEnabled to true.
  734. * @param ctx
  735. */
  736. Edge.prototype._drawControlNodes = function(ctx) {
  737. if (this.controlNodesEnabled == true) {
  738. if (this.controlNodes.from === null && this.controlNodes.to === null) {
  739. var nodeIdFrom = "edgeIdFrom:".concat(this.id);
  740. var nodeIdTo = "edgeIdTo:".concat(this.id);
  741. var constants = {
  742. nodes:{group:'', radius:8},
  743. physics:{damping:0},
  744. clustering: {maxNodeSizeIncrements: 0 ,nodeScaling: {width:0, height: 0, radius:0}}
  745. };
  746. this.controlNodes.from = new Node(
  747. {id:nodeIdFrom,
  748. shape:'dot',
  749. color:{background:'#ff4e00', border:'#3c3c3c', highlight: {background:'#07f968'}}
  750. },{},{},constants);
  751. this.controlNodes.to = new Node(
  752. {id:nodeIdTo,
  753. shape:'dot',
  754. color:{background:'#ff4e00', border:'#3c3c3c', highlight: {background:'#07f968'}}
  755. },{},{},constants);
  756. }
  757. if (this.controlNodes.from.selected == false && this.controlNodes.to.selected == false) {
  758. this.controlNodes.positions = this.getControlNodePositions(ctx);
  759. this.controlNodes.from.x = this.controlNodes.positions.from.x;
  760. this.controlNodes.from.y = this.controlNodes.positions.from.y;
  761. this.controlNodes.to.x = this.controlNodes.positions.to.x;
  762. this.controlNodes.to.y = this.controlNodes.positions.to.y;
  763. }
  764. this.controlNodes.from.draw(ctx);
  765. this.controlNodes.to.draw(ctx);
  766. }
  767. else {
  768. this.controlNodes = {from:null, to:null, positions:{}};
  769. }
  770. };
  771. /**
  772. * Enable control nodes.
  773. * @private
  774. */
  775. Edge.prototype._enableControlNodes = function() {
  776. this.controlNodesEnabled = true;
  777. };
  778. /**
  779. * disable control nodes
  780. * @private
  781. */
  782. Edge.prototype._disableControlNodes = function() {
  783. this.controlNodesEnabled = false;
  784. };
  785. /**
  786. * This checks if one of the control nodes is selected and if so, returns the control node object. Else it returns null.
  787. * @param x
  788. * @param y
  789. * @returns {null}
  790. * @private
  791. */
  792. Edge.prototype._getSelectedControlNode = function(x,y) {
  793. var positions = this.controlNodes.positions;
  794. var fromDistance = Math.sqrt(Math.pow(x - positions.from.x,2) + Math.pow(y - positions.from.y,2));
  795. var toDistance = Math.sqrt(Math.pow(x - positions.to.x ,2) + Math.pow(y - positions.to.y ,2));
  796. if (fromDistance < 15) {
  797. this.connectedNode = this.from;
  798. this.from = this.controlNodes.from;
  799. return this.controlNodes.from;
  800. }
  801. else if (toDistance < 15) {
  802. this.connectedNode = this.to;
  803. this.to = this.controlNodes.to;
  804. return this.controlNodes.to;
  805. }
  806. else {
  807. return null;
  808. }
  809. };
  810. /**
  811. * this resets the control nodes to their original position.
  812. * @private
  813. */
  814. Edge.prototype._restoreControlNodes = function() {
  815. if (this.controlNodes.from.selected == true) {
  816. this.from = this.connectedNode;
  817. this.connectedNode = null;
  818. this.controlNodes.from.unselect();
  819. }
  820. if (this.controlNodes.to.selected == true) {
  821. this.to = this.connectedNode;
  822. this.connectedNode = null;
  823. this.controlNodes.to.unselect();
  824. }
  825. };
  826. /**
  827. * this calculates the position of the control nodes on the edges of the parent nodes.
  828. *
  829. * @param ctx
  830. * @returns {{from: {x: number, y: number}, to: {x: *, y: *}}}
  831. */
  832. Edge.prototype.getControlNodePositions = function(ctx) {
  833. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  834. var dx = (this.to.x - this.from.x);
  835. var dy = (this.to.y - this.from.y);
  836. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  837. var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
  838. var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
  839. var xFrom = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
  840. var yFrom = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
  841. if (this.smooth == true) {
  842. angle = Math.atan2((this.to.y - this.via.y), (this.to.x - this.via.x));
  843. dx = (this.to.x - this.via.x);
  844. dy = (this.to.y - this.via.y);
  845. edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  846. }
  847. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  848. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  849. var xTo,yTo;
  850. if (this.smooth == true) {
  851. xTo = (1 - toBorderPoint) * this.via.x + toBorderPoint * this.to.x;
  852. yTo = (1 - toBorderPoint) * this.via.y + toBorderPoint * this.to.y;
  853. }
  854. else {
  855. xTo = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  856. yTo = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  857. }
  858. return {from:{x:xFrom,y:yFrom},to:{x:xTo,y:yTo}};
  859. };
  860. module.exports = Edge;