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.

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