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.

780 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. 'labelHighlightBold',
  99. 'length',
  100. 'line',
  101. 'opacity',
  102. 'physics',
  103. 'scaling',
  104. 'selectionWidth',
  105. 'selfReferenceSize',
  106. 'to',
  107. 'title',
  108. 'value',
  109. 'width',
  110. 'font',
  111. 'chosen'
  112. ];
  113. // only deep extend the items in the field array. These do not have shorthand.
  114. util.selectiveDeepExtend(fields, parentOptions, newOptions, allowDeletion);
  115. // Only copy label if it's a legal value.
  116. if (ComponentUtil.isValidLabel(newOptions.label)) {
  117. parentOptions.label = newOptions.label;
  118. } else {
  119. parentOptions.label = undefined;
  120. }
  121. util.mergeOptions(parentOptions, newOptions, 'smooth', globalOptions);
  122. util.mergeOptions(parentOptions, newOptions, 'shadow', globalOptions);
  123. if (newOptions.dashes !== undefined && newOptions.dashes !== null) {
  124. parentOptions.dashes = newOptions.dashes;
  125. }
  126. else if (allowDeletion === true && newOptions.dashes === null) {
  127. parentOptions.dashes = Object.create(globalOptions.dashes); // this sets the pointer of the option back to the global option.
  128. }
  129. // set the scaling newOptions
  130. if (newOptions.scaling !== undefined && newOptions.scaling !== null) {
  131. if (newOptions.scaling.min !== undefined) {parentOptions.scaling.min = newOptions.scaling.min;}
  132. if (newOptions.scaling.max !== undefined) {parentOptions.scaling.max = newOptions.scaling.max;}
  133. util.mergeOptions(parentOptions.scaling, newOptions.scaling, 'label', globalOptions.scaling);
  134. }
  135. else if (allowDeletion === true && newOptions.scaling === null) {
  136. parentOptions.scaling = Object.create(globalOptions.scaling); // this sets the pointer of the option back to the global option.
  137. }
  138. // handle multiple input cases for arrows
  139. if (newOptions.arrows !== undefined && newOptions.arrows !== null) {
  140. if (typeof newOptions.arrows === 'string') {
  141. let arrows = newOptions.arrows.toLowerCase();
  142. parentOptions.arrows.to.enabled = arrows.indexOf("to") != -1;
  143. parentOptions.arrows.middle.enabled = arrows.indexOf("middle") != -1;
  144. parentOptions.arrows.from.enabled = arrows.indexOf("from") != -1;
  145. }
  146. else if (typeof newOptions.arrows === 'object') {
  147. util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'to', globalOptions.arrows);
  148. util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'middle', globalOptions.arrows);
  149. util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'from', globalOptions.arrows);
  150. }
  151. else {
  152. throw new Error("The arrow newOptions can only be an object or a string. Refer to the documentation. You used:" + JSON.stringify(newOptions.arrows));
  153. }
  154. }
  155. else if (allowDeletion === true && newOptions.arrows === null) {
  156. parentOptions.arrows = Object.create(globalOptions.arrows); // this sets the pointer of the option back to the global option.
  157. }
  158. // handle multiple input cases for color
  159. if (newOptions.color !== undefined && newOptions.color !== null) {
  160. let fromColor = newOptions.color;
  161. let toColor = parentOptions.color;
  162. // If passed, fill in values from default options - required in the case of no prototype bridging
  163. if (copyFromGlobals) {
  164. util.deepExtend(toColor, globalOptions.color, false, allowDeletion);
  165. } else {
  166. // Clear local properties - need to do it like this in order to retain prototype bridges
  167. for (var i in toColor) {
  168. if (toColor.hasOwnProperty(i)) {
  169. delete toColor[i];
  170. }
  171. }
  172. }
  173. if (util.isString(toColor)) {
  174. toColor.color = toColor;
  175. toColor.highlight = toColor;
  176. toColor.hover = toColor;
  177. toColor.inherit = false;
  178. if (fromColor.opacity === undefined) {
  179. toColor.opacity = 1.0; // set default
  180. }
  181. }
  182. else {
  183. let colorsDefined = false;
  184. if (fromColor.color !== undefined) {toColor.color = fromColor.color; colorsDefined = true;}
  185. if (fromColor.highlight !== undefined) {toColor.highlight = fromColor.highlight; colorsDefined = true;}
  186. if (fromColor.hover !== undefined) {toColor.hover = fromColor.hover; colorsDefined = true;}
  187. if (fromColor.inherit !== undefined) {toColor.inherit = fromColor.inherit;}
  188. if (fromColor.opacity !== undefined) {toColor.opacity = Math.min(1,Math.max(0,fromColor.opacity));}
  189. if (colorsDefined === true) {
  190. toColor.inherit = false;
  191. } else {
  192. if (toColor.inherit === undefined) {
  193. toColor.inherit = 'from'; // Set default
  194. }
  195. }
  196. }
  197. }
  198. else if (allowDeletion === true && newOptions.color === null) {
  199. parentOptions.color = util.bridgeObject(globalOptions.color); // set the object back to the global options
  200. }
  201. 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 = [
  288. options,
  289. this.options,
  290. this.globalOptions, // Currently set global edge options
  291. this.defaultOptions
  292. ];
  293. this.labelModule.update(this.options, pile);
  294. if (this.labelModule.baseSize !== undefined) {
  295. this.baseFontSize = this.labelModule.baseSize;
  296. }
  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. let rotationPoint = this._getRotation(ctx);
  513. if (rotationPoint.angle != 0) {
  514. ctx.translate(rotationPoint.x, rotationPoint.y);
  515. ctx.rotate(rotationPoint.angle);
  516. }
  517. // draw the label
  518. this.labelModule.draw(ctx, point.x, point.y, this.selected, this.hover);
  519. /*
  520. // Useful debug code: draw a border around the label
  521. // This should **not** be enabled in production!
  522. var size = this.labelModule.getSize();; // ;; intentional so lint catches it
  523. ctx.strokeStyle = "#ff0000";
  524. ctx.strokeRect(size.left, size.top, size.width, size.height);
  525. // End debug code
  526. */
  527. ctx.restore();
  528. }
  529. else {
  530. // Ignore the orientations.
  531. this.labelModule.pointToSelf = true;
  532. var x, y;
  533. var radius = this.options.selfReferenceSize;
  534. if (node1.shape.width > node1.shape.height) {
  535. x = node1.x + node1.shape.width * 0.5;
  536. y = node1.y - radius;
  537. }
  538. else {
  539. x = node1.x + radius;
  540. y = node1.y - node1.shape.height * 0.5;
  541. }
  542. point = this._pointOnCircle(x, y, radius, 0.125);
  543. this.labelModule.draw(ctx, point.x, point.y, this.selected, this.hover);
  544. }
  545. }
  546. }
  547. /**
  548. * Determine all visual elements of this edge instance, in which the given
  549. * point falls within the bounding shape.
  550. *
  551. * @param {point} point
  552. * @returns {Array.<edgeClickItem|edgeLabelClickItem>} list with the items which are on the point
  553. */
  554. getItemsOnPoint(point) {
  555. var ret = [];
  556. if (this.labelModule.visible()) {
  557. let rotationPoint = this._getRotation();
  558. if (ComponentUtil.pointInRect(this.labelModule.getSize(), point, rotationPoint)) {
  559. ret.push({edgeId:this.id, labelId:0});
  560. }
  561. }
  562. let obj = {
  563. left: point.x,
  564. top: point.y
  565. };
  566. if (this.isOverlappingWith(obj)) {
  567. ret.push({edgeId:this.id});
  568. }
  569. return ret;
  570. }
  571. /**
  572. * Check if this object is overlapping with the provided object
  573. * @param {Object} obj an object with parameters left, top
  574. * @return {boolean} True if location is located on the edge
  575. */
  576. isOverlappingWith(obj) {
  577. if (this.connected) {
  578. var distMax = 10;
  579. var xFrom = this.from.x;
  580. var yFrom = this.from.y;
  581. var xTo = this.to.x;
  582. var yTo = this.to.y;
  583. var xObj = obj.left;
  584. var yObj = obj.top;
  585. var dist = this.edgeType.getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
  586. return (dist < distMax);
  587. }
  588. else {
  589. return false
  590. }
  591. }
  592. /**
  593. * Determine the rotation point, if any.
  594. *
  595. * @param {CanvasRenderingContext2D} [ctx] if passed, do a recalculation of the label size
  596. * @returns {rotationPoint} the point to rotate around and the angle in radians to rotate
  597. * @private
  598. */
  599. _getRotation(ctx) {
  600. let viaNode = this.edgeType.getViaNode();
  601. let point = this.edgeType.getPoint(0.5, viaNode);
  602. if (ctx !== undefined) {
  603. this.labelModule.calculateLabelSize(ctx, this.selected, this.hover, point.x, point.y);
  604. }
  605. let ret = {
  606. x: point.x,
  607. y: this.labelModule.size.yLine,
  608. angle: 0
  609. };
  610. if (!this.labelModule.visible()) {
  611. return ret; // Don't even bother doing the atan2, there's nothing to draw
  612. }
  613. if (this.options.font.align === "horizontal") {
  614. return ret; // No need to calculate angle
  615. }
  616. var dy = this.from.y - this.to.y;
  617. var dx = this.from.x - this.to.x;
  618. var angle = Math.atan2(dy, dx); // radians
  619. // rotate so that label is readable
  620. if ((angle < -1 && dx < 0) || (angle > 0 && dx < 0)) {
  621. angle += Math.PI;
  622. }
  623. ret.angle = angle;
  624. return ret;
  625. }
  626. /**
  627. * Get a point on a circle
  628. * @param {number} x
  629. * @param {number} y
  630. * @param {number} radius
  631. * @param {number} percentage Value between 0 (line start) and 1 (line end)
  632. * @return {Object} point
  633. * @private
  634. */
  635. _pointOnCircle(x, y, radius, percentage) {
  636. var angle = percentage * 2 * Math.PI;
  637. return {
  638. x: x + radius * Math.cos(angle),
  639. y: y - radius * Math.sin(angle)
  640. }
  641. }
  642. /**
  643. * Sets selected state to true
  644. */
  645. select() {
  646. this.selected = true;
  647. }
  648. /**
  649. * Sets selected state to false
  650. */
  651. unselect() {
  652. this.selected = false;
  653. }
  654. /**
  655. * cleans all required things on delete
  656. * @returns {*}
  657. */
  658. cleanup() {
  659. return this.edgeType.cleanup();
  660. }
  661. /**
  662. * Remove edge from the list and perform necessary cleanup.
  663. */
  664. remove() {
  665. this.cleanup();
  666. this.disconnect();
  667. delete this.body.edges[this.id];
  668. }
  669. /**
  670. * Check if both connecting nodes exist
  671. * @returns {boolean}
  672. */
  673. endPointsValid() {
  674. return this.body.nodes[this.fromId] !== undefined
  675. && this.body.nodes[this.toId] !== undefined;
  676. }
  677. }
  678. export default Edge;