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.

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