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, setAnimation } = A; 13import Axis from '../Axis/Axis.js'; 14import D from '../Defaults.js'; 15const { defaultOptions } = D; 16import Templating from '../Templating.js'; 17const { numberFormat } = Templating; 18import Foundation from '../Foundation.js'; 19const { registerEventOptions } = Foundation; 20import H from '../Globals.js'; 21const { charts, doc, marginNames, svg, win } = H; 22import RendererRegistry from '../Renderer/RendererRegistry.js'; 23import Series from '../Series/Series.js'; 24import SeriesRegistry from '../Series/SeriesRegistry.js'; 25const { seriesTypes } = SeriesRegistry; 26import SVGRenderer from '../Renderer/SVG/SVGRenderer.js'; 27import Time from '../Time.js'; 28import U from '../Utilities.js'; 29import AST from '../Renderer/HTML/AST.js'; 30import Tick from '../Axis/Tick.js'; 31const { addEvent, attr, createElement, css, defined, diffObjects, discardElement, erase, error, extend, find, fireEvent, getAlignFactor, getStyle, isArray, isNumber, isObject, isString, merge, objectEach, pick, pInt, relativeLength, removeEvent, splat, syncTimeout, uniqueKey } = U; 32/* * 33 * 34 * Class 35 * 36 * */ 37/* eslint-disable no-invalid-this, valid-jsdoc */ 38/** 39 * The Chart class. The recommended constructor is {@link Highcharts#chart}. 40 * 41 * @example 42 * let chart = Highcharts.chart('container', { 43 * title: { 44 * text: 'My chart' 45 * }, 46 * series: [{ 47 * data: [1, 3, 2, 4] 48 * }] 49 * }) 50 * 51 * @class 52 * @name Highcharts.Chart 53 * 54 * @param {string|Highcharts.HTMLDOMElement} [renderTo] 55 * The DOM element to render to, or its id. 56 * 57 * @param {Highcharts.Options} options 58 * The chart options structure. 59 * 60 * @param {Highcharts.ChartCallbackFunction} [callback] 61 * Function to run when the chart has loaded and all external images 62 * are loaded. Defining a 63 * [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load) 64 * handler is equivalent. 65 */ 66class Chart { 67 /** 68 * Factory function for basic charts. 69 * 70 * @example 71 * // Render a chart in to div#container 72 * let chart = Highcharts.chart('container', { 73 * title: { 74 * text: 'My chart' 75 * }, 76 * series: [{ 77 * data: [1, 3, 2, 4] 78 * }] 79 * }); 80 * 81 * @function Highcharts.chart 82 * 83 * @param {string|Highcharts.HTMLDOMElement} [renderTo] 84 * The DOM element to render to, or its id. 85 * 86 * @param {Highcharts.Options} options 87 * The chart options structure. 88 * 89 * @param {Highcharts.ChartCallbackFunction} [callback] 90 * Function to run when the chart has loaded and all external images are 91 * loaded. Defining a 92 * [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load) 93 * handler is equivalent. 94 * 95 * @return {Highcharts.Chart} 96 * Returns the Chart object. 97 */ 98 static chart(a, b, c) { 99 return new Chart(a, b, c); 100 } 101 // Implementation 102 constructor(a, 103 /* eslint-disable @typescript-eslint/no-unused-vars */ 104 b, c 105 /* eslint-enable @typescript-eslint/no-unused-vars */ 106 ) { 107 this.sharedClips = {}; 108 const args = [ 109 // ES5 builds fail unless we cast it to an Array 110 ...arguments 111 ]; 112 // Remove the optional first argument, renderTo, and set it on this. 113 if (isString(a) || a.nodeName) { 114 this.renderTo = args.shift(); 115 } 116 this.init(args[0], args[1]); 117 } 118 /* * 119 * 120 * Functions 121 * 122 * */ 123 /** 124 * Function setting zoom options after chart init and after chart update. 125 * Offers support for deprecated options. 126 * 127 * @private 128 * @function Highcharts.Chart#setZoomOptions 129 */ 130 setZoomOptions() { 131 const chart = this, options = chart.options.chart, zooming = options.zooming; 132 chart.zooming = { 133 ...zooming, 134 type: pick(options.zoomType, zooming.type), 135 key: pick(options.zoomKey, zooming.key), 136 pinchType: pick(options.pinchType, zooming.pinchType), 137 singleTouch: pick(options.zoomBySingleTouch, zooming.singleTouch, false), 138 resetButton: merge(zooming.resetButton, options.resetZoomButton) 139 }; 140 } 141 /** 142 * Overridable function that initializes the chart. The constructor's 143 * arguments are passed on directly. 144 * 145 * @function Highcharts.Chart#init 146 * 147 * @param {Highcharts.Options} userOptions 148 * Custom options. 149 * 150 * @param {Function} [callback] 151 * Function to run when the chart has loaded and all external 152 * images are loaded. 153 * 154 * 155 * @emits Highcharts.Chart#event:init 156 * @emits Highcharts.Chart#event:afterInit 157 */ 158 init(userOptions, callback) { 159 // Fire the event with a default function 160 fireEvent(this, 'init', { args: arguments }, function () { 161 const options = merge(defaultOptions, userOptions), // Do the merge 162 optionsChart = options.chart, renderTo = this.renderTo || optionsChart.renderTo; 163 /** 164 * The original options given to the constructor or a chart factory 165 * like {@link Highcharts.chart} and {@link Highcharts.stockChart}. 166 * The original options are shallow copied to avoid mutation. The 167 * copy, `chart.userOptions`, may later be mutated to reflect 168 * updated options throughout the lifetime of the chart. 169 * 170 * For collections, like `series`, `xAxis` and `yAxis`, the chart 171 * user options should always be reflected by the item user option, 172 * so for example the following should always be true: 173 * 174 * `chart.xAxis[0].userOptions === chart.userOptions.xAxis[0]` 175 * 176 * @name Highcharts.Chart#userOptions 177 * @type {Highcharts.Options} 178 */ 179 this.userOptions = extend({}, userOptions); 180 if (!(this.renderTo = (isString(renderTo) ? 181 doc.getElementById(renderTo) : 182 renderTo))) { 183 // Display an error if the renderTo is wrong 184 error(13, true, this); 185 } 186 this.margin = []; 187 this.spacing = []; 188 // An array of functions that returns labels that should be 189 // considered for anti-collision 190 this.labelCollectors = []; 191 this.callback = callback; 192 this.isResizing = 0; 193 /** 194 * The options structure for the chart after merging 195 * {@link #defaultOptions} and {@link #userOptions}. It contains 196 * members for the sub elements like series, legend, tooltip etc. 197 * 198 * @name Highcharts.Chart#options 199 * @type {Highcharts.Options} 200 */ 201 this.options = options; 202 /** 203 * All the axes in the chart. 204 * 205 * @see Highcharts.Chart.xAxis 206 * @see Highcharts.Chart.yAxis 207 * 208 * @name Highcharts.Chart#axes 209 * @type {Array<Highcharts.Axis>} 210 */ 211 this.axes = []; 212 /** 213 * All the current series in the chart. 214 * 215 * @name Highcharts.Chart#series 216 * @type {Array<Highcharts.Series>} 217 */ 218 this.series = []; 219 this.locale = options.lang.locale ?? 220 this.renderTo.closest('[lang]')?.lang; 221 /** 222 * The `Time` object associated with the chart. Since v6.0.5, 223 * time settings can be applied individually for each chart. If 224 * no individual settings apply, the `Time` object is shared by 225 * all instances. 226 * 227 * @name Highcharts.Chart#time 228 * @type {Highcharts.Time} 229 */ 230 this.time = new Time(extend(options.time || {}, { 231 locale: this.locale 232 })); 233 options.time = this.time.options; 234 /** 235 * Callback function to override the default function that formats 236 * all the numbers in the chart. Returns a string with the formatted 237 * number. 238 * 239 * @name Highcharts.Chart#numberFormatter 240 * @type {Highcharts.NumberFormatterCallbackFunction} 241 */ 242 this.numberFormatter = (optionsChart.numberFormatter || numberFormat).bind(this); 243 /** 244 * Whether the chart is in styled mode, meaning all presentational 245 * attributes are avoided. 246 * 247 * @name Highcharts.Chart#styledMode 248 * @type {boolean} 249 */ 250 this.styledMode = optionsChart.styledMode; 251 this.hasCartesianSeries = optionsChart.showAxes; 252 const chart = this; 253 /** 254 * Index position of the chart in the {@link Highcharts#charts} 255 * property. 256 * 257 * @name Highcharts.Chart#index 258 * @type {number} 259 * @readonly 260 */ 261 chart.index = charts.length; // Add the chart to the global lookup 262 charts.push(chart); 263 H.chartCount++; 264 // Chart event handlers 265 registerEventOptions(this, optionsChart); 266 /** 267 * A collection of the X axes in the chart. 268 * 269 * @name Highcharts.Chart#xAxis 270 * @type {Array<Highcharts.Axis>} 271 */ 272 chart.xAxis = []; 273 /** 274 * A collection of the Y axes in the chart. 275 * 276 * @name Highcharts.Chart#yAxis 277 * @type {Array<Highcharts.Axis>} 278 * 279 * @todo 280 * Make events official: Fire the event `afterInit`. 281 */ 282 chart.yAxis = []; 283 chart.pointCount = chart.colorCounter = chart.symbolCounter = 0; 284 this.setZoomOptions(); 285 // Fire after init but before first render, before axes and series 286 // have been initialized. 287 fireEvent(chart, 'afterInit');
vendor: 14,146 bytes, lines 288-656
288 chart.firstRender(); 289 }); 290 } 291 /** 292 * Internal function to unitialize an individual series. 293 * 294 * @private 295 * @function Highcharts.Chart#initSeries 296 */ 297 initSeries(options) { 298 const chart = this, optionsChart = chart.options.chart, type = (options.type || 299 optionsChart.type), SeriesClass = seriesTypes[type]; 300 // No such series type 301 if (!SeriesClass) { 302 error(17, true, chart, { missingModuleFor: type }); 303 } 304 const series = new SeriesClass(); 305 if (typeof series.init === 'function') { 306 series.init(chart, options); 307 } 308 return series; 309 } 310 /** 311 * Internal function to set data for all series with enabled sorting. 312 * 313 * @private 314 * @function Highcharts.Chart#setSortedData 315 */ 316 setSortedData() { 317 this.getSeriesOrderByLinks().forEach(function (series) { 318 // We need to set data for series with sorting after series init 319 if (!series.points && !series.data && series.enabledDataSorting) { 320 series.setData(series.options.data, false); 321 } 322 }); 323 } 324 /** 325 * Sort and return chart series in order depending on the number of linked 326 * series. 327 * 328 * @private 329 * @function Highcharts.Series#getSeriesOrderByLinks 330 */ 331 getSeriesOrderByLinks() { 332 return this.series.concat().sort(function (a, b) { 333 if (a.linkedSeries.length || b.linkedSeries.length) { 334 return b.linkedSeries.length - a.linkedSeries.length; 335 } 336 return 0; 337 }); 338 } 339 /** 340 * Order all series or axes above a given index. When series or axes are 341 * added and ordered by configuration, only the last series is handled 342 * (#248, #1123, #2456, #6112). This function is called on series and axis 343 * initialization and destroy. 344 * 345 * @private 346 * @function Highcharts.Chart#orderItems 347 * @param {string} coll The collection name 348 * @param {number} [fromIndex=0] 349 * If this is given, only the series above this index are handled. 350 */ 351 orderItems(coll, fromIndex = 0) { 352 const collection = this[coll], 353 // Item options should be reflected in chart.options.series, 354 // chart.options.yAxis etc 355 optionsArray = this.options[coll] = splat(this.options[coll]) 356 .slice(), userOptionsArray = this.userOptions[coll] = this.userOptions[coll] ? 357 splat(this.userOptions[coll]).slice() : 358 []; 359 if (this.hasRendered) { 360 // Remove all above index 361 optionsArray.splice(fromIndex); 362 userOptionsArray.splice(fromIndex); 363 } 364 if (collection) { 365 for (let i = fromIndex, iEnd = collection.length; i < iEnd; ++i) { 366 const item = collection[i]; 367 if (item) { 368 /** 369 * Contains the series' index in the `Chart.series` array. 370 * 371 * @name Highcharts.Series#index 372 * @type {number} 373 * @readonly 374 */ 375 item.index = i; 376 if (item instanceof Series) { 377 item.name = item.getName(); 378 } 379 if (!item.options.isInternal) { 380 optionsArray[i] = item.options; 381 userOptionsArray[i] = item.userOptions; 382 } 383 } 384 } 385 } 386 } 387 /** 388 * Check whether a given point is within the plot area. 389 * 390 * @function Highcharts.Chart#isInsidePlot 391 * 392 * @param {number} plotX 393 * Pixel x relative to the plot area. 394 * 395 * @param {number} plotY 396 * Pixel y relative to the plot area. 397 * 398 * @param {Highcharts.ChartIsInsideOptionsObject} [options] 399 * Options object. 400 * 401 * @return {boolean} 402 * Returns true if the given point is inside the plot area. 403 */ 404 isInsidePlot(plotX, plotY, options = {}) { 405 const { inverted, plotBox, plotLeft, plotTop, scrollablePlotBox } = this, { scrollLeft = 0, scrollTop = 0 } = (options.visiblePlotOnly && 406 this.scrollablePlotArea?.scrollingContainer) || {}, series = options.series, box = (options.visiblePlotOnly && scrollablePlotBox) || plotBox, x = options.inverted ? plotY : plotX, y = options.inverted ? plotX : plotY, e = { 407 x, 408 y, 409 isInsidePlot: true, 410 options 411 }; 412 if (!options.ignoreX) { 413 const xAxis = (series && 414 (inverted && !this.polar ? series.yAxis : series.xAxis)) || { 415 pos: plotLeft, 416 len: Infinity 417 }; 418 const chartX = options.paneCoordinates ? 419 xAxis.pos + x : plotLeft + x; 420 if (!(chartX >= Math.max(scrollLeft + plotLeft, xAxis.pos) && 421 chartX <= Math.min(scrollLeft + plotLeft + box.width, xAxis.pos + xAxis.len))) { 422 e.isInsidePlot = false; 423 } 424 } 425 if (!options.ignoreY && e.isInsidePlot) { 426 const yAxis = (!inverted && options.axis && 427 !options.axis.isXAxis && options.axis) || (series && (inverted ? series.xAxis : series.yAxis)) || { 428 pos: plotTop, 429 len: Infinity 430 }; 431 const chartY = options.paneCoordinates ? 432 yAxis.pos + y : plotTop + y; 433 if (!(chartY >= Math.max(scrollTop + plotTop, yAxis.pos) && 434 chartY <= Math.min(scrollTop + plotTop + box.height, yAxis.pos + yAxis.len))) { 435 e.isInsidePlot = false; 436 } 437 } 438 fireEvent(this, 'afterIsInsidePlot', e); 439 return e.isInsidePlot; 440 } 441 /** 442 * Redraw the chart after changes have been done to the data, axis extremes 443 * chart size or chart elements. All methods for updating axes, series or 444 * points have a parameter for redrawing the chart. This is `true` by 445 * default. But in many cases you want to do more than one operation on the 446 * chart before redrawing, for example add a number of points. In those 447 * cases it is a waste of resources to redraw the chart for each new point 448 * added. So you add the points and call `chart.redraw()` after. 449 * 450 * @function Highcharts.Chart#redraw 451 * 452 * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] 453 * If or how to apply animation to the redraw. When `undefined`, it applies 454 * the animation that is set in the `chart.animation` option. 455 * 456 * @emits Highcharts.Chart#event:afterSetExtremes 457 * @emits Highcharts.Chart#event:beforeRedraw 458 * @emits Highcharts.Chart#event:predraw 459 * @emits Highcharts.Chart#event:redraw 460 * @emits Highcharts.Chart#event:render 461 * @emits Highcharts.Chart#event:updatedData 462 */ 463 redraw(animation) { 464 fireEvent(this, 'beforeRedraw'); 465 const chart = this, axes = chart.hasCartesianSeries ? chart.axes : chart.colorAxis || [], series = chart.series, pointer = chart.pointer, legend = chart.legend, legendUserOptions = chart.userOptions.legend, renderer = chart.renderer, isHiddenChart = renderer.isHidden(), afterRedraw = []; 466 let hasDirtyStacks, hasStackedSeries, i, isDirtyBox = chart.isDirtyBox, redrawLegend = chart.isDirtyLegend, serie; 467 renderer.rootFontSize = renderer.boxWrapper.getStyle('font-size'); 468 // Handle responsive rules, not only on resize (#6130) 469 if (chart.setResponsive) { 470 chart.setResponsive(false); 471 } 472 // Set the global animation. When chart.hasRendered is not true, the 473 // redraw call comes from a responsive rule and animation should not 474 // occur. 475 setAnimation(chart.hasRendered ? animation : false, chart); 476 if (isHiddenChart) { 477 chart.temporaryDisplay(); 478 } 479 // Adjust title layout (reflow multiline text) 480 chart.layOutTitles(false); 481 // Link stacked series 482 i = series.length; 483 while (i--) { 484 serie = series[i]; 485 if (serie.options.stacking || serie.options.centerInCategory) { 486 hasStackedSeries = true; 487 if (serie.isDirty) { 488 hasDirtyStacks = true; 489 break; 490 } 491 } 492 } 493 if (hasDirtyStacks) { // Mark others as dirty 494 i = series.length; 495 while (i--) { 496 serie = series[i]; 497 if (serie.options.stacking) { 498 serie.isDirty = true; 499 } 500 } 501 } 502 // Handle updated data in the series 503 series.forEach(function (serie) { 504 if (serie.isDirty) { 505 if (serie.options.legendType === 'point') { 506 if (typeof serie.updateTotals === 'function') { 507 serie.updateTotals(); 508 } 509 redrawLegend = true; 510 } 511 else if (legendUserOptions && 512 (!!legendUserOptions.labelFormatter || 513 legendUserOptions.labelFormat)) { 514 redrawLegend = true; // #2165 515 } 516 } 517 if (serie.isDirtyData) { 518 fireEvent(serie, 'updatedData'); 519 } 520 }); 521 // Handle added or removed series 522 if (redrawLegend && legend && legend.options.enabled) { 523 // Draw legend graphics 524 legend.render(); 525 chart.isDirtyLegend = false; 526 } 527 // Reset stacks 528 if (hasStackedSeries) { 529 chart.getStacks(); 530 } 531 // Set axes scales 532 axes.forEach(function (axis) { 533 axis.updateNames(); 534 axis.setScale(); 535 }); 536 chart.getMargins(); // #3098 537 // If one axis is dirty, all axes must be redrawn (#792, #2169) 538 axes.forEach(function (axis) { 539 if (axis.isDirty) { 540 isDirtyBox = true; 541 } 542 }); 543 // Redraw axes 544 axes.forEach(function (axis) { 545 // Fire 'afterSetExtremes' only if extremes are set 546 const key = axis.min + ',' + axis.max; 547 if (axis.extKey !== key) { // #821, #4452 548 axis.extKey = key; 549 // Prevent a recursive call to chart.redraw() (#1119) 550 afterRedraw.push(function () { 551 fireEvent(axis, 'afterSetExtremes', extend(axis.eventArgs, axis.getExtremes())); // #747, #751 552 delete axis.eventArgs; 553 }); 554 } 555 if (isDirtyBox || hasStackedSeries) { 556 axis.redraw(); 557 } 558 }); 559 // The plot areas size has changed 560 if (isDirtyBox) { 561 chart.drawChartBox(); 562 } 563 // Fire an event before redrawing series, used by the boost module to 564 // clear previous series renderings. 565 fireEvent(chart, 'predraw'); 566 // Redraw affected series 567 series.forEach(function (serie) { 568 if ((isDirtyBox || serie.isDirty) && serie.visible) { 569 serie.redraw(); 570 } 571 // Set it here, otherwise we will have unlimited 'updatedData' calls 572 // for a hidden series after setData(). Fixes #6012 573 serie.isDirtyData = false; 574 }); 575 // Move tooltip or reset 576 if (pointer) { 577 pointer.reset(true); 578 } 579 // Redraw if canvas 580 renderer.draw(); 581 // Fire the events 582 fireEvent(chart, 'redraw'); 583 fireEvent(chart, 'render'); 584 if (isHiddenChart) { 585 chart.temporaryDisplay(true); 586 } 587 // Fire callbacks that are put on hold until after the redraw 588 afterRedraw.forEach(function (callback) { 589 callback.call(); 590 }); 591 } 592 /** 593 * Get an axis, series or point object by `id` as given in the configuration 594 * options. Returns `undefined` if no item is found. 595 * 596 * @sample highcharts/plotoptions/series-id/ 597 * Get series by id 598 * 599 * @function Highcharts.Chart#get 600 * 601 * @param {string} id 602 * The id as given in the configuration options. 603 * 604 * @return {Highcharts.Axis|Highcharts.Series|Highcharts.Point|undefined} 605 * The retrieved item. 606 */ 607 get(id) { 608 const series = this.series; 609 /** 610 * @private 611 */ 612 function itemById(item) { 613 return (item.id === id || 614 (item.options && item.options.id === id)); 615 } 616 let ret = 617 // Search axes 618 find(this.axes, itemById) || 619 // Search series 620 find(this.series, itemById); 621 // Search points 622 for (let i = 0; !ret && i < series.length; i++) { 623 ret = find(series[i].points || [], itemById); 624 } 625 return ret; 626 } 627 /** 628 * Create the Axis instances based on the config options. 629 * 630 * @private 631 * @function Highcharts.Chart#createAxes 632 * @emits Highcharts.Chart#event:afterCreateAxes 633 * @emits Highcharts.Chart#event:createAxes 634 */ 635 createAxes() { 636 const options = this.userOptions; 637 fireEvent(this, 'createAxes'); 638 for (const coll of ['xAxis', 'yAxis']) { 639 const arr = options[coll] = splat(options[coll] || {}); 640 for (const axisOptions of arr) { 641 // eslint-disable-next-line no-new 642 new Axis(this, axisOptions, coll); 643 } 644 } 645 fireEvent(this, 'afterCreateAxes'); 646 } 647 /** 648 * Returns an array of all currently selected points in the chart. Points 649 * can be selected by clicking or programmatically by the 650 * {@link Highcharts.Point#select} 651 * function. 652 * 653 * @sample highcharts/plotoptions/series-allowpointselect-line/ 654 * Get selected points 655 * @sample highcharts/members/point-select-lasso/ 656 * Lasso selection
657 * @sample highcharts/chart/events-selection-points/ 658 * Rectangle selection 659 * 660 * @function Highcharts.Chart#getSelectedPoints 661 * 662 * @return {Array<Highcharts.Point>} 663 * The currently selected points. 664 */ 665 getSelectedPoints() { 666 return this.series.reduce((acc, series) => { 667 // For one-to-one points inspect series.data in order to retrieve 668 // points outside the visible range (#6445). For grouped data, 669 // inspect the generated series.points. 670 series.getPointsCollection() 671 .forEach((point) => { 672 if (pick(point.selectedStaging, point.selected)) { 673 acc.push(point); 674 } 675 }); 676 return acc; 677 }, []); 678 } 679 /** 680 * Returns an array of all currently selected series in the chart. Series 681 * can be selected either programmatically by the 682 * {@link Highcharts.Series#select} 683 * function or by checking the checkbox next to the legend item if 684 * [series.showCheckBox](https://api.highcharts.com/highcharts/plotOptions.series.showCheckbox) 685 * is true. 686 * 687 * @sample highcharts/members/chart-getselectedseries/ 688 * Get selected series 689 * 690 * @function Highcharts.Chart#getSelectedSeries 691 * 692 * @return {Array<Highcharts.Series>} 693 * The currently selected series. 694 */ 695 getSelectedSeries() { 696 return this.series.filter((s) => s.selected); 697 } 698 /** 699 * Set a new title or subtitle for the chart. 700 * 701 * @sample highcharts/members/chart-settitle/ 702 * Set title text and styles 703 * 704 * @function Highcharts.Chart#setTitle 705 * 706 * @param {Highcharts.TitleOptions} [titleOptions] 707 * New title options. The title text itself is set by the 708 * `titleOptions.text` property. 709 * 710 * @param {Highcharts.SubtitleOptions} [subtitleOptions] 711 * New subtitle options. The subtitle text itself is set by the 712 * `subtitleOptions.text` property. 713 * 714 * @param {boolean} [redraw] 715 * Whether to redraw the chart or wait for a later call to 716 * `chart.redraw()`. 717 */ 718 setTitle(titleOptions, subtitleOptions, redraw) { 719 this.applyDescription('title', titleOptions); 720 this.applyDescription('subtitle', subtitleOptions); 721 // The initial call also adds the caption. On update, chart.update will 722 // relay to Chart.setCaption. 723 this.applyDescription('caption', void 0); 724 this.layOutTitles(redraw); 725 } 726 /** 727 * Apply a title, subtitle or caption for the chart 728 * 729 * @private 730 * @function Highcharts.Chart#applyDescription 731 * @param key {string} 732 * Either title, subtitle or caption 733 * @param {Highcharts.TitleOptions|Highcharts.SubtitleOptions|Highcharts.CaptionOptions|undefined} explicitOptions 734 * The options to set, will be merged with default options. 735 */ 736 applyDescription(key, explicitOptions) { 737 const chart = this; 738 // Merge default options with explicit options 739 const options = this.options[key] = merge(this.options[key], explicitOptions); 740 let elem = this[key]; 741 if (elem && explicitOptions) { 742 this[key] = elem = elem.destroy(); // Remove old 743 } 744 if (options && !elem) { 745 elem = this.renderer.text(options.text, 0, 0, options.useHTML) 746 .attr({ 747 align: options.align, 748 'class': 'highcharts-' + key, 749 zIndex: options.zIndex || 4 750 }) 751 .css({ 752 textOverflow: 'ellipsis', 753 whiteSpace: 'nowrap' 754 }) 755 .add(); 756 // Update methods, relay to `applyDescription` 757 elem.update = function (updateOptions, redraw) { 758 chart.applyDescription(key, updateOptions); 759 chart.layOutTitles(redraw); 760 }; 761 // Presentational 762 if (!this.styledMode) { 763 elem.css(extend(key === 'title' ? { 764 // #2944 765 fontSize: this.options.isStock ? '1em' : '1.2em' 766 } : {}, options.style)); 767 } 768 // Get unwrapped text length and reset 769 elem.textPxLength = elem.getBBox().width; 770 elem.css({ whiteSpace: options.style?.whiteSpace }); 771 /** 772 * The chart title. The title has an `update` method that allows 773 * modifying the options directly or indirectly via 774 * `chart.update`. 775 * 776 * @sample highcharts/members/title-update/ 777 * Updating titles 778 * 779 * @name Highcharts.Chart#title
vendor: 4,865 bytes, lines 780-876
780 * @type {Highcharts.TitleObject} 781 */ 782 /** 783 * The chart subtitle. The subtitle has an `update` method that 784 * allows modifying the options directly or indirectly via 785 * `chart.update`. 786 * 787 * @name Highcharts.Chart#subtitle 788 * @type {Highcharts.SubtitleObject} 789 */ 790 this[key] = elem; 791 } 792 } 793 /** 794 * Internal function to lay out the chart title, subtitle and caption, and 795 * cache the full offset height for use in `getMargins`. The result is 796 * stored in `this.titleOffset`. 797 * 798 * @private 799 * @function Highcharts.Chart#layOutTitles 800 * 801 * @param {boolean} [redraw=true] 802 * @emits Highcharts.Chart#event:afterLayOutTitles 803 */ 804 layOutTitles(redraw = true) { 805 const titleOffset = [0, 0, 0], { options, renderer, spacingBox } = this; 806 // Lay out the title, subtitle and caption respectively 807 ['title', 'subtitle', 'caption'].forEach((key) => { 808 const desc = this[key], descOptions = this.options[key], alignTo = merge(spacingBox), textPxLength = desc?.textPxLength || 0; 809 if (desc && descOptions) { 810 // Provide a hook for the exporting button to shift the title 811 fireEvent(this, 'layOutTitle', { alignTo, key, textPxLength }); 812 const fontMetrics = renderer.fontMetrics(desc), baseline = fontMetrics.b, lineHeight = fontMetrics.h, verticalAlign = descOptions.verticalAlign || 'top', topAligned = verticalAlign === 'top', 813 // Use minScale only for top-aligned titles. It is not 814 // likely that we will need scaling for other positions, but 815 // if it is requested, we need to adjust the vertical 816 // position to the scale. 817 minScale = topAligned && descOptions.minScale || 1, offset = key === 'title' ? 818 topAligned ? -3 : 0 : 819 // Floating subtitle (#6574) 820 topAligned ? titleOffset[0] + 2 : 0, uncappedScale = Math.min(alignTo.width / textPxLength, 1), scale = Math.max(minScale, uncappedScale), alignAttr = merge({ 821 y: verticalAlign === 'bottom' ? 822 baseline : 823 offset + baseline 824 }, { 825 align: key === 'title' ? 826 // Title defaults to center for short titles, 827 // left for word-wrapped titles 828 (uncappedScale < minScale ? 'left' : 'center') : 829 // Subtitle defaults to the title.align 830 this.title?.alignValue 831 }, descOptions), width = descOptions.width || ((uncappedScale > minScale ? 832 // One line 833 this.chartWidth : 834 // Allow word wrap 835 alignTo.width) / scale); 836 // No animation when switching alignment 837 if (desc.alignValue !== alignAttr.align) { 838 desc.placed = false; 839 } 840 // Set the width and read the height 841 const height = Math.round(desc 842 .css({ width: `${width}px` }) 843 // Skip the cache for HTML (#3481, #11666) 844 .getBBox(descOptions.useHTML).height); 845 alignAttr.height = height; 846 // Perform scaling and alignment 847 desc 848 .align(alignAttr, false, alignTo) 849 .attr({ 850 align: alignAttr.align, 851 scaleX: scale, 852 scaleY: scale, 853 'transform-origin': `${alignTo.x + 854 textPxLength * 855 scale * 856 getAlignFactor(alignAttr.align)} ${lineHeight}` 857 }); 858 // Adjust the rendered title offset 859 if (!descOptions.floating) { 860 const offset = height * ( 861 // When scaling down the title, preserve the offset as 862 // long as it's only one line, but scale down the offset 863 // if the title wraps to multiple lines. 864 height < lineHeight * 1.2 ? 1 : scale); 865 if (verticalAlign === 'top') { 866 titleOffset[0] = Math.ceil(titleOffset[0] + offset); 867 } 868 else if (verticalAlign === 'bottom') { 869 titleOffset[2] = Math.ceil(titleOffset[2] + offset); 870 } 871 } 872 } 873 }, this); 874 // Handle title.margin and caption.margin 875 if (titleOffset[0] && 876 (options.title?.verticalAlign || 'top') === 'top') {
vendor: 7,946 bytes, lines 877-1068
877 titleOffset[0] += options.title?.margin || 0; 878 } 879 if (titleOffset[2] && 880 options.caption?.verticalAlign === 'bottom') { 881 titleOffset[2] += options.caption?.margin || 0; 882 } 883 const requiresDirtyBox = (!this.titleOffset || 884 this.titleOffset.join(',') !== titleOffset.join(',')); 885 // Used in getMargins 886 this.titleOffset = titleOffset; 887 fireEvent(this, 'afterLayOutTitles'); 888 if (!this.isDirtyBox && requiresDirtyBox) { 889 this.isDirtyBox = this.isDirtyLegend = requiresDirtyBox; 890 // Redraw if necessary (#2719, #2744) 891 if (this.hasRendered && redraw && this.isDirtyBox) { 892 this.redraw(); 893 } 894 } 895 } 896 /** 897 * Internal function to get the available size of the container element 898 * 899 * @private 900 * @function Highcharts.Chart#getContainerBox 901 */ 902 getContainerBox() { 903 // Temporarily hide support divs from a11y and others, #21888 904 const nonContainers = [].map.call(this.renderTo.children, (child) => { 905 if (child !== this.container) { 906 const display = child.style.display; 907 child.style.display = 'none'; 908 return [child, display]; 909 } 910 }), box = { 911 width: getStyle(this.renderTo, 'width', true) || 0, 912 height: (getStyle(this.renderTo, 'height', true) || 0) 913 }; 914 // Restore the non-containers 915 nonContainers.filter(Boolean).forEach(([div, display]) => { 916 div.style.display = display; 917 }); 918 return box; 919 } 920 /** 921 * Internal function to get the chart width and height according to options 922 * and container size. Sets {@link Chart.chartWidth} and 923 * {@link Chart.chartHeight}. 924 * 925 * @private 926 * @function Highcharts.Chart#getChartSize 927 */ 928 getChartSize() { 929 const chart = this, optionsChart = chart.options.chart, widthOption = optionsChart.width, heightOption = optionsChart.height, containerBox = chart.getContainerBox(), enableDefaultHeight = containerBox.height <= 1 || 930 ( // #21510, prevent infinite reflow 931 !chart.renderTo.parentElement?.style.height && 932 chart.renderTo.style.height === '100%'); 933 /** 934 * The current pixel width of the chart. 935 * 936 * @name Highcharts.Chart#chartWidth 937 * @type {number} 938 */ 939 chart.chartWidth = Math.max(// #1393 940 0, widthOption || containerBox.width || 600 // #1460 941 ); 942 /** 943 * The current pixel height of the chart. 944 * 945 * @name Highcharts.Chart#chartHeight 946 * @type {number} 947 */ 948 chart.chartHeight = Math.max(0, relativeLength(heightOption, chart.chartWidth) || 949 (enableDefaultHeight ? 400 : containerBox.height)); 950 chart.containerBox = containerBox; 951 } 952 /** 953 * If the renderTo element has no offsetWidth, most likely one or more of 954 * its parents are hidden. Loop up the DOM tree to temporarily display the 955 * parents, then save the original display properties, and when the true 956 * size is retrieved, reset them. Used on first render and on redraws. 957 * 958 * @private 959 * @function Highcharts.Chart#temporaryDisplay 960 * 961 * @param {boolean} [revert] 962 * Revert to the saved original styles. 963 */ 964 temporaryDisplay(revert) { 965 let node = this.renderTo, tempStyle; 966 if (!revert) { 967 while (node && node.style) { 968 // When rendering to a detached node, it needs to be temporarily 969 // attached in order to read styling and bounding boxes (#5783, 970 // #7024). 971 if (!doc.body.contains(node) && !node.parentNode) { 972 node.hcOrigDetached = true; 973 doc.body.appendChild(node); 974 } 975 if (getStyle(node, 'display', false) === 'none' || 976 node.hcOricDetached) { 977 node.hcOrigStyle = { 978 display: node.style.display, 979 height: node.style.height, 980 overflow: node.style.overflow 981 }; 982 tempStyle = { 983 display: 'block', 984 overflow: 'hidden' 985 }; 986 if (node !== this.renderTo) { 987 tempStyle.height = 0; 988 } 989 css(node, tempStyle); 990 // If it still doesn't have an offset width after setting 991 // display to block, it probably has an !important priority 992 // #2631, 6803 993 if (!node.offsetWidth) { 994 node.style.setProperty('display', 'block', 'important'); 995 } 996 } 997 node = node.parentNode; 998 if (node === doc.body) { 999 break; 1000 } 1001 } 1002 } 1003 else { 1004 while (node && node.style) { 1005 if (node.hcOrigStyle) { 1006 css(node, node.hcOrigStyle); 1007 delete node.hcOrigStyle; 1008 } 1009 if (node.hcOrigDetached) { 1010 doc.body.removeChild(node); 1011 node.hcOrigDetached = false; 1012 } 1013 node = node.parentNode; 1014 } 1015 } 1016 } 1017 /** 1018 * Set the {@link Chart.container|chart container's} class name, in 1019 * addition to `highcharts-container`. 1020 * 1021 * @function Highcharts.Chart#setClassName 1022 * 1023 * @param {string} [className] 1024 * The additional class name. 1025 */ 1026 setClassName(className) { 1027 this.container.className = 'highcharts-container ' + (className || ''); 1028 } 1029 /** 1030 * Get the containing element, determine the size and create the inner 1031 * container div to hold the chart. 1032 * 1033 * @private 1034 * @function Highcharts.Chart#afterGetContainer 1035 * @emits Highcharts.Chart#event:afterGetContainer 1036 */ 1037 getContainer() { 1038 const chart = this, options = chart.options, optionsChart = options.chart, indexAttrName = 'data-highcharts-chart', containerId = uniqueKey(), renderTo = chart.renderTo; 1039 let containerStyle; 1040 // If the container already holds a chart, destroy it. The check for 1041 // hasRendered is there because web pages that are saved to disk from 1042 // the browser, will preserve the data-highcharts-chart attribute and 1043 // the SVG contents, but not an interactive chart. So in this case, 1044 // charts[oldChartIndex] will point to the wrong chart if any (#2609). 1045 const oldChartIndex = pInt(attr(renderTo, indexAttrName)); 1046 if (isNumber(oldChartIndex) && 1047 charts[oldChartIndex] && 1048 charts[oldChartIndex].hasRendered) { 1049 charts[oldChartIndex].destroy(); 1050 } 1051 // Make a reference to the chart from the div 1052 attr(renderTo, indexAttrName, chart.index); 1053 // Remove previous chart 1054 renderTo.innerHTML = AST.emptyHTML; 1055 // If the container doesn't have an offsetWidth, it has or is a child of 1056 // a node that has display:none. We need to temporarily move it out to a 1057 // visible state to determine the size, else the legend and tooltips 1058 // won't render properly. The skipClone option is used in sparklines as 1059 // a micro optimization, saving about 1-2 ms each chart. 1060 if (!optionsChart.skipClone && !renderTo.offsetWidth) { 1061 chart.temporaryDisplay(); 1062 } 1063 // Get the width and height 1064 chart.getChartSize(); 1065 const chartHeight = chart.chartHeight; 1066 let chartWidth = chart.chartWidth; 1067 // Allow table cells and flex-boxes to shrink without the chart blocking 1068 //
vendor: 1,597 bytes, lines 1068-1107
1068 them out (#6427) 1069 css(renderTo, { overflow: 'hidden' }); 1070 // Create the inner container 1071 if (!chart.styledMode) { 1072 containerStyle = extend({ 1073 position: 'relative', 1074 // Needed for context menu (avoidscrollbars) and content 1075 // overflow in IE 1076 overflow: 'hidden', 1077 width: chartWidth + 'px', 1078 height: chartHeight + 'px', 1079 textAlign: 'left', 1080 lineHeight: 'normal', // #427 1081 zIndex: 0, // #1072 1082 '-webkit-tap-highlight-color': 'rgba(0,0,0,0)', 1083 userSelect: 'none', // #13503 1084 'touch-action': 'manipulation', 1085 outline: 'none', 1086 padding: '0px' 1087 }, optionsChart.style || {}); 1088 } 1089 /** 1090 * The containing HTML element of the chart. The container is 1091 * dynamically inserted into the element given as the `renderTo` 1092 * parameter in the {@link Highcharts#chart} constructor. 1093 * 1094 * @name Highcharts.Chart#container 1095 * @type {Highcharts.HTMLDOMElement} 1096 */ 1097 const container = createElement('div', { 1098 id: containerId 1099 }, containerStyle, renderTo); 1100 chart.container = container; 1101 // Adjust width if setting height affected it (#20334) 1102 chart.getChartSize(); 1103 if (chartWidth !== chart.chartWidth) { 1104 chartWidth = chart.chartWidth; 1105 if (!chart.styledMode) { 1106 css(container, { 1107 width:
1107pick(optionsChart.style?.width, chartWidth + 'px') 1108 }); 1109 } 1110 } 1111 chart.containerBox = chart.getContainerBox(); 1112 // Cache the cursor (#1650) 1113 chart._cursor = container.style.cursor; 1114 // Initialize the renderer 1115 const Renderer = optionsChart.renderer || !svg ? 1116 RendererRegistry.getRendererType(optionsChart.renderer) : 1117 SVGRenderer; 1118 /** 1119 * The renderer instance of the chart. Each chart instance has only one 1120 * associated renderer. 1121 * 1122 * @name Highcharts.Chart#renderer 1123 * @type {Highcharts.SVGRenderer} 1124 */ 1125 chart.renderer = new Renderer(container, chartWidth, chartHeight, void 0, optionsChart.forExport, options.exporting && options.exporting.allowHTML, chart.styledMode); 1126 // Set the initial animation from the options 1127 setAnimation(void 0, chart); 1128 chart.setClassName(optionsChart.className); 1129 if (!chart.styledMode) { 1130 chart.renderer.setStyle(optionsChart.style); 1131 } 1132 else { 1133 // Initialize definitions 1134 for (const key in options.defs) { // eslint-disable-line guard-for-in 1135 this.renderer.definition(options.defs[key]); 1136 } 1137 } 1138 // Add a reference to the charts index 1139 chart.renderer.chartIndex = chart.index; 1140 fireEvent(this, 'afterGetContainer'); 1141 } 1142 /** 1143 * Calculate margins by rendering axis labels in a preliminary position. 1144 * Title, subtitle and legend have already been rendered at this stage, but 1145 * will be moved into their final positions. 1146 * 1147 * @private 1148 * @function Highcharts.Chart#getMargins 1149 * @emits Highcharts.Chart#event:getMargins 1150 */ 1151 getMargins(skipAxes) { 1152 const { spacing, margin, titleOffset } = this; 1153 this.resetMargins(); 1154 // Adjust for title and subtitle 1155 if (titleOffset[0] && !defined(margin[0])) { 1156 this.plotTop = Math.max(this.plotTop, titleOffset[0] + spacing[0]); 1157 } 1158 if (titleOffset[2] && !defined(margin[2])) { 1159 this.marginBottom = Math.max(this.marginBottom, titleOffset[2] + spacing[2]); 1160 } 1161 // Adjust for legend 1162 if (this.legend && this.legend.display) { 1163 this.legend.adjustMargins(margin, spacing); 1164 } 1165 fireEvent(this, 'getMargins'); 1166 if (!skipAxes) { 1167 this.getAxisMargins(); 1168 } 1169 } 1170 /** 1171 * @private 1172 * @function Highcharts.Chart#getAxisMargins 1173 */ 1174 getAxisMargins() { 1175 const chart = this, 1176 // [top, right, bottom, left] 1177 axisOffset = chart.axisOffset = [0, 0, 0, 0], colorAxis = chart.colorAxis, margin = chart.margin, getOffset = function (axes) { 1178 axes.forEach(function (axis) { 1179 if (axis.visible) { 1180 axis.getOffset(); 1181 } 1182 }); 1183 }; 1184 // Pre-render axes to get labels offset width 1185 if (chart.hasCartesianSeries) { 1186 getOffset(chart.axes); 1187 } 1188 else if (colorAxis && colorAxis.length) { 1189 getOffset(colorAxis); 1190 } 1191 // Add the axis offsets 1192 marginNames.forEach(function (m, side) { 1193 if (!defined(margin[side])) { 1194 chart[m] += axisOffset[side]; 1195 } 1196 }); 1197 chart.setChartSize(); 1198 } 1199 /** 1200 * Return the current options of the chart, but only those that differ from 1201 * default options. Items that can be either an object or an array of 1202 * objects, like `series`, `xAxis` and `yAxis`, are always returned as 1203 * array. 1204 * 1205 * @sample highcharts/members/chart-getoptions 1206 * 1207 * @function Highcharts.Chart#getOptions 1208 * 1209 * @since 11.1.0 1210 */ 1211 getOptions() { 1212 return diffObjects(this.userOptions, defaultOptions); 1213 } 1214 /** 1215 * Reflows the chart to its container. By default, the Resize Observer is 1216 * attached to the chart's div which allows to reflows the chart 1217 * automatically to its container, as per the 1218 * [chart.reflow](https://api.highcharts.com/highcharts/chart.reflow) 1219 * option. 1220 * 1221 * @sample highcharts/chart/events-container/ 1222 * Pop up and reflow 1223 * 1224 * @function Highcharts.Chart#reflow 1225 * 1226 * @param {global.Event} [e] 1227 * Event arguments. Used primarily when the function is called 1228 * internally as a response to window resize. 1229 */ 1230 reflow(e) { 1231 const chart = this, oldBox = chart.containerBox, containerBox = chart.getContainerBox(); 1232 delete chart.pointer?.chartPosition;
1233 // Width and height checks for display:none. Target is doc in Opera 1234 // and win in Firefox, Chrome and IE9. 1235 if (!chart.isPrinting && 1236 !chart.isResizing && 1237 oldBox && 1238 // When fired by resize observer inside hidden container 1239 containerBox.width) { 1240 if (containerBox.width !== oldBox.width || 1241 containerBox.height !== oldBox.height) { 1242 U.clearTimeout(chart.reflowTimeout); 1243 // When called from window.resize, e is set, else it's called 1244 // directly (#2224) 1245 chart.reflowTimeout = syncTimeout(function () { 1246 // Set size, it may have been destroyed in the meantime 1247 // (#1257) 1248 if (chart.container) { 1249 chart.setSize(void 0, void 0, false); 1250 } 1251 }, e ? 100 : 0); 1252 } 1253 chart.containerBox = containerBox; 1254 } 1255 } 1256 /** 1257 * Toggle the event handlers necessary for auto resizing, depending on the 1258 * `chart.reflow` option. 1259 * 1260 * @private 1261 * @function Highcharts.Chart#setReflow 1262 */ 1263 setReflow() { 1264 const chart = this; 1265 const runReflow = (e) => { 1266 if (chart.options?.chart.reflow && chart.hasLoaded) { 1267 chart.reflow(e); 1268 } 1269 }; 1270 if (typeof ResizeObserver === 'function') { 1271 (new ResizeObserver(runReflow)).observe(chart.renderTo); 1272 // Fallback for more legacy browser versions. 1273 } 1274 else { 1275 const unbind = addEvent(win, 'resize', runReflow); 1276 addEvent(this, 'destroy', unbind); 1277 } 1278 } 1279 /** 1280 * Resize the chart to a given width and height. In order to set the width 1281 * only, the height argument may be skipped. To set the height only, pass 1282 * `undefined` for the width. 1283 * 1284 * @sample highcharts/members/chart-setsize-button/ 1285 * Test resizing from buttons 1286 * @sample highcharts/members/chart-setsize-jquery-resizable/ 1287 * Add a jQuery UI resizable 1288 * @sample stock/members/chart-setsize/ 1289 * Highcharts Stock with UI resizable 1290 * 1291 * @function Highcharts.Chart#setSize 1292 * 1293 * @param {number|null} [width] 1294 * The new pixel width of the chart. Since v4.2.6, the argument can 1295 * be `undefined` in order to preserve the current value (when 1296 * setting height only), or `null` to adapt to the width of the 1297 * containing element. 1298 * 1299 * @param {number|null} [height] 1300 * The new pixel height of the chart. Since v4.2.6, the argument can 1301 * be `undefined` in order to preserve the current value, or `null` 1302 * in order to adapt to the height of the containing element. 1303 * 1304 * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] 1305 * Whether and how to apply animation. When `undefined`, it applies 1306 * the animation that is set in the `chart.animation` option. 1307 * 1308 * 1309 * @emits Highcharts.Chart#event:endResize 1310 * @emits Highcharts.Chart#event:resize 1311 */ 1312 setSize(width, height, animation) { 1313 const chart = this, renderer = chart.renderer; 1314 // Handle the isResizing counter 1315 chart.isResizing += 1; 1316 // Set the animation for the current process 1317 setAnimation(animation, chart); 1318 const globalAnimation = renderer.globalAnimation; 1319 chart.oldChartHeight = chart.chartHeight; 1320 chart.oldChartWidth = chart.chartWidth; 1321 if (typeof width !== 'undefined') { 1322 chart.options.chart.width = width; 1323 } 1324 if (typeof height !== 'undefined') { 1325 chart.options.chart.height = height; 1326 } 1327 chart.getChartSize();
vendor: 4,315 bytes, lines 1328-1430
1328 const { chartWidth, chartHeight, scrollablePixelsX = 0, scrollablePixelsY = 0 } = chart; 1329 // Avoid expensive redrawing if the computed size didn't change 1330 if (chart.isDirtyBox || 1331 chartWidth !== chart.oldChartWidth || 1332 chartHeight !== chart.oldChartHeight) { 1333 // Resize the container with the global animation applied if enabled 1334 // (#2503) 1335 if (!chart.styledMode) { 1336 (globalAnimation ? animate : css)(chart.container, { 1337 width: `${chartWidth + scrollablePixelsX}px`, 1338 height: `${chartHeight + scrollablePixelsY}px` 1339 }, globalAnimation); 1340 } 1341 chart.setChartSize(true); 1342 renderer.setSize(chartWidth, chartHeight, globalAnimation); 1343 // Handle axes 1344 chart.axes.forEach(function (axis) { 1345 axis.isDirty = true; 1346 axis.setScale(); 1347 }); 1348 chart.isDirtyLegend = true; // Force legend redraw 1349 chart.isDirtyBox = true; // Force redraw of plot and chart border 1350 chart.layOutTitles(); // #2857 1351 chart.getMargins(); 1352 chart.redraw(globalAnimation); 1353 chart.oldChartHeight = void 0; 1354 fireEvent(chart, 'resize'); 1355 // Fire endResize and set isResizing back. If animation is disabled, 1356 // fire without delay, but in a new thread to avoid triggering the 1357 // resize observer (#19027). 1358 setTimeout(() => { 1359 if (chart) { 1360 fireEvent(chart, 'endResize'); 1361 } 1362 }, animObject(globalAnimation).duration); 1363 } 1364 // Handle resizing counter even if we've re-rendered or not (#20548). 1365 chart.isResizing -= 1; 1366 } 1367 /** 1368 * Set the public chart properties. This is done before and after the 1369 * pre-render to determine margin sizes. 1370 * 1371 * @private 1372 * @function Highcharts.Chart#setChartSize 1373 * @emits Highcharts.Chart#event:afterSetChartSize 1374 */ 1375 setChartSize(skipAxes) { 1376 const chart = this, { chartHeight, chartWidth, inverted, spacing, renderer } = chart, clipOffset = chart.clipOffset, clipRoundFunc = Math[inverted ? 'floor' : 'round']; 1377 let plotLeft, plotTop, plotWidth, plotHeight; 1378 /** 1379 * The current left position of the plot area in pixels. 1380 * 1381 * @name Highcharts.Chart#plotLeft 1382 * @type {number} 1383 */ 1384 chart.plotLeft = plotLeft = Math.round(chart.plotLeft); 1385 /** 1386 * The current top position of the plot area in pixels. 1387 * 1388 * @name Highcharts.Chart#plotTop 1389 * @type {number} 1390 */ 1391 chart.plotTop = plotTop = Math.round(chart.plotTop); 1392 /** 1393 * The current width of the plot area in pixels. 1394 * 1395 * @name Highcharts.Chart#plotWidth 1396 * @type {number} 1397 */ 1398 chart.plotWidth = plotWidth = Math.max(0, Math.round(chartWidth - plotLeft - chart.marginRight)); 1399 /** 1400 * The current height of the plot area in pixels. 1401 * 1402 * @name Highcharts.Chart#plotHeight 1403 * @type {number} 1404 */ 1405 chart.plotHeight = plotHeight = Math.max(0, Math.round(chartHeight - plotTop - chart.marginBottom)); 1406 chart.plotSizeX = inverted ? plotHeight : plotWidth; 1407 chart.plotSizeY = inverted ? plotWidth : plotHeight; 1408 // Set boxes used for alignment 1409 chart.spacingBox = renderer.spacingBox = { 1410 x: spacing[3], 1411 y: spacing[0], 1412 width: chartWidth - spacing[3] - spacing[1], 1413 height: chartHeight - spacing[0] - spacing[2] 1414 }; 1415 chart.plotBox = renderer.plotBox = { 1416 x: plotLeft, 1417 y: plotTop, 1418 width: plotWidth, 1419 height: plotHeight 1420 }; 1421 // Compute the clipping box 1422 if (clipOffset) { 1423 chart.clipBox = { 1424 x: clipRoundFunc(clipOffset[3]), 1425 y: clipRoundFunc(clipOffset[0]), 1426 width: clipRoundFunc(chart.plotSizeX - clipOffset[1] - clipOffset[3]), 1427 height: clipRoundFunc(chart.plotSizeY - clipOffset[0] - clipOffset[2]) 1428 }; 1429 } 1430 if (!skipAxes) {
1431 chart.axes.forEach(function (axis) { 1432 axis.setAxisSize(); 1433 axis.setAxisTranslation(); 1434 }); 1435 renderer.alignElements(); 1436 } 1437 fireEvent(chart, 'afterSetChartSize', { skipAxes: skipAxes }); 1438 } 1439 /** 1440 * Initial margins before auto size margins are applied. 1441 * 1442 * @private 1443 * @function Highcharts.Chart#resetMargins 1444 */ 1445 resetMargins() { 1446 fireEvent(this, 'resetMargins'); 1447 const chart = this, chartOptions = chart.options.chart, plotBorderWidth = chartOptions.plotBorderWidth || 0, halfWidth = plotBorderWidth / 2; 1448 // Create margin and spacing array 1449 ['margin', 'spacing'].forEach(function splashArrays(target) { 1450 const value = chartOptions[target], values = isObject(value) ? value : [value, value, value, value]; 1451 [ 1452 'Top', 1453 'Right', 1454 'Bottom', 1455 'Left' 1456 ].forEach(function (sideName, side) { 1457 chart[target][side] = pick(chartOptions[target + sideName], values[side]); 1458 }); 1459 }); 1460 // Set margin names like chart.plotTop, chart.plotLeft, 1461 // chart.marginRight, chart.marginBottom. 1462 marginNames.forEach(function (m, side) { 1463 chart[m] = pick(chart.margin[side], chart.spacing[side]); 1464 }); 1465 chart.axisOffset = [0, 0, 0, 0]; // Top, right, bottom, left 1466 chart.clipOffset = [ 1467 halfWidth, 1468 halfWidth, 1469 halfWidth, 1470 halfWidth 1471 ]; 1472 chart.plotBorderWidth = plotBorderWidth; 1473 } 1474 /** 1475 * Internal function to draw or redraw the borders and backgrounds for chart 1476 * and plot area. 1477 * 1478 * @private 1479 * @function Highcharts.Chart#drawChartBox 1480 * @emits Highcharts.Chart#event:afterDrawChartBox 1481 */ 1482 drawChartBox() { 1483 const chart = this, optionsChart = chart.options.chart, renderer = chart.renderer, chartWidth = chart.chartWidth, chartHeight = chart.chartHeight, styledMode = chart.styledMode, plotBGImage = chart.plotBGImage, chartBackgroundColor = optionsChart.backgroundColor, plotBackgroun
vendor: 5,936 bytes, lines 1483-1635
1483dColor = optionsChart.plotBackgroundColor, plotBackgroundImage = optionsChart.plotBackgroundImage, plotLeft = chart.plotLeft, plotTop = chart.plotTop, plotWidth = chart.plotWidth, plotHeight = chart.plotHeight, plotBox = chart.plotBox, clipRect = chart.clipRect, clipBox = chart.clipBox; 1484 let chartBackground = chart.chartBackground, plotBackground = chart.plotBackground, plotBorder = chart.plotBorder, chartBorderWidth, mgn, bgAttr, verb = 'animate'; 1485 // Chart area 1486 if (!chartBackground) { 1487 chart.chartBackground = chartBackground = renderer.rect() 1488 .addClass('highcharts-background') 1489 .add(); 1490 verb = 'attr'; 1491 } 1492 if (!styledMode) { 1493 // Presentational 1494 chartBorderWidth = optionsChart.borderWidth || 0; 1495 mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0); 1496 bgAttr = { 1497 fill: chartBackgroundColor || 'none' 1498 }; 1499 if (chartBorderWidth || chartBackground['stroke-width']) { // #980 1500 bgAttr.stroke = optionsChart.borderColor; 1501 bgAttr['stroke-width'] = chartBorderWidth; 1502 } 1503 chartBackground 1504 .attr(bgAttr) 1505 .shadow(optionsChart.shadow); 1506 } 1507 else { 1508 chartBorderWidth = mgn = chartBackground.strokeWidth(); 1509 } 1510 chartBackground[verb]({ 1511 x: mgn / 2, 1512 y: mgn / 2, 1513 width: chartWidth - mgn - chartBorderWidth % 2, 1514 height: chartHeight - mgn - chartBorderWidth % 2, 1515 r: optionsChart.borderRadius 1516 }); 1517 // Plot background 1518 verb = 'animate'; 1519 if (!plotBackground) { 1520 verb = 'attr'; 1521 chart.plotBackground = plotBackground = renderer.rect() 1522 .addClass('highcharts-plot-background') 1523 .add(); 1524 } 1525 plotBackground[verb](plotBox); 1526 if (!styledMode) { 1527 // Presentational attributes for the background 1528 plotBackground 1529 .attr({ 1530 fill: plotBackgroundColor || 'none' 1531 }) 1532 .shadow(optionsChart.plotShadow); 1533 // Create the background image 1534 if (plotBackgroundImage) { 1535 if (!plotBGImage) { 1536 chart.plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight).add(); 1537 } 1538 else { 1539 if (plotBackgroundImage !== plotBGImage.attr('href')) { 1540 plotBGImage.attr('href', plotBackgroundImage); 1541 } 1542 plotBGImage.animate(plotBox); 1543 } 1544 } 1545 } 1546 // Plot clip 1547 if (!clipRect) { 1548 chart.clipRect = renderer.clipRect(clipBox); 1549 } 1550 else { 1551 clipRect.animate({ 1552 width: clipBox.width, 1553 height: clipBox.height 1554 }); 1555 } 1556 // Plot area border 1557 verb = 'animate'; 1558 if (!plotBorder) { 1559 verb = 'attr'; 1560 chart.plotBorder = plotBorder = renderer.rect() 1561 .addClass('highcharts-plot-border') 1562 .attr({ 1563 zIndex: 1 // Above the grid 1564 }) 1565 .add(); 1566 } 1567 if (!styledMode) { 1568 // Presentational 1569 plotBorder.attr({ 1570 stroke: optionsChart.plotBorderColor, 1571 'stroke-width': optionsChart.plotBorderWidth || 0, 1572 fill: 'none' 1573 }); 1574 } 1575 plotBorder[verb](plotBorder.crisp({ 1576 x: plotLeft, 1577 y: plotTop, 1578 width: plotWidth, 1579 height: plotHeight 1580 }, -plotBorder.strokeWidth())); // #3282 plotBorder should be negative; 1581 // reset 1582 chart.isDirtyBox = false; 1583 fireEvent(this, 'afterDrawChartBox'); 1584 } 1585 /** 1586 * Detect whether a certain chart property is needed based on inspecting its 1587 * options and series. This mainly applies to the chart.inverted property, 1588 * and in extensions to the chart.angular and chart.polar properties. 1589 * 1590 * @private 1591 * @function Highcharts.Chart#propFromSeries 1592 */ 1593 propFromSeries() { 1594 const chart = this, optionsChart = chart.options.chart, seriesOptions = chart.options.series; 1595 let i, klass, value; 1596 /** 1597 * The flag is set to `true` if a series of the chart is inverted. 1598 * 1599 * @name Highcharts.Chart#inverted 1600 * @type {boolean|undefined} 1601 */ 1602 ['inverted', 'angular', 'polar'].forEach(function (key) { 1603 // The default series type's class 1604 klass = seriesTypes[optionsChart.type]; 1605 // Get the value from available chart-wide properties 1606 value = 1607 // It is set in the options: 1608 optionsChart[key] || 1609 // The default series class: 1610 (klass && klass.prototype[key]); 1611 // Requires it 1612 // 4. Check if any the chart's series require it 1613 i = seriesOptions && seriesOptions.length; 1614 while (!value && i--) { 1615 klass = seriesTypes[seriesOptions[i].type]; 1616 if (klass && klass.prototype[key]) { 1617 value = true; 1618 } 1619 } 1620 // Set the chart property 1621 chart[key] = value; 1622 }); 1623 } 1624 /** 1625 * Internal function to link two or more series together, based on the 1626 * `linkedTo` option. This is done from `Chart.render`, and after 1627 * `Chart.addSeries` and `Series.remove`. 1628 * 1629 * @private 1630 * @function Highcharts.Chart#linkSeries 1631 * @emits Highcharts.Chart#event:afterLinkSeries 1632 */ 1633 linkSeries(isUpdating) { 1634 const chart = this, chartSeries = chart.series; 1635 // Reset links
1636 chartSeries.forEach(function (series) { 1637 series.linkedSeries.length = 0; 1638 }); 1639 // Apply new links 1640 chartSeries.forEach(function (series) { 1641 const { linkedTo } = series.options; 1642 if (isString(linkedTo)) { 1643 let linkedParent; 1644 if (linkedTo === ':previous') { 1645 linkedParent = chart.series[series.index - 1]; 1646 } 1647 else { 1648 linkedParent = chart.get(linkedTo); 1649 } 1650 // #3341 avoid mutual linking 1651 if (linkedParent && 1652 linkedParent.linkedParent !== series) { 1653 linkedParent.linkedSeries.push(series); 1654 /** 1655 * The parent series of the current series, if the current 1656 * series has a [linkedTo](https://api.highcharts.com/highcharts/series.line.linkedTo) 1657 * setting. 1658 * 1659 * @name Highcharts.Series#linkedParent 1660 * @type {Highcharts.Series} 1661 * @readonly 1662 */ 1663 series.linkedParent = linkedParent; 1664 if (linkedParent.enabledDataSorting) { 1665 series.setDataSortingOptions(); 1666 } 1667 series.visible = pick(series.options.visible, linkedParent.options.visible, series.visible); // #3879 1668 } 1669 } 1670 }); 1671 fireEvent(this, 'afterLinkSeries', { isUpdating }); 1672 } 1673 /** 1674 * Render series for the chart. 1675 * 1676 * @private 1677 * @function Highcharts.Chart#renderSeries 1678 */ 1679 renderSeries() { 1680 this.series.forEach(function (serie) { 1681 serie.translate(); 1682 serie.render(); 1683 }); 1684 } 1685 /** 1686 * Render all graphics for the chart. Runs internally on initialization. 1687 * 1688 * @private 1689 * @function Highcharts.Chart#render 1690 */ 1691 render() { 1692 const chart = this, axes = chart.axes, colorAxis = chart.colorAxis, renderer = chart.renderer, axisLayoutRuns = chart.options.chart.axisLayoutRuns || 2, renderAxes = (axes) => { 1693 axes.forEach((axis) => { 1694 if (axis.visible) { 1695 axis.render(); 1696 } 1697 }); 1698 }; 1699 let expectedSpace = 0, // Correction for X axis labels 1700 // If the plot area size has changed significantly, calculate tick 1701 // positions again 1702 redoHorizontal = true, redoVertical, run = 0; 1703 // Title 1704 chart.setTitle(); 1705 // Fire an event before the margins are computed. This is where the 1706 // legend is assigned. 1707 fireEvent(chart, 'beforeMargins'); 1708 // Get stacks 1709 chart.getStacks?.(); 1710 // Get chart margins 1711 chart.getMargins(true); 1712 chart.setChartSize(); 1713 for (const axis of axes) { 1714 const { options } = axis, { labels } = options; 1715 if (chart.hasCartesianSeries && // #20948 1716 axis.horiz && 1717 axis.visible && 1718 labels.enabled && 1719 axis.series.length && 1720 axis.coll !== 'colorAxis' && 1721 !chart.polar) { 1722 expectedSpace = options.tickLength; 1723 axis.createGroups(); 1724 // Calculate expected space based on dummy tick 1725 const mockTick = new Tick(axis, 0, '', true), label = mockTick.createLabel('x', labels); 1726 mockTick.destroy(); 1727 if (label && 1728 pick(labels.reserveSpace, !isNumber(options.crossing))) { 1729 expectedSpace = label.getBBox().height + 1730 labels.distance + 1731 Math.max(options.offset || 0, 0); 1732 } 1733 if (expectedSpace) { 1734 label?.destroy(); 1735 break; 1736 } 1737 } 1738 } 1739 // Use Math.max to prevent negative plotHeight 1740 chart.plotHeight = Math.max(chart.plotHeight - expectedSpace, 0); 1741 while ((redoHorizontal || redoVertical || axisLayoutRuns > 1) && 1742 run < axisLayoutRuns // #19794 1743 ) { 1744 const tempWidth = chart.plotWidth, tempHeight = chart.plotHeight; 1745 for (const axis of axes) { 1746 if (run === 0) { 1747 // Get margins by pre-rendering axes 1748 axis.setScale(); 1749 } 1750 else if ((axis.horiz && redoHorizontal) || 1751 (!axis.horiz && redoVertical)) { 1752 // Update to reflect the new margins 1753 axis.setTickInterval(true); 1754 } 1755 } 1756 if (run === 0) { 1757 chart.getAxisMargins(); 1758 } 1759 else { 1760 // Check again for new, rotated or moved labels 1761 chart.getMargins(); 1762 } 1763 redoHorizontal = (tempWidth / chart.plotWidth) > (run ? 1 : 1.1); 1764 redoVertical = (tempHeight / chart.plotHeight) > (run ? 1 : 1.05); 1765 run++; 1766 } 1767 // Draw the borders and backgrounds 1768 chart.drawChartBox(); 1769 // Axes
1770 if (chart.hasCartesianSeries) { 1771 renderAxes(axes); 1772 } 1773 else if (colorAxis && colorAxis.length) { 1774 renderAxes(colorAxis); 1775 } 1776 // The series 1777 if (!chart.seriesGroup) { 1778 chart.seriesGroup = renderer.g('series-group') 1779 .attr({ zIndex: 3 }) 1780 .shadow(chart.options.chart.seriesGroupShadow) 1781 .add(); 1782 } 1783 chart.renderSeries(); 1784 // Credits 1785 chart.addCredits(); 1786 // Handle responsiveness 1787 if (chart.setResponsive) { 1788 chart.setResponsive(); 1789 } 1790 // Set flag 1791 chart.hasRendered = true; 1792 } 1793 /** 1794 * Set a new credits label for the chart. 1795 * 1796 * @sample highcharts/credits/credits-update/ 1797 * Add and update credits 1798 * 1799 * @function Highcharts.Chart#addCredits 1800 * 1801 * @param {Highcharts.CreditsOptions} [credits] 1802 * A configuration object for the new credits. 1803 */ 1804 addCredits(credits) { 1805 const chart = this, creds = merge(true, this.options.credits, credits); 1806 if (creds.enabled && !this.credits) { 1807 /** 1808 * The chart's credits label. The label has an `update` method that 1809 * allows setting new options as per the 1810 * [credits options set](https://api.highcharts.com/highcharts/credits). 1811 * 1812 * @name Highcharts.Chart#credits 1813 * @type {Highcharts.SVGElement} 1814 */ 1815 this.credits = this.renderer.text(creds.text + (this.mapCredits || ''), 0, 0) 1816 .addClass('highcharts-credits') 1817 .on('click', function () { 1818 if (creds.href) { 1819 win.location.href = creds.href; 1820 } 1821 }) 1822 .attr({ 1823 align: creds.position.align, 1824 zIndex: 8 1825 }); 1826 if (!chart.styledMode) { 1827 this.credits.css(creds.style); 1828 } 1829 this.credits 1830 .add() 1831 .align(creds.position); 1832 // Dynamically update 1833 this.credits.update = function (options) { 1834 chart.credits = chart.credits.destroy(); 1835 chart.addCredits(options); 1836 }; 1837 } 1838 } 1839 /** 1840 * Remove the chart and purge memory. This method is called internally 1841 * before adding a second chart into the same container, as well as on 1842 * window unload to prevent leaks. 1843 * 1844 * @sample highcharts/members/chart-destroy/ 1845 * Destroy the chart from a button 1846 * @sample stock/members/chart-destroy/ 1847 * Destroy with Highcharts Stock 1848 * 1849 * @function Highcharts.Chart#destroy 1850 * 1851 * @emits Highcharts.Chart#event:destroy 1852 */ 1853 destroy() { 1854 const chart = this, axes = chart.axes, series = chart.series, container = chart.container, parentNode = container && container.parentNode; 1855 let i; 1856 // Fire the chart.destroy event 1857 fireEvent(chart, 'destroy'); 1858 // Delete the chart from charts lookup array 1859 if (chart.renderer.forExport) { 1860 erase(charts, chart); // #6569 1861 } 1862 else { 1863 charts[chart.index] = void 0; 1864 } 1865 H.chartCount--; 1866 chart.renderTo.removeAttribute('data-highcharts-chart'); 1867 // Remove events 1868 removeEvent(chart); 1869 // ==== Destroy collections: 1870 // Destroy axes 1871 i = axes.length; 1872 while (i--) { 1873 axes[i] = axes[i].destroy(); 1874 } 1875 // Destroy scroller & scroller series before destroying base series 1876 if (this.scroller && this.scroller.destroy) { 1877 this.scroller.destroy(); 1878 } 1879 // Destroy each series 1880 i = series.length; 1881 while (i--) { 1882 series[i] = series[i].destroy(); 1883 } 1884 // ==== Destroy chart properties: 1885 [ 1886 'title', 'subtitle', 'chartBackground', 'plotBackgroun
vendor: 4,274 bytes, lines 1886-1997
1886d', 1887 'plotBGImage', 'plotBorder', 'seriesGroup', 'clipRect', 'credits', 1888 'pointer', 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 1889 'renderer' 1890 ].forEach(function (name) { 1891 const prop = chart[name]; 1892 if (prop && prop.destroy) { 1893 chart[name] = prop.destroy(); 1894 } 1895 }); 1896 // Remove container and all SVG, check container as it can break in IE 1897 // when destroyed before finished loading 1898 if (container) { 1899 container.innerHTML = AST.emptyHTML; 1900 removeEvent(container); 1901 if (parentNode) { 1902 discardElement(container); 1903 } 1904 } 1905 // Clean it all up 1906 objectEach(chart, function (val, key) { 1907 delete chart[key]; 1908 }); 1909 } 1910 /** 1911 * Prepare for first rendering after all data are loaded. 1912 * 1913 * @private 1914 * @function Highcharts.Chart#firstRender 1915 * @emits Highcharts.Chart#event:beforeRender 1916 */ 1917 firstRender() { 1918 const chart = this, options = chart.options; 1919 // Create the container 1920 chart.getContainer(); 1921 chart.resetMargins(); 1922 chart.setChartSize(); 1923 // Set the common chart properties (mainly invert) from the given series 1924 chart.propFromSeries(); 1925 // Get axes 1926 chart.createAxes(); 1927 // Initialize the series 1928 const series = isArray(options.series) ? options.series : []; 1929 options.series = []; // Avoid mutation 1930 series.forEach( 1931 // #9680 1932 function (serieOptions) { 1933 chart.initSeries(serieOptions); 1934 }); 1935 chart.linkSeries(); 1936 chart.setSortedData(); 1937 // Run an event after axes and series are initialized, but before 1938 // render. At this stage, the series data is indexed and cached in the 1939 // xData and yData arrays, so we can access those before rendering. Used 1940 // in Highcharts Stock. 1941 fireEvent(chart, 'beforeRender'); 1942 chart.render(); 1943 chart.pointer?.getChartPosition(); // #14973 1944 // Fire the load event if there are no external images 1945 if (!chart.renderer.imgCount && !chart.hasLoaded) { 1946 chart.onload(); 1947 } 1948 // If the chart was rendered outside the top container, put it back in 1949 // (#3679) 1950 chart.temporaryDisplay(true); 1951 } 1952 /** 1953 * Internal function that runs on chart load, async if any images are loaded 1954 * in the chart. Runs the callbacks and triggers the `load` and `render` 1955 * events. 1956 * 1957 * @private 1958 * @function Highcharts.Chart#onload 1959 * @emits Highcharts.Chart#event:load 1960 * @emits Highcharts.Chart#event:render 1961 */ 1962 onload() { 1963 // Run callbacks, first the ones registered by modules, then user's one 1964 this.callbacks.concat([this.callback]).forEach(function (fn) { 1965 // Chart destroyed in its own callback (#3600) 1966 if (fn && typeof this.index !== 'undefined') { 1967 fn.apply(this, [this]); 1968 } 1969 }, this); 1970 fireEvent(this, 'load'); 1971 fireEvent(this, 'render'); 1972 // Set up auto resize, check for not destroyed (#6068) 1973 if (defined(this.index)) { 1974 this.setReflow(); 1975 } 1976 this.warnIfA11yModuleNotLoaded(); 1977 // Don't run again 1978 this.hasLoaded = true; 1979 } 1980 /** 1981 * Emit console warning if the a11y module is not loaded. 1982 * @private 1983 */ 1984 warnIfA11yModuleNotLoaded() { 1985 const { options, title } = this; 1986 if (options && !this.accessibility) { 1987 // Make chart behave as an image with the title as alt text 1988 this.renderer.boxWrapper.attr({ 1989 role: 'img', 1990 'aria-label': ((title && title.element.textContent) || '' 1991 // #17753, < is not allowed in SVG attributes 1992 ).replace(/</g, '<') 1993 }); 1994 if (!(options.accessibility && options.accessibility.enabled === false)) { 1995 error('Highcharts warning: Consider including the ' + 1996 '"accessibility.js" module to make your chart more ' + 1997 'usable for people with disabilities. Set the ' +
1998 '"accessibility.enabled" option to false to remove this ' + 1999 'warning. See https://www.highcharts.com/docs/accessibility/accessibility-module.', false, this); 2000 } 2001 } 2002 } 2003 /** 2004 * Add a series to the chart after render time. Note that this method should 2005 * never be used when adding data synchronously at chart render time, as it 2006 * adds expense to the calculations and rendering. When adding data at the 2007 * same time as the chart is initialized, add the series as a configuration 2008 * option instead. With multiple axes, the `offset` is dynamically adjusted. 2009 * 2010 * @sample highcharts/members/chart-addseries/ 2011 * Add a series from a button 2012 * @sample stock/members/chart-addseries/ 2013 * Add a series in Highcharts Stock 2014 * 2015 * @function Highcharts.Chart#addSeries 2016 * 2017 * @param {Highcharts.SeriesOptionsType} options 2018 * The config options for the series. 2019 * 2020 * @param {boolean} [redraw=true] 2021 * Whether to redraw the chart after adding. 2022 * 2023 * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] 2024 * Whether to apply animation, and optionally animation 2025 * configuration. When `undefined`, it applies the animation that is 2026 * set in the `chart.animation` option. 2027 * 2028 * @return {Highcharts.Series} 2029 * The newly created series object. 2030 * 2031 * @emits Highcharts.Chart#event:addSeries 2032 * @emits Highcharts.Chart#event:afterAddSeries 2033 */ 2034 addSeries(options, redraw, animation) { 2035 const chart = this; 2036 let series; 2037 if (options) { // <- not necessary 2038 redraw = pick(redraw, true); // Defaults to true 2039 fireEvent(chart, 'addSeries', { options: options }, function () { 2040 series = chart.initSeries(options); 2041 chart.isDirtyLegend = true; 2042 chart.linkSeries(); 2043 if (series.enabledDataSorting) { 2044 // We need to call `setData` after `linkSeries` 2045 series.setData(options.data, false); 2046 } 2047 fireEvent(chart, 'afterAddSeries', { series: series }); 2048 if (redraw) { 2049 chart.redraw(animation); 2050 } 2051 }); 2052 } 2053 return series; 2054 } 2055 /** 2056 * Add an axis to the chart after render time. Note that this method should 2057 * never be used when adding data synchronously at chart render time, as it 2058 * adds expense to the calculations and rendering. When adding data at the 2059 * same time as the chart is initialized, add the axis as a configuration 2060 * option instead. 2061 * 2062 * @sample highcharts/members/chart-addaxis/ 2063 * Add and remove axes 2064 * 2065 * @function Highcharts.Chart#addAxis 2066 * 2067 * @param {Highcharts.AxisOptions} options 2068 * The axis options. 2069 * 2070 * @param {boolean} [isX=false] 2071 * Whether it is an X axis or a value axis. 2072 * 2073 * @param {boolean} [redraw=true] 2074 * Whether to redraw the chart after adding. 2075 * 2076 * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] 2077 * Whether and how to apply animation in the redraw. When 2078 * `undefined`, it applies the animation that is set in the 2079 * `chart.animation` option. 2080 * 2081 * @return {Highcharts.Axis} 2082 * The newly generated Axis object. 2083 */ 2084 addAxis(options, isX, redraw, animation) { 2085 return this.createAxis(isX ? 'xAxis' : 'yAxis', { axis: options, redraw: redraw, animation: animation }); 2086 } 2087 /** 2088 * Add a color axis to the chart after render time. Note that this method 2089 * should never be used when adding data synchronously at chart render time, 2090 * as it adds expense to the calculations and rendering. When adding data at 2091 * the same time as the chart is initialized, add the axis as a 2092 * configuration option instead. 2093 * 2094 * @sample highcharts/members/chart-addaxis/ 2095 * Add and remove axes 2096 * 2097 * @function Highcharts.Chart#addColorAxis 2098 * 2099 * @param {Highcharts.ColorAxisOptions} options 2100 * The axis options. 2101 * 2102 * @param {boolean} [redraw=true] 2103 * Whether to redraw the chart after adding. 2104 * 2105 * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] 2106 * Whether and how to apply animation in the redraw. When 2107 * `undefined`, it applies the animation that is set in the 2108 * `chart.animation` option. 2109 * 2110 * @return {Highcharts.Axis} 2111 * The newly generated Axis object. 2112 */ 2113 addColorAxis(options, redraw, animation) { 2114 return this.createAxis('colorAxis', { axis: options, redraw: redraw, animation: animation }); 2115 } 2116 /** 2117 * Factory for creating different axis types. 2118 * 2119 * @private 2120 * @function Highcharts.Chart#createAxis 2121 * 2122 * @param {string} coll 2123 * An axis type. 2124 * 2125 * @param {...Array<*>} arguments 2126 * All arguments for the constructor. 2127 * 2128 * @return {Highcharts.Axis} 2129 * The newly generated Axis object. 2130 */ 2131 createAxis(coll, options) { 2132 const axis = new Axis(this, options.axis, coll); 2133 if (pick(options.redraw, true)) { 2134 this.redraw(options.animation); 2135 } 2136 return axis; 2137 } 2138 /** 2139 * Dim the chart and show a loading text or symbol. Options for the loading 2140 * screen are defined in {@link 2141 * https://api.highcharts.com/highcharts/loading|the loading options}. 2142 * 2143 * @sample highcharts/members/chart-hideloading/ 2144 * Show and hide loading from a button 2145 * @sample highcharts/members/chart-showloading/ 2146 * Apply different text labels 2147 * @sample stock/members/chart-show-hide-loading/ 2148 * Toggle loading in Highcharts Stock 2149 * 2150 * @function Highcharts.Chart#showLoading 2151 * 2152 * @param {string} [str] 2153 * An optional text to show in the loading label instead of the 2154 * default one. The default text is set in 2155 * [lang.loading](https://api.highcharts.com/highcharts/lang.loading). 2156 */ 2157 showLoading(str) { 2158 const chart = this, options = chart.options, loadingOptions = options.loading, setLoadingSize = function () { 2159 if (loadingDiv) { 2160 css(loadingDiv, { 2161 left: chart.plotLeft + 'px', 2162 top: chart.plotTop + 'px', 2163 width: chart.plotWidth + 'px', 2164 height: chart.plotHeight + 'px' 2165 }); 2166 } 2167 }; 2168 let loadingDiv = chart.loadingDiv, loadingSpan = chart.loadingSpan; 2169 // Create the layer at the first call 2170 if (!loadingDiv) { 2171 chart.loadingDiv = loadingDiv = createElement('div', { 2172 className: 'highcharts-loading highcharts-loading-hidden' 2173 }, null, chart.container); 2174 } 2175 if (!loadingSpan) { 2176 chart.loadingSpan = loadingSpan = createElement('span', { className: 'highcharts-loading-inner' }, null, loadingDiv); 2177 addEvent(chart, 'redraw', setLoadingSize); // #1080 2178 } 2179 loadingDiv.className = 'highcharts-loading'; 2180 // Update text 2181 AST.setElementHTML(loadingSpan, pick(str, options.lang.loading, '')); 2182 if (!chart.styledMode) { 2183 // Update visuals 2184 css(loadingDiv, extend(loadingOptions.style, { 2185 zIndex: 10 2186 })); 2187 css(loadingSpan, loadingOptions.labelStyle); 2188 // Show it 2189 if (!chart.loadingShown) { 2190 css(loadingDiv, { 2191 opacity: 0, 2192 display: '' 2193 }); 2194 animate(loadingDiv, { 2195 opacity: loadingOptions.style.opacity || 0.5 2196 }, { 2197 duration: loadingOptions.showDuration || 0 2198 }); 2199 } 2200 } 2201 chart.loadingShown = true;
2202 setLoadingSize(); 2203 } 2204 /** 2205 * Hide the loading layer. 2206 * 2207 * @see Highcharts.Chart#showLoading 2208 * 2209 * @sample highcharts/members/chart-hideloading/ 2210 * Show and hide loading from a button 2211 * @sample stock/members/chart-show-hide-loading/ 2212 * Toggle loading in Highcharts Stock 2213 * 2214 * @function Highcharts.Chart#hideLoading 2215 */ 2216 hideLoading() { 2217 const options = this.options, loadingDiv = this.loadingDiv; 2218 if (loadingDiv) { 2219 loadingDiv.className = 2220 'highcharts-loading highcharts-loading-hidden'; 2221 if (!this.styledMode) { 2222 animate(loadingDiv, { 2223 opacity: 0 2224 }, { 2225 duration: options.loading.hideDuration || 100, 2226 complete: function () { 2227 css(loadingDiv, { display: 'none' }); 2228 } 2229 }); 2230 } 2231 } 2232 this.loadingShown = false; 2233 } 2234 /** 2235 * A generic function to update any element of the chart. Elements can be 2236 * enabled and disabled, moved, re-styled, re-formatted etc. 2237 * 2238 * A special case is configuration objects that take arrays, for example 2239 * [xAxis](https://api.highcharts.com/highcharts/xAxis), 2240 * [yAxis](https://api.highcharts.com/highcharts/yAxis) or 2241 * [series](https://api.highcharts.com/highcharts/series). For these 2242 * collections, an `id` option is used to map the new option set to an 2243 * existing object. If an existing object of the same id is not found, the 2244 * corresponding item is updated. So for example, running `chart.update` 2245 * with a series item without an id, will cause the existing chart's series 2246 * with the same index in the series array to be updated. When the 2247 * `oneToOne` parameter is true, `chart.update` will also take care of 2248 * adding and removing items from the collection. Read more under the 2249 * parameter description below. 2250 * 2251 * Note that when changing series data, `chart.update` may mutate the passed 2252 * data options. 2253 * 2254 * See also the 2255 * [responsive option set](https://api.highcharts.com/highcharts/responsive). 2256 * Switching between `responsive.rules` basically runs `chart.update` under 2257 * the hood. 2258 * 2259 * @sample highcharts/members/chart-update/ 2260 * Update chart geometry 2261 * 2262 * @function Highcharts.Chart#update 2263 * 2264 * @param {Highcharts.Options} options 2265 * A configuration object for the new chart options. 2266 * 2267 * @param {boolean} [redraw=true] 2268 * Whether to redraw the chart. 2269 * 2270 * @param {boolean} [oneToOne=false] 2271 * When `true`, the `series`, `xAxis`, `yAxis` and `annotations` 2272 * collections will be updated one to one, and items will be either 2273 * added or removed to match the new updated options. For example, 2274 * if the chart has two series and we call `chart.update` with a 2275 * configuration containing three series, one will be added. If we 2276 * call `chart.update` with one series, one will be removed. Setting 2277 * an empty `series` array will remove all series, but leaving out 2278 * the`series` property will leave all series untouched. If the 2279 * series have id's, the new series options will be matched by id, 2280 * and the remaining ones removed. 2281 * 2282 * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] 2283 * Whether to apply animation, and optionally animation 2284 * configuration. When `undefined`, it applies the animation that is 2285 * set in the `chart.animation` option. 2286 * 2287 * @emits Highcharts.Chart#event:update 2288 * @emits Highcharts.Chart#event:afterUpdate 2289 */ 2290 update(options, redraw, oneToOne, animation) { 2291 const chart = this, adders = { 2292 credits: 'addCredits', 2293 title: 'setTitle', 2294 subtitle: 'setSubtitle', 2295 caption: 'setCaption' 2296 }, isResponsiveOptions = options.isResponsiveOptions, itemsForRemoval = []; 2297 let updateAllAxes, updateAllSeries, runSetSize; 2298 fireEvent(chart, 'update', { options: options }); 2299 // If there are responsive rules in action, undo the responsive rules 2300 // before we apply the updated options and replay the responsive rules 2301 // on top from the chart.redraw function (#9617). 2302 if (!isResponsiveOptions) { 2303 chart.setResponsive(false, true); 2304 } 2305 options = diffObjects(options, chart.options); 2306 chart.userOptions = merge(chart.userOptions, options); 2307 // If the top-level chart option is present, some special updates are 2308 // required 2309 const optionsChart = options.chart; 2310 if (optionsChart) { 2311 merge(true, chart.options.chart, optionsChart); 2312 // Add support for deprecated zooming options like zoomType, #17861 2313 this.setZoomOptions(); 2314 // Setter function
vendor: 11,697 bytes, lines 2315-2601
2315 if ('className' in optionsChart) { 2316 chart.setClassName(optionsChart.className); 2317 } 2318 if ('inverted' in optionsChart || 2319 'polar' in optionsChart || 2320 'type' in optionsChart) { 2321 // Parse options.chart.inverted and options.chart.polar together 2322 // with the available series. 2323 chart.propFromSeries(); 2324 updateAllAxes = true; 2325 } 2326 if ('alignTicks' in optionsChart) { // #6452 2327 updateAllAxes = true; 2328 } 2329 if ('events' in optionsChart) { 2330 // Chart event handlers 2331 registerEventOptions(this, optionsChart); 2332 } 2333 objectEach(optionsChart, function (val, key) { 2334 if (chart.propsRequireUpdateSeries.indexOf('chart.' + key) !== 2335 -1) { 2336 updateAllSeries = true; 2337 } 2338 // Only dirty box 2339 if (chart.propsRequireDirtyBox.indexOf(key) !== -1) { 2340 chart.isDirtyBox = true; 2341 } 2342 // Chart setSize 2343 if (chart.propsRequireReflow.indexOf(key) !== -1) { 2344 chart.isDirtyBox = true; 2345 if (!isResponsiveOptions) { 2346 runSetSize = true; 2347 } 2348 } 2349 }); 2350 if (!chart.styledMode && optionsChart.style) { 2351 chart.renderer.setStyle(chart.options.chart.style || {}); 2352 } 2353 } 2354 // Moved up, because tooltip needs updated plotOptions (#6218) 2355 if (!chart.styledMode && options.colors) { 2356 this.options.colors = options.colors; 2357 } 2358 // Some option structures correspond one-to-one to chart objects that 2359 // have update methods, for example 2360 // options.credits => chart.credits 2361 // options.legend => chart.legend 2362 // options.title => chart.title 2363 // options.tooltip => chart.tooltip 2364 // options.subtitle => chart.subtitle 2365 // options.mapNavigation => chart.mapNavigation 2366 // options.navigator => chart.navigator 2367 // options.scrollbar => chart.scrollbar 2368 objectEach(options, function (val, key) { 2369 if (chart[key] && 2370 typeof chart[key].update === 'function') { 2371 chart[key].update(val, false); 2372 // If a one-to-one object does not exist, look for an adder function 2373 } 2374 else if (typeof chart[adders[key]] === 'function') { 2375 chart[adders[key]](val); 2376 // Else, just merge the options. For nodes like loading, noData, 2377 // plotOptions 2378 } 2379 else if (key !== 'colors' && 2380 chart.collectionsWithUpdate.indexOf(key) === -1) { 2381 merge(true, chart.options[key], options[key]); 2382 } 2383 if (key !== 'chart' && 2384 chart.propsRequireUpdateSeries.indexOf(key) !== -1) { 2385 updateAllSeries = true; 2386 } 2387 }); 2388 // Setters for collections. For axes and series, each item is referred 2389 // by an id. If the id is not found, it defaults to the corresponding 2390 // item in the collection, so setting one series without an id, will 2391 // update the first series in the chart. Setting two series without 2392 // an id will update the first and the second respectively (#6019) 2393 // chart.update and responsive. 2394 this.collectionsWithUpdate.forEach(function (coll) { 2395 if (options[coll]) { 2396 splat(options[coll]).forEach(function (newOptions, i) { 2397 const hasId = defined(newOptions.id); 2398 let item; 2399 // Match by id 2400 if (hasId) { 2401 item = chart.get(newOptions.id); 2402 } 2403 // No match by id found, match by index instead 2404 if (!item && chart[coll]) { 2405 item = chart[coll][pick(newOptions.index, i)]; 2406 // Check if we grabbed an item with an existing but 2407 // different id (#13541). Check that the item in this 2408 // position is not internal (navigator). 2409 if (item && ((hasId && defined(item.options.id)) || 2410 item.options.isInternal)) { 2411 item = void 0; 2412 } 2413 } 2414 if (item && item.coll === coll) { 2415 item.update(newOptions, false); 2416 if (oneToOne) { 2417 item.touched = true; 2418 } 2419 } 2420 // If oneToOne and no matching item is found, add one 2421 if (!item && oneToOne && chart.collectionsWithInit[coll]) { 2422 chart.collectionsWithInit[coll][0].apply(chart, 2423 // [newOptions, ...extraArguments, redraw=false] 2424 [ 2425 newOptions 2426 ].concat( 2427 // Not all initializers require extra args 2428 chart.collectionsWithInit[coll][1] || []).concat([ 2429 false 2430 ])).touched = true; 2431 } 2432 }); 2433 // Add items for removal 2434 if (oneToOne) { 2435 chart[coll].forEach(function (item) { 2436 if (!item.touched && !item.options.isInternal) { 2437 itemsForRemoval.push(item); 2438 } 2439 else { 2440 delete item.touched; 2441 } 2442 }); 2443 } 2444 } 2445 }); 2446 itemsForRemoval.forEach(function (item) { 2447 if (item.chart && item.remove) { // #9097, avoid removing twice 2448 item.remove(false); 2449 } 2450 }); 2451 if (updateAllAxes) { 2452 chart.axes.forEach(function (axis) { 2453 axis.update({}, false); 2454 }); 2455 } 2456 // Certain options require the whole series structure to be thrown away 2457 // and rebuilt 2458 if (updateAllSeries) { 2459 chart.getSeriesOrderByLinks().forEach(function (series) { 2460 // Avoid removed navigator series 2461 if (series.chart) { 2462 series.update({}, false); 2463 } 2464 }, this); 2465 } 2466 // Update size. Redraw is forced. 2467 const newWidth = optionsChart && optionsChart.width; 2468 const newHeight = optionsChart && (isString(optionsChart.height) ? 2469 relativeLength(optionsChart.height, newWidth || chart.chartWidth) : 2470 optionsChart.height); 2471 if ( 2472 // In this case, run chart.setSize with newWidth and newHeight which 2473 // are undefined, only for reflowing chart elements because margin 2474 // or spacing has been set (#8190) 2475 runSetSize || 2476 // In this case, the size is actually set 2477 (isNumber(newWidth) && newWidth !== chart.chartWidth) || 2478 (isNumber(newHeight) && newHeight !== chart.chartHeight)) { 2479 chart.setSize(newWidth, newHeight, animation); 2480 } 2481 else if (pick(redraw, true)) { 2482 chart.redraw(animation); 2483 } 2484 fireEvent(chart, 'afterUpdate', { 2485 options: options, 2486 redraw: redraw, 2487 animation: animation 2488 }); 2489 } 2490 /** 2491 * Shortcut to set the subtitle options. This can also be done from {@link 2492 * Chart#update} or {@link Chart#setTitle}. 2493 * 2494 * @function Highcharts.Chart#setSubtitle 2495 * 2496 * @param {Highcharts.SubtitleOptions} options 2497 * New subtitle options. The subtitle text itself is set by the 2498 * `options.text` property. 2499 */ 2500 setSubtitle(options, redraw) { 2501 this.applyDescription('subtitle', options); 2502 this.layOutTitles(redraw); 2503 } 2504 /** 2505 * Set the caption options. This can also be done from {@link 2506 * Chart#update}. 2507 * 2508 * @function Highcharts.Chart#setCaption 2509 * 2510 * @param {Highcharts.CaptionOptions} options 2511 * New caption options. The caption text itself is set by the 2512 * `options.text` property. 2513 */ 2514 setCaption(options, redraw) { 2515 this.applyDescription('caption', options); 2516 this.layOutTitles(redraw); 2517 } 2518 /** 2519 * Display the zoom button, so users can reset zoom to the default view 2520 * settings. 2521 * 2522 * @function Highcharts.Chart#showResetZoom 2523 * 2524 * @emits Highcharts.Chart#event:afterShowResetZoom 2525 * @emits Highcharts.Chart#event:beforeShowResetZoom 2526 */ 2527 showResetZoom() { 2528 const chart = this, lang = defaultOptions.lang, btnOptions = chart.zooming.resetButton, theme = btnOptions.theme, alignTo = (btnOptions.relativeTo === 'chart' || 2529 btnOptions.relativeTo === 'spacingBox' ? 2530 null : 2531 'plotBox'); 2532 /** 2533 * @private 2534 */ 2535 function zoomOut() { 2536 chart.zoomOut(); 2537 } 2538 fireEvent(this, 'beforeShowResetZoom', null, function () { 2539 chart.resetZoomButton = chart.renderer 2540 .button(lang.resetZoom, null, null, zoomOut, theme) 2541 .attr({ 2542 align: btnOptions.position.align, 2543 title: lang.resetZoomTitle 2544 }) 2545 .addClass('highcharts-reset-zoom') 2546 .add() 2547 .align(btnOptions.position, false, alignTo); 2548 }); 2549 fireEvent(this, 'afterShowResetZoom'); 2550 } 2551 /** 2552 * Zoom the chart out after a user has zoomed in. See also 2553 * [Axis.setExtremes](/class-reference/Highcharts.Axis#setExtremes). 2554 * 2555 * @function Highcharts.Chart#zoomOut 2556 * 2557 * @emits Highcharts.Chart#event:selection 2558 */ 2559 zoomOut() { 2560 fireEvent(this, 'selection', { resetSelection: true }, () => this.transform({ reset: true, trigger: 'zoom' })); 2561 } 2562 /** 2563 * Pan the chart by dragging the mouse across the pane. This function is 2564 * called on mouse move, and the distance to pan is computed from chartX 2565 * compared to the first chartX position in the dragging operation. 2566 * 2567 * @private 2568 * @function Highcharts.Chart#pan 2569 * @param {Highcharts.PointerEventObject} event 2570 * @param {string} panning 2571 */ 2572 pan(event, panning) { 2573 const chart = this, panningOptions = (typeof panning === 'object' ? 2574 panning : 2575 { 2576 enabled: panning, 2577 type: 'x' 2578 }), type = panningOptions.type, axes = type && chart[{ 2579 x: 'xAxis', 2580 xy: 'axes', 2581 y: 'yAxis' 2582 }[type]] 2583 .filter((axis) => axis.options.panningEnabled && !axis.options.isInternal), chartOptions = chart.options.chart; 2584 if (chartOptions?.panning) { 2585 chartOptions.panning = panningOptions; 2586 } 2587 fireEvent(this, 'pan', { originalEvent: event }, () => { 2588 chart.transform({ 2589 axes, 2590 event, 2591 to: { 2592 x: event.chartX - (chart.mouseDownX || 0), 2593 y: event.chartY - (chart.mouseDownY || 0) 2594 }, 2595 trigger: 'pan' 2596 }); 2597 css(chart.container, { cursor: 'move' }); 2598 }); 2599 } 2600 /** 2601 * Pan and scale the chart. Used internally by mouse-pan, touch-p
2601an, 2602 * touch-zoom, and mousewheel zoom. 2603 * 2604 * The main positioning logic is created around two imaginary boxes. What is 2605 * currently within the `from` rectangle, should be transformed to fill up 2606 * the `to` rectangle. 2607 * - In a mouse zoom, the `from` rectangle is the selection, while the `to` 2608 * rectangle is the full plot area. 2609 * - In a touch zoom, the `from` rectangle is made up of the last two-finger 2610 * touch, while the `to`` rectangle is the current touch. 2611 * - In a mousewheel zoom, the `to` rectangle is a 10x10 px square, 2612 * while the `to` rectangle reflects the scale around that. 2613 * 2614 * @private 2615 * @function Highcharts.Chart#transform 2616 */ 2617 transform(params) { 2618 const { axes = this.axes, event, from = {}, reset, selection, to = {}, trigger } = params, { inverted, time } = this; 2619 let hasZoomed = false, displayButton, isAnyAxisPanning; 2620 // Remove active points for shared tooltip 2621 this.hoverPoints?.forEach((point) => point.setState()); 2622 for (const axis of axes) { 2623 const { horiz, len, minPointOffset = 0, options, reversed } = axis, wh = horiz ? 'width' : 'height', xy = horiz ? 'x' : 'y', toLength = pick(to[wh], axis.len), fromLength = pick(from[wh], axis.len), 2624 // If fingers pinched very close on this axis, treat as pan 2625 scale = Math.abs(toLength) < 10 ? 2626 1 : 2627 toLength / fromLength, fromCenter = (from[xy] || 0) + fromLength / 2 - axis.pos, toCenter = (to[xy] ?? axis.pos) + 2628 toLength / 2 - axis.pos, move = fromCenter - toCenter / scale, pointRangeDirection = (reversed && !inverted) || 2629 (!reversed && inverted) ? 2630 -1 : 2631 1, minPx = move; 2632 // Zooming in multiple panes, zoom only in the pane that receives 2633 // the input 2634 if (!reset && (fromCenter < 0 || fromCenter > axis.len)) { 2635 continue; 2636 } 2637 let newMin = axis.toValue(minPx, true) + 2638 // Don't apply offset for selection (#20784) 2639 (selection || axis.isOrdinal ? 2640 0 : minPointOffset * pointRangeDirection), newMax = axis.toValue(minPx + len / scale, true) - 2641 ( 2642 // Don't apply offset for selection (#20784) 2643 selection || axis.isOrdinal ? 2644 0 : 2645 ((minPointOffset * pointRangeDirection) || 2646 // Polar zoom tests failed when this was not 2647 // commented: 2648 // (axis.isXAxis && axis.pointRangePadding) || 2649 0)), allExtremes = axis.allExtremes; 2650 if (newMin > newMax) { 2651 [newMin, newMax] = [newMax, newMin]; 2652 } 2653 // General calculations of the full data extremes. It is calculated 2654 // on the first call to transform, then reused for subsequent 2655 // touch/pan calls. (#11315). 2656 if (scale === 1 && 2657 !reset && 2658 axis.coll === 'yAxis' && 2659 !allExtremes) { 2660 for (const series of axis.series) { 2661 const seriesExtremes = series.getExtremes(series.getProcessedData(true).modified 2662 .getColumn('y') || [], true); 2663 allExtremes ?? (allExtremes = { 2664 dataMin: Number.MAX_VALUE, 2665 dataMax: -Number.MAX_VALUE 2666 }); 2667 if (isNumber(seriesExtremes.dataMin) && 2668 isNumber(seriesExtremes.dataMax)) { 2669 allExtremes.dataMin = Math.min(seriesExtremes.dataMin, allExtremes.dataMin); 2670 allExtremes.dataMax = Math.max(seriesExtremes.dataMax, allExtremes.dataMax); 2671 } 2672 } 2673 axis.allExtremes = allExtremes; 2674 } 2675 const { dataMin, dataMax, min, max } = extend(axis.getExtremes(), allExtremes || {}), optionsMin = time.parse(options.min), optionsMax = time.parse(options.max), 2676 // For boosted chart where data extremes are skipped 2677 safeDataMin = dataMin ?? optionsMin, safeDataMax = dataMax ?? optionsMax, range = newMax - newMin, padRange = axis.categories ? 0 : Math.min(range, safeDataMax - safeDataMin), paddedMin = safeDataMin - padRange * (defined(optionsMin) ? 0 : options.minPadding), paddedMax = safeDataMax + padRange * (defined(optionsMax) ? 0 : options.maxPadding), 2678 // We're allowed to zoom outside the data extremes if we're 2679 // dealing with a bubble chart, if we're panning, or if we're 2680 // pinching or mousewheeling in. 2681 allowZoomOutside = axis.allowZoomOutside || 2682 scale === 1 || 2683 (trigger !== 'zoom' && scale > 1), 2684 // Calculate the floor and the ceiling 2685 floor = Math.min(optionsMin ?? paddedMin, paddedMin, allowZoomOutside ? min : paddedMin), ceiling = Math.max(optionsMax ?? paddedMax, paddedMax, allowZoomOutside ? max : paddedMax); 2686 // It is not necessary to calculate extremes on ordinal axis, 2687 // because they are already calculated, so we don't want to override 2688 // them. 2689 if (!axis.isOrdinal || 2690 axis.options.overscroll || // #21316 2691 scale !== 1 || 2692 reset) { 2693 // If the new range spills over, either to the min or max, 2694 // adjust it. 2695 if (newMin < floor) { 2696 newMin = floor; 2697 if (scale >= 1) { 2698 newMax = newMin + range; 2699 } 2700 } 2701 if (newMax > ceiling) { 2702 newMax = ceiling; 2703 if (scale >= 1) { 2704 newMin = newMax - range; 2705 } 2706 } 2707 // Set new extremes if they are actually new 2708 if (reset || (axis.series.length && 2709 (newMin !== min || newMax !== max) && 2710 newMin >= floor && 2711 newMax <= ceiling)) { 2712 if (selection) { 2713 selection[axis.coll].push({ 2714 axis, 2715 min: newMin, 2716 max: newMax 2717 }); 2718 } 2719 else { 2720 // Temporarily flag the axis as `isPanning` in order to 2721 // disallow certain axis padding options that would make 2722 // panning/zooming hard. Reset and redraw after the 2723 // operation has finished. 2724 axis.isPanning = trigger !== 'zoom'; 2725 if (axis.isPanning) { 2726 isAnyAxisPanning = true; // #21319 2727 } 2728 axis.setExtremes(reset ? void 0 : newMin, reset ? void 0 : newMax, false, false, { move, trigger, scale }); 2729 if (!reset && 2730 (newMin > floor || newMax < ceiling) && 2731 trigger !== 'mousewheel') { 2732 displayButton = true; 2733 } 2734 } 2735 hasZoomed = true; 2736 } 2737 if (event) { 2738 this[horiz ? 'mouseDownX' : 'mouseDownY'] = 2739 event[horiz ? 'chartX' : 'chartY']; 2740 } 2741 } 2742 } 2743 if (hasZoomed) { 2744 if (selection) { 2745 fireEvent(this, 'selection', selection, 2746 // Run transform again, this time without the selection data 2747 // so that the transform is applied. 2748 () => { 2749 delete params.selection; 2750 params.trigger = 'zoom'; 2751 this.transform(params); 2752 }); 2753 } 2754 else { 2755 // Show or hide the Reset zoom button, but not while panning 2756 if (displayButton && 2757 !isAnyAxisPanning && 2758 !this.resetZoomButton) { 2759 this.showResetZoom(); 2760 } 2761 else if (!displayButton && this.resetZoomButton) { 2762 this.resetZoomButton = this.resetZoomButton.destroy(); 2763 } 2764 this.redraw(trigger === 'zoom' && 2765 (this.options.chart.animation ?? this.pointCount < 100)); 2766 } 2767 } 2768 return hasZoomed; 2769 } 2770} 2771extend(Chart.prototype, { 2772 // Hook for adding callbacks in modules 2773 callbacks: [], 2774 /** 2775 * These collections (arrays) implement `Chart.addSomething` method used in
2776 * chart.update() to create new object in the collection. Equivalent for 2777 * deleting is resolved by simple `Something.remove()`. 2778 * 2779 * Note: We need to define these references after initializers are bound to 2780 * chart's prototype. 2781 * 2782 * @private 2783 */ 2784 collectionsWithInit: { 2785 // CollectionName: [ initializingMethod, [extraArguments] ] 2786 xAxis: [Chart.prototype.addAxis, [true]], 2787 yAxis: [Chart.prototype.addAxis, [false]], 2788 series: [Chart.prototype.addSeries] 2789 }, 2790 /** 2791 * These collections (arrays) implement update() methods with support for 2792 * one-to-one option. 2793 * @private 2794 */ 2795 collectionsWithUpdate: [ 2796 'xAxis', 2797 'yAxis', 2798 'series' 2799 ], 2800 /** 2801 * These properties cause isDirtyBox to be set to true when updating. Can be 2802 * extended from plugins. 2803 * @private 2804 */ 2805 propsRequireDirtyBox: [ 2806 'backgroundColor', 2807 'borderColor', 2808 'borderWidth', 2809 'borderRadius', 2810 'plotBackgroun
2810dColor', 2811 'plotBackgroundImage', 2812 'plotBorderColor', 2813 'plotBorderWidth', 2814 'plotShadow', 2815 'shadow' 2816 ], 2817 /** 2818 * These properties require a full reflow of chart elements, best 2819 * implemented through running `Chart.setSize` internally (#8190). 2820 * @private 2821 */ 2822 propsRequireReflow: [ 2823 'margin', 2824 'marginTop', 2825 'marginRight', 2826 'marginBottom', 2827 'marginLeft', 2828 'spacing', 2829 'spacingTop', 2830 'spacingRight', 2831 'spacingBottom', 2832 'spacingLeft' 2833 ], 2834 /** 2835 * These properties cause all series to be updated when updating. Can be 2836 * extended from plugins. 2837 * @private 2838 */ 2839 propsRequireUpdateSeries: [ 2840 'chart.inverted', 2841 'chart.polar', 2842 'chart.ignoreHiddenSeries', 2843 'chart.type', 2844 'colors', 2845 'plotOptions', 2846 'time', 2847 'tooltip' 2848 ] 2849}); 2850/* * 2851 * 2852 * Default Export 2853 * 2854 * */ 2855export default Chart; 2856/* * 2857 * 2858 * API Declarations 2859 * 2860 * */ 2861/** 2862 * Callback for chart constructors. 2863 * 2864 * @callback Highcharts.ChartCallbackFunction 2865 * 2866 * @param {Highcharts.Chart} chart 2867 * Created chart. 2868 */ 2869/** 2870 * Format a number and return a string based on input settings. 2871 * 2872 * @callback Highcharts.NumberFormatterCallbackFunction 2873 * 2874 * @param {number} number 2875 * The input number to format. 2876 * 2877 * @param {number} decimals 2878 * The amount of decimals. A value of -1 preserves the amount in the 2879 * input number. 2880 * 2881 * @param {string} [decimalPoint] 2882 * The decimal point, defaults to the one given in the lang options, or 2883 * a dot. 2884 * 2885 * @param {string} [thousandsSep] 2886 * The thousands separator, defaults to the one given in the lang 2887 * options, or a space character. 2888 * 2889 * @return {string} The formatted number. 2890 */ 2891/** 2892 * The chart title. The title has an `update` method that allows modifying the 2893 * options directly or indirectly via `chart.update`. 2894 * 2895 * @interface Highcharts.TitleObject 2896 * @extends Highcharts.SVGElement 2897 */ /** 2898* Modify options for the title. 2899* 2900* @function Highcharts.TitleObject#update 2901* 2902* @param {Highcharts.TitleOptions} titleOptions 2903* Options to modify. 2904* 2905* @param {boolean} [redraw=true] 2906* Whether to redraw the chart after the title is altered. If doing more 2907* operations on the chart, it is a good idea to set redraw to false and 2908* call {@link Chart#redraw} after. 2909*/ 2910/** 2911 * The chart subtitle. The subtitle has an `update` method that 2912 * allows modifying the options directly or indirectly via 2913 * `chart.update`. 2914 * 2915 * @interface Highcharts.SubtitleObject 2916 * @extends Highcharts.SVGElement 2917 */ /** 2918* Modify options for the subtitle. 2919* 2920* @function Highcharts.SubtitleObject#update 2921* 2922* @param {Highcharts.SubtitleOptions} subtitleOptions 2923* Options to modify. 2924* 2925* @param {boolean} [redraw=true] 2926* Whether to redraw the chart after the subtitle is altered. If doing 2927* more operations on the chart, it is a good idea to set redraw to false 2928* and call {@link Chart#redraw} after. 2929*/ 2930/** 2931 * The chart caption. The caption has an `update` method that 2932 * allows modifying the options directly or indirectly via 2933 * `chart.update`. 2934 * 2935 * @interface Highcharts.CaptionObject 2936 * @extends Highcharts.SVGElement 2937 */ /** 2938* Modify options for the caption. 2939* 2940* @function Highcharts.CaptionObject#update 2941* 2942* @param {Highcharts.CaptionOptions} captionOptions 2943* Options to modify. 2944* 2945* @param {boolean} [redraw=true] 2946* Whether to redraw the chart after the caption is altered. If doing 2947* more operations on the chart, it is a good idea to set redraw to false 2948* and call {@link Chart#redraw} after. 2949*/ 2950/** 2951 * @interface Highcharts.ChartIsInsideOptionsObject 2952 */ /** 2953* @name Highcharts.ChartIsInsideOptionsObject#axis 2954* @type {Highcharts.Axis|undefined} 2955*/ /** 2956* @name Highcharts.ChartIsInsideOptionsObject#ignoreX 2957* @type {boolean|undefined} 2958*/ /** 2959* @name Highcharts.ChartIsInsideOptionsObject#ignoreY 2960* @type {boolean|undefined} 2961*/ /** 2962* @name Highcharts.ChartIsInsideOptionsObject#inverted 2963* @type {boolean|undefined} 2964*/ /** 2965* @name Highcharts.ChartIsInsideOptionsObject#paneCoordinates 2966* @type {boolean|undefined} 2967*/ /** 2968* @name Highcharts.ChartIsInsideOptionsObject#series 2969* @type {Highcharts.Series|undefined} 2970*/ /** 2971* @name Highcharts.ChartIsInsideOptionsObject#visiblePlotOnly 2972* @type {boolean|undefined} 2973*/ 2974''; // 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.