UNPKG

highcharts

Version:
1,364 lines (1,343 loc) 91.4 kB
// SPDX-License-Identifier: LicenseRef-Highcharts /** * @license Highcharts JS v13.0.1 (2026-08-17) * @module highcharts/modules/marker-clusters * @requires highcharts * * Marker clusters module for Highcharts * * (c) 2010-2026 Highsoft AS * Author: Wojciech Chmiel * * A commercial license may be required depending on use, * see www.highcharts.com/license */ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(root["_Highcharts"]); else if(typeof define === 'function' && define.amd) define("highcharts/modules/marker-clusters", ["highcharts/highcharts"], function (amd1) {return factory(amd1);}); else if(typeof exports === 'object') exports["highcharts/modules/marker-clusters"] = factory(root["_Highcharts"]); else root["Highcharts"] = factory(root["Highcharts"]); })(typeof window === 'undefined' ? this : window, (__WEBPACK_EXTERNAL_MODULE__944__) => { return /******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ var __webpack_modules__ = ({ /***/ 944 (module) { module.exports = __WEBPACK_EXTERNAL_MODULE__944__; /***/ } /******/ }); /************************************************************************/ /******/ // The module cache /******/ const __webpack_module_cache__ = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ const cachedModule = __webpack_module_cache__[moduleId]; /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ const module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed /******/ // no module.loaded needed /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /************************************************************************/ /******/ /* webpack/runtime/compat get default export */ /******/ (() => { /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = (module) => { /******/ const getter = module && module.__esModule ? /******/ () => (module['default']) : /******/ () => (module); /******/ __webpack_require__.d(getter, { a: getter }); /******/ return getter; /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter/value functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ if(Array.isArray(definition)) { /******/ var i = 0; /******/ while(i < definition.length) { /******/ var key = definition[i++]; /******/ var binding = definition[i++]; /******/ if(!__webpack_require__.o(exports, key)) { /******/ if(binding === 0) { /******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); /******/ } else { /******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); /******/ } /******/ } else if(binding === 0) { i++; } /******/ } /******/ } else { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /************************************************************************/ let __webpack_exports__ = {}; // EXPORTS __webpack_require__.d(__webpack_exports__, { "default": () => (/* binding */ marker_clusters_src) }); // EXTERNAL MODULE: external {"amd":["highcharts/highcharts"],"commonjs":["highcharts"],"commonjs2":["highcharts"],"root":["Highcharts"]} var highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_ = __webpack_require__(944); var highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_default = /*#__PURE__*/__webpack_require__.n(highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_); ;// ./code/es-modules/Extensions/MarkerClusters/MarkerClusterDefaults.js /* * * * Marker clusters module. * * (c) 2010-2026 Highsoft AS * * Author: Wojciech Chmiel * * Integration of this software requires a license. * - For commercial use, see www.highcharts.com/license * - For non-commercial, see www.highcharts.com/license-eula * * * */ /* * * * API Options * * */ /** * Options for marker clusters, the concept of sampling the data * values into larger blocks in order to ease readability and * increase performance of the JavaScript charts. * * Note: marker clusters module is not working with `boost` * and `draggable-points` modules. * * The marker clusters feature requires the marker-clusters.js * file to be loaded, found in the modules directory of the download * package, or online at [code.highcharts.com/modules/marker-clusters.js * ](code.highcharts.com/modules/marker-clusters.js). * * @sample maps/marker-clusters/europe * Maps marker clusters * @sample highcharts/marker-clusters/basic * Scatter marker clusters * @sample maps/marker-clusters/optimized-kmeans * Marker clusters with colorAxis * * @product highcharts highmaps * @since 8.0.0 * @requires modules/marker-clusters * @optionparent plotOptions.scatter.cluster */ const cluster = { /** * Whether to enable the marker-clusters module. * * @sample maps/marker-clusters/basic * Maps marker clusters * @sample highcharts/marker-clusters/basic * Scatter marker clusters * * @requires modules/marker-clusters */ enabled: false, /** * When set to `false` prevent cluster overlapping - this option * works only when `layoutAlgorithm.type = "grid"`. * * @sample highcharts/marker-clusters/grid * Prevent overlapping * * @requires modules/marker-clusters */ allowOverlap: true, /** * Options for the cluster marker animation. * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} * @default { "duration": 500 } * @requires modules/marker-clusters */ animation: { /** @ignore-option */ duration: 500 }, /** * Zoom the plot area to the cluster points range when a cluster is clicked. * * @requires modules/marker-clusters */ drillToCluster: true, /** * The minimum amount of points to be combined into a cluster. * This value has to be greater or equal to 2. * * @sample highcharts/marker-clusters/basic * At least three points in the cluster * * @requires modules/marker-clusters */ minimumClusterSize: 2, /** * Options for layout algorithm. Inside there * are options to change the type of the algorithm, gridSize, * distance or iterations. * * @requires modules/marker-clusters */ layoutAlgorithm: { /** * Type of the algorithm used to combine points into a cluster. * There are three available algorithms: * * 1) `grid` - grid-based clustering technique. Points are assigned * to squares of set size depending on their position on the plot * area. Points inside the grid square are combined into a cluster. * The grid size can be controlled by `gridSize` property * (grid size changes at certain zoom levels). * * 2) `kmeans` - based on K-Means clustering technique. In the * first step, points are divided using the grid method (distance * property is a grid size) to find the initial amount of clusters. * Next, each point is classified by computing the distance between * each cluster center and that point. When the closest cluster * distance is lower than distance property set by a user the point * is added to this cluster otherwise is classified as `noise`. The * algorithm is repeated until each cluster center not change its * previous position more than one pixel. This technique is more * accurate but also more time consuming than the `grid` algorithm, * especially for big datasets. * * 3) `optimizedKmeans` - based on K-Means clustering technique. This * algorithm uses k-means algorithm only on the chart initialization * or when chart extremes have greater range than on initialization. * When a chart is redrawn the algorithm checks only clustered points * distance from the cluster center and rebuild it when the point is * spaced enough to be outside the cluster. It provides performance * improvement and more stable clusters position yet can be used rather * on small and sparse datasets. * * By default, the algorithm depends on visible quantity of points * and `kmeansThreshold`. When there are more visible points than the * `kmeansThreshold` the `grid` algorithm is used, otherwise `kmeans`. * * The custom clustering algorithm can be added by assigning a callback * function as the type property. This function takes an array of * `processedXData`, `processedYData`, `processedXData` indexes and * `layoutAlgorithm` options as arguments and should return an object * with grouped data. * * The algorithm should return an object like that: * <pre>{ * clusterId1: [{ * x: 573, * y: 285, * index: 1 // point index in the data array * }, { * x: 521, * y: 197, * index: 2 * }], * clusterId2: [{ * ... * }] * ... * }</pre> * * `clusterId` (example above - unique id of a cluster or noise) * is an array of points belonging to a cluster. If the * array has only one point or fewer points than set in * `cluster.minimumClusterSize` it won't be combined into a cluster. * * @sample maps/marker-clusters/optimized-kmeans * Optimized K-Means algorithm * @sample highcharts/marker-clusters/kmeans * K-Means algorithm * @sample highcharts/marker-clusters/grid * Grid algorithm * @sample maps/marker-clusters/custom-alg * Custom algorithm * * @type {string|Function} * @see [cluster.minimumClusterSize](#plotOptions.scatter.cluster.minimumClusterSize) * @requires modules/marker-clusters * @apioption plotOptions.scatter.cluster.layoutAlgorithm.type */ /** * When `type` is set to the `grid`, * `gridSize` is a size of a grid square element either as a number * defining pixels, or a percentage defining a percentage * of the plot area width. * * @type {number|string} * @requires modules/marker-clusters */ gridSize: 50, /** * When `type` is set to `kmeans`, * `iterations` are the number of iterations that this algorithm will be * repeated to find clusters positions. * * @type {number} * @requires modules/marker-clusters * @apioption plotOptions.scatter.cluster.layoutAlgorithm.iterations */ /** * When `type` is set to `kmeans`, * `distance` is a maximum distance between point and cluster center * so that this point will be inside the cluster. The distance * is either a number defining pixels or a percentage * defining a percentage of the plot area width. * * @type {number|string} * @requires modules/marker-clusters */ distance: 40, /** * When `type` is set to `undefined` and there are more visible points * than the kmeansThreshold the `grid` algorithm is used to find * clusters, otherwise `kmeans`. It ensures good performance on * large datasets and better clusters arrangement after the zoom. * * @requires modules/marker-clusters */ kmeansThreshold: 100 }, /** * Options for the cluster marker. * * @type {Highcharts.PointMarkerOptionsObject} * @extends plotOptions.series.marker * @excluding enabledThreshold, states * @requires modules/marker-clusters */ marker: { symbol: 'cluster', radius: 15, lineWidth: 0, /** * @type {Highcharts.ColorType} */ lineColor: 'var(--highcharts-background-color)' }, /** * Fires when the cluster point is clicked and `drillToCluster` is enabled. * One parameter, `event`, is passed to the function. The default action * is to zoom to the cluster points range. This can be prevented * by calling `event.preventDefault()`. * * @type {Highcharts.MarkerClusterDrillCallbackFunction} * @product highcharts highmaps * @see [cluster.drillToCluster](#plotOptions.scatter.cluster.drillToCluster) * @requires modules/marker-clusters * @apioption plotOptions.scatter.cluster.events.drillToCluster */ /** * An array defining zones within marker clusters. * * In styled mode, the color zones are styled with the * `.highcharts-cluster-zone-{n}` class, or custom * classed from the `className` * option. * * @sample highcharts/marker-clusters/basic * Marker clusters zones * @sample maps/marker-clusters/custom-alg * Zones on maps * * @type {Array<*>} * @product highcharts highmaps * @requires modules/marker-clusters * @apioption plotOptions.scatter.cluster.zones */ /** * Styled mode only. A custom class name for the zone. * * @sample highcharts/css/color-zones/ * Zones styled by class name * * @type {string} * @requires modules/marker-clusters * @apioption plotOptions.scatter.cluster.zones.className */ /** * Settings for the cluster marker belonging to the zone. * * @see [cluster.marker](#plotOptions.scatter.cluster.marker) * @extends plotOptions.scatter.cluster.marker * @product highcharts highmaps * @requires modules/marker-clusters * @apioption plotOptions.scatter.cluster.zones.marker */ /** * The value where the zone starts. * * @type {number} * @product highcharts highmaps * @requires modules/marker-clusters * @apioption plotOptions.scatter.cluster.zones.from */ /** * The value where the zone ends. * * @type {number} * @product highcharts highmaps * @requires modules/marker-clusters * @apioption plotOptions.scatter.cluster.zones.to */ /** * The fill color of the cluster marker in hover state. When * `undefined`, the series' or point's fillColor for normal * state is used. * * @type {Highcharts.ColorType} * @requires modules/marker-clusters * @apioption plotOptions.scatter.cluster.states.hover.fillColor */ /** * Options for the cluster data labels. * * @type {Highcharts.DataLabelsOptions} * @extends plotOptions.line.dataLabels * @requires modules/marker-clusters */ dataLabels: { enabled: true, format: '{point.clusterPointsAmount}', verticalAlign: 'middle', align: 'center', style: { color: 'contrast' }, inside: true } }; const tooltip = { /** * The HTML of the cluster point's in the tooltip. Works only with * marker-clusters module and analogously to * [pointFormat](#tooltip.pointFormat). * * The cluster tooltip can be also formatted using * `tooltip.formatter` callback function and `point.isCluster` flag. * * @sample highcharts/marker-clusters/grid * Format tooltip for cluster points. * * @sample maps/marker-clusters/europe/ * Format tooltip for clusters using tooltip.formatter * * @type {string} * @default Clustered points: {point.clusterPointsAmount} * @requires modules/marker-clusters * @apioption tooltip.clusterFormat */ clusterFormat: '<span>Clustered points: ' + '{point.clusterPointsAmount}</span><br/>' }; /* * * * Default Export * * */ /** @internal */ const MarkerClusterDefaults = { cluster, tooltip }; /** @internal */ /* harmony default export */ const MarkerClusters_MarkerClusterDefaults = (MarkerClusterDefaults); ;// ./code/es-modules/Data/ColumnUtils.js /* * * * (c) 2020-2026 Highsoft AS * * Integration of this software requires a license. * - For commercial use, see www.highcharts.com/license * - For non-commercial, see www.highcharts.com/license-eula * * * Authors: * - Dawid Draguła * * */ /* * * * Functions * * */ /** * Sets the length of the column array. * * @param {DataTableColumn} column * Column to be modified. * * @param {number} length * New length of the column. * * @param {boolean} asSubarray * If column is a typed array, return a subarray instead of a new array. It * is faster `O(1)`, but the entire buffer will be kept in memory until all * views of it are destroyed. Default is `false`. * * @return {DataTableColumn} * Modified column. * * @private */ function setLength(column, length, asSubarray) { if (Array.isArray(column)) { column.length = length; return column; } return column[asSubarray ? 'subarray' : 'slice'](0, length); } /** * Splices a column array. * * @param {DataTableColumn} column * Column to be modified. * * @param {number} start * Index at which to start changing the array. * * @param {number} deleteCount * An integer indicating the number of old array elements to remove. * * @param {boolean} removedAsSubarray * If column is a typed array, return a subarray instead of a new array. It * is faster `O(1)`, but the entire buffer will be kept in memory until all * views to it are destroyed. Default is `true`. * * @param {Array<number>|TypedArray} items * The elements to add to the array, beginning at the start index. If you * don't specify any elements, `splice()` will only remove elements from the * array. * * @return {SpliceResult} * Object containing removed elements and the modified column. * * @private */ function splice(column, start, deleteCount, removedAsSubarray, items = []) { if (Array.isArray(column)) { if (!Array.isArray(items)) { items = Array.from(items); } return { removed: column.splice(start, deleteCount, ...items), array: column }; } const Constructor = Object.getPrototypeOf(column) .constructor; const removed = column[removedAsSubarray ? 'subarray' : 'slice'](start, start + deleteCount); const newLength = column.length - deleteCount + items.length; const result = new Constructor(newLength); result.set(column.subarray(0, start), 0); result.set(items, start); result.set(column.subarray(start + deleteCount), start + items.length); return { removed: removed, array: result }; } /** * Converts a cell value to a number. * * @param {DataTableCellType} value * Cell value to convert to a number. * * @param {boolean} useNaN * If `true`, returns `NaN` for non-numeric values; if `false`, * returns `null` instead. * * @return {number | null} * Number or `null` if the value is not a number. * * @private */ function convertToNumber(value, useNaN) { switch (typeof value) { case 'boolean': return (value ? 1 : 0); case 'number': return (isNaN(value) && !useNaN ? null : value); default: value = parseFloat(`${value ?? ''}`); return (isNaN(value) && !useNaN ? null : value); } } /* * * * Default Export * * */ const ColumnUtils = { convertToNumber, setLength, splice }; /* harmony default export */ const Data_ColumnUtils = (ColumnUtils); ;// ./code/es-modules/Data/DataTableCore.js /* * * * (c) 2009-2026 Highsoft AS * * Integration of this software requires a license. * - For commercial use, see www.highcharts.com/license * - For non-commercial, see www.highcharts.com/license-eula * * * Authors: * - Sophie Bremer * - Gøran Slettemark * - Torstein Hønsi * * */ const { setLength: DataTableCore_setLength, splice: DataTableCore_splice } = Data_ColumnUtils; /* * * * Class * * */ /** * Class to manage columns and rows in a table structure. It provides methods * to add, remove, and manipulate columns and rows, as well as to retrieve data * from specific cells. * * Highcharts allows passing a `DataTable` or a configuration object for a data * table in the `dataTable` property, either chart-level * [dataTable](https://api.highcharts.com/highcharts/dataTable) or as * [series.dataTable](https://api.highcharts.com/highcharts/series.dataTable). * The `DataTable` is then used as a source for the series data points, mapped * by the `series.dataMapping` option. * * After chart instantiation, the data table can be accessed from the series as * `series.dataTable`. CRUD operations on the data table will be reflected in * the chart. * * @example * const dataTable = new Highcharts.DataTable({ * columns: { * year: [2020, 2021, 2022, 2023], * cost: [11, 13, 12, 14], * revenue: [12, 15, 14, 18] * } * }); * * @class * @name Highcharts.DataTable * * @param {Highcharts.DataTableOptionsObject} [options] * Options to initialize the new DataTable instance. */ class DataTableCore { constructor(options = {}) { this.isDataTable = true; this.autoId = !options.id; this.columns = {}; this.id = (options.id || (0,highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_.uniqueKey)()); this.rowCount = 0; this.versionTag = (0,highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_.uniqueKey)(); let rowCount = 0; (0,highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_.objectEach)(options.columns || {}, (column, columnId) => { this.columns[columnId] = column.slice(); rowCount = Math.max(rowCount, column.length); }); this.applyRowCount(rowCount); } /* * * * Functions * * */ /** * Applies a row count to the table by setting the `rowCount` property and * adjusting the length of all columns. * * @private * @param {number} rowCount The new row count. */ applyRowCount(rowCount) { this.rowCount = rowCount; (0,highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_.objectEach)(this.columns, (column, columnId) => { if (column.length !== rowCount) { this.columns[columnId] = DataTableCore_setLength(column, rowCount); } }); } /** * Delete rows. Simplified version of the full * `DataTable.deleteRows` method. * * @sample highcharts/datatable/live-chart/ * Add and delete rows in a live chart * @sample highcharts/datatable/shared-with-grid/ * Chart with data table CRUD operations * * @function Highcharts.DataTable#deleteRows * * @param {number} rowIndex * The start row index * * @param {number} [rowCount=1] * The number of rows to delete * * @return {void} * * @emits #afterDeleteRows */ deleteRows(rowIndex, rowCount = 1) { if (rowCount > 0 && rowIndex < this.rowCount) { let length = 0; (0,highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_.objectEach)(this.columns, (column, columnId) => { this.columns[columnId] = DataTableCore_splice(column, rowIndex, rowCount).array; length = column.length; }); this.rowCount = length; } (0,highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_.fireEvent)(this, 'afterDeleteRows', { rowIndex, rowCount }); this.versionTag = (0,highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_.uniqueKey)(); } /** * Fetches the given column by the canonical column ID. Simplified version * of the full `DataTable.getRow` method, always returning by reference. * * @function Highcharts.DataTable#getColumn * * @param {string} columnId * ID of the column to get. * * @return {Highcharts.DataTableColumn|undefined} * A copy of the column, or `undefined` if not found. */ getColumn(columnId, // eslint-disable-next-line @typescript-eslint/no-unused-vars asReference) { return this.columns[columnId]; } /** * Retrieves all or the given columns. Simplified version of the full * `DataTable.getColumns` method, always returning by reference. * * @function Highcharts.DataTable#getColumns * * @param {Array<string>} [columnIds] * Column ids to retrieve. * * @return {Highcharts.DataTableColumnCollection} * Collection of columns. If a requested column was not found, it is * `undefined`. */ getColumns(columnIds, // eslint-disable-next-line @typescript-eslint/no-unused-vars asReference) { return (columnIds || Object.keys(this.columns)).reduce((columns, columnId) => { columns[columnId] = this.columns[columnId]; return columns; }, {}); } /** * Retrieves the row at a given index. * * @function Highcharts.DataTable#getRowObject * * @param {number} rowIndex * Row index to retrieve. First row has index 0. * * @param {Array<string>} [columnNames] * Column names to retrieve. * * @return {Record<string, number|string|undefined>|undefined} * Returns the row values, or `undefined` if not found. */ getRowObject(rowIndex, columnNames) { const row = {}, columns = this.columns; columnNames ?? (columnNames = Object.keys(this.columns)); for (const columnName of columnNames) { row[columnName] = columns[columnName]?.[rowIndex]; } return row; } /** * Sets cell values for a column. Will insert a new column, if not found. * * @function Highcharts.DataTable#setColumn * * @param {string} columnId * Column name to set. * * @param {Highcharts.DataTableColumn} [column] * Values to set in the column. * * @param {number} [rowIndex] * Index of the first row to change. (Default: 0) * * @param {Record<string, (boolean|number|string|null|undefined)>} [eventDetail] * Custom information for pending events. * * @emits #setColumns * @emits #afterSetColumns */ setColumn(columnId, column = [], rowIndex = 0, eventDetail) { this.setColumns({ [columnId]: column }, rowIndex, eventDetail); } /** * Sets cell values for multiple columns. Will insert new columns, if not * found. Simplified version of the full `DataTable.setColumns`, limited * to full replacement of the columns (undefined `rowIndex`). * * @sample highcharts/datatable/shared-with-grid/ * Chart with data table CRUD operations * * @function Highcharts.DataTable#setColumns * * @param {Highcharts.DataTableColumnCollection} columns * Columns as a collection, where the keys are the column names. * * @param {number} [rowIndex] * Index of the first row to change. Ignored in the simplified `DataTable`, * as it always replaces the full column. * * @param {Record<string, (boolean|number|string|null|undefined)>} [eventDetail] * Custom information for pending events. * * @emits #setColumns * @emits #afterSetColumns */ setColumns(columns, rowIndex, eventDetail) { let rowCount = this.rowCount; (0,highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_.objectEach)(columns, (column, columnId) => { this.columns[columnId] = column.slice(); rowCount = column.length; }); this.applyRowCount(rowCount); if (!eventDetail?.silent) { (0,highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_.fireEvent)(this, 'afterSetColumns'); this.versionTag = (0,highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_.uniqueKey)(); } } /** * Sets cell values of a row. Will insert a new row if no index was * provided, or if the index is higher than the total number of table rows. * A simplified version of the full `DateTable.setRow`, limited to objects. * * @sample highcharts/datatable/live-chart/ * Add and delete rows in a live chart * @sample stock/datatable/live-candlestick/ * Live candlestick * @sample highcharts/datatable/shared-with-grid/ * Chart with data table CRUD operations * * @function Highcharts.DataTable#setRow * * @param {Record<string, number|string|undefined>} row * Cell values to set. * * @param {number} [rowIndex] * Index of the row to set. Leave `undefined` to add as a new row. * * @param {boolean} [insert] * Whether to insert the row at the given index, or to overwrite the row. * * @param {Record<string, (boolean|number|string|null|undefined)>} [eventDetail] * Custom information for pending events. * * @emits #afterSetRows */ setRow(row, rowIndex = this.rowCount, insert, eventDetail) { var _a; const { columns } = this, indexRowCount = insert ? this.rowCount + 1 : rowIndex + 1, rowKeys = Object.keys(row); if (eventDetail?.addColumns !== false) { for (let i = 0, iEnd = rowKeys.length; i < iEnd; i++) { columns[_a = rowKeys[i]] || (columns[_a] = new Array(this.rowCount)); } } (0,highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_.objectEach)(columns, (column, columnId) => { if (column) { if (insert) { column = DataTableCore_splice(column, rowIndex, 0, true, [row[columnId]]).array; } else { column[rowIndex] = // Preserve explicit null and undefined but fall back // to existing value if the new row does not have the // key columnId in row ? row[columnId] : column[rowIndex]; } columns[columnId] = column; } }); this.applyRowCount(Math.max(indexRowCount, this.rowCount)); if (!eventDetail?.silent) { (0,highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_.fireEvent)(this, 'afterSetRows', { rowIndex }); this.versionTag = (0,highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_.uniqueKey)(); } } /** * Returns the modified (clone) or the original data table if the modified * one does not exist. * * @return {Highcharts.DataTable} * The modified (clone) or the original data table. */ getModified() { return this.modified || this; } } /* * * * Default Export * * */ /* harmony default export */ const Data_DataTableCore = (DataTableCore); /* * * * API Declarations * * */ /** * A collection of data table columns defined by a object where the key is the * column ID and the value is an array of the column values. Typed arrays are * supported. * * @type {Highcharts.DataTableColumnCollection|undefined} * @apioption dataTable.columns */ /** * Custom ID to identify the new DataTable instance. * * @type {string|undefined} * @apioption dataTable.id */ /** * A typed array. * @typedef {Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} Highcharts.TypedArray */ /** * A column of values in a data table. * @typedef {Array<boolean|null|number|string|undefined>|Highcharts.TypedArray} Highcharts.DataTableColumn */ /** * A collection of data table columns defined by a object where the key is the * column ID and the value is an array of the column values. Typed arrays are * supported. * @typedef {Record<string, Highcharts.DataTableColumn>} Highcharts.DataTableColumnCollection */ /** * Options for the `DataTable` or `DataTableCore` classes. * @interface Highcharts.DataTableOptionsObject */ /** * The column options for the data table. The columns are defined by an object * where the key is the column ID and the value is an array of the column * values. * * @name Highcharts.DataTableOptionsObject.columns * @type {Highcharts.DataTableColumnCollection|undefined} */ /** * Custom ID to identify the new DataTable instance. * * @name Highcharts.DataTableOptionsObject.id * @type {string|undefined} */ (''); // Keeps doclets above in JS file ;// ./code/es-modules/Extensions/MarkerClusters/MarkerClusterScatter.js /* * * * Marker clusters module. * * (c) 2010-2026 Highsoft AS * * Author: Wojciech Chmiel * * Integration of this software requires a license. * - For commercial use, see www.highcharts.com/license * - For non-commercial, see www.highcharts.com/license-eula * * * */ const { cluster: clusterDefaults } = MarkerClusters_MarkerClusterDefaults; /* * * * Constants * * */ const markerClusterAlgorithms = { grid: function (dataX, dataY, dataIndexes, options) { const series = this, grid = {}, gridOffset = series.getGridOffset(), scaledGridSize = series.getScaledGridSize(options); let x, y, gridX, gridY, key, i; for (i = 0; i < dataX.length; i++) { const p = valuesToPixels(series, { x: dataX[i], y: dataY[i] }); x = p.x - gridOffset.plotLeft; y = p.y - gridOffset.plotTop; gridX = Math.floor(x / scaledGridSize); gridY = Math.floor(y / scaledGridSize); key = gridY + ':' + gridX; grid[key] ?? (grid[key] = []); grid[key].push({ dataIndex: dataIndexes[i], x: dataX[i], y: dataY[i] }); } return grid; }, kmeans: function (dataX, dataY, dataIndexes, options) { const series = this, clusters = [], noise = [], group = {}, pointMaxDistance = Number(options.processedDistance ?? (0,highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_.relativeLength)(options.distance || clusterDefaults.layoutAlgorithm.distance, series.chart.plotWidth)), iterations = options.iterations, // Max pixel difference between new and old cluster position. maxClusterShift = 1; let currentIteration = 0, repeat = true, pointX = 0, pointY = 0, tempPos, pointClusterDistance = []; options.processedGridSize = pointMaxDistance; // Use grid method to get groupedData object. const groupedData = series.markerClusterAlgorithms ? series.markerClusterAlgorithms.grid.call(series, dataX, dataY, dataIndexes, options) : {}; // Find clusters amount and its start positions // based on grid grouped data. for (const key in groupedData) { if (groupedData[key].length > 1) { tempPos = getClusterPosition(groupedData[key]); clusters.push({ posX: tempPos.x, posY: tempPos.y, oldX: 0, oldY: 0, startPointsLen: groupedData[key].length, points: [] }); } } // Start kmeans iteration process. while (repeat) { for (const c of clusters) { c.points.length = 0; } noise.length = 0; for (let i = 0; i < dataX.length; i++) { pointX = dataX[i]; pointY = dataY[i]; pointClusterDistance = series.getClusterDistancesFromPoint(clusters, pointX, pointY); if (pointClusterDistance.length && pointClusterDistance[0].distance < pointMaxDistance) { clusters[pointClusterDistance[0].clusterIndex].points.push({ x: pointX, y: pointY, dataIndex: dataIndexes[i] }); } else { noise.push({ x: pointX, y: pointY, dataIndex: dataIndexes[i] }); } } // When cluster points array has only one point the // point should be classified again. for (let i = 0; i < clusters.length; i++) { if (clusters[i].points.length === 1) { pointClusterDistance = series.getClusterDistancesFromPoint(clusters, clusters[i].points[0].x, clusters[i].points[0].y); if (pointClusterDistance[1].distance < pointMaxDistance) { // Add point to the next closest cluster. clusters[pointClusterDistance[1].clusterIndex].points .push(clusters[i].points[0]); // Clear points array. clusters[pointClusterDistance[0].clusterIndex] .points.length = 0; } } } // Compute a new clusters position and check if it // is different than the old one. repeat = false; for (let i = 0; i < clusters.length; i++) { tempPos = getClusterPosition(clusters[i].points); clusters[i].oldX = clusters[i].posX; clusters[i].oldY = clusters[i].posY; clusters[i].posX = tempPos.x; clusters[i].posY = tempPos.y; // Repeat the algorithm if at least one cluster // is shifted more than maxClusterShift property. if (clusters[i].posX > clusters[i].oldX + maxClusterShift || clusters[i].posX < clusters[i].oldX - maxClusterShift || clusters[i].posY > clusters[i].oldY + maxClusterShift || clusters[i].posY < clusters[i].oldY - maxClusterShift) { repeat = true; } } // If iterations property is set repeat the algorithm // specified amount of times. if (iterations) { repeat = currentIteration < iterations - 1; } currentIteration++; } for (let i = 0, iEnd = clusters.length; i < iEnd; ++i) { group['cluster' + i] = clusters[i].points; } for (let i = 0, iEnd = noise.length; i < iEnd; ++i) { group['noise' + i] = [noise[i]]; } return group; }, optimizedKmeans: function (processedXData, processedYData, dataIndexes, options) { const series = this, pointMaxDistance = Number(options.processedDistance ?? (0,highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_.relativeLength)(options.distance || clusterDefaults.layoutAlgorithm.gridSize, series.chart.plotWidth)), extremes = series.getRealExtremes(), clusterMarkerOptions = (series.options.cluster || {}).marker; let distance, group = {}; if (!series.markerClusterInfo || (series.initMaxX && series.initMaxX < extremes.maxX || series.initMinX && series.initMinX > extremes.minX || series.initMaxY && series.initMaxY < extremes.maxY || series.initMinY && series.initMinY > extremes.minY)) { series.initMaxX = extremes.maxX; series.initMinX = extremes.minX; series.initMaxY = extremes.maxY; series.initMinY = extremes.minY; group = series.markerClusterAlgorithms ? series.markerClusterAlgorithms.kmeans.call(series, processedXData, processedYData, dataIndexes, options) : {}; series.baseClusters = null; } else { series.baseClusters ?? (series.baseClusters = { clusters: series.markerClusterInfo.clusters, noise: series.markerClusterInfo.noise }); for (const cluster of series.baseClusters.clusters) { cluster.pointsOutside = []; cluster.pointsInside = []; for (const dataPoint of cluster.data) { const dataPointPx = valuesToPixels(series, dataPoint), clusterPx = valuesToPixels(series, cluster); distance = Math.sqrt(Math.pow(dataPointPx.x - clusterPx.x, 2) + Math.pow(dataPointPx.y - clusterPx.y, 2)); const radius = (cluster.clusterZone?.marker?.radius ?? clusterMarkerOptions?.radius ?? clusterDefaults.marker.radius ?? 0); const offset = pointMaxDistance - radius >= 0 ? pointMaxDistance - radius : radius; if (distance > radius + offset && (0,highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_.defined)(cluster.pointsOutside)) { cluster.pointsOutside.push(dataPoint); } else if ((0,highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_.defined)(cluster.pointsInside)) { cluster.pointsInside.push(dataPoint); } } if (cluster.pointsInside.length) { group[cluster.id] = cluster.pointsInside; } let i = 0; for (const p of cluster.pointsOutside) { group[cluster.id + '_noise' + i++] = [p]; } } for (const noise of series.baseClusters.noise) { group[noise.id] = noise.data; } } return group; } }; /* * * * Variables * * */ let baseGeneratePoints, /** * Points that ids are included in the oldPointsStateId array are hidden * before animation. Other ones are destroyed. * @internal */ oldPointsStateId = [], stateIdCounter = 0; /* * * * Functions * * */ /** * Compose marker cluster scatter hooks. * @internal */ function compose(highchartsDefaultOptions, ScatterSeriesClass) { const scatterProto = ScatterSeriesClass.prototype; if (!scatterProto.markerClusterAlgorithms) { baseGeneratePoints = scatterProto.generatePoints; scatterProto.markerClusterAlgorithms = markerClusterAlgorithms; scatterProto.animateClusterPoint = seriesAnimateClusterPoint; scatterProto.destroyClusteredData = seriesDestroyClusteredData; scatterProto.generatePoints = seriesGeneratePoints; scatterProto.getClusterDistancesFromPoint = seriesGetClusterDistancesFromPoint; scatterProto.getClusteredData = seriesGetClusteredData; scatterProto.getGridOffset = seriesGetGridOffset; scatterProto.getPointsState = seriesGetPointsState; scatterProto.getRealExtremes = seriesGetRealExtremes; scatterProto.getScaledGridSize = seriesGetScaledGridSize; scatterProto.hideClusteredData = seriesHideClusteredData; scatterProto.isValidGroupedDataObject = seriesIsValidGroupedDataObject; scatterProto.preventClusterCollisions = seriesPreventClusterCollisions; // Destroy grouped data on series destroy. (0,highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_.addEvent)(ScatterSeriesClass, 'destroy', scatterProto.destroyClusteredData); if (highchartsDefaultOptions.plotOptions) { highchartsDefaultOptions.plotOptions.series = (0,highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_.merge)(highchartsDefaultOptions.plotOptions.series, MarkerClusters_MarkerClusterDefaults); } } } /** * Util function. * @internal */ function destroyOldPoints(oldState) { for (const key of Object.keys(oldState)) { oldState[key].point?.destroy?.(); } } /** * Util function. * @internal */ function fadeInElement(elem, opacity, animation) { elem.attr({ opacity }).animate({ opacity: 1 }, animation); } /** * Util function. * @internal */ function fadeInNewPointAndDestroyOld(newPointObj, oldPoints, animation, opacity) { // Fade in new point. fadeInStatePoint(newPointObj, opacity, animation, true, true); // Destroy old animated points. for (const p of oldPoints) { p.point?.destroy?.(); } } /** * Util function. * @internal */ function fadeInStatePoint(stateObj, opacity, animation, fadeinGraphic, fadeinDataLabel) { if (stateObj.point) { if (fadeinGraphic && stateObj.point.graphic) { stateObj.point.graphic.show(); fadeInElement(stateObj.point.graphic, opacity, animation); } if (fadeinDataLabel && stateObj.point.dataLabel) { stateObj.point.dataLabel.show(); fadeInElement(stateObj.point.dataLabel, opacity, animation); } } } /** * Util function. * @internal */ function getClusterPosition(points) { const pointsLen = points.length; let sumX = 0, sumY = 0; for (let i = 0; i < pointsLen; i++) { sumX += points[i].x; sumY += points[i].y; } return { x: sumX / pointsLen, y: sumY / pointsLen }; } /** * Util function.Prepare array with sorted data objects to be compared in * getPointsState method. * @internal */ function getDataState(clusteredData, stateDataLen) { const state = []; state.length = stateDataLen; clusteredData.clusters.forEach(function (cluster) { cluster.data.forEach(function (elem) { state[elem.dataIndex] = elem; }); }); clusteredData.noise.forEach(function (noise) { state[noise.data[0].dataIndex] = noise.data[0]; }); return state; } /** * Util function. Generate unique stateId for a state element. * @internal */ function getStateId() { return Math.random().toString(36).substring(2, 7) + '-' + stateIdCounter++; } /** * Util function. * @internal */ function hideStatePoint(stateObj, hideGraphic, hideDataLabel) { if (stateObj.point) { if (hideGraphic && stateObj.point.graphic) { stateObj.point.graphic.hide(); } if (hideDataLabel && stateObj.point.dataLabel) { stateObj.point.dataLabel.hide(); } } } /** * Handle point drill-to-cluster click. * @internal */ function onPointDrillToCluster(event) { const point = event.point || event.target; point.firePointEvent('drillToCluster', event, function (e) { const point = e.point || e.target, series = point.series, { xAxis, yAxis, chart } = series, { inverted, mapView, pointer } = chart, drillToCluster = series.options .cluster?.drillToCluster; if (drillToCluster && point.clusteredData) { const sortedDataX = point.clusteredData .map((data) => data.x) .sort((a, b) => a - b), sortedDataY = point.clusteredData .map((data) => data.y) .sort((a, b) => a - b), minX = sortedDataX[0], maxX = sortedDataX[sortedDataX.length - 1], minY = sortedDataY[0], maxY = sortedDataY[sortedDataY.length - 1], offsetX = Math.abs((maxX - minX) * 0.1), offsetY = Math.abs((maxY - minY) * 0.1), x1 = Math.min(minX, maxX) - offsetX, x2 = Math.max(minX, maxX) + offsetX, y1 = Math.min(minY, maxY) - offsetY, y2 = Math.max(minY, maxY) + offsetY; if (mapView) { mapView.fitToBounds({ x1, x2, y1, y2 }); } else if (xAxis && yAxis) { let x1Px = xAxis.toPixels(x1), x2Px = xAxis.toPixels(x2), y1Px = yAxis.toPixels(y1), y2Px = yAxis.toPixels(y2); if (inverted) { [x1Px, x2Px, y1Px, y2Px] = [y1Px, y2Px, x1Px, x2Px]; } if (x1Px > x2Px) { [x1Px, x2Px] = [x2Px, x1Px]; } if (y1Px > y2Px) { [y1Px, y2Px] = [y2Px, y1Px]; } if (pointer) {