vendor: 4,713 bytes, lines 1-157
1/* * 2 * 3 * (c) 2010-2024 Torstein Honsi 4 * 5 * License: www.highcharts.com/license 6 * 7 * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! 8 * 9 * */ 10import SVGElement from './SVGElement.js'; 11import U from '../../Utilities.js'; 12const { defined, extend, getAlignFactor, isNumber, merge, pick, removeEvent } = U; 13/* * 14 * 15 * Class 16 * 17 * */ 18/** 19 * SVG label to render text. 20 * @private 21 * @class 22 * @name Highcharts.SVGLabel 23 * @augments Highcharts.SVGElement 24 */ 25class SVGLabel extends SVGElement { 26 /* * 27 * 28 * Constructor 29 * 30 * */ 31 constructor(renderer, str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) { 32 super(renderer, 'g'); 33 this.paddingLeftSetter = this.paddingSetter; 34 this.paddingRightSetter = this.paddingSetter; 35 this.doUpdate = false; 36 this.textStr = str; 37 this.x = x; 38 this.y = y; 39 this.anchorX = anchorX; 40 this.anchorY = anchorY; 41 this.baseline = baseline; 42 this.className = className; 43 this.addClass(className === 'button' ? 44 'highcharts-no-tooltip' : 45 'highcharts-label'); 46 if (className) { 47 this.addClass('highcharts-' + className); 48 } 49 // Create the text element. An undefined text content prevents redundant 50 // box calculation (#16121) 51 this.text = renderer.text(void 0, 0, 0, useHTML).attr({ zIndex: 1 }); 52 // Validate the shape argument 53 let hasBGImage; 54 if (typeof shape === 'string') { 55 hasBGImage = /^url\((.*?)\)$/.test(shape); 56 if (hasBGImage || this.renderer.symbols[shape]) { 57 this.symbolKey = shape; 58 } 59 } 60 this.bBox = SVGLabel.emptyBBox; 61 this.padding = 3; 62 this.baselineOffset = 0; 63 this.needsBox = renderer.styledMode || hasBGImage; 64 this.deferredAttr = {}; 65 this.alignFactor = 0; 66 } 67 /* * 68 * 69 * Functions 70 * 71 * */ 72 alignSetter(value) { 73 const alignFactor = getAlignFactor(value); 74 this.textAlign = value; 75 if (alignFactor !== this.alignFactor) { 76 this.alignFactor = alignFactor; 77 // Bounding box exists, means we're dynamically changing 78 if (this.bBox && isNumber(this.xSetting)) { 79 this.attr({ x: this.xSetting }); // #5134 80 } 81 } 82 } 83 anchorXSetter(value, key) { 84 this.anchorX = value; 85 this.boxAttr(key, Math.round(value) - this.getCrispAdjust() - this.xSetting); 86 } 87 anchorYSetter(value, key) { 88 this.anchorY = value; 89 this.boxAttr(key, value - this.ySetting); 90 } 91 /* 92 * Set a box attribute, or defer it if the box is not yet created 93 */ 94 boxAttr(key, value) { 95 if (this.box) { 96 this.box.attr(key, value); 97 } 98 else { 99 this.deferredAttr[key] = value; 100 } 101 } 102 /* 103 * Pick up some properties and apply them to the text instead of the 104 * wrapper. 105 */ 106 css(styles) { 107 if (styles) { 108 const textStyles = {}; 109 // Create a copy to avoid altering the original object 110 // (#537) 111 styles = merge(styles); 112 SVGLabel.textProps.forEach((prop) => { 113 if (typeof styles[prop] !== 'undefined') { 114 textStyles[prop] = styles[prop]; 115 delete styles[prop]; 116 } 117 }); 118 this.text.css(textStyles); 119 // Update existing text, box (#9400, #12163, #18212) 120 if ('fontSize' in textStyles || 'fontWeight' in textStyles) { 121 this.updateTextPadding(); 122 } 123 else if ('width' in textStyles || 'textOverflow' in textStyles) { 124 this.updateBoxSize(); 125 } 126 } 127 return SVGElement.prototype.css.call(this, styles); 128 } 129 /* 130 * Destroy and release memory. 131 */ 132 destroy() { 133 // Added by button implementation 134 removeEvent(this.element, 'mouseenter'); 135 removeEvent(this.element, 'mouseleave'); 136 if (this.text) { 137 this.text.destroy(); 138 } 139 if (this.box) { 140 this.box = this.box.destroy(); 141 } 142 // Call base implementation to destroy the rest 143 SVGElement.prototype.destroy.call(this); 144 return void 0; 145 } 146 fillSetter(value, key) { 147 if (value) { 148 this.needsBox = true; 149 } 150 // For animation getter (#6776) 151 this.fill = value; 152 this.boxAttr(key, value); 153 } 154 /* 155 * Return the bounding box of the box, not the group. 156 */ 157 getBBox(reload, rot) {
vendor: 4,296 bytes, lines 158-278
158 // If we have a text string and the DOM bBox was 0, it typically means 159 // that the label was first rendered hidden, so we need to update the 160 // bBox (#15246) 161 if (this.textStr && this.bBox.width === 0 && this.bBox.height === 0) { 162 this.updateBoxSize(); 163 } 164 const { padding, height = 0, translateX = 0, translateY = 0, width = 0 } = this, paddingLeft = pick(this.paddingLeft, padding), rotation = rot ?? (this.rotation || 0); 165 let bBox = { 166 width, 167 height, 168 x: translateX + this.bBox.x - paddingLeft, 169 y: translateY + this.bBox.y - padding + this.baselineOffset 170 }; 171 if (rotation) { 172 bBox = this.getRotatedBox(bBox, rotation); 173 } 174 return bBox; 175 } 176 getCrispAdjust() { 177 return (this.renderer.styledMode && this.box ? 178 this.box.strokeWidth() : 179 (this['stroke-width'] ? 180 parseInt(this['stroke-width'], 10) : 181 0)) % 2 / 2; 182 } 183 heightSetter(value) { 184 this.heightSetting = value; 185 this.doUpdate = true; 186 } 187 /** 188 * This method is executed in the end of `attr()`, after setting all 189 * attributes in the hash. In can be used to efficiently consolidate 190 * multiple attributes in one SVG property -- e.g., translate, rotate and 191 * scale are merged in one "transform" attribute in the SVG node. 192 * Also updating height or width should trigger update of the box size. 193 * 194 * @private 195 * @function Highcharts.SVGLabel#afterSetters 196 */ 197 afterSetters() { 198 super.afterSetters(); 199 if (this.doUpdate) { 200 this.updateBoxSize(); 201 this.doUpdate = false; 202 } 203 } 204 /* 205 * After the text element is added, get the desired size of the border 206 * box and add it before the text in the DOM. 207 */ 208 onAdd() { 209 this.text.add(this); 210 this.attr({ 211 // Alignment is available now (#3295, 0 not rendered if given 212 // as a value) 213 text: pick(this.textStr, ''), 214 x: this.x || 0, 215 y: this.y || 0 216 }); 217 if (this.box && defined(this.anchorX)) { 218 this.attr({ 219 anchorX: this.anchorX, 220 anchorY: this.anchorY 221 }); 222 } 223 } 224 paddingSetter(value, key) { 225 if (!isNumber(value)) { 226 this[key] = void 0; 227 } 228 else if (value !== this[key]) { 229 this[key] = value; 230 this.updateTextPadding(); 231 } 232 } 233 rSetter(value, key) { 234 this.boxAttr(key, value); 235 } 236 strokeSetter(value, key) { 237 // For animation getter (#6776) 238 this.stroke = value; 239 this.boxAttr(key, value); 240 } 241 'stroke-widthSetter'(value, key) { 242 if (value) { 243 this.needsBox = true; 244 } 245 this['stroke-width'] = value; 246 this.boxAttr(key, value); 247 } 248 'text-alignSetter'(value) { 249 // The text-align variety is for the pre-animation getter. The code 250 // should be unified to either textAlign or text-align. 251 this.textAlign = this['text-align'] = value; 252 this.updateTextPadding(); 253 } 254 textSetter(text) { 255 if (typeof text !== 'undefined') { 256 // Must use .attr to ensure transforms are done (#10009) 257 this.text.attr({ text }); 258 } 259 this.updateTextPadding(); 260 this.reAlign(); 261 } 262 /* 263 * This function runs after the label is added to the DOM (when the bounding 264 * box is available), and after the text of the label is updated to detect 265 * the new bounding box and reflect it in the border box. 266 */ 267 updateBoxSize() { 268 const text = this.text, attribs = {}, padding = this.padding, 269 // #12165 error when width is null (auto) 270 // #12163 when fontweight: bold, recalculate bBox without cache 271 // #3295 && 3514 box failure when string equals 0 272 bBox = this.bBox = (((!isNumber(this.widthSetting) || 273 !isNumber(this.heightSetting) || 274 this.textAlign) && defined(text.textStr)) ? 275 text.getBBox(void 0, 0) : 276 SVGLabel.emptyBBox); 277 let crispAdjust; 278 this.width = this.getPaddedWidth();
vendor: 4,378 bytes, lines 279-400
279 this.height = (this.heightSetting || bBox.height || 0) + 2 * padding; 280 const metrics = this.renderer.fontMetrics(text); 281 // Update the label-scoped y offset. Math.min because of inline 282 // style (#9400) 283 this.baselineOffset = padding + Math.min( 284 // When applicable, use the font size of the first line (#15707) 285 (this.text.firstLineMetrics || metrics).b, 286 // When the height is 0, there is no bBox, so go with the font 287 // metrics. Highmaps CSS demos. 288 bBox.height || Infinity); 289 // #15491: Vertical centering 290 if (this.heightSetting) { 291 this.baselineOffset += (this.heightSetting - metrics.h) / 2; 292 } 293 if (this.needsBox && !text.textPath) { 294 // Create the border box if it is not already present 295 if (!this.box) { 296 // Symbol definition exists (#5324) 297 const box = this.box = this.symbolKey ? 298 this.renderer.symbol(this.symbolKey) : 299 this.renderer.rect(); 300 box.addClass(// Don't use label className for buttons 301 (this.className === 'button' ? 302 '' : 'highcharts-label-box') + 303 (this.className ? 304 ' highcharts-' + this.className + '-box' : '')); 305 box.add(this); 306 } 307 crispAdjust = this.getCrispAdjust(); 308 attribs.x = crispAdjust; 309 attribs.y = ((this.baseline ? -this.baselineOffset : 0) + crispAdjust); 310 // Apply the box attributes 311 attribs.width = Math.round(this.width); 312 attribs.height = Math.round(this.height); 313 this.box.attr(extend(attribs, this.deferredAttr)); 314 this.deferredAttr = {}; 315 } 316 } 317 /* 318 * This function runs after setting text or padding, but only if padding 319 * is changed. 320 */ 321 updateTextPadding() { 322 const text = this.text, textAlign = text.styles.textAlign || this.textAlign; 323 if (!text.textPath) { 324 this.updateBoxSize(); 325 // Determine y based on the baseline 326 const textY = this.baseline ? 0 : this.baselineOffset, textX = (this.paddingLeft ?? this.padding) + 327 // Compensate for alignment 328 getAlignFactor(textAlign) * (this.widthSetting ?? this.bBox.width); 329 // Update if anything changed 330 if (textX !== text.x || textY !== text.y) { 331 text.attr({ 332 align: textAlign, 333 x: textX 334 }); 335 if (typeof textY !== 'undefined') { 336 text.attr('y', textY); 337 } 338 } 339 // Record current values 340 text.x = textX; 341 text.y = textY; 342 } 343 } 344 widthSetter(value) { 345 // `width:auto` => null 346 this.widthSetting = isNumber(value) ? value : void 0; 347 this.doUpdate = true; 348 } 349 getPaddedWidth() { 350 const padding = this.padding; 351 const paddingLeft = pick(this.paddingLeft, padding); 352 const paddingRight = pick(this.paddingRight, padding); 353 return ((this.widthSetting || this.bBox.width || 0) + 354 paddingLeft + 355 paddingRight); 356 } 357 xSetter(value) { 358 this.x = value; // For animation getter 359 if (this.alignFactor) { 360 value -= this.alignFactor * this.getPaddedWidth(); 361 // Force animation even when setting to the same value (#7898) 362 this['forceAnimate:x'] = true; 363 } 364 this.xSetting = Math.round(value); 365 this.attr('translateX', this.xSetting); 366 } 367 ySetter(value) { 368 this.ySetting = this.y = Math.round(value); 369 this.attr('translateY', this.ySetting); 370 } 371} 372/* * 373 * 374 * Static Properties 375 * 376 * */ 377SVGLabel.emptyBBox = { 378 width: 0, 379 height: 0, 380 x: 0, 381 y: 0 382}; 383/** 384 * For labels, these CSS properties are applied to the `text` node directly. 385 * 386 * @private 387 * @name Highcharts.SVGLabel#textProps 388 * @type {Array<string>} 389 */ 390SVGLabel.textProps = [ 391 'color', 'direction', 'fontFamily', 'fontSize', 'fontStyle', 392 'fontWeight', 'lineClamp', 'lineHeight', 'textAlign', 'textDecoration', 393 'textOutline', 'textOverflow', 'whiteSpace', 'width' 394]; 395/* * 396 * 397 * Default Export 398 * 399 * */ 400export default SVGLabel;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.