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.

262 lines
7.7 KiB

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