@highcharts/dashboards
Version:
Highcharts Dashboards framework
1,057 lines (1,052 loc) • 373 kB
JavaScript
/**
* @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 &&