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.

235 lines
6.9 KiB

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