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.

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