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, setAnimation } = A; 13import DataTableCore from '../../Data/DataTableCore.js'; 14import D from '../Defaults.js'; 15const { defaultOptions } = D; 16import F from '../Foundation.js'; 17const { registerEventOptions } = F; 18import H from '../Globals.js'; 19const { svg, win } = H; 20import LegendSymbol from '../Legend/LegendSymbol.js'; 21import Point from './Point.js'; 22import SeriesDefaults from './SeriesDefaults.js'; 23import SeriesRegistry from './SeriesRegistry.js'; 24const { seriesTypes } = SeriesRegistry; 25import SVGElement from '../Renderer/SVG/SVGElement.js'; 26import U from '../Utilities.js'; 27const { arrayMax, arrayMin, clamp, correctFloat, crisp, defined, destroyObjectProperties, diffObjects, erase, error, extend, find, fireEvent, getClosestDistance, getNestedProperty, insertItem, isArray, isNumber, isString, merge, objectEach, pick, removeEvent, syncTimeout } = U; 28/* * 29 * 30 * Class 31 * 32 * */ 33/** 34 * This is the base series prototype that all other series types inherit from. 35 * A new series is initialized either through the 36 * [series](https://api.highcharts.com/highcharts/series) 37 * option structure, or after the chart is initialized, through 38 * {@link Highcharts.Chart#addSeries}. 39 * 40 * The object can be accessed in a number of ways. All series and point event 41 * handlers give a reference to the `series` object. The chart object has a 42 * {@link Highcharts.Chart#series|series} property that is a collection of all 43 * the chart's series. The point objects and axis objects also have the same 44 * reference. 45 * 46 * Another way to reference the series programmatically is by `id`. Add an id 47 * in the series configuration options, and get the series object by 48 * {@link Highcharts.Chart#get}. 49 * 50 * Configuration options for the series are given in three levels. Options for 51 * all series in a chart are given in the 52 * [plotOptions.series](https://api.highcharts.com/highcharts/plotOptions.series) 53 * object. Then options for all series of a specific type 54 * are given in the plotOptions of that type, for example `plotOptions.line`. 55 * Next, options for one single series are given in the series array, or as 56 * arguments to `chart.addSeries`. 57 * 58 * The data in the series is stored in various arrays. 59 * 60 * - First, `series.options.data` contains all the original config options for 61 * each point whether added by options or methods like `series.addPoint`. 62 * 63 * - The `series.dataTable` refers to an instance of [DataTableCore](https://api.highcharts.com/class-reference/Highcharts.Data) 64 * or `DataTable` that contains the data in a tabular format. Individual 65 * columns can be read from `series.getColumn()`. 66 * 67 * - Next, `series.data` contains those values converted to points, but in case 68 * the series data length exceeds the `cropThreshold`, or if the data is 69 * grouped, `series.data` doesn't contain all the points. It only contains the 70 * points that have been created on demand. 71 * 72 * - Then there's `series.points` that contains all currently visible point 73 * objects. In case of cropping, the cropped-away points are not part of this 74 * array. The `series.points` array starts at `series.cropStart` compared to 75 * `series.data` and `series.options.data`. If however the series data is 76 * grouped, these can't be correlated one to one. 77 * 78 * @class 79 * @name Highcharts.Series 80 * 81 * @param {Highcharts.Chart} chart 82 * The chart instance. 83 * 84 * @param {Highcharts.SeriesOptionsType|object} options 85 * The series options. 86 */ 87class Series { 88 constructor() { 89 /* * 90 * 91 * Static Properties 92 * 93 * */ 94 this.zoneAxis = 'y'; 95 // eslint-enable valid-jsdoc 96 } 97 /* * 98 * 99 * Functions 100 * 101 * */ 102 /* eslint-disable valid-jsdoc */ 103 init(chart, userOptions) { 104 fireEvent(this, 'init', { options: userOptions }); 105 // Create the data table 106 this.dataTable ?? (this.dataTable = new DataTableCore()); 107 const series = this, chartSeries = chart.series; 108 // The 'eventsToUnbind' property moved from prototype into the 109 // Series init to avoid reference to the same array between 110 // the different series and charts. #12959, #13937 111 this.eventsToUnbind = []; 112 /** 113 * Read only. The chart that the series belongs to. 114 * 115 * @name Highcharts.Series#chart 116 * @type {Highcharts.Chart} 117 */ 118 series.chart = chart; 119 /** 120 * Read only. The series' type, like "line", "area", "column" etc. 121 * The type in the series options anc can be altered using 122 * {@link Series#update}. 123 * 124 * @name Highcharts.Series#type 125 * @type {string} 126 */ 127 /** 128 * Read only. The series' current options. To update, use 129 * {@link Series#update}. 130 * 131 * @name Highcharts.Series#options 132 * @type {Highcharts.SeriesOptionsType} 133 */ 134 series.options = series.setOptions(userOptions); 135 const options = series.options, visible = options.visible !== false; 136 /** 137 * All child series that are linked to the current series through the 138 * [linkedTo](https://api.highcharts.com/highcharts/series.line.linkedTo) 139 * option. 140 * 141 * @name Highcharts.Series#linkedSeries 142 * @type {Array<Highcharts.Series>} 143 * @readonly 144 */ 145 series.linkedSeries = []; 146 // Bind the axes 147 series.bindAxes(); 148 extend(series, { 149 /**
150 * The series name as given in the options. Defaults to 151 * "Series {n}". 152 * 153 * @name Highcharts.Series#name 154 * @type {string} 155 */ 156 name: options.name, 157 state: '', 158 /** 159 * Read only. The series' visibility state as set by {@link 160 * Series#show}, {@link Series#hide}, or in the initial 161 * configuration. 162 * 163 * @name Highcharts.Series#visible 164 * @type {boolean} 165 */ 166 visible, // True by default 167 /** 168 * Read only. The series' selected state as set by {@link 169 * Highcharts.Series#select}. 170 * 171 * @name Highcharts.Series#selected 172 * @type {boolean} 173 */ 174 selected: options.selected === true // False by default 175 }); 176 registerEventOptions(this, options); 177 const events = options.events; 178 if ((events && events.click) || 179 (options.point && 180 options.point.events && 181 options.point.events.click) || 182 options.allowPointSelect) { 183 chart.runTrackerClick = true; 184 } 185 series.getColor(); 186 series.getSymbol(); 187 // Mark cartesian 188 if (series.isCartesian) { 189 chart.hasCartesianSeries = true; 190 } 191 // Get the index and register the series in the chart. The index is 192 // one more than the current latest series index (#5960). 193 let lastSeries; 194 if (chartSeries.length) { 195 lastSeries = chartSeries[chartSeries.length - 1]; 196 } 197 series._i = pick(lastSeries && lastSeries._i, -1) + 1; 198 series.opacity = series.options.opacity; 199 // Insert the series and re-order all series above the insertion 200 // point. 201 chart.orderItems('series', insertItem(this, chartSeries)); 202 // Set options for series with sorting and set data later. 203 if (options.dataSorting && options.dataSorting.enabled) { 204 series.setDataSortingOptions(); 205 } 206 else if (!series.points && !series.data) { 207 series.setData(options.data, false); 208 } 209 fireEvent(this, 'afterInit'); 210 } 211 /** 212 * Check whether the series item is itself or inherits from a certain 213 * series type. 214 * 215 * @function Highcharts.Series#is 216 * @param {string} type The type of series to check for, can be either 217 * featured or custom series types. For example `column`, `pie`, 218 * `ohlc` etc. 219 * 220 * @return {boolean} 221 * True if this item is or inherits from the given type. 222 */ 223 is(type) { 224 return seriesTypes[type] && this instanceof seriesTypes[type]; 225 } 226 /** 227 * Set the xAxis and yAxis properties of cartesian series, and register 228 * the series in the `axis.series` array. 229 * 230 * @private 231 * @function Highcharts.Series#bindAxes 232 */ 233 bindAxes() { 234 const series = this, seriesOptions = series.options, chart = series.chart; 235 let axisOptions; 236 fireEvent(this, 'bindAxes', null, function () { 237 // Repeat for xAxis and yAxis 238 (series.axisTypes || []).forEach(function (coll) { 239 // Loop through the chart's axis objects 240 (chart[coll] || []).forEach(function (axis) { 241 axisOptions = axis.options; 242 // Apply if the series xAxis or yAxis option matches 243 // the number of the axis, or if undefined, use the 244 // first axis 245 if (pick(seriesOptions[coll], 0) === axis.index || 246 (typeof seriesOptions[coll] !== 247 'undefined' && 248 seriesOptions[coll] === axisOptions.id)) { 249 // Register this series in the axis.series lookup 250 insertItem(series, axis.series); 251 // Set this series.xAxis or series.yAxis reference 252 /** 253 * Read only. The unique xAxis object associated 254 * with the series. 255 * 256 * @name Highcharts.Series#xAxis 257 * @type {Highcharts.Axis} 258 */ 259 /** 260 * Read only. The unique yAxis object associated 261 * with the series. 262 * 263 * @name Highcharts.Series#yAxis 264 * @type {Highcharts.Axis} 265 */ 266 series[coll] = axis; 267 // Mark dirty for redraw 268 axis.isDirty = true; 269 } 270 }); 271 // The series needs an X and an Y axis 272 if (!series[coll] && 273 series.optionalAxis !== coll) { 274 error(18, true, chart); 275 } 276 }); 277 }); 278 fireEvent(this, 'afterBindAxes'); 279 } 280 /** 281 * Define hasData functions for series. These return true if there 282 * are data points on this series within the plot area. 283 * 284 * @private 285 * @function Highcharts.Series#hasData 286 */ 287 hasData() { 288 return ((this.visible && 289 typeof this.dataMax !== 'undefined' && 290 typeof this.dataMin !== 'undefined') || ( // #3703 291 this.visible && 292 this.dataTable.rowCount > 0 // #9758 293 )); 294 } 295 /** 296 * Determine whether the marker in a series has changed. 297 * 298 * @private 299 * @function Highcharts.Series#hasMarkerChanged 300 */ 301 hasMarkerChanged(options, oldOptions) { 302 const marker = options.marker, oldMarker = oldOptions.marker || {}; 303 return marker && ((oldMarker.enabled && !marker.enabled) || 304 oldMarker.symbol !== marker.symbol || // #10870, #15946 305 oldMarker.height !== marker.height || // #16274 306 oldMarker.width !== marker.width // #16274 307 ); 308 } 309 /** 310 * Return an auto incremented x value based on the pointStart and 311 * pointInterval options. This is only used if an x value is not given 312 * for the point that calls autoIncrement. 313 * 314 * @private 315 * @function Highcharts.Series#autoIncrement 316 */ 317 autoIncrement(x) { 318 const options = this.options, { pointIntervalUnit, relativeXValue } = this.options, time = this.chart.time, xIncrement = this.xIncrement ?? 319 time.parse(options.pointStart) ?? 320 0; 321 let pointInterval; 322 this.pointInterval = pointInterval = pick(this.pointInterval, options.pointInterval, 1); 323 if (relativeXValue && isNumber(x)) { 324 pointInterval *= x; 325 } 326 // Added code for pointInterval strings 327 if (pointIntervalUnit) { 328 const d = time.toParts(xIncrement); 329 if (pointIntervalUnit === 'day') { 330 d[2] += pointInterval; 331 } 332 else if (pointIntervalUnit === 'month') { 333 d[1] += pointInterval; 334 } 335 else if (pointIntervalUnit === 'year') { 336 d[0] += pointInterval; 337 } 338 pointInterval = time.makeTime.apply(time, d) - xIncrement; 339 } 340 if (relativeXValue && isNumber(x)) { 341 return xIncrement + pointInterval; 342 } 343 this.xIncrement = xIncrement + pointInterval; 344 return xIncrement; 345 } 346 /** 347 * Internal function to set properties for series if data sorting is 348 * enabled. 349 * 350 * @private 351 * @function Highcharts.Series#setDataSortingOptions 352 */ 353 setDataSortingOptions() { 354 const options = this.options; 355 extend(this, { 356 requireSorting: false, 357 sorted: false, 358 enabledDataSorting: true, 359 allowDG: false 360 }); 361 // To allow unsorted data for column series. 362 if (!defined(options.pointRange)) { 363 options.pointRange = 1; 364 } 365 } 366 /** 367 * Set the series options by merging from the options tree. Called 368 * internally on initializing and updating series. This function will 369 * not redraw the series. For API usage, use {@link Series#update}. 370 * @private 371 * @function Highcharts.Series#setOptions 372 * @param {Highcharts.SeriesOptionsType} itemOptions 373 * The series options. 374 * @emits Highcharts.Series#event:afterSetOptions 375 */ 376 setOptions(itemOptions) { 377 const chart = this.chart, chartOptions = chart.options, plotOptions = chartOptions.plotOptions, userOptions = chart.userOptions || {}, seriesUserOptions = merge(itemOptions), styledMode = chart.styledMode, e = { 378 plotOptions: plotOptions, 379 userOptions: seriesUserOptions 380 }; 381 let zone; 382 fireEvent(this, 'setOptions', e); 383 // These may be modified by the event 384 const typeOptions = e.plotOptions[this.type], userPlotOptions = (userOptions.plotOptions || {}), userPlotOptionsSeries = userPlotOptions.series || {}, defaultPlotOptionsType = (defaultOptions.plotOptions[this.type] || {}), userPlotOptionsType = userPlotOptions[this.type] || {}; 385 // Use copy to prevent undetected changes (#9762) 386 /** 387 * Contains series options by the user without defaults. 388 * @name Highcharts.Series#userOptions 389 * @type {Highcharts.SeriesOptionsType} 390 */ 391 this.userOptions = e.userOptions; 392 const options = merge(typeOptions, plotOptions.series, 393 // #3881, chart instance plotOptions[type] should trump 394 // plotOptions.series 395 userPlotOptionsType, seriesUserOptions); 396 // The tooltip options are merged between global and series specific 397 // options. Importance order asscendingly: 398 // globals: (1)tooltip, (2)plotOptions.series, 399 // (3)plotOptions[this.type] 400 // init userOptions with possible later updates: 4-6 like 1-3 and 401 // (7)this series options 402 this.tooltipOptions = merge(defaultOptions.tooltip, // 1 403 defaultOptions.plotOptions.series?.tooltip, // 2 404 defaultPlotOptionsType?.tooltip, // 3 405 chart.userOptions.tooltip, // 4 406 userPlotOptions.series?.tooltip, // 5 407 userPlotOptionsType.tooltip, // 6 408 seriesUserOptions.tooltip // 7 409 ); 410 // When shared tooltip, stickyTracking is true by default, 411 // unless user says otherwise. 412 this.stickyTracking = pick(seriesUserOptions.stickyTracking, userPlotOptionsType.stickyTracking, userPlotOptionsSeries.stickyTracking, (this.tooltipOptions.shared && !this.noSharedTooltip ? 413 true : 414 options.stickyTracking)); 415 // Delete marker object if not allowed (#1125) 416 if (typeOptions.marker === null) { 417 delete options.marker; 418 } 419 // Handle color zones 420 this.zoneAxis = options.zoneAxis || 'y'; 421 const zones = this.zones = // #20440, create deep copy of zones options 422 (options.zones || []).map((z) => ({ ...z })); 423 if ((options.negativeColor || options.negativeFillColor) && 424 !options.zones) { 425 zone = { 426 value: options[this.zoneAxis + 'Threshold'] || 427 options.threshold || 428 0, 429 className: 'highcharts-negative' 430 }; 431 if (!styledMode) { 432 zone.color = options.negativeColor; 433 zone.fillColor = options.negativeFillColor; 434 } 435 zones.push(zone); 436 } 437 // Push one extra zone for the rest 438 if (zones.length && defined(zones[zones.length - 1].value)) { 439 zones.push(styledMode ? {} : { 440 color: this.color, 441 fillColor: this.fillColor 442 }); 443 } 444 fireEvent(this, 'afterSetOptions', { options: options }); 445 return options; 446 } 447 /**
448 * Return series name in "Series {Number}" format or the one defined by 449 * a user. This method can be simply overridden as series name format 450 * can vary (e.g. technical indicators). 451 * 452 * @function Highcharts.Series#getName 453 * 454 * @return {string} 455 * The series name. 456 */ 457 getName() { 458 // #4119 459 return pick(this.options.name, 'Series ' + (this.index + 1)); 460 } 461 /** 462 * @private 463 * @function Highcharts.Series#getCyclic 464 */ 465 getCyclic(prop, value, defaults) { 466 const chart = this.chart, indexName = `${prop}Index`, counterName = `${prop}Counter`, len = ( 467 // Symbol count 468 defaults?.length || 469 // Color count 470 chart.options.chart.colorCount); 471 let i, setting; 472 if (!value) { 473 // Pick up either the colorIndex option, or the series.colorIndex 474 // after Series.update() 475 setting = pick(prop === 'color' ? this.options.colorIndex : void 0, this[indexName]); 476 if (defined(setting)) { // After Series.update() 477 i = setting; 478 } 479 else { 480 // #6138 481 if (!chart.series.length) { 482 chart[counterName] = 0; 483 } 484 i = chart[counterName] % len; 485 chart[counterName] += 1; 486 } 487 if (defaults) { 488 value = defaults[i]; 489 } 490 } 491 // Set the colorIndex 492 if (typeof i !== 'undefined') { 493 this[indexName] = i; 494 } 495 this[prop] = value; 496 } 497 /** 498 * Get the series' color based on either the options or pulled from 499 * global options. 500 * 501 * @private 502 * @function Highcharts.Series#getColor 503 */ 504 getColor() { 505 if (this.chart.styledMode) { 506 this.getCyclic('color'); 507 } 508 else if (this.options.colorByPoint) { 509 this.color = "#cccccc" /* Palette.neutralColor20 */; 510 } 511 else { 512 this.getCyclic('color', this.options.color || 513 defaultOptions.plotOptions[this.type].color, this.chart.options.colors); 514 } 515 } 516 /** 517 * Get all points' instances created for this series. 518 * 519 * @private 520 * @function Highcharts.Series#getPointsCollection 521 */ 522 getPointsCollection() { 523 return (this.hasGroupedData ? this.points : this.data) || []; 524 } 525 /** 526 * Get the series' symbol based on either the options or pulled from 527 * global options. 528 * 529 * @private 530 * @function Highcharts.Series#getSymbol 531 */ 532 getSymbol() { 533 const seriesMarkerOption = this.options.marker; 534 this.getCyclic('symbol', seriesMarkerOption.symbol, this.chart.options.symbols); 535 } 536 /** 537 * Shorthand to get one of the series' data columns from `Series.dataTable`. 538 * 539 * @private 540 * @function Highcharts.Series#getColumn 541 */ 542 getColumn(columnName, modified) { 543 return (modified ? this.dataTable.modified : this.dataTable) 544 .getColumn(columnName, true) || []; 545 } 546 /** 547 * Finds the index of an existing point that matches the given point 548 * options. 549 * 550 * @private 551 * @function Highcharts.Series#findPointIndex 552 * @param {Highcharts.PointOptionsObject} optionsObject 553 * The options of the point. 554 * @param {number} fromIndex 555 * The index to start searching from, used for optimizing series with 556 * required sorting. 557 * @return {number|undefined} 558 * Returns the index of a matching point, or undefined if no match is found. 559 */ 560 findPointIndex(optionsObject, fromIndex) { 561 const id = optionsObject.id, x = optionsObject.x, oldData = this.points, dataSorting = this.options.dataSorting; 562 let matchingPoint, matchedById, pointIndex; 563 if (id) { 564 const item = this.chart.get(id); 565 if (item instanceof Point) { 566 matchingPoint = item; 567 } 568 } 569 else if (this.linkedParent || 570 this.enabledDataSorting || 571 this.options.relativeXValue) { 572 let matcher = (oldPoint) => !oldPoint.touched && 573 oldPoint.index === optionsObject.index; 574 if (dataSorting && dataSorting.matchByName) { 575 matcher = (oldPoint) => !oldPoint.touched && 576 oldPoint.name === optionsObject.name; 577 } 578 else if (this.options.relativeXValue) { 579 matcher = (oldPoint) => !oldPoint.touched && 580 oldPoint.options.x === optionsObject.x; 581 } 582 matchingPoint = find(oldData, matcher); 583 // Add unmatched point as a new point 584 if (!matchingPoint) { 585 return void 0; 586 } 587 } 588 if (matchingPoint) { 589 pointIndex = matchingPoint && matchingPoint.index; 590 if (typeof pointIndex !== 'undefined') { 591 matchedById = true; 592 } 593 } 594 // Search for the same X in the existing data set 595 if (typeof pointIndex === 'undefined' && isNumber(x)) { 596 pointIndex = this.getColumn('x').indexOf(x, fromIndex); 597 } 598 // Reduce pointIndex if data is cropped 599 if (pointIndex !== -1 && 600 typeof pointIndex !== 'undefined' && 601 this.cropped) { 602 pointIndex = (pointIndex >= this.cropStart) ? 603 pointIndex - this.cropStart : pointIndex; 604 } 605 if (!matchedById && 606 isNumber(pointIndex) && 607 oldData[pointIndex] && oldData[pointIndex].touched) { 608 pointIndex = void 0; 609 } 610 return pointIndex; 611 } 612 /** 613 * Internal function called from setData. If the point count is the same 614 * as it was, or if there are overlapping X values, just run 615 * Point.update which is cheaper, allows animation, and keeps references 616 * to points. This also allows adding or removing points if the X-es 617 * don't match. 618 * 619 * @private 620 * @function Highcharts.Series#updateData 621 */ 622 updateData(data, animation) { 623 const options = this.options, dataSorting = options.dataSorting, oldData = this.points, pointsToAdd = [], requireSorting = this.requireSorting, equalLength = data.length === oldData.length; 624 let hasUpdatedByKey, i, point, lastIndex, succeeded = true; 625 this.xIncrement = null; 626 // Iterate the new data 627 data.forEach(function (pointOptions, i) {
628 const optionsObject = (defined(pointOptions) && 629 this.pointClass.prototype.optionsToObject.call({ series: this }, pointOptions)) || {}; 630 let pointIndex; 631 // Get the x of the new data point 632 const x = optionsObject.x, id = optionsObject.id; 633 if (id || isNumber(x)) { 634 pointIndex = this.findPointIndex(optionsObject, lastIndex); 635 // Matching X not found 636 // or used already due to ununique x values (#8995), 637 // add point (but later) 638 if (pointIndex === -1 || 639 typeof pointIndex === 'undefined') { 640 pointsToAdd.push(pointOptions); 641 // Matching X found, update 642 } 643 else if (oldData[pointIndex] && 644 pointOptions !== options.data[pointIndex]) { 645 oldData[pointIndex].update(pointOptions, false, null, false); 646 // Mark it touched, below we will remove all points that 647 // are not touched. 648 oldData[pointIndex].touched = true; 649 // Speed optimize by only searching after last known 650 // index. Performs ~20% bettor on large data sets. 651 if (requireSorting) { 652 lastIndex = pointIndex + 1; 653 } 654 // Point exists, no changes, don't remove it 655 } 656 else if (oldData[pointIndex]) { 657 oldData[pointIndex].touched = true; 658 } 659 // If the length is equal and some of the nodes had a 660 // match in the same position, we don't want to remove 661 // non-matches. 662 if (!equalLength || 663 i !== pointIndex || 664 (dataSorting && dataSorting.enabled) || 665 this.hasDerivedData) { 666 hasUpdatedByKey = true; 667 } 668 } 669 else { 670 // Gather all points that are not matched 671 pointsToAdd.push(pointOptions); 672 } 673 }, this); 674 // Remove points that don't exist in the updated data set 675 if (hasUpdatedByKey) { 676 i = oldData.length; 677 while (i--) { 678 point = oldData[i]; 679 if (point && !point.touched && point.remove) { 680 point.remove(false, animation); 681 } 682 } 683 // If we did not find keys (ids or x-values), and the length is the 684 // same, update one-to-one 685 } 686 else if (equalLength && (!dataSorting || !dataSorting.enabled)) { 687 data.forEach(function (point, i) { 688 // .update doesn't exist on a linked, hidden series (#3709) 689 // (#10187) 690 if (point !== oldData[i].y && !oldData[i].destroyed) { 691 oldData[i].update(point, false, null, false); 692 } 693 }); 694 // Don't add new points since those configs are used above 695 pointsToAdd.length = 0; 696 // Did not succeed in updating data 697 } 698 else { 699 succeeded = false; 700 } 701 oldData.forEach(function (point) { 702 if (point) { 703 point.touched = false; 704 } 705 }); 706 if (!succeeded) { 707 return false; 708 } 709 // Add new points 710 pointsToAdd.forEach(function (point) { 711 this.addPoint(point, false, null, null, false); 712 }, this); 713 const xData = this.getColumn('x'); 714 if (this.xIncrement === null && 715 xData.length) { 716 this.xIncrement = arrayMax(xData); 717 this.autoIncrement(); 718 } 719 return true; 720 } 721 dataColumnKeys() { 722 return ['x', ...(this.pointArrayMap || ['y'])]; 723 } 724 /** 725 * Apply a new set of data to the series and optionally redraw it. The 726 * new data array is passed by reference (except in case of 727 * `updatePoints`), and may later be mutated when updating the chart 728 * data. 729 * 730 * Note the difference in behaviour when setting the same amount of 731 * points, or a different amount of points, as handled by the 732 * `updatePoints` parameter. 733 * 734 * @sample highcharts/members/series-setdata/ 735 * Set new data from a button 736 * @sample highcharts/members/series-setdata-pie/ 737 * Set data in a pie 738 * @sample stock/members/series-setdata/ 739 * Set new data in Highcharts Stock 740 * @sample maps/members/series-setdata/ 741 * Set new data in Highmaps 742 * 743 * @function Highcharts.Series#setData 744 * 745 * @param {Array<Highcharts.PointOptionsType>} data
746 * Takes an array of data in the same format as described under 747 * `series.{type}.data` for the given series type, for example a 748 * line series would take data in the form described under 749 * [series.line.data](https://api.highcharts.com/highcharts/series.line.data). 750 * 751 * @param {boolean} [redraw=true] 752 * Whether to redraw the chart after the series is altered. If 753 * doing more operations on the chart, it is a good idea to set 754 * redraw to false and call {@link Chart#redraw} after. 755 * 756 * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] 757 * When the updated data is the same length as the existing data, 758 * points will be updated by default, and animation visualizes 759 * how the points are changed. Set false to disable animation, or 760 * a configuration object to set duration or easing. 761 * 762 * @param {boolean} [updatePoints=true] 763 * When this is true, points will be updated instead of replaced 764 * whenever possible. This occurs a) when the updated data is the 765 * same length as the existing data, b) when points are matched 766 * by their id's, or c) when points can be matched by X values. 767 * This allows updating with animation and performs better. In 768 * this case, the original array is not passed by reference. Set 769 * `false` to prevent. 770 */ 771 setData(data, redraw = true, animation, updatePoints) { 772 const series = this, oldData = series.points, oldDataLength = (oldData && oldData.length) || 0, options = series.options, chart = series.chart, dataSorting = options.dataSorting, xAxis = series.xAxis, turboThreshold = options.turboThreshold, table = this.dataTable, dataColumnKeys = this.dataColumnKeys(), pointValKey = series.pointValKey || 'y', pointArrayMap = series.pointArrayMap || [], valueCount = pointArrayMap.length, keys = options.keys; 773 let i, updatedData, indexOfX = 0, indexOfY = 1, copiedData; 774 if (!chart.options.chart.allowMutatingData) { // #4259 775 // Remove old reference 776 if (options.data) { 777 delete series.options.data; 778 } 779 if (series.userOptions.data) { 780 delete series.userOptions.data; 781 } 782 copiedData = merge(true, data); 783 } 784 data = copiedData || data || []; 785 const dataLength = data.length; 786 if (dataSorting && dataSorting.enabled) { 787 data = this.sortData(data); 788 } 789 // First try to run Point.update which is cheaper, allows animation, and 790 // keeps references to points. 791 if (chart.options.chart.allowMutatingData && 792 updatePoints !== false && 793 dataLength && 794 oldDataLength && 795 !series.cropped && 796 !series.hasGroupedData && 797 series.visible && 798 // Soft updating has no benefit in boost, and causes JS error 799 // (#8355) 800 !series.boosted) { 801 updatedData = this.updateData(data, animation); 802 } 803 if (!updatedData) { 804 // Reset properties 805 series.xIncrement = null; 806 series.colorCounter = 0; // For series with colorByPoint (#1547) 807 // In turbo mode, look for one- or twodimensional arrays of numbers. 808 // The first and the last valid value are tested, and we assume that 809 // all the rest are defined the same way. Although the 'for' loops 810 // are similar, they are repeated inside each if-else conditional 811 // for max performance. 812 let runTurbo = turboThreshold && dataLength > turboThreshold; 813 if (runTurbo) { 814 const firstPoint = series.getFirstValidPoint(data), lastPoint = series.getFirstValidPoint(data, dataLength - 1, -1), isShortArray = (a) => Boolean(isArray(a) && (keys || isNumber(a[0]))); 815 // Assume all points are numbers 816 if (isNumber(firstPoint) && isNumber(lastPoint)) { 817 const x = [], valueData = []; 818 for (const value of data) { 819 x.push(this.autoIncrement()); 820 valueData.push(value); 821 } 822 table.setColumns({ 823 x, 824 [pointValKey]: valueData 825 }); 826 // Assume all points are arrays when first point is 827 } 828 else if (isShortArray(firstPoint) && 829 isShortArray(lastPoint)) {
830 if (valueCount) { // [x, low, high] or [x, o, h, l, c] 831 // When autoX is 1, the x is skipped: [low, high]. When 832 // autoX is 0, the x is included: [x, low, high] 833 const autoX = firstPoint.length === valueCount ? 834 1 : 0, colArray = new Array(dataColumnKeys.length) 835 .fill(0).map(() => []); 836 for (const pt of data) { 837 if (autoX) { 838 colArray[0].push(this.autoIncrement()); 839 } 840 for (let j = autoX; j <= valueCount; j++) { 841 colArray[j]?.push(pt[j - autoX]); 842 } 843 } 844 table.setColumns(dataColumnKeys.reduce((columns, columnName, i) => { 845 columns[columnName] = colArray[i]; 846 return columns; 847 }, {})); 848 } 849 else { // [x, y] 850 if (keys) { 851 indexOfX = keys.indexOf('x'); 852 indexOfY = keys.indexOf('y'); 853 indexOfX = indexOfX >= 0 ? indexOfX : 0; 854 indexOfY = indexOfY >= 0 ? indexOfY : 1; 855 } 856 if (firstPoint.length === 1) { 857 indexOfY = 0; 858 } 859 const xData = [], valueData = []; 860 if (indexOfX === indexOfY) { 861 for (const pt of data) { 862 xData.push(this.autoIncrement()); 863 valueData.push(pt[indexOfY]); 864 } 865 } 866 else { 867 for (const pt of data) { 868 xData.push(pt[indexOfX]); 869 valueData.push(pt[indexOfY]); 870 } 871 } 872 table.setColumns({ 873 x: xData, 874 [pointValKey]: valueData 875 }); 876 } 877 } 878 else { 879 // Highcharts expects configs to be numbers or arrays in 880 // turbo mode 881 runTurbo = false; 882 } 883 } 884 if (!runTurbo) { 885 const columns = dataColumnKeys.reduce((columns, columnName) => { 886 columns[columnName] = []; 887 return columns; 888 }, {}); 889 for (i = 0; i < dataLength; i++) { 890 const pt = series.pointClass.prototype.applyOptions.apply({ series }, [data[i]]); 891 for (const key of dataColumnKeys) { 892 columns[key][i] = pt[key]; 893 } 894 } 895 table.setColumns(columns); 896 } 897 // Forgetting to cast strings to numbers is a common caveat when 898 // handling CSV or JSON 899 if (isString(this.getColumn('y')[0])) { 900 error(14, true, chart); 901 } 902 series.data = []; 903 series.options.data = series.userOptions.data = data; 904 // Destroy old points 905 i = oldDataLength; 906 while (i--) { 907 oldData[i]?.destroy(); 908 } 909 // Reset minRange (#878) 910 if (xAxis) { 911 xAxis.minRange = xAxis.userMinRange; 912 } 913 // Redraw 914 series.isDirty = chart.isDirtyBox = true; 915 series.isDirtyData = !!oldData; 916 animation = false; 917 } 918 // Typically for pie series, points need to be processed and 919 // generated prior to rendering the legend 920 if (options.legendType === 'point') { 921 this.processData(); 922 this.generatePoints(); 923 } 924 if (redraw) { 925 chart.redraw(animation); 926 } 927 } 928 /** 929 * Internal function to sort series data 930 * 931 * @private 932 * @function Highcharts.Series#sortData 933 * @param {Array<Highcharts.PointOptionsType>} data 934 * Force data grouping. 935 */ 936 sortData(data) { 937 const series = this, options = series.options, dataSorting = options.dataSorting, sortKey = dataSorting.sortKey || 'y', getPointOptionsObject = function (series, pointOptions) {
938 return (defined(pointOptions) && 939 series.pointClass.prototype.optionsToObject.call({ 940 series: series 941 }, pointOptions)) || {}; 942 }; 943 data.forEach(function (pointOptions, i) { 944 data[i] = getPointOptionsObject(series, pointOptions); 945 data[i].index = i; 946 }, this); 947 // Sorting 948 const sortedData = data.concat().sort((a, b) => { 949 const aValue = getNestedProperty(sortKey, a); 950 const bValue = getNestedProperty(sortKey, b); 951 return bValue < aValue ? -1 : bValue > aValue ? 1 : 0; 952 }); 953 // Set x value depending on the position in the array 954 sortedData.forEach(function (point, i) { 955 point.x = i; 956 }, this); 957 // Set the same x for linked series points if they don't have their 958 // own sorting 959 if (series.linkedSeries) { 960 series.linkedSeries.forEach(function (linkedSeries) { 961 const options = linkedSeries.options, seriesData = options.data; 962 if ((!options.dataSorting || 963 !options.dataSorting.enabled) && 964 seriesData) { 965 seriesData.forEach(function (pointOptions, i) { 966 seriesData[i] = getPointOptionsObject(linkedSeries, pointOptions); 967 if (data[i]) { 968 seriesData[i].x = data[i].x; 969 seriesData[i].index = i; 970 } 971 }); 972 linkedSeries.setData(seriesData, false); 973 } 974 }); 975 } 976 return data; 977 } 978 /** 979 * Internal function to process the data by cropping away unused data 980 * points if the series is longer than the crop threshold. This saves 981 * computing time for large series. 982 * 983 * @private 984 * @function Highcharts.Series#getProcessedData 985 * @param {boolean} [forceExtremesFromAll] 986 * Force getting extremes of a total series data range. 987 */ 988 getProcessedData(forceExtremesFromAll) { 989 const series = this, { dataTable: table, isCartesian, options, xAxis } = series, cropThreshold = options.cropThreshold, getExtremesFromAll = forceExtremesFromAll || 990 // X-range series etc, #21003 991 series.getExtremesFromAll, logarithmic = xAxis?.logarithmic, dataLength = table.rowCount; 992 let croppedData, cropped, cropStart = 0, xExtremes, min, max, xData = series.getColumn('x'), modified = table, updatingNames = false; 993 if (xAxis) { 994 // Corrected for log axis (#3053) 995 xExtremes = xAxis.getExtremes(); 996 min = xExtremes.min; 997 max = xExtremes.max; 998 updatingNames = !!(xAxis.categories && !xAxis.names.length); 999 // Optionally filter out points outside the plot area 1000 if (isCartesian && 1001 series.sorted && 1002 !getExtremesFromAll && 1003 (!cropThreshold || 1004 dataLength > cropThreshold || 1005 series.forceCrop)) { 1006 // It's outside current extremes 1007 if (xData[dataLength - 1] < min || 1008 xData[0] > max) { 1009 modified = new DataTableCore(); 1010 // Only crop if it's actually spilling out 1011 } 1012 else if ( 1013 // Don't understand why this condition is needed 1014 series.getColumn(series.pointValKey || 'y').length && (xData[0] < min || 1015 xData[dataLength - 1] > max)) { 1016 croppedData = this.cropData(table, min, max); 1017 modified = croppedData.modified; 1018 cropStart = croppedData.start; 1019 cropped = true; 1020 } 1021 } 1022 } 1023 // Find the closest distance between processed points 1024 xData = modified.getColumn('x') || []; 1025 const closestPointRange = getClosestDistance([ 1026 logarithmic ? 1027 xData.map(logarithmic.log2lin) : 1028 xData 1029 ], 1030 // Unsorted data is not supported by the line tooltip, as well as 1031 // data grouping and navigation in Stock charts (#725) and width 1032 // calculation of columns (#1900). Avoid warning during the 1033 // premature processing pass in updateNames (#16104). 1034 () => (series.requireSorting && 1035 !updatingNames && 1036 error(15, false, series.chart))); 1037 return { 1038 modified, 1039 cropped, 1040 cropStart, 1041 closestPointRange 1042 }; 1043 } 1044 /** 1045 * Internal function to apply processed data. 1046 * In Highcharts Stock, this function is extended to provide data grouping. 1047 * 1048 * @private 1049 * @function Highcharts.Series#processData 1050 * @param {boolean} [force] 1051 * Force data grouping. 1052 */ 1053 processData(force) { 1054 const series = this, xAxis = series.xAxis, table = series.dataTable; 1055 // If the series data or axes haven't changed, don't go through 1056 // this. Return false to pass the message on to override methods 1057 // like in data grouping. 1058 if (series.isCartesian && 1059 !series.isDirty && 1060 !xAxis.isDirty && 1061 !series.yAxis.isDirty && 1062 !force) { 1063 return false;
1064 } 1065 const processedData = series.getProcessedData(); 1066 // Record the properties 1067 table.modified = processedData.modified; 1068 series.cropped = processedData.cropped; // Undefined or true 1069 series.cropStart = processedData.cropStart; 1070 series.closestPointRange = (series.basePointRange = processedData.closestPointRange); 1071 fireEvent(series, 'afterProcessData'); 1072 } 1073 /** 1074 * Iterate over xData and crop values between min and max. Returns 1075 * object containing crop start/end cropped xData with corresponding 1076 * part of yData, dataMin and dataMax within the cropped range. 1077 * 1078 * @private 1079 * @function Highcharts.Series#cropData 1080 */ 1081 cropData(table, min, max) { 1082 const xData = table.getColumn('x', true) || [], dataLength = xData.length, columns = {}; 1083 let i, j, start = 0, end = dataLength; 1084 // Iterate up to find slice start 1085 for (i = 0; i < dataLength; i++) { 1086 if (xData[i] >= min) { 1087 start = Math.max(0, i - 1); 1088 break; 1089 } 1090 } 1091 // Proceed to find slice end 1092 for (j = i; j < dataLength; j++) { 1093 if (xData[j] > max) { 1094 end = j + 1; 1095 break; 1096 } 1097 } 1098 for (const key of this.dataColumnKeys()) { 1099 const column = table.getColumn(key, true); 1100 if (column) { 1101 columns[key] = column.slice(start, end); 1102 } 1103 } 1104 return { 1105 modified: new DataTableCore({ columns }), 1106 start, 1107 end 1108 }; 1109 } 1110 /** 1111 * Generate the data point after the data has been processed by cropping 1112 * away unused points and optionally grouped in Highcharts Stock. 1113 * 1114 * @private 1115 * @function Highcharts.Series#generatePoints 1116 */ 1117 generatePoints() { 1118 const series = this, options = series.options, dataOptions = series.processedData || options.data, table = series.dataTable.modified, xData = series.getColumn('x', true), PointClass = series.pointClass, processedDataLength = table.rowCount, cropStart = series.cropStart || 0, hasGroupedData = series.hasGroupedData, keys = options.keys, points = [], groupCropStartIndex = (options.dataGrouping && 1119 options.dataGrouping.groupAll ? 1120 cropStart : 1121 0), categories = series.xAxis?.categories, pointArrayMap = series.pointArrayMap || ['y'], 1122 // Create a configuration object out of a data row 1123 dataColumnKeys = this.dataColumnKeys(); 1124 let dataLength, cursor, point, i, data = series.data, pOptions; 1125 if (!data && !hasGroupedData) { 1126 const arr = []; 1127 arr.length = dataOptions?.length || 0; 1128 data = series.data = arr; 1129 } 1130 if (keys && hasGroupedData) { 1131 // Grouped data has already applied keys (#6590) 1132 series.options.keys = false; 1133 } 1134 for (i = 0; i < processedDataLength; i++) { 1135 cursor = cropStart + i; 1136 if (!hasGroupedData) { 1137 point = data[cursor]; 1138 pOptions = dataOptions ? 1139 dataOptions[cursor] : 1140 table.getRow(i, pointArrayMap); 1141 // #970: 1142 if (!point && 1143 pOptions !== void 0) { 1144 data[cursor] = point = new PointClass(series, pOptions, xData[i]); 1145 } 1146 } 1147 else { 1148 // Splat the y data in case of ohlc data array 1149 point = new PointClass(series, table.getRow(i, dataColumnKeys) || []); 1150 point.dataGroup = series.groupMap[groupCropStartIndex + i]; 1151 if (point.dataGroup?.options) { 1152 point.options = point.dataGroup.options; 1153 extend(point, point.dataGroup.options); 1154 // Collision of props and options (#9770) 1155 delete point.dataLabels; 1156 } 1157 } 1158 if (point) { // #6279 1159 /** 1160 * Contains the point's index in the `Series.points` array. 1161 * 1162 * @name Highcharts.Point#index 1163 * @type {number} 1164 * @readonly 1165 */ 1166 // For faster access in Point.update 1167 point.index = hasGroupedData ?
1168 (groupCropStartIndex + i) : cursor; 1169 points[i] = point; 1170 // Set point properties for convenient access in tooltip and 1171 // data labels 1172 point.category = categories?.[point.x] ?? point.x; 1173 point.key = point.name ?? point.category; 1174 } 1175 } 1176 // Restore keys options (#6590) 1177 series.options.keys = keys; 1178 // Hide cropped-away points - this only runs when the number of 1179 // points is above cropThreshold, or when switching view from 1180 // non-grouped data to grouped data (#637) 1181 if (data && 1182 (processedDataLength !== (dataLength = data.length) || 1183 hasGroupedData)) { 1184 for (i = 0; i < dataLength; i++) { 1185 // When has grouped data, clear all points 1186 if (i === cropStart && !hasGroupedData) { 1187 i += processedDataLength; 1188 } 1189 if (data[i]) { 1190 data[i].destroyElements(); 1191 data[i].plotX = void 0; // #1003 1192 } 1193 } 1194 } 1195 /** 1196 * Read only. An array containing those values converted to points. 1197 * In case the series data length exceeds the `cropThreshold`, or if 1198 * the data is grouped, `series.data` doesn't contain all the 1199 * points. Also, in case a series is hidden, the `data` array may be 1200 * empty. In case of cropping, the `data` array may contain `undefined` 1201 * values, instead of points. To access raw values, 1202 * `series.options.data` will always be up to date. `Series.data` only 1203 * contains the points that have been created on demand. To modify the 1204 * data, use 1205 * {@link Highcharts.Series#setData} or 1206 * {@link Highcharts.Point#update}. 1207 * 1208 * @see Series.points 1209 * 1210 * @name Highcharts.Series#data 1211 * @type {Array<Highcharts.Point>} 1212 */ 1213 series.data = data; 1214 /** 1215 * An array containing all currently visible point objects. In case 1216 * of cropping, the cropped-away points are not part of this array. 1217 * The `series.points` array starts at `series.cropStart` compared 1218 * to `series.data` and `series.options.data`. If however the series 1219 * data is grouped, these can't be correlated one to one. To modify 1220 * the data, use {@link Highcharts.Series#setData} or 1221 * {@link Highcharts.Point#update}. 1222 * 1223 * @name Highcharts.Series#points 1224 * @type {Array<Highcharts.Point>} 1225 */ 1226 series.points = points; 1227 fireEvent(this, 'afterGeneratePoints'); 1228 } 1229 /** 1230 * Get current X extremes for the visible data. 1231 * 1232 * @private 1233 * @function Highcharts.Series#getXExtremes 1234 * @param {Array<number>} xData 1235 * The data to inspect. Defaults to the current data within the visible 1236 * range. 1237 */ 1238 getXExtremes(xData) { 1239 return { 1240 min: arrayMin(xData), 1241 max: arrayMax(xData) 1242 }; 1243 } 1244 /** 1245 * Calculate Y extremes for the visible data. The result is returned 1246 * as an object with `dataMin` and `dataMax` properties. 1247 * 1248 * @private 1249 * @function Highcharts.Series#getExtremes 1250 * @param {Array<number>} [yData] 1251 * The data to inspect. Defaults to the current data within the visible 1252 * range. 1253 * @param {boolean} [forceExtremesFromAll] 1254 * Force getting extremes of a total series data range. 1255 */ 1256 getExtremes(yData, forceExtremesFromAll) { 1257 const { xAxis, yAxis } = this, getExtremesFromAll = forceExtremesFromAll || 1258 this.getExtremesFromAll || 1259 this.options.getExtremesFromAll, // #4599, #21003 1260 table = getExtremesFromAll && this.cropped ? 1261 this.dataTable : 1262 this.dataTable.modified, rowCount = table.rowCount, customData = yData || this.stackedYData, yAxisData = customData ? 1263 [customData] : 1264 (this.keysAffectYAxis || this.pointArrayMap || ['y'])?.map((key) => table.getColumn(key, true) || []) || [], xData = this.getColumn('x', true), activeYData = [], 1265 // Handle X outside the viewed area. This does not work with 1266 // non-sorted data like scatter (#7639). 1267 shoulder = this.requireSorting && !this.is('column') ? 1268 1 : 0, 1269 // #2117, need to compensate for log X axis 1270 positiveValuesOnly = yAxis ? yAxis.positiveValuesOnly : false, doAll = getExtremesFromAll || 1271 this.cropped || 1272 !xAxis;
vendor: 4,662 bytes, lines 1272-1387
1272 // For colorAxis support 1273 let xExtremes, x, i, xMin = 0, xMax = 0; 1274 if (xAxis) { 1275 xExtremes = xAxis.getExtremes(); 1276 xMin = xExtremes.min; 1277 xMax = xExtremes.max; 1278 } 1279 for (i = 0; i < rowCount; i++) { 1280 x = xData[i]; 1281 // Check if it is within the selected x axis range 1282 if (doAll || 1283 ((xData[i + shoulder] || x) >= xMin && 1284 (xData[i - shoulder] || x) <= xMax)) { 1285 for (const values of yAxisData) { 1286 const val = values[i]; 1287 // For points within the visible range, including the 1288 // first point outside the visible range (#7061), 1289 // consider y extremes. 1290 if (isNumber(val) && 1291 (val > 0 || !positiveValuesOnly)) { 1292 activeYData.push(val); 1293 } 1294 } 1295 } 1296 } 1297 const dataExtremes = { 1298 activeYData, // Needed for Stock Cumulative Sum 1299 dataMin: arrayMin(activeYData), 1300 dataMax: arrayMax(activeYData) 1301 }; 1302 fireEvent(this, 'afterGetExtremes', { dataExtremes }); 1303 return dataExtremes; 1304 } 1305 /** 1306 * Set the current data extremes as `dataMin` and `dataMax` on the 1307 * Series item. Use this only when the series properties should be 1308 * updated. 1309 * 1310 * @private 1311 * @function Highcharts.Series#applyExtremes 1312 */ 1313 applyExtremes() { 1314 const dataExtremes = this.getExtremes(); 1315 /** 1316 * Contains the minimum value of the series' data point. Some series 1317 * types like `networkgraph` do not support this property as they 1318 * lack a `y`-value. 1319 * @name Highcharts.Series#dataMin 1320 * @type {number|undefined} 1321 * @readonly 1322 */ 1323 this.dataMin = dataExtremes.dataMin; 1324 /** 1325 * Contains the maximum value of the series' data point. Some series 1326 * types like `networkgraph` do not support this property as they 1327 * lack a `y`-value. 1328 * @name Highcharts.Series#dataMax 1329 * @type {number|undefined} 1330 * @readonly 1331 */ 1332 this.dataMax = dataExtremes.dataMax; 1333 return dataExtremes; 1334 } 1335 /** 1336 * Find and return the first non nullish point in the data 1337 * 1338 * @private 1339 * @function Highcharts.Series.getFirstValidPoint 1340 * @param {Array<Highcharts.PointOptionsType>} data 1341 * Array of options for points 1342 * @param {number} [start=0] 1343 * Index to start searching from 1344 * @param {number} [increment=1] 1345 * Index increment, set -1 to search backwards 1346 */ 1347 getFirstValidPoint(data, start = 0, increment = 1) { 1348 const dataLength = data.length; 1349 let i = start; 1350 while (i >= 0 && i < dataLength) { 1351 if (defined(data[i])) { 1352 return data[i]; 1353 } 1354 i += increment; 1355 } 1356 } 1357 /** 1358 * Translate data points from raw data values to chart specific 1359 * positioning data needed later in the `drawPoints` and `drawGraph` 1360 * functions. This function can be overridden in plugins and custom 1361 * series type implementations. 1362 * 1363 * @function Highcharts.Series#translate 1364 * 1365 * @emits Highcharts.Series#events:translate 1366 */ 1367 translate() { 1368 this.generatePoints(); 1369 const series = this, options = series.options, stacking = options.stacking, xAxis = series.xAxis, enabledDataSorting = series.enabledDataSorting, yAxis = series.yAxis, points = series.points, dataLength = points.length, pointPlacement = series.pointPlacementToXValue(), // #7860 1370 dynamicallyPlaced = Boolean(pointPlacement), threshold = options.threshold, stackThreshold = options.startFromThreshold ? threshold : 0; 1371 let i, plotX, lastPlotX, stackIndicator, closestPointRangePx = Number.MAX_VALUE; 1372 /** 1373 * Plotted coordinates need to be within a limited range. Drawing 1374 * too far outside the viewport causes various rendering issues 1375 * (#3201, #3923, #7555). 1376 * @private 1377 */ 1378 function limitedRange(val) { 1379 return clamp(val, -1e9, 1e9); 1380 } 1381 // Translate each point 1382 for (i = 0; i < dataLength; i++) { 1383 const point = points[i], xValue = point.x; 1384 let stackItem, stackValues, yValue = point.y, lowValue = point.low; 1385 const stacks = stacking && yAxis.stacking?.stacks[(series.negStacks && 1386 yValue < 1387 (stackThreshold ? 0 : threshold) ?
1388 '-' : 1389 '') + series.stackKey]; 1390 plotX = xAxis.translate(// #3923 1391 xValue, false, false, false, true, pointPlacement); 1392 /** 1393 * The translated X value for the point in terms of pixels. Relative 1394 * to the X axis position if the series has one, otherwise relative 1395 * to the plot area. Depending on the series type this value might 1396 * not be defined. 1397 * 1398 * In an inverted chart the x-axis is going from the bottom to the 1399 * top so the `plotX` value is the number of pixels from the bottom 1400 * of the axis. 1401 * 1402 * @see Highcharts.Point#pos 1403 * @name Highcharts.Point#plotX 1404 * @type {number|undefined} 1405 */ 1406 point.plotX = isNumber(plotX) ? correctFloat(// #5236 1407 limitedRange(plotX) // #3923 1408 ) : void 0; 1409 // Calculate the bottom y value for stacked series 1410 if (stacking && 1411 series.visible && 1412 stacks && 1413 stacks[xValue]) { 1414 stackIndicator = series.getStackIndicator(stackIndicator, xValue, series.index); 1415 if (!point.isNull && stackIndicator.key) { 1416 stackItem = stacks[xValue]; 1417 stackValues = stackItem.points[stackIndicator.key]; 1418 } 1419 if (stackItem && isArray(stackValues)) { 1420 lowValue = stackValues[0]; 1421 yValue = stackValues[1]; 1422 if (lowValue === stackThreshold && 1423 stackIndicator.key === stacks[xValue].base) { 1424 lowValue = pick(isNumber(threshold) ? threshold : yAxis.min); 1425 } 1426 // #1200, #1232 1427 if (yAxis.positiveValuesOnly && 1428 defined(lowValue) && 1429 lowValue <= 0) { 1430 lowValue = void 0; 1431 } 1432 point.total = point.stackTotal = pick(stackItem.total); 1433 point.percentage = defined(point.y) && stackItem.total ? 1434 (point.y / stackItem.total * 100) : void 0; 1435 point.stackY = yValue; 1436 // In case of variwide series (where widths of points are 1437 // different in most cases), stack labels are positioned 1438 // wrongly, so the call of the setOffset is omitted here and 1439 // labels are correctly positioned later, at the end of the 1440 // variwide's translate function (#10962) 1441 if (!series.irregularWidths) { 1442 stackItem.setOffset(series.pointXOffset || 0, series.barW || 0, void 0, void 0, void 0, series.xAxis); 1443 } 1444 } 1445 } 1446 // Set translated yBottom or remove it 1447 point.yBottom = defined(lowValue) ? 1448 limitedRange(yAxis.translate(lowValue, false, true, false, true)) : 1449 void 0; 1450 // General hook, used for Highcharts Stock compare and cumulative 1451 if (series.dataModify) { 1452 yValue = series.dataModify.modifyValue(yValue, i); 1453 } 1454 // Set the plotY value, reset it for redraws #3201, #18422 1455 let plotY; 1456 if (isNumber(yValue) && point.plotX !== void 0) { 1457 plotY = yAxis.translate(yValue, false, true, false, true); 1458 plotY = isNumber(plotY) ? limitedRange(plotY) : void 0; 1459 } 1460 /** 1461 * The translated Y value for the point in terms of pixels. Relative 1462 * to the Y axis position if the series has one, otherwise relative 1463 * to the plot area. Depending on the series type this value might 1464 * not be defined. 1465 * 1466 * In an inverted chart the y-axis is going from right to left 1467 * so the `plotY` value is the number of pixels from the right 1468 * of the `yAxis`. 1469 * 1470 * @see Highcharts.Point#pos 1471 * @name Highcharts.Point#plotY 1472 * @type {number|undefined} 1473 */ 1474 point.plotY = plotY; 1475 point.isInside = this.isPointInside(point); 1476 // Set client related positions for mouse tracking 1477 point.clientX = dynamicallyPlaced ? 1478 correctFloat(xAxis.translate(xValue, false, false, false, true, pointPlacement)) : 1479 plotX; // #1514, #5383, #5518 1480 // Negative points #19028 1481 point.negative = (point.y || 0) < (threshold || 0); 1482 // Determine auto enabling of markers (#3635, #5099) 1483 if (!point.isNull && point.visible !== false) { 1484 if (typeof lastPlotX !== 'undefined') { 1485 closestPointRangePx = Math.min(closestPointRangePx, Math.abs(plotX - lastPlotX)); 1486 } 1487 lastPlotX = plotX; 1488 } 1489 // Find point zone 1490 point.zone = this.zones.length ? point.getZone() : void 0; 1491 // Animate new points with data sorting 1492 if (!point.graphic && series.group && enabledDataSorting) { 1493 point.isNew = true; 1494 } 1495 } 1496 series.closestPointRangePx = closestPointRangePx; 1497 fireEvent(this, 'afterTranslate'); 1498 } 1499 /** 1500 * Return the series points with null points filtered out. 1501 * 1502 * @function Highcharts.Series#getValidPoints 1503 * 1504 * @param {Array<Highcharts.Point>} [points] 1505 * The points to inspect, defaults to {@link Series.points}. 1506 * 1507 * @param {boolean} [insideOnly=false] 1508 * Whether to inspect only the points that are inside the visible view. 1509 * 1510 * @param {boolean} [allowNull=false] 1511 * Whether to allow null points to pass as valid points. 1512 * 1513 * @return {Array<Highcharts.Point>} 1514 * The valid points. 1515 */ 1516 getValidPoints(points, insideOnly, allowNull) { 1517 const chart = this.chart; 1518 // #3916, #5029, #5085 1519 return (points || this.points || []).filter(function (point) { 1520 const { plotX, plotY } = point, 1521 // Undefined plotY is treated as null when negative values 1522 // in log axis (#18422) 1523 asNull = !allowNull && (point.isNull || !isNumber(plotY)); 1524 if (asNull || (insideOnly && !chart.isInsidePlot(plotX, plotY, { inverted: chart.inverted }))) { 1525 return false;
1526 } 1527 return point.visible !== false; 1528 }); 1529 } 1530 /** 1531 * Get the clipping for the series. Could be called for a series to 1532 * initiate animating the clip or to set the final clip (only width 1533 * and x). 1534 * 1535 * @private 1536 * @function Highcharts.Series#getClip 1537 */ 1538 getClipBox() { 1539 const { chart, xAxis, yAxis } = this; 1540 // If no axes on the series, use global clipBox 1541 let { x, y, width, height } = merge(chart.clipBox); 1542 // Otherwise, use clipBox.width which is corrected for plotBorderWidth 1543 // and clipOffset 1544 if (xAxis && xAxis.len !== chart.plotSizeX) { 1545 width = xAxis.len; 1546 } 1547 if (yAxis && yAxis.len !== chart.plotSizeY) { 1548 height = yAxis.len; 1549 } 1550 // If the chart is inverted and the series is not invertible, the chart 1551 // clip box should be inverted, but not the series clip box (#20264) 1552 if (chart.inverted && !this.invertible) { 1553 [width, height] = [height, width]; 1554 } 1555 return { x, y, width, height }; 1556 } 1557 /** 1558 * Get the shared clip key, creating it if it doesn't exist. 1559 * 1560 * @private 1561 * @function Highcharts.Series#getSharedClipKey 1562 */ 1563 getSharedClipKey() { 1564 this.sharedClipKey = (this.options.xAxis || 0) + ',' + 1565 (this.options.yAxis || 0); 1566 return this.sharedClipKey; 1567 } 1568 /** 1569 * Set the clipping for the series. For animated series the clip is later 1570 * modified. 1571 * 1572 * @private 1573 * @function Highcharts.Series#setClip 1574 */ 1575 setClip() { 1576 const { chart, group, markerGroup } = this, sharedClips = chart.sharedClips, renderer = chart.renderer, clipBox = this.getClipBox(), sharedClipKey = this.getSharedClipKey(); // #4526
1577 let clipRect = sharedClips[sharedClipKey]; 1578 // If a clipping rectangle for the same set of axes does not exist, 1579 // create it 1580 if (!clipRect) { 1581 sharedClips[sharedClipKey] = clipRect = renderer.clipRect(clipBox); 1582 // When setting chart size, or when the series is rendered again before 1583 // starting animating, in compliance to a responsive rule 1584 } 1585 else { 1586 clipRect.animate(clipBox); 1587 } 1588 if (group) { 1589 // When clip is false, reset to no clip after animation 1590 group.clip(this.options.clip === false ? void 0 : clipRect); 1591 } 1592 // Unclip temporary animation clip 1593 if (markerGroup) { 1594 markerGroup.clip(); 1595 } 1596 } 1597 /** 1598 * Animate in the series. Called internally twice. First with the `init` 1599 * parameter set to true, which sets up the initial state of the 1600 * animation. Then when ready, it is called with the `init` parameter 1601 * undefined, in order to perform the actual animation. 1602 * 1603 * @function Highcharts.Series#animate 1604 * 1605 * @param {boolean} [init] 1606 * Initialize the animation. 1607 */ 1608 animate(init) { 1609 const { chart, group, markerGroup } = this, inverted = chart.inverted, animation = animObject(this.options.animation), 1610 // The key for temporary animation clips 1611 animationClipKey = [
1612 this.getSharedClipKey(), 1613 animation.duration, 1614 animation.easing, 1615 animation.defer 1616 ].join(','); 1617 let animationClipRect = chart.sharedClips[animationClipKey], markerAnimationClipRect = chart.sharedClips[animationClipKey + 'm']; 1618 // Initialize the animation. Set up the clipping rectangle. 1619 if (init && group) { 1620 const clipBox = this.getClipBox(); 1621 // Create temporary animation clips 1622 if (!animationClipRect) { 1623 clipBox.width = 0; 1624 if (inverted) { 1625 clipBox.x = chart.plotHeight; 1626 } 1627 animationClipRect = chart.renderer.clipRect(clipBox); 1628 chart.sharedClips[animationClipKey] = animationClipRect; 1629 // The marker clip box. The number 99 is a safe margin to avoid 1630 // markers being clipped during animation. 1631 const markerClipBox = { 1632 x: inverted ? -99 : -99, 1633 y: inverted ? -99 : -99, 1634 width: inverted ? chart.plotWidth + 199 : 99, 1635 height: inverted ? 99 : chart.plotHeight + 199 1636 }; 1637 markerAnimationClipRect = chart.renderer.clipRect(markerClipBox); 1638 chart.sharedClips[animationClipKey + 'm'] = markerAnimationClipRect; 1639 } 1640 else { 1641 // When height changes during animation, typically due to 1642 // responsive settings 1643 animationClipRect.attr('height', clipBox.height); 1644 } 1645 group.clip(animationClipRect); 1646 markerGroup?.clip(markerAnimationClipRect); 1647 // Run the animation 1648 } 1649 else if (animationClipRect && 1650 // Only first series in this pane 1651 !animationClipRect.hasClass('highcharts-animating')) { 1652 const finalBox = this.getClipBox(), step = animation.step; 1653 // Only do this when there are actually markers, or we have multiple 1654 // series (#20473) 1655 if (markerGroup?.element.childNodes.length || 1656 chart.series.length > 1) { 1657 // To provide as smooth animation as possible, update the marker 1658 // group clipping in steps of the main group animation 1659 animation.step = function (val, fx) { 1660 if (step) { 1661 step.apply(fx, arguments); 1662 } 1663 if (fx.prop === 'width' && 1664 markerAnimationClipRect?.element) { 1665 markerAnimationClipRect.attr(inverted ? 'height' : 'width', val + 99); 1666 } 1667 }; 1668 } 1669 animationClipRect 1670 .addClass('highcharts-animating') 1671 .animate(finalBox, animation); 1672 } 1673 } 1674 /** 1675 * This runs after animation to land on the final plot clipping. 1676 * 1677 * @private 1678 * @function Highcharts.Series#afterAnimate 1679 * 1680 * @emits Highcharts.Series#event:afterAnimate 1681 */ 1682 afterAnimate() { 1683 this.setClip(); 1684 // Destroy temporary clip rectangles that are no longer in use 1685 objectEach(this.chart.sharedClips, (clip, key, sharedClips) => { 1686 if (clip && !this.chart.container.querySelector(`[clip-path="url(#${clip.id})"]`)) { 1687 clip.destroy(); 1688 delete sharedClips[key]; 1689 } 1690 }); 1691 this.finishedAnimating = true; 1692 fireEvent(this, 'afterAnimate'); 1693 } 1694 /** 1695 * Draw the markers for line-like series types, and columns or other 1696 * graphical representation for {@link Point} objects for other series 1697 * types. The resulting element is typically stored as 1698 * {@link Point.graphic}, and is created on the first call and updated 1699 * and moved on subsequent calls. 1700 * 1701 * @function Highcharts.Series#drawPoints 1702 */ 1703 drawPoints(points = this.points) { 1704 const series = this, chart = series.chart, styledMode = chart.styledMode, { colorAxis, options } = series, seriesMarkerOptions = options.marker, markerGroup = series[series.specialGroup || 'markerGroup'], xAxis = series.xAxis, globallyEnabled = pick(seriesMarkerOptions.enabled, !xAxis || xAxis.isRadial ? true : null, 1705 // Use larger or equal as radius is null in bubbles (#6321) 1706 series.closestPointRangePx >= (seriesMarkerOptions.enabledThreshold * 1707 seriesMarkerOptions.radius)); 1708 let i, point, graphic, verb, pointMarkerOptions, hasPointMarker, markerAttribs; 1709 if (seriesMarkerOptions.enabled !== false || 1710 series._hasPointMarkers) { 1711 for (i = 0; i < points.length; i++) { 1712 point = points[i]; 1713 graphic = point.graphic; 1714 verb = graphic ? 'animate' : 'attr'; 1715 pointMarkerOptions = point.marker || {}; 1716 hasPointMarker = !!point.marker; 1717 const shouldDrawMarker = ((globallyEnabled && 1718 typeof pointMarkerOptions.enabled === 'undefined') || pointMarkerOptions.enabled) && !point.isNull && point.visible !== false; 1719 // Only draw the point if y is defined 1720 if (shouldDrawMarker) { 1721 // Shortcuts 1722 const symbol = pick(pointMarkerOptions.symbol, series.symbol, 'rect'); 1723 markerAttribs = series.markerAttribs(point, (point.selected && 'select')); 1724 // Set starting position for point sliding animation. 1725 if (series.enabledDataSorting) { 1726 point.startXPos = xAxis.reversed ? 1727 -(markerAttribs.width || 0) : 1728 xAxis.width; 1729 } 1730 const isInside = point.isInside !== false; 1731 if (!graphic && 1732 isInside && 1733 ((markerAttribs.width || 0) > 0 || point.hasImage)) { 1734 /** 1735 * SVG graphic representing the point in the chart. In 1736 * some cases it may be a hidden graphic to improve 1737 * accessibility. 1738 * 1739 * Typically this is a simple shape, like a `rect` 1740 * for column charts or `path` for line markers, but 1741 * for some complex series types like boxplot or 3D 1742 * charts, the graphic may be a `g` element 1743 * containing other shapes. The graphic is generated 1744 * the first time {@link Series#drawPoints} runs, 1745 * and updated and moved on subsequent runs. 1746 * 1747 * @see Highcharts.Point#graphics 1748 * 1749 * @name Highcharts.Point#graphic 1750 * @type {Highcharts.SVGElement|undefined} 1751 */ 1752 point.graphic = graphic = chart.renderer 1753 .symbol(symbol, markerAttribs.x, markerAttribs.y, markerAttribs.width, markerAttribs.height, hasPointMarker ? 1754 pointMarkerOptions : 1755 seriesMarkerOptions) 1756 .add(markerGroup); 1757 // Sliding animation for new points 1758 if (series.enabledDataSorting && 1759 chart.hasRendered) { 1760 graphic.attr({ 1761 x: point.startXPos 1762 }); 1763 verb = 'animate'; 1764 } 1765 } 1766 if (graphic && verb === 'animate') { // Update 1767 // Since the marker group isn't clipped, each 1768 // individual marker must be toggled 1769 graphic[isInside ? 'show' : 'hide'](isInside) 1770 .animate(markerAttribs); 1771 } 1772 // Presentational attributes 1773 if (graphic) { 1774 const pointAttr = series.pointAttribs(point, ((styledMode || !point.selected) ? 1775 void 0 : 1776 'select')); 1777 if (!styledMode) { 1778 graphic[verb](pointAttr); 1779 } 1780 else if (colorAxis) { // #14114 1781 graphic['css']({ 1782 fill: pointAttr.fill 1783 }); 1784 } 1785 } 1786 if (graphic) { 1787 graphic.addClass(point.getClassName(), true); 1788 } 1789 } 1790 else if (graphic) { 1791 point.graphic = graphic.destroy(); // #1269 1792 } 1793 } 1794 } 1795 } 1796 /** 1797 * Get non-presentational attributes for a point. Used internally for 1798 * both styled mode and classic. Can be overridden for different series 1799 * types. 1800 * 1801 * @see Series#pointAttribs 1802 * 1803 * @function Highcharts.Series#markerAttribs 1804 * 1805 * @param {Highcharts.Point} point 1806 * The Point to inspect. 1807 * 1808 * @param {string} [state] 1809 * The state, can be either `hover`, `select` or undefined. 1810 * 1811 * @return {Highcharts.SVGAttributes} 1812 * A hash containing those attributes that are not settable from CSS. 1813 */ 1814 markerAttribs(point, state) { 1815 const seriesOptions = this.options, seriesMarkerOptions = seriesOptions.marker, pointMarkerOptions = point.marker || {}, symbol = (pointMarkerOptions.symbol || 1816 seriesMarkerOptions.symbol), attribs = {}; 1817 let seriesStateOptions, pointStateOptions, radius = pick(pointMarkerOptions.radius, seriesMarkerOptions && seriesMarkerOptions.radius); 1818 // Handle hover and select states 1819 if (state) { 1820 seriesStateOptions = seriesMarkerOptions.states[state]; 1821 pointStateOptions = pointMarkerOptions.states && 1822 pointMarkerOptions.states[state]; 1823 radius = pick(pointStateOptions && pointStateOptions.radius, seriesStateOptions && seriesStateOptions.radius, radius && radius + (seriesStateOptions && seriesStateOptions.radiusPlus || 1824 0)); 1825 } 1826 point.hasImage = symbol && symbol.indexOf('url') === 0; 1827 if (point.hasImage) { 1828 radius = 0; // And subsequently width and height is not set 1829 } 1830 const pos = point.pos(); 1831 if (isNumber(radius) && pos) { 1832 if (seriesOptions.crisp) { 1833 pos[0] = crisp(pos[0], point.hasImage ? 1834 0 : 1835 symbol === 'rect' ? 1836 // Rectangle symbols need crisp edges, others don't 1837 seriesMarkerOptions?.lineWidth || 0 : 1838 1); 1839 }
1840 attribs.x = pos[0] - radius; 1841 attribs.y = pos[1] - radius; 1842 } 1843 if (radius) { 1844 attribs.width = attribs.height = 2 * radius; 1845 } 1846 return attribs; 1847 } 1848 /** 1849 * Internal function to get presentational attributes for each point. 1850 * Unlike {@link Series#markerAttribs}, this function should return 1851 * those attributes that can also be set in CSS. In styled mode, 1852 * `pointAttribs` won't be called. 1853 * 1854 * @private 1855 * @function Highcharts.Series#pointAttribs 1856 * 1857 * @param {Highcharts.Point} [point] 1858 * The point instance to inspect. 1859 * 1860 * @param {string} [state] 1861 * The point state, can be either `hover`, `select` or 'normal'. If 1862 * undefined, normal state is assumed. 1863 * 1864 * @return {Highcharts.SVGAttributes} 1865 * The presentational attributes to be set on the point. 1866 */ 1867 pointAttribs(point, state) { 1868 const seriesMarkerOptions = this.options.marker, pointOptions = point && point.options, pointMarkerOptions = ((pointOptions && pointOptions.marker) || {}), pointColorOption = pointOptions && pointOptions.color, pointColor = point && point.color, zoneColor = point && point.zone && point.zone.color; 1869 let seriesStateOptions, pointStateOptions, color = this.color, fill, stroke, strokeWidth = pick(pointMarkerOptions.lineWidth, seriesMarkerOptions.lineWidth), opacity = 1; 1870 color = (pointColorOption || 1871 zoneColor || 1872 pointColor || 1873 color); 1874 fill = (pointMarkerOptions.fillColor || 1875 seriesMarkerOptions.fillColor || 1876 color); 1877 stroke = (pointMarkerOptions.lineColor || 1878 seriesMarkerOptions.lineColor || 1879 color); 1880 // Handle hover and select states 1881 state = state || 'normal'; 1882 if (state) { 1883 seriesStateOptions = (seriesMarkerOptions.states[state] || {}); 1884 pointStateOptions = (pointMarkerOptions.states && 1885 pointMarkerOptions.states[state]) || {}; 1886 strokeWidth = pick(pointStateOptions.lineWidth, seriesStateOptions.lineWidth, strokeWidth + pick(pointStateOptions.lineWidthPlus, seriesStateOptions.lineWidthPlus, 0)); 1887 fill = (pointStateOptions.fillColor || 1888 seriesStateOptions.fillColor || 1889 fill); 1890 stroke = (pointStateOptions.lineColor || 1891 seriesStateOptions.lineColor || 1892 stroke); 1893 opacity = pick(pointStateOptions.opacity, seriesStateOptions.opacity, opacity); 1894 } 1895 return { 1896 'stroke': stroke, 1897 'stroke-width': strokeWidth, 1898 'fill': fill, 1899 'opacity': opacity 1900 }; 1901 } 1902 /** 1903 * Clear DOM objects and free up memory. 1904 * 1905 * @private 1906 * @function Highcharts.Series#destroy 1907 * 1908 * @emits Highcharts.Series#event:destroy 1909 */ 1910 destroy(keepEventsForUpdate) { 1911 const series = this, chart = series.chart, issue134 = /AppleWebKit\/533/.test(win.navigator.userAgent), data = series.data || []; 1912 let destroy, i, point, axis; 1913 // Add event hook 1914 fireEvent(series, 'destroy', { keepEventsForUpdate }); 1915 // Remove events 1916 this.removeEvents(keepEventsForUpdate); 1917 // Erase from axes 1918 (series.axisTypes || []).forEach(function (AXIS) { 1919 axis = series[AXIS]; 1920 if (axis && axis.series) { 1921 erase(axis.series, series); 1922 axis.isDirty = axis.forceRedraw = true; 1923 } 1924 }); 1925 // Remove legend items 1926 if (series.legendItem) { 1927 series.chart.legend.destroyItem(series); 1928 } 1929 // Destroy all points with their elements 1930 i = data.length; 1931 while (i--) { 1932 point = data[i]; 1933 if (point && point.destroy) { 1934 point.destroy(); 1935 } 1936 } 1937 for (const zone of series.zones) { 1938 // Destroy SVGElement's but preserve primitive props (#20426) 1939 destroyObjectProperties(zone, void 0, true); 1940 } 1941 // Clear the animation timeout if we are destroying the series 1942 // during initial animation 1943 U.clearTimeout(series.animationTimeout); 1944 // Destroy all SVGElements associated to the series 1945 objectEach(series, function (val, prop) { 1946 // Survive provides a hook for not destroying 1947 if (val instanceof SVGElement && !val.survive) { 1948 // Issue 134 workaround 1949 destroy = issue134 && prop === 'group' ? 1950 'hide' : 1951 'destroy'; 1952 val[destroy](); 1953 } 1954 }); 1955 // Remove from hoverSeries 1956 if (chart.hoverSeries === series) { 1957 chart.hoverSeries = void 0; 1958 } 1959 erase(chart.series, series); 1960 chart.orderItems('series'); 1961 // Clear all members 1962 objectEach(series, function (val, prop) { 1963 if (!keepEventsForUpdate || prop !== 'hcEvents') { 1964 delete series[prop]; 1965 } 1966 }); 1967 } 1968 /** 1969 * Clip the graphs into zones for colors and styling. 1970 * 1971 * @private 1972 * @function Highcharts.Series#applyZones 1973 */ 1974 applyZones() { 1975 const series = this, { area, chart, graph, zones, points, xAxis, yAxis, zoneAxis } = series, { inverted, renderer } = chart, axis = this[`${zoneAxis}Axis`], { isXAxis, len = 0, minPointOffset = 0 } = axis || {}, halfWidth = (graph?.strokeWidth() || 0) / 2 + 1, 1976 // Avoid points that are so close to the threshold that the graph 1977 // line would be split 1978 avoidClose = (zone, plotX = 0, plotY = 0) => { 1979 if (inverted) { 1980 plotY = len - plotY; 1981 } 1982 const { translated = 0, lineClip } = zone, distance = plotY - translated; 1983 lineClip?.push([ 1984 'L', 1985 plotX, 1986 Math.abs(distance) < halfWidth ? 1987 plotY - halfWidth * (distance <= 0 ? -1 : 1) : 1988 translated 1989 ]); 1990 }; 1991 if (zones.length && 1992 (graph || area) && 1993 axis && 1994 isNumber(axis.min)) { 1995 const axisMax = axis.getExtremes().max + minPointOffset, 1996 // Invert the x and y coordinates of inverted charts 1997 invertPath = (path) => { 1998 path.forEach((segment, i) => { 1999 if (segment[0] === 'M' || segment[0] === 'L') { 2000 path[i] = [ 2001 segment[0], 2002 isXAxis ? len - segment[1] : segment[1], 2003 isXAxis ? segment[2] : len - segment[2] 2004 ]; 2005 } 2006 }); 2007 }; 2008 // Reset
vendor: 10,385 bytes, lines 2009-2280
2009 zones.forEach((zone) => { 2010 zone.lineClip = []; 2011 zone.translated = clamp(axis.toPixels(pick(zone.value, axisMax), true) || 0, 0, len); 2012 }); 2013 // The use of the Color Threshold assumes there are no gaps so it is 2014 // safe to hide the original graph and area unless it is not 2015 // waterfall series, then use showLine property to set lines between 2016 // columns to be visible (#7862) 2017 if (graph && !this.showLine) { 2018 graph.hide(); 2019 } 2020 if (area) { 2021 area.hide(); 2022 } 2023 // Prepare for adaptive clips, avoiding segments close to the 2024 // threshold (#19709) 2025 if (zoneAxis === 'y' && 2026 // Overheat protection 2027 points.length < xAxis.len) { 2028 for (const point of points) { 2029 const { plotX, plotY, zone } = point, zoneBelow = zone && zones[zones.indexOf(zone) - 1]; 2030 // Close to upper boundary 2031 if (zone) { 2032 avoidClose(zone, plotX, plotY); 2033 } 2034 // Close to lower boundary 2035 if (zoneBelow) { 2036 avoidClose(zoneBelow, plotX, plotY); 2037 } 2038 } 2039 } 2040 // Compute and apply the clips 2041 let lastLineClip = [], 2042 // Starting point of the first zone. Offset for category axis 2043 // (#22188). 2044 lastTranslated = axis.toPixels(axis.getExtremes().min - minPointOffset, true); 2045 zones.forEach((zone) => { 2046 const lineClip = zone.lineClip || [], translated = Math.round(zone.translated || 0); 2047 if (xAxis.reversed) { 2048 lineClip.reverse(); 2049 } 2050 let { clip, simpleClip } = zone, x1 = 0, y1 = 0, x2 = xAxis.len, y2 = yAxis.len; 2051 if (isXAxis) { 2052 x1 = translated; 2053 x2 = lastTranslated; 2054 } 2055 else { 2056 y1 = translated; 2057 y2 = lastTranslated; 2058 } 2059 // Adaptive clips 2060 const simplePath = [ 2061 ['M', x1, y1], 2062 ['L', x2, y1], 2063 ['L', x2, y2], 2064 ['L', x1, y2], 2065 ['Z'] 2066 ], adaptivePath = [ 2067 simplePath[0], 2068 ...lineClip, 2069 simplePath[1], 2070 simplePath[2], 2071 ...lastLineClip, 2072 simplePath[3], 2073 simplePath[4] 2074 ]; 2075 lastLineClip = lineClip.reverse(); 2076 lastTranslated = translated; 2077 if (inverted) { 2078 invertPath(adaptivePath); 2079 if (area) { 2080 invertPath(simplePath); 2081 } 2082 } 2083 /* Debug clip paths 2084 zone.path?.destroy(); 2085 zone.path = chart.renderer.path(adaptivePath) 2086 .attr({ 2087 stroke: zone.color || this.color || 'gray', 2088 'stroke-width': 1, 2089 'dashstyle': 'Dash' 2090 }) 2091 .add(series.group); 2092 // */ 2093 if (clip) { 2094 clip.animate({ d: adaptivePath }); 2095 simpleClip?.animate({ d: simplePath }); 2096 } 2097 else { 2098 clip = zone.clip = renderer.path(adaptivePath); 2099 if (area) { 2100 simpleClip = zone.simpleClip = renderer.path(simplePath); 2101 } 2102 } 2103 // When no data, graph zone is not applied and after setData 2104 // clip was ignored. As a result, it should be applied each 2105 // time. 2106 if (graph) { 2107 zone.graph?.clip(clip); 2108 } 2109 if (area) { 2110 zone.area?.clip(simpleClip); 2111 } 2112 }); 2113 } 2114 else if (series.visible) { 2115 // If zones were removed, restore graph and area 2116 if (graph) { 2117 graph.show(); 2118 } 2119 if (area) { 2120 area.show(); 2121 } 2122 } 2123 } 2124 /** 2125 * General abstraction for creating plot groups like series.group, 2126 * series.dataLabelsGroup and series.markerGroup. On subsequent calls, 2127 * the group will only be adjusted to the updated plot size. 2128 * 2129 * @private 2130 * @function Highcharts.Series#plotGroup 2131 */ 2132 plotGroup(prop, name, visibility, zIndex, parent) { 2133 let group = this[prop]; 2134 const isNew = !group, attrs = { 2135 visibility, 2136 zIndex: zIndex || 0.1 // Pointer logic uses this 2137 }; 2138 // Avoid setting undefined opacity, or in styled mode 2139 if (defined(this.opacity) && 2140 !this.chart.styledMode && this.state !== 'inactive' // #13719 2141 ) { 2142 attrs.opacity = this.opacity; 2143 } 2144 // Generate it on first call 2145 if (!group) { 2146 this[prop] = group = this.chart.renderer 2147 .g() 2148 .add(parent); 2149 } 2150 // Add the class names, and replace existing ones as response to 2151 // Series.update (#6660) 2152 group.addClass(('highcharts-' + name + 2153 ' highcharts-series-' + this.index + 2154 ' highcharts-' + this.type + '-series ' + 2155 (defined(this.colorIndex) ? 2156 'highcharts-color-' + this.colorIndex + ' ' : 2157 '') + 2158 (this.options.className || '') + 2159 (group.hasClass('highcharts-tracker') ? 2160 ' highcharts-tracker' : 2161 '')), true); 2162 // Place it on first and subsequent (redraw) calls 2163 group.attr(attrs)[isNew ? 'attr' : 'animate'](this.getPlotBox(name)); 2164 return group; 2165 } 2166 /** 2167 * Get the translation and scale for the plot area of this series. 2168 * 2169 * @function Highcharts.Series#getPlotBox 2170 */ 2171 getPlotBox(name) { 2172 let horAxis = this.xAxis, vertAxis = this.yAxis; 2173 const chart = this.chart, inverted = (chart.inverted && 2174 !chart.polar && 2175 horAxis && 2176 this.invertible && 2177 name === 'series'); 2178 // Swap axes for inverted (#2339) 2179 if (chart.inverted) { 2180 horAxis = vertAxis; 2181 vertAxis = this.xAxis; 2182 } 2183 return { 2184 translateX: horAxis ? horAxis.left : chart.plotLeft, 2185 translateY: vertAxis ? vertAxis.top : chart.plotTop, 2186 rotation: inverted ? 90 : 0, 2187 rotationOriginX: inverted ? 2188 (horAxis.len - vertAxis.len) / 2 : 2189 0, 2190 rotationOriginY: inverted ? 2191 (horAxis.len + vertAxis.len) / 2 : 2192 0, 2193 scaleX: inverted ? -1 : 1, // #1623 2194 scaleY: 1 2195 }; 2196 } 2197 /** 2198 * Removes the event handlers attached previously with addEvents. 2199 * @private 2200 * @function Highcharts.Series#removeEvents 2201 */ 2202 removeEvents(keepEventsForUpdate) { 2203 const { eventsToUnbind } = this; 2204 if (!keepEventsForUpdate) { 2205 // Remove all events 2206 removeEvent(this); 2207 } 2208 if (eventsToUnbind.length) { 2209 // Remove only internal events for proper update. #12355 solves 2210 // problem with multiple destroy events 2211 eventsToUnbind.forEach((unbind) => { 2212 unbind(); 2213 }); 2214 eventsToUnbind.length = 0; 2215 } 2216 } 2217 /** 2218 * Render the graph and markers. Called internally when first rendering 2219 * and later when redrawing the chart. This function can be extended in 2220 * plugins, but normally shouldn't be called directly. 2221 * 2222 * @function Highcharts.Series#render 2223 * 2224 * @emits Highcharts.Series#event:afterRender 2225 */ 2226 render() { 2227 const series = this, { chart, options, hasRendered } = series, animOptions = animObject(options.animation), visibility = series.visible ? 2228 'inherit' : 'hidden', // #2597 2229 zIndex = options.zIndex, chartSeriesGroup = chart.seriesGroup; 2230 let animDuration = series.finishedAnimating ? 2231 0 : animOptions.duration; 2232 fireEvent(this, 'render'); 2233 // The group 2234 series.plotGroup('group', 'series', visibility, zIndex, chartSeriesGroup); 2235 series.markerGroup = series.plotGroup('markerGroup', 'markers', visibility, zIndex, chartSeriesGroup); 2236 // Initial clipping, applies to columns etc. (#3839). 2237 if (options.clip !== false) { 2238 series.setClip(); 2239 } 2240 // Initialize the animation 2241 if (animDuration) { 2242 series.animate?.(true); 2243 } 2244 // Draw the graph if any 2245 if (series.drawGraph) { 2246 series.drawGraph(); 2247 series.applyZones(); 2248 } 2249 // Draw the points 2250 if (series.visible) { 2251 series.drawPoints(); 2252 } 2253 // Draw the data labels 2254 series.drawDataLabels?.(); 2255 // In pie charts, slices are added to the DOM, but actual rendering 2256 // is postponed until labels reserved their space 2257 series.redrawPoints?.(); 2258 // Draw the mouse tracking area 2259 if (options.enableMouseTracking) { 2260 series.drawTracker?.(); 2261 } 2262 // Run the animation 2263 if (animDuration) { 2264 series.animate?.(); 2265 } 2266 // Call the afterAnimate function on animation complete (but don't 2267 // overwrite the animation.complete option which should be available 2268 // to the user). 2269 if (!hasRendered) { 2270 // Additional time if defer is defined before afterAnimate 2271 // will be triggered 2272 if (animDuration && animOptions.defer) { 2273 animDuration += animOptions.defer; 2274 } 2275 series.animationTimeout = syncTimeout(() => { 2276 series.afterAnimate(); 2277 }, animDuration || 0); 2278 } 2279 // Means data is in accordance with what you see 2280 series.isDirty = false;
vendor: 5,303 bytes, lines 2281-2410
2281 // (See #322) series.isDirty = series.isDirtyData = false; // means 2282 // data is in accordance with what you see 2283 series.hasRendered = true; 2284 fireEvent(series, 'afterRender'); 2285 } 2286 /** 2287 * Redraw the series. This function is called internally from 2288 * `chart.redraw` and normally shouldn't be called directly. 2289 * @private 2290 * @function Highcharts.Series#redraw 2291 */ 2292 redraw() { 2293 // Cache it here as it is set to false in render, but used after 2294 const wasDirty = this.isDirty || this.isDirtyData; 2295 this.translate(); 2296 this.render(); 2297 if (wasDirty) { // #3868, #3945 2298 delete this.kdTree; 2299 } 2300 } 2301 /** 2302 * Whether to reserve space for the series, either because it is visible or 2303 * because the `chart.ignoreHiddenSeries` option is false. 2304 * 2305 * @private 2306 */ 2307 reserveSpace() { 2308 return this.visible || !this.chart.options.chart.ignoreHiddenSeries; 2309 } 2310 /** 2311 * Find the nearest point from a pointer event. This applies to series that 2312 * use k-d-trees to get the nearest point. Native pointer events must be 2313 * normalized using `Pointer.normalize`, that adds `chartX` and `chartY` 2314 * properties. 2315 * 2316 * @sample highcharts/demo/synchronized-charts 2317 * Synchronized charts with tooltips 2318 * 2319 * @function Highcharts.Series#searchPoint 2320 * 2321 * @param {Highcharts.PointerEvent} e 2322 * The normalized pointer event 2323 * @param {boolean} [compareX=false] 2324 * Search only by the X value, not Y 2325 * 2326 * @return {Point|undefined} 2327 * The closest point to the pointer event 2328 */ 2329 searchPoint(e, compareX) { 2330 const { xAxis, yAxis } = this, inverted = this.chart.inverted; 2331 return this.searchKDTree({ 2332 clientX: inverted ? 2333 xAxis.len - e.chartY + xAxis.pos : 2334 e.chartX - xAxis.pos, 2335 plotY: inverted ? 2336 yAxis.len - e.chartX + yAxis.pos : 2337 e.chartY - yAxis.pos 2338 }, compareX, e); 2339 } 2340 /** 2341 * Build the k-d-tree that is used by mouse and touch interaction to get 2342 * the closest point. Line-like series typically have a one-dimensional 2343 * tree where points are searched along the X axis, while scatter-like 2344 * series typically search in two dimensions, X and Y. 2345 * 2346 * @private 2347 * @function Highcharts.Series#buildKDTree 2348 */ 2349 buildKDTree(e) { 2350 // Prevent multiple k-d-trees from being built simultaneously 2351 // (#6235) 2352 this.buildingKdTree = true; 2353 const series = this, dimensions = series.options.findNearestPointBy 2354 .indexOf('y') > -1 ? 2 : 1; 2355 /** 2356 * Internal function 2357 * @private 2358 */ 2359 function kdtree(points, depth, dimensions) { 2360 const length = points?.length; 2361 let axis, median; 2362 if (length) { 2363 // Alternate between the axis 2364 axis = series.kdAxisArray[depth % dimensions]; 2365 // Sort point array 2366 points.sort((a, b) => (a[axis] || 0) - (b[axis] || 0)); 2367 median = Math.floor(length / 2); 2368 // Build and return node 2369 return { 2370 point: points[median], 2371 left: kdtree(points.slice(0, median), depth + 1, dimensions), 2372 right: kdtree(points.slice(median + 1), depth + 1, dimensions) 2373 }; 2374 } 2375 } 2376 /** 2377 * Start the recursive build process with a clone of the points 2378 * array and null points filtered out. (#3873) 2379 * @private 2380 */ 2381 function startRecursive() { 2382 series.kdTree = kdtree(series.getValidPoints(void 0, 2383 // For line-type series restrict to plot area, but 2384 // column-type series not (#3916, #4511) 2385 !series.directTouch), dimensions, dimensions); 2386 series.buildingKdTree = false; 2387 } 2388 delete series.kdTree; 2389 // For testing tooltips, don't build async. Also if touchstart, we may 2390 // be dealing with click events on mobile, so don't delay (#6817). 2391 syncTimeout(startRecursive, series.options.kdNow || e?.type === 'touchstart' ? 0 : 1); 2392 } 2393 /** 2394 * @private 2395 * @function Highcharts.Series#searchKDTree 2396 */ 2397 searchKDTree(point, compareX, e, suppliedPointEvaluator, suppliedBSideCheckEvaluator) { 2398 const series = this, [kdX, kdY] = this.kdAxisArray, kdComparer = compareX ? 'distX' : 'dist', kdDimensions = (series.options.findNearestPointBy || '') 2399 .indexOf('y') > -1 ? 2 : 1, useRadius = !!series.isBubble, pointEvaluator = suppliedPointEvaluator || ((p1, p2, comparisonProp) => [ 2400 (p1[comparisonProp] || 0) < (p2[comparisonProp] || 0) ? 2401 p1 : 2402 p2, 2403 false 2404 ]), bSideCheckEvaluator = suppliedBSideCheckEvaluator || ((a, b) => a < b); 2405 /** 2406 * Set the one and two dimensional distance on the point object. 2407 * @private 2408 */ 2409 function setDistance(p1, p2) { 2410 const p1kdX = p1[kdX], p2kdX = p2[kdX], x = (defined(p1kdX) && defined(p2kdX)) ? p1kdX - p2kdX : null, p1kdY = p1[kdY], p2k
vendor: 1,218 bytes, lines 2410-2430
2410dY = p2[kdY], y = (defined(p1kdY) && defined(p2kdY)) ? p1kdY - p2kdY : 0, radius = useRadius ? (p2.marker?.radius || 0) : 0; 2411 p2.dist = Math.sqrt(((x && x * x) || 0) + y * y) - radius; 2412 p2.distX = defined(x) ? (Math.abs(x) - radius) : Number.MAX_VALUE; 2413 } 2414 /** 2415 * @private 2416 */ 2417 function doSearch(search, tree, depth, dimensions) { 2418 const point = tree.point, axis = series.kdAxisArray[depth % dimensions]; 2419 let ret = point, flip = false; 2420 setDistance(search, point); 2421 // Pick side based on distance to splitting point 2422 const tdist = (search[axis] || 0) - (point[axis] || 0) + 2423 (useRadius ? (point.marker?.radius || 0) : 0), sideA = tdist < 0 ? 'left' : 'right', sideB = tdist < 0 ? 'right' : 'left'; 2424 // End of tree 2425 if (tree[sideA]) { 2426 [ret, flip] = pointEvaluator(point, doSearch(search, tree[sideA], depth + 1, dimensions), kdComparer); 2427 } 2428 if (tree[sideB]) { 2429 const sqrtTDist = Math.sqrt(tdist * tdist), retDist = ret[kdComparer]; 2430 // Compare distance to current best to splitting point to decide
vendor: 4,975 bytes, lines 2431-2553
2431 // whether to check side B or no 2432 if (bSideCheckEvaluator(sqrtTDist, retDist, flip)) { 2433 ret = pointEvaluator(ret, doSearch(search, tree[sideB], depth + 1, dimensions), kdComparer)[0]; 2434 } 2435 } 2436 return ret; 2437 } 2438 if (!this.kdTree && !this.buildingKdTree) { 2439 this.buildKDTree(e); 2440 } 2441 if (this.kdTree) { 2442 return doSearch(point, this.kdTree, kdDimensions, kdDimensions); 2443 } 2444 } 2445 /** 2446 * @private 2447 * @function Highcharts.Series#pointPlacementToXValue 2448 */ 2449 pointPlacementToXValue() { 2450 const { options, xAxis } = this; 2451 let factor = options.pointPlacement; 2452 // Point placement is relative to each series pointRange (#5889) 2453 if (factor === 'between') { 2454 factor = xAxis.reversed ? -0.5 : 0.5; // #11955 2455 } 2456 return isNumber(factor) ? 2457 factor * (options.pointRange || xAxis.pointRange) : 2458 0; 2459 } 2460 /** 2461 * @private 2462 * @function Highcharts.Series#isPointInside 2463 */ 2464 isPointInside(point) { 2465 const { chart, xAxis, yAxis } = this, { plotX = -1, plotY = -1 } = point, isInside = (plotY >= 0 && 2466 plotY <= (yAxis ? yAxis.len : chart.plotHeight) && 2467 plotX >= 0 && 2468 plotX <= (xAxis ? xAxis.len : chart.plotWidth)); 2469 return isInside; 2470 } 2471 /** 2472 * Draw the tracker object that sits above all data labels and markers to 2473 * track mouse events on the graph or points. For the line type charts 2474 * the tracker uses the same graphPath, but with a greater stroke width 2475 * for better control. 2476 * @private 2477 */ 2478 drawTracker() { 2479 const series = this, options = series.options, trackByArea = options.trackByArea, trackerPath = [].concat((trackByArea ? series.areaPath : series.graphPath) || []), chart = series.chart, pointer = chart.pointer, renderer = chart.renderer, snap = chart.options.tooltip?.snap || 0, onMouseOver = () => { 2480 if (options.enableMouseTracking && 2481 chart.hoverSeries !== series) { 2482 series.onMouseOver(); 2483 } 2484 }, 2485 /* 2486 * Empirical lowest possible opacities for TRACKER_FILL for an 2487 * element to stay invisible but clickable 2488 * IE9: 0.00000000001 (unlimited) 2489 * IE10: 0.0001 (exporting only) 2490 * FF: 0.00000000001 (unlimited) 2491 * Chrome: 0.000001 2492 * Safari: 0.000001 2493 * Opera: 0.00000000001 (unlimited) 2494 */ 2495 TRACKER_FILL = 'rgba(192,192,192,' + (svg ? 0.0001 : 0.002) + ')'; 2496 let tracker = series.tracker; 2497 // Draw the tracker 2498 if (tracker) { 2499 tracker.attr({ d: trackerPath }); 2500 } 2501 else if (series.graph) { // Create 2502 series.tracker = tracker = renderer.path(trackerPath) 2503 .attr({ 2504 visibility: series.visible ? 'inherit' : 'hidden', 2505 zIndex: 2 2506 }) 2507 .addClass(trackByArea ? 2508 'highcharts-tracker-area' : 2509 'highcharts-tracker-line') 2510 .add(series.group); 2511 if (!chart.styledMode) { 2512 tracker.attr({ 2513 'stroke-linecap': 'round', 2514 'stroke-linejoin': 'round', // #1225 2515 stroke: TRACKER_FILL, 2516 fill: trackByArea ? TRACKER_FILL : 'none', 2517 'stroke-width': series.graph.strokeWidth() + 2518 (trackByArea ? 0 : 2 * snap) 2519 }); 2520 } 2521 // The tracker is added to the series group, which is clipped, but 2522 // is covered by the marker group. So the marker group also needs to 2523 // capture events. 2524 [ 2525 series.tracker, 2526 series.markerGroup, 2527 series.dataLabelsGroup 2528 ].forEach((tracker) => { 2529 if (tracker) { 2530 tracker.addClass('highcharts-tracker') 2531 .on('mouseover', onMouseOver) 2532 .on('mouseout', (e) => { 2533 pointer?.onTrackerMouseOut(e); 2534 }); 2535 if (options.cursor && !chart.styledMode) { 2536 tracker.css({ cursor: options.cursor }); 2537 } 2538 tracker.on('touchstart', onMouseOver); 2539 } 2540 }); 2541 } 2542 fireEvent(this, 'afterDrawTracker'); 2543 } 2544 /** 2545 * Add a point to the series after render time. The point can be added at 2546 * the end, or by giving it an X value, to the start or in the middle of the 2547 * series. 2548 * 2549 * @sample highcharts/members/series-addpoint-append/ 2550 * Append point 2551 * @sample highcharts/members/series-addpoint-append-and-shift/ 2552 * Append and shift 2553 * @sample highcharts/members/series-addpoint-x-and-y/
2554 * Both X and Y values given 2555 * @sample highcharts/members/series-addpoint-pie/ 2556 * Append pie slice 2557 * @sample stock/members/series-addpoint/ 2558 * Append 100 points in Highcharts Stock 2559 * @sample stock/members/series-addpoint-shift/ 2560 * Append and shift in Highcharts Stock 2561 * @sample maps/members/series-addpoint/ 2562 * Add a point in Highmaps 2563 * 2564 * @function Highcharts.Series#addPoint 2565 * 2566 * @param {Highcharts.PointOptionsType} options 2567 * The point options. If options is a single number, a point with 2568 * that y value is appended to the series. If it is an array, it will 2569 * be interpreted as x and y values respectively. If it is an 2570 * object, advanced options as outlined under `series.data` are 2571 * applied. 2572 * 2573 * @param {boolean} [redraw=true] 2574 * Whether to redraw the chart after the point is added. When adding 2575 * more than one point, it is highly recommended that the redraw 2576 * option be set to false, and instead {@link Chart#redraw} is 2577 * explicitly called after the adding of points is finished. 2578 * Otherwise, the chart will redraw after adding each point. 2579 * 2580 * @param {boolean} [shift=false] 2581 * If true, a point is shifted off the start of the series as one is 2582 * appended to the end. 2583 * 2584 * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] 2585 * Whether to apply animation, and optionally animation 2586 * configuration. 2587 * 2588 * @param {boolean} [withEvent=true] 2589 * Used internally, whether to fire the series `addPoint` event. 2590 * 2591 * @emits Highcharts.Series#event:addPoint 2592 */ 2593 addPoint(options, redraw, shift, animation, withEvent) { 2594 const series = this, seriesOptions = series.options, { chart, data, dataTable: table, xAxis } = series, names = xAxis && xAxis.hasNames && xAxis.names, dataOptions = seriesOptions.data, xData = series.getColumn('x'); 2595 let isInTheMiddle, i; 2596 // Optional redraw, defaults to true 2597 redraw = pick(redraw, true); 2598 // Get options and push the point to xData, yData and series.options. In 2599 // series.generatePoints the Point instance will be created on demand 2600 // and pushed to the series.data array. 2601 const point = { series: series }; 2602 series.pointClass.prototype.applyOptions.apply(point, [options]); 2603 const x = point.x; 2604 // Get the insertion point 2605 i = xData.length; 2606 if (series.requireSorting && x < xData[i - 1]) { 2607 isInTheMiddle = true; 2608 while (i && xData[i - 1] > x) { 2609 i--; 2610 } 2611 } 2612 // Insert the row at the given index 2613 table.setRow(point, i, true, { addColumns: false }); 2614 if (names && point.name) { 2615 names[x] = point.name; 2616 } 2617 dataOptions?.splice(i, 0, options); 2618 if (isInTheMiddle || 2619 // When processedData is present we need to splice an empty slot 2620 // into series.data, otherwise generatePoints won't pick it up. 2621 series.processedData) { 2622 series.data.splice(i, 0, null); 2623 series.processData(); 2624 } 2625 // Generate points to be added to the legend (#1329) 2626 if (seriesOptions.legendType === 'point') { 2627 series.generatePoints(); 2628 } 2629 // Shift the first point off the parallel arrays 2630 if (shift) { 2631 if (data[0] && !!data[0].remove) { 2632 data[0].remove(false); 2633 } 2634 else { 2635 [ 2636 data, 2637 dataOptions, 2638 ...Object.values(table.getColumns()) 2639 ].filter(defined).forEach((coll) => { 2640 coll.shift(); 2641 }); 2642 table.rowCount -= 1; 2643 fireEvent(table, 'afterDeleteRows'); 2644 } 2645 } 2646 // Fire event 2647 if (withEvent !== false) { 2648 fireEvent(series, 'addPoint', { point: point }); 2649 } 2650 // Redraw 2651 series.isDirty = true; 2652 series.isDirtyData = true; 2653 if (redraw) { 2654 chart.redraw(animation); // Animation is set anyway on redraw, #5665 2655 } 2656 } 2657 /** 2658 * Remove a point from the series. Unlike the 2659 * {@link Highcharts.Point#remove} method, this can also be done on a point 2660 * that is not instantiated because it is outside the view or subject to 2661 * Highcharts Stock data grouping. 2662 * 2663 * @sample highcharts/members/series-removepoint/ 2664 * Remove cropped point 2665 * 2666 * @function Highcharts.Series#removePoint 2667 * 2668 * @param {number} i 2669 * The index of the point in the {@link Highcharts.Series.data|data} 2670 * array. 2671 * 2672 * @param {boolean} [redraw=true] 2673 * Whether to redraw the chart after the point is added. When 2674 * removing more than one point, it is highly recommended that the 2675 * `redraw` option be set to `false`, and instead {@link 2676 * Highcharts.Chart#redraw} is explicitly called after the adding of 2677 * points is finished. 2678 * 2679 * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] 2680 * Whether and optionally how the series should be animated. 2681 * 2682 * @emits Highcharts.Point#event:remove 2683 */ 2684 removePoint(i, redraw, animation) { 2685 const series = this, { chart, data, points, dataTable: table } = series, point = data[i], remove = function () { 2686 // Splice out the point's data from all parallel arrays 2687 [ 2688 // #4935 2689 points?.length === data.length ? points : void 0, 2690 data, 2691 series.options.data, 2692 ...Object.values(table.getColumns())
2693 ].filter(defined).forEach((coll) => { 2694 coll.splice(i, 1); 2695 }); 2696 // Shorthand row deletion in order to avoid including the whole 2697 // `deleteRows` function in the DataTableCore module. 2698 table.rowCount -= 1; 2699 fireEvent(table, 'afterDeleteRows'); 2700 point?.destroy(); 2701 // Redraw 2702 series.isDirty = true; 2703 series.isDirtyData = true; 2704 if (redraw) { 2705 chart.redraw(); 2706 } 2707 }; 2708 setAnimation(animation, chart); 2709 redraw = pick(redraw, true); 2710 // Fire the event with a default handler of removing the point 2711 if (point) { 2712 point.firePointEvent('remove', null, remove); 2713 } 2714 else { 2715 remove(); 2716 } 2717 } 2718 /** 2719 * Remove a series and optionally redraw the chart. 2720 * 2721 * @sample highcharts/members/series-remove/ 2722 * Remove first series from a button 2723 * 2724 * @function Highcharts.Series#remove 2725 * 2726 * @param {boolean} [redraw=true] 2727 * Whether to redraw the chart or wait for an explicit call to 2728 * {@link Highcharts.Chart#redraw}. 2729 * 2730 * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] 2731 * Whether to apply animation, and optionally animation 2732 * configuration. 2733 * 2734 * @param {boolean} [withEvent=true] 2735 * Used internally, whether to fire the series `remove` event. 2736 * 2737 * @emits Highcharts.Series#event:remove 2738 */ 2739 remove(redraw, animation, withEvent, keepEvents) { 2740 const series = this, chart = series.chart; 2741 /** 2742 * @private 2743 */ 2744 function remove() { 2745 // Destroy elements 2746 series.destroy(keepEvents); 2747 // Redraw 2748 chart.isDirtyLegend = chart.isDirtyBox = true; 2749 chart.linkSeries(keepEvents); 2750 if (pick(redraw, true)) { 2751 chart.redraw(animation); 2752 } 2753 } 2754 // Fire the event with a default handler of removing the point 2755 if (withEvent !== false) { 2756 fireEvent(series, 'remove', null, remove); 2757 } 2758 else { 2759 remove(); 2760 } 2761 } 2762 /** 2763 * Update the series with a new set of options. For a clean and precise 2764 * handling of new options, all methods and elements from the series are 2765 * removed, and it is initialized from scratch. Therefore, this method is 2766 * more performance expensive than some other utility methods like {@link 2767 * Series#setData} or {@link Series#setVisible}. 2768 * 2769 * Note that `Series.update` may mutate the passed `data` options. 2770 * 2771 * @sample highcharts/members/series-update/ 2772 * Updating series options 2773 * @sample maps/members/series-update/ 2774 * Update series options in Highmaps 2775 * 2776 * @function Highcharts.Series#update 2777 * 2778 * @param {Highcharts.SeriesOptionsType} options 2779 * New options that will be merged with the series' existing options. 2780 * 2781 * @param {boolean} [redraw=true] 2782 * Whether to redraw the chart after the series is altered. If doing 2783 * more operations on the chart, it is a good idea to set redraw to 2784 * false and call {@link Chart#redraw} after. 2785 * 2786 * @emits Highcharts.Series#event:update 2787 * @emits Highcharts.Series#event:afterUpdate 2788 */ 2789 update(options, redraw) { 2790 options = diffObjects(options, this.userOptions); 2791 fireEvent(this, 'update', { options: options }); 2792 const series = this, chart = series.chart, 2793 // Must use user options when changing type because series.options 2794 // is merged in with type specific plotOptions 2795 oldOptions = series.userOptions, initialType = series.initialType || series.type, plotOptions = chart.options.plotOptions, initialSeriesProto = seriesTypes[initialType].prototype, groups = [ 2796 'group', 2797 'markerGroup', 2798 'dataLabelsGroup', 2799 'transformGroup' 2800 ], optionsToCheck = [ 2801 'dataGrouping', 2802 'pointStart', 2803 'pointInterval', 2804 'pointIntervalUnit', 2805 'keys' 2806 ], 2807 // Animation must be enabled when calling update before the initial 2808 // animation has first run. This happens when calling update 2809 // directly after chart initialization, or when applying responsive 2810 // rules (#6912). 2811 animation = series.finishedAnimating && { animation: false }, kinds = {}; 2812 let seriesOptions, n, preserve = [ 2813 'colorIndex', 2814 'eventOptions', 2815 'navigatorSeries', 2816 'symbolIndex', 2817 'baseSeries' 2818 ], newType = (options.type || 2819 oldOptions.type || 2820 chart.options.chart.type);
2821 const keepPoints = !( 2822 // Indicators, histograms etc recalculate the data. It should be 2823 // possible to omit this. 2824 this.hasDerivedData || 2825 // New type requires new point classes 2826 (newType && newType !== this.type) || 2827 // New options affecting how the data points are built 2828 typeof options.keys !== 'undefined' || 2829 typeof options.pointStart !== 'undefined' || 2830 typeof options.pointInterval !== 'undefined' || 2831 typeof options.relativeXValue !== 'undefined' || 2832 options.joinBy || 2833 options.mapData || // #11636 2834 // Changes to data grouping requires new points in new group 2835 optionsToCheck.some((option) => series.hasOptionChanged(option))); 2836 newType = newType || initialType; 2837 if (keepPoints) { 2838 preserve.push('data', 'isDirtyData', 2839 // GeoHeatMap interpolation 2840 'isDirtyCanvas', 'points', 'dataTable', 'processedData', // #17057 2841 'xIncrement', 'cropped', '_hasPointMarkers', 'hasDataLabels', 2842 // Networkgraph (#14397) 2843 'nodes', 'layout', 2844 // Treemap 2845 'level', 2846 // Map specific, consider moving it to series-specific preserve- 2847 // properties (#10617) 2848 'mapMap', 'mapData', 'minY', 'maxY', 'minX', 'maxX', 'transformGroups' // #18857 2849 ); 2850 if (options.visible !== false) { 2851 preserve.push('area', 'graph'); 2852 } 2853 series.parallelArrays.forEach(function (key) { 2854 preserve.push(key + 'Data'); 2855 }); 2856 if (options.data) { 2857 // `setData` uses `dataSorting` options so we need to update 2858 // them earlier 2859 if (options.dataSorting) { 2860 extend(series.options.dataSorting, options.dataSorting); 2861 } 2862 this.setData(options.data, false); 2863 } 2864 } 2865 else { 2866 this.dataTable.modified = this.dataTable; 2867 } 2868 // Do the merge, with some forced options 2869 options = merge(oldOptions, { 2870 // When oldOptions.index is null it should't be cleared. 2871 // Otherwise navigator series will have wrong indexes (#10193). 2872 index: oldOptions.index === void 0 ? 2873 series.index : oldOptions.index, 2874 pointStart: 2875 // When updating from blank (#7933) 2876 plotOptions?.series?.pointStart ?? 2877 oldOptions.pointStart ?? 2878 // When updating after addPoint 2879 series.getColumn('x')[0] 2880 }, !keepPoints && { data: series.options.data }, options, animation); 2881 // Merge does not merge arrays, but replaces them. Since points were 2882 // updated, `series.options.data` has correct merged options, use it: 2883 if (keepPoints && options.data) { 2884 options.data = series.options.data; 2885 } 2886 // Make sure preserved properties are not destroyed (#3094) 2887 preserve = groups.concat(preserve); 2888 preserve.forEach(function (prop) { 2889 preserve[prop] = series[prop]; 2890 delete series[prop]; 2891 }); 2892 let casting = false; 2893 if (seriesTypes[newType]) { 2894 casting = newType !== series.type; 2895 // Destroy the series and delete all properties, it will be 2896 // reinserted within the `init` call below 2897 series.remove(false, false, false, true); 2898 if (casting) { 2899 // #20264: Re-detect a certain chart properties from new series 2900 chart.propFromSeries(); 2901 // Modern browsers including IE11 2902 if (Object.setPrototypeOf) { 2903 Object.setPrototypeOf(series, seriesTypes[newType].prototype); 2904 // Legacy (IE < 11) 2905 } 2906 else { 2907 const ownEvents = Object.hasOwnProperty.call(series, 'hcEvents') && series.hcEvents; 2908 for (n in initialSeriesProto) { // eslint-disable-line guard-for-in 2909 series[n] = void 0; 2910 } 2911 // Reinsert all methods and properties from the new type 2912 // prototype (#2270, #3719). 2913 extend(series, seriesTypes[newType].prototype); 2914 // The events are tied to the prototype chain, don't copy if 2915 // they're not the series' own 2916 if (ownEvents) { 2917 series.hcEvents = ownEvents; 2918 } 2919 else { 2920 delete series.hcEvents; 2921 } 2922 } 2923 } 2924 } 2925 else { 2926 error(17, true, chart, { missingModuleFor: newType }); 2927 } 2928 // Re-register groups (#3094) and other preserved properties 2929 preserve.forEach(function (prop) { 2930 series[prop] = preserve[prop]; 2931 }); 2932 series.init(chart, options); 2933 // Remove particular elements of the points. Check `series.options` 2934 // because we need to consider the options being set on plotOptions as 2935 // well. 2936 if (keepPoints && this.points) { 2937 seriesOptions = series.options; 2938 // What kind of elements to destroy 2939 if (seriesOptions.visible === false) { 2940 kinds.graphic = 1; 2941 kinds.dataLabel = 1; 2942 } 2943 else { 2944 // If the marker got disabled or changed its symbol, width or 2945 // height - destroy 2946 if (this.hasMarkerChanged(seriesOptions, oldOptions)) { 2947 kinds.graphic = 1; 2948 } 2949 if (!series.hasDataLabels?.()) { 2950 kinds.dataLabel = 1; 2951 } 2952 } 2953 for (const point of this.points) { 2954 if (point && point.series) {
2955 point.resolveColor(); 2956 // Destroy elements in order to recreate based on updated 2957 // series options. 2958 if (Object.keys(kinds).length) { 2959 point.destroyElements(kinds); 2960 } 2961 if (seriesOptions.showInLegend === false && 2962 point.legendItem) { 2963 chart.legend.destroyItem(point); 2964 } 2965 } 2966 } 2967 } 2968 series.initialType = initialType; 2969 chart.linkSeries(); // Links are lost in series.remove (#3028) 2970 // Set data for series with sorting enabled if it isn't set yet (#19715) 2971 chart.setSortedData(); 2972 // #15383: Fire updatedData if the type has changed to keep linked 2973 // series such as indicators updated 2974 if (casting && series.linkedSeries.length) { 2975 series.isDirtyData = true; 2976 } 2977 fireEvent(this, 'afterUpdate'); 2978 if (pick(redraw, true)) { 2979 chart.redraw(keepPoints ? void 0 : false); 2980 } 2981 } 2982 /** 2983 * Used from within series.update 2984 * @private 2985 */ 2986 setName(name) { 2987 this.name = this.options.name = this.userOptions.name = name; 2988 this.chart.isDirtyLegend = true; 2989 } 2990 /** 2991 * Check if the option has changed. 2992 * @private 2993 */ 2994 hasOptionChanged(optionName) { 2995 const chart = this.chart, option = this.options[optionName], plotOptions = chart.options.plotOptions, oldOption = this.userOptions[optionName], plotOptionsOption = pick(plotOptions?.[this.type]?.[optionName], plotOptions?.series?.[optionName]); 2996 // Check if `plotOptions` are defined already, #19203 2997 if (oldOption && !defined(plotOptionsOption)) { 2998 return option !== oldOption; 2999 } 3000 return option !== pick(plotOptionsOption, option); 3001 } 3002 /** 3003 * Runs on mouse over the series graphical items. 3004 * 3005 * @function Highcharts.Series#onMouseOver 3006 * @emits Highcharts.Series#event:mouseOver 3007 */ 3008 onMouseOver() { 3009 const series = this, chart = series.chart, hoverSeries = chart.hoverSeries, pointer = chart.pointer; 3010 pointer?.setHoverChartIndex(); 3011 // Set normal state to previous series 3012 if (hoverSeries && hoverSeries !== series) { 3013 hoverSeries.onMouseOut(); 3014 } 3015 // Trigger the event, but to save processing time, 3016 // only if defined 3017 if (series.options.events.mouseOver) { 3018 fireEvent(series, 'mouseOver'); 3019 } 3020 // Hover this 3021 series.setState('hover'); 3022 /** 3023 * Contains the original hovered series. 3024 * 3025 * @name Highcharts.Chart#hoverSeries 3026 * @type {Highcharts.Series|null} 3027 */ 3028 chart.hoverSeries = series; 3029 } 3030 /** 3031 * Runs on mouse out of the series graphical items. 3032 * 3033 * @function Highcharts.Series#onMouseOut 3034 * 3035 * @emits Highcharts.Series#event:mouseOut 3036 */ 3037 onMouseOut() { 3038 // Trigger the event only if listeners exist 3039 const series = this, options = series.options, chart = series.chart, tooltip = chart.tooltip, hoverPoint = chart.hoverPoint; 3040 // #182, set to null before the mouseOut event fires 3041 chart.hoverSeries = null; 3042 // Trigger mouse out on the point, which must be in this series 3043 if (hoverPoint) { 3044 hoverPoint.onMouseOut(); 3045 } 3046 // Fire the mouse out event 3047 if (series && options.events.mouseOut) { 3048 fireEvent(series, 'mouseOut'); 3049 } 3050 // Hide the tooltip 3051 if (tooltip && 3052 !series.stickyTracking && 3053 (!tooltip.shared || series.noSharedTooltip)) { 3054 tooltip.hide(); 3055 } 3056 // Reset all inactive states 3057 chart.series.forEach(function (s) { 3058 s.setState('', true); 3059 }); 3060 } 3061 /** 3062 * Set the state of the series. Called internally on mouse interaction 3063 * operations, but it can also be called directly to visually 3064 * highlight a series. 3065 * 3066 * @function Highcharts.Series#setState 3067 * 3068 * @param {Highcharts.SeriesStateValue|""} [state] 3069 * The new state, can be either `'hover'`, `'inactive'`, `'select'`, 3070 * or `''` (an empty string), `'normal'` or `undefined` to set to 3071 * normal state. 3072 * @param {boolean} [inherit] 3073 * Determines if state should be inherited by points too. 3074 */ 3075 setState(state, inherit) { 3076 const series = this, options = series.options, graph = series.graph, inactiveOtherPoints = options.inactiveOtherPoints, stateOptions = options.states, 3077 // By default a quick animation to hover/inactive, 3078 // slower to un-hover 3079 stateAnimation = pick((stateOptions[state || 'normal'] && 3080 stateOptions[state || 'normal'].animation), series.chart.options.chart.animation); 3081 let lineWidth = options.lineWidth, opacity = options.opacity; 3082 state = state || ''; 3083 if (series.state !== state) { 3084 // Toggle class names 3085 [ 3086 series.group, 3087 series.markerGroup, 3088 series.dataLabelsGroup 3089 ].forEach(function (group) { 3090 if (group) { 3091 // Old state 3092 if (series.state) { 3093 group.removeClass('highcharts-series-' + series.state); 3094 } 3095 // New state 3096 if (state) { 3097 group.addClass('highcharts-series-' + state); 3098 } 3099 } 3100 }); 3101 series.state = state; 3102 if (!series.chart.styledMode) { 3103 if (stateOptions[state] && 3104 stateOptions[state].enabled === false) { 3105 return; 3106 } 3107 if (state) { 3108 lineWidth = (stateOptions[state].lineWidth || 3109 lineWidth + (stateOptions[state].lineWidthPlus || 0)); // #4035 3110 opacity = pick(stateOptions[state].opacity, opacity); 3111 } 3112 if (graph && !graph.dashstyle && isNumber(lineWidth)) { 3113 // Animate the graph stroke-width 3114 for (const graphElement of [ 3115 graph, 3116 ...this.zones.map((zone) => zone.graph) 3117 ]) { 3118 graphElement?.animate({ 3119 'stroke-width': lineWidth 3120 }, stateAnimation); 3121 } 3122 } 3123 // For some types (pie, networkgraph, sankey) opacity is 3124 // resolved on a point level 3125 if (!inactiveOtherPoints) { 3126 [ 3127 series.group, 3128 series.markerGroup, 3129 series.dataLabelsGroup, 3130 series.labelBySeries 3131 ].forEach(function (group) { 3132 if (group) { 3133 group.animate({ 3134 opacity: opacity 3135 }, stateAnimation); 3136 } 3137 }); 3138 } 3139 } 3140 } 3141 // Don't loop over points on a series that doesn't apply inactive state 3142 // to siblings markers (e.g. line, column) 3143 if (inherit && inactiveOtherPoints && series.points) { 3144 series.setAllPointsToState(state || void 0); 3145 } 3146 } 3147 /** 3148 * Set the state for all points in the series. 3149 * 3150 * @function Highcharts.Series#setAllPointsToState 3151 * 3152 * @private 3153 * 3154 * @param {string} [state] 3155 * Can be either `hover` or undefined to set to normal state. 3156 */ 3157 setAllPointsToState(state) {
vendor: 6,275 bytes, lines 3158-3367
3158 this.points.forEach(function (point) { 3159 if (point.setState) { 3160 point.setState(state); 3161 } 3162 }); 3163 } 3164 /** 3165 * Show or hide the series. 3166 * 3167 * @function Highcharts.Series#setVisible 3168 * 3169 * @param {boolean} [visible] 3170 * True to show the series, false to hide. If undefined, the visibility is 3171 * toggled. 3172 * 3173 * @param {boolean} [redraw=true] 3174 * Whether to redraw the chart after the series is altered. If doing more 3175 * operations on the chart, it is a good idea to set redraw to false and 3176 * call {@link Chart#redraw|chart.redraw()} after. 3177 * 3178 * @emits Highcharts.Series#event:hide 3179 * @emits Highcharts.Series#event:show 3180 */ 3181 setVisible(vis, redraw) { 3182 const series = this, chart = series.chart, ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries, oldVisibility = series.visible; 3183 // If called without an argument, toggle visibility 3184 series.visible = 3185 vis = 3186 series.options.visible = 3187 series.userOptions.visible = 3188 typeof vis === 'undefined' ? !oldVisibility : vis; // #5618 3189 const showOrHide = vis ? 'show' : 'hide'; 3190 // Show or hide elements 3191 [ 3192 'group', 3193 'dataLabelsGroup', 3194 'markerGroup', 3195 'tracker', 3196 'tt' 3197 ].forEach((key) => { 3198 series[key]?.[showOrHide](); 3199 }); 3200 // Hide tooltip (#1361) 3201 if (chart.hoverSeries === series || 3202 chart.hoverPoint?.series === series) { 3203 series.onMouseOut(); 3204 } 3205 if (series.legendItem) { 3206 chart.legend.colorizeItem(series, vis); 3207 } 3208 // Rescale or adapt to resized chart 3209 series.isDirty = true; 3210 // In a stack, all other series are affected 3211 if (series.options.stacking) { 3212 chart.series.forEach((otherSeries) => { 3213 if (otherSeries.options.stacking && otherSeries.visible) { 3214 otherSeries.isDirty = true; 3215 } 3216 }); 3217 } 3218 // Show or hide linked series 3219 series.linkedSeries.forEach((otherSeries) => { 3220 otherSeries.setVisible(vis, false); 3221 }); 3222 if (ignoreHiddenSeries) { 3223 chart.isDirtyBox = true; 3224 } 3225 fireEvent(series, showOrHide); 3226 if (redraw !== false) { 3227 chart.redraw(); 3228 } 3229 } 3230 /** 3231 * Show the series if hidden. 3232 * 3233 * @sample highcharts/members/series-hide/ 3234 * Toggle visibility from a button 3235 * 3236 * @function Highcharts.Series#show 3237 * @emits Highcharts.Series#event:show 3238 */ 3239 show() { 3240 this.setVisible(true); 3241 } 3242 /** 3243 * Hide the series if visible. If the 3244 * [chart.ignoreHiddenSeries](https://api.highcharts.com/highcharts/chart.ignoreHiddenSeries) 3245 * option is true, the chart is redrawn without this series. 3246 * 3247 * @sample highcharts/members/series-hide/ 3248 * Toggle visibility from a button 3249 * 3250 * @function Highcharts.Series#hide 3251 * @emits Highcharts.Series#event:hide 3252 */ 3253 hide() { 3254 this.setVisible(false); 3255 } 3256 /** 3257 * Select or unselect the series. This means its 3258 * {@link Highcharts.Series.selected|selected} 3259 * property is set, the checkbox in the legend is toggled and when selected, 3260 * the series is returned by the {@link Highcharts.Chart#getSelectedSeries} 3261 * function. 3262 * 3263 * @sample highcharts/members/series-select/ 3264 * Select a series from a button 3265 * 3266 * @function Highcharts.Series#select 3267 * 3268 * @param {boolean} [selected] 3269 * True to select the series, false to unselect. If undefined, the selection 3270 * state is toggled. 3271 * 3272 * @emits Highcharts.Series#event:select 3273 * @emits Highcharts.Series#event:unselect 3274 */ 3275 select(selected) { 3276 const series = this; 3277 series.selected = 3278 selected = 3279 this.options.selected = (typeof selected === 'undefined' ? 3280 !series.selected : 3281 selected); 3282 if (series.checkbox) { 3283 series.checkbox.checked = selected; 3284 } 3285 fireEvent(series, selected ? 'select' : 'unselect'); 3286 } 3287 /** 3288 * Checks if a tooltip should be shown for a given point. 3289 * 3290 * @private 3291 */ 3292 shouldShowTooltip(plotX, plotY, options = {}) { 3293 options.series = this; 3294 options.visiblePlotOnly = true; 3295 return this.chart.isInsidePlot(plotX, plotY, options); 3296 } 3297 /** 3298 * Draws the legend symbol based on the legendSymbol user option. 3299 * 3300 * @private 3301 */ 3302 drawLegendSymbol(legend, item) { 3303 LegendSymbol[this.options.legendSymbol || 'rectangle'] 3304 ?.call(this, legend, item); 3305 } 3306} 3307Series.defaultOptions = SeriesDefaults; 3308/** 3309 * Registry of all available series types. 3310 * 3311 * @name Highcharts.Series.types 3312 * @type {Highcharts.Dictionary<typeof_Highcharts.Series>} 3313 */ 3314Series.types = SeriesRegistry.seriesTypes; 3315/* * 3316 * 3317 * Static Functions 3318 * 3319 * */ 3320/** 3321 * Registers a series class to be accessible via `Series.types`. 3322 * 3323 * @function Highcharts.Series.registerType 3324 * 3325 * @param {string} seriesType 3326 * The series type as an identifier string in lower case. 3327 * 3328 * @param {Function} SeriesClass 3329 * The series class as a class pattern or a constructor function with 3330 * prototype. 3331 */ 3332Series.registerType = SeriesRegistry.registerSeriesType; 3333extend(Series.prototype, { 3334 axisTypes: ['xAxis', 'yAxis'], 3335 coll: 'series', 3336 colorCounter: 0, 3337 directTouch: false, 3338 invertible: true, 3339 isCartesian: true, 3340 kdAxisArray: ['clientX', 'plotY'], 3341 // Each point's x and y values are stored in this.xData and this.yData: 3342 parallelArrays: ['x', 'y'], 3343 pointClass: Point, 3344 requireSorting: true, 3345 // Requires the data to be sorted: 3346 sorted: true 3347}); 3348/* * 3349 * 3350 * Registry 3351 * 3352 * */ 3353SeriesRegistry.series = Series; 3354/* * 3355 * 3356 * Default Export 3357 * 3358 * */ 3359export default Series; 3360/* * 3361 * 3362 * API Declarations 3363 * 3364 * */ 3365/** 3366 * This is a placeholder type of the possible series options for 3367 * [Highcharts](../highcharts/series), [Highcharts Stock](../highstock/series),
3368 * [Highmaps](../highmaps/series), and [Gantt](../gantt/series). 3369 * 3370 * In TypeScript is this dynamically generated to reference all possible types 3371 * of series options. 3372 * 3373 * @ignore-declaration 3374 * @typedef {Highcharts.SeriesOptions|Highcharts.Dictionary<*>} Highcharts.SeriesOptionsType 3375 */ 3376/** 3377 * Options for `dataSorting`. 3378 * 3379 * @interface Highcharts.DataSortingOptionsObject 3380 * @since 8.0.0 3381 */ /** 3382* Enable or disable data sorting for the series. 3383* @name Highcharts.DataSortingOptionsObject#enabled 3384* @type {boolean|undefined} 3385*/ /** 3386* Whether to allow matching points by name in an update. 3387* @name Highcharts.DataSortingOptionsObject#matchByName 3388* @type {boolean|undefined} 3389*/ /** 3390* Determines what data value should be used to sort by. 3391* @name Highcharts.DataSortingOptionsObject#sortKey 3392* @type {string|undefined} 3393*/ 3394/** 3395 * Function callback when a series has been animated. 3396 * 3397 * @callback Highcharts.SeriesAfterAnimateCallbackFunction 3398 * 3399 * @param {Highcharts.Series} this 3400 * The series where the event occurred. 3401 * 3402 * @param {Highcharts.SeriesAfterAnimateEventObject} event 3403 * Event arguments. 3404 */ 3405/** 3406 * Event information regarding completed animation of a series. 3407 * 3408 * @interface Highcharts.SeriesAfterAnimateEventObject 3409 */ /** 3410* Animated series. 3411* @name Highcharts.SeriesAfterAnimateEventObject#target 3412* @type {Highcharts.Series} 3413*/ /** 3414* Event type. 3415* @name Highcharts.SeriesAfterAnimateEventObject#type 3416* @type {"afterAnimate"} 3417*/ 3418/** 3419 * Function callback when the checkbox next to the series' name in the legend is 3420 * clicked. 3421 * 3422 * @callback Highcharts.SeriesCheckboxClickCallbackFunction 3423 * 3424 * @param {Highcharts.Series} this 3425 * The series where the event occurred. 3426 * 3427 * @param {Highcharts.SeriesCheckboxClickEventObject} event 3428 * Event arguments. 3429 */ 3430/** 3431 * Event information regarding check of a series box. 3432 * 3433 * @interface Highcharts.SeriesCheckboxClickEventObject 3434 */ /** 3435* Whether the box has been checked. 3436* @name Highcharts.SeriesCheckboxClickEventObject#checked 3437* @type {boolean} 3438*/ /** 3439* Related series. 3440* @name Highcharts.SeriesCheckboxClickEventObject#item 3441* @type {Highcharts.Series} 3442*/ /** 3443* Related series. 3444* @name Highcharts.SeriesCheckboxClickEventObject#target 3445* @type {Highcharts.Series} 3446*/ /** 3447* Event type. 3448* @name Highcharts.SeriesCheckboxClickEventObject#type 3449* @type {"checkboxClick"} 3450*/ 3451/** 3452 * Function callback when a series is clicked. Return false to cancel toogle 3453 * actions. 3454 * 3455 * @callback Highcharts.SeriesClickCallbackFunction 3456 * 3457 * @param {Highcharts.Series} this 3458 * The series where the event occurred. 3459 * 3460 * @param {Highcharts.SeriesClickEventObject} event 3461 * Event arguments. 3462 */ 3463/** 3464 * Common information for a click event on a series. 3465 * 3466 * @interface Highcharts.SeriesClickEventObject 3467 * @extends global.Event 3468 */ /** 3469* Nearest point on the graph. 3470* @name Highcharts.SeriesClickEventObject#point 3471* @type {Highcharts.Point} 3472*/ 3473/** 3474 * Gets fired when the series is hidden after chart generation time, either by 3475 * clicking the legend item or by calling `.hide()`. 3476 * 3477 * @callback Highcharts.SeriesHideCallbackFunction 3478 * 3479 * @param {Highcharts.Series} this 3480 * The series where the event occurred. 3481 * 3482 * @param {global.Event} event 3483 * The event that occurred. 3484 */ 3485/** 3486 * The SVG value used for the `stroke-linecap` and `stroke-linejoin` of a line 3487 * graph. 3488 * 3489 * @typedef {"butt"|"round"|"square"|string} Highcharts.SeriesLinecapValue 3490 */ 3491/** 3492 * Gets fired when the legend item belonging to the series is clicked. The 3493 * default action is to toggle the visibility of the series. This can be 3494 * prevented by returning `false` or calling `event.preventDefault()`. 3495 * 3496 * **Note:** This option is deprecated in favor of 3497 * Highcharts.LegendItemClickCallbackFunction. 3498 * 3499 * @deprecated 11.4.4 3500 * @callback Highcharts.SeriesLegendItemClickCallbackFunction 3501 * 3502 * @param {Highcharts.Series} this 3503 * The series where the event occurred. 3504 * 3505 * @param {Highcharts.SeriesLegendItemClickEventObject} event 3506 * The event that occurred. 3507 */ 3508/** 3509 * Information about the event. 3510 *
3511 * **Note:** This option is deprecated in favor of 3512 * Highcharts.LegendItemClickEventObject. 3513 * 3514 * @deprecated 11.4.4 3515 * @interface Highcharts.SeriesLegendItemClickEventObject 3516 */ /** 3517* Related browser event. 3518* @name Highcharts.SeriesLegendItemClickEventObject#browserEvent 3519* @type {global.PointerEvent} 3520*/ /** 3521* Prevent the default action of toggle the visibility of the series. 3522* @name Highcharts.SeriesLegendItemClickEventObject#preventDefault 3523* @type {Function} 3524*/ /** 3525* Related series. 3526* @name Highcharts.SeriesCheckboxClickEventObject#target 3527* @type {Highcharts.Series} 3528*/ /** 3529* Event type. 3530* @name Highcharts.SeriesCheckboxClickEventObject#type 3531* @type {"checkboxClick"} 3532*/ 3533/** 3534 * Gets fired when the mouse leaves the graph. 3535 * 3536 * @callback Highcharts.SeriesMouseOutCallbackFunction 3537 * 3538 * @param {Highcharts.Series} this 3539 * Series where the event occurred. 3540 * 3541 * @param {global.PointerEvent} event 3542 * Event that occurred. 3543 */ 3544/** 3545 * Gets fired when the mouse enters the graph. 3546 * 3547 * @callback Highcharts.SeriesMouseOverCallbackFunction 3548 * 3549 * @param {Highcharts.Series} this 3550 * Series where the event occurred. 3551 * 3552 * @param {global.PointerEvent} event 3553 * Event that occurred. 3554 */ 3555/** 3556 * Translation and scale for the plot area of a series. 3557 * 3558 * @interface Highcharts.SeriesPlotBoxObject 3559 */ /** 3560* @name Highcharts.SeriesPlotBoxObject#scaleX 3561* @type {number} 3562*/ /** 3563* @name Highcharts.SeriesPlotBoxObject#scaleY 3564* @type {number} 3565*/ /** 3566* @name Highcharts.SeriesPlotBoxObject#translateX 3567* @type {number} 3568*/ /** 3569* @name Highcharts.SeriesPlotBoxObject#translateY 3570* @type {number} 3571*/ 3572/** 3573 * Gets fired when the series is shown after chart generation time, either by 3574 * clicking the legend item or by calling `.show()`. 3575 * 3576 * @callback Highcharts.SeriesShowCallbackFunction 3577 * 3578 * @param {Highcharts.Series} this 3579 * Series where the event occurred. 3580 * 3581 * @param {global.Event} event 3582 * Event that occurred. 3583 */ 3584/** 3585 * Possible key values for the series state options. 3586 * 3587 * @typedef {"hover"|"inactive"|"normal"|"select"} Highcharts.SeriesStateValue 3588 */ 3589''; // Detach doclets above 3590/* * 3591 * 3592 * API Options 3593 * 3594 * */ 3595/** 3596 * Series options for specific data and the data itself. In TypeScript you 3597 * have to cast the series options to specific series types, to get all 3598 * possible options for a series. 3599 * 3600 * @example 3601 * // TypeScript example 3602 * Highcharts.chart('container', { 3603 * series: [{ 3604 * color: '#06C', 3605 * data: [[0, 1], [2, 3]] 3606 * } as Highcharts.SeriesLineOptions ] 3607 * }); 3608 * 3609 * @type {Array<*>} 3610 * @apioption series 3611 */ 3612/** 3613 * An id for the series. This can be used after render time to get a pointer 3614 * to the series object through `chart.get()`. 3615 * 3616 * @sample {highcharts} highcharts/plotoptions/series-id/ 3617 * Get series by id 3618 * 3619 * @type {string} 3620 * @since 1.2.0 3621 * @apioption series.id 3622 */ 3623/** 3624 * The index of the series in the chart, affecting the internal index in the 3625 * `chart.series` array, the visible Z index as well as the order in the 3626 * legend. 3627 * 3628 * @type {number} 3629 * @since 2.3.0 3630 * @apioption series.index 3631 */ 3632/** 3633 * The sequential index of the series in the legend. 3634 * 3635 * @see [legend.reversed](#legend.reversed), 3636 * [yAxis.reversedStacks](#yAxis.reversedStacks) 3637 * 3638 * @sample {highcharts|highstock} highcharts/series/legendindex/ 3639 * Legend in opposite order 3640 * 3641 * @type {number} 3642 * @apioption series.legendIndex 3643 */ 3644/** 3645 * The name of the series as shown in the legend, tooltip etc. 3646 * 3647 * @sample {highcharts} highcharts/series/name/ 3648 * Series name 3649 * @sample {highmaps} maps/demo/category-map/ 3650 * Series name 3651 * 3652 * @type {string} 3653 * @apioption series.name 3654 */ 3655/** 3656 * This option allows grouping series in a stacked chart. The stack option 3657 * can be a string or anything else, as long as the grouped series' stack 3658 * options match each other after conversion into a string. 3659 * 3660 * @sample {highcharts} highcharts/series/stack/ 3661 * Stacked and grouped columns 3662 * @sample {highcharts} highcharts/series/stack-centerincategory/ 3663 * Stacked and grouped, centered in category 3664 * 3665 * @type {number|string} 3666 * @since 2.1 3667 * @product highcharts highstock 3668 * @apioption series.stack 3669 */ 3670/** 3671 * The type of series, for example `line` or `column`. By default, the 3672 * series type is inherited from [chart.type](#chart.type), so unless the 3673 * chart is a combination of series types, there is no need to set it on the 3674 * series level. 3675 * 3676 * @sample {highcharts} highcharts/series/type/ 3677 * Line and column in the same chart
3678 * @sample highcharts/series/type-dynamic/ 3679 * Dynamic types with button selector 3680 * @sample {highmaps} maps/demo/mapline-mappoint/ 3681 * Multiple types in the same map 3682 * 3683 * @type {string} 3684 * @apioption series.type 3685 */ 3686/** 3687 * When using dual or multiple x axes, this number defines which xAxis the 3688 * particular series is connected to. It refers to either the 3689 * {@link #xAxis.id|axis id} 3690 * or the index of the axis in the xAxis array, with 0 being the first. 3691 * 3692 * @type {number|string} 3693 * @default 0 3694 * @product highcharts highstock 3695 * @apioption series.xAxis 3696 */ 3697/** 3698 * When using dual or multiple y axes, this number defines which yAxis the 3699 * particular series is connected to. It refers to either the 3700 * {@link #yAxis.id|axis id} 3701 * or the index of the axis in the yAxis array, with 0 being the first. 3702 * 3703 * @sample {highcharts} highcharts/series/yaxis/ 3704 * Apply the column series to the secondary Y axis 3705 * 3706 * @type {number|string} 3707 * @default 0 3708 * @product highcharts highstock 3709 * @apioption series.yAxis 3710 */ 3711/** 3712 * Define the visual z index of the series. 3713 * 3714 * @sample {highcharts} highcharts/plotoptions/series-zindex-default/ 3715 * With no z index, the series defined last are on top 3716 * @sample {highcharts} highcharts/plotoptions/series-zindex/ 3717 * With a z index, the series with the highest z index is on top 3718 * @sample {highstock} highcharts/plotoptions/series-zindex-default/ 3719 * With no z index, the series defined last are on top 3720 * @sample {highstock} highcharts/plotoptions/series-zindex/ 3721 * With a z index, the series with the highest z index is on top 3722 * 3723 * @type {number} 3724 * @product highcharts highstock 3725 * @apioption series.zIndex 3726 */ 3727''; // Include precedent doclets in transpiled
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.