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.

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