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

/**
* Callback to determine text dimensions, using the parent label settings.
* @callback MeasureText
* @param {text} text
* @param {text} mod
* @return {Object} { width, values} width in pixels and font attributes
*/
/**
* Helper class for Label which collects results of splitting labels into lines and blocks.
*
* @private
*/
class LabelAccumulator {
/**
* @param {MeasureText} measureText
*/
constructor(measureText) {
this.measureText = measureText;
this.current = 0;
this.width = 0;
this.height = 0;
this.lines = [];
}
/**
* Append given text to the given line.
*
* @param {number} l index of line to add to
* @param {string} text string to append to line
* @param {'bold'|'ital'|'boldital'|'mono'|'normal'} [mod='normal']
* @private
*/
_add(l, text, mod = 'normal') {
if (this.lines[l] === undefined) {
this.lines[l] = {
width : 0,
height: 0,
blocks: []
};
}
// We still need to set a block for undefined and empty texts, hence return at this point
// This is necessary because we don't know at this point if we're at the
// start of an empty line or not.
// To compensate, empty blocks are removed in `finalize()`.
//
// Empty strings should still have a height
let tmpText = text;
if (text === undefined || text === "") tmpText = " ";
// Determine width and get the font properties
let result = this.measureText(tmpText, mod);
let block = Object.assign({}, result.values);
block.text = text;
block.width = result.width;
block.mod = mod;
if (text === undefined || text === "") {
block.width = 0;
}
this.lines[l].blocks.push(block);
// Update the line width. We need this for determining if a string goes over max width
this.lines[l].width += block.width;
}
/**
* Returns the width in pixels of the current line.
*
* @returns {number}
*/
curWidth() {
let line = this.lines[this.current];
if (line === undefined) return 0;
return line.width;
}
/**
* Add text in block to current line
*
* @param {string} text
* @param {'bold'|'ital'|'boldital'|'mono'|'normal'} [mod='normal']
*/
append(text, mod = 'normal') {
this._add(this.current, text, mod);
}
/**
* Add text in block to current line and start a new line
*
* @param {string} text
* @param {'bold'|'ital'|'boldital'|'mono'|'normal'} [mod='normal']
*/
newLine(text, mod = 'normal') {
this._add(this.current, text, mod);
this.current++;
}
/**
* Determine and set the heights of all the lines currently contained in this instance
*
* Note that width has already been set.
*
* @private
*/
determineLineHeights() {
for (let k = 0; k < this.lines.length; k++) {
let line = this.lines[k];
// Looking for max height of blocks in line
let height = 0;
if (line.blocks !== undefined) { // Can happen if text contains e.g. '\n '
for (let l = 0; l < line.blocks.length; l++) {
let block = line.blocks[l];
if (height < block.height) {
height = block.height;
}
}
}
line.height = height;
}
}
/**
* Determine the full size of the label text, as determined by current lines and blocks
*
* @private
*/
determineLabelSize() {
let width = 0;
let height = 0;
for (let k = 0; k < this.lines.length; k++) {
let line = this.lines[k];
if (line.width > width) {
width = line.width;
}
height += line.height;
}
this.width = width;
this.height = height;
}
/**
* Remove all empty blocks and empty lines we don't need
*
* This must be done after the width/height determination,
* so that these are set properly for processing here.
*
* @returns {Array<Line>} Lines with empty blocks (and some empty lines) removed
* @private
*/
removeEmptyBlocks() {
let tmpLines = [];
for (let k = 0; k < this.lines.length; k++) {
let line = this.lines[k];
// Note: an empty line in between text has width zero but is still relevant to layout.
// So we can't use width for testing empty line here
if (line.blocks.length === 0) continue;
// Discard final empty line always
if(k === this.lines.length - 1) {
if (line.width === 0) continue;
}
let tmpLine = {};
Object.assign(tmpLine, line);
tmpLine.blocks = [];
let firstEmptyBlock;
let tmpBlocks = []
for (let l = 0; l < line.blocks.length; l++) {
let block = line.blocks[l];
if (block.width !== 0) {
tmpBlocks.push(block);
} else {
if (firstEmptyBlock === undefined) {
firstEmptyBlock = block;
}
}
}
// Ensure that there is *some* text present
if (tmpBlocks.length === 0 && firstEmptyBlock !== undefined) {
tmpBlocks.push(firstEmptyBlock);
}
tmpLine.blocks = tmpBlocks;
tmpLines.push(tmpLine);
}
return tmpLines;
}
/**
* Set the sizes for all lines and the whole thing.
*
* @returns {{width: (number|*), height: (number|*), lines: Array}}
*/
finalize() {
//console.log(JSON.stringify(this.lines, null, 2));
this.determineLineHeights();
this.determineLabelSize();
let tmpLines = this.removeEmptyBlocks();
// Return a simple hash object for further processing.
return {
width : this.width,
height: this.height,
lines : tmpLines
}
}
}
export default LabelAccumulator;