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 { animObject } = A; 13import AxisDefaults from './AxisDefaults.js'; 14const { xAxis, yAxis } = AxisDefaults; 15import Color from '../Color/Color.js'; 16import D from '../Defaults.js'; 17const { defaultOptions } = D; 18import F from '../Foundation.js'; 19const { registerEventOptions } = F; 20import H from '../Globals.js'; 21const { deg2rad } = H; 22import Tick from './Tick.js'; 23import U from '../Utilities.js'; 24const { arrayMax, arrayMin, clamp, correctFloat, defined, destroyObjectProperties, erase, error, extend, fireEvent, getClosestDistance, insertItem, isArray, isNumber, isString, merge, normalizeTickInterval, objectEach, pick, relativeLength, removeEvent, splat, syncTimeout } = U; 25const getNormalizedTickInterval = (axis, tickInterval) => normalizeTickInterval(tickInterval, void 0, void 0, pick(axis.options.allowDecimals, 26// If the tick interval is greater than 0.5, avoid decimals, as 27// linear axes are often used to render discrete values (#3363). If 28// a tick amount is set, allow decimals by default, as it increases 29// the chances for a good fit. 30tickInterval < 0.5 || axis.tickAmount !== void 0), !!axis.tickAmount); 31extend(defaultOptions, { xAxis, yAxis: merge(xAxis, yAxis) }); 32/* * 33 * 34 * Class 35 * 36 * */ 37/** 38 * Create a new axis object. Called internally when instantiating a new chart or 39 * adding axes by {@link Highcharts.Chart#addAxis}. 40 * 41 * A chart can have from 0 axes (pie chart) to multiples. In a normal, single 42 * series cartesian chart, there is one X axis and one Y axis. 43 * 44 * The X axis or axes are referenced by {@link Highcharts.Chart.xAxis}, which is 45 * an array of Axis objects. If there is only one axis, it can be referenced 46 * through `chart.xAxis[0]`, and multiple axes have increasing indices. The same 47 * pattern goes for Y axes. 48 * 49 * If you need to get the axes from a series object, use the `series.xAxis` and 50 * `series.yAxis` properties. These are not arrays, as one series can only be 51 * associated to one X and one Y axis. 52 * 53 * A third way to reference the axis programmatically is by `id`. Add an `id` in 54 * the axis configuration options, and get the axis by 55 * {@link Highcharts.Chart#get}. 56 * 57 * Configuration options for the axes are given in options.xAxis and 58 * options.yAxis. 59 * 60 * @class 61 * @name Highcharts.Axis 62 * 63 * @param {Highcharts.Chart} chart 64 * The Chart instance to apply the axis on. 65 * 66 * @param {Highcharts.AxisOptions} userOptions 67 * Axis options 68 */ 69class Axis { 70 /* * 71 * 72 * Constructors 73 * 74 * */ 75 constructor(chart, userOptions, coll) { 76 this.init(chart, userOptions, coll); 77 } 78 /* * 79 * 80 * Functions 81 * 82 * */ 83 /** 84 * Overrideable function to initialize the axis. 85 * 86 * @see {@link Axis} 87 * 88 * @function Highcharts.Axis#init 89 * 90 * @param {Highcharts.Chart} chart 91 * The Chart instance to apply the axis on. 92 * 93 * @param {AxisOptions} userOptions 94 * Axis options. 95 * 96 * @emits Highcharts.Axis#event:afterInit 97 * @emits Highcharts.Axis#event:init 98 */ 99 init(chart, userOptions, coll = this.coll) { 100 const isXAxis = coll === 'xAxis', axis = this, horiz = axis.isZAxis || (chart.inverted ? !isXAxis : isXAxis); 101 /** 102 * The Chart that the axis belongs to. 103 * 104 * @name Highcharts.Axis#chart 105 * @type {Highcharts.Chart} 106 */ 107 axis.chart = chart; 108 /** 109 * Whether the axis is horizontal. 110 * 111 * @name Highcharts.Axis#horiz 112 * @type {boolean|undefined} 113 */ 114 axis.horiz = horiz; 115 /** 116 * Whether the axis is the x-axis. 117 * 118 * @name Highcharts.Axis#isXAxis 119 * @type {boolean|undefined} 120 */ 121 axis.isXAxis = isXAxis; 122 /** 123 * The collection where the axis belongs, for example `xAxis`, `yAxis` 124 * or `colorAxis`. Corresponds to properties on Chart, for example 125 * {@link Chart.xAxis}. 126 * 127 * @name Highcharts.Axis#coll
128 * @type {string} 129 */ 130 axis.coll = coll; 131 fireEvent(this, 'init', { userOptions: userOptions }); 132 // Needed in setOptions 133 axis.opposite = pick(userOptions.opposite, axis.opposite); 134 /** 135 * The side on which the axis is rendered. 0 is top, 1 is right, 2 136 * is bottom and 3 is left. 137 * 138 * @name Highcharts.Axis#side 139 * @type {number} 140 */ 141 axis.side = pick(userOptions.side, axis.side, (horiz ? 142 (axis.opposite ? 0 : 2) : // Top : bottom 143 (axis.opposite ? 1 : 3)) // Right : left 144 ); 145 /** 146 * Current options for the axis after merge of defaults and user's 147 * options. 148 * 149 * @name Highcharts.Axis#options 150 * @type {Highcharts.AxisOptions} 151 */ 152 axis.setOptions(userOptions); 153 const options = axis.options, labelsOptions = options.labels; 154 // Set the type and fire an event 155 axis.type ?? (axis.type = options.type || 'linear'); 156 axis.uniqueNames ?? (axis.uniqueNames = options.uniqueNames ?? true); 157 fireEvent(axis, 'afterSetType'); 158 /** 159 * User's options for this axis without defaults. 160 * 161 * @name Highcharts.Axis#userOptions 162 * @type {Highcharts.AxisOptions} 163 */ 164 axis.userOptions = userOptions; 165 axis.minPixelPadding = 0; 166 /** 167 * Whether the axis is reversed. Based on the `axis.reversed`, 168 * option, but inverted charts have reversed xAxis by default. 169 * 170 * @name Highcharts.Axis#reversed 171 * @type {boolean} 172 */ 173 axis.reversed = pick(options.reversed, axis.reversed); 174 axis.visible = options.visible; 175 axis.zoomEnabled = options.zoomEnabled; 176 // Initial categories 177 axis.hasNames = this.type === 'category' || options.categories === true; 178 /** 179 * If categories are present for the axis, names are used instead of 180 * numbers for that axis. 181 * 182 * Since Highcharts 3.0, categories can also be extracted by giving each 183 * point a name and setting axis type to `category`. However, if you 184 * have multiple series, best practice remains defining the `categories` 185 * array. 186 * 187 * @see [xAxis.categories](/highcharts/xAxis.categories) 188 * 189 * @name Highcharts.Axis#categories 190 * @type {Array<string>} 191 * @readonly 192 */ 193 axis.categories = (isArray(options.categories) && options.categories) || 194 (axis.hasNames ? [] : void 0); 195 if (!axis.names) { // Preserve on update (#3830) 196 axis.names = []; 197 axis.names.keys = {}; 198 } 199 // Placeholder for plotlines and plotbands groups 200 axis.plotLinesAndBandsGroups = {}; 201 // Shorthand types 202 axis.positiveValuesOnly = !!axis.logarithmic; 203 // Flag, if axis is linked to another axis 204 axis.isLinked = defined(options.linkedTo); 205 /** 206 * List of major ticks mapped by position on axis. 207 * 208 * @see {@link Highcharts.Tick} 209 * 210 * @name Highcharts.Axis#ticks 211 * @type {Highcharts.Dictionary<Highcharts.Tick>} 212 */ 213 axis.ticks = {}; 214 axis.labelEdge = []; 215 /** 216 * List of minor ticks mapped by position on the axis. 217 * 218 * @see {@link Highcharts.Tick} 219 * 220 * @name Highcharts.Axis#minorTicks 221 * @type {Highcharts.Dictionary<Highcharts.Tick>} 222 */ 223 axis.minorTicks = {}; 224 // List of plotLines/Bands 225 axis.plotLinesAndBands = []; 226 // Alternate bands 227 axis.alternateBands = {}; 228 /** 229 * The length of the axis in terms of pixels. 230 * 231 * @name Highcharts.Axis#len 232 * @type {number} 233 */ 234 axis.len ?? (axis.len = 0); 235 axis.minRange = axis.userMinRange = options.minRange || options.maxZoom; 236 axis.range = options.range; 237 axis.offset = options.offset || 0; 238 /** 239 * The maximum value of the axis. In a logarithmic axis, this is the 240 * logarithm of the real value, and the real value can be obtained from 241 * {@link Axis#getExtremes}. 242 * 243 * @name Highcharts.Axis#max 244 * @type {number|undefined} 245 */ 246 axis.max = void 0; 247 /** 248 * The minimum value of the axis. In a logarithmic axis, this is the 249 * logarithm of the real value, and the real value can be obtained from 250 * {@link Axis#getExtremes}. 251 * 252 * @name Highcharts.Axis#min 253 * @type {number|undefined} 254 */ 255 axis.min = void 0; 256 /** 257 * The processed crosshair options. 258 * 259 * @name Highcharts.Axis#crosshair 260 * @type {boolean|Highcharts.AxisCrosshairOptions} 261 */ 262 const crosshair = pick(options.crosshair, splat(chart.options.tooltip.crosshairs)[isXAxis ? 0 : 1]); 263 axis.crosshair = crosshair === true ? {} : crosshair; 264 // Register. Don't add it again on Axis.update(). 265 if (chart.axes.indexOf(axis) === -1) { // 266 if (isXAxis) { // #2713 267 chart.axes.splice(chart.xAxis.length, 0, axis); 268 } 269 else { 270 chart.axes.push(axis); 271 } 272 insertItem(this, chart[this.coll]); 273 } 274 chart.orderItems(axis.coll); 275 /** 276 * All series associated to the axis. 277 * 278 * @name Highcharts.Axis#series 279 * @type {Array<Highcharts.Series>} 280 */ 281 axis.series = axis.series || []; // Populated by Series 282 // Reversed axis 283 if (chart.inverted && 284 !axis.isZAxis && 285 isXAxis && 286 !defined(axis.reversed)) { 287 axis.reversed = true; 288 } 289 axis.labelRotation = isNumber(labelsOptions.rotation) ? 290 labelsOptions.rotation : 291 void 0; 292 // Register event listeners 293 registerEventOptions(axis, options); 294 fireEvent(this, 'afterInit'); 295 } 296 /** 297 * Merge and set options. 298 * 299 * @private 300 * @function Highcharts.Axis#setOptions 301 * 302 * @param {Highcharts.AxisOptions} userOptions 303 * Axis options. 304 * 305 * @emits Highcharts.Axis#event:afterSetOptions 306 */ 307 setOptions(userOptions) { 308 const sideSpecific = this.horiz ? 309 // Top and bottom axis defaults 310 { 311 labels: { 312 autoRotation: [-45], 313 padding: 3 314 }, 315 margin: 15 316 } : 317 // Left and right axis, title rotated 90 or 270 degrees 318 // respectively 319 { 320 labels: { 321 padding: 1 322 }, 323 title: { 324 rotation: 90 * this.side 325 } 326 }; 327 this.options = merge(sideSpecific, defaultOptions[this.coll], userOptions); 328 fireEvent(this, 'afterSetOptions', { userOptions }); 329 } 330 /** 331 * The default label formatter. The context is a special config object for 332 * the label. In apps, use the 333 * [labels.formatter](https://api.highcharts.com/highcharts/xAxis.labels.formatter) 334 * instead, except when a modification is needed. 335 * 336 * @function Highcharts.Axis#defaultLabelFormatter 337 * 338 * @param {Highcharts.AxisLabelsFormatterContextObject} this 339 * Formatter context of axis label. 340 * 341 * @param {Highcharts.AxisLabelsFormatterContextObject} [ctx] 342 * Formatter context of axis label. 343 * 344 * @return {string} 345 * The formatted label content. 346 */ 347 defaultLabelFormatter() { 348 const axis = this.axis, chart = this.chart, { numberFormatter } = chart, value = isNumber(this.value) ? this.value : NaN, time = axis.chart.time, categories = axis.categories, dateTimeLabelFormat = this.dateTimeLabelFormat, lang = defaultOptions.lang, numericSymbol
348s = lang.numericSymbols, numSymMagnitude = lang.numericSymbolMagnitude || 1000, 349 // Make sure the same symbol is added for all labels on a linear 350 // axis 351 numericSymbolDetector = axis.logarithmic ? 352 Math.abs(value) : 353 axis.tickInterval; 354 let i = numericSymbols && numericSymbols.length, multi, ret; 355 if (categories) { 356 ret = `${this.value}`; 357 } 358 else if (dateTimeLabelFormat) { // Datetime axis 359 ret = time.dateFormat(dateTimeLabelFormat, value, true); 360 } 361 else if (i && 362 numericSymbols && 363 numericSymbolDetector >= 1000) { 364 // Decide whether we should add a numeric symbol like k (thousands) 365 // or M (millions). If we are to enable this in tooltip or other 366 // places as well, we can move this logic to the numberFormatter and 367 // enable it by a parameter. 368 while (i-- && typeof ret === 'undefined') { 369 multi = Math.pow(numSymMagnitude, i + 1); 370 if ( 371 // Only accept a numeric symbol when the distance is more 372 // than a full unit. So for example if the symbol is k, we 373 // don't accept numbers like 0.5k. 374 numericSymbolDetector >= multi && 375 // Accept one decimal before the symbol. Accepts 0.5k but 376 // not 0.25k. How does this work with the previous? 377 (value * 10) % multi === 0 && 378 numericSymbols[i] !== null && 379 value !== 0) { // #5480 380 ret = numberFormatter(value / multi, -1) + numericSymbols[i]; 381 } 382 } 383 } 384 if (typeof ret === 'undefined') { 385 if (Math.abs(value) >= 10000) { // Add thousands separators 386 ret = numberFormatter(value, -1); 387 } 388 else { // Small numbers 389 ret = numberFormatter(value, -1, void 0, ''); // #2466 390 } 391 } 392 return ret; 393 } 394 /** 395 * Get the minimum and maximum for the series of each axis. The function 396 * analyzes the axis series and updates `this.dataMin` and `this.dataMax`. 397 * 398 * @private 399 * @function Highcharts.Axis#getSeriesExtremes 400 * 401 * @emits Highcharts.Axis#event:afterGetSeriesExtremes 402 * @emits Highcharts.Axis#event:getSeriesExtremes 403 */ 404 getSeriesExtremes() { 405 const axis = this; 406 let xExtremes; 407 fireEvent(this, 'getSeriesExtremes', null, function () { 408 axis.hasVisibleSeries = false; 409 // Reset properties in case we're redrawing (#3353) 410 axis.dataMin = axis.dataMax = axis.threshold = void 0; 411 axis.softThreshold = !axis.isXAxis; 412 // Loop through this axis' series 413 axis.series.forEach((series) => { 414 if (series.reserveSpace()) { 415 const seriesOptions = series.options; 416 let xData, threshold = seriesOptions.threshold, seriesDataMin, seriesDataMax; 417 axis.hasVisibleSeries = true; 418 // Validate threshold in logarithmic axes 419 if (axis.positiveValuesOnly && (threshold || 0) <= 0) { 420 threshold = void 0; 421 } 422 // Get dataMin and dataMax for X axes 423 if (axis.isXAxis) { 424 xData = series.getColumn('x'); 425 if (xData.length) { 426 xData = axis.logarithmic ? 427 xData.filter((x) => x > 0) : 428 xData; 429 xExtremes = series.getXExtremes(xData); 430 // If xData contains values which is not numbers, 431 // then filter them out. To prevent performance hit, 432 // we only do this after we have already found 433 // seriesDataMin because in most cases all data is 434 // valid. #5234. 435 seriesDataMin = xExtremes.min; 436 seriesDataMax = xExtremes.max; 437 if (!isNumber(seriesDataMin) && 438 // #5010:
vendor: 6,362 bytes, lines 439-588
439 !(seriesDataMin instanceof Date)) { 440 xData = xData.filter(isNumber); 441 xExtremes = series.getXExtremes(xData); 442 // Do it again with valid data 443 seriesDataMin = xExtremes.min; 444 seriesDataMax = xExtremes.max; 445 } 446 if (xData.length) { 447 axis.dataMin = Math.min(pick(axis.dataMin, seriesDataMin), seriesDataMin); 448 axis.dataMax = Math.max(pick(axis.dataMax, seriesDataMax), seriesDataMax); 449 } 450 } 451 // Get dataMin and dataMax for Y axes, as well as handle 452 // stacking and processed data 453 } 454 else { 455 // Get this particular series extremes 456 const dataExtremes = series.applyExtremes(); 457 // Get the dataMin and dataMax so far. If percentage is 458 // used, the min and max are always 0 and 100. If 459 // seriesDataMin and seriesDataMax is null, then series 460 // doesn't have active y data, we continue with nulls 461 if (isNumber(dataExtremes.dataMin)) { 462 seriesDataMin = dataExtremes.dataMin; 463 axis.dataMin = Math.min(pick(axis.dataMin, seriesDataMin), seriesDataMin); 464 } 465 if (isNumber(dataExtremes.dataMax)) { 466 seriesDataMax = dataExtremes.dataMax; 467 axis.dataMax = Math.max(pick(axis.dataMax, seriesDataMax), seriesDataMax); 468 } 469 // Adjust to threshold 470 if (defined(threshold)) { 471 axis.threshold = threshold; 472 } 473 // If any series has a hard threshold, it takes 474 // precedence 475 if (!seriesOptions.softThreshold || 476 axis.positiveValuesOnly) { 477 axis.softThreshold = false; 478 } 479 } 480 } 481 }); 482 }); 483 fireEvent(this, 'afterGetSeriesExtremes'); 484 } 485 /** 486 * Translate from axis value to pixel position on the chart, or back. Use 487 * the `toPixels` and `toValue` functions in applications. 488 * 489 * @private 490 * @function Highcharts.Axis#translate 491 */ 492 translate(val, backwards, cvsCoord, old, handleLog, pointPlacement) { 493 const axis = (this.linkedParent || this), // #1417 494 localMin = (old && axis.old ? axis.old.min : axis.min); 495 if (!isNumber(localMin)) { 496 return NaN; 497 } 498 const minPixelPadding = axis.minPixelPadding, doPostTranslate = (axis.isOrdinal || 499 axis.brokenAxis?.hasBreaks || 500 (axis.logarithmic && handleLog)) && axis.lin2val; 501 let sign = 1, cvsOffset = 0, localA = old && axis.old ? axis.old.transA : axis.transA, returnValue = 0; 502 if (!localA) { 503 localA = axis.transA; 504 } 505 // In vertical axes, the canvas coordinates start from 0 at the top like 506 // in SVG. 507 if (cvsCoord) { 508 sign *= -1; // Canvas coordinates inverts the value 509 cvsOffset = axis.len; 510 } 511 // Handle reversed axis 512 if (axis.reversed) { 513 sign *= -1; 514 cvsOffset -= sign * (axis.sector || axis.len); 515 } 516 // From pixels to value 517 if (backwards) { // Reverse translation 518 val = val * sign + cvsOffset; 519 val -= minPixelPadding; 520 // From chart pixel to value: 521 returnValue = val / localA + localMin; 522 if (doPostTranslate) { // Log, ordinal and broken axis 523 returnValue = axis.lin2val(returnValue); 524 } 525 // From value to pixels 526 } 527 else { 528 if (doPostTranslate) { // Log, ordinal and broken axis 529 val = axis.val2lin(val); 530 } 531 const value = sign * (val - localMin) * localA; 532 returnValue = value + 533 cvsOffset + 534 (sign * minPixelPadding) + 535 (isNumber(pointPlacement) ? localA * pointPlacement : 0); 536 if (!axis.isRadial) { 537 returnValue = correctFloat(returnValue); 538 } 539 } 540 return returnValue; 541 } 542 /** 543 * Translate a value in terms of axis units into pixels within the chart. 544 * 545 * @function Highcharts.Axis#toPixels 546 * 547 * @param {number|string} value 548 * A value in terms of axis units. For datetime axes, a timestamp or 549 * date/time string is accepted. 550 * 551 * @param {boolean} [paneCoordinates=false] 552 * Whether to return the pixel coordinate relative to the chart or just the 553 * axis/pane itself. 554 * 555 * @return {number} 556 * Pixel position of the value on the chart or axis. 557 */ 558 toPixels(value, paneCoordinates) { 559 return this.translate(this.chart?.time.parse(value) ?? NaN, false, !this.horiz, void 0, true) + (paneCoordinates ? 0 : this.pos); 560 } 561 /** 562 * Translate a pixel position along the axis to a value in terms of axis 563 * units. 564 * 565 * @function Highcharts.Axis#toValue 566 * 567 * @param {number} pixel 568 * The pixel value coordinate. 569 * 570 * @param {boolean} [paneCoordinates=false] 571 * Whether the input pixel is relative to the chart or just the axis/pane 572 * itself. 573 * 574 * @return {number} 575 * The axis value. 576 */ 577 toValue(pixel, paneCoordinates) { 578 return this.translate(pixel - (paneCoordinates ? 0 : this.pos), true, !this.horiz, void 0, true); 579 } 580 /** 581 * Create the path for a plot line that goes from the given value on 582 * this axis, across the plot to the opposite side. Also used internally for 583 * grid lines and crosshairs. 584 * 585 * @function Highcharts.Axis#getPlotLinePath 586 * 587 * @param {Highcharts.AxisPlotLinePathOptionsObject} options 588 * Options for the path.
vendor: 4,445 bytes, lines 589-694
589 * 590 * @return {Highcharts.SVGPathArray|null} 591 * The SVG path definition for the plot line. 592 */ 593 getPlotLinePath(options) { 594 const axis = this, chart = axis.chart, axisLeft = axis.left, axisTop = axis.top, old = options.old, value = options.value, lineWidth = options.lineWidth, cHeight = (old && chart.oldChartHeight) || chart.chartHeight, cWidth = (old && chart.oldChartWidth) || chart.chartWidth, transB = axis.transB; 595 let translatedValue = options.translatedValue, force = options.force, x1, y1, x2, y2, skip; 596 // eslint-disable-next-line valid-jsdoc 597 /** 598 * Check if x is between a and b. If not, either move to a/b 599 * or skip, depending on the force parameter. 600 * @private 601 */ 602 function between(x, a, b) { 603 if (force !== 'pass' && (x < a || x > b)) { 604 if (force) { 605 x = clamp(x, a, b); 606 } 607 else { 608 skip = true; 609 } 610 } 611 return x; 612 } 613 const evt = { 614 value, 615 lineWidth, 616 old, 617 force, 618 acrossPanes: options.acrossPanes, 619 translatedValue 620 }; 621 fireEvent(this, 'getPlotLinePath', evt, function (e) { 622 translatedValue = pick(translatedValue, axis.translate(value, void 0, void 0, old)); 623 // Keep the translated value within sane bounds, and avoid Infinity 624 // to fail the isNumber test (#7709). 625 translatedValue = clamp(translatedValue, -1e9, 1e9); 626 x1 = x2 = translatedValue + transB; 627 y1 = y2 = cHeight - translatedValue - transB; 628 if (!isNumber(translatedValue)) { // No min or max 629 skip = true; 630 force = false; // #7175, don't force it when path is invalid 631 } 632 else if (axis.horiz) { 633 y1 = axisTop; 634 y2 = cHeight - axis.bottom + (axis.options.isInternal ? 635 0 : 636 (chart.scrollablePixelsY || 0)); // #20354, scrollablePixelsY shouldn't be used for navigator 637 x1 = x2 = between(x1, axisLeft, axisLeft + axis.width); 638 } 639 else { 640 x1 = axisLeft; 641 x2 = cWidth - axis.right + (chart.scrollablePixelsX || 0); 642 y1 = y2 = between(y1, axisTop, axisTop + axis.height); 643 } 644 e.path = skip && !force ? 645 void 0 : 646 chart.renderer.crispLine([['M', x1, y1], ['L', x2, y2]], lineWidth || 1); 647 }); 648 return evt.path; 649 } 650 /** 651 * Internal function to get the tick positions of a linear axis to round 652 * values like whole tens or every five. 653 * 654 * @function Highcharts.Axis#getLinearTickPositions 655 * 656 * @param {number} tickInterval 657 * The normalized tick interval. 658 * 659 * @param {number} min 660 * Axis minimum. 661 * 662 * @param {number} max 663 * Axis maximum. 664 * 665 * @return {Array<number>} 666 * An array of axis values where ticks should be placed. 667 */ 668 getLinearTickPositions(tickInterval, min, max) { 669 const roundedMin = correctFloat(Math.floor(min / tickInterval) * tickInterval), roundedMax = correctFloat(Math.ceil(max / tickInterval) * tickInterval), tickPositions = []; 670 let pos, lastPos, precision; 671 // When the precision is higher than what we filter out in 672 // correctFloat, skip it (#6183). 673 if (correctFloat(roundedMin + tickInterval) === roundedMin) { 674 precision = 20; 675 } 676 // For single points, add a tick regardless of the relative position 677 // (#2662, #6274) 678 if (this.single) { 679 return [min]; 680 } 681 // Populate the intermediate values 682 pos = roundedMin; 683 while (pos <= roundedMax) { 684 // Place the tick on the rounded value 685 tickPositions.push(pos); 686 // Always add the raw tickInterval, not the corrected one. 687 pos = correctFloat(pos + tickInterval, precision); 688 // If the interval is not big enough in the current min - max range 689 // to actually increase the loop variable, we need to break out to 690 // prevent endless loop. Issue #619 691 if (pos === lastPos) { 692 break; 693 } 694 // Record the last value
vendor: 4,902 bytes, lines 695-802
695 lastPos = pos; 696 } 697 return tickPositions; 698 } 699 /** 700 * Resolve the new minorTicks/minorTickInterval options into the legacy 701 * loosely typed minorTickInterval option. 702 * 703 * @function Highcharts.Axis#getMinorTickInterval 704 * 705 * @return {number|"auto"|null} 706 * Legacy option 707 */ 708 getMinorTickInterval() { 709 const { minorTicks, minorTickInterval } = this.options; 710 if (minorTicks === true) { 711 return pick(minorTickInterval, 'auto'); 712 } 713 if (minorTicks === false) { 714 return; 715 } 716 return minorTickInterval; 717 } 718 /** 719 * Internal function to return the minor tick positions. For logarithmic 720 * axes, the same logic as for major ticks is reused. 721 * 722 * @function Highcharts.Axis#getMinorTickPositions 723 * 724 * @return {Array<number>} 725 * An array of axis values where ticks should be placed. 726 */ 727 getMinorTickPositions() { 728 const axis = this, options = axis.options, tickPositions = axis.tickPositions, minorTickInterval = axis.minorTickInterval, pointRangePadding = axis.pointRangePadding || 0, min = (axis.min || 0) - pointRangePadding, // #1498 729 max = (axis.max || 0) + pointRangePadding, // #1498 730 range = axis.brokenAxis?.hasBreaks ? 731 axis.brokenAxis.unitLength : max - min; 732 let minorTickPositions = [], pos; 733 // If minor ticks get too dense, they are hard to read, and may cause 734 // long running script. So we don't draw them. 735 if (range && range / minorTickInterval < axis.len / 3) { // #3875 736 const logarithmic = axis.logarithmic; 737 if (logarithmic) { 738 // For each interval in the major ticks, compute the minor ticks 739 // separately. 740 this.paddedTicks.forEach(function (_pos, i, paddedTicks) { 741 if (i) { 742 minorTickPositions.push.apply(minorTickPositions, logarithmic.getLogTickPositions(minorTickInterval, paddedTicks[i - 1], paddedTicks[i], true)); 743 } 744 }); 745 } 746 else if (axis.dateTime && 747 this.getMinorTickInterval() === 'auto') { // #1314 748 minorTickPositions = minorTickPositions.concat(axis.getTimeTicks(axis.dateTime.normalizeTimeTickInterval(minorTickInterval), min, max, options.startOfWeek)); 749 } 750 else { 751 for (pos = min + (tickPositions[0] - min) % minorTickInterval; pos <= max; pos += minorTickInterval) { 752 // Very, very, tight grid lines (#5771) 753 if (pos === minorTickPositions[0]) { 754 break; 755 } 756 minorTickPositions.push(pos); 757 } 758 } 759 } 760 if (minorTickPositions.length !== 0) { 761 axis.trimTicks(minorTickPositions); // #3652 #3743 #1498 #6330 762 } 763 return minorTickPositions; 764 } 765 /** 766 * Adjust the min and max for the minimum range. Keep in mind that the 767 * series data is not yet processed, so we don't have information on data 768 * cropping and grouping, or updated `axis.pointRange` or 769 * `series.pointRange`. The data can't be processed until we have finally 770 * established min and max. 771 * 772 * @private 773 * @function Highcharts.Axis#adjustForMinRange 774 */ 775 adjustForMinRange() { 776 const axis = this, options = axis.options, logarithmic = axis.logarithmic, time = axis.chart.time; 777 let { max, min, minRange } = axis, zoomOffset, spaceAvailable, closestDataRange, minArgs, maxArgs; 778 // Set the automatic minimum range based on the closest point distance 779 if (axis.isXAxis && 780 typeof minRange === 'undefined' && 781 !logarithmic) { 782 if (defined(options.min) || 783 defined(options.max) || 784 defined(options.floor) || 785 defined(options.ceiling)) { 786 // Setting it to null, as opposed to undefined, signals we don't 787 // run this block again as per the condition above. 788 minRange = null; 789 } 790 else { 791 // Find the closest distance between raw data points, as opposed 792 // to closestPointRange that applies to processed points 793 // (cropped and grouped) 794 closestDataRange = getClosestDistance(axis.series.map((s) => { 795 const xData = s.getColumn('x'); 796 // If xIncrement, we only need to measure the two first 797 // points to get the distance. Saves processing time. 798 return s.xIncrement ? xData.slice(0, 2) : xData; 799 })) || 0; 800 minRange = Math.min(closestDataRange * 5, axis.dataMax - axis.dataMin); 801 } 802 }
803 // If minRange is exceeded, adjust 804 if (isNumber(max) && 805 isNumber(min) && 806 isNumber(minRange) && 807 max - min < minRange) { 808 spaceAvailable = 809 axis.dataMax - axis.dataMin >= 810 minRange; 811 zoomOffset = (minRange - max + min) / 2; 812 // If min and max options have been set, don't go beyond it 813 minArgs = [ 814 min - zoomOffset, 815 time.parse(options.min) ?? (min - zoomOffset) 816 ]; 817 // If space is available, stay within the data range 818 if (spaceAvailable) { 819 minArgs[2] = logarithmic ? 820 logarithmic.log2lin(axis.dataMin) : 821 axis.dataMin; 822 } 823 min = arrayMax(minArgs); 824 maxArgs = [ 825 min + minRange, 826 time.parse(options.max) ?? (min + minRange) 827 ]; 828 // If space is available, stay within the data range 829 if (spaceAvailable) { 830 maxArgs[2] = logarithmic ? 831 logarithmic.log2lin(axis.dataMax) : 832 axis.dataMax; 833 } 834 max = arrayMin(maxArgs); 835 // Now if the max is adjusted, adjust the min back 836 if (max - min < minRange) { 837 minArgs[0] = max - minRange; 838 minArgs[1] = time.parse(options.min) ?? (max - minRange); 839 min = arrayMax(minArgs); 840 } 841 } 842 // Record modified extremes 843 axis.minRange = minRange; 844 axis.min = min; 845 axis.max = max; 846 } 847 /** 848 * Find the closestPointRange across all series, including the single data 849 * series. 850 * 851 * @private 852 * @function Highcharts.Axis#getClosest 853 */ 854 getClosest() { 855 let closestSingleDistance, closestDistance; 856 if (this.categories) { 857 closestDistance = 1; 858 } 859 else { 860 const singleXs = []; 861 this.series.forEach(function (series) { 862 const seriesClosest = series.closestPointRange, xData = series.getColumn('x'); 863 if (xData.length === 1) { 864 singleXs.push(xData[0]); 865 } 866 else if (series.sorted && 867 defined(seriesClosest) && 868 series.reserveSpace()) { 869 closestDistance = defined(closestDistance) ? 870 Math.min(closestDistance, seriesClosest) : 871 seriesClosest; 872 } 873 }); 874 if (singleXs.length) { 875 singleXs.sort((a, b) => a - b); 876 closestSingleDistance = getClosestDistance([singleXs]); 877 } 878 } 879 if (closestSingleDistance && closestDistance) { 880 return Math.min(closestSingleDistance, closestDistance); 881 } 882 return closestSingleDistance || closestDistance; 883 } 884 /** 885 * When a point name is given and no x, search for the name in the existing 886 * categories, or if categories aren't provided, search names or create a 887 * new category (#2522). 888 * 889 * @private 890 * @function Highcharts.Axis#nameToX 891 * 892 * @param {Highcharts.Point} point 893 * The point to inspect. 894 * 895 * @return {number} 896 * The X value that the point is given. 897 */ 898 nameToX(point) { 899 const explicitCategories = isArray(this.options.categories), names = explicitCategories ? this.categories : this.names; 900 let nameX = point.options.x, x; 901 point.series.requireSorting = false; 902 if (!defined(nameX)) { 903 nameX = this.uniqueNames && names ? 904 (explicitCategories ? 905 names.indexOf(point.name) : 906 pick(names.keys[point.name], -1)) : 907 point.series.autoIncrement(); 908 } 909 if (nameX === -1) { // Not found in current categories 910 if (!explicitCategories && names) { 911 x = names.length; 912 } 913 } 914 else if (isNumber(nameX)) { 915 x = nameX; 916 } 917 // Write the last point's name to the names array 918 if (typeof x !== 'undefined') { 919 this.names[x] = point.name; 920 // Backwards mapping is much faster than array searching (#7725) 921 this.names.keys[point.name] = x; 922 } 923 else if (point.x) { 924 x = point.x; // #17438 925 } 926 return x; 927 } 928 /** 929 * When changes have been done to series data, update the axis.names. 930 * 931 * @private 932 * @function Highcharts.Axis#updateNames 933 */ 934 updateNames() { 935 const axis = this, names = this.names, i = names.length; 936 if (i > 0) {
937 Object.keys(names.keys).forEach(function (key) { 938 delete (names.keys)[key]; 939 }); 940 names.length = 0; 941 this.minRange = this.userMinRange; // Reset 942 (this.series || []).forEach((series) => { 943 // Reset incrementer (#5928) 944 series.xIncrement = null; 945 // When adding a series, points are not yet generated 946 if (!series.points || series.isDirtyData) { 947 // When we're updating the series with data that is longer 948 // than it was, and cropThreshold is passed, we need to make 949 // sure that the axis.max is increased _before_ running the 950 // premature processData. Otherwise this early iteration of 951 // processData will crop the points to axis.max, and the 952 // names array will be too short (#5857). 953 axis.max = Math.max(axis.max || 0, series.dataTable.rowCount - 1); 954 series.processData(); 955 series.generatePoints(); 956 } 957 const xData = series.getColumn('x').slice(); 958 series.data.forEach((point, i) => { 959 let x = xData[i]; 960 if (point?.options && 961 typeof point.name !== 'undefined' // #9562 962 ) { 963 x = axis.nameToX(point); 964 if (typeof x !== 'undefined' && x !== point.x) { 965 xData[i] = point.x = x; 966 } 967 } 968 }); 969 series.dataTable.setColumn('x', xData); 970 }); 971 } 972 } 973 /** 974 * Update translation information. 975 * 976 * @private 977 * @function Highcharts.Axis#setAxisTranslation 978 * 979 * @emits Highcharts.Axis#event:afterSetAxisTranslation 980 */ 981 setAxisTranslation() { 982 const axis = this, range = axis.max - axis.min, linkedParent = axis.linkedParent, hasCategories = !!axis.categories, isXAxis = axis.isXAxis; 983 let pointRange = axis.axisPointRange || 0, closestPointRange, minPointOffset = 0, pointRangePadding = 0, ordinalCorrection, transA = axis.transA; 984 // Adjust translation for padding. Y axis with categories need to go 985 // through the same (#1784). 986 if (isXAxis || hasCategories || pointRange) { 987 // Get the closest points 988 closestPointRange = axis.getClosest(); 989 if (linkedParent) { 990 minPointOffset = linkedParent.minPointOffset; 991 pointRangePadding = linkedParent.pointRangePadding; 992 } 993 else { 994 axis.series.forEach(function (series) { 995 const seriesPointRange = hasCategories ? 996 1 : 997 (isXAxis ? 998 pick(series.options.pointRange, closestPointRange, 0) : 999 (axis.axisPointRange || 0)), // #2806 1000 pointPlacement = series.options.pointPlacement; 1001 pointRange = Math.max(pointRange, seriesPointRange); 1002 if (!axis.single || hasCategories) { 1003 // TODO: series should internally set x- and y- 1004 // pointPlacement to simplify this logic. 1005 const isPointPlacementAxis = series.is('xrange') ? 1006 !isXAxis : 1007 isXAxis; 1008 // The `minPointOffset` is the value padding to the left 1009 // of the axis in order to make room for points with a 1010 // pointRange, typically columns, or line/scatter points 1011 // on a category axis. When the `pointPlacement` option 1012 // is 'between' or 'on', this padding does not apply. 1013 minPointOffset = Math.max(minPointOffset, isPointPlacementAxis && isString(pointPlacement) ? 1014 0 : 1015 seriesPointRange / 2); 1016 // Determine the total padding needed to the length of 1017 // the axis to make room for the pointRange. If the 1018 // series' pointPlacement is 'on', no padding is added. 1019 pointRangePadding = Math.max(pointRangePadding, isPointPlacementAxis && pointPlacement === 'on' ? 1020 0 : 1021 seriesPointRange); 1022 } 1023 }); 1024 } 1025 // Record minPointOffset and pointRangePadding 1026 ordinalCorrection = (axis.ordinal && axis.ordinal.slope && closestPointRange) ? 1027 axis.ordinal.slope / closestPointRange : 1028 1; // #988, #1853 1029 axis.minPointOffset = minPointOffset = 1030 minPointOffset * ordinalCorrection; 1031 axis.pointRangePadding = 1032 pointRangePadding = pointRangePadding * ordinalCorrection; 1033 // The `pointRange` is the width reserved for each point, like in a 1034 // column chart 1035 axis.pointRange = Math.min(pointRange, axis.single && hasCategories ? 1 : range); 1036 // The `closestPointRange` is the closest distance between points. 1037 // In columns it is mostly equal to pointRange, but in lines 1038 // pointRange is 0 while closestPointRange is some other value 1039 if (isXAxis && closestPointRange) { 1040 axis.closestPointRange = closestPointRange; 1041 } 1042 } 1043 // Secondary values 1044 axis.translationSlope = axis.transA = transA = 1045 axis.staticScale || 1046 axis.len / ((range + pointRangePadding) || 1); 1047 // Translation addend 1048 axis.transB = axis.horiz ? axis.left : axis.bottom; 1049 axis.minPixelPadding = transA * minPointOffset; 1050 fireEvent(this, 'afterSetAxisTranslation'); 1051 } 1052 /** 1053 * @private 1054 * @function Highcharts.Axis#minFromRange 1055 */ 1056 minFromRange() { 1057 const { max, min } = this; 1058 return isNumber(max) && isNumber(min) && max - min || void 0; 1059 } 1060 /** 1061 * Set the tick positions to round values and optionally extend the extremes 1062 * to the nearest tick. 1063 * 1064 * @private 1065 * @function Highcharts.Axis#setTickInterval 1066 * 1067 * @param {boolean} secondPass 1068 * TO-DO: parameter description 1069 * 1070 * @emits Highcharts.Axis#event:foundExtremes 1071 */ 1072 setTickInterval(secondPass) { 1073 const axis = this, { categories, chart, dataMax, dataMin, dateTime, isXAxis, logarithmic, options, softThreshold } = axis, time = chart.time, threshold = isNumber(axis.threshold) ? axis.threshold : void 0, minRange = axis.minRange || 0, { ceiling, floor, linkedTo, softMax, softMin } = options, linkedParent = isNumber(linkedTo) && chart[axis.coll]?.[linkedTo], tickPixelIntervalOption = options.tickPixelInterval; 1074 let maxPadding = options.maxPadding, minPadding = options.minPadding, range = 0, linkedParentExtremes, 1075 // Only non-negative tickInterval is valid, #12961 1076 tickIntervalOption = isNumber(options.tickInterval) && options.tickInterval >= 0 ? 1077 options.tickInterval : void 0, thresholdMin, thresholdMax, hardMin, hardMax; 1078 if (!dateTime && !categories && !linkedParent) { 1079 this.getTickAmount(); 1080 } 1081 // Min or max set either by zooming/setExtremes or initial options 1082 hardMin = pick(axis.userMin, time.parse(options.min)); 1083 hardMax = pick(axis.userMax, time.parse(options.max)); 1084 // Linked axis gets the extremes from the parent axis 1085 if (linkedParent) { 1086 axis.linkedParent = linkedParent; 1087 linkedParentExtremes = linkedParent.getExtremes(); 1088 axis.min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin); 1089 axis.max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax);
vendor: 6,691 bytes, lines 1090-1241
1090 if (this.type !== linkedParent.type) { 1091 // Can't link axes of different type 1092 error(11, true, chart); 1093 } 1094 // Initial min and max from the extreme data values 1095 } 1096 else { 1097 // Adjust to hard threshold 1098 if (softThreshold && 1099 defined(threshold) && 1100 isNumber(dataMax) && 1101 isNumber(dataMin)) { 1102 if (dataMin >= threshold) { 1103 thresholdMin = threshold; 1104 minPadding = 0; 1105 } 1106 else if (dataMax <= threshold) { 1107 thresholdMax = threshold; 1108 maxPadding = 0; 1109 } 1110 } 1111 axis.min = pick(hardMin, thresholdMin, dataMin); 1112 axis.max = pick(hardMax, thresholdMax, dataMax); 1113 } 1114 if (isNumber(axis.max) && isNumber(axis.min)) { 1115 if (logarithmic) { 1116 if (axis.positiveValuesOnly && 1117 !secondPass && 1118 Math.min(axis.min, pick(dataMin, axis.min)) <= 0) { // #978 1119 // Can't plot negative values on log axis 1120 error(10, true, chart); 1121 } 1122 // The correctFloat cures #934, float errors on full tens. But 1123 // it was too aggressive for #4360 because of conversion back to 1124 // lin, therefore use precision 15. 1125 axis.min = correctFloat(logarithmic.log2lin(axis.min), 16); 1126 axis.max = correctFloat(logarithmic.log2lin(axis.max), 16); 1127 } 1128 // Handle zoomed range 1129 if (axis.range && isNumber(dataMin)) { 1130 // #618, #6773: 1131 axis.userMin = axis.min = hardMin = Math.max(dataMin, axis.minFromRange() || 0); 1132 axis.userMax = hardMax = axis.max; 1133 axis.range = void 0; // Don't use it when running setExtremes 1134 } 1135 } 1136 // Hook for Highcharts Stock Scroller and bubble axis padding 1137 fireEvent(axis, 'foundExtremes'); 1138 // Adjust min and max for the minimum range 1139 axis.adjustForMinRange(); 1140 if (isNumber(axis.min) && isNumber(axis.max)) { 1141 // Handle options for floor, ceiling, softMin and softMax (#6359) 1142 if (!isNumber(axis.userMin) && 1143 isNumber(softMin) && 1144 softMin < axis.min) { 1145 axis.min = hardMin = softMin; // #6894 1146 } 1147 if (!isNumber(axis.userMax) && 1148 isNumber(softMax) && 1149 softMax > axis.max) { 1150 axis.max = hardMax = softMax; // #6894 1151 } 1152 // Pad the values to get clear of the chart's edges. To avoid 1153 // tickInterval taking the padding into account, we do this after 1154 // computing tick interval (#1337). 1155 if (!categories && 1156 !axis.axisPointRange && 1157 !axis.stacking?.usePercentage && 1158 !linkedParent) { 1159 range = axis.max - axis.min; 1160 if (range) { 1161 if (!defined(hardMin) && minPadding) { 1162 axis.min -= range * minPadding; 1163 } 1164 if (!defined(hardMax) && maxPadding) { 1165 axis.max += range * maxPadding; 1166 } 1167 } 1168 } 1169 if (!isNumber(axis.userMin) && isNumber(floor)) { 1170 axis.min = Math.max(axis.min, floor); 1171 } 1172 if (!isNumber(axis.userMax) && isNumber(ceiling)) { 1173 axis.max = Math.min(axis.max, ceiling); 1174 } 1175 // When the threshold is soft, adjust the extreme value only if the 1176 // data extreme and the padded extreme land on either side of the 1177 // threshold. For example, a series of [0, 1, 2, 3] would make the 1178 // yAxis add a tick for -1 because of the default `minPadding` and 1179 // `startOnTick` options. This is prevented by the `softThreshold` 1180 // option. 1181 if (softThreshold && 1182 isNumber(dataMin) && 1183 isNumber(dataMax)) { 1184 const numThreshold = threshold || 0; 1185 if (!defined(hardMin) && 1186 axis.min < numThreshold && 1187 dataMin >= numThreshold) { 1188 axis.min = options.minRange ? 1189 Math.min(numThreshold, axis.max - minRange) : 1190 numThreshold; 1191 } 1192 else if (!defined(hardMax) && 1193 axis.max > numThreshold && 1194 dataMax <= numThreshold) { 1195 axis.max = options.minRange ? 1196 Math.max(numThreshold, axis.min + minRange) : 1197 numThreshold; 1198 } 1199 } 1200 // If min is bigger than highest, or if max less than lowest value, 1201 // the chart should not render points. (#14417) 1202 if (!chart.polar && axis.min > axis.max) { 1203 if (defined(options.min)) { 1204 axis.max = axis.min; 1205 } 1206 else if (defined(options.max)) { 1207 axis.min = axis.max; 1208 } 1209 } 1210 range = axis.max - axis.min; 1211 } 1212 // Get tickInterval 1213 if (axis.min === axis.max || 1214 !isNumber(axis.min) || 1215 !isNumber(axis.max)) { 1216 axis.tickInterval = 1; 1217 } 1218 else if (linkedParent && 1219 !tickIntervalOption && 1220 tickPixelIntervalOption === linkedParent.options.tickPixelInterval) { 1221 axis.tickInterval = tickIntervalOption = linkedParent.tickInterval; 1222 } 1223 else { 1224 axis.tickInterval = pick(tickIntervalOption, this.tickAmount ? 1225 range / Math.max(this.tickAmount - 1, 1) : 1226 void 0, 1227 // For categorized axis, 1 is default, for linear axis use 1228 // tickPix 1229 categories ? 1230 1 : 1231 // Don't let it be more than the data range 1232 range * tickPixelIntervalOption / 1233 Math.max(axis.len, tickPixelIntervalOption)); 1234 } 1235 // Now we're finished detecting min and max, crop and group series data. 1236 // This is in turn needed in order to find tick positions in ordinal 1237 // axes. 1238 if (isXAxis && !secondPass) { 1239 const hasExtremesChanged = axis.min !== axis.old?.min || 1240 axis.max !== axis.old?.max; 1241 // First process all series assigned to that axis.
vendor: 5,927 bytes, lines 1242-1354
1242 axis.series.forEach(function (series) { 1243 // Allows filtering out points outside the plot area. 1244 series.forceCrop = series.forceCropping?.(); 1245 series.processData(hasExtremesChanged); 1246 }); 1247 // Then apply grouping if needed. The hasExtremesChanged helps to 1248 // decide if the data grouping should be skipped in the further 1249 // calculations #16319. 1250 fireEvent(this, 'postProcessData', { hasExtremesChanged }); 1251 } 1252 // Set the translation factor used in translate function 1253 axis.setAxisTranslation(); 1254 // Hook for ordinal axes and radial axes 1255 fireEvent(this, 'initialAxisTranslation'); 1256 // In column-like charts, don't cramp in more ticks than there are 1257 // points (#1943, #4184) 1258 if (axis.pointRange && !tickIntervalOption) { 1259 axis.tickInterval = Math.max(axis.pointRange, axis.tickInterval); 1260 } 1261 // Before normalizing the tick interval, handle minimum tick interval. 1262 // This applies only if tickInterval is not defined. 1263 const minTickInterval = pick(options.minTickInterval, 1264 // In datetime axes, don't go below the data interval, except when 1265 // there are scatter-like series involved (#13369). 1266 dateTime && 1267 !axis.series.some((s) => !s.sorted) ? 1268 axis.closestPointRange : 0); 1269 if (!tickIntervalOption && axis.tickInterval < minTickInterval) { 1270 axis.tickInterval = minTickInterval; 1271 } 1272 // For linear axes, normalize the interval 1273 if (!dateTime && !logarithmic && !tickIntervalOption) { 1274 axis.tickInterval = getNormalizedTickInterval(axis, axis.tickInterval); 1275 } 1276 // Prevent ticks from getting so close that we can't draw the labels 1277 if (!this.tickAmount) { 1278 axis.tickInterval = axis.unsquish(); 1279 } 1280 this.setTickPositions(); 1281 } 1282 /** 1283 * Now we have computed the normalized tickInterval, get the tick positions. 1284 * 1285 * @private 1286 * @function Highcharts.Axis#setTickPositions 1287 * 1288 * @emits Highcharts.Axis#event:afterSetTickPositions 1289 */ 1290 setTickPositions() { 1291 const axis = this, options = this.options, tickPositionsOption = options.tickPositions, tickPositioner = options.tickPositioner, minorTickIntervalOption = this.getMinorTickInterval(), allowEndOnTick = !this.isPanning, startOnTick = allowEndOnTick && options.startOnTick, endOnTick = allowEndOnTick && options.endOnTick; 1292 let tickPositions = [], tickPositionerResult; 1293 // Set the tickmarkOffset 1294 this.tickmarkOffset = (this.categories && 1295 options.tickmarkPlacement === 'between' && 1296 this.tickInterval === 1) ? 0.5 : 0; // #3202 1297 // When there is only one point, or all points have the same value on 1298 // this axis, then min and max are equal and tickPositions.length is 0 1299 // or 1. In this case, add some padding in order to center the point, 1300 // but leave it with one tick. #1337. 1301 this.single = 1302 this.min === this.max && 1303 defined(this.min) && 1304 !this.tickAmount && 1305 ( 1306 // Data is on integer (#6563) 1307 this.min % 1 === 0 || 1308 // Between integers and decimals are not allowed (#6274) 1309 options.allowDecimals !== false); 1310 /** 1311 * Contains the current positions that are laid out on the axis. The 1312 * positions are numbers in terms of axis values. In a category axis 1313 * they are integers, in a datetime axis they are also integers, but 1314 * designating milliseconds. 1315 * 1316 * This property is read only - for modifying the tick positions, use 1317 * the `tickPositioner` callback or [axis.tickPositions( 1318 * https://api.highcharts.com/highcharts/xAxis.tickPositions) option 1319 * instead. 1320 * 1321 * @name Highcharts.Axis#tickPositions 1322 * @type {Highcharts.AxisTickPositionsArray|undefined} 1323 */ 1324 if (tickPositionsOption) { 1325 // Find the tick positions. Work on a copy (#1565) 1326 tickPositions = tickPositionsOption.slice(); 1327 } 1328 else if (isNumber(this.min) && isNumber(this.max)) { 1329 // Too many ticks (#6405). Create a friendly warning and provide two 1330 // ticks so at least we can show the data series. 1331 if (!axis.ordinal?.positions && 1332 ((this.max - this.min) / 1333 this.tickInterval > 1334 Math.max(2 * this.len, 200))) { 1335 tickPositions = [this.min, this.max]; 1336 error(19, false, this.chart); 1337 } 1338 else if (axis.dateTime) { 1339 tickPositions = axis.getTimeTicks(axis.dateTime.normalizeTimeTickInterval(this.tickInterval, options.units), this.min, this.max, options.startOfWeek, axis.ordinal?.positions, this.closestPointRange, true); 1340 } 1341 else if (axis.logarithmic) { 1342 tickPositions = axis.logarithmic.getLogTickPositions(this.tickInterval, this.min, this.max); 1343 } 1344 else { 1345 const startingTickInterval = this.tickInterval; 1346 let adjustedTickInterval = startingTickInterval; 1347 while (adjustedTickInterval <= startingTickInterval * 2) { 1348 tickPositions = this.getLinearTickPositions(this.tickInterval, this.min, this.max); 1349 // If there are more tick positions than the set tickAmount, 1350 // increase the tickInterval and continue until it fits. 1351 // (#17100) 1352 if (this.tickAmount && 1353 tickPositions.length > this.tickAmount) { 1354 this.tickInterval = getNormalizedTickInterval(this, adju
vendor: 7,230 bytes, lines 1354-1532
1354stedTickInterval *= 1.1); 1355 } 1356 else { 1357 break; 1358 } 1359 } 1360 } 1361 // Too dense ticks, keep only the first and last (#4477) 1362 if (tickPositions.length > this.len) { 1363 tickPositions = [ 1364 tickPositions[0], 1365 tickPositions[tickPositions.length - 1] 1366 ]; 1367 // Reduce doubled value (#7339) 1368 if (tickPositions[0] === tickPositions[1]) { 1369 tickPositions.length = 1; 1370 } 1371 } 1372 // Run the tick positioner callback, that allows modifying auto tick 1373 // positions. 1374 if (tickPositioner) { 1375 // Make it available to the positioner 1376 this.tickPositions = tickPositions; 1377 tickPositionerResult = tickPositioner.apply(axis, [this.min, this.max]); 1378 if (tickPositionerResult) { 1379 tickPositions = tickPositionerResult; 1380 } 1381 } 1382 } 1383 this.tickPositions = tickPositions; 1384 // Get minorTickInterval 1385 this.minorTickInterval = 1386 minorTickIntervalOption === 'auto' && this.tickInterval ? 1387 this.tickInterval / options.minorTicksPerMajor : 1388 minorTickIntervalOption; 1389 // Reset min/max or remove extremes based on start/end on tick 1390 this.paddedTicks = tickPositions.slice(0); // Used for logarithmic minor 1391 this.trimTicks(tickPositions, startOnTick, endOnTick); 1392 if (!this.isLinked && isNumber(this.min) && isNumber(this.max)) { 1393 // Substract half a unit (#2619, #2846, #2515, #3390), but not in 1394 // case of multiple ticks (#6897) 1395 if (this.single && 1396 tickPositions.length < 2 && 1397 !this.categories && 1398 !this.series.some((s) => (s.is('heatmap') && s.options.pointPlacement === 'between'))) { 1399 this.min -= 0.5; 1400 this.max += 0.5; 1401 } 1402 if (!tickPositionsOption && !tickPositionerResult) { 1403 this.adjustTickAmount(); 1404 } 1405 } 1406 fireEvent(this, 'afterSetTickPositions'); 1407 } 1408 /** 1409 * Handle startOnTick and endOnTick by either adapting to padding min/max or 1410 * rounded min/max. Also handle single data points. 1411 * 1412 * @private 1413 * @function Highcharts.Axis#trimTicks 1414 * 1415 * @param {Array<number>} tickPositions 1416 * TO-DO: parameter description 1417 * 1418 * @param {boolean} [startOnTick] 1419 * TO-DO: parameter description 1420 * 1421 * @param {boolean} [endOnTick] 1422 * TO-DO: parameter description 1423 */ 1424 trimTicks(tickPositions, startOnTick, endOnTick) { 1425 const roundedMin = tickPositions[0], roundedMax = tickPositions[tickPositions.length - 1], minPointOffset = (!this.isOrdinal && this.minPointOffset) || 0; // (#12716) 1426 fireEvent(this, 'trimTicks'); 1427 if (!this.isLinked) { 1428 if (startOnTick && roundedMin !== -Infinity) { // #6502 1429 this.min = roundedMin; 1430 } 1431 else { 1432 while (this.min - minPointOffset > tickPositions[0]) { 1433 tickPositions.shift(); 1434 } 1435 } 1436 if (endOnTick) { 1437 this.max = roundedMax; 1438 } 1439 else { 1440 while (this.max + minPointOffset < 1441 tickPositions[tickPositions.length - 1]) { 1442 tickPositions.pop(); 1443 } 1444 } 1445 // If no tick are left, set one tick in the middle (#3195) 1446 if (tickPositions.length === 0 && 1447 defined(roundedMin) && 1448 !this.options.tickPositions) { 1449 tickPositions.push((roundedMax + roundedMin) / 2); 1450 } 1451 } 1452 } 1453 /** 1454 * Check if there are multiple axes in the same pane. 1455 * 1456 * @private 1457 * @function Highcharts.Axis#alignToOthers 1458 * 1459 * @return {boolean|undefined} 1460 * True if there are other axes. 1461 */ 1462 alignToOthers() { 1463 const axis = this, chart = axis.chart, alignedAxes = [this], options = axis.options, chartOptions = chart.options.chart, alignThresholds = (this.coll === 'yAxis' && 1464 chartOptions.alignThresholds), thresholdAlignments = []; 1465 let hasOther; 1466 axis.thresholdAlignment = void 0; 1467 if (( 1468 // Only if alignTicks or alignThresholds is true 1469 (chartOptions.alignTicks !== false && options.alignTicks) || 1470 alignThresholds) && 1471 // Disabled when startOnTick or endOnTick are false (#7604) 1472 options.startOnTick !== false && 1473 options.endOnTick !== false && 1474 // Don't try to align ticks on a log axis, they are not evenly 1475 // spaced (#6021) 1476 !axis.logarithmic) { 1477 // Get a key identifying which pane the axis belongs to 1478 const getKey = (axis) => { 1479 const { horiz, options } = axis; 1480 return [ 1481 horiz ? options.left : options.top, 1482 options.width, 1483 options.height, 1484 options.pane 1485 ].join(','); 1486 }; 1487 const thisKey = getKey(this); 1488 chart[this.coll].forEach(function (otherAxis) { 1489 const { series } = otherAxis; 1490 if ( 1491 // #4442 1492 series.length && 1493 series.some((s) => s.visible) && 1494 otherAxis !== axis && 1495 getKey(otherAxis) === thisKey) { 1496 hasOther = true; // #4201 1497 alignedAxes.push(otherAxis); 1498 } 1499 }); 1500 } 1501 if (hasOther && alignThresholds) { 1502 // Handle alignThresholds. The `thresholdAlignments` array keeps 1503 // records of where each axis in the group wants its threshold, from 1504 // 0 which is on `axis.min`, to 1 which is on `axis.max`. 1505 alignedAxes.forEach((otherAxis) => { 1506 const threshAlign = otherAxis.getThresholdAlignment(axis); 1507 if (isNumber(threshAlign)) { 1508 thresholdAlignments.push(threshAlign); 1509 } 1510 }); 1511 // For each of the axes in the group, record the average 1512 // `thresholdAlignment`. 1513 const thresholdAlignment = thresholdAlignments.length > 1 ? 1514 thresholdAlignments.reduce((sum, n) => (sum += n), 0) / thresholdAlignments.length : 1515 void 0; 1516 alignedAxes.forEach((axis) => { 1517 axis.thresholdAlignment = thresholdAlignment; 1518 }); 1519 } 1520 return hasOther; 1521 } 1522 /** 1523 * Where the axis wants its threshold, from 0 which is on `axis.min`, to 1 which 1524 * is on `axis.max`. 1525 * 1526 * @private 1527 * @function Highcharts.Axis#getThresholdAlignment 1528 */ 1529 getThresholdAlignment(callerAxis) { 1530 if (!isNumber(this.dataMin) || 1531 (this !== callerAxis && 1532 this.series.some((s) => (s.isDirty || s.isDirtyData)))) {
vendor: 7,425 bytes, lines 1533-1695
1533 this.getSeriesExtremes(); 1534 } 1535 if (isNumber(this.threshold)) { 1536 let thresholdAlignment = clamp(((this.threshold - (this.dataMin || 0)) / 1537 ((this.dataMax || 0) - (this.dataMin || 0))), 0, 1); 1538 if (this.options.reversed) { 1539 thresholdAlignment = 1 - thresholdAlignment; 1540 } 1541 return thresholdAlignment; 1542 } 1543 } 1544 /** 1545 * Find the max ticks of either the x and y axis collection, and record it 1546 * in `this.tickAmount`. 1547 * 1548 * @private 1549 * @function Highcharts.Axis#getTickAmount 1550 */ 1551 getTickAmount() { 1552 const axis = this, options = this.options, tickPixelInterval = options.tickPixelInterval; 1553 let tickAmount = options.tickAmount; 1554 if (!defined(options.tickInterval) && 1555 !tickAmount && 1556 this.len < tickPixelInterval && 1557 !this.isRadial && 1558 !axis.logarithmic && 1559 options.startOnTick && 1560 options.endOnTick) { 1561 tickAmount = 2; 1562 } 1563 if (!tickAmount && this.alignToOthers()) { 1564 // Add 1 because 4 tick intervals require 5 ticks (including first 1565 // and last) 1566 tickAmount = Math.ceil(this.len / tickPixelInterval) + 1; 1567 } 1568 // For tick amounts of 2 and 3, compute five ticks and remove the 1569 // intermediate ones. This prevents the axis from adding ticks that are 1570 // too far away from the data extremes. 1571 if (tickAmount < 4) { 1572 this.finalTickAmt = tickAmount; 1573 tickAmount = 5; 1574 } 1575 this.tickAmount = tickAmount; 1576 } 1577 /** 1578 * When using multiple axes, adjust the number of ticks to match the highest 1579 * number of ticks in that group. 1580 * 1581 * @private 1582 * @function Highcharts.Axis#adjustTickAmount 1583 */ 1584 adjustTickAmount() { 1585 const axis = this, { finalTickAmt, max, min, options, tickPositions, tickAmount, thresholdAlignment } = axis, currentTickAmount = tickPositions?.length, threshold = pick(axis.threshold, axis.softThreshold ? 0 : null); 1586 let len, i, tickInterval = axis.tickInterval, thresholdTickIndex; 1587 const 1588 // Extend the tickPositions by appending a position 1589 append = () => tickPositions.push(correctFloat(tickPositions[tickPositions.length - 1] + 1590 tickInterval)), 1591 // Extend the tickPositions by prepending a position 1592 prepend = () => tickPositions.unshift(correctFloat(tickPositions[0] - tickInterval)); 1593 // If `thresholdAlignment` is a number, it means the `alignThresholds` 1594 // option is true. The `thresholdAlignment` is a scalar value between 0 1595 // and 1 for where the threshold should be relative to `axis.min` and 1596 // `axis.max`. Now that we know the tick amount, convert this to the 1597 // tick index. Unless `thresholdAlignment` is exactly 0 or 1, avoid the 1598 // first or last tick because that would lead to series being clipped. 1599 if (isNumber(thresholdAlignment)) { 1600 thresholdTickIndex = thresholdAlignment < 0.5 ? 1601 Math.ceil(thresholdAlignment * (tickAmount - 1)) : 1602 Math.floor(thresholdAlignment * (tickAmount - 1)); 1603 if (options.reversed) { 1604 thresholdTickIndex = tickAmount - 1 - thresholdTickIndex; 1605 } 1606 } 1607 if (axis.hasData() && isNumber(min) && isNumber(max)) { // #14769 1608 // Adjust extremes and translation to the modified tick positions 1609 const adjustExtremes = () => { 1610 axis.transA *= (currentTickAmount - 1) / (tickAmount - 1); 1611 // Do not crop when ticks are not extremes (#9841) 1612 axis.min = options.startOnTick ? 1613 tickPositions[0] : 1614 Math.min(min, tickPositions[0]); 1615 axis.max = options.endOnTick ? 1616 tickPositions[tickPositions.length - 1] : 1617 Math.max(max, tickPositions[tickPositions.length - 1]); 1618 }; 1619 // When the axis is subject to the alignThresholds option. Use 1620 // axis.threshold because the local threshold includes the 1621 // `softThreshold`. 1622 if (isNumber(thresholdTickIndex) && isNumber(axis.threshold)) { 1623 // Throw away the previously computed tickPositions and start 1624 // from scratch with only the threshold itself, then add ticks 1625 // below the threshold first, then fill up above the threshold. 1626 // If we are not able to fill up to axis.max, double the 1627 // tickInterval and run again. 1628 while (tickPositions[thresholdTickIndex] !== threshold || 1629 tickPositions.length !== tickAmount || 1630 tickPositions[0] > min || 1631 tickPositions[tickPositions.length - 1] < max) { 1632 tickPositions.length = 0; 1633 tickPositions.push(axis.threshold); 1634 while (tickPositions.length < tickAmount) { 1635 if ( 1636 // Start by prepending positions until the threshold 1637 // is at the required index... 1638 tickPositions[thresholdTickIndex] === void 0 || 1639 tickPositions[thresholdTickIndex] > axis.threshold) { 1640 prepend(); 1641 } 1642 else { 1643 // ... then append positions until we have the 1644 // required length 1645 append(); 1646 } 1647 } 1648 // Safety vent 1649 if (tickInterval > axis.tickInterval * 8) { 1650 break; 1651 } 1652 tickInterval *= 2; 1653 } 1654 adjustExtremes(); 1655 } 1656 else if (currentTickAmount < tickAmount) { 1657 while (tickPositions.length < tickAmount) { 1658 // Extend evenly for both sides unless we're on the 1659 // threshold (#3965) 1660 if (tickPositions.length % 2 || min === threshold) { 1661 append(); 1662 } 1663 else { 1664 prepend(); 1665 } 1666 } 1667 adjustExtremes(); 1668 } 1669 // The finalTickAmt property is set in getTickAmount 1670 if (defined(finalTickAmt)) { 1671 i = len = tickPositions.length; 1672 while (i--) { 1673 if ( 1674 // Remove every other tick 1675 (finalTickAmt === 3 && i % 2 === 1) || 1676 // Remove all but first and last 1677 (finalTickAmt <= 2 && i > 0 && i < len - 1)) { 1678 tickPositions.splice(i, 1); 1679 } 1680 } 1681 axis.finalTickAmt = void 0; 1682 } 1683 } 1684 } 1685 /** 1686 * Set the scale based on data min and max, user set min and max or options. 1687 * 1688 * @private 1689 * @function Highcharts.Axis#setScale 1690 * 1691 * @emits Highcharts.Axis#event:afterSetScale 1692 */ 1693 setScale() { 1694 const axis = this, { coll, stacking } = axis; 1695 let isDirtyData = false, isXAxisDirty = false;
1696 axis.series.forEach((series) => { 1697 isDirtyData = isDirtyData || series.isDirtyData || series.isDirty; 1698 // When x axis is dirty, we need new data extremes for y as 1699 // well: 1700 isXAxisDirty = (isXAxisDirty || 1701 (series.xAxis && series.xAxis.isDirty) || 1702 false); 1703 }); 1704 // Set the new axisLength 1705 axis.setAxisSize(); 1706 const isDirtyAxisLength = axis.len !== (axis.old && axis.old.len); 1707 // Do we really need to go through all this? 1708 if (isDirtyAxisLength || 1709 isDirtyData || 1710 isXAxisDirty || 1711 axis.isLinked || 1712 axis.forceRedraw || 1713 axis.userMin !== (axis.old && axis.old.userMin) || 1714 axis.userMax !== (axis.old && axis.old.userMax) || 1715 axis.alignToOthers()) { 1716 if (stacking && coll === 'yAxis') { 1717 stacking.buildStacks(); 1718 } 1719 axis.forceRedraw = false; 1720 // #18066 delete minRange property to ensure that it will be 1721 // calculated again after dirty data in series 1722 if (!axis.userMinRange) { 1723 axis.minRange = void 0; 1724 } 1725 // Get data extremes if needed 1726 axis.getSeriesExtremes(); 1727 // Get fixed positions based on tickInterval 1728 axis.setTickInterval(); 1729 if (stacking && coll === 'xAxis') { 1730 stacking.buildStacks(); 1731 } 1732 // Mark as dirty if it is not already set to dirty and extremes have 1733 // changed. #595. 1734 if (!axis.isDirty) { 1735 axis.isDirty = 1736 isDirtyAxisLength || 1737 axis.min !== axis.old?.min || 1738 axis.max !== axis.old?.max; 1739 } 1740 } 1741 else if (stacking) { 1742 stacking.cleanStacks(); 1743 } 1744 // Recalculate all extremes object when the data has changed. It is 1745 // required when vertical panning is enabled. 1746 if (isDirtyData) { 1747 delete axis.allExtremes; 1748 } 1749 fireEvent(this, 'afterSetScale'); 1750 } 1751 /** 1752 * Set the minimum and maximum of the axes after render time. If the 1753 * `startOnTick` and `endOnTick` options are true, the minimum and maximum 1754 * values are rounded off to the nearest tick. To prevent this, these 1755 * options can be set to false before calling setExtremes. Also, setExtremes 1756 * will not allow a range lower than the `minRange` option, which by default 1757 * is the range of five points. 1758 * 1759 * @sample highcharts/members/axis-setextremes/ 1760 * Set extremes from a button 1761 * @sample highcharts/members/axis-setextremes-datetime/ 1762 * Set extremes on a datetime axis 1763 * @sample highcharts/members/axis-setextremes-off-ticks/ 1764 * Set extremes off ticks 1765 * @sample stock/members/axis-setextremes/ 1766 * Set extremes in Highcharts Stock 1767 * 1768 * @function Highcharts.Axis#setExtremes 1769 * 1770 * @param {number|string} [newMin] 1771 * The new minimum value. For datetime axes, date strings are accepted. 1772 * 1773 * @param {number|string} [newMax] 1774 * The new maximum value. For datetime axes, date strings are accepted. 1775 * 1776 * @param {boolean} [redraw=true] 1777 * Whether to redraw the chart or wait for an explicit call to 1778 * {@link Highcharts.Chart#redraw} 1779 * 1780 * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] 1781 * Enable or modify animations. 1782 * 1783 * @param {*} [eventArguments] 1784 * Arguments to be accessed in event handler. 1785 * 1786 * @emits Highcharts.Axis#event:setExtremes 1787 */ 1788 setExtremes(min, max, redraw = true, animation, eventArguments) { 1789 const chart = this.chart; 1790 this.series.forEach((serie) => { 1791 delete serie.kdTree; 1792 }); 1793 min = chart.time.parse(min); 1794 max = chart.time.parse(max); 1795 // Extend the arguments with min and max 1796 eventArguments = extend(eventArguments, { min, max }); 1797 // Fire the event 1798 fireEvent(this, 'setExtremes', eventArguments, (e) => { 1799 this.userMin = e.min; 1800 this.userMax = e.max; 1801 this.eventArgs = e; 1802 if (redraw) { 1803 chart.redraw(animation); 1804 } 1805 }); 1806 } 1807 /** 1808 * Update the axis metrics. 1809 * 1810 * @private 1811 * @function Highcharts.Axis#setAxisSize 1812 */ 1813 setAxisSize() { 1814 const chart = this.chart, options = this.options, 1815 // [top, right, bottom, left] 1816 offsets = options.offsets || [0, 0, 0, 0], horiz = this.horiz, 1817 // Check for percentage based input values. Rounding fixes problems 1818 // with column overflow and plot line filtering (#4898, #4899) 1819 width = this.width = Math.round(relativeLength(pick(options.width, chart.plotWidth - offsets[3] + offsets[1]), chart.plotWidth)), height = this.height = Math.round(relativeLength(pick(options.height, chart.plotHeight - offsets[0] + offsets[2]), chart.plotHeight)), top = this.top = Math.round(relativeLength(pick(options.top, chart.plotTop + offsets[0]), chart.plotHeight, chart.plotTop)), left = this.left = Math.round(relativeLength(pick(options.left, chart.plotLeft + offsets[3]), chart.plotWidth, chart.plotLeft)); 1820 // Expose basic values to use in Series object and navigator 1821 this.bottom = chart.chartHeight - height - top; 1822 this.right = chart.chartWidth - width - left; 1823 // Direction agnostic properties 1824 this.len = Math.max(horiz ? width : height, 0); // Math.max fixes #905 1825 /** 1826 * The position of the axis in terms of pixels, compared to the chart 1827 * edge. In a horizontal axis it is the same as `chart.plotLeft` unless 1828 * the axis is explicitly positioned, and in a default vertical axis it 1829 * is the same as `chart.plotTop`. 1830 * 1831 * @name Highcharts.Axis#pos 1832 * @type {number} 1833 */ 1834 this.pos = horiz ? left : top; // Distance from SVG origin 1835 } 1836 /** 1837 * Get the current extremes for the axis. 1838 * 1839 * @sample highcharts/members/axis-getextremes/ 1840 * Report extremes by click on a button 1841 * 1842 * @function Highcharts.Axis#getExtremes 1843 * 1844 * @return {Highcharts.ExtremesObject}
vendor: 7,649 bytes, lines 1845-2040
1845 * An object containing extremes information. 1846 */ 1847 getExtremes() { 1848 const axis = this, log = axis.logarithmic; 1849 return { 1850 min: log ? 1851 correctFloat(log.lin2log(axis.min)) : 1852 axis.min, 1853 max: log ? 1854 correctFloat(log.lin2log(axis.max)) : 1855 axis.max, 1856 dataMin: axis.dataMin, 1857 dataMax: axis.dataMax, 1858 userMin: axis.userMin, 1859 userMax: axis.userMax 1860 }; 1861 } 1862 /** 1863 * Get the zero plane either based on zero or on the min or max value. 1864 * Used in bar and area plots. 1865 * 1866 * @function Highcharts.Axis#getThreshold 1867 * 1868 * @param {number} threshold 1869 * The threshold in axis values. 1870 * 1871 * @return {number} 1872 * The translated threshold position in terms of pixels, and corrected to 1873 * stay within the axis bounds. 1874 */ 1875 getThreshold(threshold) { 1876 const axis = this, log = axis.logarithmic, realMin = log ? log.lin2log(axis.min) : axis.min, realMax = log ? log.lin2log(axis.max) : axis.max; 1877 if (threshold === null || threshold === -Infinity) { 1878 threshold = realMin; 1879 } 1880 else if (threshold === Infinity) { 1881 threshold = realMax; 1882 } 1883 else if (realMin > threshold) { 1884 threshold = realMin; 1885 } 1886 else if (realMax < threshold) { 1887 threshold = realMax; 1888 } 1889 return axis.translate(threshold, 0, 1, 0, 1); 1890 } 1891 /** 1892 * Compute auto alignment for the axis label based on which side the axis is 1893 * on and the given rotation for the label. 1894 * 1895 * @private 1896 * @function Highcharts.Axis#autoLabelAlign 1897 * 1898 * @param {number} rotation 1899 * The rotation in degrees as set by either the `rotation` or `autoRotation` 1900 * options. 1901 * 1902 * @return {Highcharts.AlignValue} 1903 * Can be `"center"`, `"left"` or `"right"`. 1904 */ 1905 autoLabelAlign(rotation) { 1906 const angle = (pick(rotation, 0) - (this.side * 90) + 720) % 360, evt = { align: 'center' }; 1907 fireEvent(this, 'autoLabelAlign', evt, function (e) { 1908 if (angle > 15 && angle < 165) { 1909 e.align = 'right'; 1910 } 1911 else if (angle > 195 && angle < 345) { 1912 e.align = 'left'; 1913 } 1914 }); 1915 return evt.align; 1916 } 1917 /** 1918 * Get the tick length and width for the axis based on axis options. 1919 * 1920 * @private 1921 * @function Highcharts.Axis#tickSize 1922 * 1923 * @param {string} [prefix] 1924 * 'tick' or 'minorTick' 1925 * 1926 * @return {Array<number,number>|undefined} 1927 * An array of tickLength and tickWidth 1928 */ 1929 tickSize(prefix) { 1930 const options = this.options, tickWidth = pick(options[prefix === 'tick' ? 'tickWidth' : 'minorTickWidth'], 1931 // Default to 1 on linear and datetime X axes 1932 prefix === 'tick' && this.isXAxis && !this.categories ? 1 : 0); 1933 let tickLength = options[prefix === 'tick' ? 'tickLength' : 'minorTickLength'], tickSize; 1934 if (tickWidth && tickLength) { 1935 // Negate the length 1936 if (options[prefix + 'Position'] === 'inside') { 1937 tickLength = -tickLength; 1938 } 1939 tickSize = [tickLength, tickWidth]; 1940 } 1941 const e = { tickSize }; 1942 fireEvent(this, 'afterTickSize', e); 1943 return e.tickSize; 1944 } 1945 /** 1946 * Return the size of the labels. 1947 * 1948 * @private 1949 * @function Highcharts.Axis#labelMetrics 1950 */ 1951 labelMetrics() { 1952 const renderer = this.chart.renderer, ticks = this.ticks, tick = ticks[Object.keys(ticks)[0]] || {}; 1953 return this.chart.renderer.fontMetrics(tick.label || 1954 tick.movedLabel || 1955 renderer.box); 1956 } 1957 /** 1958 * Prevent the ticks from getting so close we can't draw the labels. On a 1959 * horizontal axis, this is handled by rotating the labels, removing ticks 1960 * and adding ellipsis. On a vertical axis remove ticks and add ellipsis. 1961 * 1962 * @private 1963 * @function Highcharts.Axis#unsquish 1964 */ 1965 unsquish() { 1966 const labelOptions = this.options.labels, padding = labelOptions.padding || 0, horiz = this.horiz, tickInterval = this.tickInterval, slotSize = this.len / (((this.categories ? 1 : 0) + 1967 this.max - 1968 this.min) / 1969 tickInterval), rotationOption = labelOptions.rotation, 1970 // We don't know the actual rendered line height at this point, but 1971 // it defaults to 0.8em 1972 lineHeight = correctFloat(this.labelMetrics().h * 0.8), range = Math.max(this.max - this.min, 0), 1973 // Return the multiple of tickInterval that is needed to avoid 1974 // collision 1975 getStep = function (spaceNeeded) { 1976 let step = (spaceNeeded + 2 * padding) / (slotSize || 1); 1977 step = step > 1 ? Math.ceil(step) : 1; 1978 // Guard for very small or negative angles (#9835) 1979 if (step * tickInterval > range && 1980 spaceNeeded !== Infinity && 1981 slotSize !== Infinity && 1982 range) { 1983 step = Math.ceil(range / tickInterval); 1984 } 1985 return correctFloat(step * tickInterval); 1986 }; 1987 let newTickInterval = tickInterval, rotation, bestScore = Number.MAX_VALUE, autoRotation; 1988 if (horiz) { 1989 if (!labelOptions.staggerLines) { 1990 if (isNumber(rotationOption)) { 1991 autoRotation = [rotationOption]; 1992 } 1993 else if (slotSize < labelOptions.autoRotationLimit) { 1994 autoRotation = labelOptions.autoRotation; 1995 } 1996 } 1997 if (autoRotation) { 1998 let step, score; 1999 // Loop over the given autoRotation options, and determine which 2000 // gives the best score. The best score is that with the lowest 2001 // number of steps and a rotation closest to horizontal. 2002 for (const rot of autoRotation) { 2003 if (rot === rotationOption || 2004 (rot && rot >= -90 && rot <= 90)) { // #3891 2005 step = getStep(Math.abs(lineHeight / Math.sin(deg2rad * rot))); 2006 score = step + Math.abs(rot / 360); 2007 if (score < bestScore) { 2008 bestScore = score; 2009 rotation = rot; 2010 newTickInterval = step; 2011 } 2012 } 2013 } 2014 } 2015 } 2016 else { // #4411 2017 newTickInterval = getStep(lineHeight * 0.75); 2018 } 2019 this.autoRotation = autoRotation; 2020 this.labelRotation = pick(rotation, isNumber(rotationOption) ? rotationOption : 0); 2021 return labelOptions.step ? tickInterval : newTickInterval; 2022 } 2023 /** 2024 * Get the general slot width for labels/categories on this axis. This may 2025 * change between the pre-render (from Axis.getOffset) and the final tick 2026 * rendering and placement. 2027 * 2028 * @private 2029 * @function Highcharts.Axis#getSlotWidth 2030 * 2031 * @param {Highcharts.Tick} [tick] Optionally, calculate the slot width 2032 * basing on tick label. It is used in highcharts-3d module, where the slots 2033 * has different widths depending on perspective angles. 2034 * 2035 * @return {number} 2036 * The pixel width allocated to each axis label. 2037 */ 2038 getSlotWidth(tick) { 2039 // #5086, #1580, #1931 2040 const chart = this.chart, horiz = this.horiz, labelOptions = this.options.labels, slot
2040Count = Math.max(this.tickPositions.length - (this.categories ? 0 : 1), 1), marginLeft = chart.margin[3]; 2041 // Used by grid axis 2042 if (tick && isNumber(tick.slotWidth)) { // #13221, can be 0 2043 return tick.slotWidth; 2044 } 2045 if (horiz && labelOptions.step < 2 && !this.isRadial) { 2046 if (labelOptions.rotation) { // #4415 2047 return 0; 2048 } 2049 return ((this.staggerLines || 1) * this.len) / slotCount; 2050 } 2051 if (!horiz) { 2052 // #7028 2053 const cssWidth = labelOptions.style.width; 2054 if (cssWidth !== void 0) { 2055 return parseInt(String(cssWidth), 10); 2056 } 2057 if (marginLeft) { 2058 return marginLeft - chart.spacing[3]; 2059 } 2060 } 2061 // Last resort, a fraction of the available size 2062 return chart.chartWidth * 0.33; 2063 } 2064 /** 2065 * Render the axis labels and determine whether ellipsis or rotation need to 2066 * be applied. 2067 * 2068 * @private 2069 * @function Highcharts.Axis#renderUnsquish 2070 */ 2071 renderUnsquish() { 2072 const chart = this.chart, renderer = chart.renderer, tickPositions = this.tickPositions, ticks = this.ticks, labelOptions = this.options.labels, labelStyleOptions = labelOptions.style, horiz = this.horiz, slotWidth = this.getSlotWidth(), innerWidth = Math.max(1, Math.round(slotWidth - (horiz ? 2073 2 * (labelOptions.padding || 0) : 2074 labelOptions.distance || 0 // #21172 2075 ))), attr = {}, labelMetrics = this.labelMetrics(), lineClampOption = labelStyleOptions.lineClamp; 2076 let commonWidth, lineClamp = lineClampOption ?? (Math.floor(this.len / (tickPositions.length * labelMetrics.h)) || 1), maxLabelLength = 0; 2077 // Set rotation option unless it is "auto", like in gauges 2078 if (!isString(labelOptions.rotation)) { 2079 // #4443 2080 attr.rotation = labelOptions.rotation || 0; 2081 } 2082 // Get the longest label length 2083 tickPositions.forEach(function (tickPosition) { 2084 const tick = ticks[tickPosition]; 2085 // Replace label - sorting animation 2086 if (tick.movedLabel) { 2087 tick.replaceMovedLabel(); 2088 } 2089 const textPxLength = tick.label?.textPxLength || 0; 2090 if (textPxLength > maxLabelLength) { 2091 maxLabelLength = textPxLength; 2092 } 2093 }); 2094 this.maxLabelLength = maxLabelLength; 2095 // Handle auto rotation on horizontal axis 2096 if (this.autoRotation) { 2097 // Apply rotation only if the label is too wide for the slot, and 2098 // the label is wider than its height. 2099 if (maxLabelLength > innerWidth && 2100 maxLabelLength > labelMetrics.h) { 2101 attr.rotation = this.labelRotation; 2102 } 2103 else { 2104 this.labelRotation = 0; 2105 } 2106 // Handle word-wrap or ellipsis on vertical axis 2107 } 2108 else if (slotWidth) { 2109 // For word-wrap or ellipsis 2110 commonWidth = innerWidth; 2111 } 2112 // Add ellipsis if the label length is significantly longer than ideal 2113 if (attr.rotation) { 2114 commonWidth = (maxLabelLength > chart.chartHeight * 0.5 ? 2115 chart.chartHeight * 0.33 : 2116 maxLabelLength); 2117 if (!lineClampOption) { 2118 lineClamp = 1; 2119 } 2120 } 2121 // Set the explicit or automatic label alignment 2122 this.labelAlign = labelOptions.align || 2123 this.autoLabelAlign(this.labelRotation); 2124 if (this.labelAlign) { 2125 attr.align = this.labelAlign; 2126 } 2127 // Apply general and specific CSS 2128 tickPositions.forEach(function (pos) { 2129 const tick = ticks[pos], label = tick && tick.label, widthOption = labelStyleOptions.width, css = {}; 2130 if (label) { 2131 // This needs to go before the CSS in old IE (#4502) 2132 label.attr(attr); 2133 if (tick.shortenLabel) { 2134 tick.shortenLabel(); 2135 } 2136 else if (commonWidth && 2137 !widthOption && 2138 // Setting width in this case messes with the bounding box 2139 // (#7975) 2140 labelStyleOptions.whiteSpace !== 'nowrap' && 2141 ( 2142 // Speed optimizing, #7656 2143 commonWidth < (label.textPxLength || 0) || 2144 // Resetting CSS, #4928 2145 label.element.tagName === 'SPAN')) { 2146 label.css(extend(css, { 2147 width: `${commonWidth}px`, 2148 lineClamp 2149 })); 2150 // Reset previously shortened label (#8210) 2151 } 2152 else if (label.styles.width && !css.width && !widthOption) { 2153 label.css({ width: 'auto' }); 2154 } 2155 tick.rotation = attr.rotation; 2156 } 2157 }, this); 2158 // Note: Why is this not part of getLabelPosition? 2159 this.tickRotCorr = renderer.rotCorr(labelMetrics.b, this.labelRotation || 0, this.side !== 0); 2160 } 2161 /** 2162 * Return true if the axis has associated data. 2163 * 2164 * @function Highcharts.Axis#hasData 2165 * 2166 * @return {boolean} 2167 * True if the axis has associated visible series and those series have 2168 * either valid data points or explicit `min` and `max` settings. 2169 */ 2170 hasData() { 2171 return this.series.some(function (s) { 2172 return s.hasData(); 2173 }) || 2174 (this.options.showEmpty && 2175 defined(this.min) && 2176 defined(this.max)); 2177 } 2178 /** 2179 * Adds the title defined in axis.options.title. 2180 * 2181 * @function Highcharts.Axis#addTitle 2182 * 2183 * @param {boolean} [display] 2184 * Whether or not to display the title. 2185 */ 2186 addTitle(display) { 2187 const axis = this, renderer = axis.chart.renderer, horiz = axis.horiz, opposite = axis.opposite, options = axis.options, axisTitleOptions = options.title, styledMode = axis.chart.styledMode; 2188 let textAlign; 2189 if (!axis.axisTitle) { 2190 textAlign = axisTitleOptions.textAlign; 2191 if (!textAlign) { 2192 textAlign = (horiz ? { 2193 low: 'left', 2194 middle: 'center', 2195 high: 'right' 2196 } : { 2197 low: opposite ? 'right' : 'left', 2198 middle: 'center', 2199 high: opposite ? 'left' : 'right' 2200 })[axisTitleOptions.align]; 2201 } 2202 axis.axisTitle = renderer 2203 .text(axisTitleOptions.text || '', 0, 0, axisTitleOptions.useHTML) 2204 .attr({ 2205 zIndex: 7, 2206 rotation: axisTitleOptions.rotation || 0, 2207 align: textAlign 2208 }) 2209 .addClass('highcharts-axis-title'); 2210 // #7814, don't mutate style option 2211 if (!styledMode) { 2212 axis.axisTitle.css(merge(axisTitleOptions.style)); 2213 } 2214 axis.axisTitle.add(axis.axisGroup); 2215 axis.axisTitle.isNew = true; 2216 } 2217 // Max width defaults to the length of the axis 2218 if (!styledMode && 2219 !axisTitleOptions.style.width && 2220 !axis.isRadial) { 2221 axis.axisTitle.css({ 2222 width: axis.len + 'px' 2223 }); 2224 } 2225 // Hide or show the title depending on whether showEmpty is set 2226 axis.axisTitle[display ? 'show' : 'hide'](display); 2227 } 2228 /** 2229 * Generates a tick for initial positioning. 2230 * 2231 * @private 2232 * @function Highcharts.Axis#generateTick 2233 * 2234 * @param {number} pos 2235 * The tick position in axis values. 2236 * 2237 * @param {number} [i] 2238 * The index of the tick in {@link Axis.tickPositions}. 2239 */ 2240 generateTick(pos) { 2241 const axis = this, ticks = axis.ticks; 2242 if (!ticks[pos]) { 2243 ticks[pos] = new Tick(axis, pos); 2244 } 2245 else { 2246 ticks[pos].addLabel(); // Update labels depending on tick interval 2247 } 2248 } 2249 /** 2250 * Create the axisGroup and gridGroup elements on first iteration. 2251 * 2252 * @private 2253 * @function Highcharts.Axis#getOffset 2254 * 2255 * @emits Highcharts.Axis#event:afterGetOffset 2256 */ 2257 createGroups() { 2258 const { axisParent, // Used in color axis 2259 chart, coll, options } = this, renderer = chart.renderer; 2260 const createGroup = (name, suffix, zIndex) => renderer.g(name) 2261 .attr({ zIndex }) 2262 .addClass(`highcharts-${coll.toLowerCase()}${suffix} ` + 2263 (this.isRadial ? `highcharts-radial-axis${suffix} ` : '') + 2264 (options.className || '')) 2265 .add(axisParent); 2266 if (!this.axisGroup) { 2267 this.gridGroup = createGroup('grid', '-grid', options.gridZIndex); 2268 this.axisGroup = createGroup('axis', '', options.zIndex); 2269 this.labelGroup = createGroup('axis-labels', '-labels', options.labels.zIndex); 2270 } 2271 } 2272 /**
2273 * Render the tick labels to a preliminary position to get their sizes 2274 * 2275 * @private 2276 * @function Highcharts.Axis#getOffset 2277 * 2278 * @emits Highcharts.Axis#event:afterGetOffset 2279 */ 2280 getOffset() { 2281 const axis = this, { chart, horiz, options, side, ticks, tickPositions, coll } = axis, invertedSide = (chart.inverted && !axis.isZAxis ? 2282 [1, 0, 3, 2][side] : 2283 side), hasData = axis.hasData(), axisTitleOptions = options.title, labelOptions = options.labels, hasCrossing = isNumber(options.crossing), axisOffset = chart.axisOffset, clipOffset = chart.clipOffset, directionFactor = [-1, 1, 1, -1][side]; 2284 let showAxis, titleOffset = 0, titleOffsetOption, titleMargin = 0, labelOffset = 0, // Reset 2285 labelOffsetPadded, lineHeightCorrection; 2286 // For reuse in Axis.render 2287 axis.showAxis = showAxis = hasData || options.showEmpty; 2288 // Set/reset staggerLines 2289 axis.staggerLines = (axis.horiz && labelOptions.staggerLines) || void 0; 2290 axis.createGroups(); 2291 if (hasData || axis.isLinked) { 2292 // Generate ticks 2293 tickPositions.forEach(function (pos) { 2294 axis.generateTick(pos); 2295 }); 2296 axis.renderUnsquish(); 2297 // Left side must be align: right and right side must 2298 // have align: left for labels 2299 axis.reserveSpaceDefault = (side === 0 || 2300 side === 2 || 2301 { 1: 'left', 3: 'right' }[side] === axis.labelAlign); 2302 if (pick(labelOptions.reserveSpace, hasCrossing ? false : null, axis.labelAlign === 'center' ? true : null, axis.reserveSpaceDefault)) { 2303 tickPositions.forEach(function (pos) { 2304 // Get the highest offset 2305 labelOffset = Math.max(ticks[pos].getLabelSize(), labelOffset); 2306 }); 2307 } 2308 if (axis.staggerLines) { 2309 labelOffset *= axis.staggerLines; 2310 } 2311 axis.labelOffset = labelOffset * (axis.opposite ? -1 : 1); 2312 } 2313 else { // Doesn't have data 2314 objectEach(ticks, function (tick, n) { 2315 tick.destroy(); 2316 delete ticks[n]; 2317 }); 2318 } 2319 if (axisTitleOptions?.text && 2320 axisTitleOptions.enabled !== false) { 2321 axis.addTitle(showAxis); 2322 if (showAxis && 2323 !hasCrossing && 2324 axisTitleOptions.reserveSpace !== false) { 2325 axis.titleOffset = titleOffset = 2326 axis.axisTitle.getBBox()[horiz ? 'height' : 'width']; 2327 titleOffsetOption = axisTitleOptions.offset; 2328 titleMargin = defined(titleOffsetOption) ? 2329 0 : 2330 pick(axisTitleOptions.margin, horiz ? 5 : 10); 2331 } 2332 } 2333 // Render the axis line 2334 axis.renderLine(); 2335 // Handle automatic or user set offset 2336 axis.offset = directionFactor * pick(options.offset, axisOffset[side] ? axisOffset[side] + (options.margin || 0) : 0); 2337 axis.tickRotCorr = axis.tickRotCorr || { x: 0, y: 0 }; // Polar 2338 if (side === 0) { 2339 lineHeightCorrection = -axis.labelMetrics().h; 2340 } 2341 else if (side === 2) { 2342 lineHeightCorrection = axis.tickRotCorr.y; 2343 } 2344 else { 2345 lineHeightCorrection = 0; 2346 } 2347 // Find the padded label offset 2348 labelOffsetPadded = Math.abs(labelOffset) + titleMargin; 2349 if (labelOffset) { 2350 labelOffsetPadded -= lineHeightCorrection; 2351 labelOffsetPadded += directionFactor * (horiz ? 2352 pick(labelOptions.y, axis.tickRotCorr.y + 2353 directionFactor * labelOptions.distance) : 2354 pick(labelOptions.x, directionFactor * labelOptions.distance)); 2355 } 2356 axis.axisTitleMargin = pick(titleOffsetOption, labelOffsetPadded); 2357 if (axis.getMaxLabelDimensions) { 2358 axis.maxLabelDimensions = axis.getMaxLabelDimensions(ticks, tickPositions); 2359 } 2360 // Due to GridAxis.tickSize, tickSize should be calculated after ticks 2361 // has rendered. 2362 if (coll !== 'colorAxis' && clipOffset) { 2363 const tickSize = this.tickSize('tick'); 2364 axisOffset[side] = Math.max(axisOffset[side], (axis.axisTitleMargin || 0) + titleOffset + 2365 directionFactor * axis.offset, labelOffsetPadded, // #3
vendor: 4,701 bytes, lines 2365-2483
2365027 2366 tickPositions && tickPositions.length && tickSize ? 2367 tickSize[0] + directionFactor * axis.offset : 2368 0 // #4866 2369 ); 2370 // Decide the clipping needed to keep the graph inside 2371 // the plot area and axis lines 2372 const clip = !axis.axisLine || options.offset ? 2373 0 : 2374 // #4308, #4371 2375 axis.axisLine.strokeWidth() / 2; 2376 clipOffset[invertedSide] = Math.max(clipOffset[invertedSide], clip); 2377 } 2378 fireEvent(this, 'afterGetOffset'); 2379 } 2380 /** 2381 * Internal function to get the path for the axis line. Extended for polar 2382 * charts. 2383 * 2384 * @function Highcharts.Axis#getLinePath 2385 * 2386 * @param {number} lineWidth 2387 * The line width in pixels. 2388 * 2389 * @return {Highcharts.SVGPathArray} 2390 * The SVG path definition in array form. 2391 */ 2392 getLinePath(lineWidth) { 2393 const chart = this.chart, opposite = this.opposite, offset = this.offset, horiz = this.horiz, lineLeft = this.left + (opposite ? this.width : 0) + offset, lineTop = chart.chartHeight - this.bottom - 2394 (opposite ? this.height : 0) + offset; 2395 if (opposite) { 2396 lineWidth *= -1; // Crispify the other way - #1480, #1687 2397 } 2398 return chart.renderer 2399 .crispLine([ 2400 [ 2401 'M', 2402 horiz ? 2403 this.left : 2404 lineLeft, 2405 horiz ? 2406 lineTop : 2407 this.top 2408 ], 2409 [ 2410 'L', 2411 horiz ? 2412 chart.chartWidth - this.right : 2413 lineLeft, 2414 horiz ? 2415 lineTop : 2416 chart.chartHeight - this.bottom 2417 ] 2418 ], lineWidth); 2419 } 2420 /** 2421 * Render the axis line. Called internally when rendering and redrawing the 2422 * axis. 2423 * 2424 * @function Highcharts.Axis#renderLine 2425 */ 2426 renderLine() { 2427 if (!this.axisLine) { 2428 this.axisLine = this.chart.renderer.path() 2429 .addClass('highcharts-axis-line') 2430 .add(this.axisGroup); 2431 if (!this.chart.styledMode) { 2432 this.axisLine.attr({ 2433 stroke: this.options.lineColor, 2434 'stroke-width': this.options.lineWidth, 2435 zIndex: 7 2436 }); 2437 } 2438 } 2439 } 2440 /** 2441 * Position the axis title. 2442 * 2443 * @private 2444 * @function Highcharts.Axis#getTitlePosition 2445 * 2446 * @return {Highcharts.PositionObject} 2447 * X and Y positions for the title. 2448 */ 2449 getTitlePosition(axisTitle) { 2450 // Compute anchor points for each of the title align options 2451 const horiz = this.horiz, axisLeft = this.left, axisTop = this.top, axisLength = this.len, axisTitleOptions = this.options.title, margin = horiz ? axisLeft : axisTop, opposite = this.opposite, offset = this.offset, xOption = axisTitleOptions.x, yOption = axisTitleOptions.y, fontMetrics = this.chart.renderer.fontMetrics(axisTitle), 2452 // The part of a multiline text that is below the baseline of the 2453 // first line. Subtract 1 to preserve pixel-perfectness from the 2454 // old behaviour (v5.0.12), where only one line was allowed. 2455 textHeightOvershoot = axisTitle ? Math.max(axisTitle.getBBox(false, 0).height - fontMetrics.h - 1, 0) : 0, 2456 // The position in the length direction of the axis 2457 alongAxis = ({ 2458 low: margin + (horiz ? 0 : axisLength), 2459 middle: margin + axisLength / 2, 2460 high: margin + (horiz ? axisLength : 0) 2461 })[axisTitleOptions.align], 2462 // The position in the perpendicular direction of the axis 2463 offAxis = (horiz ? axisTop + this.height : axisLeft) + 2464 (horiz ? 1 : -1) * // Horizontal axis reverses the margin 2465 (opposite ? -1 : 1) * // So does opposite axes 2466 (this.axisTitleMargin || 0) + 2467 [ 2468 -textHeightOvershoot, // Top 2469 textHeightOvershoot, // Right 2470 fontMetrics.f, // Bottom 2471 -textHeightOvershoot // Left 2472 ][this.side], titlePosition = { 2473 x: horiz ? 2474 alongAxis + xOption : 2475 offAxis + (opposite ? this.width : 0) + offset + xOption, 2476 y: horiz ? 2477 offAxis + yOption - (opposite ? this.height : 0) + offset : 2478 alongAxis + yOption 2479 }; 2480 fireEvent(this, 'afterGetTitlePosition', { titlePosition: titlePosition }); 2481 return titlePosition; 2482 } 2483 /**
2484 * Render a minor tick into the given position. If a minor tick already 2485 * exists in this position, move it. 2486 * 2487 * @function Highcharts.Axis#renderMinorTick 2488 * 2489 * @param {number} pos 2490 * The position in axis values. 2491 * 2492 * @param {boolean} slideIn 2493 * Whether the tick should animate in from last computed position 2494 */ 2495 renderMinorTick(pos, slideIn) { 2496 const axis = this; 2497 const minorTicks = axis.minorTicks; 2498 if (!minorTicks[pos]) { 2499 minorTicks[pos] = new Tick(axis, pos, 'minor'); 2500 } 2501 // Render new ticks in old position 2502 if (slideIn && minorTicks[pos].isNew) { 2503 minorTicks[pos].render(null, true); 2504 } 2505 minorTicks[pos].render(null, false, 1); 2506 } 2507 /** 2508 * Render a major tick into the given position. If a tick already exists 2509 * in this position, move it. 2510 * 2511 * @function Highcharts.Axis#renderTick 2512 * 2513 * @param {number} pos 2514 * The position in axis values. 2515 * 2516 * @param {number} i 2517 * The tick index. 2518 * 2519 * @param {boolean} slideIn 2520 * Whether the tick should animate in from last computed position 2521 */ 2522 renderTick(pos, i, slideIn) { 2523 const axis = this, isLinked = axis.isLinked, ticks = axis.ticks; 2524 // Linked axes need an extra check to find out if 2525 if (!isLinked || 2526 (pos >= axis.min && pos <= axis.max) || 2527 (axis.grid && axis.grid.isColumn)) { 2528 if (!ticks[pos]) { 2529 ticks[pos] = new Tick(axis, pos); 2530 } 2531 // NOTE this seems like overkill. Could be handled in tick.render by 2532 // setting old position in attr, then set new position in animate. 2533 // render new ticks in old position 2534 if (slideIn && ticks[pos].isNew) { 2535 // Start with negative opacity so that it is visible from 2536 // halfway into the animation 2537 ticks[pos].render(i, true, -1); 2538 } 2539 ticks[pos].render(i); 2540 } 2541 } 2542 /** 2543 * Render the axis. 2544 * 2545 * @private 2546 * @function Highcharts.Axis#render 2547 * 2548 * @emits Highcharts.Axis#event:afterRender 2549 */ 2550 render() { 2551 const axis = this, chart = axis.chart, log = axis.logarithmic, renderer = chart.renderer, options = axis.options, isLinked = axis.isLinked, tickPositions = axis.tickPositions, axisTitle = axis.axisTitle, ticks = axis.ticks, minorTicks = axis.minorTicks, alternateBands = axis.alternateBands, stackLabelOptions = options.stackLabels, alternateGridColor = options.alternateGridColor, crossing = options.crossing, tickmarkOffset = axis.tickmarkOffset, axisLine = axis.axisLine, showAxis = axis.showAxis, animation = animObject(renderer.globalAnimation); 2552 let from, to; 2553 // Reset 2554 axis.labelEdge.length = 0; 2555 axis.overlap = false; 2556 // Mark all elements inActive before we go over and mark the active ones 2557 [ticks, minorTicks, alternateBands].forEach(function (coll) { 2558 objectEach(coll, function (tick) { 2559 tick.isActive = false; 2560 }); 2561 }); 2562 // Crossing 2563 if (isNumber(crossing)) { 2564 const otherAxis = this.isXAxis ? chart.yAxis[0] : chart.xAxis[0], directionFactor = [1, -1, -1, 1][this.side]; 2565 if (otherAxis) { 2566 let px = otherAxis.toPixels(crossing, true); 2567 if (axis.horiz) { 2568 px = otherAxis.len - px; 2569 } 2570 axis.offset = directionFactor * px; 2571 } 2572 } 2573 // If the series has data draw the ticks. Else only the line and title 2574 if (axis.hasData() || isLinked) { 2575 const slideInTicks = axis.chart.hasRendered && 2576 axis.old && isNumber(axis.old.min); 2577 // Minor ticks 2578 if (axis.minorTickInterval && !axis.categories) { 2579 axis.getMinorTickPositions().forEach(function (pos) { 2580 axis.renderMinorTick(pos, slideInTicks); 2581 }); 2582 } 2583 // Major ticks. Pull out the first item and render it last so that 2584 // we can get the position of the neighbour label. #808. 2585 if (tickPositions.length) { // #1300 2586 tickPositions.forEach(function (pos, i) { 2587 axis.renderTick(pos, i, slideInTicks); 2588 }); 2589 // In a categorized axis, the tick marks are displayed 2590 // between labels. So we need to add a tick mark and 2591 // grid line at the left edge of the X axis. 2592 if (tickmarkOffset && (axis.min === 0 || axis.single)) { 2593 if (!ticks[-1]) {
2594 ticks[-1] = new Tick(axis, -1, null, true); 2595 } 2596 ticks[-1].render(-1); 2597 } 2598 } 2599 // Alternate grid color 2600 if (alternateGridColor) { 2601 tickPositions.forEach(function (pos, i) { 2602 to = typeof tickPositions[i + 1] !== 'undefined' ? 2603 tickPositions[i + 1] + tickmarkOffset : 2604 axis.max - tickmarkOffset; 2605 if (i % 2 === 0 && 2606 pos < axis.max && 2607 to <= axis.max + (chart.polar ? 2608 -tickmarkOffset : 2609 tickmarkOffset)) { // #2248, #4660 2610 if (!alternateBands[pos]) { 2611 // Should be imported from PlotLineOrBand.js, but 2612 // the dependency cycle with axis is a problem 2613 alternateBands[pos] = new H.PlotLineOrBand(axis, {}); 2614 } 2615 from = pos + tickmarkOffset; // #949 2616 alternateBands[pos].options = { 2617 from: log ? log.lin2log(from) : from, 2618 to: log ? log.lin2log(to) : to, 2619 color: alternateGridColor, 2620 className: 'highcharts-alternate-grid' 2621 }; 2622 alternateBands[pos].render(); 2623 alternateBands[pos].isActive = true; 2624 } 2625 }); 2626 } 2627 // Custom plot lines and bands 2628 if (!axis._addedPlotLB) { // Only first time 2629 axis._addedPlotLB = true; 2630 (options.plotLines || []) 2631 .concat(options.plotBands || []) 2632 .forEach(function (plotLineOptions) { 2633 axis 2634 .addPlotBandOrLine(plotLineOptions); 2635 }); 2636 } 2637 } // End if hasData 2638 // Remove inactive ticks 2639 [ticks, minorTicks, alternateBands].forEach(function (coll) { 2640 const forDestruction = [], delay = animation.duration, destroyInactiveItems = function () { 2641 let i = forDestruction.length; 2642 while (i--) { 2643 // When resizing rapidly, the same items 2644 // may be destroyed in different timeouts, 2645 // or the may be reactivated 2646 if (coll[forDestruction[i]] && 2647 !coll[forDestruction[i]].isActive) { 2648 coll[forDestruction[i]].destroy(); 2649 delete coll[forDestruction[i]]; 2650 } 2651 } 2652 }; 2653 objectEach(coll, function (tick, pos) { 2654 if (!tick.isActive) { 2655 // Render to zero opacity 2656 tick.render(pos, false, 0); 2657 tick.isActive = false; 2658 forDestruction.push(pos); 2659 } 2660 }); 2661 // When the objects are finished fading out, destroy them 2662 syncTimeout(destroyInactiveItems, coll === alternateBands || 2663 !chart.hasRendered || 2664 !delay ? 2665 0 : 2666 delay); 2667 }); 2668 // Set the axis line path 2669 if (axisLine) { 2670 axisLine[axisLine.isPlaced ? 'animate' : 'attr']({ 2671 d: this.getLinePath(axisLine.strokeWidth()) 2672 }); 2673 axisLine.isPlaced = true; 2674 // Show or hide the line depending on options.showEmpty 2675 axisLine[showAxis ? 'show' : 'hide'](showAxis); 2676 } 2677 if (axisTitle && showAxis) { 2678 axisTitle[axisTitle.isNew ? 'attr' : 'animate'](axis.getTitlePosition(axisTitle)); 2679 axisTitle.isNew = false; 2680 } 2681 // Stacked totals: 2682 if (stackLabelOptions && stackLabelOptions.enabled && axis.stacking) { 2683 axis.stacking.renderStackTotals(); 2684 } 2685 // End stacked totals 2686 // Record old scaling for updating/animation. Pinch base must be 2687 // preserved until the pinch ends.
2688 axis.old = { 2689 len: axis.len, 2690 max: axis.max, 2691 min: axis.min, 2692 transA: axis.transA, 2693 userMax: axis.userMax, 2694 userMin: axis.userMin 2695 }; 2696 axis.isDirty = false; 2697 fireEvent(this, 'afterRender'); 2698 } 2699 /** 2700 * Redraw the axis to reflect changes in the data or axis extremes. Called 2701 * internally from Highcharts.Chart#redraw. 2702 * 2703 * @private 2704 * @function Highcharts.Axis#redraw 2705 */ 2706 redraw() { 2707 if (this.visible) { 2708 // Render the axis 2709 this.render(); 2710 // Move plot lines and bands 2711 this.plotLinesAndBands.forEach(function (plotLine) { 2712 plotLine.render(); 2713 }); 2714 } 2715 // Mark associated series as dirty and ready for redraw 2716 this.series.forEach(function (series) { 2717 series.isDirty = true; 2718 }); 2719 } 2720 /** 2721 * Returns an array of axis properties, that should be untouched during 2722 * reinitialization. 2723 * 2724 * @private 2725 * @function Highcharts.Axis#getKeepProps 2726 */ 2727 getKeepProps() { 2728 return (this.keepProps || Axis.keepProps); 2729 } 2730 /** 2731 * Destroys an Axis instance. See {@link Axis#remove} for the API endpoint 2732 * to fully remove the axis. 2733 * 2734 * @private 2735 * @function Highcharts.Axis#destroy 2736 * 2737 * @param {boolean} [keepEvents] 2738 * Whether to preserve events, used internally in Axis.update. 2739 */ 2740 destroy(keepEvents) { 2741 const axis = this, plotLinesAndBands = axis.plotLinesAndBands, eventOptions = this.eventOptions; 2742 fireEvent(this, 'destroy', { keepEvents: keepEvents }); 2743 // Remove the events 2744 if (!keepEvents) { 2745 removeEvent(axis); 2746 } 2747 // Destroy collections 2748 [axis.ticks, axis.minorTicks, axis.alternateBands].forEach(function (coll) { 2749 destroyObjectProperties(coll); 2750 }); 2751 if (plotLinesAndBands) { 2752 let i = plotLinesAndBands.length; 2753 while (i--) { // #1975 2754 plotLinesAndBands[i].destroy(); 2755 } 2756 } 2757 // Destroy elements 2758 [ 2759 'axisLine', 'axisTitle', 'axisGroup', 2760 'gridGroup', 'labelGroup', 'cross', 'scrollbar' 2761 ].forEach(function (prop) { 2762 if (axis[prop]) { 2763 axis[prop] = axis[prop].destroy(); 2764 } 2765 }); 2766 // Destroy each generated group for plotlines and plotbands 2767 for (const plotGroup in axis.plotLinesAndBandsGroups) { // eslint-disable-line guard-for-in 2768 axis.plotLinesAndBandsGroups[plotGroup] = 2769 axis.plotLinesAndBandsGroups[plotGroup].destroy(); 2770 } 2771 // Delete all properties and fall back to the prototype. 2772 objectEach(axis, function (val, key) { 2773 if (axis.getKeepProps().indexOf(key) === -1) { 2774 delete axis[key]; 2775 } 2776 }); 2777 this.eventOptions = eventOptions; 2778 } 2779 /** 2780 * Internal function to draw a crosshair. 2781 * 2782 * @function Highcharts.Axis#drawCrosshair 2783 * 2784 * @param {Highcharts.PointerEventObject} [e] 2785 * The event arguments from the modified pointer event, extended with 2786 * `chartX` and `chartY` 2787 * 2788 * @param {Highcharts.Point} [point] 2789 * The Point object if the crosshair snaps to points. 2790 * 2791 * @emits Highcharts.Axis#event:afterDrawCrosshair 2792 * @emits Highcharts.Axis#event:drawCrosshair 2793 */ 2794 drawCrosshair(e, point) { 2795 const options = this.crosshair, snap = pick(options && options.snap, true), chart = this.chart; 2796 let path, pos, categorized, graphic = this.cross, crossOptions; 2797 fireEvent(this, 'drawCrosshair', { e: e, point: point }); 2798 // Use last available event when updating non-snapped crosshairs without 2799 // mouse interaction (#5287) 2800 if (!e) { 2801 e = this.cross && this.cross.e; 2802 } 2803 if ( 2804 // Disabled in options 2805 !options || 2806 // Snap 2807 ((defined(point) || !snap) === false)) { 2808 this.hideCrosshair(); 2809 } 2810 else { 2811 // Get the path 2812 if (!snap) { 2813 pos = e && 2814 (this.horiz ? 2815 e.chartX - this.pos : 2816 this.len - e.chartY + this.pos); 2817 }
2818 else if (defined(point)) { 2819 // #3834 2820 pos = pick(this.coll !== 'colorAxis' ? 2821 point.crosshairPos : // 3D axis extension 2822 null, this.isXAxis ? 2823 point.plotX : 2824 this.len - point.plotY); 2825 } 2826 if (defined(pos)) { 2827 crossOptions = { 2828 // Value, only used on radial 2829 value: point && (this.isXAxis ? 2830 point.x : 2831 pick(point.stackY, point.y)), 2832 translatedValue: pos 2833 }; 2834 if (chart.polar) { 2835 // Additional information required for crosshairs in 2836 // polar chart 2837 extend(crossOptions, { 2838 isCrosshair: true, 2839 chartX: e && e.chartX, 2840 chartY: e && e.chartY, 2841 point: point 2842 }); 2843 } 2844 path = this.getPlotLinePath(crossOptions) || 2845 null; // #3189 2846 } 2847 if (!defined(path)) { 2848 this.hideCrosshair(); 2849 return; 2850 } 2851 categorized = this.categories && !this.isRadial; 2852 // Draw the cross 2853 if (!graphic) { 2854 this.cross = graphic = chart.renderer 2855 .path() 2856 .addClass('highcharts-crosshair highcharts-crosshair-' + 2857 (categorized ? 'category ' : 'thin ') + 2858 (options.className || '')) 2859 .attr({ 2860 zIndex: pick(options.zIndex, 2) 2861 }) 2862 .add(); 2863 // Presentational attributes 2864 if (!chart.styledMode) { 2865 graphic.attr({ 2866 stroke: options.color || 2867 (categorized ? 2868 Color 2869 .parse("#ccd3ff" /* Palette.highlightColor20 */) 2870 .setOpacity(0.25) 2871 .get() : 2872 "#cccccc" /* Palette.neutralColor20 */), 2873 'stroke-width': pick(options.width, 1) 2874 }).css({ 2875 'pointer-events': 'none' 2876 }); 2877 if (options.dashStyle) { 2878 graphic.attr({ 2879 dashstyle: options.dashStyle 2880 }); 2881 } 2882 } 2883 } 2884 graphic.show().attr({ 2885 d: path 2886 }); 2887 if (categorized && !options.width) { 2888 graphic.attr({ 2889 'stroke-width': this.transA 2890 }); 2891 } 2892 this.cross.e = e; 2893 } 2894 fireEvent(this, 'afterDrawCrosshair', { e: e, point: point }); 2895 } 2896 /** 2897 * Hide the crosshair if visible. 2898 * 2899 * @function Highcharts.Axis#hideCrosshair 2900 */ 2901 hideCrosshair() { 2902 if (this.cross) { 2903 this.cross.hide(); 2904 } 2905 fireEvent(this, 'afterHideCrosshair'); 2906 } 2907 /** 2908 * Update an axis object with a new set of options. The options are merged 2909 * with the existing options, so only new or altered options need to be 2910 * specified. 2911 * 2912 * @sample highcharts/members/axis-update/ 2913 * Axis update demo 2914 * 2915 * @function Highcharts.Axis#update 2916 * 2917 * @param {Highcharts.AxisOptions} options 2918 * The new options that will be merged in with existing options on the axis. 2919 * 2920 * @param {boolean} [redraw=true] 2921 * Whether to redraw the chart after the axis is altered. If doing more 2922 * operations on the chart, it is a good idea to set redraw to false and 2923 * call {@link Chart#redraw} after. 2924 */ 2925 update(options, redraw) { 2926 const chart = this.chart; 2927 options = merge(this.userOptions, options); 2928 this.destroy(true); 2929 this.init(chart, options); 2930 chart.isDirtyBox = true; 2931 if (pick(redraw, true)) { 2932 chart.redraw(); 2933 } 2934 } 2935 /** 2936 * Remove the axis from the chart. 2937 * 2938 * @sample highcharts/members/chart-addaxis/ 2939 * Add and remove axes 2940 * 2941 * @function Highcharts.Axis#remove 2942 * 2943 * @param {boolean} [redraw=true] 2944 * Whether to redraw the chart following the remove. 2945 */ 2946 remove(redraw) { 2947 const chart = this.chart, coll = this.coll, axisSeries = this.series; 2948 let i = axisSeries.length; 2949 // Remove associated series (#2687) 2950 while (i--) { 2951 if (axisSeries[i]) { 2952 axisSeries[i].remove(false); 2953 } 2954 } 2955 // Remove the axis 2956 erase(chart.axes, this); 2957 erase(chart[coll] || [], this); 2958 chart.orderItems(coll); 2959 this.destroy(); 2960 chart.isDirtyBox = true; 2961 if (pick(redraw, true)) { 2962 chart.redraw(); 2963 } 2964 } 2965 /** 2966 * Update the axis title by options after render time. 2967 * 2968 * @sample highcharts/members/axis-settitle/ 2969 * Set a new Y axis title 2970 * 2971 * @function Highcharts.Axis#setTitle 2972 * 2973 * @param {Highcharts.AxisTitleOptions} titleOptions 2974 * The additional title options. 2975 * 2976 * @param {boolean} [redraw=true] 2977 * Whether to redraw the chart after setting the title. 2978 */ 2979 setTitle(titleOptions, redraw) { 2980 this.update({ title: titleOptions }, redraw); 2981 } 2982 /** 2983 * Set new axis categories and optionally redraw. 2984 * 2985 * @sample highcharts/members/axis-setcategories/ 2986 * Set categories by click on a button 2987 * 2988 * @function Highcharts.Axis#setCategories 2989 * 2990 * @param {Array<string>} categories 2991 * The new categories. 2992 * 2993 * @param {boolean} [redraw=true] 2994 * Whether to redraw the chart. 2995 */ 2996 setCategories(categories, redraw) { 2997 this.update({ categories: categories }, redraw); 2998 } 2999} 3000/* * 3001 * 3002 * Static Properties 3003 * 3004 * */ 3005// Properties to survive after destroy, needed for Axis.update (#4317, 3006// #5773, #5881). 3007Axis.keepProps = [ 3008 'coll', 3009 'extKey', 3010 'hcEvents', 3011 'len',
3012 'names', 3013 'series', 3014 'userMax', 3015 'userMin' 3016]; 3017/* * 3018 * 3019 * Default Export 3020 * 3021 * */ 3022export default Axis; 3023/* * 3024 * 3025 * API Declarations 3026 * 3027 * */ 3028/** 3029 * Options for the path on the Axis to be calculated. 3030 * @interface Highcharts.AxisPlotLinePathOptionsObject 3031 */ /** 3032* Axis value. 3033* @name Highcharts.AxisPlotLinePathOptionsObject#value 3034* @type {number|undefined} 3035*/ /** 3036* Line width used for calculation crisp line coordinates. Defaults to 1. 3037* @name Highcharts.AxisPlotLinePathOptionsObject#lineWidth 3038* @type {number|undefined} 3039*/ /** 3040* If `false`, the function will return null when it falls outside the axis 3041* bounds. If `true`, the function will return a path aligned to the plot area 3042* sides if it falls outside. If `pass`, it will return a path outside. 3043* @name Highcharts.AxisPlotLinePathOptionsObject#force 3044* @type {string|boolean|undefined} 3045*/ /** 3046* Used in Highcharts Stock. When `true`, plot paths 3047* (crosshair, plotLines, gridLines) 3048* will be rendered on all axes when defined on the first axis. 3049* @name Highcharts.AxisPlotLinePathOptionsObject#acrossPanes 3050* @type {boolean|undefined} 3051*/ /** 3052* Use old coordinates (for resizing and rescaling). 3053* If not set, defaults to `false`. 3054* @name Highcharts.AxisPlotLinePathOptionsObject#old 3055* @type {boolean|undefined} 3056*/ /** 3057* If given, return the plot line path of a pixel position on the axis. 3058* @name Highcharts.AxisPlotLinePathOptionsObject#translatedValue 3059* @type {number|undefined} 3060*/ /** 3061* Used in Polar axes. Reverse the positions for concatenation of polygonal 3062* plot bands 3063* @name Highcharts.AxisPlotLinePathOptionsObject#reverse 3064* @type {boolean|undefined} 3065*/ 3066/** 3067 * Options for crosshairs on axes. 3068 * 3069 * @product highstock 3070 * 3071 * @typedef {Highcharts.XAxisCrosshairOptions|Highcharts.YAxisCrosshairOptions} Highcharts.AxisCrosshairOptions 3072 */ 3073/** 3074 * @typedef {"navigator"|"pan"|"rangeSelectorButton"|"rangeSelectorInput"|"scrollbar"|"traverseUpButton"|"zoom"} Highcharts.AxisExtremesTriggerValue 3075 */ 3076/** 3077 * @callback Highcharts.AxisEventCallbackFunction 3078 * 3079 * @param {Highcharts.Axis} this 3080 */ 3081/** 3082 * @callback Highcharts.AxisLabelsFormatterCallbackFunction 3083 * 3084 * @param {Highcharts.AxisLabelsFormatterContextObject} this 3085 * 3086 * @param {Highcharts.AxisLabelsFormatterContextObject} ctx 3087 * 3088 * @return {string} 3089 */ 3090/** 3091 * @interface Highcharts.AxisLabelsFormatterContextObject 3092 */ /** 3093* The axis item of the label 3094* @name Highcharts.AxisLabelsFormatterContextObject#axis 3095* @type {Highcharts.Axis} 3096*/ /** 3097* The chart instance. 3098* @name Highcharts.AxisLabelsFormatterContextObject#chart 3099* @type {Highcharts.Chart} 3100*/ /** 3101* Default formatting of date/time labels. 3102* @name Highcharts.AxisLabelsFormatterContextObject#dateTimeLabelFormat 3103* @type {string|undefined} 3104*/ /** 3105* Whether the label belongs to the first tick on the axis. 3106* @name Highcharts.AxisLabelsFormatterContextObject#isFirst 3107* @type {boolean} 3108*/ /** 3109* Whether the label belongs to the last tick on the axis. 3110* @name Highcharts.AxisLabelsFormatterContextObject#isLast 3111* @type {boolean} 3112*/ /** 3113* The position on the axis in terms of axis values. For category axes, a 3114* zero-based index. For datetime axes, the JavaScript time in milliseconds 3115* since 1970. 3116* @name Highcharts.AxisLabelsFormatterContextObject#pos 3117* @type {number} 3118*/ /** 3119* The preformatted text as the result of the default formatting. For example 3120* dates will be formatted as strings, and numbers with language-specific comma 3121* separators, thousands separators and numeric symbols like `k` or `M`. 3122* @name Highcharts.AxisLabelsFormatterContextObject#text 3123* @type {string|undefined} 3124*/ /** 3125* The Tick instance. 3126* @name Highcharts.AxisLabelsFormatterContextObject#tick 3127* @type {Highcharts.Tick} 3128*/ /** 3129* This can be either a numeric value or a category string. 3130* @name Highcharts.AxisLabelsFormatterContextObject#value 3131* @type {number|string} 3132*/ 3133/** 3134 * Options for axes. 3135 * 3136 * @typedef {Highcharts.XAxisOptions|Highcharts.YAxisOptions|Highcharts.ZAxisOptions} Highcharts.AxisOptions 3137 */ 3138/** 3139 * @callback Highcharts.AxisPointBreakEventCallbackFunction 3140 * 3141 * @param {Highcharts.Axis} this 3142 * 3143 * @param {Highcharts.AxisPointBreakEventObject} evt 3144 */ 3145/** 3146 * @interface Highcharts.AxisPointBreakEventObject 3147 */ /** 3148* @name Highcharts.AxisPointBreakEventObject#brk 3149* @type {Highcharts.Dictionary<number>} 3150*/ /** 3151* @name Highcharts.AxisPointBreakEventObject#point 3152* @type {Highcharts.Point} 3153*/ /** 3154* @name Highcharts.AxisPointBreakEventObject#preventDefault 3155* @type {Function} 3156*/ /** 3157* @name Highcharts.AxisPointBreakEventObject#target 3158* @type {Highcharts.SVGElement} 3159*/ /** 3160* @name Highcharts.AxisPointBreakEventObject#type 3161* @type {"pointBreak"|"pointInBreak"} 3162*/ 3163/** 3164 * @callback Highcharts.AxisSetExtremesEventCallbackFunction 3165 * 3166 * @param {Highcharts.Axis} this 3167 * 3168 * @param {Highcharts.AxisSetExtremesEventObject} evt 3169 */ 3170/** 3171 * @interface Highcharts.AxisSetExtremesEventObject 3172 * @extends Highcharts.ExtremesObject 3173 */ /** 3174* @name Highcharts.AxisSetExtremesEventObject#preventDefault 3175* @type {Function} 3176*/ /** 3177* @name Highcharts.AxisSetExtremesEventObject#target 3178* @type {Highcharts.SVGElement} 3179*/ /** 3180* @name Highcharts.AxisSetExtremesEventObject#trigger 3181* @type {Highcharts.AxisExtremesTriggerValue|string} 3182*/ /** 3183* @name Highcharts.AxisSetExtremesEventObject#type 3184* @type {"setExtremes"} 3185*/ 3186/** 3187 * @callback Highcharts.AxisTickPositionerCallbackFunction 3188 * 3189 * @param {Highcharts.Axis} this 3190 * 3191 * @return {Highcharts.AxisTickPositionsArray} 3192 */ 3193/** 3194 * @interface Highcharts.AxisTickPositionsArray 3195 * @augments Array<number> 3196 */ 3197/** 3198 * @typedef {"high"|"low"|"middle"} Highcharts.AxisTitleAlignValue 3199 */ 3200/** 3201 * @typedef {Highcharts.XAxisTitleOptions|Highcharts.YAxisTitleOptions|Highcharts.ZAxisTitleOptions} Highcharts.AxisTitleOptions 3202 */ 3203/** 3204 * @typedef {"linear"|"logarithmic"|"datetime"|"category"|"treegrid"} Highcharts.AxisTypeValue 3205 */ 3206/** 3207 * The returned object literal from the {@link Highcharts.Axis#getExtremes} 3208 * function. 3209 * 3210 * @interface Highcharts.ExtremesObject 3211 */ /** 3212* The maximum value of the axis' associated series. 3213* @name Highcharts.ExtremesObject#dataMax 3214* @type {number} 3215*/ /** 3216* The minimum value of the axis' associated series. 3217* @name Highcharts.ExtremesObject#dataMin 3218* @type {number} 3219*/ /** 3220* The maximum axis value, either automatic or set manually. If the `max` option 3221* is not set, `maxPadding` is 0 and `endOnTick` is false, this value will be 3222* the same as `dataMax`. 3223* @name Highcharts.ExtremesObject#max 3224* @type {number} 3225*/ /** 3226* The minimum axis value, either automatic or set manually. If the `min` option 3227* is not set, `minPadding` is 0 and `startOnTick` is false, this value will be 3228* the same as `dataMin`. 3229* @name Highcharts.ExtremesObject#min 3230* @type {number} 3231*/ /** 3232* The user defined maximum, either from the `max` option or from a zoom or 3233* `setExtremes` action. 3234* @name Highcharts.ExtremesObject#userMax 3235* @type {number|undefined} 3236*/ /** 3237* The user defined minimum, either from the `min` option or from a zoom or 3238* `setExtremes` action. 3239* @name Highcharts.ExtremesObject#userMin 3240* @type {number|undefined} 3241*/ 3242/** 3243 * Formatter function for the text of a crosshair label. 3244 * 3245 * @callback Highcharts.XAxisCrosshairLabelFormatterCallbackFunction 3246 * 3247 * @param {Highcharts.Axis} this 3248 * Axis context 3249 * 3250 * @param {number} value 3251 * Y value of the data point 3252 * 3253 * @return {string} 3254 */ 3255''; // 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.