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.

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