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.

1401 lines
42 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. var util = require('../util');
  2. var Node = require('./Node');
  3. /**
  4. * @class Edge
  5. *
  6. * A edge connects two nodes
  7. * @param {Object} properties Object with properties. Must contain
  8. * At least properties from and to.
  9. * Available properties: from (number),
  10. * to (number), label (string, color (string),
  11. * width (number), style (string),
  12. * length (number), title (string)
  13. * @param {Network} network A Network object, used to find and edge to
  14. * nodes.
  15. * @param {Object} constants An object with default values for
  16. * example for the color
  17. */
  18. function Edge (properties, network, networkConstants) {
  19. if (!network) {
  20. throw "No network provided";
  21. }
  22. var fields = ['edges','physics'];
  23. var constants = util.selectiveBridgeObject(fields,networkConstants);
  24. this.options = constants.edges;
  25. this.physics = constants.physics;
  26. this.options['smoothCurves'] = networkConstants['smoothCurves'];
  27. this.network = network;
  28. // initialize variables
  29. this.id = undefined;
  30. this.fromId = undefined;
  31. this.toId = undefined;
  32. this.title = undefined;
  33. this.widthSelected = this.options.width * this.options.widthSelectionMultiplier;
  34. this.value = undefined;
  35. this.selected = false;
  36. this.hover = false;
  37. this.labelDimensions = {top:0,left:0,width:0,height:0,yLine:0}; // could be cached
  38. this.dirtyLabel = true;
  39. this.colorDirty = true;
  40. this.from = null; // a node
  41. this.to = null; // a node
  42. this.via = null; // a temp node
  43. this.fromBackup = null; // used to clean up after reconnect
  44. this.toBackup = null;; // used to clean up after reconnect
  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. this.widthFixed = false;
  51. this.lengthFixed = false;
  52. this.setProperties(properties);
  53. this.controlNodesEnabled = false;
  54. this.controlNodes = {from:null, to:null, positions:{}};
  55. this.connectedNode = null;
  56. }
  57. /**
  58. * Set or overwrite properties for the edge
  59. * @param {Object} properties an object with properties
  60. * @param {Object} constants and object with default, global properties
  61. */
  62. Edge.prototype.setProperties = function(properties) {
  63. this.colorDirty = true;
  64. if (!properties) {
  65. return;
  66. }
  67. var fields = ['style','fontSize','fontFace','fontColor','fontFill','fontStrokeWidth','fontStrokeColor','width',
  68. 'widthSelectionMultiplier','hoverWidth','arrowScaleFactor','dash','inheritColor','labelAlignment', 'opacity',
  69. 'customScalingFunction','useGradients'
  70. ];
  71. util.selectiveDeepExtend(fields, this.options, properties);
  72. if (properties.from !== undefined) {this.fromId = properties.from;}
  73. if (properties.to !== undefined) {this.toId = properties.to;}
  74. if (properties.id !== undefined) {this.id = properties.id;}
  75. if (properties.label !== undefined) {this.label = properties.label; this.dirtyLabel = true;}
  76. if (properties.title !== undefined) {this.title = properties.title;}
  77. if (properties.value !== undefined) {this.value = properties.value;}
  78. if (properties.length !== undefined) {this.physics.springLength = properties.length;}
  79. if (properties.color !== undefined) {
  80. this.options.inheritColor = false;
  81. if (util.isString(properties.color)) {
  82. this.options.color.color = properties.color;
  83. this.options.color.highlight = properties.color;
  84. }
  85. else {
  86. if (properties.color.color !== undefined) {this.options.color.color = properties.color.color;}
  87. if (properties.color.highlight !== undefined) {this.options.color.highlight = properties.color.highlight;}
  88. if (properties.color.hover !== undefined) {this.options.color.hover = properties.color.hover;}
  89. }
  90. }
  91. // A node is connected when it has a from and to node.
  92. this.connect();
  93. this.widthFixed = this.widthFixed || (properties.width !== undefined);
  94. this.lengthFixed = this.lengthFixed || (properties.length !== undefined);
  95. this.widthSelected = this.options.width* this.options.widthSelectionMultiplier;
  96. // set draw method based on style
  97. switch (this.options.style) {
  98. case 'line': this.draw = this._drawLine; break;
  99. case 'arrow': this.draw = this._drawArrow; break;
  100. case 'arrow-center': this.draw = this._drawArrowCenter; break;
  101. case 'dash-line': this.draw = this._drawDashLine; break;
  102. default: this.draw = this._drawLine; break;
  103. }
  104. };
  105. /**
  106. * Connect an edge to its nodes
  107. */
  108. Edge.prototype.connect = function () {
  109. this.disconnect();
  110. this.from = this.network.nodes[this.fromId] || null;
  111. this.to = this.network.nodes[this.toId] || null;
  112. this.connected = (this.from && this.to);
  113. if (this.connected) {
  114. this.from.attachEdge(this);
  115. this.to.attachEdge(this);
  116. }
  117. else {
  118. if (this.from) {
  119. this.from.detachEdge(this);
  120. }
  121. if (this.to) {
  122. this.to.detachEdge(this);
  123. }
  124. }
  125. };
  126. /**
  127. * Disconnect an edge from its nodes
  128. */
  129. Edge.prototype.disconnect = function () {
  130. if (this.from) {
  131. this.from.detachEdge(this);
  132. this.from = null;
  133. }
  134. if (this.to) {
  135. this.to.detachEdge(this);
  136. this.to = null;
  137. }
  138. this.connected = false;
  139. };
  140. /**
  141. * get the title of this edge.
  142. * @return {string} title The title of the edge, or undefined when no title
  143. * has been set.
  144. */
  145. Edge.prototype.getTitle = function() {
  146. return typeof this.title === "function" ? this.title() : this.title;
  147. };
  148. /**
  149. * Retrieve the value of the edge. Can be undefined
  150. * @return {Number} value
  151. */
  152. Edge.prototype.getValue = function() {
  153. return this.value;
  154. };
  155. /**
  156. * Adjust the value range of the edge. The edge will adjust it's width
  157. * based on its value.
  158. * @param {Number} min
  159. * @param {Number} max
  160. */
  161. Edge.prototype.setValueRange = function(min, max, total) {
  162. if (!this.widthFixed && this.value !== undefined) {
  163. var scale = this.options.customScalingFunction(min, max, total, this.value);
  164. var widthDiff = this.options.widthMax - this.options.widthMin;
  165. this.options.width = this.options.widthMin + scale * widthDiff;
  166. this.widthSelected = this.options.width* this.options.widthSelectionMultiplier;
  167. }
  168. };
  169. /**
  170. * Redraw a edge
  171. * Draw this edge in the given canvas
  172. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  173. * @param {CanvasRenderingContext2D} ctx
  174. */
  175. Edge.prototype.draw = function(ctx) {
  176. throw "Method draw not initialized in edge";
  177. };
  178. /**
  179. * Check if this object is overlapping with the provided object
  180. * @param {Object} obj an object with parameters left, top
  181. * @return {boolean} True if location is located on the edge
  182. */
  183. Edge.prototype.isOverlappingWith = function(obj) {
  184. if (this.connected) {
  185. var distMax = 10;
  186. var xFrom = this.from.x;
  187. var yFrom = this.from.y;
  188. var xTo = this.to.x;
  189. var yTo = this.to.y;
  190. var xObj = obj.left;
  191. var yObj = obj.top;
  192. var dist = this._getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
  193. return (dist < distMax);
  194. }
  195. else {
  196. return false
  197. }
  198. };
  199. Edge.prototype._getColor = function(ctx) {
  200. var colorObj = this.options.color;
  201. if (this.options.useGradients == true) {
  202. var grd = ctx.createLinearGradient(this.from.x, this.from.y, this.to.x, this.to.y);
  203. var fromColor, toColor;
  204. fromColor = this.from.options.color.highlight.border;
  205. toColor = this.to.options.color.highlight.border;
  206. if (this.from.selected == false && this.to.selected == false) {
  207. fromColor = util.overrideOpacity(this.from.options.color.border, this.options.opacity);
  208. toColor = util.overrideOpacity(this.to.options.color.border, this.options.opacity);
  209. }
  210. else if (this.from.selected == true && this.to.selected == false) {
  211. toColor = this.to.options.color.border;
  212. }
  213. else if (this.from.selected == false && this.to.selected == true) {
  214. fromColor = this.from.options.color.border;
  215. }
  216. grd.addColorStop(0, fromColor);
  217. grd.addColorStop(1, toColor);
  218. return grd;
  219. }
  220. if (this.colorDirty === true) {
  221. if (this.options.inheritColor == "to") {
  222. colorObj = {
  223. highlight: this.to.options.color.highlight.border,
  224. hover: this.to.options.color.hover.border,
  225. color: util.overrideOpacity(this.from.options.color.border, this.options.opacity)
  226. };
  227. }
  228. else if (this.options.inheritColor == "from" || this.options.inheritColor == true) {
  229. colorObj = {
  230. highlight: this.from.options.color.highlight.border,
  231. hover: this.from.options.color.hover.border,
  232. color: util.overrideOpacity(this.from.options.color.border, this.options.opacity)
  233. };
  234. }
  235. this.options.color = colorObj;
  236. this.colorDirty = false;
  237. }
  238. if (this.selected == true) {return colorObj.highlight;}
  239. else if (this.hover == true) {return colorObj.hover;}
  240. else {return colorObj.color;}
  241. };
  242. /**
  243. * Redraw a edge as a line
  244. * Draw this edge in the given canvas
  245. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  246. * @param {CanvasRenderingContext2D} ctx
  247. * @private
  248. */
  249. Edge.prototype._drawLine = function(ctx) {
  250. // set style
  251. ctx.strokeStyle = this._getColor(ctx);
  252. ctx.lineWidth = this._getLineWidth();
  253. if (this.from != this.to) {
  254. // draw line
  255. var via = this._line(ctx);
  256. // draw label
  257. var point;
  258. if (this.label) {
  259. if (this.options.smoothCurves.enabled == true && via != null) {
  260. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  261. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  262. point = {x:midpointX, y:midpointY};
  263. }
  264. else {
  265. point = this._pointOnLine(0.5);
  266. }
  267. this._label(ctx, this.label, point.x, point.y);
  268. }
  269. }
  270. else {
  271. var x, y;
  272. var radius = this.physics.springLength / 4;
  273. var node = this.from;
  274. if (!node.width) {
  275. node.resize(ctx);
  276. }
  277. if (node.width > node.height) {
  278. x = node.x + node.width / 2;
  279. y = node.y - radius;
  280. }
  281. else {
  282. x = node.x + radius;
  283. y = node.y - node.height / 2;
  284. }
  285. this._circle(ctx, x, y, radius);
  286. point = this._pointOnCircle(x, y, radius, 0.5);
  287. this._label(ctx, this.label, point.x, point.y);
  288. }
  289. };
  290. /**
  291. * Get the line width of the edge. Depends on width and whether one of the
  292. * connected nodes is selected.
  293. * @return {Number} width
  294. * @private
  295. */
  296. Edge.prototype._getLineWidth = function() {
  297. if (this.selected == true) {
  298. return Math.max(Math.min(this.widthSelected, this.options.widthMax), 0.3*this.networkScaleInv);
  299. }
  300. else {
  301. if (this.hover == true) {
  302. return Math.max(Math.min(this.options.hoverWidth, this.options.widthMax), 0.3*this.networkScaleInv);
  303. }
  304. else {
  305. return Math.max(this.options.width, 0.3*this.networkScaleInv);
  306. }
  307. }
  308. };
  309. Edge.prototype._getViaCoordinates = function () {
  310. if (this.options.smoothCurves.dynamic == true && this.options.smoothCurves.enabled == true ) {
  311. return this.via;
  312. }
  313. else if (this.options.smoothCurves.enabled == false) {
  314. return {x:0,y:0};
  315. }
  316. else {
  317. var xVia = null;
  318. var yVia = null;
  319. var factor = this.options.smoothCurves.roundness;
  320. var type = this.options.smoothCurves.type;
  321. var dx = Math.abs(this.from.x - this.to.x);
  322. var dy = Math.abs(this.from.y - this.to.y);
  323. if (type == 'discrete' || type == 'diagonalCross') {
  324. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
  325. if (this.from.y > this.to.y) {
  326. if (this.from.x < this.to.x) {
  327. xVia = this.from.x + factor * dy;
  328. yVia = this.from.y - factor * dy;
  329. }
  330. else if (this.from.x > this.to.x) {
  331. xVia = this.from.x - factor * dy;
  332. yVia = this.from.y - factor * dy;
  333. }
  334. }
  335. else if (this.from.y < this.to.y) {
  336. if (this.from.x < this.to.x) {
  337. xVia = this.from.x + factor * dy;
  338. yVia = this.from.y + factor * dy;
  339. }
  340. else if (this.from.x > this.to.x) {
  341. xVia = this.from.x - factor * dy;
  342. yVia = this.from.y + factor * dy;
  343. }
  344. }
  345. if (type == "discrete") {
  346. xVia = dx < factor * dy ? this.from.x : xVia;
  347. }
  348. }
  349. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
  350. if (this.from.y > this.to.y) {
  351. if (this.from.x < this.to.x) {
  352. xVia = this.from.x + factor * dx;
  353. yVia = this.from.y - factor * dx;
  354. }
  355. else if (this.from.x > this.to.x) {
  356. xVia = this.from.x - factor * dx;
  357. yVia = this.from.y - factor * dx;
  358. }
  359. }
  360. else if (this.from.y < this.to.y) {
  361. if (this.from.x < this.to.x) {
  362. xVia = this.from.x + factor * dx;
  363. yVia = this.from.y + factor * dx;
  364. }
  365. else if (this.from.x > this.to.x) {
  366. xVia = this.from.x - factor * dx;
  367. yVia = this.from.y + factor * dx;
  368. }
  369. }
  370. if (type == "discrete") {
  371. yVia = dy < factor * dx ? this.from.y : yVia;
  372. }
  373. }
  374. }
  375. else if (type == "straightCross") {
  376. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { // up - down
  377. xVia = this.from.x;
  378. if (this.from.y < this.to.y) {
  379. yVia = this.to.y - (1 - factor) * dy;
  380. }
  381. else {
  382. yVia = this.to.y + (1 - factor) * dy;
  383. }
  384. }
  385. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { // left - right
  386. if (this.from.x < this.to.x) {
  387. xVia = this.to.x - (1 - factor) * dx;
  388. }
  389. else {
  390. xVia = this.to.x + (1 - factor) * dx;
  391. }
  392. yVia = this.from.y;
  393. }
  394. }
  395. else if (type == 'horizontal') {
  396. if (this.from.x < this.to.x) {
  397. xVia = this.to.x - (1 - factor) * dx;
  398. }
  399. else {
  400. xVia = this.to.x + (1 - factor) * dx;
  401. }
  402. yVia = this.from.y;
  403. }
  404. else if (type == 'vertical') {
  405. xVia = this.from.x;
  406. if (this.from.y < this.to.y) {
  407. yVia = this.to.y - (1 - factor) * dy;
  408. }
  409. else {
  410. yVia = this.to.y + (1 - factor) * dy;
  411. }
  412. }
  413. else if (type == 'curvedCW') {
  414. var dx = this.to.x - this.from.x;
  415. var dy = this.from.y - this.to.y;
  416. var radius = Math.sqrt(dx*dx + dy*dy);
  417. var pi = Math.PI;
  418. var originalAngle = Math.atan2(dy,dx);
  419. var myAngle = (originalAngle + ((factor * 0.5) + 0.5) * pi) % (2 * pi);
  420. xVia = this.from.x + (factor*0.5 + 0.5)*radius*Math.sin(myAngle);
  421. yVia = this.from.y + (factor*0.5 + 0.5)*radius*Math.cos(myAngle);
  422. }
  423. else if (type == 'curvedCCW') {
  424. var dx = this.to.x - this.from.x;
  425. var dy = this.from.y - this.to.y;
  426. var radius = Math.sqrt(dx*dx + dy*dy);
  427. var pi = Math.PI;
  428. var originalAngle = Math.atan2(dy,dx);
  429. var myAngle = (originalAngle + ((-factor * 0.5) + 0.5) * pi) % (2 * pi);
  430. xVia = this.from.x + (factor*0.5 + 0.5)*radius*Math.sin(myAngle);
  431. yVia = this.from.y + (factor*0.5 + 0.5)*radius*Math.cos(myAngle);
  432. }
  433. else { // continuous
  434. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
  435. if (this.from.y > this.to.y) {
  436. if (this.from.x < this.to.x) {
  437. xVia = this.from.x + factor * dy;
  438. yVia = this.from.y - factor * dy;
  439. xVia = this.to.x < xVia ? this.to.x : xVia;
  440. }
  441. else if (this.from.x > this.to.x) {
  442. xVia = this.from.x - factor * dy;
  443. yVia = this.from.y - factor * dy;
  444. xVia = this.to.x > xVia ? this.to.x : xVia;
  445. }
  446. }
  447. else if (this.from.y < this.to.y) {
  448. if (this.from.x < this.to.x) {
  449. xVia = this.from.x + factor * dy;
  450. yVia = this.from.y + factor * dy;
  451. xVia = this.to.x < xVia ? this.to.x : xVia;
  452. }
  453. else if (this.from.x > this.to.x) {
  454. xVia = this.from.x - factor * dy;
  455. yVia = this.from.y + factor * dy;
  456. xVia = this.to.x > xVia ? this.to.x : xVia;
  457. }
  458. }
  459. }
  460. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
  461. if (this.from.y > this.to.y) {
  462. if (this.from.x < this.to.x) {
  463. xVia = this.from.x + factor * dx;
  464. yVia = this.from.y - factor * dx;
  465. yVia = this.to.y > yVia ? this.to.y : yVia;
  466. }
  467. else if (this.from.x > this.to.x) {
  468. xVia = this.from.x - factor * dx;
  469. yVia = this.from.y - factor * dx;
  470. yVia = this.to.y > yVia ? this.to.y : yVia;
  471. }
  472. }
  473. else if (this.from.y < this.to.y) {
  474. if (this.from.x < this.to.x) {
  475. xVia = this.from.x + factor * dx;
  476. yVia = this.from.y + factor * dx;
  477. yVia = this.to.y < yVia ? this.to.y : yVia;
  478. }
  479. else if (this.from.x > this.to.x) {
  480. xVia = this.from.x - factor * dx;
  481. yVia = this.from.y + factor * dx;
  482. yVia = this.to.y < yVia ? this.to.y : yVia;
  483. }
  484. }
  485. }
  486. }
  487. return {x: xVia, y: yVia};
  488. }
  489. };
  490. /**
  491. * Draw a line between two nodes
  492. * @param {CanvasRenderingContext2D} ctx
  493. * @private
  494. */
  495. Edge.prototype._line = function (ctx) {
  496. // draw a straight line
  497. ctx.beginPath();
  498. ctx.moveTo(this.from.x, this.from.y);
  499. if (this.options.smoothCurves.enabled == true) {
  500. if (this.options.smoothCurves.dynamic == false) {
  501. var via = this._getViaCoordinates();
  502. if (via.x == null) {
  503. ctx.lineTo(this.to.x, this.to.y);
  504. ctx.stroke();
  505. return null;
  506. }
  507. else {
  508. // this.via.x = via.x;
  509. // this.via.y = via.y;
  510. ctx.quadraticCurveTo(via.x,via.y,this.to.x, this.to.y);
  511. ctx.stroke();
  512. //ctx.circle(via.x,via.y,2)
  513. //ctx.stroke();
  514. return via;
  515. }
  516. }
  517. else {
  518. ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y);
  519. ctx.stroke();
  520. return this.via;
  521. }
  522. }
  523. else {
  524. ctx.lineTo(this.to.x, this.to.y);
  525. ctx.stroke();
  526. return null;
  527. }
  528. };
  529. /**
  530. * Draw a line from a node to itself, a circle
  531. * @param {CanvasRenderingContext2D} ctx
  532. * @param {Number} x
  533. * @param {Number} y
  534. * @param {Number} radius
  535. * @private
  536. */
  537. Edge.prototype._circle = function (ctx, x, y, radius) {
  538. // draw a circle
  539. ctx.beginPath();
  540. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  541. ctx.stroke();
  542. };
  543. /**
  544. * Draw label with white background and with the middle at (x, y)
  545. * @param {CanvasRenderingContext2D} ctx
  546. * @param {String} text
  547. * @param {Number} x
  548. * @param {Number} y
  549. * @private
  550. */
  551. Edge.prototype._label = function (ctx, text, x, y) {
  552. if (text) {
  553. ctx.font = ((this.from.selected || this.to.selected) ? "bold " : "") +
  554. this.options.fontSize + "px " + this.options.fontFace;
  555. var yLine;
  556. if (this.dirtyLabel == true) {
  557. var lines = String(text).split('\n');
  558. var lineCount = lines.length;
  559. var fontSize = Number(this.options.fontSize);
  560. yLine = y + (1 - lineCount) / 2 * fontSize;
  561. var width = ctx.measureText(lines[0]).width;
  562. for (var i = 1; i < lineCount; i++) {
  563. var lineWidth = ctx.measureText(lines[i]).width;
  564. width = lineWidth > width ? lineWidth : width;
  565. }
  566. var height = this.options.fontSize * lineCount;
  567. var left = x - width / 2;
  568. var top = y - height / 2;
  569. // cache
  570. this.labelDimensions = {top:top,left:left,width:width,height:height,yLine:yLine};
  571. }
  572. var yLine = this.labelDimensions.yLine;
  573. ctx.save();
  574. if (this.options.labelAlignment != "horizontal"){
  575. ctx.translate(x, yLine);
  576. this._rotateForLabelAlignment(ctx);
  577. x = 0;
  578. yLine = 0;
  579. }
  580. this._drawLabelRect(ctx);
  581. this._drawLabelText(ctx,x,yLine, lines, lineCount, fontSize);
  582. ctx.restore();
  583. }
  584. };
  585. /**
  586. * Rotates the canvas so the text is most readable
  587. * @param {CanvasRenderingContext2D} ctx
  588. * @private
  589. */
  590. Edge.prototype._rotateForLabelAlignment = function(ctx) {
  591. var dy = this.from.y - this.to.y;
  592. var dx = this.from.x - this.to.x;
  593. var angleInDegrees = Math.atan2(dy, dx);
  594. // rotate so label it is readable
  595. if((angleInDegrees < -1 && dx < 0) || (angleInDegrees > 0 && dx < 0)){
  596. angleInDegrees = angleInDegrees + Math.PI;
  597. }
  598. ctx.rotate(angleInDegrees);
  599. };
  600. /**
  601. * Draws the label rectangle
  602. * @param {CanvasRenderingContext2D} ctx
  603. * @param {String} labelAlignment
  604. * @private
  605. */
  606. Edge.prototype._drawLabelRect = function(ctx) {
  607. if (this.options.fontFill !== undefined && this.options.fontFill !== null && this.options.fontFill !== "none") {
  608. ctx.fillStyle = this.options.fontFill;
  609. var lineMargin = 2;
  610. if (this.options.labelAlignment == 'line-center') {
  611. ctx.fillRect(-this.labelDimensions.width * 0.5, -this.labelDimensions.height * 0.5, this.labelDimensions.width, this.labelDimensions.height);
  612. }
  613. else if (this.options.labelAlignment == 'line-above') {
  614. ctx.fillRect(-this.labelDimensions.width * 0.5, -(this.labelDimensions.height + lineMargin), this.labelDimensions.width, this.labelDimensions.height);
  615. }
  616. else if (this.options.labelAlignment == 'line-below') {
  617. ctx.fillRect(-this.labelDimensions.width * 0.5, lineMargin, this.labelDimensions.width, this.labelDimensions.height);
  618. }
  619. else {
  620. ctx.fillRect(this.labelDimensions.left, this.labelDimensions.top, this.labelDimensions.width, this.labelDimensions.height);
  621. }
  622. }
  623. };
  624. /**
  625. * Draws the label text
  626. * @param {CanvasRenderingContext2D} ctx
  627. * @param {Number} x
  628. * @param {Number} yLine
  629. * @param {Array} lines
  630. * @param {Number} lineCount
  631. * @param {Number} fontSize
  632. * @private
  633. */
  634. Edge.prototype._drawLabelText = function(ctx, x, yLine, lines, lineCount, fontSize) {
  635. // draw text
  636. ctx.fillStyle = this.options.fontColor || "black";
  637. ctx.textAlign = "center";
  638. // check for label alignment
  639. if (this.options.labelAlignment != 'horizontal') {
  640. var lineMargin = 2;
  641. if (this.options.labelAlignment == 'line-above') {
  642. ctx.textBaseline = "alphabetic";
  643. yLine -= 2 * lineMargin; // distance from edge, required because we use alphabetic. Alphabetic has less difference between browsers
  644. }
  645. else if (this.options.labelAlignment == 'line-below') {
  646. ctx.textBaseline = "hanging";
  647. yLine += 2 * lineMargin;// distance from edge, required because we use hanging. Hanging has less difference between browsers
  648. }
  649. else {
  650. ctx.textBaseline = "middle";
  651. }
  652. }
  653. else {
  654. ctx.textBaseline = "middle";
  655. }
  656. // check for strokeWidth
  657. if (this.options.fontStrokeWidth > 0){
  658. ctx.lineWidth = this.options.fontStrokeWidth;
  659. ctx.strokeStyle = this.options.fontStrokeColor;
  660. ctx.lineJoin = 'round';
  661. }
  662. for (var i = 0; i < lineCount; i++) {
  663. if(this.options.fontStrokeWidth > 0){
  664. ctx.strokeText(lines[i], x, yLine);
  665. }
  666. ctx.fillText(lines[i], x, yLine);
  667. yLine += fontSize;
  668. }
  669. };
  670. /**
  671. * Redraw a edge as a dashed line
  672. * Draw this edge in the given canvas
  673. * @author David Jordan
  674. * @date 2012-08-08
  675. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  676. * @param {CanvasRenderingContext2D} ctx
  677. * @private
  678. */
  679. Edge.prototype._drawDashLine = function(ctx) {
  680. // set style
  681. ctx.strokeStyle = this._getColor(ctx);
  682. ctx.lineWidth = this._getLineWidth();
  683. var via = null;
  684. // only firefox and chrome support this method, else we use the legacy one.
  685. if (ctx.setLineDash !== undefined) {
  686. ctx.save();
  687. // configure the dash pattern
  688. var pattern = [0];
  689. if (this.options.dash.length !== undefined && this.options.dash.gap !== undefined) {
  690. pattern = [this.options.dash.length,this.options.dash.gap];
  691. }
  692. else {
  693. pattern = [5,5];
  694. }
  695. // set dash settings for chrome or firefox
  696. ctx.setLineDash(pattern);
  697. ctx.lineDashOffset = 0;
  698. // draw the line
  699. via = this._line(ctx);
  700. // restore the dash settings.
  701. ctx.setLineDash([0]);
  702. ctx.lineDashOffset = 0;
  703. ctx.restore();
  704. }
  705. else { // unsupporting smooth lines
  706. // draw dashed line
  707. ctx.beginPath();
  708. ctx.lineCap = 'round';
  709. if (this.options.dash.altLength !== undefined) //If an alt dash value has been set add to the array this value
  710. {
  711. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  712. [this.options.dash.length,this.options.dash.gap,this.options.dash.altLength,this.options.dash.gap]);
  713. }
  714. else if (this.options.dash.length !== undefined && this.options.dash.gap !== undefined) //If a dash and gap value has been set add to the array this value
  715. {
  716. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  717. [this.options.dash.length,this.options.dash.gap]);
  718. }
  719. else //If all else fails draw a line
  720. {
  721. ctx.moveTo(this.from.x, this.from.y);
  722. ctx.lineTo(this.to.x, this.to.y);
  723. }
  724. ctx.stroke();
  725. }
  726. // draw label
  727. if (this.label) {
  728. var point;
  729. if (this.options.smoothCurves.enabled == true && via != null) {
  730. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  731. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  732. point = {x:midpointX, y:midpointY};
  733. }
  734. else {
  735. point = this._pointOnLine(0.5);
  736. }
  737. this._label(ctx, this.label, point.x, point.y);
  738. }
  739. };
  740. /**
  741. * Get a point on a line
  742. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  743. * @return {Object} point
  744. * @private
  745. */
  746. Edge.prototype._pointOnLine = function (percentage) {
  747. return {
  748. x: (1 - percentage) * this.from.x + percentage * this.to.x,
  749. y: (1 - percentage) * this.from.y + percentage * this.to.y
  750. }
  751. };
  752. /**
  753. * Get a point on a circle
  754. * @param {Number} x
  755. * @param {Number} y
  756. * @param {Number} radius
  757. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  758. * @return {Object} point
  759. * @private
  760. */
  761. Edge.prototype._pointOnCircle = function (x, y, radius, percentage) {
  762. var angle = (percentage - 3/8) * 2 * Math.PI;
  763. return {
  764. x: x + radius * Math.cos(angle),
  765. y: y - radius * Math.sin(angle)
  766. }
  767. };
  768. /**
  769. * Redraw a edge as a line with an arrow halfway the line
  770. * Draw this edge in the given canvas
  771. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  772. * @param {CanvasRenderingContext2D} ctx
  773. * @private
  774. */
  775. Edge.prototype._drawArrowCenter = function(ctx) {
  776. var point;
  777. // set style
  778. ctx.strokeStyle = this._getColor(ctx);
  779. ctx.fillStyle = ctx.strokeStyle;
  780. ctx.lineWidth = this._getLineWidth();
  781. if (this.from != this.to) {
  782. // draw line
  783. var via = this._line(ctx);
  784. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  785. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  786. // draw an arrow halfway the line
  787. if (this.options.smoothCurves.enabled == true && via != null) {
  788. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  789. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  790. point = {x:midpointX, y:midpointY};
  791. }
  792. else {
  793. point = this._pointOnLine(0.5);
  794. }
  795. ctx.arrow(point.x, point.y, angle, length);
  796. ctx.fill();
  797. ctx.stroke();
  798. // draw label
  799. if (this.label) {
  800. this._label(ctx, this.label, point.x, point.y);
  801. }
  802. }
  803. else {
  804. // draw circle
  805. var x, y;
  806. var radius = 0.25 * Math.max(100,this.physics.springLength);
  807. var node = this.from;
  808. if (!node.width) {
  809. node.resize(ctx);
  810. }
  811. if (node.width > node.height) {
  812. x = node.x + node.width * 0.5;
  813. y = node.y - radius;
  814. }
  815. else {
  816. x = node.x + radius;
  817. y = node.y - node.height * 0.5;
  818. }
  819. this._circle(ctx, x, y, radius);
  820. // draw all arrows
  821. var angle = 0.2 * Math.PI;
  822. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  823. point = this._pointOnCircle(x, y, radius, 0.5);
  824. ctx.arrow(point.x, point.y, angle, length);
  825. ctx.fill();
  826. ctx.stroke();
  827. // draw label
  828. if (this.label) {
  829. point = this._pointOnCircle(x, y, radius, 0.5);
  830. this._label(ctx, this.label, point.x, point.y);
  831. }
  832. }
  833. };
  834. Edge.prototype._pointOnBezier = function(t) {
  835. var via = this._getViaCoordinates();
  836. var x = Math.pow(1-t,2)*this.from.x + (2*t*(1 - t))*via.x + Math.pow(t,2)*this.to.x;
  837. var y = Math.pow(1-t,2)*this.from.y + (2*t*(1 - t))*via.y + Math.pow(t,2)*this.to.y;
  838. return {x:x,y:y};
  839. }
  840. /**
  841. * This function uses binary search to look for the point where the bezier curve crosses the border of the node.
  842. *
  843. * @param from
  844. * @param ctx
  845. * @returns {*}
  846. * @private
  847. */
  848. Edge.prototype._findBorderPosition = function(from,ctx) {
  849. var maxIterations = 10;
  850. var iteration = 0;
  851. var low = 0;
  852. var high = 1;
  853. var pos,angle,distanceToBorder, distanceToNodes, difference;
  854. var threshold = 0.2;
  855. var node = this.to;
  856. if (from == true) {
  857. node = this.from;
  858. }
  859. while (low <= high && iteration < maxIterations) {
  860. var middle = (low + high) * 0.5;
  861. pos = this._pointOnBezier(middle);
  862. angle = Math.atan2((node.y - pos.y), (node.x - pos.x));
  863. distanceToBorder = node.distanceToBorder(ctx,angle);
  864. distanceToNodes = Math.sqrt(Math.pow(pos.x-node.x,2) + Math.pow(pos.y-node.y,2));
  865. difference = distanceToBorder - distanceToNodes;
  866. if (Math.abs(difference) < threshold) {
  867. break; // found
  868. }
  869. else if (difference < 0) { // distance to nodes is larger than distance to border --> t needs to be bigger if we're looking at the to node.
  870. if (from == false) {
  871. low = middle;
  872. }
  873. else {
  874. high = middle;
  875. }
  876. }
  877. else {
  878. if (from == false) {
  879. high = middle;
  880. }
  881. else {
  882. low = middle;
  883. }
  884. }
  885. iteration++;
  886. }
  887. pos.t = middle;
  888. return pos;
  889. };
  890. /**
  891. * Redraw a edge as a line with an arrow
  892. * Draw this edge in the given canvas
  893. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  894. * @param {CanvasRenderingContext2D} ctx
  895. * @private
  896. */
  897. Edge.prototype._drawArrow = function(ctx) {
  898. // set style
  899. ctx.strokeStyle = this._getColor(ctx);
  900. ctx.fillStyle = ctx.strokeStyle;
  901. ctx.lineWidth = this._getLineWidth();
  902. // set vars
  903. var angle, length, arrowPos;
  904. // if not connected to itself
  905. if (this.from != this.to) {
  906. // draw line
  907. this._line(ctx);
  908. // draw arrow head
  909. if (this.options.smoothCurves.enabled == true) {
  910. var via = this._getViaCoordinates();
  911. arrowPos = this._findBorderPosition(false, ctx);
  912. var guidePos = this._pointOnBezier(Math.max(0.0, arrowPos.t - 0.1))
  913. angle = Math.atan2((arrowPos.y - guidePos.y), (arrowPos.x - guidePos.x));
  914. }
  915. else {
  916. angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  917. var dx = (this.to.x - this.from.x);
  918. var dy = (this.to.y - this.from.y);
  919. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  920. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  921. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  922. arrowPos = {};
  923. arrowPos.x = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  924. arrowPos.y = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  925. }
  926. // draw arrow at the end of the line
  927. length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  928. ctx.arrow(arrowPos.x,arrowPos.y, angle, length);
  929. ctx.fill();
  930. ctx.stroke();
  931. // draw label
  932. if (this.label) {
  933. var point;
  934. if (this.options.smoothCurves.enabled == true && via != null) {
  935. point = this._pointOnBezier(0.5);
  936. }
  937. else {
  938. point = this._pointOnLine(0.5);
  939. }
  940. this._label(ctx, this.label, point.x, point.y);
  941. }
  942. }
  943. else {
  944. // draw circle
  945. var node = this.from;
  946. var x, y, arrow;
  947. var radius = 0.25 * Math.max(100,this.physics.springLength);
  948. if (!node.width) {
  949. node.resize(ctx);
  950. }
  951. if (node.width > node.height) {
  952. x = node.x + node.width * 0.5;
  953. y = node.y - radius;
  954. arrow = {
  955. x: x,
  956. y: node.y,
  957. angle: 0.9 * Math.PI
  958. };
  959. }
  960. else {
  961. x = node.x + radius;
  962. y = node.y - node.height * 0.5;
  963. arrow = {
  964. x: node.x,
  965. y: y,
  966. angle: 0.6 * Math.PI
  967. };
  968. }
  969. ctx.beginPath();
  970. // TODO: similarly, for a line without arrows, draw to the border of the nodes instead of the center
  971. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  972. ctx.stroke();
  973. // draw all arrows
  974. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  975. ctx.arrow(arrow.x, arrow.y, arrow.angle, length);
  976. ctx.fill();
  977. ctx.stroke();
  978. // draw label
  979. if (this.label) {
  980. point = this._pointOnCircle(x, y, radius, 0.5);
  981. this._label(ctx, this.label, point.x, point.y);
  982. }
  983. }
  984. };
  985. /**
  986. * Calculate the distance between a point (x3,y3) and a line segment from
  987. * (x1,y1) to (x2,y2).
  988. * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
  989. * @param {number} x1
  990. * @param {number} y1
  991. * @param {number} x2
  992. * @param {number} y2
  993. * @param {number} x3
  994. * @param {number} y3
  995. * @private
  996. */
  997. Edge.prototype._getDistanceToEdge = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is the point
  998. var returnValue = 0;
  999. if (this.from != this.to) {
  1000. if (this.options.smoothCurves.enabled == true) {
  1001. var xVia, yVia;
  1002. if (this.options.smoothCurves.enabled == true && this.options.smoothCurves.dynamic == true) {
  1003. xVia = this.via.x;
  1004. yVia = this.via.y;
  1005. }
  1006. else {
  1007. var via = this._getViaCoordinates();
  1008. xVia = via.x;
  1009. yVia = via.y;
  1010. }
  1011. var minDistance = 1e9;
  1012. var distance;
  1013. var i,t,x,y, lastX, lastY;
  1014. for (i = 0; i < 10; i++) {
  1015. t = 0.1*i;
  1016. x = Math.pow(1-t,2)*x1 + (2*t*(1 - t))*xVia + Math.pow(t,2)*x2;
  1017. y = Math.pow(1-t,2)*y1 + (2*t*(1 - t))*yVia + Math.pow(t,2)*y2;
  1018. if (i > 0) {
  1019. distance = this._getDistanceToLine(lastX,lastY,x,y, x3,y3);
  1020. minDistance = distance < minDistance ? distance : minDistance;
  1021. }
  1022. lastX = x; lastY = y;
  1023. }
  1024. returnValue = minDistance;
  1025. }
  1026. else {
  1027. returnValue = this._getDistanceToLine(x1,y1,x2,y2,x3,y3);
  1028. }
  1029. }
  1030. else {
  1031. var x, y, dx, dy;
  1032. var radius = 0.25 * this.physics.springLength;
  1033. var node = this.from;
  1034. if (node.width > node.height) {
  1035. x = node.x + 0.5 * node.width;
  1036. y = node.y - radius;
  1037. }
  1038. else {
  1039. x = node.x + radius;
  1040. y = node.y - 0.5 * node.height;
  1041. }
  1042. dx = x - x3;
  1043. dy = y - y3;
  1044. returnValue = Math.abs(Math.sqrt(dx*dx + dy*dy) - radius);
  1045. }
  1046. if (this.labelDimensions.left < x3 &&
  1047. this.labelDimensions.left + this.labelDimensions.width > x3 &&
  1048. this.labelDimensions.top < y3 &&
  1049. this.labelDimensions.top + this.labelDimensions.height > y3) {
  1050. return 0;
  1051. }
  1052. else {
  1053. return returnValue;
  1054. }
  1055. };
  1056. Edge.prototype._getDistanceToLine = function(x1,y1,x2,y2,x3,y3) {
  1057. var px = x2-x1,
  1058. py = y2-y1,
  1059. something = px*px + py*py,
  1060. u = ((x3 - x1) * px + (y3 - y1) * py) / something;
  1061. if (u > 1) {
  1062. u = 1;
  1063. }
  1064. else if (u < 0) {
  1065. u = 0;
  1066. }
  1067. var x = x1 + u * px,
  1068. y = y1 + u * py,
  1069. dx = x - x3,
  1070. dy = y - y3;
  1071. //# Note: If the actual distance does not matter,
  1072. //# if you only want to compare what this function
  1073. //# returns to other results of this function, you
  1074. //# can just return the squared distance instead
  1075. //# (i.e. remove the sqrt) to gain a little performance
  1076. return Math.sqrt(dx*dx + dy*dy);
  1077. };
  1078. /**
  1079. * This allows the zoom level of the network to influence the rendering
  1080. *
  1081. * @param scale
  1082. */
  1083. Edge.prototype.setScale = function(scale) {
  1084. this.networkScaleInv = 1.0/scale;
  1085. };
  1086. Edge.prototype.select = function() {
  1087. this.selected = true;
  1088. };
  1089. Edge.prototype.unselect = function() {
  1090. this.selected = false;
  1091. };
  1092. Edge.prototype.positionBezierNode = function() {
  1093. if (this.via !== null && this.from !== null && this.to !== null) {
  1094. this.via.x = 0.5 * (this.from.x + this.to.x);
  1095. this.via.y = 0.5 * (this.from.y + this.to.y);
  1096. }
  1097. else if (this.via !== null) {
  1098. this.via.x = 0;
  1099. this.via.y = 0;
  1100. }
  1101. };
  1102. /**
  1103. * This function draws the control nodes for the manipulator.
  1104. * In order to enable this, only set the this.controlNodesEnabled to true.
  1105. * @param ctx
  1106. */
  1107. Edge.prototype._drawControlNodes = function(ctx) {
  1108. if (this.controlNodesEnabled == true) {
  1109. if (this.controlNodes.from === null && this.controlNodes.to === null) {
  1110. var nodeIdFrom = "edgeIdFrom:".concat(this.id);
  1111. var nodeIdTo = "edgeIdTo:".concat(this.id);
  1112. var constants = {
  1113. nodes:{group:'', radius:7, borderWidth:2, borderWidthSelected: 2},
  1114. physics:{damping:0},
  1115. clustering: {maxNodeSizeIncrements: 0 ,nodeScaling: {width:0, height: 0, radius:0}}
  1116. };
  1117. this.controlNodes.from = new Node(
  1118. {id:nodeIdFrom,
  1119. shape:'dot',
  1120. color:{background:'#ff0000', border:'#3c3c3c', highlight: {background:'#07f968'}}
  1121. },{},{},constants);
  1122. this.controlNodes.to = new Node(
  1123. {id:nodeIdTo,
  1124. shape:'dot',
  1125. color:{background:'#ff0000', border:'#3c3c3c', highlight: {background:'#07f968'}}
  1126. },{},{},constants);
  1127. }
  1128. this.controlNodes.positions = {};
  1129. if (this.controlNodes.from.selected == false) {
  1130. this.controlNodes.positions.from = this.getControlNodeFromPosition(ctx);
  1131. this.controlNodes.from.x = this.controlNodes.positions.from.x;
  1132. this.controlNodes.from.y = this.controlNodes.positions.from.y;
  1133. }
  1134. if (this.controlNodes.to.selected == false) {
  1135. this.controlNodes.positions.to = this.getControlNodeToPosition(ctx);
  1136. this.controlNodes.to.x = this.controlNodes.positions.to.x;
  1137. this.controlNodes.to.y = this.controlNodes.positions.to.y;
  1138. }
  1139. this.controlNodes.from.draw(ctx);
  1140. this.controlNodes.to.draw(ctx);
  1141. }
  1142. else {
  1143. this.controlNodes = {from:null, to:null, positions:{}};
  1144. }
  1145. };
  1146. /**
  1147. * Enable control nodes.
  1148. * @private
  1149. */
  1150. Edge.prototype._enableControlNodes = function() {
  1151. this.fromBackup = this.from;
  1152. this.toBackup = this.to;
  1153. this.controlNodesEnabled = true;
  1154. };
  1155. /**
  1156. * disable control nodes and remove from dynamicEdges from old node
  1157. * @private
  1158. */
  1159. Edge.prototype._disableControlNodes = function() {
  1160. this.fromId = this.from.id;
  1161. this.toId = this.to.id;
  1162. if (this.fromId != this.fromBackup.id) { // from was changed, remove edge from old 'from' node dynamic edges
  1163. this.fromBackup.detachEdge(this);
  1164. }
  1165. else if (this.toId != this.toBackup.id) { // to was changed, remove edge from old 'to' node dynamic edges
  1166. this.toBackup.detachEdge(this);
  1167. }
  1168. this.fromBackup = null;
  1169. this.toBackup = null;
  1170. this.controlNodesEnabled = false;
  1171. };
  1172. /**
  1173. * This checks if one of the control nodes is selected and if so, returns the control node object. Else it returns null.
  1174. * @param x
  1175. * @param y
  1176. * @returns {null}
  1177. * @private
  1178. */
  1179. Edge.prototype._getSelectedControlNode = function(x,y) {
  1180. var positions = this.controlNodes.positions;
  1181. var fromDistance = Math.sqrt(Math.pow(x - positions.from.x,2) + Math.pow(y - positions.from.y,2));
  1182. var toDistance = Math.sqrt(Math.pow(x - positions.to.x ,2) + Math.pow(y - positions.to.y ,2));
  1183. if (fromDistance < 15) {
  1184. this.connectedNode = this.from;
  1185. this.from = this.controlNodes.from;
  1186. return this.controlNodes.from;
  1187. }
  1188. else if (toDistance < 15) {
  1189. this.connectedNode = this.to;
  1190. this.to = this.controlNodes.to;
  1191. return this.controlNodes.to;
  1192. }
  1193. else {
  1194. return null;
  1195. }
  1196. };
  1197. /**
  1198. * this resets the control nodes to their original position.
  1199. * @private
  1200. */
  1201. Edge.prototype._restoreControlNodes = function() {
  1202. if (this.controlNodes.from.selected == true) {
  1203. this.from = this.connectedNode;
  1204. this.connectedNode = null;
  1205. this.controlNodes.from.unselect();
  1206. }
  1207. else if (this.controlNodes.to.selected == true) {
  1208. this.to = this.connectedNode;
  1209. this.connectedNode = null;
  1210. this.controlNodes.to.unselect();
  1211. }
  1212. };
  1213. /**
  1214. * this calculates the position of the control nodes on the edges of the parent nodes.
  1215. *
  1216. * @param ctx
  1217. * @returns {x: *, y: *}
  1218. */
  1219. Edge.prototype.getControlNodeFromPosition = function(ctx) {
  1220. // draw arrow head
  1221. var controlnodeFromPos;
  1222. if (this.options.smoothCurves.enabled == true) {
  1223. controlnodeFromPos = this._findBorderPosition(true, ctx);
  1224. }
  1225. else {
  1226. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  1227. var dx = (this.to.x - this.from.x);
  1228. var dy = (this.to.y - this.from.y);
  1229. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  1230. var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
  1231. var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
  1232. controlnodeFromPos = {};
  1233. controlnodeFromPos.x = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
  1234. controlnodeFromPos.y = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
  1235. }
  1236. return controlnodeFromPos;
  1237. };
  1238. /**
  1239. * this calculates the position of the control nodes on the edges of the parent nodes.
  1240. *
  1241. * @param ctx
  1242. * @returns {{from: {x: number, y: number}, to: {x: *, y: *}}}
  1243. */
  1244. Edge.prototype.getControlNodeToPosition = function(ctx) {
  1245. // draw arrow head
  1246. var controlnodeFromPos,controlnodeToPos;
  1247. if (this.options.smoothCurves.enabled == true) {
  1248. controlnodeToPos = this._findBorderPosition(false, ctx);
  1249. }
  1250. else {
  1251. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  1252. var dx = (this.to.x - this.from.x);
  1253. var dy = (this.to.y - this.from.y);
  1254. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  1255. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  1256. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  1257. controlnodeToPos = {};
  1258. controlnodeToPos.x = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  1259. controlnodeToPos.y = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  1260. }
  1261. return controlnodeToPos;
  1262. };
  1263. module.exports = Edge;