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 AST from '../Renderer/HTML/AST.js'; 12import A from '../Animation/AnimationUtilities.js'; 13const { animObject } = A; 14import D from '../Defaults.js'; 15const { defaultOptions } = D; 16import F from '../Templating.js'; 17const { format } = F; 18import U from '../Utilities.js'; 19const { addEvent, crisp, erase, extend, fireEvent, getNestedProperty, isArray, isFunction, isNumber, isObject, merge, pick, syncTimeout, removeEvent, uniqueKey } = U; 20/* eslint-disable no-invalid-this, valid-jsdoc */ 21/* * 22 * 23 * Class 24 * 25 * */ 26/** 27 * The Point object. The point objects are generated from the `series.data` 28 * configuration objects or raw numbers. They can be accessed from the 29 * `Series.points` array. Other ways to instantiate points are through {@link 30 * Highcharts.Series#addPoint} or {@link Highcharts.Series#setData}. 31 * 32 * @class 33 * @name Highcharts.Point 34 */ 35class Point { 36 /** 37 * For categorized axes this property holds the category name for the 38 * point. For other axes it holds the X value. 39 * 40 * @name Highcharts.Point#category 41 * @type {number|string} 42 */ 43 /** 44 * The name of the point. The name can be given as the first position of the 45 * point configuration array, or as a `name` property in the configuration: 46 * 47 * @example 48 * // Array config 49 * data: [ 50 * ['John', 1], 51 * ['Jane', 2] 52 * ] 53 * 54 * // Object config 55 * data: [{ 56 * name: 'John', 57 * y: 1 58 * }, { 59 * name: 'Jane', 60 * y: 2 61 * }] 62 * 63 * @name Highcharts.Point#name 64 * @type {string} 65 */ 66 /** 67 * The point's name if it is defined, or its category in case of a category, 68 * otherwise the x value. Convenient for tooltip and data label formatting. 69 * 70 * @name Highcharts.Point#key 71 * @type {number|string} 72 */ 73 /** 74 * The point's options as applied in the initial configuration, or 75 * extended through `Point.update`. 76 * 77 * In TypeScript you have to extend `PointOptionsObject` via an 78 * additional interface to allow custom data options: 79 * 80 * ``` 81 * declare interface PointOptionsObject { 82 * customProperty: string; 83 * } 84 * ``` 85 * 86 * @name Highcharts.Point#options 87 * @type {Highcharts.PointOptionsObject} 88 */ 89 /** 90 * The percentage for points in a stacked series, pies or gauges. 91 * 92 * @name Highcharts.Point#percentage 93 * @type {number|undefined} 94 */ 95 /** 96 * The series object associated with the point. 97 * 98 * @name Highcharts.Point#series 99 * @type {Highcharts.Series} 100 */ 101 /** 102 * The attributes of the rendered SVG shape like in `column` or `pie` 103 * series. 104 * 105 * @readonly 106 * @name Highcharts.Point#shapeArgs 107 * @type {Readonly<Highcharts.SVGAttributes>|undefined} 108 */ 109 /** 110 * The total of values in either a stack for stacked series, or a pie in a 111 * pie series. 112 * 113 * @name Highcharts.Point#total 114 * @type {number|undefined} 115 */ 116 /** 117 * For certain series types, like pie charts, where individual points can 118 * be shown or hidden. 119 * 120 * @name Highcharts.Point#visible 121 * @type {boolean} 122 * @default true 123 */ 124 /* * 125 * 126 * Functions 127 * 128 * */ 129 /** 130 * Animate SVG elements associated with the point. 131 * 132 * @private 133 * @function Highcharts.Point#animateBeforeDestroy 134 */ 135 animateBeforeDestroy() { 136 const point = this, animateParams = { x: point.startXPos, opacity: 0 }, graphicalProps = point.getGraphicalProps(); 137 graphicalProps.singular.forEach(function (prop) { 138 const isDataLabel = prop === 'dataLabel'; 139 point[prop] = point[prop].animate(isDataLabel ? { 140 x: point[prop].startXPos, 141 y: point[prop].startYPos, 142 opacity: 0 143 } : animateParams); 144 }); 145 graphicalProps.plural.forEach(function (plural) { 146 point[plural].forEach(function (item) { 147 if (item.element) { 148 item.animate(extend({ x: point.startXPos }, (item.startYPos ? { 149 x: item.startXPos, 150 y: item.startYPos 151 } : {}))); 152 } 153 }); 154 }); 155 } 156 /** 157 * Apply the options containing the x and y data and possible some extra 158 * properties. Called on point init or from point.update. 159 * 160 * @private 161 * @function Highcharts.Point#applyOptions 162 * 163 * @param {Highcharts.PointOptionsType} options 164 * The point options as defined in series.data. 165 * 166 * @param {number} [x] 167 * Optionally, the x value. 168 * 169 * @return {Highcharts.Point} 170 * The Point instance. 171 */ 172 applyOptions(options, x) { 173 const point = this, series = point.series, pointValKey = series.options.pointV
173alKey || series.pointValKey; 174 options = Point.prototype.optionsToObject.call(this, options); 175 // Copy options directly to point 176 extend(point, options); 177 point.options = point.options ? 178 extend(point.options, options) : 179 options; 180 // Since options are copied into the Point instance, some accidental 181 // options must be shielded (#5681) 182 if (options.group) { 183 delete point.group; 184 } 185 if (options.dataLabels) { 186 delete point.dataLabels; 187 } 188 /** 189 * The y value of the point. 190 * @name Highcharts.Point#y 191 * @type {number|undefined} 192 */ 193 // For higher dimension series types. For instance, for ranges, point.y 194 // is mapped to point.low. 195 if (pointValKey) { 196 point.y = Point.prototype.getNestedProperty.call(point, pointValKey); 197 } 198 // The point is initially selected by options (#5777) 199 if (point.selected) { 200 point.state = 'select'; 201 } 202 /** 203 * The x value of the point. 204 * @name Highcharts.Point#x 205 * @type {number} 206 */ 207 // If no x is set by now, get auto incremented value. All points must 208 // have an x value, however the y value can be null to create a gap in 209 // the series 210 if ('name' in point && 211 typeof x === 'undefined' && 212 series.xAxis && 213 series.xAxis.hasNames) { 214 point.x = series.xAxis.nameToX(point); 215 } 216 if (typeof point.x === 'undefined' && series) { 217 point.x = x ?? series.autoIncrement(); 218 } 219 else if (isNumber(options.x) && series.options.relativeXValue) { 220 point.x = series.autoIncrement(options.x); 221 // If x is a string, try to parse it to a datetime 222 } 223 else if (typeof point.x === 'string') { 224 x ?? (x = series.chart.time.parse(point.x)); 225 if (isNumber(x)) { 226 point.x = x; 227 } 228 } 229 point.isNull = this.isValid && !this.isValid(); 230 point.formatPrefix = point.isNull ? 'null' : 'point'; // #9233, #10874 231 return point; 232 } 233 /** 234 * Destroy a point to clear memory. Its reference still stays in 235 * `series.data`. 236 * 237 * @private 238 * @function Highcharts.Point#destroy 239 */ 240 destroy() { 241 if (!this.destroyed) { 242 const point = this, series = point.series, chart = series.chart, dataSorting = series.options.dataSorting, hoverPoints = chart.hoverPoints, globalAnimation = point.series.chart.renderer.globalAnimation, animation = animObject(globalAnimation); 243 /** 244 * Allow to call after animation. 245 * @private 246 */ 247 const destroyPoint = () => { 248 // Remove all events and elements 249 if (point.graphic || 250 point.graphics || 251 point.dataLabel || 252 point.dataLabels) { 253 removeEvent(point); 254 point.destroyElements(); 255 } 256 for (const prop in point) { // eslint-disable-line guard-for-in 257 delete point[prop]; 258 } 259 }; 260 if (point.legendItem) { 261 // Pies have legend items 262 chart.legend.destroyItem(point); 263 } 264 if (hoverPoints) { 265 point.setState(); 266 erase(hoverPoints, point); 267 if (!hoverPoints.length) { 268 chart.hoverPoints = null; 269 } 270 } 271 if (point === chart.hoverPoint) { 272 point.onMouseOut(); 273 } 274 // Remove properties after animation 275 if (!dataSorting || !dataSorting.enabled) { 276 destroyPoint(); 277 } 278 else { 279 this.animateBeforeDestroy(); 280 syncTimeout(destroyPoint, animation.duration); 281 } 282 chart.pointCount--; 283 } 284 this.destroyed = true; 285 } 286 /** 287 * Destroy SVG elements associated with the point. 288 * 289 * @private 290 * @function Highcharts.Point#destroyElements 291 * @param {Highcharts.Dictionary<number>} [kinds] 292 */ 293 destroyElements(kinds) { 294 const point = this, props = point.getGraphicalProps(kinds); 295 props.singular.forEach(function (prop) { 296 point[prop] = point[prop].destroy(); 297 }); 298 props.plural.forEach(function (plural) { 299 point[plural].forEach(function (item) { 300 if (item && item.element) { 301 item.destroy(); 302 } 303 }); 304 delete point[plural]; 305 }); 306 } 307 /** 308 * Fire an event on the Point object. 309 * 310 * @private 311 * @function Highcharts.Point#firePointEvent 312 * 313 * @param {string} eventType 314 * Type of the event. 315 * 316 * @param {Highcharts.Dictionary<any>|Event} [eventArgs] 317 * Additional event arguments. 318 * 319 * @param {Highcharts.EventCallbackFunction<Highcharts.Point>|Function} [defaultFunction] 320 * Default event handler. 321 * 322 * @emits Highcharts.Point#event:* 323 */ 324 firePointEvent(eventType, eventArgs, defaultFunction) { 325 const point = this, series = this.series, seriesOptions = series.options; 326 // Load event handlers on demand to save time on mouseover/out 327 point.manageEvent(eventType); 328 // Add default handler if in selection mode 329 if (eventType === 'click' && seriesOptions.allowPointSelect) { 330 defaultFunction = function (event) { 331 // Control key is for Windows, meta (= Cmd key) for Mac, Shift 332 // for Opera. 333 if (!point.destroyed && point.select) { // #2911, #19075 334 point.select(null, event.ctrlKey || event.metaKey || event.shiftKey); 335 } 336 }; 337 } 338 fireEvent(point, eventType, eventArgs, defaultFunction); 339 } 340 /** 341 * Get the CSS class names for individual points. Used internally where the 342 * returned value is set on every point. 343 * 344 * @function Highcharts.Point#getClassName 345 * 346 * @return {string} 347 * The class names. 348 */ 349 getClassName() { 350 const point = this; 351 return 'highcharts-point' + 352 (point.selected ? ' highcharts-point-select' : '') + 353 (point.negative ? ' highcharts-negative' : '') + 354 (point.isNull ? ' highcharts-null-point' : '') + 355 (typeof point.colorIndex !== 'undefined' ? 356 ' highcharts-color-' + point.colorIndex : '') + 357 (point.options.className ? ' ' + point.options.className : '') + 358 (point.zone && point.zone.className ? ' ' + 359 point.zone.className.replace('highcharts-negative', '') : ''); 360 } 361 /** 362 * Get props of all existing graphical point elements. 363 * 364 * @private 365 * @function Highcharts.Point#getGraphicalProps 366 */ 367 getGraphicalProps(kinds) { 368 const point = this, props = [], graphicalProps = { singular: [], plural: [] }; 369 let prop, i; 370 kinds = kinds || { graphic: 1, dataLabel: 1 }; 371 if (kinds.graphic) { 372 props.push('graphic', 'connector' // Used by dumbbell 373 ); 374 } 375 if (kinds.dataLabel) { 376 props.push('dataLabel', 'dataLabelPath', 'dataLabelUpper'); 377 } 378 i = props.length; 379 while (i--) { 380 prop = props[i]; 381 if (point[prop]) { 382 graphicalProps.singular.push(prop); 383 } 384 } 385 [ 386 'graphic', 387 'dataLabel' 388 ].forEach(function (prop) { 389 const plural = prop + 's'; 390 if (kinds[prop] && point[plural]) { 391 graphicalProps.plural.push(plural); 392 } 393 }); 394 return graphicalProps; 395 } 396 /** 397 * Returns the value of the point property for a given value. 398 * @private 399 */ 400 getNestedProperty(key) { 401 if (!key) { 402 return; 403 } 404 if (key.indexOf('custom.') === 0) { 405 return getNestedProperty(key, this.options); 406 } 407 return this[key]; 408 } 409 /**
410 * In a series with `zones`, return the zone that the point belongs to. 411 * 412 * @function Highcharts.Point#getZone 413 * 414 * @return {Highcharts.SeriesZonesOptionsObject} 415 * The zone item. 416 */ 417 getZone() { 418 const series = this.series, zones = series.zones, zoneAxis = series.zoneAxis || 'y'; 419 let zone, i = 0; 420 zone = zones[i]; 421 while (this[zoneAxis] >= zone.value) { 422 zone = zones[++i]; 423 } 424 // For resetting or reusing the point (#8100) 425 if (!this.nonZonedColor) { 426 this.nonZonedColor = this.color; 427 } 428 if (zone && zone.color && !this.options.color) { 429 this.color = zone.color; 430 } 431 else { 432 this.color = this.nonZonedColor; 433 } 434 return zone; 435 } 436 /** 437 * Utility to check if point has new shape type. Used in column series and 438 * all others that are based on column series. 439 * @private 440 */ 441 hasNewShapeType() { 442 const point = this; 443 const oldShapeType = point.graphic && 444 (point.graphic.symbolName || point.graphic.element.nodeName); 445 return oldShapeType !== this.shapeType; 446 } 447 /** 448 * Initialize the point. Called internally based on the `series.data` 449 * option. 450 * 451 * @function Highcharts.Point#init 452 * 453 * @param {Highcharts.Series} series 454 * The series object containing this point. 455 * 456 * @param {Highcharts.PointOptionsType} options 457 * The data in either number, array or object format. 458 * 459 * @param {number} [x] 460 * Optionally, the X value of the point. 461 * 462 * @return {Highcharts.Point} 463 * The Point instance. 464 * 465 * @emits Highcharts.Point#event:afterInit 466 */ 467 constructor(series, options, x) { 468 this.formatPrefix = 'point'; 469 this.visible = true; 470 // For tooltip and data label formatting 471 this.point = this; 472 this.series = series; 473 this.applyOptions(options, x); 474 // Add a unique ID to the point if none is assigned 475 this.id ?? (this.id = uniqueKey()); 476 this.resolveColor(); 477 series.chart.pointCount++; 478 fireEvent(this, 'afterInit'); 479 } 480 /** 481 * Determine if point is valid. 482 * @private 483 * @function Highcharts.Point#isValid 484 */ 485 isValid() { 486 return ((isNumber(this.x) || 487 this.x instanceof Date) && 488 isNumber(this.y)); 489 } 490 /** 491 * Transform number or array configs into objects. Also called for object 492 * configs. Used internally to unify the different configuration formats for 493 * points. For example, a simple number `10` in a line series will be 494 * transformed to `{ y: 10 }`, and an array config like `[1, 10]` in a 495 * scatter series will be transformed to `{ x: 1, y: 10 }`. 496 * 497 * @function Highcharts.Point#optionsToObject 498 * 499 * @param {Highcharts.PointOptionsType} options 500 * Series data options. 501 * 502 * @return {Highcharts.Dictionary<*>} 503 * Transformed point options. 504 */ 505 optionsToObject(options) { 506 const series = this.series, keys = series.options.keys, pointArrayMap = keys || series.pointArrayMap || ['y'], valueCount = pointArrayMap.length; 507 let ret = {}, firstItemType, i = 0, j = 0; 508 if (isNumber(options) || options === null) { 509 ret[pointArrayMap[0]] = options; 510 } 511 else if (isArray(options)) {
512 // With leading x value 513 if (!keys && options.length > valueCount) { 514 firstItemType = typeof options[0]; 515 if (firstItemType === 'string') { 516 if (series.xAxis?.dateTime) { 517 ret.x = series.chart.time.parse(options[0]); 518 } 519 else { 520 ret.name = options[0]; 521 } 522 } 523 else if (firstItemType === 'number') { 524 ret.x = options[0]; 525 } 526 i++; 527 } 528 while (j < valueCount) { 529 // Skip undefined positions for keys 530 if (!keys || typeof options[i] !== 'undefined') { 531 if (pointArrayMap[j].indexOf('.') > 0) { 532 // Handle nested keys, e.g. ['color.pattern.image'] 533 // Avoid function call unless necessary. 534 Point.prototype.setNestedProperty(ret, options[i], pointArrayMap[j]); 535 } 536 else { 537 ret[pointArrayMap[j]] = options[i]; 538 } 539 } 540 i++; 541 j++; 542 } 543 } 544 else if (typeof options === 'object') { 545 ret = options; 546 // This is the fastest way to detect if there are individual point 547 // dataLabels that need to be considered in drawDataLabels. These 548 // can only occur in object configs. 549 if (options.dataLabels) { 550 // Override the prototype function to always return true, 551 // regardless of whether data labels are enabled series-wide 552 series.hasDataLabels = () => true; 553 } 554 // Same approach as above for markers 555 if (options.marker) { 556 series._hasPointMarkers = true; 557 } 558 } 559 return ret; 560 } 561 /** 562 * Get the pixel position of the point relative to the plot area. 563 * @function Highcharts.Point#pos 564 * 565 * @sample highcharts/point/position 566 * Get point's position in pixels. 567 * 568 * @param {boolean} chartCoordinates 569 * If true, the returned position is relative to the full chart area. 570 * If false, it is relative to the plot area determined by the axes. 571 * 572 * @param {number|undefined} plotY 573 * A custom plot y position to be computed. Used internally for some 574 * series types that have multiple `y` positions, like area range (low 575 * and high values). 576 * 577 * @return {Array<number>|undefined} 578 * Coordinates of the point if the point exists. 579 */ 580 pos(chartCoordinates, plotY = this.plotY) { 581 if (!this.destroyed) { 582 const { plotX, series } = this, { chart, xAxis, yAxis } = series; 583 let posX = 0, posY = 0; 584 if (isNumber(plotX) && isNumber(plotY)) { 585 if (chartCoordinates) { 586 posX = xAxis ? xAxis.pos : chart.plotLeft; 587 posY = yAxis ? yAxis.pos : chart.plotTop; 588 } 589 return chart.inverted && xAxis && yAxis ? 590 [yAxis.len - plotY + posY, xAxis.len - plotX + posX] : 591 [plotX + posX, plotY + posY]; 592 } 593 } 594 } 595 /** 596 * @private 597 * @function Highcharts.Point#resolveColor 598 */ 599 resolveColor() { 600 const series = this.series, optionsChart = series.chart.options.chart, styledMode = series.chart.styledMode; 601 let color, colors, colorCount = optionsChart.colorCount, colorIndex; 602 // Remove points nonZonedColor for later recalculation 603 delete this.nonZonedColor; 604 if (series.options.colorByPoint) { 605 if (!styledMode) { 606 colors = series.options.colors || series.chart.options.colors; 607 color = colors[series.colorCounter]; 608 colorCount = colors.length; 609 } 610 colorIndex = series.colorCounter; 611 series.colorCounter++; 612 // Loop back to zero 613 if (series.colorCounter === colorCount) { 614 series.colorCounter = 0; 615 } 616 } 617 else { 618 if (!styledMode) { 619 color = series.color; 620 } 621 colorIndex = series.colorIndex; 622 } 623 /** 624 * The point's current color index, used in styled mode instead of 625 * `color`. The color index is inserted in class names used for styling. 626 * 627 * @name Highcharts.Point#colorIndex 628 * @type {number|undefined} 629 */ 630 this.colorIndex = pick(this.options.colorIndex, colorIndex); 631 /** 632 * The point's current color. 633 * 634 * @name Highcharts.Point#color 635 * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject|undefined} 636 */ 637 this.color = pick(this.options.color, color); 638 } 639 /** 640 * Set a value in an object, on the property defined by key. The key 641 * supports nested properties using dot notation. The function modifies the 642 * input object and does not make a copy. 643 * 644 * @function Highcharts.Point#setNestedProperty<T> 645 * 646 * @param {T} object 647 * The object to set the value on. 648 * 649 * @param {*} value 650 * The value to set. 651 * 652 * @param {string} key 653 * Key to the property to set. 654 * 655 * @return {T} 656 * The modified object. 657 */ 658 setNestedProperty(object, value, key) { 659 const nestedKeys = key.split('.'); 660 nestedKeys.reduce(function (result, key, i, arr) { 661 const isLastKey = arr.length - 1 === i; 662 result[key] = (isLastKey ? 663 value : 664 isObject(result[key], true) ? 665 result[key] : 666 {}); 667 return result[key]; 668 }, object); 669 return object; 670 } 671 shouldDraw() { 672 return !this.isNull; 673 } 674 /** 675 * Extendable method for formatting each point's tooltip line. 676 * 677 * @function Highcharts.Point#tooltipFormatter 678 * 679 * @param {string} pointFormat 680 * The point format. 681 * 682 * @return {string} 683 * A string to be concatenated in to the common tooltip text. 684 */ 685 tooltipFormatter(pointFormat) { 686 // Insert options for valueDecimals, valuePrefix, and valueSuffix 687 const { chart, pointArrayMap = ['y'], tooltipOptions } = this.series, { valueDecimals = '', valuePrefix = '', valueSuffix = '' } = tooltipOptions; 688 // Replace default point style with class name 689 if (chart.styledMode) { 690 pointFormat = chart.tooltip?.styledModeFormat(pointFormat) || 691 pointFormat; 692 } 693 // Loop over the point array map and replace unformatted values with 694 // sprintf formatting markup
695 pointArrayMap.forEach((key) => { 696 key = '{point.' + key; // Without the closing bracket 697 if (valuePrefix || valueSuffix) { 698 pointFormat = pointFormat.replace(RegExp(key + '}', 'g'), valuePrefix + key + '}' + valueSuffix); 699 } 700 pointFormat = pointFormat.replace(RegExp(key + '}', 'g'), key + ':,.' + valueDecimals + 'f}'); 701 }); 702 return format(pointFormat, this, chart); 703 } 704 /** 705 * Update point with new options (typically x/y data) and optionally redraw 706 * the series. 707 * 708 * @sample highcharts/members/point-update-column/ 709 * Update column value 710 * @sample highcharts/members/point-update-pie/ 711 * Update pie slice 712 * @sample maps/members/point-update/ 713 * Update map area value in Highmaps 714 * 715 * @function Highcharts.Point#update 716 * 717 * @param {Highcharts.PointOptionsType} options 718 * The point options. Point options are handled as described under 719 * the `series.type.data` item for each series type. For example 720 * for a line series, if options is a single number, the point will 721 * be given that number as the marin y value. If it is an array, it 722 * will be interpreted as x and y values respectively. If it is an 723 * object, advanced options are applied. 724 * 725 * @param {boolean} [redraw=true] 726 * Whether to redraw the chart after the point is updated. If doing 727 * more operations on the chart, it is best practice to set 728 * `redraw` to false and call `chart.redraw()` after. 729 * 730 * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] 731 * Whether to apply animation, and optionally animation 732 * configuration. 733 * 734 * @emits Highcharts.Point#event:update 735 */ 736 update(options, redraw, animation, runEvent) { 737 const point = this, series = point.series, graphic = point.graphic, chart = series.chart, seriesOptions = series.options; 738 let i; 739 redraw = pick(redraw, true); 740 /** 741 * @private 742 */ 743 function update() { 744 point.applyOptions(options); 745 // Update visuals, #4146 746 // Handle mock graphic elements for a11y, #12718 747 const hasMockGraphic = graphic && point.hasMockGraphic; 748 const shouldDestroyGraphic = point.y === null ? 749 !hasMockGraphic : 750 hasMockGraphic; 751 if (graphic && shouldDestroyGraphic) { 752 point.graphic = graphic.destroy(); 753 delete point.hasMockGraphic; 754 } 755 if (isObject(options, true)) { 756 // Destroy so we can get new elements 757 if (graphic && graphic.element) { 758 // "null" is also a valid symbol 759 if (options && 760 options.marker && 761 typeof options.marker.symbol !== 'undefined') { 762 point.graphic = graphic.destroy(); 763 } 764 } 765 if (options?.dataLabels && point.dataLabel) { 766 point.dataLabel = point.dataLabel.destroy(); // #2468 767 } 768 } 769 // Record changes in the data table 770 i = point.index; 771 const row = {}; 772 for (const key of series.dataColumnKeys()) { 773 row[key] = point[key]; 774 } 775 series.dataTable.setRow(row, i); 776 // Record the options to options.data. If the old or the new config 777 // is an object, use point options, otherwise use raw options 778 // (#4701, #4916). 779 seriesOptions.data[i] = (isObject(seriesOptions.data[i], true) || 780 isObject(options, true)) ? 781 point.options : 782 pick(options, seriesOptions.data[i]); 783 // Redraw 784 series.isDirty = series.isDirtyData = true; 785 if (!series.fixedBox && series.hasCartesianSeries) { // #1906, #2320 786 chart.isDirtyBox = true; 787 }
788 if (seriesOptions.legendType === 'point') { // #1831, #1885 789 chart.isDirtyLegend = true; 790 } 791 if (redraw) { 792 chart.redraw(animation); 793 } 794 } 795 // Fire the event with a default handler of doing the update 796 if (runEvent === false) { // When called from setData 797 update(); 798 } 799 else { 800 point.firePointEvent('update', { options: options }, update); 801 } 802 } 803 /** 804 * Remove a point and optionally redraw the series and if necessary the axes 805 * 806 * @sample highcharts/plotoptions/series-point-events-remove/ 807 * Remove point and confirm 808 * @sample highcharts/members/point-remove/ 809 * Remove pie slice 810 * @sample maps/members/point-remove/ 811 * Remove selected points in Highmaps 812 * 813 * @function Highcharts.Point#remove 814 * 815 * @param {boolean} [redraw=true] 816 * Whether to redraw the chart or wait for an explicit call. When 817 * doing more operations on the chart, for example running 818 * `point.remove()` in a loop, it is best practice to set `redraw` 819 * to false and call `chart.redraw()` after. 820 * 821 * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=false] 822 * Whether to apply animation, and optionally animation 823 * configuration. 824 */ 825 remove(redraw, animation) { 826 this.series.removePoint(this.series.data.indexOf(this), redraw, animation); 827 } 828 /** 829 * Toggle the selection status of a point. 830 * 831 * @see Highcharts.Chart#getSelectedPoints 832 * 833 * @sample highcharts/members/point-select/ 834 * Select a point from a button 835 * @sample highcharts/members/point-select-lasso/ 836 * Lasso selection 837 * @sample highcharts/chart/events-selection-points/ 838 * Rectangle selection 839 * @sample maps/series/data-id/ 840 * Select a point in Highmaps 841 * 842 * @function Highcharts.Point#select 843 * 844 * @param {boolean} [selected] 845 * When `true`, the point is selected. When `false`, the point is 846 * unselected. When `null` or `undefined`, the selection state is toggled. 847 * 848 * @param {boolean} [accumulate=false] 849 * When `true`, the selection is added to other selected points. 850 * When `false`, other selected points are deselected. Internally in 851 * Highcharts, when 852 * [allowPointSelect](https://api.highcharts.com/highcharts/plotOptions.series.allowPointSelect) 853 * is `true`, selected points are accumulated on Control, Shift or Cmd 854 * clicking the point. 855 * 856 * @emits Highcharts.Point#event:select 857 * @emits Highcharts.Point#event:unselect 858 */ 859 select(selected, accumulate) { 860 const point = this, series = point.series, chart = series.chart; 861 selected = pick(selected, !point.selected); 862 this.selectedStaging = selected; 863 // Fire the event with the default handler 864 point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () { 865 /** 866 * Whether the point is selected or not. 867 * 868 * @see Point#select 869 * @see Chart#getSelectedPoints 870 * 871 * @name Highcharts.Point#selected 872 * @type {boolean} 873 */ 874 point.selected = point.options.selected = selected; 875 series.options.data[series.data.indexOf(point)] = 876 point.options; 877 point.setState(selected && 'select'); 878 // Unselect all other points unless Ctrl or Cmd + click 879 if (!accumulate) {
880 chart.getSelectedPoints().forEach(function (loopPoint) { 881 const loopSeries = loopPoint.series; 882 if (loopPoint.selected && loopPoint !== point) { 883 loopPoint.selected = loopPoint.options.selected = 884 false; 885 loopSeries.options.data[loopSeries.data.indexOf(loopPoint)] = loopPoint.options; 886 // Programmatically selecting a point should restore 887 // normal state, but when click happened on other 888 // point, set inactive state to match other points 889 loopPoint.setState(chart.hoverPoints && 890 loopSeries.options.inactiveOtherPoints ? 891 'inactive' : ''); 892 loopPoint.firePointEvent('unselect'); 893 } 894 }); 895 } 896 }); 897 delete this.selectedStaging; 898 } 899 /** 900 * Runs on mouse over the point. Called internally from mouse and touch 901 * events. 902 * 903 * @function Highcharts.Point#onMouseOver 904 * 905 * @param {Highcharts.PointerEventObject} [e] 906 * The event arguments. 907 */ 908 onMouseOver(e) { 909 const point = this, series = point.series, { inverted, pointer } = series.chart; 910 if (pointer) { 911 e = e ? 912 pointer.normalize(e) : 913 // In cases where onMouseOver is called directly without an 914 // event 915 pointer.getChartCoordinatesFromPoint(point, inverted); 916 pointer.runPointActions(e, point); 917 } 918 } 919 /** 920 * Runs on mouse out from the point. Called internally from mouse and touch 921 * events. 922 * 923 * @function Highcharts.Point#onMouseOut 924 * @emits Highcharts.Point#event:mouseOut 925 */ 926 onMouseOut() { 927 const point = this, chart = point.series.chart; 928 point.firePointEvent('mouseOut'); 929 if (!point.series.options.inactiveOtherPoints) { 930 (chart.hoverPoints || []).forEach(function (p) { 931 p.setState(); 932 }); 933 } 934 chart.hoverPoints = chart.hoverPoint = null; 935 } 936 /** 937 * Manage specific event from the series' and point's options. Only do it on 938 * demand, to save processing time on hovering. 939 * 940 * @private 941 * @function Highcharts.Point#importEvents 942 */ 943 manageEvent(eventType) { 944 const point = this, options = merge(point.series.options.point, point.options), userEvent = options.events?.[eventType]; 945 if (isFunction(userEvent) && 946 (!point.hcEvents?.[eventType] || 947 // Some HC modules, like marker-clusters, draggable-poins etc. 948 // use events in their logic, so we need to be sure, that 949 // callback function is different 950 point.hcEvents?.[eventType]?.map((el) => el.fn) 951 .indexOf(userEvent) === -1)) { 952 // While updating the existing callback event the old one should be 953 // removed 954 point.importedUserEvent?.(); 955 point.importedUserEvent = addEvent(point, eventType, userEvent); 956 if (point.hcEvents) { 957 point.hcEvents[eventType].userEvent = true; 958 } 959 } 960 else if (point.importedUserEvent && 961 !userEvent && 962 point.hcEvents?.[eventType] && 963 point.hcEvents?.[eventType].userEvent) { 964 removeEvent(point, eventType); 965 delete point.hcEvents[eventType]; 966 if (!Object.keys(point.hcEvents)) { 967 delete point.importedUserEvent; 968 } 969 } 970 } 971 /** 972 * Set the point's state. 973 * 974 * @function Highcharts.Point#setState 975 * 976 * @param {Highcharts.PointStateValue|""} [state] 977 * The new state, can be one of `'hover'`, `'select'`, `'inactive'`, 978 * or `''` (an empty string), `'normal'` or `undefined` to set to 979 * normal state. 980 * @param {boolean} [move] 981 * State for animation. 982 * 983 * @emits Highcharts.Point#event:afterSetState 984 */ 985 setState(state, move) { 986 const point = this, series = point.series, previousState = point.state, stateOptions = (series.options.states[state || 'normal'] || 987 {}), markerOptions = (defaultOptions.plotOptions[series.type].marker && 988 series.options.marker), normalDisabled = (markerOptions && markerOptions.enabled === false), markerStateOptions = ((markerOptions && 989 markerOptions.states && 990 markerOptions.states[state || 'normal']) || {}), stateDisabled = markerStateOptions.enabled === false, pointMarker = point.marker || {}, chart = series.chart, hasMarkers = (markerOptions && series.markerAttribs); 991 let halo = series.halo, markerAttribs, pointAttribs, pointAttribsAnimation, stateMarkerGraphic = series.stateMarkerGraphic, newSymbol; 992 state = state || ''; // Empty string 993 if ( 994 // Already has this state 995 (state === point.state && !move) || 996 // Selected points don't respond to hover 997 (point.selected && state !== 'select') || 998 // Series' state options is disabled 999 (stateOptions.enabled === false) || 1000 // General point marker's state options is disabled 1001 (state && (stateDisabled || 1002 (normalDisabled && 1003 markerStateOptions.enabled === false))) || 1004 // Individual point marker's state options is disabled 1005 (state && 1006 pointMarker.states && 1007 pointMarker.states[state] && 1008 pointMarker.states[state].enabled === false) // #1610 1009 ) { 1010 return; 1011 } 1012 point.state = state; 1013 if (hasMarkers) { 1014 markerAttribs = series.markerAttribs(point, state); 1015 } 1016 // Apply hover styles to the existing point 1017 // Prevent from mocked null points (#14966) 1018 if (point.graphic && !point.hasMockGraphic) { 1019 if (previousState) { 1020 point.graphic.removeClass('highcharts-point-' + previousState); 1021 } 1022 if (state) { 1023 point.graphic.addClass('highcharts-point-' + state); 1024 } 1025 if (!chart.styledMode) { 1026 pointAttribs = series.pointAttribs(point, state); 1027 pointAttribsAnimation = pick(chart.options.chart.animation, stateOptions.animation); 1028 const opacity = pointAttribs.opacity;
vendor: 5,779 bytes, lines 1029-1165
1029 // Some inactive points (e.g. slices in pie) should apply 1030 // opacity also for their labels 1031 if (series.options.inactiveOtherPoints && isNumber(opacity)) { 1032 (point.dataLabels || []).forEach(function (label) { 1033 if (label && 1034 !label.hasClass('highcharts-data-label-hidden')) { 1035 label.animate({ opacity }, pointAttribsAnimation); 1036 if (label.connector) { 1037 label.connector.animate({ opacity }, pointAttribsAnimation); 1038 } 1039 } 1040 }); 1041 } 1042 point.graphic.animate(pointAttribs, pointAttribsAnimation); 1043 } 1044 if (markerAttribs) { 1045 point.graphic.animate(markerAttribs, pick( 1046 // Turn off globally: 1047 chart.options.chart.animation, markerStateOptions.animation, markerOptions.animation)); 1048 } 1049 // Zooming in from a range with no markers to a range with markers 1050 if (stateMarkerGraphic) { 1051 stateMarkerGraphic.hide(); 1052 } 1053 } 1054 else { 1055 // If a graphic is not applied to each point in the normal state, 1056 // create a shared graphic for the hover state 1057 if (state && markerStateOptions) { 1058 newSymbol = pointMarker.symbol || series.symbol; 1059 // If the point has another symbol than the previous one, throw 1060 // away the state marker graphic and force a new one (#1459) 1061 if (stateMarkerGraphic && 1062 stateMarkerGraphic.currentSymbol !== newSymbol) { 1063 stateMarkerGraphic = stateMarkerGraphic.destroy(); 1064 } 1065 // Add a new state marker graphic 1066 if (markerAttribs) { 1067 if (!stateMarkerGraphic) { 1068 if (newSymbol) { 1069 series.stateMarkerGraphic = stateMarkerGraphic = 1070 chart.renderer 1071 .symbol(newSymbol, markerAttribs.x, markerAttribs.y, markerAttribs.width, markerAttribs.height, merge(markerOptions, markerStateOptions)) 1072 .add(series.markerGroup); 1073 stateMarkerGraphic.currentSymbol = newSymbol; 1074 } 1075 // Move the existing graphic 1076 } 1077 else { 1078 stateMarkerGraphic[move ? 'animate' : 'attr']({ 1079 x: markerAttribs.x, 1080 y: markerAttribs.y 1081 }); 1082 } 1083 } 1084 if (!chart.styledMode && stateMarkerGraphic && 1085 point.state !== 'inactive') { 1086 stateMarkerGraphic.attr(series.pointAttribs(point, state)); 1087 } 1088 } 1089 if (stateMarkerGraphic) { 1090 stateMarkerGraphic[state && point.isInside ? 'show' : 'hide'](); // #2450 1091 stateMarkerGraphic.element.point = point; // #4310 1092 stateMarkerGraphic.addClass(point.getClassName(), true); 1093 } 1094 } 1095 // Show me your halo 1096 const haloOptions = stateOptions.halo; 1097 const markerGraphic = (point.graphic || stateMarkerGraphic); 1098 const markerVisibility = (markerGraphic && markerGraphic.visibility || 'inherit'); 1099 if (haloOptions && 1100 haloOptions.size && 1101 markerGraphic && 1102 markerVisibility !== 'hidden' && 1103 !point.isCluster) { 1104 if (!halo) { 1105 series.halo = halo = chart.renderer.path() 1106 // #5818, #5903, #6705 1107 .add(markerGraphic.parentGroup); 1108 } 1109 halo.show()[move ? 'animate' : 'attr']({ 1110 d: point.haloPath(haloOptions.size) 1111 }); 1112 halo.attr({ 1113 'class': 'highcharts-halo highcharts-color-' + 1114 pick(point.colorIndex, series.colorIndex) + 1115 (point.className ? ' ' + point.className : ''), 1116 'visibility': markerVisibility, 1117 'zIndex': -1 // #4929, #8276 1118 }); 1119 halo.point = point; // #6055 1120 if (!chart.styledMode) { 1121 halo.attr(extend({ 1122 'fill': point.color || series.color, 1123 'fill-opacity': haloOptions.opacity 1124 }, AST.filterUserAttributes(haloOptions.attributes || {}))); 1125 } 1126 } 1127 else if (halo?.point?.haloPath && 1128 !halo.point.destroyed) { 1129 // Animate back to 0 on the current halo point (#6055) 1130 halo.animate({ d: halo.point.haloPath(0) }, null, 1131 // Hide after unhovering. The `complete` callback runs in the 1132 // halo's context (#7681). 1133 halo.hide); 1134 } 1135 fireEvent(point, 'afterSetState', { state }); 1136 } 1137 /** 1138 * Get the path definition for the halo, which is usually a shadow-like 1139 * circle around the currently hovered point. 1140 * 1141 * @function Highcharts.Point#haloPath 1142 * 1143 * @param {number} size 1144 * The radius of the circular halo. 1145 * 1146 * @return {Highcharts.SVGPathArray} 1147 * The path definition. 1148 */ 1149 haloPath(size) { 1150 const pos = this.pos(); 1151 return pos ? this.series.chart.renderer.symbols.circle(crisp(pos[0], 1) - size, pos[1] - size, size * 2, size * 2) : []; 1152 } 1153} 1154/* * 1155 * 1156 * Default Export 1157 * 1158 * */ 1159export default Point; 1160/* * 1161 * 1162 * API Declarations 1163 * 1164 * */ 1165/**
1166 * Function callback when a series point is clicked. Return false to cancel the 1167 * action. 1168 * 1169 * @callback Highcharts.PointClickCallbackFunction 1170 * 1171 * @param {Highcharts.Point} this 1172 * The point where the event occurred. 1173 * 1174 * @param {Highcharts.PointClickEventObject} event 1175 * Event arguments. 1176 */ 1177/** 1178 * Common information for a click event on a series point. 1179 * 1180 * @interface Highcharts.PointClickEventObject 1181 * @extends Highcharts.PointerEventObject 1182 */ /** 1183* Clicked point. 1184* @name Highcharts.PointClickEventObject#point 1185* @type {Highcharts.Point} 1186*/ 1187/** 1188 * Gets fired when the mouse leaves the area close to the point. 1189 * 1190 * @callback Highcharts.PointMouseOutCallbackFunction 1191 * 1192 * @param {Highcharts.Point} this 1193 * Point where the event occurred. 1194 * 1195 * @param {global.PointerEvent} event 1196 * Event that occurred. 1197 */ 1198/** 1199 * Gets fired when the mouse enters the area close to the point. 1200 * 1201 * @callback Highcharts.PointMouseOverCallbackFunction 1202 * 1203 * @param {Highcharts.Point} this 1204 * Point where the event occurred. 1205 * 1206 * @param {global.Event} event 1207 * Event that occurred. 1208 */ 1209/** 1210 * The generic point options for all series. 1211 * 1212 * In TypeScript you have to extend `PointOptionsObject` with an additional 1213 * declaration to allow custom data options: 1214 * 1215 * ``` 1216 * declare interface PointOptionsObject { 1217 * customProperty: string; 1218 * } 1219 * ``` 1220 * 1221 * @interface Highcharts.PointOptionsObject 1222 */ 1223/** 1224 * Possible option types for a data point. Use `null` to indicate a gap. 1225 * 1226 * @typedef {number|string|Highcharts.PointOptionsObject|Array<(number|string|null)>|null} Highcharts.PointOptionsType 1227 */ 1228/** 1229 * Gets fired when the point is removed using the `.remove()` method. 1230 * 1231 * @callback Highcharts.PointRemoveCallbackFunction 1232 * 1233 * @param {Highcharts.Point} this 1234 * Point where the event occurred. 1235 * 1236 * @param {global.Event} event 1237 * Event that occurred. 1238 */ 1239/** 1240 * Possible key values for the point state options. 1241 * 1242 * @typedef {"hover"|"inactive"|"normal"|"select"} Highcharts.PointStateValue 1243 */ 1244/** 1245 * Gets fired when the point is updated programmatically through the `.update()` 1246 * method. 1247 * 1248 * @callback Highcharts.PointUpdateCallbackFunction 1249 * 1250 * @param {Highcharts.Point} this 1251 * Point where the event occurred. 1252 * 1253 * @param {Highcharts.PointUpdateEventObject} event 1254 * Event that occurred. 1255 */ 1256/** 1257 * Information about the update event. 1258 * 1259 * @interface Highcharts.PointUpdateEventObject 1260 * @extends global.Event 1261 */ /** 1262* Options data of the update event. 1263* @name Highcharts.PointUpdateEventObject#options 1264* @type {Highcharts.PointOptionsType} 1265*/ 1266/** 1267 * @interface Highcharts.PointEventsOptionsObject 1268 */ /** 1269* Fires when the point is selected either programmatically or following a click 1270* on the point. One parameter, `event`, is passed to the function. Returning 1271* `false` cancels the operation. 1272* @name Highcharts.PointEventsOptionsObject#select 1273* @type {Highcharts.PointSelectCallbackFunction|undefined} 1274*/ /** 1275* Fires when the point is unselected either programmatically or following a 1276* click on the point. One parameter, `event`, is passed to the function. 1277* Returning `false` cancels the operation. 1278* @name Highcharts.PointEventsOptionsObject#unselect 1279* @type {Highcharts.PointUnselectCallbackFunction|undefined} 1280*/ 1281/** 1282 * Information about the select/unselect event. 1283 * 1284 * @interface Highcharts.PointInteractionEventObject 1285 * @extends global.Event 1286 */ /** 1287* @name Highcharts.PointInteractionEventObject#accumulate 1288* @type {boolean} 1289*/ 1290/** 1291 * Gets fired when the point is selected either programmatically or following a 1292 * click on the point. 1293 * 1294 * @callback Highcharts.PointSelectCallbackFunction 1295 * 1296 * @param {Highcharts.Point} this 1297 * Point where the event occurred. 1298 * 1299 * @param {Highcharts.PointInteractionEventObject} event 1300 * Event that occurred. 1301 */ 1302/** 1303 * Fires when the point is unselected either programmatically or following a 1304 * click on the point. 1305 * 1306 * @callback Highcharts.PointUnselectCallbackFunction 1307 * 1308 * @param {Highcharts.Point} this 1309 * Point where the event occurred. 1310 * 1311 * @param {Highcharts.PointInteractionEventObject} event 1312 * Event that occurred. 1313 */ 1314''; // Keeps doclets above in JS file.
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.