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.

774 lines
26 KiB

9 years ago
Network: Fix handling of multi-fonts (#3486) * The next fix on Travis unit test failure This is the next escalation on the war against the Travis unit tests failing (which came into being by yours truly). By accident, I could recreate the unit test failure on my development machine. This led to a more directed effort to squash the bug. The insight here is that test `(window === undefined)` fails, but `(typeof window === 'undefined`)` succeeds. This undoubtedly has to do with the special status `window` has as a global object. Changes: - Added check on presence of `window` in `Canvas._requestNextFrame()`, fixed local source errors. - Added catch clause in `CanvasRendered._determinePixelRatio()` - small fix: raised timeout for the network `worldCup2014` unit test * Preliminary refactoring in utils.js * Added unit tests for extend routines, commenting and small fixes * More unit tests for extend routines * - Completed unit tests for extend routines in - Small fixes and cleanup in `util.js` - Removed `util.protoExtend()`, not used anywhere * Added unit tests for known font options * Interim save before trying out another proto chain strategy * Fixed problem in first example #3408 * Removed silly file that shouldn't be there * Added unit test for multi-fonts * Comment edits * Verufy unit tests, small adjustments for groups * Further work on getting unit tests to work. PARTS NEED TO BE CLEANED UP! * Further tweaks to get unit tests working * All unit tests passing * Fixes due to linting * Small edits * Removed prototype handling from font pile * Fixes during testing examples of #3408 * Added unit test for edge labels, small fixes * Added unit tests for shorthand string fonts; some tests still failing * All unit tests pass * Removed Label.parseOptions() * Completed shorthand font tests, code cleanup, fixed choosify for edges * Addressed review comments * Addressed review comments, cleanup
7 years ago
Network: Fix handling of multi-fonts (#3486) * The next fix on Travis unit test failure This is the next escalation on the war against the Travis unit tests failing (which came into being by yours truly). By accident, I could recreate the unit test failure on my development machine. This led to a more directed effort to squash the bug. The insight here is that test `(window === undefined)` fails, but `(typeof window === 'undefined`)` succeeds. This undoubtedly has to do with the special status `window` has as a global object. Changes: - Added check on presence of `window` in `Canvas._requestNextFrame()`, fixed local source errors. - Added catch clause in `CanvasRendered._determinePixelRatio()` - small fix: raised timeout for the network `worldCup2014` unit test * Preliminary refactoring in utils.js * Added unit tests for extend routines, commenting and small fixes * More unit tests for extend routines * - Completed unit tests for extend routines in - Small fixes and cleanup in `util.js` - Removed `util.protoExtend()`, not used anywhere * Added unit tests for known font options * Interim save before trying out another proto chain strategy * Fixed problem in first example #3408 * Removed silly file that shouldn't be there * Added unit test for multi-fonts * Comment edits * Verufy unit tests, small adjustments for groups * Further work on getting unit tests to work. PARTS NEED TO BE CLEANED UP! * Further tweaks to get unit tests working * All unit tests passing * Fixes due to linting * Small edits * Removed prototype handling from font pile * Fixes during testing examples of #3408 * Added unit test for edge labels, small fixes * Added unit tests for shorthand string fonts; some tests still failing * All unit tests pass * Removed Label.parseOptions() * Completed shorthand font tests, code cleanup, fixed choosify for edges * Addressed review comments * Addressed review comments, cleanup
7 years ago
Network: Fix handling of multi-fonts (#3486) * The next fix on Travis unit test failure This is the next escalation on the war against the Travis unit tests failing (which came into being by yours truly). By accident, I could recreate the unit test failure on my development machine. This led to a more directed effort to squash the bug. The insight here is that test `(window === undefined)` fails, but `(typeof window === 'undefined`)` succeeds. This undoubtedly has to do with the special status `window` has as a global object. Changes: - Added check on presence of `window` in `Canvas._requestNextFrame()`, fixed local source errors. - Added catch clause in `CanvasRendered._determinePixelRatio()` - small fix: raised timeout for the network `worldCup2014` unit test * Preliminary refactoring in utils.js * Added unit tests for extend routines, commenting and small fixes * More unit tests for extend routines * - Completed unit tests for extend routines in - Small fixes and cleanup in `util.js` - Removed `util.protoExtend()`, not used anywhere * Added unit tests for known font options * Interim save before trying out another proto chain strategy * Fixed problem in first example #3408 * Removed silly file that shouldn't be there * Added unit test for multi-fonts * Comment edits * Verufy unit tests, small adjustments for groups * Further work on getting unit tests to work. PARTS NEED TO BE CLEANED UP! * Further tweaks to get unit tests working * All unit tests passing * Fixes due to linting * Small edits * Removed prototype handling from font pile * Fixes during testing examples of #3408 * Added unit test for edge labels, small fixes * Added unit tests for shorthand string fonts; some tests still failing * All unit tests pass * Removed Label.parseOptions() * Completed shorthand font tests, code cleanup, fixed choosify for edges * Addressed review comments * Addressed review comments, cleanup
7 years ago
9 years ago
9 years ago
Network: Fix handling of multi-fonts (#3486) * The next fix on Travis unit test failure This is the next escalation on the war against the Travis unit tests failing (which came into being by yours truly). By accident, I could recreate the unit test failure on my development machine. This led to a more directed effort to squash the bug. The insight here is that test `(window === undefined)` fails, but `(typeof window === 'undefined`)` succeeds. This undoubtedly has to do with the special status `window` has as a global object. Changes: - Added check on presence of `window` in `Canvas._requestNextFrame()`, fixed local source errors. - Added catch clause in `CanvasRendered._determinePixelRatio()` - small fix: raised timeout for the network `worldCup2014` unit test * Preliminary refactoring in utils.js * Added unit tests for extend routines, commenting and small fixes * More unit tests for extend routines * - Completed unit tests for extend routines in - Small fixes and cleanup in `util.js` - Removed `util.protoExtend()`, not used anywhere * Added unit tests for known font options * Interim save before trying out another proto chain strategy * Fixed problem in first example #3408 * Removed silly file that shouldn't be there * Added unit test for multi-fonts * Comment edits * Verufy unit tests, small adjustments for groups * Further work on getting unit tests to work. PARTS NEED TO BE CLEANED UP! * Further tweaks to get unit tests working * All unit tests passing * Fixes due to linting * Small edits * Removed prototype handling from font pile * Fixes during testing examples of #3408 * Added unit test for edge labels, small fixes * Added unit tests for shorthand string fonts; some tests still failing * All unit tests pass * Removed Label.parseOptions() * Completed shorthand font tests, code cleanup, fixed choosify for edges * Addressed review comments * Addressed review comments, cleanup
7 years ago
Network: Fix handling of multi-fonts (#3486) * The next fix on Travis unit test failure This is the next escalation on the war against the Travis unit tests failing (which came into being by yours truly). By accident, I could recreate the unit test failure on my development machine. This led to a more directed effort to squash the bug. The insight here is that test `(window === undefined)` fails, but `(typeof window === 'undefined`)` succeeds. This undoubtedly has to do with the special status `window` has as a global object. Changes: - Added check on presence of `window` in `Canvas._requestNextFrame()`, fixed local source errors. - Added catch clause in `CanvasRendered._determinePixelRatio()` - small fix: raised timeout for the network `worldCup2014` unit test * Preliminary refactoring in utils.js * Added unit tests for extend routines, commenting and small fixes * More unit tests for extend routines * - Completed unit tests for extend routines in - Small fixes and cleanup in `util.js` - Removed `util.protoExtend()`, not used anywhere * Added unit tests for known font options * Interim save before trying out another proto chain strategy * Fixed problem in first example #3408 * Removed silly file that shouldn't be there * Added unit test for multi-fonts * Comment edits * Verufy unit tests, small adjustments for groups * Further work on getting unit tests to work. PARTS NEED TO BE CLEANED UP! * Further tweaks to get unit tests working * All unit tests passing * Fixes due to linting * Small edits * Removed prototype handling from font pile * Fixes during testing examples of #3408 * Added unit test for edge labels, small fixes * Added unit tests for shorthand string fonts; some tests still failing * All unit tests pass * Removed Label.parseOptions() * Completed shorthand font tests, code cleanup, fixed choosify for edges * Addressed review comments * Addressed review comments, cleanup
7 years ago
9 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. */
  18. constructor(options, body, globalOptions, defaultOptions) {
  19. if (body === undefined) {
  20. throw new Error("No body provided");
  21. }
  22. // Since globalOptions is constant in values as well as reference,
  23. // Following needs to be done only once.
  24. this.options = util.bridgeObject(globalOptions);
  25. this.globalOptions = globalOptions;
  26. this.defaultOptions = defaultOptions;
  27. this.body = body;
  28. // initialize variables
  29. this.id = undefined;
  30. this.fromId = undefined;
  31. this.toId = undefined;
  32. this.selected = false;
  33. this.hover = false;
  34. this.labelDirty = true;
  35. this.baseWidth = this.options.width;
  36. this.baseFontSize = this.options.font.size;
  37. this.from = undefined; // a node
  38. this.to = undefined; // a node
  39. this.edgeType = undefined;
  40. this.connected = false;
  41. this.labelModule = new Label(this.body, this.options, true /* It's an edge label */);
  42. this.setOptions(options);
  43. }
  44. /**
  45. * Set or overwrite options for the edge
  46. * @param {Object} options an object with options
  47. * @returns {null|boolean} null if no options, boolean if date changed
  48. */
  49. setOptions(options) {
  50. if (!options) {
  51. return;
  52. }
  53. Edge.parseOptions(this.options, options, true, this.globalOptions);
  54. if (options.id !== undefined) {
  55. this.id = options.id;
  56. }
  57. if (options.from !== undefined) {
  58. this.fromId = options.from;
  59. }
  60. if (options.to !== undefined) {
  61. this.toId = options.to;
  62. }
  63. if (options.title !== undefined) {
  64. this.title = options.title;
  65. }
  66. if (options.value !== undefined) {
  67. options.value = parseFloat(options.value);
  68. }
  69. let pile = [options, this.options, this.defaultOptions];
  70. this.chooser = ComponentUtil.choosify('edge', pile);
  71. // update label Module
  72. this.updateLabelModule(options);
  73. let dataChanged = this.updateEdgeType();
  74. // if anything has been updates, reset the selection width and the hover width
  75. this._setInteractionWidths();
  76. // A node is connected when it has a from and to node that both exist in the network.body.nodes.
  77. this.connect();
  78. if (options.hidden !== undefined || options.physics !== undefined) {
  79. dataChanged = true;
  80. }
  81. return dataChanged;
  82. }
  83. /**
  84. *
  85. * @param {Object} parentOptions
  86. * @param {Object} newOptions
  87. * @param {boolean} [allowDeletion=false]
  88. * @param {Object} [globalOptions={}]
  89. * @param {boolean} [copyFromGlobals=false]
  90. */
  91. static parseOptions(parentOptions, newOptions, allowDeletion = false, globalOptions = {}, copyFromGlobals = false) {
  92. var fields = [
  93. 'arrowStrikethrough',
  94. 'id',
  95. 'from',
  96. 'hidden',
  97. 'hoverWidth',
  98. 'label',
  99. 'labelHighlightBold',
  100. 'length',
  101. 'line',
  102. 'opacity',
  103. 'physics',
  104. 'scaling',
  105. 'selectionWidth',
  106. 'selfReferenceSize',
  107. 'to',
  108. 'title',
  109. 'value',
  110. 'width',
  111. 'font',
  112. 'chosen'
  113. ];
  114. // only deep extend the items in the field array. These do not have shorthand.
  115. util.selectiveDeepExtend(fields, parentOptions, newOptions, allowDeletion);
  116. util.mergeOptions(parentOptions, newOptions, 'smooth', globalOptions);
  117. util.mergeOptions(parentOptions, newOptions, 'shadow', globalOptions);
  118. if (newOptions.dashes !== undefined && newOptions.dashes !== null) {
  119. parentOptions.dashes = newOptions.dashes;
  120. }
  121. else if (allowDeletion === true && newOptions.dashes === null) {
  122. parentOptions.dashes = Object.create(globalOptions.dashes); // this sets the pointer of the option back to the global option.
  123. }
  124. // set the scaling newOptions
  125. if (newOptions.scaling !== undefined && newOptions.scaling !== null) {
  126. if (newOptions.scaling.min !== undefined) {parentOptions.scaling.min = newOptions.scaling.min;}
  127. if (newOptions.scaling.max !== undefined) {parentOptions.scaling.max = newOptions.scaling.max;}
  128. util.mergeOptions(parentOptions.scaling, newOptions.scaling, 'label', globalOptions.scaling);
  129. }
  130. else if (allowDeletion === true && newOptions.scaling === null) {
  131. parentOptions.scaling = Object.create(globalOptions.scaling); // this sets the pointer of the option back to the global option.
  132. }
  133. // handle multiple input cases for arrows
  134. if (newOptions.arrows !== undefined && newOptions.arrows !== null) {
  135. if (typeof newOptions.arrows === 'string') {
  136. let arrows = newOptions.arrows.toLowerCase();
  137. parentOptions.arrows.to.enabled = arrows.indexOf("to") != -1;
  138. parentOptions.arrows.middle.enabled = arrows.indexOf("middle") != -1;
  139. parentOptions.arrows.from.enabled = arrows.indexOf("from") != -1;
  140. }
  141. else if (typeof newOptions.arrows === 'object') {
  142. util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'to', globalOptions.arrows);
  143. util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'middle', globalOptions.arrows);
  144. util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'from', globalOptions.arrows);
  145. }
  146. else {
  147. throw new Error("The arrow newOptions can only be an object or a string. Refer to the documentation. You used:" + JSON.stringify(newOptions.arrows));
  148. }
  149. }
  150. else if (allowDeletion === true && newOptions.arrows === null) {
  151. parentOptions.arrows = Object.create(globalOptions.arrows); // this sets the pointer of the option back to the global option.
  152. }
  153. // handle multiple input cases for color
  154. if (newOptions.color !== undefined && newOptions.color !== null) {
  155. let fromColor = newOptions.color;
  156. let toColor = parentOptions.color;
  157. // If passed, fill in values from default options - required in the case of no prototype bridging
  158. if (copyFromGlobals) {
  159. util.deepExtend(toColor, globalOptions.color, false, allowDeletion);
  160. } else {
  161. // Clear local properties - need to do it like this in order to retain prototype bridges
  162. for (var i in toColor) {
  163. if (toColor.hasOwnProperty(i)) {
  164. delete toColor[i];
  165. }
  166. }
  167. }
  168. if (util.isString(toColor)) {
  169. toColor.color = toColor;
  170. toColor.highlight = toColor;
  171. toColor.hover = toColor;
  172. toColor.inherit = false;
  173. if (fromColor.opacity === undefined) {
  174. toColor.opacity = 1.0; // set default
  175. }
  176. }
  177. else {
  178. let colorsDefined = false;
  179. if (fromColor.color !== undefined) {toColor.color = fromColor.color; colorsDefined = true;}
  180. if (fromColor.highlight !== undefined) {toColor.highlight = fromColor.highlight; colorsDefined = true;}
  181. if (fromColor.hover !== undefined) {toColor.hover = fromColor.hover; colorsDefined = true;}
  182. if (fromColor.inherit !== undefined) {toColor.inherit = fromColor.inherit;}
  183. if (fromColor.opacity !== undefined) {toColor.opacity = Math.min(1,Math.max(0,fromColor.opacity));}
  184. if (colorsDefined === true) {
  185. toColor.inherit = false;
  186. } else {
  187. if (toColor.inherit === undefined) {
  188. toColor.inherit = 'from'; // Set default
  189. }
  190. }
  191. }
  192. }
  193. else if (allowDeletion === true && newOptions.color === null) {
  194. parentOptions.color = util.bridgeObject(globalOptions.color); // set the object back to the global options
  195. }
  196. if (allowDeletion === true && newOptions.font === null) {
  197. parentOptions.font = util.bridgeObject(globalOptions.font); // set the object back to the global options
  198. }
  199. }
  200. /**
  201. *
  202. * @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: *}}
  203. */
  204. getFormattingValues() {
  205. let toArrow = (this.options.arrows.to === true) || (this.options.arrows.to.enabled === true)
  206. let fromArrow = (this.options.arrows.from === true) || (this.options.arrows.from.enabled === true)
  207. let middleArrow = (this.options.arrows.middle === true) || (this.options.arrows.middle.enabled === true)
  208. let inheritsColor = this.options.color.inherit;
  209. let values = {
  210. toArrow: toArrow,
  211. toArrowScale: this.options.arrows.to.scaleFactor,
  212. toArrowType: this.options.arrows.to.type,
  213. middleArrow: middleArrow,
  214. middleArrowScale: this.options.arrows.middle.scaleFactor,
  215. middleArrowType: this.options.arrows.middle.type,
  216. fromArrow: fromArrow,
  217. fromArrowScale: this.options.arrows.from.scaleFactor,
  218. fromArrowType: this.options.arrows.from.type,
  219. arrowStrikethrough: this.options.arrowStrikethrough,
  220. color: (inheritsColor? undefined : this.options.color.color),
  221. inheritsColor: inheritsColor,
  222. opacity: this.options.color.opacity,
  223. hidden: this.options.hidden,
  224. length: this.options.length,
  225. shadow: this.options.shadow.enabled,
  226. shadowColor: this.options.shadow.color,
  227. shadowSize: this.options.shadow.size,
  228. shadowX: this.options.shadow.x,
  229. shadowY: this.options.shadow.y,
  230. dashes: this.options.dashes,
  231. width: this.options.width
  232. };
  233. if (this.selected || this.hover) {
  234. if (this.chooser === true) {
  235. if (this.selected) {
  236. let selectedWidth = this.options.selectionWidth;
  237. if (typeof selectedWidth === 'function') {
  238. values.width = selectedWidth(values.width);
  239. } else if (typeof selectedWidth === 'number') {
  240. values.width += selectedWidth;
  241. }
  242. values.width = Math.max(values.width, 0.3 / this.body.view.scale);
  243. values.color = this.options.color.highlight;
  244. values.shadow = this.options.shadow.enabled;
  245. } else if (this.hover) {
  246. let hoverWidth = this.options.hoverWidth;
  247. if (typeof hoverWidth === 'function') {
  248. values.width = hoverWidth(values.width);
  249. } else if (typeof hoverWidth === 'number') {
  250. values.width += hoverWidth;
  251. }
  252. values.width = Math.max(values.width, 0.3 / this.body.view.scale);
  253. values.color = this.options.color.hover;
  254. values.shadow = this.options.shadow.enabled;
  255. }
  256. } else if (typeof this.chooser === 'function') {
  257. this.chooser(values, this.options.id, this.selected, this.hover);
  258. if (values.color !== undefined) {
  259. values.inheritsColor = false;
  260. }
  261. if (values.shadow === false) {
  262. if ((values.shadowColor !== this.options.shadow.color) ||
  263. (values.shadowSize !== this.options.shadow.size) ||
  264. (values.shadowX !== this.options.shadow.x) ||
  265. (values.shadowY !== this.options.shadow.y)) {
  266. values.shadow = true;
  267. }
  268. }
  269. }
  270. } else {
  271. values.shadow = this.options.shadow.enabled;
  272. values.width = Math.max(values.width, 0.3 / this.body.view.scale);
  273. }
  274. return values;
  275. }
  276. /**
  277. * update the options in the label module
  278. *
  279. * @param {Object} options
  280. */
  281. updateLabelModule(options) {
  282. let pile = [
  283. options,
  284. this.options,
  285. this.globalOptions, // Currently set global edge options
  286. this.defaultOptions
  287. ];
  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. let rotationPoint = this._getRotation(ctx);
  508. if (rotationPoint.angle != 0) {
  509. ctx.translate(rotationPoint.x, rotationPoint.y);
  510. ctx.rotate(rotationPoint.angle);
  511. }
  512. // draw the label
  513. this.labelModule.draw(ctx, point.x, point.y, this.selected, this.hover);
  514. /*
  515. // Useful debug code: draw a border around the label
  516. // This should **not** be enabled in production!
  517. var size = this.labelModule.getSize();; // ;; intentional so lint catches it
  518. ctx.strokeStyle = "#ff0000";
  519. ctx.strokeRect(size.left, size.top, size.width, size.height);
  520. // End debug code
  521. */
  522. ctx.restore();
  523. }
  524. else {
  525. // Ignore the orientations.
  526. this.labelModule.pointToSelf = true;
  527. var x, y;
  528. var radius = this.options.selfReferenceSize;
  529. if (node1.shape.width > node1.shape.height) {
  530. x = node1.x + node1.shape.width * 0.5;
  531. y = node1.y - radius;
  532. }
  533. else {
  534. x = node1.x + radius;
  535. y = node1.y - node1.shape.height * 0.5;
  536. }
  537. point = this._pointOnCircle(x, y, radius, 0.125);
  538. this.labelModule.draw(ctx, point.x, point.y, this.selected, this.hover);
  539. }
  540. }
  541. }
  542. /**
  543. * Determine all visual elements of this edge instance, in which the given
  544. * point falls within the bounding shape.
  545. *
  546. * @param {point} point
  547. * @returns {Array.<edgeClickItem|edgeLabelClickItem>} list with the items which are on the point
  548. */
  549. getItemsOnPoint(point) {
  550. var ret = [];
  551. if (this.labelModule.visible()) {
  552. let rotationPoint = this._getRotation();
  553. if (ComponentUtil.pointInRect(this.labelModule.getSize(), point, rotationPoint)) {
  554. ret.push({edgeId:this.id, labelId:0});
  555. }
  556. }
  557. let obj = {
  558. left: point.x,
  559. top: point.y
  560. };
  561. if (this.isOverlappingWith(obj)) {
  562. ret.push({edgeId:this.id});
  563. }
  564. return ret;
  565. }
  566. /**
  567. * Check if this object is overlapping with the provided object
  568. * @param {Object} obj an object with parameters left, top
  569. * @return {boolean} True if location is located on the edge
  570. */
  571. isOverlappingWith(obj) {
  572. if (this.connected) {
  573. var distMax = 10;
  574. var xFrom = this.from.x;
  575. var yFrom = this.from.y;
  576. var xTo = this.to.x;
  577. var yTo = this.to.y;
  578. var xObj = obj.left;
  579. var yObj = obj.top;
  580. var dist = this.edgeType.getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
  581. return (dist < distMax);
  582. }
  583. else {
  584. return false
  585. }
  586. }
  587. /**
  588. * Determine the rotation point, if any.
  589. *
  590. * @param {CanvasRenderingContext2D} [ctx] if passed, do a recalculation of the label size
  591. * @returns {rotationPoint} the point to rotate around and the angle in radians to rotate
  592. * @private
  593. */
  594. _getRotation(ctx) {
  595. let viaNode = this.edgeType.getViaNode();
  596. let point = this.edgeType.getPoint(0.5, viaNode);
  597. if (ctx !== undefined) {
  598. this.labelModule.calculateLabelSize(ctx, this.selected, this.hover, point.x, point.y);
  599. }
  600. let ret = {
  601. x: point.x,
  602. y: this.labelModule.size.yLine,
  603. angle: 0
  604. };
  605. if (!this.labelModule.visible()) {
  606. return ret; // Don't even bother doing the atan2, there's nothing to draw
  607. }
  608. if (this.options.font.align === "horizontal") {
  609. return ret; // No need to calculate angle
  610. }
  611. var dy = this.from.y - this.to.y;
  612. var dx = this.from.x - this.to.x;
  613. var angle = Math.atan2(dy, dx); // radians
  614. // rotate so that label is readable
  615. if ((angle < -1 && dx < 0) || (angle > 0 && dx < 0)) {
  616. angle += Math.PI;
  617. }
  618. ret.angle = angle;
  619. return ret;
  620. }
  621. /**
  622. * Get a point on a circle
  623. * @param {number} x
  624. * @param {number} y
  625. * @param {number} radius
  626. * @param {number} percentage Value between 0 (line start) and 1 (line end)
  627. * @return {Object} point
  628. * @private
  629. */
  630. _pointOnCircle(x, y, radius, percentage) {
  631. var angle = percentage * 2 * Math.PI;
  632. return {
  633. x: x + radius * Math.cos(angle),
  634. y: y - radius * Math.sin(angle)
  635. }
  636. }
  637. /**
  638. * Sets selected state to true
  639. */
  640. select() {
  641. this.selected = true;
  642. }
  643. /**
  644. * Sets selected state to false
  645. */
  646. unselect() {
  647. this.selected = false;
  648. }
  649. /**
  650. * cleans all required things on delete
  651. * @returns {*}
  652. */
  653. cleanup() {
  654. return this.edgeType.cleanup();
  655. }
  656. /**
  657. * Remove edge from the list and perform necessary cleanup.
  658. */
  659. remove() {
  660. this.cleanup();
  661. this.disconnect();
  662. delete this.body.edges[this.id];
  663. }
  664. /**
  665. * Check if both connecting nodes exist
  666. * @returns {boolean}
  667. */
  668. endPointsValid() {
  669. return this.body.nodes[this.fromId] !== undefined
  670. && this.body.nodes[this.toId] !== undefined;
  671. }
  672. }
  673. export default Edge;