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.

786 lines
24 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. 'widthConstraint'
  113. ];
  114. // only deep extend the items in the field array. These do not have shorthand.
  115. util.selectiveDeepExtend(fields, parentOptions, newOptions, allowDeletion);
  116. // Only copy label if it's a legal value.
  117. if (ComponentUtil.isValidLabel(newOptions.label)) {
  118. parentOptions.label = newOptions.label;
  119. } else {
  120. parentOptions.label = undefined;
  121. }
  122. util.mergeOptions(parentOptions, newOptions, 'smooth', globalOptions);
  123. util.mergeOptions(parentOptions, newOptions, 'shadow', globalOptions);
  124. util.mergeOptions(parentOptions, newOptions, 'background', globalOptions);
  125. if (newOptions.dashes !== undefined && newOptions.dashes !== null) {
  126. parentOptions.dashes = newOptions.dashes;
  127. }
  128. else if (allowDeletion === true && newOptions.dashes === null) {
  129. parentOptions.dashes = Object.create(globalOptions.dashes); // this sets the pointer of the option back to the global option.
  130. }
  131. // set the scaling newOptions
  132. if (newOptions.scaling !== undefined && newOptions.scaling !== null) {
  133. if (newOptions.scaling.min !== undefined) {parentOptions.scaling.min = newOptions.scaling.min;}
  134. if (newOptions.scaling.max !== undefined) {parentOptions.scaling.max = newOptions.scaling.max;}
  135. util.mergeOptions(parentOptions.scaling, newOptions.scaling, 'label', globalOptions.scaling);
  136. }
  137. else if (allowDeletion === true && newOptions.scaling === null) {
  138. parentOptions.scaling = Object.create(globalOptions.scaling); // this sets the pointer of the option back to the global option.
  139. }
  140. // handle multiple input cases for arrows
  141. if (newOptions.arrows !== undefined && newOptions.arrows !== null) {
  142. if (typeof newOptions.arrows === 'string') {
  143. let arrows = newOptions.arrows.toLowerCase();
  144. parentOptions.arrows.to.enabled = arrows.indexOf("to") != -1;
  145. parentOptions.arrows.middle.enabled = arrows.indexOf("middle") != -1;
  146. parentOptions.arrows.from.enabled = arrows.indexOf("from") != -1;
  147. }
  148. else if (typeof newOptions.arrows === 'object') {
  149. util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'to', globalOptions.arrows);
  150. util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'middle', globalOptions.arrows);
  151. util.mergeOptions(parentOptions.arrows, newOptions.arrows, 'from', globalOptions.arrows);
  152. }
  153. else {
  154. throw new Error("The arrow newOptions can only be an object or a string. Refer to the documentation. You used:" + JSON.stringify(newOptions.arrows));
  155. }
  156. }
  157. else if (allowDeletion === true && newOptions.arrows === null) {
  158. parentOptions.arrows = Object.create(globalOptions.arrows); // this sets the pointer of the option back to the global option.
  159. }
  160. // handle multiple input cases for color
  161. if (newOptions.color !== undefined && newOptions.color !== null) {
  162. let fromColor = newOptions.color;
  163. let toColor = parentOptions.color;
  164. // If passed, fill in values from default options - required in the case of no prototype bridging
  165. if (copyFromGlobals) {
  166. util.deepExtend(toColor, globalOptions.color, false, allowDeletion);
  167. } else {
  168. // Clear local properties - need to do it like this in order to retain prototype bridges
  169. for (var i in toColor) {
  170. if (toColor.hasOwnProperty(i)) {
  171. delete toColor[i];
  172. }
  173. }
  174. }
  175. if (util.isString(toColor)) {
  176. toColor.color = toColor;
  177. toColor.highlight = toColor;
  178. toColor.hover = toColor;
  179. toColor.inherit = false;
  180. if (fromColor.opacity === undefined) {
  181. toColor.opacity = 1.0; // set default
  182. }
  183. }
  184. else {
  185. let colorsDefined = false;
  186. if (fromColor.color !== undefined) {toColor.color = fromColor.color; colorsDefined = true;}
  187. if (fromColor.highlight !== undefined) {toColor.highlight = fromColor.highlight; colorsDefined = true;}
  188. if (fromColor.hover !== undefined) {toColor.hover = fromColor.hover; colorsDefined = true;}
  189. if (fromColor.inherit !== undefined) {toColor.inherit = fromColor.inherit;}
  190. if (fromColor.opacity !== undefined) {toColor.opacity = Math.min(1,Math.max(0,fromColor.opacity));}
  191. if (colorsDefined === true) {
  192. toColor.inherit = false;
  193. } else {
  194. if (toColor.inherit === undefined) {
  195. toColor.inherit = 'from'; // Set default
  196. }
  197. }
  198. }
  199. }
  200. else if (allowDeletion === true && newOptions.color === null) {
  201. parentOptions.color = util.bridgeObject(globalOptions.color); // set the object back to the global options
  202. }
  203. if (allowDeletion === true && newOptions.font === null) {
  204. parentOptions.font = util.bridgeObject(globalOptions.font); // set the object back to the global options
  205. }
  206. }
  207. /**
  208. *
  209. * @returns {ArrowOptions}
  210. */
  211. getFormattingValues() {
  212. let toArrow = (this.options.arrows.to === true) || (this.options.arrows.to.enabled === true)
  213. let fromArrow = (this.options.arrows.from === true) || (this.options.arrows.from.enabled === true)
  214. let middleArrow = (this.options.arrows.middle === true) || (this.options.arrows.middle.enabled === true)
  215. let inheritsColor = this.options.color.inherit;
  216. let values = {
  217. toArrow: toArrow,
  218. toArrowScale: this.options.arrows.to.scaleFactor,
  219. toArrowType: this.options.arrows.to.type,
  220. middleArrow: middleArrow,
  221. middleArrowScale: this.options.arrows.middle.scaleFactor,
  222. middleArrowType: this.options.arrows.middle.type,
  223. fromArrow: fromArrow,
  224. fromArrowScale: this.options.arrows.from.scaleFactor,
  225. fromArrowType: this.options.arrows.from.type,
  226. arrowStrikethrough: this.options.arrowStrikethrough,
  227. color: (inheritsColor? undefined : this.options.color.color),
  228. inheritsColor: inheritsColor,
  229. opacity: this.options.color.opacity,
  230. hidden: this.options.hidden,
  231. length: this.options.length,
  232. shadow: this.options.shadow.enabled,
  233. shadowColor: this.options.shadow.color,
  234. shadowSize: this.options.shadow.size,
  235. shadowX: this.options.shadow.x,
  236. shadowY: this.options.shadow.y,
  237. dashes: this.options.dashes,
  238. width: this.options.width,
  239. background: this.options.background.enabled,
  240. backgroundColor: this.options.background.color,
  241. backgroundSize: this.options.background.size,
  242. backgroundDashes: this.options.background.dashes
  243. };
  244. if (this.selected || this.hover) {
  245. if (this.chooser === true) {
  246. if (this.selected) {
  247. let selectedWidth = this.options.selectionWidth;
  248. if (typeof selectedWidth === 'function') {
  249. values.width = selectedWidth(values.width);
  250. } else if (typeof selectedWidth === 'number') {
  251. values.width += selectedWidth;
  252. }
  253. values.width = Math.max(values.width, 0.3 / this.body.view.scale);
  254. values.color = this.options.color.highlight;
  255. values.shadow = this.options.shadow.enabled;
  256. } else if (this.hover) {
  257. let hoverWidth = this.options.hoverWidth;
  258. if (typeof hoverWidth === 'function') {
  259. values.width = hoverWidth(values.width);
  260. } else if (typeof hoverWidth === 'number') {
  261. values.width += hoverWidth;
  262. }
  263. values.width = Math.max(values.width, 0.3 / this.body.view.scale);
  264. values.color = this.options.color.hover;
  265. values.shadow = this.options.shadow.enabled;
  266. }
  267. } else if (typeof this.chooser === 'function') {
  268. this.chooser(values, this.options.id, this.selected, this.hover);
  269. if (values.color !== undefined) {
  270. values.inheritsColor = false;
  271. }
  272. if (values.shadow === false) {
  273. if ((values.shadowColor !== this.options.shadow.color) ||
  274. (values.shadowSize !== this.options.shadow.size) ||
  275. (values.shadowX !== this.options.shadow.x) ||
  276. (values.shadowY !== this.options.shadow.y)) {
  277. values.shadow = true;
  278. }
  279. }
  280. }
  281. } else {
  282. values.shadow = this.options.shadow.enabled;
  283. values.width = Math.max(values.width, 0.3 / this.body.view.scale);
  284. }
  285. return values;
  286. }
  287. /**
  288. * update the options in the label module
  289. *
  290. * @param {Object} options
  291. */
  292. updateLabelModule(options) {
  293. let pile = [
  294. options,
  295. this.options,
  296. this.globalOptions, // Currently set global edge options
  297. this.defaultOptions
  298. ];
  299. this.labelModule.update(this.options, pile);
  300. if (this.labelModule.baseSize !== undefined) {
  301. this.baseFontSize = this.labelModule.baseSize;
  302. }
  303. }
  304. /**
  305. * update the edge type, set the options
  306. * @returns {boolean}
  307. */
  308. updateEdgeType() {
  309. let smooth = this.options.smooth;
  310. let dataChanged = false;
  311. let changeInType = true;
  312. if (this.edgeType !== undefined) {
  313. if ((((this.edgeType instanceof BezierEdgeDynamic) &&
  314. (smooth.enabled === true) &&
  315. (smooth.type === 'dynamic'))) ||
  316. (((this.edgeType instanceof CubicBezierEdge) &&
  317. (smooth.enabled === true) &&
  318. (smooth.type === 'cubicBezier'))) ||
  319. (((this.edgeType instanceof BezierEdgeStatic) &&
  320. (smooth.enabled === true) &&
  321. (smooth.type !== 'dynamic') &&
  322. (smooth.type !== 'cubicBezier'))) ||
  323. (((this.edgeType instanceof StraightEdge) &&
  324. (smooth.type.enabled === false)))) {
  325. changeInType = false;
  326. }
  327. if (changeInType === true) {
  328. dataChanged = this.cleanup();
  329. }
  330. }
  331. if (changeInType === true) {
  332. if (smooth.enabled === true) {
  333. if (smooth.type === 'dynamic') {
  334. dataChanged = true;
  335. this.edgeType = new BezierEdgeDynamic(this.options, this.body, this.labelModule);
  336. } else if (smooth.type === 'cubicBezier') {
  337. this.edgeType = new CubicBezierEdge(this.options, this.body, this.labelModule);
  338. } else {
  339. this.edgeType = new BezierEdgeStatic(this.options, this.body, this.labelModule);
  340. }
  341. } else {
  342. this.edgeType = new StraightEdge(this.options, this.body, this.labelModule);
  343. }
  344. } else { // if nothing changes, we just set the options.
  345. this.edgeType.setOptions(this.options);
  346. }
  347. return dataChanged;
  348. }
  349. /**
  350. * Connect an edge to its nodes
  351. */
  352. connect() {
  353. this.disconnect();
  354. this.from = this.body.nodes[this.fromId] || undefined;
  355. this.to = this.body.nodes[this.toId] || undefined;
  356. this.connected = (this.from !== undefined && this.to !== undefined);
  357. if (this.connected === true) {
  358. this.from.attachEdge(this);
  359. this.to.attachEdge(this);
  360. }
  361. else {
  362. if (this.from) {
  363. this.from.detachEdge(this);
  364. }
  365. if (this.to) {
  366. this.to.detachEdge(this);
  367. }
  368. }
  369. this.edgeType.connect();
  370. }
  371. /**
  372. * Disconnect an edge from its nodes
  373. */
  374. disconnect() {
  375. if (this.from) {
  376. this.from.detachEdge(this);
  377. this.from = undefined;
  378. }
  379. if (this.to) {
  380. this.to.detachEdge(this);
  381. this.to = undefined;
  382. }
  383. this.connected = false;
  384. }
  385. /**
  386. * get the title of this edge.
  387. * @return {string} title The title of the edge, or undefined when no title
  388. * has been set.
  389. */
  390. getTitle() {
  391. return this.title;
  392. }
  393. /**
  394. * check if this node is selecte
  395. * @return {boolean} selected True if node is selected, else false
  396. */
  397. isSelected() {
  398. return this.selected;
  399. }
  400. /**
  401. * Retrieve the value of the edge. Can be undefined
  402. * @return {number} value
  403. */
  404. getValue() {
  405. return this.options.value;
  406. }
  407. /**
  408. * Adjust the value range of the edge. The edge will adjust it's width
  409. * based on its value.
  410. * @param {number} min
  411. * @param {number} max
  412. * @param {number} total
  413. */
  414. setValueRange(min, max, total) {
  415. if (this.options.value !== undefined) {
  416. var scale = this.options.scaling.customScalingFunction(min, max, total, this.options.value);
  417. var widthDiff = this.options.scaling.max - this.options.scaling.min;
  418. if (this.options.scaling.label.enabled === true) {
  419. var fontDiff = this.options.scaling.label.max - this.options.scaling.label.min;
  420. this.options.font.size = this.options.scaling.label.min + scale * fontDiff;
  421. }
  422. this.options.width = this.options.scaling.min + scale * widthDiff;
  423. }
  424. else {
  425. this.options.width = this.baseWidth;
  426. this.options.font.size = this.baseFontSize;
  427. }
  428. this._setInteractionWidths();
  429. this.updateLabelModule();
  430. }
  431. /**
  432. *
  433. * @private
  434. */
  435. _setInteractionWidths() {
  436. if (typeof this.options.hoverWidth === 'function') {
  437. this.edgeType.hoverWidth = this.options.hoverWidth(this.options.width);
  438. } else {
  439. this.edgeType.hoverWidth = this.options.hoverWidth + this.options.width;
  440. }
  441. if (typeof this.options.selectionWidth === 'function') {
  442. this.edgeType.selectionWidth = this.options.selectionWidth(this.options.width);
  443. } else {
  444. this.edgeType.selectionWidth = this.options.selectionWidth + this.options.width;
  445. }
  446. }
  447. /**
  448. * Redraw a edge
  449. * Draw this edge in the given canvas
  450. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  451. * @param {CanvasRenderingContext2D} ctx
  452. */
  453. draw(ctx) {
  454. let values = this.getFormattingValues();
  455. if (values.hidden) {
  456. return;
  457. }
  458. // get the via node from the edge type
  459. let viaNode = this.edgeType.getViaNode();
  460. let arrowData = {};
  461. // restore edge targets to defaults
  462. this.edgeType.fromPoint = this.edgeType.from;
  463. this.edgeType.toPoint = this.edgeType.to;
  464. // from and to arrows give a different end point for edges. we set them here
  465. if (values.fromArrow) {
  466. arrowData.from = this.edgeType.getArrowData(ctx, 'from', viaNode, this.selected, this.hover, values);
  467. if (values.arrowStrikethrough === false)
  468. this.edgeType.fromPoint = arrowData.from.core;
  469. }
  470. if (values.toArrow) {
  471. arrowData.to = this.edgeType.getArrowData(ctx, 'to', viaNode, this.selected, this.hover, values);
  472. if (values.arrowStrikethrough === false)
  473. this.edgeType.toPoint = arrowData.to.core;
  474. }
  475. // the middle arrow depends on the line, which can depend on the to and from arrows so we do this one lastly.
  476. if (values.middleArrow) {
  477. arrowData.middle = this.edgeType.getArrowData(ctx,'middle', viaNode, this.selected, this.hover, values);
  478. }
  479. // draw everything
  480. this.edgeType.drawLine(ctx, values, this.selected, this.hover, viaNode);
  481. this.drawArrows(ctx, arrowData, values);
  482. this.drawLabel(ctx, viaNode);
  483. }
  484. /**
  485. *
  486. * @param {CanvasRenderingContext2D} ctx
  487. * @param {Object} arrowData
  488. * @param {ArrowOptions} values
  489. */
  490. drawArrows(ctx, arrowData, values) {
  491. if (values.fromArrow) {
  492. this.edgeType.drawArrowHead(ctx, values, this.selected, this.hover, arrowData.from);
  493. }
  494. if (values.middleArrow) {
  495. this.edgeType.drawArrowHead(ctx, values, this.selected, this.hover, arrowData.middle);
  496. }
  497. if (values.toArrow) {
  498. this.edgeType.drawArrowHead(ctx, values, this.selected, this.hover, arrowData.to);
  499. }
  500. }
  501. /**
  502. *
  503. * @param {CanvasRenderingContext2D} ctx
  504. * @param {Node} viaNode
  505. */
  506. drawLabel(ctx, viaNode) {
  507. if (this.options.label !== undefined) {
  508. // set style
  509. var node1 = this.from;
  510. var node2 = this.to;
  511. if (this.labelModule.differentState(this.selected, this.hover)) {
  512. this.labelModule.getTextSize(ctx, this.selected, this.hover);
  513. }
  514. if (node1.id != node2.id) {
  515. this.labelModule.pointToSelf = false;
  516. var point = this.edgeType.getPoint(0.5, viaNode);
  517. ctx.save();
  518. let rotationPoint = this._getRotation(ctx);
  519. if (rotationPoint.angle != 0) {
  520. ctx.translate(rotationPoint.x, rotationPoint.y);
  521. ctx.rotate(rotationPoint.angle);
  522. }
  523. // draw the label
  524. this.labelModule.draw(ctx, point.x, point.y, this.selected, this.hover);
  525. /*
  526. // Useful debug code: draw a border around the label
  527. // This should **not** be enabled in production!
  528. var size = this.labelModule.getSize();; // ;; intentional so lint catches it
  529. ctx.strokeStyle = "#ff0000";
  530. ctx.strokeRect(size.left, size.top, size.width, size.height);
  531. // End debug code
  532. */
  533. ctx.restore();
  534. }
  535. else {
  536. // Ignore the orientations.
  537. this.labelModule.pointToSelf = true;
  538. var x, y;
  539. var radius = this.options.selfReferenceSize;
  540. if (node1.shape.width > node1.shape.height) {
  541. x = node1.x + node1.shape.width * 0.5;
  542. y = node1.y - radius;
  543. }
  544. else {
  545. x = node1.x + radius;
  546. y = node1.y - node1.shape.height * 0.5;
  547. }
  548. point = this._pointOnCircle(x, y, radius, 0.125);
  549. this.labelModule.draw(ctx, point.x, point.y, this.selected, this.hover);
  550. }
  551. }
  552. }
  553. /**
  554. * Determine all visual elements of this edge instance, in which the given
  555. * point falls within the bounding shape.
  556. *
  557. * @param {point} point
  558. * @returns {Array.<edgeClickItem|edgeLabelClickItem>} list with the items which are on the point
  559. */
  560. getItemsOnPoint(point) {
  561. var ret = [];
  562. if (this.labelModule.visible()) {
  563. let rotationPoint = this._getRotation();
  564. if (ComponentUtil.pointInRect(this.labelModule.getSize(), point, rotationPoint)) {
  565. ret.push({edgeId:this.id, labelId:0});
  566. }
  567. }
  568. let obj = {
  569. left: point.x,
  570. top: point.y
  571. };
  572. if (this.isOverlappingWith(obj)) {
  573. ret.push({edgeId:this.id});
  574. }
  575. return ret;
  576. }
  577. /**
  578. * Check if this object is overlapping with the provided object
  579. * @param {Object} obj an object with parameters left, top
  580. * @return {boolean} True if location is located on the edge
  581. */
  582. isOverlappingWith(obj) {
  583. if (this.connected) {
  584. var distMax = 10;
  585. var xFrom = this.from.x;
  586. var yFrom = this.from.y;
  587. var xTo = this.to.x;
  588. var yTo = this.to.y;
  589. var xObj = obj.left;
  590. var yObj = obj.top;
  591. var dist = this.edgeType.getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
  592. return (dist < distMax);
  593. }
  594. else {
  595. return false
  596. }
  597. }
  598. /**
  599. * Determine the rotation point, if any.
  600. *
  601. * @param {CanvasRenderingContext2D} [ctx] if passed, do a recalculation of the label size
  602. * @returns {rotationPoint} the point to rotate around and the angle in radians to rotate
  603. * @private
  604. */
  605. _getRotation(ctx) {
  606. let viaNode = this.edgeType.getViaNode();
  607. let point = this.edgeType.getPoint(0.5, viaNode);
  608. if (ctx !== undefined) {
  609. this.labelModule.calculateLabelSize(ctx, this.selected, this.hover, point.x, point.y);
  610. }
  611. let ret = {
  612. x: point.x,
  613. y: this.labelModule.size.yLine,
  614. angle: 0
  615. };
  616. if (!this.labelModule.visible()) {
  617. return ret; // Don't even bother doing the atan2, there's nothing to draw
  618. }
  619. if (this.options.font.align === "horizontal") {
  620. return ret; // No need to calculate angle
  621. }
  622. var dy = this.from.y - this.to.y;
  623. var dx = this.from.x - this.to.x;
  624. var angle = Math.atan2(dy, dx); // radians
  625. // rotate so that label is readable
  626. if ((angle < -1 && dx < 0) || (angle > 0 && dx < 0)) {
  627. angle += Math.PI;
  628. }
  629. ret.angle = angle;
  630. return ret;
  631. }
  632. /**
  633. * Get a point on a circle
  634. * @param {number} x
  635. * @param {number} y
  636. * @param {number} radius
  637. * @param {number} percentage Value between 0 (line start) and 1 (line end)
  638. * @return {Object} point
  639. * @private
  640. */
  641. _pointOnCircle(x, y, radius, percentage) {
  642. var angle = percentage * 2 * Math.PI;
  643. return {
  644. x: x + radius * Math.cos(angle),
  645. y: y - radius * Math.sin(angle)
  646. }
  647. }
  648. /**
  649. * Sets selected state to true
  650. */
  651. select() {
  652. this.selected = true;
  653. }
  654. /**
  655. * Sets selected state to false
  656. */
  657. unselect() {
  658. this.selected = false;
  659. }
  660. /**
  661. * cleans all required things on delete
  662. * @returns {*}
  663. */
  664. cleanup() {
  665. return this.edgeType.cleanup();
  666. }
  667. /**
  668. * Remove edge from the list and perform necessary cleanup.
  669. */
  670. remove() {
  671. this.cleanup();
  672. this.disconnect();
  673. delete this.body.edges[this.id];
  674. }
  675. /**
  676. * Check if both connecting nodes exist
  677. * @returns {boolean}
  678. */
  679. endPointsValid() {
  680. return this.body.nodes[this.fromId] !== undefined
  681. && this.body.nodes[this.toId] !== undefined;
  682. }
  683. }
  684. export default Edge;