CursorSync: Extract EventBus logic into shared plugin (#84111)

This commit is contained in:
Leon Sorokin
2024-03-12 17:15:38 -05:00
committed by GitHub
parent 3f5526b915
commit 220fea966b
24 changed files with 271 additions and 317 deletions

View File

@@ -1159,11 +1159,9 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
[0, 0, 0, "Do not use any type assertions.", "5"],
[0, 0, 0, "Do not use any type assertions.", "6"],
[0, 0, 0, "Do not use any type assertions.", "7"],
[0, 0, 0, "Unexpected any. Specify a different type.", "8"],
[0, 0, 0, "Do not use any type assertions.", "9"],
[0, 0, 0, "Do not use any type assertions.", "10"],
[0, 0, 0, "Do not use any type assertions.", "11"]
[0, 0, 0, "Unexpected any. Specify a different type.", "7"],
[0, 0, 0, "Do not use any type assertions.", "8"],
[0, 0, 0, "Do not use any type assertions.", "9"]
],
"public/app/core/components/GraphNG/hooks.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]

View File

@@ -1,16 +1,7 @@
import { merge } from 'lodash';
import uPlot, { Cursor, Band, Hooks, Select, AlignedData, Padding, Series } from 'uplot';
import {
DataFrame,
DefaultTimeZone,
EventBus,
Field,
getTimeZoneInfo,
GrafanaTheme2,
TimeRange,
TimeZone,
} from '@grafana/data';
import { DataFrame, DefaultTimeZone, Field, getTimeZoneInfo, GrafanaTheme2, TimeRange, TimeZone } from '@grafana/data';
import { AxisPlacement, VizOrientation } from '@grafana/schema';
import { FacetedData, PlotConfig, PlotTooltipInterpolator } from '../types';
@@ -306,7 +297,6 @@ type UPlotConfigPrepOpts<T extends Record<string, unknown> = {}> = {
theme: GrafanaTheme2;
timeZones: TimeZone[];
getTimeRange: () => TimeRange;
eventBus: EventBus;
allFrames: DataFrame[];
renderers?: Renderers;
tweakScale?: (opts: ScaleProps, forField: Field) => ScaleProps;

View File

@@ -0,0 +1,164 @@
import { throttle } from 'lodash';
import { useLayoutEffect, useRef } from 'react';
import { Subscription } from 'rxjs';
import { throttleTime } from 'rxjs/operators';
import {
DataFrame,
DataHoverClearEvent,
DataHoverEvent,
DataHoverPayload,
EventBus,
LegacyGraphHoverEvent,
} from '@grafana/data';
import { UPlotConfigBuilder } from '../config/UPlotConfigBuilder';
interface EventBusPluginProps {
config: UPlotConfigBuilder;
eventBus: EventBus;
sync: () => boolean;
frame?: DataFrame;
}
/**
* @alpha
*/
export const EventBusPlugin = ({ config, eventBus, sync, frame }: EventBusPluginProps) => {
const frameRef = useRef<DataFrame | undefined>(frame);
frameRef.current = frame;
useLayoutEffect(() => {
let u: uPlot | null = null;
const payload: DataHoverPayload = {
point: {
time: null,
},
data: frameRef.current,
};
config.addHook('init', (_u) => {
u = _u;
});
let closestSeriesIdx: number | null = null;
config.addHook('setSeries', (u, seriesIdx) => {
closestSeriesIdx = seriesIdx;
});
config.addHook('setLegend', () => {
let viaSync = u!.cursor.event == null;
if (!viaSync && sync()) {
let dataIdx = u!.cursor.idxs!.find((v) => v != null);
if (dataIdx == null) {
throttledClear();
} else {
let rowIdx = dataIdx;
let colIdx = closestSeriesIdx;
let xData = u!.data[0] ?? u!.data[1][0];
payload.point.time = xData[rowIdx];
payload.rowIndex = rowIdx ?? undefined;
payload.columnIndex = colIdx ?? undefined;
payload.data = frameRef.current;
// used by old graph panel to position tooltip
let top = u!.cursor.top!;
payload.point.panelRelY = top === 0 ? 0.001 : top > 0 ? top / u!.rect.height : 1;
throttledHover();
}
}
});
function handleCursorUpdate(evt: DataHoverEvent | LegacyGraphHoverEvent) {
const time = evt.payload?.point?.time;
if (time) {
// Try finding left position on time axis
const left = u!.valToPos(time, 'x');
// let top;
// if (left) {
// top = findMidPointYPosition(u!, u!.posToIdx(left));
// }
// if (!top || !left) {
// return;
// }
u!.setCursor({
left,
top: u!.rect.height / 2,
});
}
}
const subscription = new Subscription();
const hoverEvent = new DataHoverEvent(payload).setTags(['uplot']);
const clearEvent = new DataHoverClearEvent().setTags(['uplot']);
let throttledHover = throttle(() => {
eventBus.publish(hoverEvent);
}, 100);
let throttledClear = throttle(() => {
eventBus.publish(clearEvent);
}, 100);
subscription.add(
eventBus.getStream(DataHoverEvent).subscribe({
next: (evt) => {
// ignore uplot-emitted events, since we already use uPlot's sync
if (eventBus === evt.origin || evt.tags?.has('uplot')) {
return;
}
handleCursorUpdate(evt);
},
})
);
// Legacy events (from flot graph)
subscription.add(
eventBus.getStream(LegacyGraphHoverEvent).subscribe({
next: (evt) => handleCursorUpdate(evt),
})
);
subscription.add(
eventBus
.getStream(DataHoverClearEvent)
.pipe(throttleTime(50)) // dont throttle here, throttle on emission
.subscribe({
next: (evt) => {
// ignore uplot-emitted events, since we already use uPlot's sync
if (eventBus === evt.origin || evt.tags?.has('uplot')) {
return;
}
// @ts-ignore
if (!u!.cursor._lock) {
u!.setCursor({
left: -10,
top: -10,
});
}
},
})
);
return () => {
subscription.unsubscribe();
};
}, [config]);
return null;
};

View File

@@ -501,9 +501,9 @@ export const TooltipPlugin2 = ({
}
});
const onscroll = () => {
const onscroll = (e: Event) => {
updatePlotVisible();
_isHovering && !_isPinned && dismiss();
_isHovering && !_isPinned && e.target instanceof HTMLElement && e.target.contains(_plot!.root) && dismiss();
};
window.addEventListener('resize', updateWinSize);

View File

@@ -1,4 +1,5 @@
export { ZoomPlugin } from './ZoomPlugin';
export { TooltipPlugin } from './TooltipPlugin';
export { TooltipPlugin2 } from './TooltipPlugin2';
export { EventBusPlugin } from './EventBusPlugin';
export { KeyboardPlugin } from './KeyboardPlugin';

View File

@@ -75,13 +75,10 @@ exports[`GraphNG utils preparePlotConfigBuilder 1`] = `
"width": [Function],
},
"sync": {
"filters": {
"pub": [Function],
},
"key": "__global_",
"scales": [
"x",
"__fixed/na-na/na-na/auto/linear/na/number",
null,
],
},
},

View File

@@ -3,7 +3,7 @@ import {
DashboardCursorSync,
DataFrame,
DefaultTimeZone,
EventBusSrv,
// EventBusSrv,
FieldColorModeId,
FieldConfig,
FieldMatcherID,
@@ -215,7 +215,6 @@ describe('GraphNG utils', () => {
theme: createTheme(),
timeZones: [DefaultTimeZone],
getTimeRange: getDefaultTimeRange,
eventBus: new EventBusSrv(),
sync: () => DashboardCursorSync.Tooltip,
allFrames: [frame!],
}).getConfig();

View File

@@ -19,7 +19,7 @@ export class UnthemedTimeSeries extends Component<TimeSeriesProps> {
declare context: React.ContextType<typeof PanelContextRoot>;
prepConfig = (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => {
const { eventBus, eventsScope, sync } = this.context;
const { eventsScope, sync } = this.context;
const { theme, timeZone, renderers, tweakAxis, tweakScale } = this.props;
return preparePlotConfigBuilder({
@@ -27,7 +27,6 @@ export class UnthemedTimeSeries extends Component<TimeSeriesProps> {
theme,
timeZones: Array.isArray(timeZone) ? timeZone : [timeZone],
getTimeRange,
eventBus,
sync,
allFrames,
renderers,

View File

@@ -4,9 +4,6 @@ import uPlot from 'uplot';
import {
DashboardCursorSync,
DataFrame,
DataHoverClearEvent,
DataHoverEvent,
DataHoverPayload,
FieldConfig,
FieldType,
formattedValueToString,
@@ -81,7 +78,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
theme,
timeZones,
getTimeRange,
eventBus,
sync,
allFrames,
renderers,
@@ -107,7 +103,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
}
const xScaleKey = 'x';
let xScaleUnit = '_x';
let yScaleKey = '';
const xFieldAxisPlacement =
@@ -115,7 +110,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
const xFieldAxisShow = xField.config.custom?.axisPlacement !== AxisPlacement.Hidden;
if (xField.type === FieldType.time) {
xScaleUnit = 'time';
builder.addScale({
scaleKey: xScaleKey,
orientation: ScaleOrientation.Horizontal,
@@ -173,11 +167,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
});
}
} else {
// Not time!
if (xField.config.unit) {
xScaleUnit = xField.config.unit;
}
builder.addScale({
scaleKey: xScaleKey,
orientation: ScaleOrientation.Horizontal,
@@ -609,41 +598,9 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
};
if (sync && sync() !== DashboardCursorSync.Off) {
const payload: DataHoverPayload = {
point: {
[xScaleKey]: null,
[yScaleKey]: null,
},
data: frame,
};
const hoverEvent = new DataHoverEvent(payload);
cursor.sync = {
key: eventsScope,
filters: {
pub: (type: string, src: uPlot, x: number, y: number, w: number, h: number, dataIdx: number) => {
if (sync && sync() === DashboardCursorSync.Off) {
return false;
}
payload.rowIndex = dataIdx;
if (x < 0 && y < 0) {
payload.point[xScaleUnit] = null;
payload.point[yScaleKey] = null;
eventBus.publish(new DataHoverClearEvent());
} else {
// convert the points
payload.point[xScaleUnit] = src.posToVal(x, xScaleKey);
payload.point[yScaleKey] = src.posToVal(y, yScaleKey);
payload.point.panelRelY = y > 0 ? y / h : 1; // used by old graph panel to position tooltip
eventBus.publish(hoverEvent);
hoverEvent.payload.down = undefined;
}
return true;
},
},
scales: [xScaleKey, yScaleKey],
// match: [() => true, (a, b) => a === b],
scales: [xScaleKey, null],
};
}

View File

@@ -1,12 +1,8 @@
import React, { Component } from 'react';
import { Subscription } from 'rxjs';
import { throttleTime } from 'rxjs/operators';
import uPlot, { AlignedData } from 'uplot';
import {
DataFrame,
DataHoverClearEvent,
DataHoverEvent,
DataLinkPostProcessor,
Field,
FieldMatcherID,
@@ -14,17 +10,16 @@ import {
FieldType,
getLinksSupplier,
InterpolateFunction,
LegacyGraphHoverEvent,
TimeRange,
TimeZone,
} from '@grafana/data';
import { VizLegendOptions } from '@grafana/schema';
import { Themeable2, PanelContext, PanelContextRoot, VizLayout } from '@grafana/ui';
import { Themeable2, PanelContextRoot, VizLayout } from '@grafana/ui';
import { UPlotChart } from '@grafana/ui/src/components/uPlot/Plot';
import { AxisProps } from '@grafana/ui/src/components/uPlot/config/UPlotAxisBuilder';
import { Renderers, UPlotConfigBuilder } from '@grafana/ui/src/components/uPlot/config/UPlotConfigBuilder';
import { ScaleProps } from '@grafana/ui/src/components/uPlot/config/UPlotScaleBuilder';
import { findMidPointYPosition, pluginLog } from '@grafana/ui/src/components/uPlot/utils';
import { pluginLog } from '@grafana/ui/src/components/uPlot/utils';
import { GraphNGLegendEvent, XYFieldMatchers } from './types';
import { preparePlotFrame as defaultPreparePlotFrame } from './utils';
@@ -92,11 +87,8 @@ export interface GraphNGState {
*/
export class GraphNG extends Component<GraphNGProps, GraphNGState> {
static contextType = PanelContextRoot;
panelContext: PanelContext = {} as PanelContext;
private plotInstance: React.RefObject<uPlot>;
private subscription = new Subscription();
constructor(props: GraphNGProps) {
super(props);
let state = this.prepState(props);
@@ -180,82 +172,6 @@ export class GraphNG extends Component<GraphNGProps, GraphNGState> {
return state;
}
handleCursorUpdate(evt: DataHoverEvent | LegacyGraphHoverEvent) {
// ignore uplot-emitted events, since we already use uPlot's sync
if (evt.tags?.has('uplot')) {
return;
}
const time = evt.payload?.point?.time;
const u = this.plotInstance.current;
if (u && time) {
// Try finding left position on time axis
const left = u.valToPos(time, 'x');
let top;
if (left) {
// find midpoint between points at current idx
top = findMidPointYPosition(u, u.posToIdx(left));
}
if (!top || !left) {
return;
}
u.setCursor({
left,
top,
});
}
}
componentDidMount() {
this.panelContext = this.context as PanelContext;
const { eventBus } = this.panelContext;
this.subscription.add(
eventBus
.getStream(DataHoverEvent)
.pipe(throttleTime(50))
.subscribe({
next: (evt) => {
if (eventBus === evt.origin) {
return;
}
this.handleCursorUpdate(evt);
},
})
);
// Legacy events (from flot graph)
this.subscription.add(
eventBus
.getStream(LegacyGraphHoverEvent)
.pipe(throttleTime(50))
.subscribe({
next: (evt) => this.handleCursorUpdate(evt),
})
);
this.subscription.add(
eventBus
.getStream(DataHoverClearEvent)
.pipe(throttleTime(50))
.subscribe({
next: () => {
const u = this.plotInstance?.current;
// @ts-ignore
if (u && !u.cursor._lock) {
u.setCursor({
left: -10,
top: -10,
});
}
},
})
);
}
componentDidUpdate(prevProps: GraphNGProps) {
const { frames, structureRev, timeZone, propsToDiff } = this.props;
@@ -284,10 +200,6 @@ export class GraphNG extends Component<GraphNGProps, GraphNGState> {
}
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
render() {
const { width, height, children, renderLegend } = this.props;
const { config, alignedFrame, alignedData } = this.state;

View File

@@ -80,9 +80,6 @@ exports[`GraphNG utils preparePlotConfigBuilder 1`] = `
"width": [Function],
},
"sync": {
"filters": {
"pub": [Function],
},
"key": "__global_",
"scales": [
"x",

View File

@@ -3,7 +3,6 @@ import {
DashboardCursorSync,
DataFrame,
DefaultTimeZone,
EventBusSrv,
FieldColorModeId,
FieldConfig,
FieldMatcherID,
@@ -215,7 +214,6 @@ describe('GraphNG utils', () => {
theme: createTheme(),
timeZones: [DefaultTimeZone],
getTimeRange: getDefaultTimeRange,
eventBus: new EventBusSrv(),
sync: () => DashboardCursorSync.Tooltip,
allFrames: [frame!],
}).getConfig();

View File

@@ -19,7 +19,7 @@ export class UnthemedTimeSeries extends Component<TimeSeriesProps> {
declare context: React.ContextType<typeof PanelContextRoot>;
prepConfig = (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => {
const { eventBus, eventsScope, sync } = this.context;
const { eventsScope, sync } = this.context;
const { theme, timeZone, options, renderers, tweakAxis, tweakScale } = this.props;
return preparePlotConfigBuilder({
@@ -27,7 +27,6 @@ export class UnthemedTimeSeries extends Component<TimeSeriesProps> {
theme,
timeZones: Array.isArray(timeZone) ? timeZone : [timeZone],
getTimeRange,
eventBus,
sync,
allFrames,
renderers,

View File

@@ -4,9 +4,6 @@ import uPlot from 'uplot';
import {
DashboardCursorSync,
DataFrame,
DataHoverClearEvent,
DataHoverEvent,
DataHoverPayload,
FieldConfig,
FieldType,
formattedValueToString,
@@ -83,7 +80,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
theme,
timeZones,
getTimeRange,
eventBus,
sync,
allFrames,
renderers,
@@ -113,7 +109,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
}
const xScaleKey = 'x';
let xScaleUnit = '_x';
let yScaleKey = '';
const xFieldAxisPlacement =
@@ -125,7 +120,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
const xFieldAxisShow = xField.config.custom?.axisPlacement !== AxisPlacement.Hidden;
if (xField.type === FieldType.time) {
xScaleUnit = 'time';
builder.addScale({
scaleKey: xScaleKey,
orientation: isHorizontal ? ScaleOrientation.Horizontal : ScaleOrientation.Vertical,
@@ -185,11 +179,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
});
}
} else {
// Not time!
if (xField.config.unit) {
xScaleUnit = xField.config.unit;
}
builder.addScale({
scaleKey: xScaleKey,
orientation: isHorizontal ? ScaleOrientation.Horizontal : ScaleOrientation.Vertical,
@@ -597,40 +586,9 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
},
};
if (sync && sync() !== DashboardCursorSync.Off && xField.type === FieldType.time) {
const payload: DataHoverPayload = {
point: {
[xScaleKey]: null,
[yScaleKey]: null,
},
data: frame,
};
const hoverEvent = new DataHoverEvent(payload).setTags(['uplot']);
const clearEvent = new DataHoverClearEvent().setTags(['uplot']);
if (xField.type === FieldType.time && sync && sync() !== DashboardCursorSync.Off) {
cursor.sync = {
key: eventsScope,
filters: {
pub: (type: string, src: uPlot, x: number, y: number, w: number, h: number, dataIdx: number) => {
if (sync && sync() === DashboardCursorSync.Off) {
return false;
}
payload.rowIndex = dataIdx;
if (x < 0 && y < 0) {
eventBus.publish(clearEvent);
} else {
// convert the points
payload.point[xScaleUnit] = src.posToVal(x, xScaleKey);
payload.point[yScaleKey] = src.posToVal(y, yScaleKey);
payload.point.panelRelY = y > 0 ? y / h : 1; // used by old graph panel to position tooltip
eventBus.publish(hoverEvent);
hoverEvent.payload.down = undefined;
}
return true;
},
},
scales: [xScaleKey, null],
// match: [() => true, () => false],
};

View File

@@ -43,12 +43,11 @@ export class TimelineChart extends React.Component<TimelineProps> {
prepConfig = (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => {
this.panelContext = this.context;
const { eventBus, sync } = this.panelContext;
const { sync } = this.panelContext;
return preparePlotConfigBuilder({
frame: alignedFrame,
getTimeRange,
eventBus,
sync,
allFrames: this.props.frames,
...this.props,

View File

@@ -4,9 +4,6 @@ import uPlot from 'uplot';
import {
DataFrame,
DashboardCursorSync,
DataHoverPayload,
DataHoverEvent,
DataHoverClearEvent,
FALLBACK_COLOR,
Field,
FieldColorModeId,
@@ -99,7 +96,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<UPlotConfigOptions> = (
timeZones,
getTimeRange,
mode,
eventBus,
sync,
rowHeight,
colWidth,
@@ -112,7 +108,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<UPlotConfigOptions> = (
}) => {
const builder = new UPlotConfigBuilder(timeZones[0]);
const xScaleUnit = 'time';
const xScaleKey = 'x';
const isDiscrete = (field: Field) => {
@@ -177,16 +172,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<UPlotConfigOptions> = (
let hoveredDataIdx: number | null = null;
const coreConfig = getConfig(opts);
const payload: DataHoverPayload = {
point: {
[xScaleUnit]: null,
[FIXED_UNIT]: null,
},
data: frame,
};
const hoverEvent = new DataHoverEvent(payload).setTags(['uplot']);
const clearEvent = new DataHoverClearEvent().setTags(['uplot']);
builder.addHook('init', coreConfig.init);
builder.addHook('drawClear', coreConfig.drawClear);
@@ -294,23 +279,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<UPlotConfigOptions> = (
cursor.sync = {
key: eventsScope,
filters: {
pub: (type: string, src: uPlot, x: number, y: number, w: number, h: number, dataIdx: number) => {
if (sync && sync() === DashboardCursorSync.Off) {
return false;
}
payload.rowIndex = dataIdx;
if (x < 0 && y < 0) {
eventBus.publish(clearEvent);
} else {
payload.point[xScaleUnit] = src.posToVal(x, xScaleKey);
payload.point.panelRelY = y > 0 ? y / h : 1; // used for old graph panel to position tooltip
payload.down = undefined;
eventBus.publish(hoverEvent);
}
return true;
},
},
scales: [xScaleKey, null],
};
builder.setSync();

View File

@@ -71,7 +71,7 @@ interface Props extends PanelProps<Options> {}
export const BarChartPanel = ({ data, options, fieldConfig, width, height, timeZone, id, replaceVariables }: Props) => {
const theme = useTheme2();
const { eventBus, dataLinkPostProcessor } = usePanelContext();
const { dataLinkPostProcessor } = usePanelContext();
const oldConfig = useRef<UPlotConfigBuilder | undefined>(undefined);
const isToolTipOpen = useRef<boolean>(false);
@@ -288,7 +288,6 @@ export const BarChartPanel = ({ data, options, fieldConfig, width, height, timeZ
timeZone,
theme,
timeZones: [timeZone],
eventBus,
orientation,
barWidth,
barRadius,

View File

@@ -3,7 +3,6 @@ import { assertIsDefined } from 'test/helpers/asserts';
import {
createTheme,
DefaultTimeZone,
EventBusSrv,
FieldConfig,
FieldType,
getDefaultTimeRange,
@@ -120,7 +119,6 @@ describe('BarChart utils', () => {
theme: createTheme(),
timeZones: [DefaultTimeZone],
getTimeRange: getDefaultTimeRange,
eventBus: new EventBusSrv(),
allFrames: [frame],
}).getConfig();
expect(result).toMatchSnapshot();
@@ -135,7 +133,6 @@ describe('BarChart utils', () => {
theme: createTheme(),
timeZones: [DefaultTimeZone],
getTimeRange: getDefaultTimeRange,
eventBus: new EventBusSrv(),
allFrames: [frame],
}).getConfig()
).toMatchSnapshot();
@@ -150,7 +147,6 @@ describe('BarChart utils', () => {
theme: createTheme(),
timeZones: [DefaultTimeZone],
getTimeRange: getDefaultTimeRange,
eventBus: new EventBusSrv(),
allFrames: [frame],
}).getConfig()
).toMatchSnapshot();

View File

@@ -4,10 +4,19 @@
import React, { useMemo, useState, useCallback } from 'react';
import uPlot from 'uplot';
import { Field, getDisplayProcessor, getLinksSupplier, PanelProps } from '@grafana/data';
import { Field, getDisplayProcessor, PanelProps } from '@grafana/data';
import { PanelDataErrorView } from '@grafana/runtime';
import { DashboardCursorSync, TooltipDisplayMode } from '@grafana/schema';
import { TooltipPlugin, TooltipPlugin2, UPlotConfigBuilder, usePanelContext, useTheme2, ZoomPlugin } from '@grafana/ui';
import {
EventBusPlugin,
KeyboardPlugin,
TooltipPlugin,
TooltipPlugin2,
UPlotConfigBuilder,
usePanelContext,
useTheme2,
ZoomPlugin,
} from '@grafana/ui';
import { AxisProps } from '@grafana/ui/src/components/uPlot/config/UPlotAxisBuilder';
import { ScaleProps } from '@grafana/ui/src/components/uPlot/config/UPlotScaleBuilder';
import { TimeRange2, TooltipHoverMode } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2';
@@ -42,8 +51,15 @@ export const CandlestickPanel = ({
onChangeTimeRange,
replaceVariables,
}: CandlestickPanelProps) => {
const { sync, canAddAnnotations, onThresholdsChange, canEditThresholds, showThresholds, dataLinkPostProcessor } =
usePanelContext();
const {
sync,
canAddAnnotations,
onThresholdsChange,
canEditThresholds,
showThresholds,
dataLinkPostProcessor,
eventBus,
} = usePanelContext();
const theme = useTheme2();
@@ -54,6 +70,12 @@ export const CandlestickPanel = ({
[]
);
const syncAny = useCallback(
() => sync?.() !== DashboardCursorSync.Off,
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
const info = useMemo(() => {
return prepareCandlestickFields(data.series, options, theme, timeRange);
}, [data.series, options, theme, timeRange]);
@@ -260,19 +282,11 @@ export const CandlestickPanel = ({
replaceVariables={replaceVariables}
dataLinkPostProcessor={dataLinkPostProcessor}
>
{(uplotConfig, alignedDataFrame) => {
alignedDataFrame.fields.forEach((field) => {
field.getLinks = getLinksSupplier(
alignedDataFrame,
field,
field.state!.scopedVars!,
replaceVariables,
timeZone
);
});
{(uplotConfig, alignedFrame) => {
return (
<>
<KeyboardPlugin config={uplotConfig} />
<EventBusPlugin config={uplotConfig} sync={syncAny} eventBus={eventBus} frame={alignedFrame} />
{showNewVizTooltips ? (
<TooltipPlugin2
config={uplotConfig}
@@ -299,7 +313,7 @@ export const CandlestickPanel = ({
return (
<TimeSeriesTooltip
frames={[info.frame]}
seriesFrame={alignedDataFrame}
seriesFrame={alignedFrame}
dataIdxs={dataIdxs}
seriesIdx={seriesIdx}
mode={viaSync ? TooltipDisplayMode.Multi : options.tooltip.mode}
@@ -317,7 +331,7 @@ export const CandlestickPanel = ({
<>
<ZoomPlugin config={uplotConfig} onZoom={onChangeTimeRange} withZoomY={true} />
<TooltipPlugin
data={alignedDataFrame}
data={alignedFrame}
config={uplotConfig}
mode={TooltipDisplayMode.Multi}
sync={sync}
@@ -342,11 +356,11 @@ export const CandlestickPanel = ({
{/* Enables annotations creation*/}
{!showNewVizTooltips ? (
enableAnnotationCreation ? (
<AnnotationEditorPlugin data={alignedDataFrame} timeZone={timeZone} config={uplotConfig}>
<AnnotationEditorPlugin data={alignedFrame} timeZone={timeZone} config={uplotConfig}>
{({ startAnnotating }) => {
return (
<ContextMenuPlugin
data={alignedDataFrame}
data={alignedFrame}
config={uplotConfig}
timeZone={timeZone}
replaceVariables={replaceVariables}
@@ -377,7 +391,7 @@ export const CandlestickPanel = ({
</AnnotationEditorPlugin>
) : (
<ContextMenuPlugin
data={alignedDataFrame}
data={alignedFrame}
config={uplotConfig}
timeZone={timeZone}
replaceVariables={replaceVariables}

View File

@@ -16,6 +16,7 @@ import {
useTheme2,
VizLayout,
VizTooltipContainer,
EventBusPlugin,
} from '@grafana/ui';
import { TimeRange2, TooltipHoverMode } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2';
import { ColorScale } from 'app/core/components/ColorScale/ColorScale';
@@ -57,6 +58,12 @@ export const HeatmapPanel = ({
[]
);
const syncAny = useCallback(
() => sync?.() !== DashboardCursorSync.Off,
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
// temp range set for adding new annotation set by TooltipPlugin2, consumed by AnnotationPlugin2
const [newAnnotationRange, setNewAnnotationRange] = useState<TimeRange2 | null>(null);
@@ -140,7 +147,6 @@ export const HeatmapPanel = ({
return prepConfig({
dataRef,
theme,
eventBus,
onhover: !showNewVizTooltips ? onhover : null,
onclick: !showNewVizTooltips && options.tooltip.mode !== TooltipDisplayMode.None ? onclick : null,
isToolTipOpen,
@@ -207,6 +213,7 @@ export const HeatmapPanel = ({
<VizLayout width={width} height={height} legend={renderLegend()}>
{(vizWidth: number, vizHeight: number) => (
<UPlotChart config={builder} data={facets as any} width={vizWidth} height={vizHeight}>
<EventBusPlugin config={builder} sync={syncAny} eventBus={eventBus} frame={info.series ?? info.heatmap} />
{!showNewVizTooltips && <ZoomPlugin config={builder} onZoom={onChangeTimeRange} />}
{showNewVizTooltips && (
<>

View File

@@ -4,10 +4,6 @@ import uPlot, { Cursor } from 'uplot';
import {
DashboardCursorSync,
DataFrameType,
DataHoverClearEvent,
DataHoverEvent,
DataHoverPayload,
EventBus,
formattedValueToString,
getValueFormat,
GrafanaTheme2,
@@ -60,7 +56,6 @@ export interface HeatmapZoomEvent {
interface PrepConfigOpts {
dataRef: RefObject<HeatmapData>;
theme: GrafanaTheme2;
eventBus: EventBus;
onhover?: null | ((evt?: HeatmapHoverEvent | null) => void);
onclick?: null | ((evt?: Object) => void);
onzoom?: null | ((evt: HeatmapZoomEvent) => void);
@@ -82,7 +77,6 @@ export function prepConfig(opts: PrepConfigOpts) {
const {
dataRef,
theme,
eventBus,
onhover,
onclick,
isToolTipOpen,
@@ -98,11 +92,9 @@ export function prepConfig(opts: PrepConfigOpts) {
} = opts;
const xScaleKey = 'x';
let xScaleUnit = 'time';
let isTime = true;
if (dataRef.current?.heatmap?.fields[0].type !== FieldType.time) {
xScaleUnit = dataRef.current?.heatmap?.fields[0].config?.unit ?? 'x';
isTime = false;
}
@@ -166,16 +158,6 @@ export function prepConfig(opts: PrepConfigOpts) {
rect = r;
});
const payload: DataHoverPayload = {
point: {
[xScaleUnit]: null,
},
data: dataRef.current?.heatmap,
};
const hoverEvent = new DataHoverEvent(payload).setTags(['uplot']);
const clearEvent = new DataHoverClearEvent().setTags(['uplot']);
let pendingOnleave: ReturnType<typeof setTimeout> | 0;
onhover &&
@@ -185,9 +167,6 @@ export function prepConfig(opts: PrepConfigOpts) {
const sel = u.cursor.idxs[i];
if (sel != null) {
const { left, top } = u.cursor;
payload.rowIndex = sel;
payload.point[xScaleUnit] = u.posToVal(left!, xScaleKey);
eventBus.publish(hoverEvent);
if (!isToolTipOpen?.current) {
if (pendingOnleave) {
@@ -211,7 +190,6 @@ export function prepConfig(opts: PrepConfigOpts) {
if (!pendingOnleave) {
pendingOnleave = setTimeout(() => {
onhover(null);
eventBus.publish(clearEvent);
}, 100);
}
}
@@ -609,19 +587,6 @@ export function prepConfig(opts: PrepConfigOpts) {
cursor.sync = {
key: eventsScope,
scales: [xScaleKey, null],
filters: {
pub: (type: string, src: uPlot, x: number, y: number, w: number, h: number, dataIdx: number) => {
if (x < 0) {
payload.point[xScaleUnit] = null;
eventBus.publish(new DataHoverClearEvent());
} else {
payload.point[xScaleUnit] = src.posToVal(x, xScaleKey);
eventBus.publish(hoverEvent);
}
return true;
},
},
};
builder.setSync();

View File

@@ -4,6 +4,7 @@ import { CartesianCoords2D, DashboardCursorSync, DataFrame, FieldType, PanelProp
import { getLastStreamingDataFramePacket } from '@grafana/data/src/dataframe/StreamingDataFrame';
import { config } from '@grafana/runtime';
import {
EventBusPlugin,
Portal,
TooltipDisplayMode,
TooltipPlugin2,
@@ -59,6 +60,12 @@ export const StateTimelinePanel = ({
[]
);
const syncAny = useCallback(
() => sync?.() !== DashboardCursorSync.Off,
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
const oldConfig = useRef<UPlotConfigBuilder | undefined>(undefined);
const isToolTipOpen = useRef<boolean>(false);
@@ -70,7 +77,7 @@ export const StateTimelinePanel = ({
const [shouldDisplayCloseButton, setShouldDisplayCloseButton] = useState<boolean>(false);
// temp range set for adding new annotation set by TooltipPlugin2, consumed by AnnotationPlugin2
const [newAnnotationRange, setNewAnnotationRange] = useState<TimeRange2 | null>(null);
const { sync, canAddAnnotations, dataLinkPostProcessor } = usePanelContext();
const { sync, canAddAnnotations, dataLinkPostProcessor, eventBus } = usePanelContext();
const onCloseToolTip = () => {
isToolTipOpen.current = false;
@@ -205,6 +212,7 @@ export const StateTimelinePanel = ({
return (
<>
<EventBusPlugin config={builder} sync={syncAny} eventBus={eventBus} frame={alignedFrame} />
{showNewVizTooltips ? (
<>
{options.tooltip.mode !== TooltipDisplayMode.None && (

View File

@@ -3,6 +3,7 @@ import React, { useCallback, useMemo, useRef, useState } from 'react';
import { CartesianCoords2D, DashboardCursorSync, DataFrame, FieldType, PanelProps } from '@grafana/data';
import { config } from '@grafana/runtime';
import {
EventBusPlugin,
Portal,
TooltipDisplayMode,
TooltipPlugin2,
@@ -57,6 +58,12 @@ export const StatusHistoryPanel = ({
[]
);
const syncAny = useCallback(
() => sync?.() !== DashboardCursorSync.Off,
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
const oldConfig = useRef<UPlotConfigBuilder | undefined>(undefined);
const isToolTipOpen = useRef<boolean>(false);
@@ -68,7 +75,7 @@ export const StatusHistoryPanel = ({
const [shouldDisplayCloseButton, setShouldDisplayCloseButton] = useState<boolean>(false);
// temp range set for adding new annotation set by TooltipPlugin2, consumed by AnnotationPlugin2
const [newAnnotationRange, setNewAnnotationRange] = useState<TimeRange2 | null>(null);
const { sync, canAddAnnotations, dataLinkPostProcessor } = usePanelContext();
const { sync, canAddAnnotations, dataLinkPostProcessor, eventBus } = usePanelContext();
const enableAnnotationCreation = Boolean(canAddAnnotations && canAddAnnotations());
@@ -234,6 +241,7 @@ export const StatusHistoryPanel = ({
return (
<>
<EventBusPlugin config={builder} sync={syncAny} eventBus={eventBus} frame={alignedFrame} />
{showNewVizTooltips ? (
<>
{options.tooltip.mode !== TooltipDisplayMode.None && (

View File

@@ -3,7 +3,14 @@ import React, { useMemo, useState, useCallback } from 'react';
import { PanelProps, DataFrameType, DashboardCursorSync } from '@grafana/data';
import { PanelDataErrorView } from '@grafana/runtime';
import { TooltipDisplayMode, VizOrientation } from '@grafana/schema';
import { KeyboardPlugin, TooltipPlugin, TooltipPlugin2, usePanelContext, ZoomPlugin } from '@grafana/ui';
import {
EventBusPlugin,
KeyboardPlugin,
TooltipPlugin,
TooltipPlugin2,
usePanelContext,
ZoomPlugin,
} from '@grafana/ui';
import { TimeRange2, TooltipHoverMode } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2';
import { TimeSeries } from 'app/core/components/TimeSeries/TimeSeries';
import { config } from 'app/core/config';
@@ -34,8 +41,15 @@ export const TimeSeriesPanel = ({
replaceVariables,
id,
}: TimeSeriesPanelProps) => {
const { sync, canAddAnnotations, onThresholdsChange, canEditThresholds, showThresholds, dataLinkPostProcessor } =
usePanelContext();
const {
sync,
canAddAnnotations,
onThresholdsChange,
canEditThresholds,
showThresholds,
dataLinkPostProcessor,
eventBus,
} = usePanelContext();
// Vertical orientation is not available for users through config.
// It is simplified version of horizontal time series panel and it does not support all plugins.
const isVerticallyOriented = options.orientation === VizOrientation.Vertical;
@@ -64,6 +78,12 @@ export const TimeSeriesPanel = ({
[]
);
const syncAny = useCallback(
() => sync?.() !== DashboardCursorSync.Off,
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
if (!frames || suggestions) {
return (
<PanelDataErrorView
@@ -99,10 +119,11 @@ export const TimeSeriesPanel = ({
replaceVariables={replaceVariables}
dataLinkPostProcessor={dataLinkPostProcessor}
>
{(uplotConfig, alignedDataFrame) => {
{(uplotConfig, alignedFrame) => {
return (
<>
<KeyboardPlugin config={uplotConfig} />
<EventBusPlugin config={uplotConfig} sync={syncAny} eventBus={eventBus} frame={alignedFrame} />
{options.tooltip.mode === TooltipDisplayMode.None || (
<>
{showNewVizTooltips ? (
@@ -132,7 +153,7 @@ export const TimeSeriesPanel = ({
// not sure it header time here works for annotations, since it's taken from nearest datapoint index
<TimeSeriesTooltip
frames={frames}
seriesFrame={alignedDataFrame}
seriesFrame={alignedFrame}
dataIdxs={dataIdxs}
seriesIdx={seriesIdx}
mode={viaSync ? TooltipDisplayMode.Multi : options.tooltip.mode}
@@ -151,7 +172,7 @@ export const TimeSeriesPanel = ({
<ZoomPlugin config={uplotConfig} onZoom={onChangeTimeRange} withZoomY={true} />
<TooltipPlugin
frames={frames}
data={alignedDataFrame}
data={alignedFrame}
config={uplotConfig}
mode={options.tooltip.mode}
sortOrder={options.tooltip.sort}
@@ -181,11 +202,11 @@ export const TimeSeriesPanel = ({
{/*Enables annotations creation*/}
{!showNewVizTooltips ? (
enableAnnotationCreation && !isVerticallyOriented ? (
<AnnotationEditorPlugin data={alignedDataFrame} timeZone={timeZone} config={uplotConfig}>
<AnnotationEditorPlugin data={alignedFrame} timeZone={timeZone} config={uplotConfig}>
{({ startAnnotating }) => {
return (
<ContextMenuPlugin
data={alignedDataFrame}
data={alignedFrame}
config={uplotConfig}
timeZone={timeZone}
replaceVariables={replaceVariables}
@@ -212,7 +233,7 @@ export const TimeSeriesPanel = ({
</AnnotationEditorPlugin>
) : (
<ContextMenuPlugin
data={alignedDataFrame}
data={alignedFrame}
frames={frames}
config={uplotConfig}
timeZone={timeZone}