@highcharts/dashboards
Version:
Highcharts Dashboards framework
339 lines (338 loc) • 13.5 kB
JavaScript
/* *
*
* (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:
* - Dawid Draguła
*
* */
'use strict';
import U from '../../../Utilities.js';
const { error } = U;
/* *
*
* Constants
*
* */
const defaultOptions = {
affectedSeriesId: null,
highlightPoint: true,
showTooltip: true,
showCrosshair: true
};
const syncPair = {
emitter: function () {
if (this.type !== 'Highcharts') {
return;
}
const component = this;
const { chart, board } = component;
const highlightOptions = this.sync.syncConfig.highlight;
const groupKey = highlightOptions.group ?
':' + highlightOptions.group : '';
if (!highlightOptions.enabled || !chart) {
return;
}
const { dataCursor: cursor } = board;
for (let i = 0, iEnd = chart.series?.length ?? 0; i < iEnd; ++i) {
const series = chart.series[i];
const seriesId = series.options.id ?? '';
const connectorHandler = component.seriesFromConnector[seriesId];
const connectorId = connectorHandler?.options.id;
const table = this.getDataTable(connectorId);
let columnId;
if (!table) {
continue;
}
const presTable = table?.getModified();
const colAssignment = connectorHandler?.columnAssignment?.find((s) => s.seriesId === seriesId);
// TODO: Better way to recognize the column name.
if (colAssignment) {
const { data } = colAssignment;
if (typeof data === 'string') {
columnId = data;
}
else if (Array.isArray(data)) {
columnId = data[1];
}
else {
columnId = data.y ?? data.value;
}
}
if (!columnId) {
columnId = series.name;
}
series.update({
point: {
events: {
// Emit table cursor
mouseOver: function () {
cursor.emitCursor(table, {
type: 'position',
row: presTable.getOriginalRowIndex(this.index),
column: columnId,
state: 'point.mouseOver' + groupKey,
sourceId: component.id
});
},
mouseOut: function () {
cursor.emitCursor(table, {
type: 'position',
row: presTable.getOriginalRowIndex(this.index),
column: columnId,
state: 'point.mouseOut' + groupKey,
sourceId: component.id
});
}
}
}
}, false);
}
chart.redraw();
// 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
}
}
}, false);
});
chart.redraw();
}
};
},
handler: function () {
if (this.type !== 'Highcharts') {
return;
}
const component = this;
const groupKey = this.sync.syncConfig.highlight.group ?
':' + this.sync.syncConfig.highlight.group : '';
const { chart, board } = component;
const getHoveredPoint = (e) => {
const { table, cursor } = e;
const highlightOptions = this.sync
.syncConfig.highlight;
if (chart && chart.series?.length && cursor.type === 'position') {
let series;
const seriesId = highlightOptions.affectedSeriesId;
if (highlightOptions.affectedSeriesId) {
const foundSeries = chart.get(highlightOptions.affectedSeriesId);
if (foundSeries?.points) {
series = foundSeries;
}
else {
error('No series with ID \'' + seriesId + '\' found in ' +
'the chart. Affected series will be selected ' +
'according to the column assignment.');
}
}
if (!series) {
const seriesIds = Object.keys(component.seriesFromConnector);
for (let i = 0, iEnd = seriesIds.length; i < iEnd; ++i) {
const seriesId = seriesIds[i];
const connectorHandler = component.seriesFromConnector[seriesId];
const dataTableKey = connectorHandler?.options.dataTableKey;
const connectorTable = connectorHandler?.connector?.getTable(dataTableKey);
if (connectorTable !== table) {
continue;
}
const colAssignment = connectorHandler.columnAssignment;
series = chart.get(seriesId);
if (!colAssignment) {
break;
}
const { data } = colAssignment.find((s) => s.seriesId === seriesId) ?? {};
if (!data || !cursor.column) {
break;
}
if (typeof data === 'string') {
if (data === cursor.column) {
break;
}
}
else if (Array.isArray(data)) {
if (data.includes(cursor.column)) {
break;
}
}
else {
if (Object.keys(data)
.map((key) => data[key])
.includes(cursor.column)) {
break;
}
}
}
}
const row = cursor.row;
if (series?.visible && row !== void 0) {
const rowIndex = table.getModified().getLocalRowIndex(row);
if (rowIndex === void 0) {
return;
}
const point = series.data[rowIndex];
if (point?.visible) {
return point;
}
}
}
};
const handleCursor = (e) => {
const highlightOptions = this.sync
.syncConfig.highlight;
if (!highlightOptions.enabled ||
e.cursor.sourceId === component.id) {
return;
}
const point = getHoveredPoint(e);
if (!point || !chart ||
// Non-cartesian points do not use 'isInside'
(!point.isInside && point.series.isCartesian) ||
// Abort if the affected chart is the same as the one
// that is currently affected manually.
point === chart.hoverPoint) {
return;
}
const tooltip = chart.tooltip;
if (tooltip && highlightOptions.showTooltip) {
const useSharedTooltip = tooltip.shared;
const hoverPoint = chart.hoverPoint;
const hoverSeries = hoverPoint?.series ||
chart.hoverSeries;
const points = chart.pointer?.getHoverData(point, hoverSeries, chart.series, true, true);
if (chart.tooltip && points?.hoverPoints.length) {
tooltip.refresh(useSharedTooltip ? points.hoverPoints : point);
}
}
if (highlightOptions.highlightPoint && (
// If the tooltip is shared, the hover state is
// already set on the point.
(!tooltip?.shared && highlightOptions.showTooltip) ||
!highlightOptions.showTooltip)) {
point.setState('hover');
}
if (highlightOptions.showCrosshair) {
point.series.xAxis?.drawCrosshair(void 0, point);
point.series.yAxis?.drawCrosshair(void 0, point);
}
};
const handleCursorOut = (e) => {
const highlightOptions = this.sync
.syncConfig.highlight;
if (!chart || !chart.series.length ||
!highlightOptions.enabled ||
e.cursor.sourceId === component.id) {
return;
}
const point = getHoveredPoint(e);
// Abort if the affected chart is the same as the one
// that is currently affected manually.
if (point && (!point.isInside && point.series.isCartesian ||
point === chart.hoverPoint)) {
return;
}
let unhovered = false;
const unhoverAllPoints = () => {
// If the 'row' parameter is missing in the event
// object, the unhovered point cannot be identified.
const series = chart.series;
const seriesLength = series.length;
for (let i = 0; i < seriesLength; i++) {
const points = chart.series[i].points;
const pointsLength = points.length;
for (let j = 0; j < pointsLength; j++) {
points[j].setState();
}
}
};
const tooltip = chart.tooltip;
if (tooltip && highlightOptions.showTooltip) {
tooltip.hide();
// Shared tooltip refresh always hovers points, so it's
// important to unhover all points on cursor out.
if (tooltip.shared) {
unhoverAllPoints();
unhovered = true;
}
}
if (highlightOptions.highlightPoint && !unhovered) {
if (point) {
point.setState();
}
else {
unhoverAllPoints();
}
}
if (highlightOptions.showCrosshair) {
if (point) {
point.series.xAxis?.drawCrosshair();
point.series.yAxis?.drawCrosshair();
}
else {
// If the 'row' parameter is missing in the event
// object, the unhovered point cannot be identified.
const xAxes = chart.xAxis;
const yAxes = chart.yAxis;
for (let i = 0, l = xAxes.length; i < l; i++) {
xAxes[i].drawCrosshair();
}
for (let i = 0, l = yAxes.length; i < l; i++) {
yAxes[i].drawCrosshair();
}
}
}
};
const registerCursorListeners = () => {
const { dataCursor: cursor } = board;
const { connectorHandlers } = this;
if (!cursor) {
return;
}
for (let i = 0, iEnd = connectorHandlers.length; i < iEnd; ++i) {
const connectorId = connectorHandlers[i]?.options.id;
const table = this.getDataTable(connectorId);
if (!table) {
continue;
}
cursor.addListener(table.id, 'point.mouseOver' + groupKey, handleCursor);
cursor.addListener(table.id, 'point.mouseOut' + groupKey, handleCursorOut);
}
};
const unregisterCursorListeners = () => {
const { dataCursor: cursor } = board;
const { connectorHandlers } = this;
if (!cursor) {
return;
}
for (let i = 0, iEnd = connectorHandlers.length; i < iEnd; ++i) {
const table = connectorHandlers[i]?.connector?.getTable();
if (!table) {
continue;
}
cursor.removeListener(table.id, 'point.mouseOver' + groupKey, handleCursor);
cursor.removeListener(table.id, 'point.mouseOut' + groupKey, handleCursorOut);
}
};
if (board) {
registerCursorListeners();
return unregisterCursorListeners;
}
}
};
/* *
*
* Default export
*
* */
export default { defaultOptions, syncPair };