diff --git a/.betterer.results b/.betterer.results index 94498648a29..b96a9ce1e5b 100644 --- a/.betterer.results +++ b/.betterer.results @@ -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"] diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.ts index 70f48798788..baf292e42b4 100644 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.ts @@ -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 = {}> = { theme: GrafanaTheme2; timeZones: TimeZone[]; getTimeRange: () => TimeRange; - eventBus: EventBus; allFrames: DataFrame[]; renderers?: Renderers; tweakScale?: (opts: ScaleProps, forField: Field) => ScaleProps; diff --git a/packages/grafana-ui/src/components/uPlot/plugins/EventBusPlugin.tsx b/packages/grafana-ui/src/components/uPlot/plugins/EventBusPlugin.tsx new file mode 100644 index 00000000000..f3528620dec --- /dev/null +++ b/packages/grafana-ui/src/components/uPlot/plugins/EventBusPlugin.tsx @@ -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(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; +}; diff --git a/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx b/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx index 214a0b3b3e6..d1aaa71e698 100644 --- a/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx +++ b/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx @@ -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); diff --git a/packages/grafana-ui/src/components/uPlot/plugins/index.ts b/packages/grafana-ui/src/components/uPlot/plugins/index.ts index b2936f2397d..4f07a8d80f7 100644 --- a/packages/grafana-ui/src/components/uPlot/plugins/index.ts +++ b/packages/grafana-ui/src/components/uPlot/plugins/index.ts @@ -1,4 +1,5 @@ export { ZoomPlugin } from './ZoomPlugin'; export { TooltipPlugin } from './TooltipPlugin'; export { TooltipPlugin2 } from './TooltipPlugin2'; +export { EventBusPlugin } from './EventBusPlugin'; export { KeyboardPlugin } from './KeyboardPlugin'; diff --git a/packages/grafana-ui/src/graveyard/GraphNG/__snapshots__/utils.test.ts.snap b/packages/grafana-ui/src/graveyard/GraphNG/__snapshots__/utils.test.ts.snap index 3b9265254b5..2aff27a97fa 100644 --- a/packages/grafana-ui/src/graveyard/GraphNG/__snapshots__/utils.test.ts.snap +++ b/packages/grafana-ui/src/graveyard/GraphNG/__snapshots__/utils.test.ts.snap @@ -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, ], }, }, diff --git a/packages/grafana-ui/src/graveyard/GraphNG/utils.test.ts b/packages/grafana-ui/src/graveyard/GraphNG/utils.test.ts index 9675cd7ca56..23775b1df23 100644 --- a/packages/grafana-ui/src/graveyard/GraphNG/utils.test.ts +++ b/packages/grafana-ui/src/graveyard/GraphNG/utils.test.ts @@ -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(); diff --git a/packages/grafana-ui/src/graveyard/TimeSeries/TimeSeries.tsx b/packages/grafana-ui/src/graveyard/TimeSeries/TimeSeries.tsx index 9a748236d77..9ee08192383 100644 --- a/packages/grafana-ui/src/graveyard/TimeSeries/TimeSeries.tsx +++ b/packages/grafana-ui/src/graveyard/TimeSeries/TimeSeries.tsx @@ -19,7 +19,7 @@ export class UnthemedTimeSeries extends Component { declare context: React.ContextType; 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 { theme, timeZones: Array.isArray(timeZone) ? timeZone : [timeZone], getTimeRange, - eventBus, sync, allFrames, renderers, diff --git a/packages/grafana-ui/src/graveyard/TimeSeries/utils.ts b/packages/grafana-ui/src/graveyard/TimeSeries/utils.ts index 12a6e3908c6..41090a35a47 100644 --- a/packages/grafana-ui/src/graveyard/TimeSeries/utils.ts +++ b/packages/grafana-ui/src/graveyard/TimeSeries/utils.ts @@ -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], }; } diff --git a/public/app/core/components/GraphNG/GraphNG.tsx b/public/app/core/components/GraphNG/GraphNG.tsx index 9c53722f21f..9dd45cc312a 100644 --- a/public/app/core/components/GraphNG/GraphNG.tsx +++ b/public/app/core/components/GraphNG/GraphNG.tsx @@ -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 { static contextType = PanelContextRoot; - panelContext: PanelContext = {} as PanelContext; private plotInstance: React.RefObject; - private subscription = new Subscription(); - constructor(props: GraphNGProps) { super(props); let state = this.prepState(props); @@ -180,82 +172,6 @@ export class GraphNG extends Component { 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 { } } - componentWillUnmount() { - this.subscription.unsubscribe(); - } - render() { const { width, height, children, renderLegend } = this.props; const { config, alignedFrame, alignedData } = this.state; diff --git a/public/app/core/components/GraphNG/__snapshots__/utils.test.ts.snap b/public/app/core/components/GraphNG/__snapshots__/utils.test.ts.snap index 53a5ef32322..8443971551d 100644 --- a/public/app/core/components/GraphNG/__snapshots__/utils.test.ts.snap +++ b/public/app/core/components/GraphNG/__snapshots__/utils.test.ts.snap @@ -80,9 +80,6 @@ exports[`GraphNG utils preparePlotConfigBuilder 1`] = ` "width": [Function], }, "sync": { - "filters": { - "pub": [Function], - }, "key": "__global_", "scales": [ "x", diff --git a/public/app/core/components/GraphNG/utils.test.ts b/public/app/core/components/GraphNG/utils.test.ts index 485384ab2a3..234a587d0e7 100644 --- a/public/app/core/components/GraphNG/utils.test.ts +++ b/public/app/core/components/GraphNG/utils.test.ts @@ -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(); diff --git a/public/app/core/components/TimeSeries/TimeSeries.tsx b/public/app/core/components/TimeSeries/TimeSeries.tsx index 3e714e9587d..4ecc98b5b51 100644 --- a/public/app/core/components/TimeSeries/TimeSeries.tsx +++ b/public/app/core/components/TimeSeries/TimeSeries.tsx @@ -19,7 +19,7 @@ export class UnthemedTimeSeries extends Component { declare context: React.ContextType; 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 { theme, timeZones: Array.isArray(timeZone) ? timeZone : [timeZone], getTimeRange, - eventBus, sync, allFrames, renderers, diff --git a/public/app/core/components/TimeSeries/utils.ts b/public/app/core/components/TimeSeries/utils.ts index 1baf1bd85e5..82b78da39cd 100644 --- a/public/app/core/components/TimeSeries/utils.ts +++ b/public/app/core/components/TimeSeries/utils.ts @@ -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], }; diff --git a/public/app/core/components/TimelineChart/TimelineChart.tsx b/public/app/core/components/TimelineChart/TimelineChart.tsx index 9d2474a4611..57048baed16 100644 --- a/public/app/core/components/TimelineChart/TimelineChart.tsx +++ b/public/app/core/components/TimelineChart/TimelineChart.tsx @@ -43,12 +43,11 @@ export class TimelineChart extends React.Component { 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, diff --git a/public/app/core/components/TimelineChart/utils.ts b/public/app/core/components/TimelineChart/utils.ts index 731d53351b7..6a7bccfed0a 100644 --- a/public/app/core/components/TimelineChart/utils.ts +++ b/public/app/core/components/TimelineChart/utils.ts @@ -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 = ( timeZones, getTimeRange, mode, - eventBus, sync, rowHeight, colWidth, @@ -112,7 +108,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ( }) => { const builder = new UPlotConfigBuilder(timeZones[0]); - const xScaleUnit = 'time'; const xScaleKey = 'x'; const isDiscrete = (field: Field) => { @@ -177,16 +172,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ( 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 = ( 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(); diff --git a/public/app/plugins/panel/barchart/BarChartPanel.tsx b/public/app/plugins/panel/barchart/BarChartPanel.tsx index abdeb0c0822..4bb4be5753b 100644 --- a/public/app/plugins/panel/barchart/BarChartPanel.tsx +++ b/public/app/plugins/panel/barchart/BarChartPanel.tsx @@ -71,7 +71,7 @@ interface Props extends PanelProps {} 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(undefined); const isToolTipOpen = useRef(false); @@ -288,7 +288,6 @@ export const BarChartPanel = ({ data, options, fieldConfig, width, height, timeZ timeZone, theme, timeZones: [timeZone], - eventBus, orientation, barWidth, barRadius, diff --git a/public/app/plugins/panel/barchart/utils.test.ts b/public/app/plugins/panel/barchart/utils.test.ts index ae999840fb6..07ea9e55e24 100644 --- a/public/app/plugins/panel/barchart/utils.test.ts +++ b/public/app/plugins/panel/barchart/utils.test.ts @@ -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(); diff --git a/public/app/plugins/panel/candlestick/CandlestickPanel.tsx b/public/app/plugins/panel/candlestick/CandlestickPanel.tsx index 7008948b7ed..fc920337f0b 100644 --- a/public/app/plugins/panel/candlestick/CandlestickPanel.tsx +++ b/public/app/plugins/panel/candlestick/CandlestickPanel.tsx @@ -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 ( <> + + {showNewVizTooltips ? ( + {({ startAnnotating }) => { return ( ) : ( 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(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 = ({ {(vizWidth: number, vizHeight: number) => ( + {!showNewVizTooltips && } {showNewVizTooltips && ( <> diff --git a/public/app/plugins/panel/heatmap/utils.ts b/public/app/plugins/panel/heatmap/utils.ts index 141a94541d0..d6c9589811d 100644 --- a/public/app/plugins/panel/heatmap/utils.ts +++ b/public/app/plugins/panel/heatmap/utils.ts @@ -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; 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 | 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(); diff --git a/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx b/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx index f613e935d49..93874e7ba4f 100644 --- a/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx +++ b/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx @@ -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(undefined); const isToolTipOpen = useRef(false); @@ -70,7 +77,7 @@ export const StateTimelinePanel = ({ const [shouldDisplayCloseButton, setShouldDisplayCloseButton] = useState(false); // temp range set for adding new annotation set by TooltipPlugin2, consumed by AnnotationPlugin2 const [newAnnotationRange, setNewAnnotationRange] = useState(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 ( <> + {showNewVizTooltips ? ( <> {options.tooltip.mode !== TooltipDisplayMode.None && ( diff --git a/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx b/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx index 560c80e8714..57178bf15c8 100644 --- a/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx +++ b/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx @@ -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(undefined); const isToolTipOpen = useRef(false); @@ -68,7 +75,7 @@ export const StatusHistoryPanel = ({ const [shouldDisplayCloseButton, setShouldDisplayCloseButton] = useState(false); // temp range set for adding new annotation set by TooltipPlugin2, consumed by AnnotationPlugin2 const [newAnnotationRange, setNewAnnotationRange] = useState(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 ( <> + {showNewVizTooltips ? ( <> {options.tooltip.mode !== TooltipDisplayMode.None && ( diff --git a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx index 1a4ed79cdd9..792d1e4000c 100644 --- a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx +++ b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx @@ -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 ( - {(uplotConfig, alignedDataFrame) => { + {(uplotConfig, alignedFrame) => { return ( <> + {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 + {({ startAnnotating }) => { return ( ) : (