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.

712 lines
24 KiB

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