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.

711 lines
24 KiB

9 years ago
9 years ago
9 years ago
10 years ago
  1. var util = require('../../../util');
  2. var Label = require('./shared/Label').default;
  3. var ComponentUtil = require('./shared/ComponentUtil').default;
  4. var CubicBezierEdge = require('./edges/CubicBezierEdge').default;
  5. var BezierEdgeDynamic = require('./edges/BezierEdgeDynamic').default;
  6. var BezierEdgeStatic = require('./edges/BezierEdgeStatic').default;
  7. var StraightEdge = require('./edges/StraightEdge').default;
  8. /**
  9. * An edge connects two nodes and has a specific direction.
  10. */
  11. class Edge {
  12. /**
  13. * @param {Object} options values specific to this edge, must contain at least 'from' and 'to'
  14. * @param {Object} body shared state from Network instance
  15. * @param {Object} globalOptions options from the EdgesHandler instance
  16. * @param {Object} defaultOptions default options from the EdgeHandler instance. Value and reference are constant
  17. * @param {Object} edgeOptions option values specific for edges.
  18. */
  19. constructor(options, body, globalOptions, defaultOptions, edgeOptions) {
  20. if (body === undefined) {
  21. throw new Error("No body provided");
  22. }
  23. // Since globalOptions is constant in values as well as reference,
  24. // Following needs to be done only once.
  25. this.options = util.bridgeObject(globalOptions);
  26. this.globalOptions = globalOptions;
  27. this.defaultOptions = defaultOptions;
  28. this.edgeOptions = edgeOptions;
  29. this.body = body;
  30. // initialize variables
  31. this.id = undefined;
  32. this.fromId = undefined;
  33. this.toId = undefined;
  34. this.selected = false;
  35. this.hover = false;
  36. this.labelDirty = true;
  37. this.baseWidth = this.options.width;
  38. this.baseFontSize = this.options.font.size;
  39. this.from = undefined; // a node
  40. this.to = undefined; // a node
  41. this.edgeType = undefined;
  42. this.connected = false;
  43. this.labelModule = new Label(this.body, this.options, true /* It's an edge label */);
  44. this.setOptions(options);
  45. }
  46. /**
  47. * Set or overwrite options for the edge
  48. * @param {Object} options an object with options
  49. * @returns {null|boolean} null if no options, boolean if date changed
  50. */
  51. setOptions(options) {
  52. if (!options) {
  53. return;
  54. }
  55. Edge.parseOptions(this.options, options, true, this.globalOptions);
  56. if (options.id !== undefined) {
  57. this.id = options.id;
  58. }
  59. if (options.from !== undefined) {
  60. this.fromId = options.from;
  61. }
  62. if (options.to !== undefined) {
  63. this.toId = options.to;
  64. }
  65. if (options.title !== undefined) {
  66. this.title = options.title;
  67. }
  68. if (options.value !== undefined) {
  69. options.value = parseFloat(options.value);
  70. }
  71. let pile = [options, this.options, this.edgeOptions, this.defaultOptions];
  72. this.chooser = ComponentUtil.choosify('edge', pile);
  73. // update label Module
  74. this.updateLabelModule(options);
  75. this.labelModule.propagateFonts(this.edgeOptions, options, this.defaultOptions);
  76. let dataChanged = this.updateEdgeType();
  77. // if anything has been updates, reset the selection width and the hover width
  78. this._setInteractionWidths();
  79. // A node is connected when it has a from and to node that both exist in the network.body.nodes.
  80. this.connect();
  81. if (options.hidden !== undefined || options.physics !== undefined) {
  82. dataChanged = true;
  83. }
  84. return dataChanged;
  85. }
  86. /**
  87. *
  88. * @param {Object} parentOptions
  89. * @param {Object} newOptions
  90. * @param {boolean} [allowDeletion=false]
  91. * @param {Object} [globalOptions={}]
  92. * @param {boolean} [copyFromGlobals=false]
  93. */
  94. static parseOptions(parentOptions, newOptions, allowDeletion = false, globalOptions = {}, copyFromGlobals = false) {
  95. var fields = [
  96. 'arrowStrikethrough',
  97. 'id',
  98. 'from',
  99. 'hidden',
  100. 'hoverWidth',
  101. 'label',
  102. 'labelHighlightBold',
  103. 'length',
  104. 'line',
  105. 'opacity',
  106. 'physics',
  107. 'scaling',
  108. 'selectionWidth',
  109. 'selfReferenceSize',
  110. 'to',
  111. 'title',
  112. 'value',
  113. 'width'
  114. ];
  115. // only deep extend the items in the field array. These do not have shorthand.
  116. util.selectiveDeepExtend(fields, parentOptions, newOptions, allowDeletion);
  117. util.mergeOptions(parentOptions, newOptions, 'smooth', globalOptions);
  118. util.mergeOptions(parentOptions, newOptions, 'shadow', globalOptions);
  119. if (newOptions.dashes !== undefined && newOptions.dashes !== null) {
  120. parentOptions.dashes = newOptions.dashes;
  121. }
  122. else if (allowDeletion === true && newOptions.dashes === null) {
  123. parentOptions.dashes = Object.create(globalOptions.dashes); // this sets the pointer of the option back to the global option.
  124. }
  125. // set the scaling newOptions
  126. if (newOptions.scaling !== undefined && newOptions.scaling !== null) {
  127. if (newOptions.scaling.min !== undefined) {parentOptions.scaling.min = newOptions.scaling.min;}
  128. if (newOptions.scaling.max !== undefined) {parentOptions.scaling.max = newOptions.scaling.max;}
  129. util.mergeOptions(parentOptions.scaling, newOptions.scaling, 'label', globalOptions.scaling);
  130. }
  131. else if (allowDeletion === true && newOptions.scaling === null) {
  132. parentOptions.scaling = Object.create(globalOptions.scaling); // this sets the pointer of the option back to the global option.
  133. }
  134. // handle multiple input cases for arrows
  135. if (newOptions.arrows !== undefined && newOptions.arrows !== null) {
  136. if (typeof newOptions.arrows === 'string') {
  137. let arrows = newOptions.arrows.toLowerCase();
  138. parentOptions.arrows.to.enabled = arrows.indexOf("to") != -1;
  139. parentOptions.arrows.middle.enabled = arrows.indexOf("middle") != -1;
  140. parentOptions.arrows.from.enabled = arrows.indexOf("from") != -1;
  141. }
  142. else if (typeof newOptions.arrows === 'object') {
  143. util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'to', globalOptions.arrows);
  144. util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'middle', globalOptions.arrows);
  145. util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'from', globalOptions.arrows);
  146. }
  147. else {
  148. throw new Error("The arrow newOptions can only be an object or a string. Refer to the documentation. You used:" + JSON.stringify(newOptions.arrows));
  149. }
  150. }
  151. else if (allowDeletion === true && newOptions.arrows === null) {
  152. parentOptions.arrows = Object.create(globalOptions.arrows); // this sets the pointer of the option back to the global option.
  153. }
  154. // handle multiple input cases for color
  155. if (newOptions.color !== undefined && newOptions.color !== null) {
  156. let fromColor = newOptions.color;
  157. let toColor = parentOptions.color;
  158. // If passed, fill in values from default options - required in the case of no prototype bridging
  159. if (copyFromGlobals) {
  160. util.deepExtend(toColor, globalOptions.color, false, allowDeletion);
  161. } else {
  162. // Clear local properties - need to do it like this in order to retain prototype bridges
  163. for (var i in toColor) {
  164. if (toColor.hasOwnProperty(i)) {
  165. delete toColor[i];
  166. }
  167. }
  168. }
  169. if (util.isString(toColor)) {
  170. toColor.color = toColor;
  171. toColor.highlight = toColor;
  172. toColor.hover = toColor;
  173. toColor.inherit = false;
  174. if (fromColor.opacity === undefined) {
  175. toColor.opacity = 1.0; // set default
  176. }
  177. }
  178. else {
  179. let colorsDefined = false;
  180. if (fromColor.color !== undefined) {toColor.color = fromColor.color; colorsDefined = true;}
  181. if (fromColor.highlight !== undefined) {toColor.highlight = fromColor.highlight; colorsDefined = true;}
  182. if (fromColor.hover !== undefined) {toColor.hover = fromColor.hover; colorsDefined = true;}
  183. if (fromColor.inherit !== undefined) {toColor.inherit = fromColor.inherit;}
  184. if (fromColor.opacity !== undefined) {toColor.opacity = Math.min(1,Math.max(0,fromColor.opacity));}
  185. if (colorsDefined === true) {
  186. toColor.inherit = false;
  187. } else {
  188. if (toColor.inherit === undefined) {
  189. toColor.inherit = 'from'; // Set default
  190. }
  191. }
  192. }
  193. }
  194. else if (allowDeletion === true && newOptions.color === null) {
  195. parentOptions.color = util.bridgeObject(globalOptions.color); // set the object back to the global options
  196. }
  197. // handle the font settings
  198. if (newOptions.font !== undefined && newOptions.font !== null) {
  199. Label.parseOptions(parentOptions.font, newOptions);
  200. }
  201. else if (allowDeletion === true && newOptions.font === null) {
  202. parentOptions.font = util.bridgeObject(globalOptions.font); // set the object back to the global options
  203. }
  204. }
  205. /**
  206. *
  207. * @returns {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}}
  208. */
  209. getFormattingValues() {
  210. let toArrow = (this.options.arrows.to === true) || (this.options.arrows.to.enabled === true)
  211. let fromArrow = (this.options.arrows.from === true) || (this.options.arrows.from.enabled === true)
  212. let middleArrow = (this.options.arrows.middle === true) || (this.options.arrows.middle.enabled === true)
  213. let inheritsColor = this.options.color.inherit;
  214. let values = {
  215. toArrow: toArrow,
  216. toArrowScale: this.options.arrows.to.scaleFactor,
  217. toArrowType: this.options.arrows.to.type,
  218. middleArrow: middleArrow,
  219. middleArrowScale: this.options.arrows.middle.scaleFactor,
  220. middleArrowType: this.options.arrows.middle.type,
  221. fromArrow: fromArrow,
  222. fromArrowScale: this.options.arrows.from.scaleFactor,
  223. fromArrowType: this.options.arrows.from.type,
  224. arrowStrikethrough: this.options.arrowStrikethrough,
  225. color: (inheritsColor? undefined : this.options.color.color),
  226. inheritsColor: inheritsColor,
  227. opacity: this.options.color.opacity,
  228. hidden: this.options.hidden,
  229. length: this.options.length,
  230. shadow: this.options.shadow.enabled,
  231. shadowColor: this.options.shadow.color,
  232. shadowSize: this.options.shadow.size,
  233. shadowX: this.options.shadow.x,
  234. shadowY: this.options.shadow.y,
  235. dashes: this.options.dashes,
  236. width: this.options.width
  237. };
  238. if (this.selected || this.hover) {
  239. if (this.chooser === true) {
  240. if (this.selected) {
  241. let selectedWidth = this.options.selectionWidth;
  242. if (typeof selectedWidth === 'function') {
  243. values.width = selectedWidth(values.width);
  244. } else if (typeof selectedWidth === 'number') {
  245. values.width += selectedWidth;
  246. }
  247. values.width = Math.max(values.width, 0.3 / this.body.view.scale);
  248. values.color = this.options.color.highlight;
  249. values.shadow = this.options.shadow.enabled;
  250. } else if (this.hover) {
  251. let hoverWidth = this.options.hoverWidth;
  252. if (typeof hoverWidth === 'function') {
  253. values.width = hoverWidth(values.width);
  254. } else if (typeof hoverWidth === 'number') {
  255. values.width += hoverWidth;
  256. }
  257. values.width = Math.max(values.width, 0.3 / this.body.view.scale);
  258. values.color = this.options.color.hover;
  259. values.shadow = this.options.shadow.enabled;
  260. }
  261. } else if (typeof this.chooser === 'function') {
  262. this.chooser(values, this.options.id, this.selected, this.hover);
  263. if (values.color !== undefined) {
  264. values.inheritsColor = false;
  265. }
  266. if (values.shadow === false) {
  267. if ((values.shadowColor !== this.options.shadow.color) ||
  268. (values.shadowSize !== this.options.shadow.size) ||
  269. (values.shadowX !== this.options.shadow.x) ||
  270. (values.shadowY !== this.options.shadow.y)) {
  271. values.shadow = true;
  272. }
  273. }
  274. }
  275. } else {
  276. values.shadow = this.options.shadow.enabled;
  277. values.width = Math.max(values.width, 0.3 / this.body.view.scale);
  278. }
  279. return values;
  280. }
  281. /**
  282. * update the options in the label module
  283. *
  284. * @param {Object} options
  285. */
  286. updateLabelModule(options) {
  287. let pile = [options, this.edgeOptions, this.defaultOptions];
  288. this.labelModule.update(this.options, pile);
  289. if (this.labelModule.baseSize !== undefined) {
  290. this.baseFontSize = this.labelModule.baseSize;
  291. }
  292. }
  293. /**
  294. * update the edge type, set the options
  295. * @returns {boolean}
  296. */
  297. updateEdgeType() {
  298. let smooth = this.options.smooth;
  299. let dataChanged = false;
  300. let changeInType = true;
  301. if (this.edgeType !== undefined) {
  302. if ((((this.edgeType instanceof BezierEdgeDynamic) &&
  303. (smooth.enabled === true) &&
  304. (smooth.type === 'dynamic'))) ||
  305. (((this.edgeType instanceof CubicBezierEdge) &&
  306. (smooth.enabled === true) &&
  307. (smooth.type === 'cubicBezier'))) ||
  308. (((this.edgeType instanceof BezierEdgeStatic) &&
  309. (smooth.enabled === true) &&
  310. (smooth.type !== 'dynamic') &&
  311. (smooth.type !== 'cubicBezier'))) ||
  312. (((this.edgeType instanceof StraightEdge) &&
  313. (smooth.type.enabled === false)))) {
  314. changeInType = false;
  315. }
  316. if (changeInType === true) {
  317. dataChanged = this.cleanup();
  318. }
  319. }
  320. if (changeInType === true) {
  321. if (smooth.enabled === true) {
  322. if (smooth.type === 'dynamic') {
  323. dataChanged = true;
  324. this.edgeType = new BezierEdgeDynamic(this.options, this.body, this.labelModule);
  325. } else if (smooth.type === 'cubicBezier') {
  326. this.edgeType = new CubicBezierEdge(this.options, this.body, this.labelModule);
  327. } else {
  328. this.edgeType = new BezierEdgeStatic(this.options, this.body, this.labelModule);
  329. }
  330. } else {
  331. this.edgeType = new StraightEdge(this.options, this.body, this.labelModule);
  332. }
  333. } else { // if nothing changes, we just set the options.
  334. this.edgeType.setOptions(this.options);
  335. }
  336. return dataChanged;
  337. }
  338. /**
  339. * Connect an edge to its nodes
  340. */
  341. connect() {
  342. this.disconnect();
  343. this.from = this.body.nodes[this.fromId] || undefined;
  344. this.to = this.body.nodes[this.toId] || undefined;
  345. this.connected = (this.from !== undefined && this.to !== undefined);
  346. if (this.connected === true) {
  347. this.from.attachEdge(this);
  348. this.to.attachEdge(this);
  349. }
  350. else {
  351. if (this.from) {
  352. this.from.detachEdge(this);
  353. }
  354. if (this.to) {
  355. this.to.detachEdge(this);
  356. }
  357. }
  358. this.edgeType.connect();
  359. }
  360. /**
  361. * Disconnect an edge from its nodes
  362. */
  363. disconnect() {
  364. if (this.from) {
  365. this.from.detachEdge(this);
  366. this.from = undefined;
  367. }
  368. if (this.to) {
  369. this.to.detachEdge(this);
  370. this.to = undefined;
  371. }
  372. this.connected = false;
  373. }
  374. /**
  375. * get the title of this edge.
  376. * @return {string} title The title of the edge, or undefined when no title
  377. * has been set.
  378. */
  379. getTitle() {
  380. return this.title;
  381. }
  382. /**
  383. * check if this node is selecte
  384. * @return {boolean} selected True if node is selected, else false
  385. */
  386. isSelected() {
  387. return this.selected;
  388. }
  389. /**
  390. * Retrieve the value of the edge. Can be undefined
  391. * @return {number} value
  392. */
  393. getValue() {
  394. return this.options.value;
  395. }
  396. /**
  397. * Adjust the value range of the edge. The edge will adjust it's width
  398. * based on its value.
  399. * @param {number} min
  400. * @param {number} max
  401. * @param {number} total
  402. */
  403. setValueRange(min, max, total) {
  404. if (this.options.value !== undefined) {
  405. var scale = this.options.scaling.customScalingFunction(min, max, total, this.options.value);
  406. var widthDiff = this.options.scaling.max - this.options.scaling.min;
  407. if (this.options.scaling.label.enabled === true) {
  408. var fontDiff = this.options.scaling.label.max - this.options.scaling.label.min;
  409. this.options.font.size = this.options.scaling.label.min + scale * fontDiff;
  410. }
  411. this.options.width = this.options.scaling.min + scale * widthDiff;
  412. }
  413. else {
  414. this.options.width = this.baseWidth;
  415. this.options.font.size = this.baseFontSize;
  416. }
  417. this._setInteractionWidths();
  418. this.updateLabelModule();
  419. }
  420. /**
  421. *
  422. * @private
  423. */
  424. _setInteractionWidths() {
  425. if (typeof this.options.hoverWidth === 'function') {
  426. this.edgeType.hoverWidth = this.options.hoverWidth(this.options.width);
  427. } else {
  428. this.edgeType.hoverWidth = this.options.hoverWidth + this.options.width;
  429. }
  430. if (typeof this.options.selectionWidth === 'function') {
  431. this.edgeType.selectionWidth = this.options.selectionWidth(this.options.width);
  432. } else {
  433. this.edgeType.selectionWidth = this.options.selectionWidth + this.options.width;
  434. }
  435. }
  436. /**
  437. * Redraw a edge
  438. * Draw this edge in the given canvas
  439. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  440. * @param {CanvasRenderingContext2D} ctx
  441. */
  442. draw(ctx) {
  443. let values = this.getFormattingValues();
  444. if (values.hidden) {
  445. return;
  446. }
  447. // get the via node from the edge type
  448. let viaNode = this.edgeType.getViaNode();
  449. let arrowData = {};
  450. // restore edge targets to defaults
  451. this.edgeType.fromPoint = this.edgeType.from;
  452. this.edgeType.toPoint = this.edgeType.to;
  453. // from and to arrows give a different end point for edges. we set them here
  454. if (values.fromArrow) {
  455. arrowData.from = this.edgeType.getArrowData(ctx, 'from', viaNode, this.selected, this.hover, values);
  456. if (values.arrowStrikethrough === false)
  457. this.edgeType.fromPoint = arrowData.from.core;
  458. }
  459. if (values.toArrow) {
  460. arrowData.to = this.edgeType.getArrowData(ctx, 'to', viaNode, this.selected, this.hover, values);
  461. if (values.arrowStrikethrough === false)
  462. this.edgeType.toPoint = arrowData.to.core;
  463. }
  464. // the middle arrow depends on the line, which can depend on the to and from arrows so we do this one lastly.
  465. if (values.middleArrow) {
  466. arrowData.middle = this.edgeType.getArrowData(ctx,'middle', viaNode, this.selected, this.hover, values);
  467. }
  468. // draw everything
  469. this.edgeType.drawLine(ctx, values, this.selected, this.hover, viaNode);
  470. this.drawArrows(ctx, arrowData, values);
  471. this.drawLabel (ctx, viaNode);
  472. }
  473. /**
  474. *
  475. * @param {CanvasRenderingContext2D} ctx
  476. * @param {Object} arrowData
  477. * @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
  478. */
  479. drawArrows(ctx, arrowData, values) {
  480. if (values.fromArrow) {
  481. this.edgeType.drawArrowHead(ctx, values, this.selected, this.hover, arrowData.from);
  482. }
  483. if (values.middleArrow) {
  484. this.edgeType.drawArrowHead(ctx, values, this.selected, this.hover, arrowData.middle);
  485. }
  486. if (values.toArrow) {
  487. this.edgeType.drawArrowHead(ctx, values, this.selected, this.hover, arrowData.to);
  488. }
  489. }
  490. /**
  491. *
  492. * @param {CanvasRenderingContext2D} ctx
  493. * @param {Node} viaNode
  494. */
  495. drawLabel(ctx, viaNode) {
  496. if (this.options.label !== undefined) {
  497. // set style
  498. var node1 = this.from;
  499. var node2 = this.to;
  500. if (this.labelModule.differentState(this.selected, this.hover)) {
  501. this.labelModule.getTextSize(ctx, this.selected, this.hover);
  502. }
  503. if (node1.id != node2.id) {
  504. this.labelModule.pointToSelf = false;
  505. var point = this.edgeType.getPoint(0.5, viaNode);
  506. ctx.save();
  507. // if the label has to be rotated:
  508. if (this.options.font.align !== "horizontal") {
  509. this.labelModule.calculateLabelSize(ctx, this.selected, this.hover, point.x, point.y);
  510. ctx.translate(point.x, this.labelModule.size.yLine);
  511. this._rotateForLabelAlignment(ctx);
  512. }
  513. // draw the label
  514. this.labelModule.draw(ctx, point.x, point.y, this.selected, this.hover);
  515. ctx.restore();
  516. }
  517. else {
  518. // Ignore the orientations.
  519. this.labelModule.pointToSelf = true;
  520. var x, y;
  521. var radius = this.options.selfReferenceSize;
  522. if (node1.shape.width > node1.shape.height) {
  523. x = node1.x + node1.shape.width * 0.5;
  524. y = node1.y - radius;
  525. }
  526. else {
  527. x = node1.x + radius;
  528. y = node1.y - node1.shape.height * 0.5;
  529. }
  530. point = this._pointOnCircle(x, y, radius, 0.125);
  531. this.labelModule.draw(ctx, point.x, point.y, this.selected, this.hover);
  532. }
  533. }
  534. }
  535. /**
  536. * Check if this object is overlapping with the provided object
  537. * @param {Object} obj an object with parameters left, top
  538. * @return {boolean} True if location is located on the edge
  539. */
  540. isOverlappingWith(obj) {
  541. if (this.connected) {
  542. var distMax = 10;
  543. var xFrom = this.from.x;
  544. var yFrom = this.from.y;
  545. var xTo = this.to.x;
  546. var yTo = this.to.y;
  547. var xObj = obj.left;
  548. var yObj = obj.top;
  549. var dist = this.edgeType.getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
  550. return (dist < distMax);
  551. }
  552. else {
  553. return false
  554. }
  555. }
  556. /**
  557. * Rotates the canvas so the text is most readable
  558. * @param {CanvasRenderingContext2D} ctx
  559. * @private
  560. */
  561. _rotateForLabelAlignment(ctx) {
  562. var dy = this.from.y - this.to.y;
  563. var dx = this.from.x - this.to.x;
  564. var angleInDegrees = Math.atan2(dy, dx);
  565. // rotate so label it is readable
  566. if ((angleInDegrees < -1 && dx < 0) || (angleInDegrees > 0 && dx < 0)) {
  567. angleInDegrees = angleInDegrees + Math.PI;
  568. }
  569. ctx.rotate(angleInDegrees);
  570. }
  571. /**
  572. * Get a point on a circle
  573. * @param {number} x
  574. * @param {number} y
  575. * @param {number} radius
  576. * @param {number} percentage Value between 0 (line start) and 1 (line end)
  577. * @return {Object} point
  578. * @private
  579. */
  580. _pointOnCircle(x, y, radius, percentage) {
  581. var angle = percentage * 2 * Math.PI;
  582. return {
  583. x: x + radius * Math.cos(angle),
  584. y: y - radius * Math.sin(angle)
  585. }
  586. }
  587. /**
  588. * Sets selected state to true
  589. */
  590. select() {
  591. this.selected = true;
  592. }
  593. /**
  594. * Sets selected state to false
  595. */
  596. unselect() {
  597. this.selected = false;
  598. }
  599. /**
  600. * cleans all required things on delete
  601. * @returns {*}
  602. */
  603. cleanup() {
  604. return this.edgeType.cleanup();
  605. }
  606. /**
  607. * Remove edge from the list and perform necessary cleanup.
  608. */
  609. remove() {
  610. this.cleanup();
  611. this.disconnect();
  612. delete this.body.edges[this.id];
  613. }
  614. /**
  615. * Check if both connecting nodes exist
  616. * @returns {boolean}
  617. */
  618. endPointsValid() {
  619. return this.body.nodes[this.fromId] !== undefined
  620. && this.body.nodes[this.toId] !== undefined;
  621. }
  622. }
  623. export default Edge;