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.

295 lines
14 KiB

  1. /**
  2. * The Base class for all Nodes.
  3. */
  4. class NodeBase {
  5. /**
  6. * @param {Object} options
  7. * @param {Object} body
  8. * @param {Label} labelModule
  9. */
  10. constructor(options, body, labelModule) {
  11. this.body = body;
  12. this.labelModule = labelModule;
  13. this.setOptions(options);
  14. this.top = undefined;
  15. this.left = undefined;
  16. this.height = undefined;
  17. this.width = undefined;
  18. this.radius = undefined;
  19. this.margin = undefined;
  20. this.refreshNeeded = true;
  21. this.boundingBox = {top: 0, left: 0, right: 0, bottom: 0};
  22. }
  23. /**
  24. *
  25. * @param {Object} options
  26. */
  27. setOptions(options) {
  28. this.options = options;
  29. }
  30. /**
  31. *
  32. * @param {Label} labelModule
  33. * @private
  34. */
  35. _setMargins(labelModule) {
  36. this.margin = {};
  37. if (this.options.margin) {
  38. if (typeof this.options.margin == 'object') {
  39. this.margin.top = this.options.margin.top;
  40. this.margin.right = this.options.margin.right;
  41. this.margin.bottom = this.options.margin.bottom;
  42. this.margin.left = this.options.margin.left;
  43. } else {
  44. this.margin.top = this.options.margin;
  45. this.margin.right = this.options.margin;
  46. this.margin.bottom = this.options.margin;
  47. this.margin.left = this.options.margin;
  48. }
  49. }
  50. labelModule.adjustSizes(this.margin)
  51. }
  52. /**
  53. *
  54. * @param {CanvasRenderingContext2D} ctx
  55. * @param {number} angle
  56. * @returns {number}
  57. * @private
  58. */
  59. _distanceToBorder(ctx,angle) {
  60. var borderWidth = this.options.borderWidth;
  61. this.resize(ctx);
  62. return Math.min(
  63. Math.abs(this.width / 2 / Math.cos(angle)),
  64. Math.abs(this.height / 2 / Math.sin(angle))) + borderWidth;
  65. }
  66. /**
  67. *
  68. * @param {CanvasRenderingContext2D} ctx
  69. * @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
  70. */
  71. enableShadow(ctx, values) {
  72. if (values.shadow) {
  73. ctx.shadowColor = values.shadowColor;
  74. ctx.shadowBlur = values.shadowSize;
  75. ctx.shadowOffsetX = values.shadowX;
  76. ctx.shadowOffsetY = values.shadowY;
  77. }
  78. }
  79. /**
  80. *
  81. * @param {CanvasRenderingContext2D} ctx
  82. * @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
  83. */
  84. disableShadow(ctx, values) {
  85. if (values.shadow) {
  86. ctx.shadowColor = 'rgba(0,0,0,0)';
  87. ctx.shadowBlur = 0;
  88. ctx.shadowOffsetX = 0;
  89. ctx.shadowOffsetY = 0;
  90. }
  91. }
  92. /**
  93. *
  94. * @param {CanvasRenderingContext2D} ctx
  95. * @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
  96. */
  97. enableBorderDashes(ctx, values) {
  98. if (values.borderDashes !== false) {
  99. if (ctx.setLineDash !== undefined) {
  100. let dashes = values.borderDashes;
  101. if (dashes === true) {
  102. dashes = [5,15]
  103. }
  104. ctx.setLineDash(dashes);
  105. }
  106. else {
  107. console.warn("setLineDash is not supported in this browser. The dashed borders cannot be used.");
  108. this.options.shapeProperties.borderDashes = false;
  109. values.borderDashes = false;
  110. }
  111. }
  112. }
  113. /**
  114. *
  115. * @param {CanvasRenderingContext2D} ctx
  116. * @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
  117. */
  118. disableBorderDashes(ctx, values) {
  119. if (values.borderDashes !== false) {
  120. if (ctx.setLineDash !== undefined) {
  121. ctx.setLineDash([0]);
  122. }
  123. else {
  124. console.warn("setLineDash is not supported in this browser. The dashed borders cannot be used.");
  125. this.options.shapeProperties.borderDashes = false;
  126. values.borderDashes = false;
  127. }
  128. }
  129. }
  130. /**
  131. * Determine if the shape of a node needs to be recalculated.
  132. *
  133. * @param {boolean} selected
  134. * @param {boolean} hover
  135. * @returns {boolean}
  136. * @protected
  137. */
  138. needsRefresh(selected, hover) {
  139. if (this.refreshNeeded === true) {
  140. // This is probably not the best location to reset this member.
  141. // However, in the current logic, it is the most convenient one.
  142. this.refreshNeeded = false;
  143. return true;
  144. }
  145. return (this.width === undefined) || (this.labelModule.differentState(selected, hover));
  146. }
  147. /**
  148. *
  149. * @param {CanvasRenderingContext2D} ctx
  150. * @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
  151. */
  152. initContextForDraw(ctx, values) {
  153. var borderWidth = values.borderWidth / this.body.view.scale;
  154. ctx.lineWidth = Math.min(this.width, borderWidth);
  155. ctx.strokeStyle = values.borderColor;
  156. ctx.fillStyle = values.color;
  157. }
  158. /**
  159. *
  160. * @param {CanvasRenderingContext2D} ctx
  161. * @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
  162. */
  163. performStroke(ctx, values) {
  164. var borderWidth = values.borderWidth / this.body.view.scale;
  165. //draw dashed border if enabled, save and restore is required for firefox not to crash on unix.
  166. ctx.save();
  167. // if borders are zero width, they will be drawn with width 1 by default. This prevents that
  168. if (borderWidth > 0) {
  169. this.enableBorderDashes(ctx, values);
  170. //draw the border
  171. ctx.stroke();
  172. //disable dashed border for other elements
  173. this.disableBorderDashes(ctx, values);
  174. }
  175. ctx.restore();
  176. }
  177. /**
  178. *
  179. * @param {CanvasRenderingContext2D} ctx
  180. * @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
  181. */
  182. performFill(ctx, values) {
  183. // draw shadow if enabled
  184. this.enableShadow(ctx, values);
  185. // draw the background
  186. ctx.fill();
  187. // disable shadows for other elements.
  188. this.disableShadow(ctx, values);
  189. this.performStroke(ctx, values);
  190. }
  191. /**
  192. *
  193. * @param {number} margin
  194. * @private
  195. */
  196. _addBoundingBoxMargin(margin) {
  197. this.boundingBox.left -= margin;
  198. this.boundingBox.top -= margin;
  199. this.boundingBox.bottom += margin;
  200. this.boundingBox.right += margin;
  201. }
  202. /**
  203. * Actual implementation of this method call.
  204. *
  205. * Doing it like this makes it easier to override
  206. * in the child classes.
  207. *
  208. * @param {number} x width
  209. * @param {number} y height
  210. * @param {CanvasRenderingContext2D} ctx
  211. * @param {boolean} selected
  212. * @param {boolean} hover
  213. * @private
  214. */
  215. _updateBoundingBox(x, y, ctx, selected, hover) {
  216. if (ctx !== undefined) {
  217. this.resize(ctx, selected, hover);
  218. }
  219. this.left = x - this.width / 2;
  220. this.top = y - this.height/ 2;
  221. this.boundingBox.left = this.left;
  222. this.boundingBox.top = this.top;
  223. this.boundingBox.bottom = this.top + this.height;
  224. this.boundingBox.right = this.left + this.width;
  225. }
  226. /**
  227. * Default implementation of this method call.
  228. * This acts as a stub which can be overridden.
  229. *
  230. * @param {number} x width
  231. * @param {number} y height
  232. * @param {CanvasRenderingContext2D} ctx
  233. * @param {boolean} selected
  234. * @param {boolean} hover
  235. */
  236. updateBoundingBox(x, y, ctx, selected, hover) {
  237. this._updateBoundingBox(x, y, ctx, selected, hover);
  238. }
  239. /**
  240. * Determine the dimensions to use for nodes with an internal label
  241. *
  242. * Currently, these are: Circle, Ellipse, Database, Box
  243. * The other nodes have external labels, and will not call this method
  244. *
  245. * If there is no label, decent default values are supplied.
  246. *
  247. * @param {CanvasRenderingContext2D} ctx
  248. * @param {boolean} [selected]
  249. * @param {boolean} [hover]
  250. * @returns {{width:number, height:number}}
  251. */
  252. getDimensionsFromLabel(ctx, selected, hover) {
  253. // NOTE: previously 'textSize' was not put in 'this' for Ellipse
  254. // TODO: examine the consequences.
  255. this.textSize = this.labelModule.getTextSize(ctx, selected, hover);
  256. var width = this.textSize.width;
  257. var height = this.textSize.height;
  258. const DEFAULT_SIZE = 14;
  259. if (width === 0) {
  260. // This happens when there is no label text set
  261. width = DEFAULT_SIZE; // use a decent default
  262. height = DEFAULT_SIZE; // if width zero, then height also always zero
  263. }
  264. return {width:width, height:height};
  265. }
  266. }
  267. export default NodeBase;