UNPKG

@highcharts/dashboards

Version:
1,057 lines (1,052 loc) 373 kB
/** * @license Highcharts Dashboards v1.3.1 (2024-02-14) * * (c) 2009-2024 Highsoft AS * * License: www.highcharts.com/license * */ (function (factory) { if (typeof module === 'object' && module.exports) { factory['default'] = factory; module.exports = factory; } else if (typeof define === 'function' && define.amd) { define('dashboards/modules/dashboards-plugin', ['dashboards'], function (Dashboards) { factory(Dashboards); factory.Dashboards = Dashboards; return factory; }); } else { factory(typeof Dashboards !== 'undefined' ? Dashboards : undefined); } }(function (Dashboards) { 'use strict'; var _modules = Dashboards ? Dashboards._modules : {}; function _registerModule(obj, path, args, fn) { if (!obj.hasOwnProperty(path)) { obj[path] = fn.apply(null, args); if (typeof CustomEvent === 'function') { window.dispatchEvent(new CustomEvent( 'DashboardsModuleLoaded', { detail: { path: path, module: obj[path] } } )); } } } _registerModule(_modules, 'Dashboards/Plugins/DataGridSyncHandlers.js', [_modules['Core/Utilities.js']], function (U) { /* * * * (c) 2009-2024 Highsoft AS * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * Authors: * - Karol Kolodziej * * */ /* eslint-disable require-jsdoc, max-len */ const { addEvent } = U; /* * * * Constants * * */ const configs = { emitters: { highlightEmitter: [ 'highlightEmitter', function () { if (this.type === 'DataGrid') { const { dataGrid, board } = this; if (board) { const { dataCursor: cursor } = board; const callbacks = []; if (!dataGrid) { return; } callbacks.push(addEvent(dataGrid.container, 'dataGridHover', (e) => { const table = this.connector && this.connector.table; if (table) { const row = e.row; const cell = row.querySelector(`.highcharts-datagrid-cell[data-original-data="${row.dataset.rowXIndex}"]`); cursor.emitCursor(table, { type: 'position', row: parseInt(row.dataset.rowIndex, 10), column: cell ? cell.dataset.columnName : void 0, state: 'dataGrid.hoverRow' }); } })); callbacks.push(addEvent(dataGrid.container, 'mouseout', () => { const table = this.connector && this.connector.table; if (table) { cursor.emitCursor(table, { type: 'position', state: 'dataGrid.hoverOut' }); } })); // Return a function that calls the callbacks return function () { callbacks.forEach((callback) => callback()); }; } } } ] }, handlers: { highlightHandler: [ 'highlightHandler', function () { const { board } = this; const handlCursor = (e) => { const cursor = e.cursor; if (cursor.type === 'position') { const { row } = cursor; const { dataGrid } = this; if (row !== void 0 && dataGrid) { const highlightedDataRow = dataGrid.container .querySelector(`.highcharts-datagrid-row[data-row-index="${row}"]`); if (highlightedDataRow) { dataGrid.toggleRowHighlight(highlightedDataRow); dataGrid.hoveredRow = highlightedDataRow; } } } }; const handleCursorOut = () => { const { dataGrid } = this; if (dataGrid) { dataGrid.toggleRowHighlight(void 0); } }; const registerCursorListeners = () => { const { dataCursor: cursor } = board; if (!cursor) { return; } const table = this.connector && this.connector.table; if (!table) { return; } cursor.addListener(table.id, 'point.mouseOver', handlCursor); cursor.addListener(table.id, 'point.mouseOut', handleCursorOut); }; const unregisterCursorListeners = () => { const cursor = board.dataCursor; const table = this.connector && this.connector.table; if (!table) { return; } cursor.addListener(table.id, 'point.mouseOver', handlCursor); cursor.addListener(table.id, 'point.mouseOut', handleCursorOut); }; if (board) { registerCursorListeners(); this.on('setConnector', () => unregisterCursorListeners()); this.on('afterSetConnector', () => registerCursorListeners()); } } ], extremesHandler: function () { const { board } = this; const handleChangeExtremes = (e) => { const cursor = e.cursor; if (cursor.type === 'position' && this.dataGrid && typeof cursor?.row === 'number') { const { row } = cursor; this.dataGrid.scrollToRow(row); } }; const registerCursorListeners = () => { const { dataCursor: cursor } = board; if (!cursor) { return; } const table = this.connector && this.connector.table; if (!table) { return; } cursor.addListener(table.id, 'xAxis.extremes.min', handleChangeExtremes); }; const unregisterCursorListeners = () => { const table = this.connector && this.connector.table; const { dataCursor: cursor } = board; if (!table) { return; } cursor.removeListener(table.id, 'xAxis.extremes.min', handleChangeExtremes); }; if (board) { registerCursorListeners(); this.on('setConnector', () => unregisterCursorListeners()); this.on('afterSetConnector', () => registerCursorListeners()); } }, visibilityHandler: function () { const component = this, { board } = component; const handleVisibilityChange = (e) => { const cursor = e.cursor, dataGrid = component.dataGrid; if (!(dataGrid && cursor.type === 'position' && cursor.column)) { return; } const columnName = cursor.column; dataGrid.update({ columns: { [columnName]: { show: cursor.state !== 'series.hide' } } }); }; const registerCursorListeners = () => { const { dataCursor: cursor } = board; if (!cursor) { return; } const table = this.connector && this.connector.table; if (!table) { return; } cursor.addListener(table.id, 'series.show', handleVisibilityChange); cursor.addListener(table.id, 'series.hide', handleVisibilityChange); }; const unregisterCursorListeners = () => { const table = this.connector && this.connector.table; const { dataCursor: cursor } = board; if (!table) { return; } cursor.removeListener(table.id, 'series.show', handleVisibilityChange); cursor.removeListener(table.id, 'series.hide', handleVisibilityChange); }; if (board) { registerCursorListeners(); this.on('setConnector', () => unregisterCursorListeners()); this.on('afterSetConnector', () => registerCursorListeners()); } } } }; const defaults = { highlight: { emitter: configs.emitters.highlightEmitter, handler: configs.handlers.highlightHandler }, extremes: { handler: configs.handlers.extremesHandler }, visibility: { handler: configs.handlers.visibilityHandler } }; return defaults; }); _registerModule(_modules, 'Dashboards/Plugins/DataGridComponent.js', [_modules['Dashboards/Components/Component.js'], _modules['Data/Converters/DataConverter.js'], _modules['Dashboards/Plugins/DataGridSyncHandlers.js'], _modules['Core/Utilities.js']], function (Component, DataConverter, DataGridSyncHandlers, U) { /* * * * (c) 2009-2024 Highsoft AS * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * Authors: * - Karol Kolodziej * * */ const { diffObjects, merge, uniqueKey } = U; /* * * * Class * * */ /** * DataGrid component for Highcharts Dashboards. * @private */ class DataGridComponent extends Component { /* * * * Static Functions * * */ /** * Default update function, if data grid has changed. This functionality can * be replaced with the {@link DataGridComponent.DataGridOptions#onUpdate} * option. * * @private * * @param e * Related keyboard event of the change. * * @param connector * Relate store of the change. */ static onUpdate(e, connector) { const inputElement = e.target; if (inputElement) { const parentRow = inputElement .closest('.highcharts-datagrid-row'); const cell = inputElement.closest('.highcharts-datagrid-cell'); const converter = new DataConverter(); if (parentRow && parentRow instanceof HTMLElement && cell && cell instanceof HTMLElement) { const dataTableRowIndex = parentRow .dataset.rowIndex; const { columnName } = cell.dataset; if (dataTableRowIndex !== void 0 && columnName !== void 0) { const table = connector.table; if (table) { let valueToSet = converter .asGuessedType(inputElement.value); if (valueToSet instanceof Date) { valueToSet = valueToSet.toString(); } table.setCell(columnName, parseInt(dataTableRowIndex, 10), valueToSet); } } } } } /** @private */ static fromJSON(json, cell) { const options = json.options; const dataGridOptions = JSON.parse(json.options.dataGridOptions || ''); const component = new DataGridComponent(cell, merge(options, { dataGridOptions, syncHandlers: DataGridComponent.syncHandlers })); component.emit({ type: 'fromJSON', json }); return component; } /* * * * Constructor * * */ constructor(cell, options) { options = merge(DataGridComponent.defaultOptions, options); super(cell, options); this.connectorListeners = []; this.options = options; this.type = 'DataGrid'; if (this.options.dataGridClassName) { this.contentElement.classList.add(this.options.dataGridClassName); } if (this.options.dataGridID) { this.contentElement.id = this.options.dataGridID; } this.sync = new DataGridComponent.Sync(this, this.syncHandlers); this.dataGridOptions = (this.options.dataGridOptions || {}); this.innerResizeTimeouts = []; this.on('afterSetConnector', (e) => { this.disableEditingModifiedColumns(e.connector); }); } onTableChanged() { if (this.dataGrid && !this.dataGrid?.cellInputEl) { this.dataGrid.update({ dataTable: this.filterColumns() }); } } /** * Disable editing of the columns that are modified by the data modifier. * @internal * * @param connector * Attached connector */ disableEditingModifiedColumns(connector) { const options = this.getColumnOptions(connector); this.dataGrid?.update({ columns: options }); } /** * Get the column options for the data grid. * @internal */ getColumnOptions(connector) { const modifierOptions = connector.options.dataModifier; if (!modifierOptions || modifierOptions.type !== 'Math') { return {}; } const modifierColumns = modifierOptions.columnFormulas; if (!modifierColumns) { return {}; } const options = {}; for (let i = 0, iEnd = modifierColumns.length; i < iEnd; ++i) { const columnName = modifierColumns[i].column; options[columnName] = { editable: false }; } return options; } /* * * * Class methods * * */ /** * Triggered on component initialization. * @private */ async load() { this.emit({ type: 'load' }); await super.load(); if (this.connector && !this.connectorListeners.length) { const connectorListeners = this.connectorListeners; // Reload the store when polling. connectorListeners.push(this.connector .on('afterLoad', (e) => { if (e.table && this.connector) { this.connector.table.setColumns(e.table.getColumns()); } })); // Update the DataGrid when connector changed. connectorListeners.push(this.connector.table .on('afterSetCell', (e) => { const dataGrid = this.dataGrid; let shouldUpdateTheGrid = true; if (dataGrid) { const row = dataGrid.rowElements[e.rowIndex]; let cells = []; if (row) { cells = Array.prototype.slice.call(row.childNodes); } cells.forEach((cell) => { if (cell.childElementCount > 0) { const input = cell.childNodes[0], convertedInputValue = typeof e.cellValue === 'string' ? input.value : +input.value; if (cell.dataset.columnName === e.columnName && convertedInputValue === e.cellValue) { shouldUpdateTheGrid = false; } } }); } shouldUpdateTheGrid ? this.update({}) : void 0; })); } this.emit({ type: 'afterLoad' }); return this; } /** @private */ render() { super.render(); if (!this.dataGrid) { this.dataGrid = this.constructDataGrid(); } if (this.connector && this.dataGrid && this.dataGrid.dataTable.modified !== this.connector.table.modified) { this.dataGrid.update({ dataTable: this.filterColumns() }); } this.sync.start(); this.emit({ type: 'afterRender' }); this.setupConnectorUpdate(); return this; } /** @private */ resize(width, height) { if (this.dataGrid) { super.resize(width, height); } } async update(options) { if (options.connector?.id !== this.connectorId) { const connectorListeners = this.connectorListeners; for (let i = 0, iEnd = connectorListeners.length; i < iEnd; ++i) { connectorListeners[i](); } connectorListeners.length = 0; } await super.update(options); if (this.dataGrid) { this.filterAndAssignSyncOptions(DataGridSyncHandlers); this.dataGrid.update(this.options.dataGridOptions || {}); } this.emit({ type: 'afterUpdate' }); } /** @private */ constructDataGrid() { if (DataGridComponent.DataGridConstructor) { const columnOptions = this.connector ? this.getColumnOptions(this.connector) : {}; this.dataGrid = new DataGridComponent.DataGridConstructor(this.contentElement, { ...this.options.dataGridOptions, dataTable: this.options.dataGridOptions?.dataTable || this.filterColumns(), columns: merge(columnOptions, this.options.dataGridOptions?.columns) }); return this.dataGrid; } throw new Error('DataGrid not connected.'); } setupConnectorUpdate() { const { connector, dataGrid } = this; if (connector && dataGrid) { dataGrid.on('cellClick', (e) => { if ('input' in e) { e.input.addEventListener('keyup', (keyEvent) => this.options.onUpdate(keyEvent, connector)); } }); } } /** * Based on the `visibleColumns` option, filter the columns of the table. * * @internal */ filterColumns() { const table = this.connector?.table.modified, visibleColumns = this.options.visibleColumns; if (table) { // Show all columns if no visibleColumns is provided. if (!visibleColumns?.length) { return table; } const columnsToDelete = table .getColumnNames() .filter((columnName) => (visibleColumns?.length > 0 && // Don't add columns that are not listed. !visibleColumns.includes(columnName) // Else show the other columns. )); // On a fresh table clone remove the columns that are not mapped. const filteredTable = table.clone(); filteredTable.deleteColumns(columnsToDelete); return filteredTable; } } getOptionsOnDrop(sidebar) { const connectorsIds = sidebar.editMode.board.dataPool.getConnectorIds(); let options = { cell: '', type: 'DataGrid' }; if (connectorsIds.length) { options = { ...options, connector: { id: connectorsIds[0] } }; } return options; } /** @private */ toJSON() { const dataGridOptions = JSON.stringify(this.options.dataGridOptions); const base = super.toJSON(); const json = { ...base, options: { ...base.options, dataGridOptions } }; this.emit({ type: 'toJSON', json }); return json; } /** * Get the DataGrid component's options. * @returns * The JSON of DataGrid component's options. * * @internal * */ getOptions() { return { ...diffObjects(this.options, DataGridComponent.defaultOptions), type: 'DataGrid' }; } /** * Destroys the data grid component. */ destroy() { this.dataGrid?.containerResizeObserver.disconnect(); super.destroy(); } } /* * * * Static Properties * * */ /** @private */ DataGridComponent.syncHandlers = DataGridSyncHandlers; /** @private */ DataGridComponent.defaultOptions = merge(Component.defaultOptions, { dataGridClassName: 'dataGrid-container', dataGridID: 'dataGrid-' + uniqueKey(), dataGridOptions: {}, editableOptions: [{ name: 'connectorName', propertyPath: ['connector', 'id'], type: 'select' }], syncHandlers: DataGridSyncHandlers, onUpdate: DataGridComponent.onUpdate }); /* * * * Default Export * * */ return DataGridComponent; }); _registerModule(_modules, 'Dashboards/Plugins/DataGridPlugin.js', [_modules['Dashboards/Plugins/DataGridComponent.js']], function (DataGridComponent) { /* * * * (c) 2009-2024 Highsoft AS * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * Authors: * - Karol Kolodziej * * */ /* * * * Functions * * */ /** * Connects DataGrid with the Dashboard plugin. * * @param {Dashboards.DataGrid} dataGrid DataGrid core to connect. */ function connectDataGrid(DataGridClass) { DataGridComponent.DataGridConstructor = DataGridClass; } /** * Callback function of the Dashboard plugin. * * @param {Dashboards.PluginHandler.Event} e * Plugin context provided by the Dashboard. */ function onRegister(e) { const { ComponentRegistry } = e; ComponentRegistry.registerComponent('DataGrid', DataGridComponent); } /** * Callback function of the Dashboard plugin. * * @param {Dashboard.PluginHandler.Event} e Plugin context provided by the Dashboard. */ function onUnregister( // eslint-disable-next-line @typescript-eslint/no-unused-vars e) { } /* * * * Default Export * * */ const DataGridCustom = { connectDataGrid }; const DataGridPlugin = { custom: DataGridCustom, name: 'DataGrid.DashboardsPlugin', onRegister, onUnregister }; return DataGridPlugin; }); _registerModule(_modules, 'Dashboards/Plugins/HighchartsSyncHandlers.js', [_modules['Core/Utilities.js']], function (U) { /* * * * (c) 2009-2024 Highsoft AS * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * Authors: * - Gøran Slettemark * - Sophie Bremer * * */ /* eslint-disable require-jsdoc, max-len */ const { addEvent } = U; /** * Utility function that returns the first row index * if the table has been modified by a range modifier * * @param {DataTable} table * The table to get the offset from. * * @param {RangeModifierOptions} modifierOptions * The modifier options to use * * @return {number} * The row offset of the modified table. */ function getModifiedTableOffset(table, modifierOptions) { const { ranges } = modifierOptions; if (ranges) { const minRange = ranges.reduce((minRange, currentRange) => { if (currentRange.minValue > minRange.minValue) { minRange = currentRange; } return minRange; }, ranges[0]); const tableRowIndex = table.getRowIndexBy(minRange.column, minRange.minValue); if (tableRowIndex) { return tableRowIndex; } } return 0; } /* * * * Constants * * */ const configs = { emitters: { highlightEmitter: [ 'highlightEmitter', function () { if (this.type === 'Highcharts') { const { chart, board } = this; if (board) { const { dataCursor: cursor } = board; this.on('afterRender', () => { const table = this.connector && this.connector.table; if (chart && chart.series && table) { chart.series.forEach((series) => { series.update({ point: { events: { // Emit table cursor mouseOver: function () { let offset = 0; const modifier = table.getModifier(); if (modifier && modifier.options.type === 'Range') { offset = getModifiedTableOffset(table, modifier.options); } cursor.emitCursor(table, { type: 'position', row: offset + this.index, column: series.name, state: 'point.mouseOver' }); }, mouseOut: function () { let offset = 0; const modifier = table.getModifier(); if (modifier && modifier.options.type === 'Range') { offset = getModifiedTableOffset(table, modifier.options); } cursor.emitCursor(table, { type: 'position', row: offset + this.index, column: series.name, state: 'point.mouseOut' }); } } } }); }); } }); // Return function that handles cleanup return function () { if (chart && chart.series) { chart.series.forEach((series) => { series.update({ point: { events: { mouseOver: void 0, mouseOut: void 0 } } }); }); } }; } } } ], seriesVisibilityEmitter: function () { if (this.type === 'Highcharts') { const component = this; return this.on('afterRender', () => { const { chart, connector, board } = component; const table = connector && connector.table; if (table && // Has a connector board && chart) { const { dataCursor: cursor } = board; const { series } = chart; series.forEach((series) => { series.update({ events: { show: function () { cursor.emitCursor(table, { type: 'position', state: 'series.show', column: this.name }); }, hide: function () { cursor.emitCursor(table, { type: 'position', state: 'series.hide', column: this.name }); } } }); }); } }); } }, extremesEmitter: function () { if (this.type === 'Highcharts') { const component = this; const callbacks = []; this.on('afterRender', () => { const { chart, connector, board } = component; const table = connector && connector.table; const { dataCursor: cursor } = board; if (table && chart) { const extremesEventHandler = (e) => { const reset = !!e.resetSelection; if ((!e.trigger || (e.trigger && e.trigger !== 'dashboards-sync')) && !reset) { // TODO: investigate this type? const axis = e.target; // Prefer a series that's in a related table, // but allow for other data const seriesInTable = axis.series .filter((series) => table.hasColumns([series.name])); const [series] = seriesInTable.length ? seriesInTable : axis.series; if (series) { // Get the indexes of the first and last drawn points const visiblePoints = series.points .filter((point) => point.isInside || false); const minCursorData = { type: 'position', state: `${axis.coll}.extremes.min` }; const maxCursorData = { type: 'position', state: `${axis.coll}.extremes.max` }; if (seriesInTable.length && axis.coll === 'xAxis' && visiblePoints.length) { const columnName = axis.dateTime && table.hasColumns(['x']) ? 'x' : series.name; minCursorData.row = visiblePoints[0].index; minCursorData.column = columnName; maxCursorData.row = visiblePoints[visiblePoints.length - 1].index; maxCursorData.column = columnName; } // Emit as lasting cursors cursor.emitCursor(table, minCursorData, e, true).emitCursor(table, maxCursorData, e, true); } } }; const addExtremesEvent = () => chart.axes.map((axis) => addEvent(axis, 'afterSetExtremes', extremesEventHandler)); let addExtremesEventCallbacks = addExtremesEvent(); const resetExtremesEvent = () => { addExtremesEventCallbacks.forEach((callback) => { callback(); }); addExtremesEventCallbacks = []; }; const handleChartResetSelection = (e) => { if (e.resetSelection) { resetExtremesEvent(); cursor.emitCursor(table, { type: 'position', state: 'chart.zoomOut' }, e); addExtremesEventCallbacks.push(...addExtremesEvent()); } }; callbacks.push(addEvent(chart, 'selection', handleChartResetSelection)); callbacks.push(() => { cursor.remitCursor(table.id, { type: 'position', state: 'xAxis.extremes.min' }); cursor.remitCursor(table.id, { type: 'position', state: 'xAxis.extremes.max' }); resetExtremesEvent(); }); } }); // Return cleanup return function () { // Call back the cleanup callbacks callbacks.forEach((callback) => callback()); }; } } }, handlers: { seriesVisibilityHandler: function () { const component = this; const { board } = this; const findSeries = (seriesArray, name) => { for (const series of seriesArray) { if (series.name === name) { return series; } } }; const handleShow = (e) => { const chart = component.chart; if (!chart) { return; } if (e.cursor.type === 'position' && e.cursor.column !== void 0) { const series = findSeries(chart.series, e.cursor.column); if (series) { series.setVisible(true, true); } } }; const handleHide = (e) => { const chart = component.chart; if (!chart) { return; } if (e.cursor.type === 'position' && e.cursor.column !== void 0) { const series = findSeries(chart.series, e.cursor.column); if (series) { series.setVisible(false, true); } } }; const registerCursorListeners = () => { const { dataCursor } = board; if (!dataCursor) { return; } const table = this.connector && this.connector.table; if (!table) { return; } dataCursor.addListener(table.id, 'series.show', handleShow); dataCursor.addListener(table.id, 'series.hide', handleHide); }; const unregisterCursorListeners = () => { const table = this.connector && this.connector.table; if (table) { board.dataCursor.removeListener(table.id, 'series.show', handleShow); board.dataCursor.removeListener(table.id, 'series.hide', handleHide); } }; if (board) { registerCursorListeners(); this.on('setConnector', () => unregisterCursorListeners()); this.on('afterSetConnector', () => registerCursorListeners()); } }, highlightHandler: function () { const { chart, board } = this; const handleCursor = (e) => { const table = this.connector && this.connector.table; if (!table) { return; } let offset = 0; const modifier = table.getModifier(); if (modifier && modifier.options.type === 'Range') { offset = getModifiedTableOffset(table, modifier.options); } if (chart && chart.series.length) { const cursor = e.cursor; if (cursor.type === 'position') { let [series] = chart.series; // #20133 - Highcharts dashboards don't sync // tooltips when charts have multiple series if (chart.series.length > 1 && cursor.column) { const relatedSeries = chart.series.filter((series) => series.name === cursor.column); if (relatedSeries.length > 0) { [series] = relatedSeries; } } if (series?.visible && cursor.row !== void 0) { const point = series.points[cursor.row - offset], useSharedTooltip = chart.tooltip?.shared; if (point) { const hoverPoint = chart.hoverPoint, hoverSeries = hoverPoint?.series || chart.hoverSeries, points = chart.pointer.getHoverData(point, hoverSeries, chart.series, true, true); chart.tooltip && chart.tooltip.refresh(useSharedTooltip ? points.hoverPoints : point); } } } } }; const handleCursorOut = () => { if (chart && chart.series.length) { chart.tooltip && chart.tooltip.hide(); } }; const registerCursorListeners = () => { const { dataCursor: cursor } = board; // @todo wrap in a listener on component.update with // connector change if (cursor) { const table = this.connector && this.connector.table; if (table) { cursor.addListener(table.id, 'point.mouseOver', handleCursor); cursor.addListener(table.id, 'dataGrid.hoverRow', handleCursor); cursor.addListener(table.id, 'point.mouseOut', handleCursorOut); cursor.addListener(table.id, 'dataGrid.hoverOut', handleCursorOut); } } }; const unregisterCursorListeners = () => { const table = this.connector && this.connector.table; if (table) { board.dataCursor.removeListener(table.id, 'point.mouseOver', handleCursor); board.dataCursor.removeListener(table.id, 'dataGrid.hoverRow', handleCursor); board.dataCursor.removeListener(table.id, 'point.mouseOut', handleCursorOut); board.dataCursor.removeListener(table.id, 'dataGrid.hoverOut', handleCursorOut); } }; if (board) { registerCursorListeners(); this.on('setConnector', () => unregisterCursorListeners()); this.on('afterSetConnector', () => registerCursorListeners()); } }, extremesHandler: function () { const { chart, board } = this; if (chart && board && chart.zooming?.type) { const dimensions = chart.zooming.type.split('') .map((c) => c + 'Axis'); dimensions.forEach((dimension) => { const callbacks = []; const handleUpdateExtremes = (e) => { const { cursor, event } = e; if (cursor.type === 'position') { const eventTarget = event && event.target; if (eventTarget && chart) { const axes = chart[dimension]; let didZoom = false; axes.forEach((axis) => { if (eventTarget.coll === axis.coll &&