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.

260 lines
7.9 KiB

  1. let util = require('../../../../util');
  2. class Label {
  3. constructor(body,options) {
  4. this.body = body;
  5. this.pointToSelf = false;
  6. this.baseSize = undefined;
  7. this.setOptions(options);
  8. this.size = {top: 0, left: 0, width: 0, height: 0, yLine: 0}; // could be cached
  9. }
  10. setOptions(options, allowDeletion = false) {
  11. this.options = options;
  12. if (options.label !== undefined) {
  13. this.labelDirty = true;
  14. }
  15. if (options.font !== undefined) {
  16. Label.parseOptions(this.options.font, options, allowDeletion);
  17. if (typeof options.font === 'string') {
  18. this.baseSize = this.options.font.size;
  19. }
  20. else if (typeof options.font === 'object') {
  21. if (options.font.size !== undefined) {
  22. this.baseSize = options.font.size;
  23. }
  24. }
  25. }
  26. }
  27. static parseOptions(parentOptions, newOptions, allowDeletion = false) {
  28. if (typeof newOptions.font === 'string') {
  29. let newOptionsArray = newOptions.font.split(" ");
  30. parentOptions.size = newOptionsArray[0].replace("px",'');
  31. parentOptions.face = newOptionsArray[1];
  32. parentOptions.color = newOptionsArray[2];
  33. }
  34. else if (typeof newOptions.font === 'object') {
  35. util.fillIfDefined(parentOptions, newOptions.font, allowDeletion);
  36. }
  37. parentOptions.size = Number(parentOptions.size);
  38. }
  39. /**
  40. * Main function. This is called from anything that wants to draw a label.
  41. * @param ctx
  42. * @param x
  43. * @param y
  44. * @param selected
  45. * @param baseline
  46. */
  47. draw(ctx, x, y, selected, baseline = 'middle') {
  48. // if no label, return
  49. if (this.options.label === undefined)
  50. return;
  51. // check if we have to render the label
  52. let viewFontSize = this.options.font.size * this.body.view.scale;
  53. if (this.options.label && viewFontSize < this.options.scaling.label.drawThreshold - 1)
  54. return;
  55. // update the size cache if required
  56. this.calculateLabelSize(ctx, selected, x, y, baseline);
  57. // create the fontfill background
  58. this._drawBackground(ctx);
  59. // draw text
  60. this._drawText(ctx, selected, x, y, baseline);
  61. }
  62. /**
  63. * Draws the label background
  64. * @param {CanvasRenderingContext2D} ctx
  65. * @private
  66. */
  67. _drawBackground(ctx) {
  68. if (this.options.font.background !== undefined && this.options.font.background !== "none") {
  69. ctx.fillStyle = this.options.font.background;
  70. let lineMargin = 2;
  71. switch (this.options.font.align) {
  72. case 'middle':
  73. ctx.fillRect(-this.size.width * 0.5, -this.size.height * 0.5, this.size.width, this.size.height);
  74. break;
  75. case 'top':
  76. ctx.fillRect(-this.size.width * 0.5, -(this.size.height + lineMargin), this.size.width, this.size.height);
  77. break;
  78. case 'bottom':
  79. ctx.fillRect(-this.size.width * 0.5, lineMargin, this.size.width, this.size.height);
  80. break;
  81. default:
  82. ctx.fillRect(this.size.left, this.size.top - 0.5*lineMargin, this.size.width, this.size.height);
  83. break;
  84. }
  85. }
  86. }
  87. /**
  88. *
  89. * @param ctx
  90. * @param x
  91. * @param baseline
  92. * @private
  93. */
  94. _drawText(ctx, selected, x, y, baseline = 'middle') {
  95. let fontSize = this.options.font.size;
  96. let viewFontSize = fontSize * this.body.view.scale;
  97. // this ensures that there will not be HUGE letters on screen by setting an upper limit on the visible text size (regardless of zoomLevel)
  98. if (viewFontSize >= this.options.scaling.label.maxVisible) {
  99. fontSize = Number(this.options.scaling.label.maxVisible) / this.body.view.scale;
  100. }
  101. let yLine = this.size.yLine;
  102. let [fontColor, strokeColor] = this._getColor(viewFontSize);
  103. [x, yLine] = this._setAlignment(ctx, x, yLine, baseline);
  104. // configure context for drawing the text
  105. ctx.font = (selected && this.options.labelHighlightBold ? 'bold ' : '') + fontSize + "px " + this.options.font.face;
  106. ctx.fillStyle = fontColor;
  107. ctx.textAlign = 'center';
  108. // set the strokeWidth
  109. if (this.options.font.strokeWidth > 0) {
  110. ctx.lineWidth = this.options.font.strokeWidth;
  111. ctx.strokeStyle = strokeColor;
  112. ctx.lineJoin = 'round';
  113. }
  114. // draw the text
  115. for (let i = 0; i < this.lineCount; i++) {
  116. if (this.options.font.strokeWidth > 0) {
  117. ctx.strokeText(this.lines[i], x, yLine);
  118. }
  119. ctx.fillText(this.lines[i], x, yLine);
  120. yLine += fontSize;
  121. }
  122. }
  123. _setAlignment(ctx, x, yLine, baseline) {
  124. // check for label alignment (for edges)
  125. // TODO: make alignment for nodes
  126. if (this.options.font.align !== 'horizontal' && this.pointToSelf === false) {
  127. x = 0;
  128. yLine = 0;
  129. let lineMargin = 2;
  130. if (this.options.font.align === 'top') {
  131. ctx.textBaseline = 'alphabetic';
  132. yLine -= 2 * lineMargin; // distance from edge, required because we use alphabetic. Alphabetic has less difference between browsers
  133. }
  134. else if (this.options.font.align === 'bottom') {
  135. ctx.textBaseline = 'hanging';
  136. yLine += 2 * lineMargin;// distance from edge, required because we use hanging. Hanging has less difference between browsers
  137. }
  138. else {
  139. ctx.textBaseline = 'middle';
  140. }
  141. }
  142. else {
  143. ctx.textBaseline = baseline;
  144. }
  145. return [x,yLine];
  146. }
  147. /**
  148. * fade in when relative scale is between threshold and threshold - 1.
  149. * If the relative scale would be smaller than threshold -1 the draw function would have returned before coming here.
  150. *
  151. * @param viewFontSize
  152. * @returns {*[]}
  153. * @private
  154. */
  155. _getColor(viewFontSize) {
  156. let fontColor = this.options.font.color || '#000000';
  157. let strokeColor = this.options.font.strokeColor || '#ffffff';
  158. if (viewFontSize <= this.options.scaling.label.drawThreshold) {
  159. let opacity = Math.max(0, Math.min(1, 1 - (this.options.scaling.label.drawThreshold - viewFontSize)));
  160. fontColor = util.overrideOpacity(fontColor, opacity);
  161. strokeColor = util.overrideOpacity(strokeColor, opacity);
  162. }
  163. return [fontColor, strokeColor];
  164. }
  165. /**
  166. *
  167. * @param ctx
  168. * @param selected
  169. * @returns {{width: number, height: number}}
  170. */
  171. getTextSize(ctx, selected = false) {
  172. let size = {
  173. width: this._processLabel(ctx,selected),
  174. height: this.options.font.size * this.lineCount,
  175. lineCount: this.lineCount
  176. };
  177. return size;
  178. }
  179. /**
  180. *
  181. * @param ctx
  182. * @param selected
  183. * @param x
  184. * @param y
  185. * @param baseline
  186. */
  187. calculateLabelSize(ctx, selected, x = 0, y = 0, baseline = 'middle') {
  188. if (this.labelDirty === true) {
  189. this.size.width = this._processLabel(ctx,selected);
  190. }
  191. this.size.height = this.options.font.size * this.lineCount;
  192. this.size.left = x - this.size.width * 0.5;
  193. this.size.top = y - this.size.height * 0.5;
  194. this.size.yLine = y + (1 - this.lineCount) * 0.5 * this.options.font.size;
  195. if (baseline === "hanging") {
  196. this.size.top += 0.5 * this.options.font.size;
  197. this.size.top += 4; // distance from node, required because we use hanging. Hanging has less difference between browsers
  198. this.size.yLine += 4; // distance from node
  199. }
  200. this.labelDirty = false;
  201. }
  202. /**
  203. * This calculates the width as well as explodes the label string and calculates the amount of lines.
  204. * @param ctx
  205. * @param selected
  206. * @returns {number}
  207. * @private
  208. */
  209. _processLabel(ctx,selected) {
  210. let width = 0;
  211. let lines = [''];
  212. let lineCount = 0;
  213. if (this.options.label !== undefined) {
  214. lines = String(this.options.label).split('\n');
  215. lineCount = lines.length;
  216. ctx.font = (selected && this.options.labelHighlightBold ? 'bold ' : '') + this.options.font.size + "px " + this.options.font.face;
  217. width = ctx.measureText(lines[0]).width;
  218. for (let i = 1; i < lineCount; i++) {
  219. let lineWidth = ctx.measureText(lines[i]).width;
  220. width = lineWidth > width ? lineWidth : width;
  221. }
  222. }
  223. this.lines = lines;
  224. this.lineCount = lineCount;
  225. return width;
  226. }
  227. }
  228. export default Label;