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.

238 lines
5.6 KiB

  1. /**
  2. * Callback to determine text dimensions, using the parent label settings.
  3. * @callback MeasureText
  4. * @param {text} text
  5. * @param {text} mod
  6. * @return {Object} { width, values} width in pixels and font attributes
  7. */
  8. /**
  9. * Helper class for Label which collects results of splitting labels into lines and blocks.
  10. *
  11. * @private
  12. */
  13. class LabelAccumulator {
  14. /**
  15. * @param {MeasureText} measureText
  16. */
  17. constructor(measureText) {
  18. this.measureText = measureText;
  19. this.current = 0;
  20. this.width = 0;
  21. this.height = 0;
  22. this.lines = [];
  23. }
  24. /**
  25. * Append given text to the given line.
  26. *
  27. * @param {number} l index of line to add to
  28. * @param {string} text string to append to line
  29. * @param {'bold'|'ital'|'boldital'|'mono'|'normal'} [mod='normal']
  30. * @private
  31. */
  32. _add(l, text, mod = 'normal') {
  33. if (this.lines[l] === undefined) {
  34. this.lines[l] = {
  35. width : 0,
  36. height: 0,
  37. blocks: []
  38. };
  39. }
  40. // We still need to set a block for undefined and empty texts, hence return at this point
  41. // This is necessary because we don't know at this point if we're at the
  42. // start of an empty line or not.
  43. // To compensate, empty blocks are removed in `finalize()`.
  44. //
  45. // Empty strings should still have a height
  46. let tmpText = text;
  47. if (text === undefined || text === "") tmpText = " ";
  48. // Determine width and get the font properties
  49. let result = this.measureText(tmpText, mod);
  50. let block = Object.assign({}, result.values);
  51. block.text = text;
  52. block.width = result.width;
  53. block.mod = mod;
  54. if (text === undefined || text === "") {
  55. block.width = 0;
  56. }
  57. this.lines[l].blocks.push(block);
  58. // Update the line width. We need this for determining if a string goes over max width
  59. this.lines[l].width += block.width;
  60. }
  61. /**
  62. * Returns the width in pixels of the current line.
  63. *
  64. * @returns {number}
  65. */
  66. curWidth() {
  67. let line = this.lines[this.current];
  68. if (line === undefined) return 0;
  69. return line.width;
  70. }
  71. /**
  72. * Add text in block to current line
  73. *
  74. * @param {string} text
  75. * @param {'bold'|'ital'|'boldital'|'mono'|'normal'} [mod='normal']
  76. */
  77. append(text, mod = 'normal') {
  78. this._add(this.current, text, mod);
  79. }
  80. /**
  81. * Add text in block to current line and start a new line
  82. *
  83. * @param {string} text
  84. * @param {'bold'|'ital'|'boldital'|'mono'|'normal'} [mod='normal']
  85. */
  86. newLine(text, mod = 'normal') {
  87. this._add(this.current, text, mod);
  88. this.current++;
  89. }
  90. /**
  91. * Determine and set the heights of all the lines currently contained in this instance
  92. *
  93. * Note that width has already been set.
  94. *
  95. * @private
  96. */
  97. determineLineHeights() {
  98. for (let k = 0; k < this.lines.length; k++) {
  99. let line = this.lines[k];
  100. // Looking for max height of blocks in line
  101. let height = 0;
  102. if (line.blocks !== undefined) { // Can happen if text contains e.g. '\n '
  103. for (let l = 0; l < line.blocks.length; l++) {
  104. let block = line.blocks[l];
  105. if (height < block.height) {
  106. height = block.height;
  107. }
  108. }
  109. }
  110. line.height = height;
  111. }
  112. }
  113. /**
  114. * Determine the full size of the label text, as determined by current lines and blocks
  115. *
  116. * @private
  117. */
  118. determineLabelSize() {
  119. let width = 0;
  120. let height = 0;
  121. for (let k = 0; k < this.lines.length; k++) {
  122. let line = this.lines[k];
  123. if (line.width > width) {
  124. width = line.width;
  125. }
  126. height += line.height;
  127. }
  128. this.width = width;
  129. this.height = height;
  130. }
  131. /**
  132. * Remove all empty blocks and empty lines we don't need
  133. *
  134. * This must be done after the width/height determination,
  135. * so that these are set properly for processing here.
  136. *
  137. * @returns {Array<Line>} Lines with empty blocks (and some empty lines) removed
  138. * @private
  139. */
  140. removeEmptyBlocks() {
  141. let tmpLines = [];
  142. for (let k = 0; k < this.lines.length; k++) {
  143. let line = this.lines[k];
  144. // Note: an empty line in between text has width zero but is still relevant to layout.
  145. // So we can't use width for testing empty line here
  146. if (line.blocks.length === 0) continue;
  147. // Discard final empty line always
  148. if(k === this.lines.length - 1) {
  149. if (line.width === 0) continue;
  150. }
  151. let tmpLine = {};
  152. Object.assign(tmpLine, line);
  153. tmpLine.blocks = [];
  154. let firstEmptyBlock;
  155. let tmpBlocks = []
  156. for (let l = 0; l < line.blocks.length; l++) {
  157. let block = line.blocks[l];
  158. if (block.width !== 0) {
  159. tmpBlocks.push(block);
  160. } else {
  161. if (firstEmptyBlock === undefined) {
  162. firstEmptyBlock = block;
  163. }
  164. }
  165. }
  166. // Ensure that there is *some* text present
  167. if (tmpBlocks.length === 0 && firstEmptyBlock !== undefined) {
  168. tmpBlocks.push(firstEmptyBlock);
  169. }
  170. tmpLine.blocks = tmpBlocks;
  171. tmpLines.push(tmpLine);
  172. }
  173. return tmpLines;
  174. }
  175. /**
  176. * Set the sizes for all lines and the whole thing.
  177. *
  178. * @returns {{width: (number|*), height: (number|*), lines: Array}}
  179. */
  180. finalize() {
  181. //console.log(JSON.stringify(this.lines, null, 2));
  182. this.determineLineHeights();
  183. this.determineLabelSize();
  184. let tmpLines = this.removeEmptyBlocks();
  185. // Return a simple hash object for further processing.
  186. return {
  187. width : this.width,
  188. height: this.height,
  189. lines : tmpLines
  190. }
  191. }
  192. }
  193. export default LabelAccumulator;