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 * */ 10'use strict'; 11import A from '../../Animation/AnimationUtilities.js'; 12const { animate, animObject, stop } = A; 13import Color from '../../Color/Color.js'; 14import H from '../../Globals.js'; 15const { deg2rad, doc, svg, SVG_NS, win, isFirefox } = H; 16import U from '../../Utilities.js'; 17const { addEvent, attr, createElement, crisp, css, defined, erase, extend, fireEvent, getAlignFactor, isArray, isFunction, isNumber, isObject, isString, merge, objectEach, pick, pInt, pushUnique, replaceNested, syncTimeout, uniqueKey } = U; 18/* * 19 * 20 * Class 21 * 22 * */ 23/* eslint-disable no-invalid-this, valid-jsdoc */ 24/** 25 * The SVGElement prototype is a JavaScript wrapper for SVG elements used in the 26 * rendering layer of Highcharts. Combined with the 27 * {@link Highcharts.SVGRenderer} 28 * object, these prototypes allow freeform annotation in the charts or even in 29 * HTML pages without instanciating a chart. The SVGElement can also wrap HTML 30 * labels, when `text` or `label` elements are created with the `useHTML` 31 * parameter. 32 * 33 * The SVGElement instances are created through factory functions on the 34 * {@link Highcharts.SVGRenderer} 35 * object, like 36 * {@link Highcharts.SVGRenderer#rect|rect}, 37 * {@link Highcharts.SVGRenderer#path|path}, 38 * {@link Highcharts.SVGRenderer#text|text}, 39 * {@link Highcharts.SVGRenderer#label|label}, 40 * {@link Highcharts.SVGRenderer#g|g} 41 * and more. 42 * 43 * See [How to use the SVG Renderer]( 44 * https://www.highcharts.com/docs/advanced-chart-features/renderer) for a 45 * comprehensive tutorial on how to draw SVG elements on a chart. 46 * 47 * @class 48 * @name Highcharts.SVGElement 49 */ 50class SVGElement { 51 // @todo public zIndex?: number; 52 /* * 53 * 54 * Functions 55 * 56 * */ 57 /** 58 * Get the current value of an attribute or pseudo attribute, 59 * used mainly for animation. Called internally from 60 * the {@link Highcharts.SVGRenderer#attr} function. 61 * 62 * @private 63 * @function Highcharts.SVGElement#_defaultGetter 64 * 65 * @param {string} key 66 * Property key. 67 * 68 * @return {number|string} 69 * Property value. 70 */ 71 _defaultGetter(key) { 72 let ret = pick(this[key + 'Value'], // Align getter 73 this[key], this.element ? this.element.getAttribute(key) : null, 0); 74 if (/^-?[\d\.]+$/.test(ret)) { // Is numerical 75 ret = parseFloat(ret); 76 } 77 return ret; 78 } 79 /** 80 * @private 81 * @function Highcharts.SVGElement#_defaultSetter 82 * 83 * @param {string} value 84 * 85 * @param {string} key 86 * 87 * @param {Highcharts.SVGDOMElement} element 88 * 89 */ 90 _defaultSetter(value, key, element) { 91 element.setAttribute(key, value); 92 } 93 /** 94 * Add the element to the DOM. All elements must be added this way. 95 * 96 * @sample highcharts/members/renderer-g 97 * Elements added to a group 98 * 99 * @function Highcharts.SVGElement#add 100 * 101 * @param {Highcharts.SVGElement} [parent] 102 * The parent item to add it to. If undefined, the element is added 103 * to the {@link Highcharts.SVGRenderer.box}. 104 * 105 * @return {Highcharts.SVGElement} 106 * Returns the SVGElement for chaining. 107 */ 108 add(parent) { 109 const renderer = this.renderer, element = this.element; 110 let inserted; 111 if (parent) { 112 this.parentGroup = parent; 113 } 114 // Build formatted text 115 if (typeof this.textStr !== 'undefined' && 116 this.element.nodeName === 'text' // Not for SVGLabel instances 117 ) { 118 renderer.buildText(this); 119 } 120 // Mark as added 121 this.added = true; 122 // If we're adding to renderer root, or other elements in the group 123 // have a z index, we need to handle it 124 if (!parent || parent.handleZ || this.zIndex) { 125 inserted = this.zIndexSetter(); 126 } 127 // If zIndex is not handled, append at the end 128 if (!inserted) { 129 (parent ? 130 parent.element : 131 renderer.box).appendChild(element); 132 } 133 // Fire an event for internal hooks 134 if (this.onAdd) { 135 this.onAdd(); 136 } 137 return this; 138 } 139 /** 140 * Add a class name to an element. 141 * 142 * @function Highcharts.SVGElement#addClass 143 * 144 * @param {string} className
145 * The new class name to add. 146 * 147 * @param {boolean} [replace=false] 148 * When true, the existing class name(s) will be overwritten with the new 149 * one. When false, the new one is added. 150 * 151 * @return {Highcharts.SVGElement} 152 * Return the SVG element for chainability. 153 */ 154 addClass(className, replace) { 155 const currentClassName = replace ? '' : (this.attr('class') || ''); 156 // Trim the string and remove duplicates 157 className = (className || '') 158 .split(/ /g) 159 .reduce(function (newClassName, name) { 160 if (currentClassName.indexOf(name) === -1) { 161 newClassName.push(name); 162 } 163 return newClassName; 164 }, (currentClassName ? 165 [currentClassName] : 166 [])) 167 .join(' '); 168 if (className !== currentClassName) { 169 this.attr('class', className); 170 } 171 return this; 172 } 173 /** 174 * This method is executed in the end of `attr()`, after setting all 175 * attributes in the hash. In can be used to efficiently consolidate 176 * multiple attributes in one SVG property -- e.g., translate, rotate and 177 * scale are merged in one "transform" attribute in the SVG node. 178 * 179 * @private 180 * @function Highcharts.SVGElement#afterSetters 181 */ 182 afterSetters() { 183 // Update transform. Do this outside the loop to prevent redundant 184 // updating for batch setting of attributes. 185 if (this.doTransform) { 186 this.updateTransform(); 187 this.doTransform = false; 188 } 189 } 190 /** 191 * Align the element relative to the chart or another box. 192 * 193 * @function Highcharts.SVGElement#align 194 * 195 * @param {Highcharts.AlignObject} [alignOptions] 196 * The alignment options. The function can be called without this 197 * parameter in order to re-align an element after the box has been 198 * updated. 199 * 200 * @param {boolean} [alignByTranslate] 201 * Align element by translation. 202 * 203 * @param {string|Highcharts.BBoxObject} [alignTo] 204 * The box to align to, needs a width and height. When the box is a 205 * string, it refers to an object in the Renderer. For example, when 206 * box is `spacingBox`, it refers to `Renderer.spacingBox` which 207 * holds `width`, `height`, `x` and `y` properties. 208 * 209 * @param {boolean} [redraw] 210 * Decide if SVGElement should be redrawn with new alignment or 211 * just change its attributes. 212 * 213 * @return {Highcharts.SVGElement} Returns the SVGElement for chaining. 214 */ 215 align(alignOptions, alignByTranslate, alignTo, redraw = true) { 216 const renderer = this.renderer, alignedObjects = renderer.alignedObjects, initialAlignment = Boolean(alignOptions); 217 // First call on instanciate 218 if (alignOptions) { 219 this.alignOptions = alignOptions; 220 this.alignByTranslate = alignByTranslate; 221 this.alignTo = alignTo; 222 // When called on resize, no arguments are supplied 223 } 224 else { 225 alignOptions = this.alignOptions || {}; 226 alignByTranslate = this.alignByTranslate; 227 alignTo = this.alignTo; 228 } 229 const alignToKey = !alignTo || isString(alignTo) ? 230 alignTo || 'renderer' : 231 void 0; 232 // When aligned to a key, automatically re-align on redraws 233 if (alignToKey) { 234 // Prevent duplicates, like legendGroup after resize 235 if (initialAlignment) { 236 pushUnique(alignedObjects, this); 237 } 238 alignTo = void 0; // Do not use the box 239 } 240 const alignToBox = pick(alignTo, renderer[alignToKey], renderer), 241 // Default: left align 242 x = (alignToBox.x || 0) + (alignOptions.x || 0) + 243 ((alignToBox.width || 0) - (alignOptions.width || 0)) * 244 getAlignFactor(alignOptions.align), 245 // Default: top align 246 y = (alignToBox.y || 0) + (alignOptions.y || 0) +
vendor: 9,351 bytes, lines 247-485
247 ((alignToBox.height || 0) - (alignOptions.height || 0)) * 248 getAlignFactor(alignOptions.verticalAlign), attribs = { 249 'text-align': alignOptions?.align 250 }; 251 attribs[alignByTranslate ? 'translateX' : 'x'] = Math.round(x); 252 attribs[alignByTranslate ? 'translateY' : 'y'] = Math.round(y); 253 // Animate only if already placed 254 if (redraw) { 255 this[this.placed ? 'animate' : 'attr'](attribs); 256 this.placed = true; 257 } 258 this.alignAttr = attribs; 259 return this; 260 } 261 /** 262 * @private 263 * @function Highcharts.SVGElement#alignSetter 264 * @param {"left"|"center"|"right"} value 265 */ 266 alignSetter(value) { 267 const convert = { 268 left: 'start', 269 center: 'middle', 270 right: 'end' 271 }; 272 if (convert[value]) { 273 this.alignValue = value; 274 this.element.setAttribute('text-anchor', convert[value]); 275 } 276 } 277 /** 278 * Animate to given attributes or CSS properties. 279 * 280 * @sample highcharts/members/element-on/ 281 * Setting some attributes by animation 282 * 283 * @function Highcharts.SVGElement#animate 284 * 285 * @param {Highcharts.SVGAttributes} params 286 * SVG attributes or CSS to animate. 287 * 288 * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [options] 289 * Animation options. 290 * 291 * @param {Function} [complete] 292 * Function to perform at the end of animation. 293 * 294 * @return {Highcharts.SVGElement} 295 * Returns the SVGElement for chaining. 296 */ 297 animate(params, options, complete) { 298 const animOptions = animObject(pick(options, this.renderer.globalAnimation, true)), deferTime = animOptions.defer; 299 // When the page is hidden save resources in the background by not 300 // running animation at all (#9749). 301 if (doc.hidden) { 302 animOptions.duration = 0; 303 } 304 if (animOptions.duration !== 0) { 305 // Allows using a callback with the global animation without 306 // overwriting it 307 if (complete) { 308 animOptions.complete = complete; 309 } 310 // If defer option is defined delay the animation #12901 311 syncTimeout(() => { 312 if (this.element) { 313 animate(this, params, animOptions); 314 } 315 }, deferTime); 316 } 317 else { 318 this.attr(params, void 0, complete || animOptions.complete); 319 // Call the end step synchronously 320 objectEach(params, function (val, prop) { 321 if (animOptions.step) { 322 animOptions.step.call(this, val, { prop: prop, pos: 1, elem: this }); 323 } 324 }, this); 325 } 326 return this; 327 } 328 /** 329 * Apply a text outline through a custom CSS property, by copying the text 330 * element and apply stroke to the copy. Used internally. Contrast checks at 331 * [example](https://jsfiddle.net/highcharts/43soe9m1/2/). 332 * 333 * @example 334 * // Specific color 335 * text.css({ 336 * textOutline: '1px black' 337 * }); 338 * // Automatic contrast 339 * text.css({ 340 * color: '#000000', // black text 341 * textOutline: '1px contrast' // => white outline 342 * }); 343 * 344 * @private 345 * @function Highcharts.SVGElement#applyTextOutline 346 * 347 * @param {string} textOutline 348 * A custom CSS `text-outline` setting, defined by `width color`. 349 */ 350 applyTextOutline(textOutline) { 351 const elem = this.element, hasContrast = textOutline.indexOf('contrast') !== -1, styles = {}; 352 // When the text shadow is set to contrast, use dark stroke for light 353 // text and vice versa. 354 if (hasContrast) { 355 styles.textOutline = textOutline = textOutline.replace(/contrast/g, this.renderer.getContrast(elem.style.fill)); 356 } 357 // Extract the stroke width and color 358 const parts = textOutline.split(' '); 359 const color = parts[parts.length - 1]; 360 let strokeWidth = parts[0]; 361 if (strokeWidth && strokeWidth !== 'none' && H.svg) { 362 this.fakeTS = true; // Fake text shadow 363 // Since the stroke is applied on center of the actual outline, we 364 // need to double it to get the correct stroke-width outside the 365 // glyphs. 366 strokeWidth = strokeWidth.replace(/(^[\d\.]+)(.*?)$/g, function (match, digit, unit) { 367 return (2 * Number(digit)) + unit; 368 }); 369 // Remove shadows from previous runs. 370 this.removeTextOutline(); 371 const outline = doc.createElementNS(SVG_NS, 'tspan'); 372 attr(outline, { 373 'class': 'highcharts-text-outline', 374 fill: color, 375 stroke: color, 376 'stroke-width': strokeWidth, 377 'stroke-linejoin': 'round' 378 }); 379 // For each of the tspans and text nodes, create a copy in the 380 // outline. 381 const parentElem = elem.querySelector('textPath') || elem; 382 [].forEach.call(parentElem.childNodes, (childNode) => { 383 const clone = childNode.cloneNode(true); 384 if (clone.removeAttribute) { 385 ['fill', 'stroke', 'stroke-width', 'stroke'].forEach((prop) => clone 386 .removeAttribute(prop)); 387 } 388 outline.appendChild(clone); 389 }); 390 // Collect the sum of dy from all children, included nested ones 391 let totalHeight = 0; 392 [].forEach.call(parentElem.querySelectorAll('text tspan'), (element) => { 393 totalHeight += Number(element.getAttribute('dy')); 394 }); 395 // Insert an absolutely positioned break before the original text 396 // to keep it in place 397 const br = doc.createElementNS(SVG_NS, 'tspan'); 398 br.textContent = '\u200B'; 399 // Reset the position for the following text 400 attr(br, { 401 x: Number(elem.getAttribute('x')), 402 dy: -totalHeight 403 }); 404 // Insert the outline 405 outline.appendChild(br); 406 parentElem.insertBefore(outline, parentElem.firstChild); 407 } 408 } 409 /** 410 * @function Highcharts.SVGElement#attr 411 * @param {string} key 412 * @return {number|string} 413 */ /** 414 * Apply native and custom attributes to the SVG elements. 415 * 416 * In order to set the rotation center for rotation, set x and y to 0 and 417 * use `translateX` and `translateY` attributes to position the element 418 * instead. 419 * 420 * Attributes frequently used in Highcharts are `fill`, `stroke`, 421 * `stroke-width`. 422 * 423 * @sample highcharts/members/renderer-rect/ 424 * Setting some attributes 425 * 426 * @example 427 * // Set multiple attributes 428 * element.attr({ 429 * stroke: 'red', 430 * fill: 'blue', 431 * x: 10, 432 * y: 10 433 * }); 434 * 435 * // Set a single attribute 436 * element.attr('stroke', 'red'); 437 * 438 * // Get an attribute 439 * element.attr('stroke'); // => 'red' 440 * 441 * @function Highcharts.SVGElement#attr 442 * 443 * @param {string|Highcharts.SVGAttributes} [hash] 444 * The native and custom SVG attributes. 445 * 446 * @param {number|string|Highcharts.SVGPathArray} [val] 447 * If the type of the first argument is `string`, the second can be a 448 * value, which will serve as a single attribute setter. If the first 449 * argument is a string and the second is undefined, the function 450 * serves as a getter and the current value of the property is 451 * returned. 452 * 453 * @param {Function} [complete] 454 * A callback function to execute after setting the attributes. This 455 * makes the function compliant and interchangeable with the 456 * {@link SVGElement#animate} function. 457 * 458 * @param {boolean} [continueAnimation=true] 459 * Used internally when `.attr` is called as part of an animation 460 * step. Otherwise, calling `.attr` for an attribute will stop 461 * animation for that attribute. 462 * 463 * @return {Highcharts.SVGElement} 464 * If used as a setter, it returns the current 465 * {@link Highcharts.SVGElement} so the calls can be chained. If 466 * used as a getter, the current value of the attribute is returned. 467 */ 468 attr(hash, val, complete, continueAnimation) { 469 const { element } = this, symbolCustomAttribs = SVGElement.symbolCustomAttribs; 470 let key, hasSetSymbolSize, ret = this, skipAttr, setter; 471 // Single key-value pair 472 if (typeof hash === 'string' && typeof val !== 'undefined') { 473 key = hash; 474 hash = {}; 475 hash[key] = val; 476 } 477 // Used as a getter: first argument is a string, second is undefined 478 if (typeof hash === 'string') { 479 ret = (this[hash + 'Getter'] || 480 this._defaultGetter).call(this, hash, element); 481 // Setter 482 } 483 else { 484 objectEach(hash, function eachAttribute(val, key) { 485 skipAttr = false;
vendor: 14,071 bytes, lines 486-822
486 // Unless .attr is from the animator update, stop current 487 // running animation of this property 488 if (!continueAnimation) { 489 stop(this, key); 490 } 491 // Special handling of symbol attributes 492 if (this.symbolName && 493 symbolCustomAttribs.indexOf(key) !== -1) { 494 if (!hasSetSymbolSize) { 495 this.symbolAttr(hash); 496 hasSetSymbolSize = true; 497 } 498 skipAttr = true; 499 } 500 if (this.rotation && (key === 'x' || key === 'y')) { 501 this.doTransform = true; 502 } 503 if (!skipAttr) { 504 setter = (this[key + 'Setter'] || 505 this._defaultSetter); 506 setter.call(this, val, key, element); 507 } 508 }, this); 509 this.afterSetters(); 510 } 511 // In accordance with animate, run a complete callback 512 if (complete) { 513 complete.call(this); 514 } 515 return ret; 516 } 517 /** 518 * Apply a clipping shape to this element. 519 * 520 * @function Highcharts.SVGElement#clip 521 * 522 * @param {SVGElement} [clipElem] 523 * The clipping shape. If skipped, the current clip is removed. 524 * 525 * @return {Highcharts.SVGElement} 526 * Returns the SVG element to allow chaining. 527 */ 528 clip(clipElem) { 529 if (clipElem && !clipElem.clipPath) { 530 // Add a hyphen at the end to avoid confusion in testing indexes 531 // -1 and -10, -11 etc (#6550) 532 const id = uniqueKey() + '-', clipPath = this.renderer.createElement('clipPath') 533 .attr({ id }) 534 .add(this.renderer.defs); 535 extend(clipElem, { clipPath, id, count: 0 }); 536 clipElem.add(clipPath); 537 } 538 return this.attr('clip-path', clipElem ? 539 `url(${this.renderer.url}#${clipElem.id})` : 540 'none'); 541 } 542 /** 543 * Calculate the coordinates needed for drawing a rectangle crisply and 544 * return the calculated attributes. 545 * 546 * @function Highcharts.SVGElement#crisp 547 * 548 * @param {Highcharts.RectangleObject} rect 549 * Rectangle to crisp. 550 * 551 * @param {number} [strokeWidth] 552 * The stroke width to consider when computing crisp positioning. It can 553 * also be set directly on the rect parameter. 554 * 555 * @return {Highcharts.RectangleObject} 556 * The modified rectangle arguments. 557 */ 558 crisp(rect, strokeWidth) { 559 // Math.round because strokeWidth can sometimes have roundoff errors 560 strokeWidth = Math.round(strokeWidth || rect.strokeWidth || 0); 561 const x1 = rect.x || this.x || 0, y1 = rect.y || this.y || 0, x2 = (rect.width || this.width || 0) + x1, y2 = (rect.height || this.height || 0) + y1, 562 // Find all the rounded coordinates for corners 563 x = crisp(x1, strokeWidth), y = crisp(y1, strokeWidth), x2Crisp = crisp(x2, strokeWidth), y2Crisp = crisp(y2, strokeWidth); 564 extend(rect, { 565 x, 566 y, 567 width: x2Crisp - x, 568 height: y2Crisp - y 569 }); 570 if (defined(rect.strokeWidth)) { 571 rect.strokeWidth = strokeWidth; 572 } 573 return rect; 574 } 575 /** 576 * Build and apply an SVG gradient out of a common JavaScript configuration 577 * object. This function is called from the attribute setters. An event 578 * hook is added for supporting other complex color types. 579 * 580 * @private 581 * @function Highcharts.SVGElement#complexColor 582 * 583 * @param {Highcharts.GradientColorObject|Highcharts.PatternObject} colorOptions 584 * The gradient or pattern options structure. 585 * 586 * @param {string} prop 587 * The property to apply, can either be `fill` or `stroke`. 588 * 589 * @param {Highcharts.SVGDOMElement} elem 590 * SVG element to apply the gradient on. 591 */ 592 complexColor(colorOptions, prop, elem) { 593 const renderer = this.renderer; 594 let colorObject, gradName, gradAttr, radAttr, gradients, stops, stopColor, stopOpacity, radialReference, id, key = [], value; 595 fireEvent(this.renderer, 'complexColor', { 596 args: arguments 597 }, function () { 598 // Apply linear or radial gradients 599 if (colorOptions.radialGradient) { 600 gradName = 'radialGradient'; 601 } 602 else if (colorOptions.linearGradient) { 603 gradName = 'linearGradient'; 604 } 605 if (gradName) { 606 gradAttr = colorOptions[gradName]; 607 gradients = renderer.gradients; 608 stops = colorOptions.stops; 609 radialReference = elem.radialReference; 610 // Keep < 2.2 compatibility 611 if (isArray(gradAttr)) { 612 colorOptions[gradName] = gradAttr = { 613 x1: gradAttr[0], 614 y1: gradAttr[1], 615 x2: gradAttr[2], 616 y2: gradAttr[3], 617 gradientUnits: 'userSpaceOnUse' 618 }; 619 } 620 // Correct the radial gradient for the radial reference system 621 if (gradName === 'radialGradient' && 622 radialReference && 623 !defined(gradAttr.gradientUnits)) { 624 // Save the radial attributes for updating 625 radAttr = gradAttr; 626 gradAttr = merge(gradAttr, renderer.getRadialAttr(radialReference, radAttr), { gradientUnits: 'userSpaceOnUse' }); 627 } 628 // Build the unique key to detect whether we need to create a 629 // new element (#1282) 630 objectEach(gradAttr, function (value, n) { 631 if (n !== 'id') { 632 key.push(n, value); 633 } 634 }); 635 objectEach(stops, function (val) { 636 key.push(val); 637 }); 638 key = key.join(','); 639 // Check if a gradient object with the same config object is 640 // created within this renderer 641 if (gradients[key]) { 642 id = gradients[key].attr('id'); 643 } 644 else { 645 // Set the id and create the element 646 gradAttr.id = id = uniqueKey(); 647 const gradientObject = gradients[key] = 648 renderer.createElement(gradName) 649 .attr(gradAttr) 650 .add(renderer.defs); 651 gradientObject.radAttr = radAttr; 652 // The gradient needs to keep a list of stops to be able to 653 // destroy them 654 gradientObject.stops = []; 655 stops.forEach(function (stop) { 656 if (stop[1].indexOf('rgba') === 0) { 657 colorObject = Color.parse(stop[1]); 658 stopColor = colorObject.get('rgb'); 659 stopOpacity = colorObject.get('a'); 660 } 661 else { 662 stopColor = stop[1]; 663 stopOpacity = 1; 664 } 665 const stopObject = renderer.createElement('stop').attr({ 666 offset: stop[0], 667 'stop-color': stopColor, 668 'stop-opacity': stopOpacity 669 }).add(gradientObject); 670 // Add the stop element to the gradient 671 gradientObject.stops.push(stopObject); 672 }); 673 } 674 // Set the reference to the gradient object 675 value = 'url(' + renderer.url + '#' + id + ')'; 676 elem.setAttribute(prop, value); 677 elem.gradient = key; 678 // Allow the color to be concatenated into tooltips formatters 679 // etc. (#2995) 680 colorOptions.toString = function () { 681 return value; 682 }; 683 } 684 }); 685 } 686 /** 687 * Set styles for the element. In addition to CSS styles supported by 688 * native SVG and HTML elements, there are also some custom made for 689 * Highcharts, like `width`, `ellipsis` and `textOverflow` for SVG text 690 * elements. 691 * 692 * @sample highcharts/members/renderer-text-on-chart/ 693 * Styled text 694 * 695 * @function Highcharts.SVGElement#css 696 * 697 * @param {Highcharts.CSSObject} styles 698 * The new CSS styles. 699 * 700 * @return {Highcharts.SVGElement} 701 * Return the SVG element for chaining. 702 */ 703 css(styles) { 704 const oldStyles = this.styles, newStyles = {}, elem = this.element; 705 let textWidth, hasNew = !oldStyles; 706 // Filter out existing styles to increase performance (#2640) 707 if (oldStyles) { 708 objectEach(styles, function (value, n) { 709 if (oldStyles && oldStyles[n] !== value) { 710 newStyles[n] = value; 711 hasNew = true; 712 } 713 }); 714 } 715 if (hasNew) { 716 // Merge the new styles with the old ones 717 if (oldStyles) { 718 styles = extend(oldStyles, newStyles); 719 } 720 // Get the text width from style 721 // Previously set, unset it (#8234) 722 if (styles.width === null || styles.width === 'auto') { 723 delete this.textWidth; 724 // Apply new 725 } 726 else if (elem.nodeName.toLowerCase() === 'text' && 727 styles.width) { 728 textWidth = this.textWidth = pInt(styles.width); 729 } 730 // Store object 731 extend(this.styles, styles); 732 if (textWidth && (!svg && this.renderer.forExport)) { 733 delete styles.width; 734 } 735 const fontSize = isFirefox && styles.fontSize || null; 736 // Necessary in firefox to be able to set font-size, #22124 737 if (fontSize && (isNumber(fontSize) || 738 /^\d+$/.test(fontSize))) { 739 styles.fontSize += 'px'; 740 } 741 const stylesToApply = merge(styles); 742 if (elem.namespaceURI === this.SVG_NS) { 743 // These CSS properties are interpreted internally by the SVG 744 // renderer, but are not supported by SVG and should not be 745 // added to the DOM. In styled mode, no CSS should find its way 746 // to the DOM whatsoever (#6173, #6474). 747 ['textOutline', 'textOverflow', 'whiteSpace', 'width'].forEach((key) => (stylesToApply && 748 delete stylesToApply[key])); 749 // SVG requires fill for text 750 if (stylesToApply.color) { 751 stylesToApply.fill = stylesToApply.color; 752 } 753 } 754 css(elem, stylesToApply); 755 } 756 if (this.added) { 757 // Rebuild text after added. Cache mechanisms in the buildText will 758 // prevent building if there are no significant changes. 759 if (this.element.nodeName === 'text') { 760 this.renderer.buildText(this); 761 } 762 // Apply text outline after added 763 if (styles.textOutline) { 764 this.applyTextOutline(styles.textOutline); 765 } 766 } 767 return this; 768 } 769 /** 770 * @private 771 * @function Highcharts.SVGElement#dashstyleSetter 772 * @param {string} value 773 */ 774 dashstyleSetter(value) { 775 let i, strokeWidth = this['stroke-width']; 776 // If "inherit", like maps in IE, assume 1 (#4981). With HC5 and the new 777 // strokeWidth function, we should be able to use that instead. 778 if (strokeWidth === 'inherit') { 779 strokeWidth = 1; 780 } 781 value = value && value.toLowerCase(); 782 if (value) { 783 const v = value 784 .replace('shortdashdotdot', '3,1,1,1,1,1,') 785 .replace('shortdashdot', '3,1,1,1') 786 .replace('shortdot', '1,1,') 787 .replace('shortdash', '3,1,') 788 .replace('longdash', '8,3,') 789 .replace(/dot/g, '1,3,') 790 .replace('dash', '4,3,') 791 .replace(/,$/, '') 792 .split(','); // Ending comma 793 i = v.length; 794 while (i--) { 795 v[i] = '' + (pInt(v[i]) * pick(strokeWidth, NaN)); 796 } 797 value = v.join(',').replace(/NaN/g, 'none'); // #3226 798 this.element.setAttribute('stroke-dasharray', value); 799 } 800 } 801 /** 802 * Destroy the element and element wrapper and clear up the DOM and event 803 * hooks. 804 * 805 * @function Highcharts.SVGElement#destroy 806 */ 807 destroy() { 808 const wrapper = this, element = wrapper.element || {}, renderer = wrapper.renderer, ownerSVGElement = element.ownerSVGElement; 809 let parentToClean = (element.nodeName === 'SPAN' && 810 wrapper.parentGroup || 811 void 0), grandParent, i; 812 // Remove events 813 element.onclick = element.onmouseout = element.onmouseover = 814 element.onmousemove = element.point = null; 815 stop(wrapper); // Stop running animations 816 if (wrapper.clipPath && ownerSVGElement) { 817 const clipPath = wrapper.clipPath; 818 // Look for existing references to this clipPath and remove them 819 // before destroying the element (#6196). 820 // The upper case version is for Edge 821 [].forEach.call(ownerSVGElement.querySelectorAll('[clip-path],[CLIP-PATH]'), function (el) { 822 if (el.getAttribute('clip-path').indexOf(clipPath.element.i
822d) > -1) { 823 el.removeAttribute('clip-path'); 824 } 825 }); 826 wrapper.clipPath = clipPath.destroy(); 827 } 828 wrapper.connector = wrapper.connector?.destroy(); 829 // Destroy stops in case this is a gradient object @todo old code? 830 if (wrapper.stops) { 831 for (i = 0; i < wrapper.stops.length; i++) { 832 wrapper.stops[i].destroy(); 833 } 834 wrapper.stops.length = 0; 835 wrapper.stops = void 0; 836 } 837 // Remove element 838 wrapper.safeRemoveChild(element); 839 // In case of useHTML, clean up empty containers emulating SVG groups 840 // (#1960, #2393, #2697). 841 while (parentToClean && 842 parentToClean.div && 843 parentToClean.div.childNodes.length === 0) { 844 grandParent = parentToClean.parentGroup; 845 wrapper.safeRemoveChild(parentToClean.div); 846 delete parentToClean.div; 847 parentToClean = grandParent; 848 } 849 // Remove from alignObjects 850 if (wrapper.alignOptions) { 851 erase(renderer.alignedObjects, wrapper); 852 } 853 objectEach(wrapper, function (val, key) { 854 // Destroy child elements of a group 855 if (wrapper[key] && 856 wrapper[key].parentGroup === wrapper && 857 wrapper[key].destroy) { 858 wrapper[key].destroy(); 859 } 860 // Delete all properties 861 delete wrapper[key]; 862 }); 863 return; 864 } 865 /** 866 * @private 867 * @function Highcharts.SVGElement#dSettter 868 * @param {number|string|Highcharts.SVGPathArray} value 869 * @param {string} key 870 * @param {Highcharts.SVGDOMElement} element 871 */ 872 dSetter(value, key, element) { 873 if (isArray(value)) { 874 // Backwards compatibility, convert one-dimensional array into an 875 // array of segments 876 if (typeof value[0] === 'string') { 877 value = this.renderer.pathToSegments(value); 878 } 879 this.pathArray = value; 880 value = value.reduce((acc, seg, i) => { 881 if (!seg || !seg.join) { 882 return (seg || '').toString(); 883 } 884 return (i ? acc + ' ' : '') + seg.join(' '); 885 }, ''); 886 } 887 if (/(NaN| {2}|^$)/.test(value)) { 888 value = 'M 0 0'; 889 } 890 // Check for cache before resetting. Resetting causes disturbance in the 891 // DOM, causing flickering in some cases in Edge/IE (#6747). Also 892 // possible performance gain. 893 if (this[key] !== value) { 894 element.setAttribute(key, value); 895 this[key] = value; 896 } 897 } 898 /** 899 * @private 900 * @function Highcharts.SVGElement#fillSetter 901 * @param {Highcharts.ColorType} value 902 * @param {string} key 903 * @param {Highcharts.SVGDOMElement} element 904 */ 905 fillSetter(value, key, element) { 906 if (typeof value === 'string') { 907 element.setAttribute(key, value); 908 } 909 else if (value) { 910 this.complexColor(value, key, element); 911 } 912 } 913 /** 914 * @private 915 * @function Highcharts.SVGElement#hrefSetter 916 * @param {Highcharts.ColorType} value 917 * @param {string} key 918 * @param {Highcharts.SVGDOMElement} element 919 */ 920 hrefSetter(value, key, element) { 921 // Namespace is needed for offline export, #19106 922 element.setAttributeNS('http://www.w3.org/1999/xlink', key, value); 923 } 924 /** 925 * Get the bounding box (width, height, x and y) for the element. Generally 926 * used to get rendered text size. Since this is called a lot in charts, 927 * the results are cached based on text properties, in order to save DOM 928 * traffic. The returned bounding box includes the rotation, so for example 929 * a single text line of rotation 90 will report a greater height, and a 930 * width corresponding to the line-height. 931 * 932 * @sample highcharts/members/renderer-on-chart/ 933 * Draw a rectangle based on a text's bounding box 934 * 935 * @function Highcharts.SVGElement#getBBox 936 * 937 * @param {boolean} [reload] 938 * Skip the cache and get the updated DOM bounding box. 939 * 940 * @param {number} [rot] 941 * Override the element's rotation. This is internally used on axis 942 * labels with a value of 0 to find out what the bounding box would 943 * be have been if it were not rotated. 944 * 945 * @return {Highcharts.BBoxObject} 946 * The bounding box with `x`, `y`, `width` and `height` properties. 947 */ 948 getBBox(reload, rot) { 949 const wrapper = this, { alignValue, element, renderer, styles, textStr } = wrapper, { cache, cacheKeys } = renderer, isSVG = element.namespaceURI === wrapper.SVG_NS, rotation = pick(rot, wrapper.rotation, 0), fontSize = renderer.styledMode ? (element && 950 SVGElement.prototype.getStyle.call(element, 'font-size')) : (styles.fontSize); 951 let bBox, height, toggleTextShadowShim, cacheKey; 952 // Avoid undefined and null (#7316) 953 if (defined(textStr)) { 954 cacheKey = textStr.toString(); 955 // Since numbers are monospaced, and numerical labels appear a lot 956 // in a chart, we assume that a label of n characters has the same 957 // bounding box as others of the same length. Unless there is inner 958 // HTML in the label. In that case, leave the numbers as is (#5899). 959 if (cacheKey.indexOf('<') === -1) { 960 cacheKey = cacheKey.replace(/\d/g, '0'); 961 } 962 // Properties that affect bounding box 963 cacheKey += [ 964 '', 965 renderer.rootFontSize, 966 fontSize, 967 rotation, 968 wrapper.textWidth, // #7874, also useHTML 969 alignValue, 970 styles.lineClamp, 971 styles.textOverflow, // #5968 972 styles.fontWeight // #12163 973 ].join(','); 974 }
vendor: 16,317 bytes, lines 975-1415
975 if (cacheKey && !reload) { 976 bBox = cache[cacheKey]; 977 } 978 // No cache found 979 if (!bBox || bBox.polygon) { 980 // SVG elements 981 if (isSVG || renderer.forExport) { 982 try { // Fails in Firefox if the container has display: none. 983 // When the text shadow shim is used, we need to hide the 984 // fake shadows to get the correct bounding box (#3872) 985 toggleTextShadowShim = this.fakeTS && function (display) { 986 const outline = element.querySelector('.highcharts-text-outline'); 987 if (outline) { 988 css(outline, { display }); 989 } 990 }; 991 // Workaround for #3842, Firefox reporting wrong bounding 992 // box for shadows 993 if (isFunction(toggleTextShadowShim)) { 994 toggleTextShadowShim('none'); 995 } 996 bBox = element.getBBox ? 997 // SVG: use extend because IE9 is not allowed to change 998 // width and height in case of rotation (below) 999 extend({}, element.getBBox()) : { 1000 // HTML elements with `exporting.allowHTML` and 1001 // legacy IE in export mode 1002 width: element.offsetWidth, 1003 height: element.offsetHeight, 1004 x: 0, 1005 y: 0 1006 }; 1007 // #3842 1008 if (isFunction(toggleTextShadowShim)) { 1009 toggleTextShadowShim(''); 1010 } 1011 } 1012 catch (e) { 1013 ''; 1014 } 1015 // If the bBox is not set, the try-catch block above failed. The 1016 // other condition is for Opera that returns a width of 1017 // -Infinity on hidden elements. 1018 if (!bBox || bBox.width < 0) { 1019 bBox = { x: 0, y: 0, width: 0, height: 0 }; 1020 } 1021 // Use HTML within SVG 1022 } 1023 else { 1024 bBox = wrapper.htmlGetBBox(); 1025 } 1026 // True SVG elements as well as HTML elements in modern browsers 1027 // using the .useHTML option need to compensated for rotation 1028 height = bBox.height; 1029 // Workaround for wrong bounding box in IE, Edge and Chrome on 1030 // Windows. With Highcharts' default font, IE and Edge report 1031 // a box height of 16.899 and Chrome rounds it to 17. If this 1032 // stands uncorrected, it results in more padding added below 1033 // the text than above when adding a label border or background. 1034 // Also vertical positioning is affected. 1035 // https://jsfiddle.net/highcharts/em37nvuj/ 1036 // (#1101, #1505, #1669, #2568, #6213). 1037 if (isSVG) { 1038 bBox.height = height = ({ 1039 '11px,17': 14, 1040 '13px,20': 16 1041 }[`${fontSize || ''},${Math.round(height)}`] || 1042 height); 1043 } 1044 // Adjust for rotated text 1045 if (rotation) { 1046 bBox = this.getRotatedBox(bBox, rotation); 1047 } 1048 // Create a reference to catch changes to bBox 1049 const e = { bBox }; 1050 fireEvent(this, 'afterGetBBox', e); 1051 // Pick up any changes after the fired event 1052 bBox = e.bBox; 1053 } 1054 // Cache it. When loading a chart in a hidden iframe in Firefox and 1055 // IE/Edge, the bounding box height is 0, so don't cache it (#5620). 1056 if (cacheKey && (textStr === '' || bBox.height > 0)) { 1057 // Rotate (#4681) 1058 while (cacheKeys.length > 250) { 1059 delete cache[cacheKeys.shift()]; 1060 } 1061 if (!cache[cacheKey]) { 1062 cacheKeys.push(cacheKey); 1063 } 1064 cache[cacheKey] = bBox; 1065 } 1066 return bBox; 1067 } 1068 /** 1069 * Get the rotated box. 1070 * @private 1071 */ 1072 getRotatedBox(box, rotation) { 1073 const { x: boxX, y: boxY, width, height } = box, { alignValue, translateY, rotationOriginX = 0, rotationOriginY = 0 } = this, alignFactor = getAlignFactor(alignValue), baseline = Number(this.element.getAttribute('y') || 0) - 1074 (translateY ? 0 : boxY), rad = rotation * deg2rad, rad90 = (rotation - 90) * deg2rad, cosRad = Math.cos(rad), sinRad = Math.sin(rad), wCosRad = width * cosRad, wSinRad = width * sinRad, cosRad90 = Math.cos(rad90), sinRad90 = Math.sin(rad90), [[xOriginCosRad, xOriginSinRad], [yOriginCosRad, yOriginSinRad]] = [ 1075 rotationOriginX, 1076 rotationOriginY 1077 ].map((rotOrigin) => [ 1078 rotOrigin - (rotOrigin * cosRad), 1079 rotOrigin * sinRad 1080 ]), 1081 // Find the starting point on the left side baseline of 1082 // the text 1083 pX = ((boxX + alignFactor * (width - wCosRad)) + 1084 xOriginCosRad + yOriginSinRad), pY = ((boxY + baseline - alignFactor * wSinRad) - 1085 xOriginSinRad + yOriginCosRad), 1086 // Find all corners 1087 aX = pX + baseline * cosRad90, bX = aX + wCosRad, cX = bX - height * cosRad90, dX = cX - wCosRad, aY = pY + baseline * sinRad90, bY = aY + wSinRad, cY = bY - height * sinRad90, dY = cY - wSinRad; 1088 // Deduct the bounding box from the corners 1089 const x = Math.min(aX, bX, cX, dX), y = Math.min(aY, bY, cY, dY), boxWidth = Math.max(aX, bX, cX, dX) - x, boxHeight = Math.max(aY, bY, cY, dY) - y; 1090 /* Uncomment to debug boxes 1091 this.renderer.path([ 1092 ['M', aX, aY], 1093 ['L', bX, bY], 1094 ['L', cX, cY], 1095 ['L', dX, dY], 1096 ['Z'] 1097 ]) 1098 .attr({ 1099 stroke: 'red', 1100 'stroke-width': 1 1101 }) 1102 .add(); 1103 // */ 1104 return { 1105 x, 1106 y, 1107 width: boxWidth, 1108 height: boxHeight, 1109 polygon: [ 1110 [aX, aY], 1111 [bX, bY], 1112 [cX, cY], 1113 [dX, dY] 1114 ] 1115 }; 1116 } 1117 /** 1118 * Get the computed style. Only in styled mode. 1119 * 1120 * @example 1121 * chart.series[0].points[0].graphic.getStyle('stroke-width'); // => '1px' 1122 * 1123 * @function Highcharts.SVGElement#getStyle 1124 * 1125 * @param {string} prop 1126 * The property name to check for. 1127 * 1128 * @return {string} 1129 * The current computed value. 1130 */ 1131 getStyle(prop) { 1132 return win 1133 .getComputedStyle(this.element || this, '') 1134 .getPropertyValue(prop); 1135 } 1136 /** 1137 * Check if an element has the given class name. 1138 * 1139 * @function Highcharts.SVGElement#hasClass 1140 * 1141 * @param {string} className 1142 * The class name to check for. 1143 * 1144 * @return {boolean} 1145 * Whether the class name is found. 1146 */ 1147 hasClass(className) { 1148 return ('' + this.attr('class')) 1149 .split(' ') 1150 .indexOf(className) !== -1; 1151 } 1152 /** 1153 * Hide the element, similar to setting the `visibility` attribute to 1154 * `hidden`. 1155 * 1156 * @function Highcharts.SVGElement#hide 1157 * 1158 * @return {Highcharts.SVGElement} 1159 * Returns the SVGElement for chaining. 1160 */ 1161 hide() { 1162 return this.attr({ visibility: 'hidden' }); 1163 } 1164 /** 1165 * @private 1166 */ 1167 htmlGetBBox() { 1168 return { height: 0, width: 0, x: 0, y: 0 }; 1169 } 1170 /** 1171 * Initialize the SVG element. This function only exists to make the 1172 * initialization process overridable. It should not be called directly. 1173 * 1174 * @function Highcharts.SVGElement#init 1175 * 1176 * @param {Highcharts.SVGRenderer} renderer 1177 * The SVGRenderer instance to initialize to. 1178 * 1179 * @param {string} nodeName 1180 * The SVG node name. 1181 */ 1182 constructor(renderer, nodeName) { 1183 this.onEvents = {}; 1184 this.opacity = 1; // Default base for animation 1185 this.SVG_NS = SVG_NS; 1186 /** 1187 * The primary DOM node. Each `SVGElement` instance wraps a main DOM 1188 * node, but may also represent more nodes. 1189 * 1190 * @name Highcharts.SVGElement#element 1191 * @type {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement} 1192 */ 1193 this.element = nodeName === 'span' || nodeName === 'body' ? 1194 createElement(nodeName) : 1195 doc.createElementNS(this.SVG_NS, nodeName); 1196 /** 1197 * The renderer that the SVGElement belongs to. 1198 * 1199 * @name Highcharts.SVGElement#renderer 1200 * @type {Highcharts.SVGRenderer} 1201 */ 1202 this.renderer = renderer; 1203 this.styles = {}; 1204 fireEvent(this, 'afterInit'); 1205 } 1206 /** 1207 * Add an event listener. This is a simple setter that replaces the 1208 * previous event of the same type added by this function, as opposed to 1209 * the {@link Highcharts#addEvent} function. 1210 * 1211 * @sample highcharts/members/element-on/ 1212 * A clickable rectangle 1213 * 1214 * @function Highcharts.SVGElement#on 1215 * 1216 * @param {string} eventType 1217 * The event type. 1218 * 1219 * @param {Function} handler 1220 * The handler callback. 1221 * 1222 * @return {Highcharts.SVGElement} 1223 * The SVGElement for chaining. 1224 */ 1225 on(eventType, handler) { 1226 const { onEvents } = this; 1227 if (onEvents[eventType]) { 1228 // Unbind existing event 1229 onEvents[eventType](); 1230 } 1231 onEvents[eventType] = addEvent(this.element, eventType, handler); 1232 return this; 1233 } 1234 /** 1235 * @private 1236 * @function Highcharts.SVGElement#opacitySetter 1237 * @param {string} value 1238 * @param {string} key 1239 * @param {Highcharts.SVGDOMElement} element 1240 */ 1241 opacitySetter(value, key, element) { 1242 // Round off to avoid float errors, like tests where opacity lands on 1243 // 9.86957e-06 instead of 0 1244 const opacity = Number(Number(value).toFixed(3)); 1245 this.opacity = opacity; 1246 element.setAttribute(key, opacity); 1247 } 1248 /** 1249 * Re-align an aligned text or label after setting the text. 1250 * 1251 * @private 1252 * @function Highcharts.SVGElement#reAlign 1253 * 1254 */ 1255 reAlign() { 1256 if (this.alignOptions?.width && this.alignOptions.align !== 'left') { 1257 this.alignOptions.width = this.getBBox().width; 1258 this.placed = false; // Block animation 1259 this.align(); 1260 } 1261 } 1262 /** 1263 * Remove a class name from the element. 1264 * 1265 * @function Highcharts.SVGElement#removeClass 1266 * 1267 * @param {string|RegExp} className 1268 * The class name to remove. 1269 * 1270 * @return {Highcharts.SVGElement} Returns the SVG element for chainability. 1271 */ 1272 removeClass(className) { 1273 return this.attr('class', ('' + this.attr('class')) 1274 .replace(isString(className) ? 1275 new RegExp(`(^| )${className}( |$)`) : // #12064, #13590 1276 className, ' ') 1277 .replace(/ +/g, ' ') 1278 .trim()); 1279 } 1280 /** 1281 * 1282 * @private 1283 */ 1284 removeTextOutline() { 1285 const outline = this.element 1286 .querySelector('tspan.highcharts-text-outline'); 1287 if (outline) { 1288 this.safeRemoveChild(outline); 1289 } 1290 } 1291 /** 1292 * Removes an element from the DOM. 1293 * 1294 * @private 1295 * @function Highcharts.SVGElement#safeRemoveChild 1296 * 1297 * @param {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement} element 1298 * The DOM node to remove. 1299 */ 1300 safeRemoveChild(element) { 1301 const parentNode = element.parentNode; 1302 if (parentNode) { 1303 parentNode.removeChild(element); 1304 } 1305 } 1306 /** 1307 * Set the coordinates needed to draw a consistent radial gradient across 1308 * a shape regardless of positioning inside the chart. Used on pie slices 1309 * to make all the slices have the same radial reference point. 1310 * 1311 * @function Highcharts.SVGElement#setRadialReference 1312 * 1313 * @param {Array<number>} coordinates 1314 * The center reference. The format is `[centerX, centerY, diameter]` in 1315 * pixels. 1316 * 1317 * @return {Highcharts.SVGElement} 1318 * Returns the SVGElement for chaining. 1319 */ 1320 setRadialReference(coordinates) { 1321 const existingGradient = (this.element.gradient && 1322 this.renderer.gradients[this.element.gradient]); 1323 this.element.radialReference = coordinates; 1324 // On redrawing objects with an existing gradient, the gradient needs 1325 // to be repositioned (#3801) 1326 if (existingGradient && existingGradient.radAttr) { 1327 existingGradient.animate(this.renderer.getRadialAttr(coordinates, existingGradient.radAttr)); 1328 } 1329 return this; 1330 } 1331 /** 1332 * Add a shadow to the element. In styled mode, this method is not used, 1333 * instead use `defs` and filters. 1334 * 1335 * @example 1336 * renderer.rect(10, 100, 100, 100) 1337 * .attr({ fill: 'red' }) 1338 * .shadow(true); 1339 * 1340 * @function Highcharts.SVGElement#shadow 1341 * 1342 * @param {boolean|Highcharts.ShadowOptionsObject} [shadowOptions] The 1343 * shadow options. If `true`, the default options are applied. If 1344 * `false`, the current shadow will be removed. 1345 * 1346 * @return {Highcharts.SVGElement} Returns the SVGElement for chaining. 1347 */ 1348 shadow(shadowOptions) { 1349 const { renderer } = this, options = merge(this.parentGroup?.rotation === 90 ? { 1350 offsetX: -1, 1351 offsetY: -1 1352 } : {}, isObject(shadowOptions) ? shadowOptions : {}), id = renderer.shadowDefinition(options); 1353 return this.attr({ 1354 filter: shadowOptions ? 1355 `url(${renderer.url}#${id})` : 1356 'none' 1357 }); 1358 } 1359 /** 1360 * Show the element after it has been hidden. 1361 * 1362 * @function Highcharts.SVGElement#show 1363 * 1364 * @param {boolean} [inherit=true] 1365 * Set the visibility attribute to `inherit` rather than `visible`. 1366 * The difference is that an element with `visibility="visible"` 1367 * will be visible even if the parent is hidden. 1368 * 1369 * @return {Highcharts.SVGElement} 1370 * Returns the SVGElement for chaining. 1371 */ 1372 show(inherit = true) { 1373 return this.attr({ visibility: inherit ? 'inherit' : 'visible' }); 1374 } 1375 /** 1376 * Set the stroke-width and record it on the SVGElement 1377 * 1378 * @private 1379 * @function Highcharts.SVGElement#strokeSetter 1380 * @param {number|string|ColorType} value 1381 * @param {string} key 1382 * @param {Highcharts.SVGDOMElement} element 1383 */ 1384 'stroke-widthSetter'(value, key, element) { 1385 // Record it for quick access in getter 1386 this[key] = value; 1387 element.setAttribute(key, value); 1388 } 1389 /** 1390 * Get the computed stroke width in pixel values. This is used extensively 1391 * when drawing shapes to ensure the shapes are rendered crisp and 1392 * positioned correctly relative to each other. Using 1393 * `shape-rendering: crispEdges` leaves us less control over positioning, 1394 * for example when we want to stack columns next to each other, or position 1395 * things pixel-perfectly within the plot box. 1396 * 1397 * The common pattern when placing a shape is: 1398 * - Create the SVGElement and add it to the DOM. In styled mode, it will 1399 * now receive a stroke width from the style sheet. In classic mode we 1400 * will add the `stroke-width` attribute. 1401 * - Read the computed `elem.strokeWidth()`. 1402 * - Place it based on the stroke width. 1403 * 1404 * @function Highcharts.SVGElement#strokeWidth 1405 * 1406 * @return {number} 1407 * The stroke width in pixels. Even if the given stroke width (in CSS or by 1408 * attributes) is based on `em` or other units, the pixel size is returned. 1409 */ 1410 strokeWidth() { 1411 // In non-styled mode, read the stroke width as set by .attr 1412 if (!this.renderer.styledMode) { 1413 return this['stroke-width'] || 0; 1414 } 1415 // In styled mode, read computed stroke width
1416 const val = this.getStyle('stroke-width'); 1417 let ret = 0, tempElement; 1418 // Read pixel values directly 1419 if (/px$/.test(val)) { 1420 ret = pInt(val); 1421 // Other values like em, pt etc need to be measured 1422 } 1423 else if (val !== '') { 1424 tempElement = doc.createElementNS(SVG_NS, 'rect'); 1425 attr(tempElement, { 1426 width: val, 1427 'stroke-width': 0 1428 }); 1429 this.element.parentNode.appendChild(tempElement); 1430 ret = tempElement.getBBox().width; 1431 tempElement.parentNode.removeChild(tempElement); 1432 } 1433 return ret; 1434 } 1435 /** 1436 * If one of the symbol size affecting parameters are changed, 1437 * check all the others only once for each call to an element's 1438 * .attr() method 1439 * 1440 * @private 1441 * @function Highcharts.SVGElement#symbolAttr 1442 * 1443 * @param {Highcharts.SVGAttributes} hash 1444 * The attributes to set. 1445 */ 1446 symbolAttr(hash) { 1447 const wrapper = this; 1448 SVGElement.symbolCustomAttribs.forEach(function (key) { 1449 wrapper[key] = pick(hash[key], wrapper[key]); 1450 }); 1451 wrapper.attr({ 1452 d: wrapper.renderer.symbols[wrapper.symbolName](wrapper.x, wrapper.y, wrapper.width, wrapper.height, wrapper) 1453 }); 1454 } 1455 /** 1456 * @private 1457 * @function Highcharts.SVGElement#textSetter 1458 * @param {string} value 1459 */ 1460 textSetter(value) { 1461 if (value !== this.textStr) { 1462 // Delete size caches when the text changes 1463 // delete this.bBox; // old code in series-label 1464 delete this.textPxLength; 1465 this.textStr = value; 1466 if (this.added) { 1467 this.renderer.buildText(this); 1468 } 1469 this.reAlign(); 1470 } 1471 } 1472 /** 1473 * @private 1474 * @function Highcharts.SVGElement#titleSetter 1475 * @param {string} value 1476 */ 1477 titleSetter(value) { 1478 const el = this.element; 1479 const titleNode = el.getElementsByTagName('title')[0] || 1480 doc.createElementNS(this.SVG_NS, 'title'); 1481 // Move to first child 1482 if (el.insertBefore) { 1483 el.insertBefore(titleNode, el.firstChild); 1484 } 1485 else { 1486 el.appendChild(titleNode); 1487 } 1488 // Replace text content and escape markup 1489 titleNode.textContent = replaceNested(// Scan #[73] 1490 pick(value, ''), // #3276, #3895 1491 [/<[^>]*>/g, '']).replace(/</g, '<').replace(/>/g, '>'); 1492 } 1493 /** 1494 * Bring the element to the front. Alternatively, a new zIndex can be set. 1495 * 1496 * @sample highcharts/members/element-tofront/ 1497 * Click an element to bring it to front 1498 * 1499 * @function Highcharts.SVGElement#toFront 1500 * 1501 * @return {Highcharts.SVGElement} 1502 * Returns the SVGElement for chaining. 1503 */ 1504 toFront() { 1505 const element = this.element; 1506 element.parentNode.appendChild(element); 1507 return this; 1508 } 1509 /** 1510 * Move an object and its children by x and y values. 1511 * 1512 * @function Highcharts.SVGElement#translate 1513 * 1514 * @param {number} x 1515 * The x value. 1516 * 1517 * @param {number} y 1518 * The y value. 1519 * 1520 * @return {Highcharts.SVGElement} 1521 * Translated element. 1522 */ 1523 translate(x, y) { 1524 return this.attr({ 1525 translateX: x, 1526 translateY: y 1527 }); 1528 } 1529 /** 1530 * Update the transform attribute based on internal properties. Deals with 1531 * the custom `translateX`, `translateY`, `rotation`, `scaleX` and `scaleY` 1532 * attributes and updates the SVG `transform` attribute. 1533 * 1534 * @private 1535 * @function Highcharts.SVGElement#updateTransform 1536 */ 1537 updateTransform(attrib = 'transform') { 1538 const { element, matrix, rotation = 0, rotationOriginX, rotationOriginY, scaleX, scaleY, translateX = 0, translateY = 0 } = this; 1539 // Apply translate. Nearly all transformed elements have translation, 1540 // so instead of checking for translate = 0, do it always (#1767, 1541 // #1846). 1542 const transform = ['translate(' + translateX + ',' + translateY + ')']; 1543 // Apply matrix 1544 if (defined(matrix)) { 1545 transform.push('matrix(' + matrix.join(',') + ')'); 1546 } 1547 // Apply rotation 1548 if (rotation) { 1549 transform.push('rotate(' + rotation + ' ' + 1550 pick(rotationOriginX, element.getAttribute('x'), 0) + 1551 ' ' + 1552 pick(rotationOriginY, element.getAttribute('y') || 0) + ')'); 1553 // HTML labels rotation (#20685) 1554 if (this.text?.element.tagName === 'SPAN') { 1555 this.text.attr({ 1556 rotation, 1557 rotationOriginX: (rotationOriginX || 0) - this.padding, 1558 rotationOriginY: (rotationOriginY || 0) - this.padding 1559 }); 1560 } 1561 } 1562 // Apply scale 1563 if (defined(scaleX) || defined(scaleY)) { 1564 transform.push('scale(' + pick(scaleX, 1) + ' ' + pick(scaleY, 1) + ')'); 1565 } 1566 if (transform.length && !(this.text || this).textPath) { 1567 element.setAttribute(attrib, transform.join(' ')); 1568 } 1569 } 1570 /** 1571 * @private 1572 * @function Highcharts.SVGElement#visibilitySetter 1573 * 1574 * @param {string} value 1575 * 1576 * @param {string} key 1577 * 1578 * @param {Highcharts.SVGDOMElement} element 1579 * 1580 */ 1581 visibilitySetter(value, key, element) { 1582 // IE9-11 doesn't handle visibility:inherit well, so we remove the 1583 // attribute instead (#2881, #3909) 1584 if (value === 'inherit') { 1585 element.removeAttribute(key); 1586 } 1587 else if (this[key] !== value) { // #6747 1588 element.setAttribute(key, value); 1589 } 1590 this[key] = value; 1591 } 1592 /** 1593 * @private 1594 * @function Highcharts.SVGElement#xGetter 1595 */ 1596 xGetter(key) { 1597 if (this.element.nodeName === 'circle') { 1598 if (key === 'x') { 1599 key = 'cx'; 1600 } 1601 else if (key === 'y') { 1602 key = 'cy'; 1603 } 1604 } 1605 return this._defaultGetter(key); 1606 } 1607 /** 1608 * @private 1609 * @function Highcharts.SVGElement#zIndexSetter 1610 */ 1611 zIndexSetter(value, key) { 1612 const renderer = this.renderer, parentGroup = this.parentGroup, parentWrapper = parentGroup || renderer, parentNode = parentWrapper.element || renderer.box, element = this.element, svgParent = parentNode === renderer.box; 1613 let childNodes, otherElement, otherZIndex, inserted = false, undefinedOtherZIndex, run = this.added, i; 1614 if (defined(value)) { 1615 // So we can read it for other elements in the group 1616 element.setAttribute('data-z-index', value); 1617 value = +value; 1618 if (this[key] === value) { 1619 // Only update when needed (#3865) 1620 run = false;
vendor: 5,011 bytes, lines 1621-1784
1621 } 1622 } 1623 else if (defined(this[key])) { 1624 element.removeAttribute('data-z-index'); 1625 } 1626 this[key] = value; 1627 // Insert according to this and other elements' zIndex. Before .add() is 1628 // called, nothing is done. Then on add, or by later calls to 1629 // zIndexSetter, the node is placed on the right place in the DOM. 1630 if (run) { 1631 value = this.zIndex; 1632 if (value && parentGroup) { 1633 parentGroup.handleZ = true; 1634 } 1635 childNodes = parentNode.childNodes; 1636 for (i = childNodes.length - 1; i >= 0 && !inserted; i--) { 1637 otherElement = childNodes[i]; 1638 otherZIndex = otherElement.getAttribute('data-z-index'); 1639 undefinedOtherZIndex = !defined(otherZIndex); 1640 if (otherElement !== element) { 1641 if ( 1642 // Negative zIndex versus no zIndex: 1643 // On all levels except the highest. If the parent is 1644 // <svg>, then we don't want to put items before <desc> 1645 // or <defs> 1646 value < 0 && 1647 undefinedOtherZIndex && 1648 !svgParent && 1649 !i) { 1650 parentNode.insertBefore(element, childNodes[i]); 1651 inserted = true; 1652 } 1653 else if ( 1654 // Insert after the first element with a lower zIndex 1655 pInt(otherZIndex) <= value || 1656 // If negative zIndex, add this before first undefined 1657 // zIndex element 1658 (undefinedOtherZIndex && 1659 (!defined(value) || value >= 0))) { 1660 parentNode.insertBefore(element, childNodes[i + 1]); 1661 inserted = true; 1662 } 1663 } 1664 } 1665 if (!inserted) { 1666 parentNode.insertBefore(element, childNodes[svgParent ? 3 : 0]); 1667 inserted = true; 1668 } 1669 } 1670 return inserted; 1671 } 1672} 1673/* * 1674 * 1675 * Properties 1676 * 1677 * */ 1678// Custom attributes used for symbols, these should be filtered out when 1679// setting SVGElement attributes (#9375). 1680SVGElement.symbolCustomAttribs = [ 1681 'anchorX', 1682 'anchorY', 1683 'clockwise', 1684 'end', 1685 'height', 1686 'innerR', 1687 'r', 1688 'start', 1689 'width', 1690 'x', 1691 'y' 1692]; 1693// Some shared setters and getters 1694SVGElement.prototype.strokeSetter = SVGElement.prototype.fillSetter; 1695SVGElement.prototype.yGetter = SVGElement.prototype.xGetter; 1696SVGElement.prototype.matrixSetter = 1697 SVGElement.prototype.rotationOriginXSetter = 1698 SVGElement.prototype.rotationOriginYSetter = 1699 SVGElement.prototype.rotationSetter = 1700 SVGElement.prototype.scaleXSetter = 1701 SVGElement.prototype.scaleYSetter = 1702 SVGElement.prototype.translateXSetter = 1703 SVGElement.prototype.translateYSetter = 1704 SVGElement.prototype.verticalAlignSetter = function (value, key) { 1705 this[key] = value; 1706 this.doTransform = true; 1707 }; 1708/* * 1709 * 1710 * Default Export 1711 * 1712 * */ 1713export default SVGElement; 1714/* * 1715 * 1716 * API Declarations 1717 * 1718 * */ 1719/** 1720 * Reference to the global SVGElement class as a workaround for a name conflict 1721 * in the Highcharts namespace. 1722 * 1723 * @global 1724 * @typedef {global.SVGElement} GlobalSVGElement 1725 * 1726 * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGElement 1727 */ 1728/** 1729 * The horizontal alignment of an element. 1730 * 1731 * @typedef {"center"|"left"|"right"} Highcharts.AlignValue 1732 */ 1733/** 1734 * Options to align the element relative to the chart or another box. 1735 * 1736 * @interface Highcharts.AlignObject 1737 */ /** 1738* Horizontal alignment. Can be one of `left`, `center` and `right`. 1739* 1740* @name Highcharts.AlignObject#align 1741* @type {Highcharts.AlignValue|undefined} 1742* 1743* @default left 1744*/ /** 1745* Vertical alignment. Can be one of `top`, `middle` and `bottom`. 1746* 1747* @name Highcharts.AlignObject#verticalAlign 1748* @type {Highcharts.VerticalAlignValue|undefined} 1749* 1750* @default top 1751*/ /** 1752* Horizontal pixel offset from alignment. 1753* 1754* @name Highcharts.AlignObject#x 1755* @type {number|undefined} 1756* 1757* @default 0 1758*/ /** 1759* Vertical pixel offset from alignment. 1760* 1761* @name Highcharts.AlignObject#y 1762* @type {number|undefined} 1763* 1764* @default 0 1765*/ /** 1766* Use the `transform` attribute with translateX and translateY custom 1767* attributes to align this elements rather than `x` and `y` attributes. 1768* 1769* @name Highcharts.AlignObject#alignByTranslate 1770* @type {boolean|undefined} 1771* 1772* @default false 1773*/ 1774/** 1775 * Bounding box of an element. 1776 * 1777 * @interface Highcharts.BBoxObject 1778 * @extends Highcharts.PositionObject 1779 */ /** 1780* Height of the bounding box. 1781* 1782* @name Highcharts.BBoxObject#height 1783* @type {number} 1784*/ /**
1785* Width of the bounding box. 1786* 1787* @name Highcharts.BBoxObject#width 1788* @type {number} 1789*/ /** 1790* Horizontal position of the bounding box. 1791* 1792* @name Highcharts.BBoxObject#x 1793* @type {number} 1794*/ /** 1795* Vertical position of the bounding box. 1796* 1797* @name Highcharts.BBoxObject#y 1798* @type {number} 1799*/ 1800/** 1801 * An object of key-value pairs for SVG attributes. Attributes in Highcharts 1802 * elements for the most parts correspond to SVG, but some are specific to 1803 * Highcharts, like `zIndex`, `rotation`, `rotationOriginX`, 1804 * `rotationOriginY`, `translateX`, `translateY`, `scaleX` and `scaleY`. SVG 1805 * attributes containing a hyphen are _not_ camel-cased, they should be 1806 * quoted to preserve the hyphen. 1807 * 1808 * @example 1809 * { 1810 * 'stroke': '#ff0000', // basic 1811 * 'stroke-width': 2, // hyphenated 1812 * 'rotation': 45 // custom 1813 * 'd': ['M', 10, 10, 'L', 30, 30, 'z'] // path definition, note format 1814 * } 1815 * 1816 * @interface Highcharts.SVGAttributes 1817 */ /** 1818* @name Highcharts.SVGAttributes#[key:string] 1819* @type {*} 1820*/ /** 1821* @name Highcharts.SVGAttributes#d 1822* @type {string|Highcharts.SVGPathArray|undefined} 1823*/ /** 1824* @name Highcharts.SVGAttributes#dx 1825* @type {number|undefined} 1826*/ /** 1827* @name Highcharts.SVGAttributes#dy 1828* @type {number|undefined} 1829*/ /** 1830* @name Highcharts.SVGAttributes#fill 1831* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject|undefined} 1832*/ /** 1833* @name Highcharts.SVGAttributes#inverted 1834* @type {boolean|undefined} 1835*/ /** 1836* @name Highcharts.SVGAttributes#matrix 1837* @type {Array<number>|undefined} 1838*/ /** 1839* @name Highcharts.SVGAttributes#rotation 1840* @type {number|undefined} 1841*/ /** 1842* @name Highcharts.SVGAttributes#rotationOriginX 1843* @type {number|undefined} 1844*/ /** 1845* @name Highcharts.SVGAttributes#rotationOriginY 1846* @type {number|undefined} 1847*/ /** 1848* @name Highcharts.SVGAttributes#scaleX 1849* @type {number|undefined} 1850*/ /** 1851* @name Highcharts.SVGAttributes#scaleY 1852* @type {number|undefined} 1853*/ /** 1854* @name Highcharts.SVGAttributes#stroke 1855* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject|undefined} 1856*/ /** 1857* @name Highcharts.SVGAttributes#style 1858* @type {string|Highcharts.CSSObject|undefined} 1859*/ /** 1860* @name Highcharts.SVGAttributes#translateX 1861* @type {number|undefined} 1862*/ /** 1863* @name Highcharts.SVGAttributes#translateY 1864* @type {number|undefined} 1865*/ /** 1866* @name Highcharts.SVGAttributes#zIndex 1867* @type {number|undefined} 1868*/ 1869/** 1870 * An SVG DOM element. The type is a reference to the regular SVGElement in the 1871 * global scope. 1872 * 1873 * @typedef {globals.GlobalSVGElement} Highcharts.SVGDOMElement 1874 * 1875 * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGElement 1876 */ 1877/** 1878 * The vertical alignment of an element. 1879 * 1880 * @typedef {"bottom"|"middle"|"top"} Highcharts.VerticalAlignValue 1881 */ 1882''; // Keeps doclets above in JS file
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.