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.

1520 lines
45 KiB

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