diff --git a/packages/grafana-ui/src/components/GraphNG/__snapshots__/utils.test.ts.snap b/packages/grafana-ui/src/components/GraphNG/__snapshots__/utils.test.ts.snap index 9d949b59de4..4bf28866688 100644 --- a/packages/grafana-ui/src/components/GraphNG/__snapshots__/utils.test.ts.snap +++ b/packages/grafana-ui/src/components/GraphNG/__snapshots__/utils.test.ts.snap @@ -95,7 +95,7 @@ Object { ], "scales": Array [ "x", - "__fixed", + null, ], }, }, diff --git a/packages/grafana-ui/src/components/TimeSeries/utils.ts b/packages/grafana-ui/src/components/TimeSeries/utils.ts index 8ccea19c698..c68610373ce 100644 --- a/packages/grafana-ui/src/components/TimeSeries/utils.ts +++ b/packages/grafana-ui/src/components/TimeSeries/utils.ts @@ -363,7 +363,8 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{ sync: DashboardCursor }, }, // ??? setSeries: syncMode === DashboardCursorSync.Tooltip, - scales: builder.scaleKeys, + //TODO: remove any once https://github.com/leeoniya/uPlot/pull/611 got merged or the typing is fixed + scales: [xScaleKey, null as any], match: [() => true, () => true], }; } diff --git a/packages/grafana-ui/src/components/uPlot/utils.ts b/packages/grafana-ui/src/components/uPlot/utils.ts index 3d7c20ce740..67035224ced 100755 --- a/packages/grafana-ui/src/components/uPlot/utils.ts +++ b/packages/grafana-ui/src/components/uPlot/utils.ts @@ -181,6 +181,11 @@ export function findMidPointYPosition(u: uPlot, idx: number) { y = u.valToPos((min || max)!, u.series[(sMaxIdx || sMinIdx)!].scale!); } + // if y is out of canvas bounds, snap it to the bottom + if (y !== undefined && y < 0) { + y = u.bbox.height / devicePixelRatio; + } + return y; } diff --git a/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx b/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx index ba9cb48851a..7e5c54b0c45 100755 --- a/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx +++ b/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useMemo } from 'react'; import { DataFrame, PanelProps } from '@grafana/data'; -import { TooltipPlugin, useTheme2, ZoomPlugin } from '@grafana/ui'; +import { TooltipPlugin, useTheme2, ZoomPlugin, usePanelContext } from '@grafana/ui'; import { TimelineMode, TimelineOptions } from './types'; import { TimelineChart } from './TimelineChart'; import { prepareTimelineFields, prepareTimelineLegendItems } from './utils'; @@ -22,6 +22,7 @@ export const StateTimelinePanel: React.FC = ({ onChangeTimeRange, }) => { const theme = useTheme2(); + const { sync } = usePanelContext(); const { frames, warn } = useMemo(() => prepareTimelineFields(data?.series, options.mergeValues ?? true, theme), [ data, @@ -103,6 +104,7 @@ export const StateTimelinePanel: React.FC = ({ { prepConfig = (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => { this.panelContext = this.context as PanelContext; - const { eventBus } = this.panelContext; + const { eventBus, sync } = this.panelContext; return preparePlotConfigBuilder({ frame: alignedFrame, getTimeRange, eventBus, + sync, allFrames: this.props.frames, ...this.props, diff --git a/public/app/plugins/panel/state-timeline/types.ts b/public/app/plugins/panel/state-timeline/types.ts index b9a8d011eb9..c5d672e15dd 100644 --- a/public/app/plugins/panel/state-timeline/types.ts +++ b/public/app/plugins/panel/state-timeline/types.ts @@ -1,4 +1,5 @@ -import { OptionsWithTooltip, OptionsWithLegend, HideableFieldConfig, VisibilityMode } from '@grafana/schema'; +import { DashboardCursorSync } from '@grafana/data'; +import { HideableFieldConfig, OptionsWithLegend, OptionsWithTooltip, VisibilityMode } from '@grafana/schema'; /** * @alpha @@ -15,6 +16,8 @@ export interface TimelineOptions extends OptionsWithLegend, OptionsWithTooltip { mergeValues?: boolean; // only used in "changes" mode (state-timeline) alignValue?: TimelineValueAlignment; + + sync?: DashboardCursorSync; } export type TimelineValueAlignment = 'center' | 'left' | 'right'; diff --git a/public/app/plugins/panel/state-timeline/utils.ts b/public/app/plugins/panel/state-timeline/utils.ts index e060c2180be..f12dad6f15c 100644 --- a/public/app/plugins/panel/state-timeline/utils.ts +++ b/public/app/plugins/panel/state-timeline/utils.ts @@ -3,6 +3,10 @@ import { XYFieldMatchers } from '@grafana/ui/src/components/GraphNG/types'; import { ArrayVector, DataFrame, + DashboardCursorSync, + DataHoverPayload, + DataHoverEvent, + DataHoverClearEvent, FALLBACK_COLOR, Field, FieldColorModeId, @@ -58,6 +62,8 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ({ timeZone, getTimeRange, mode, + eventBus, + sync, rowHeight, colWidth, showValue, @@ -65,6 +71,9 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ({ }) => { const builder = new UPlotConfigBuilder(timeZone); + const xScaleUnit = 'time'; + const xScaleKey = 'x'; + const isDiscrete = (field: Field) => { const mode = field.config?.color?.mode; return !(mode && field.display && mode.startsWith('continuous-')); @@ -116,6 +125,13 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ({ let hoveredDataIdx: number | null = null; const coreConfig = getConfig(opts); + const payload: DataHoverPayload = { + point: { + [xScaleUnit]: null, + [FIXED_UNIT]: null, + }, + data: frame, + }; builder.addHook('init', coreConfig.init); builder.addHook('drawClear', coreConfig.drawClear); @@ -148,7 +164,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ({ builder.setCursor(coreConfig.cursor); builder.addScale({ - scaleKey: 'x', + scaleKey: xScaleKey, isTime: true, orientation: ScaleOrientation.Horizontal, direction: ScaleDirection.Right, @@ -164,7 +180,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ({ }); builder.addAxis({ - scaleKey: 'x', + scaleKey: xScaleKey, isTime: true, splits: coreConfig.xSplits!, placement: AxisPlacement.Bottom, @@ -219,6 +235,34 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ({ }); } + if (sync !== DashboardCursorSync.Off) { + let cursor: Partial = {}; + + cursor.sync = { + key: '__global_', + filters: { + pub: (type: string, src: uPlot, x: number, y: number, w: number, h: number, dataIdx: number) => { + payload.rowIndex = dataIdx; + if (x < 0 && y < 0) { + payload.point[xScaleUnit] = null; + payload.point[FIXED_UNIT] = null; + eventBus.publish(new DataHoverClearEvent()); + } 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(new DataHoverEvent(payload)); + } + return true; + }, + }, + //TODO: remove any once https://github.com/leeoniya/uPlot/pull/611 got merged or the typing is fixed + scales: [xScaleKey, null as any], + }; + builder.setSync(); + builder.setCursor(cursor); + } + return builder; };