mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
TimeSeriesPanel: Null points no longer get tooltips (#42371)
* TimeSeriesPanel: Null points no longer get tooltips Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
parent
7c5d5c0128
commit
4855fe78f8
@ -344,19 +344,19 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{ sync: DashboardCursor
|
|||||||
let seriesData = self.data[seriesIdx];
|
let seriesData = self.data[seriesIdx];
|
||||||
|
|
||||||
if (seriesData[hoveredIdx] == null) {
|
if (seriesData[hoveredIdx] == null) {
|
||||||
let nonNullLft = hoveredIdx,
|
let nonNullLft = null,
|
||||||
nonNullRgt = hoveredIdx,
|
nonNullRgt = null,
|
||||||
i;
|
i;
|
||||||
|
|
||||||
i = hoveredIdx;
|
i = hoveredIdx;
|
||||||
while (nonNullLft === hoveredIdx && i-- > 0) {
|
while (nonNullLft == null && i-- > 0) {
|
||||||
if (seriesData[i] != null) {
|
if (seriesData[i] != null) {
|
||||||
nonNullLft = i;
|
nonNullLft = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
i = hoveredIdx;
|
i = hoveredIdx;
|
||||||
while (nonNullRgt === hoveredIdx && i++ < seriesData.length) {
|
while (nonNullRgt == null && i++ < seriesData.length) {
|
||||||
if (seriesData[i] != null) {
|
if (seriesData[i] != null) {
|
||||||
nonNullRgt = i;
|
nonNullRgt = i;
|
||||||
}
|
}
|
||||||
@ -365,19 +365,19 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{ sync: DashboardCursor
|
|||||||
let xVals = self.data[0];
|
let xVals = self.data[0];
|
||||||
|
|
||||||
let curPos = self.valToPos(cursorXVal, 'x');
|
let curPos = self.valToPos(cursorXVal, 'x');
|
||||||
let rgtPos = self.valToPos(xVals[nonNullRgt], 'x');
|
let rgtPos = nonNullRgt == null ? Infinity : self.valToPos(xVals[nonNullRgt], 'x');
|
||||||
let lftPos = self.valToPos(xVals[nonNullLft], 'x');
|
let lftPos = nonNullLft == null ? -Infinity : self.valToPos(xVals[nonNullLft], 'x');
|
||||||
|
|
||||||
let lftDelta = curPos - lftPos;
|
let lftDelta = curPos - lftPos;
|
||||||
let rgtDelta = rgtPos - curPos;
|
let rgtDelta = rgtPos - curPos;
|
||||||
|
|
||||||
if (lftDelta <= rgtDelta) {
|
if (lftDelta <= rgtDelta) {
|
||||||
if (lftDelta <= hoverProximityPx) {
|
if (lftDelta <= hoverProximityPx) {
|
||||||
hoveredIdx = nonNullLft;
|
hoveredIdx = nonNullLft!;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (rgtDelta <= hoverProximityPx) {
|
if (rgtDelta <= hoverProximityPx) {
|
||||||
hoveredIdx = nonNullRgt;
|
hoveredIdx = nonNullRgt!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||||
|
import { useMountedState } from 'react-use';
|
||||||
|
import uPlot from 'uplot';
|
||||||
import {
|
import {
|
||||||
CartesianCoords2D,
|
CartesianCoords2D,
|
||||||
DashboardCursorSync,
|
DashboardCursorSync,
|
||||||
@ -10,9 +13,6 @@ import {
|
|||||||
TimeZone,
|
TimeZone,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { TooltipDisplayMode } from '@grafana/schema';
|
import { TooltipDisplayMode } from '@grafana/schema';
|
||||||
import React, { useEffect, useLayoutEffect, useState } from 'react';
|
|
||||||
import { useMountedState } from 'react-use';
|
|
||||||
import uPlot from 'uplot';
|
|
||||||
import { useTheme2 } from '../../../themes/ThemeContext';
|
import { useTheme2 } from '../../../themes/ThemeContext';
|
||||||
import { Portal } from '../../Portal/Portal';
|
import { Portal } from '../../Portal/Portal';
|
||||||
import { SeriesTable, SeriesTableRowProps, VizTooltipContainer } from '../../VizTooltip';
|
import { SeriesTable, SeriesTableRowProps, VizTooltipContainer } from '../../VizTooltip';
|
||||||
@ -43,6 +43,7 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
|||||||
renderTooltip,
|
renderTooltip,
|
||||||
...otherProps
|
...otherProps
|
||||||
}) => {
|
}) => {
|
||||||
|
const plotInstance = useRef<uPlot>();
|
||||||
const theme = useTheme2();
|
const theme = useTheme2();
|
||||||
const [focusedSeriesIdx, setFocusedSeriesIdx] = useState<number | null>(null);
|
const [focusedSeriesIdx, setFocusedSeriesIdx] = useState<number | null>(null);
|
||||||
const [focusedPointIdx, setFocusedPointIdx] = useState<number | null>(null);
|
const [focusedPointIdx, setFocusedPointIdx] = useState<number | null>(null);
|
||||||
@ -60,7 +61,6 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
|||||||
|
|
||||||
// Add uPlot hooks to the config, or re-add when the config changed
|
// Add uPlot hooks to the config, or re-add when the config changed
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
let plotInstance: uPlot | undefined = undefined;
|
|
||||||
let bbox: DOMRect | undefined = undefined;
|
let bbox: DOMRect | undefined = undefined;
|
||||||
|
|
||||||
const plotMouseLeave = () => {
|
const plotMouseLeave = () => {
|
||||||
@ -69,7 +69,7 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
|||||||
}
|
}
|
||||||
setCoords(null);
|
setCoords(null);
|
||||||
setIsActive(false);
|
setIsActive(false);
|
||||||
plotInstance?.root.classList.remove('plot-active');
|
plotInstance.current?.root.classList.remove('plot-active');
|
||||||
};
|
};
|
||||||
|
|
||||||
const plotMouseEnter = () => {
|
const plotMouseEnter = () => {
|
||||||
@ -77,16 +77,14 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIsActive(true);
|
setIsActive(true);
|
||||||
plotInstance?.root.classList.add('plot-active');
|
plotInstance.current?.root.classList.add('plot-active');
|
||||||
};
|
};
|
||||||
|
|
||||||
// cache uPlot plotting area bounding box
|
// cache uPlot plotting area bounding box
|
||||||
config.addHook('syncRect', (u, rect) => {
|
config.addHook('syncRect', (u, rect) => (bbox = rect));
|
||||||
bbox = rect;
|
|
||||||
});
|
|
||||||
|
|
||||||
config.addHook('init', (u) => {
|
config.addHook('init', (u) => {
|
||||||
plotInstance = u;
|
plotInstance.current = u;
|
||||||
|
|
||||||
u.over.addEventListener('mouseleave', plotMouseLeave);
|
u.over.addEventListener('mouseleave', plotMouseLeave);
|
||||||
u.over.addEventListener('mouseenter', plotMouseEnter);
|
u.over.addEventListener('mouseenter', plotMouseEnter);
|
||||||
@ -155,9 +153,9 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
setCoords(null);
|
setCoords(null);
|
||||||
if (plotInstance) {
|
if (plotInstance.current) {
|
||||||
plotInstance.over.removeEventListener('mouseleave', plotMouseLeave);
|
plotInstance.current.over.removeEventListener('mouseleave', plotMouseLeave);
|
||||||
plotInstance.over.removeEventListener('mouseenter', plotMouseEnter);
|
plotInstance.current.over.removeEventListener('mouseenter', plotMouseEnter);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [config, setCoords, setIsActive, setFocusedPointIdx, setFocusedPointIdxs]);
|
}, [config, setCoords, setIsActive, setFocusedPointIdx, setFocusedPointIdxs]);
|
||||||
@ -174,7 +172,7 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
|||||||
const xFieldFmt = xField.display || getDisplayProcessor({ field: xField, timeZone, theme });
|
const xFieldFmt = xField.display || getDisplayProcessor({ field: xField, timeZone, theme });
|
||||||
let tooltip: React.ReactNode = null;
|
let tooltip: React.ReactNode = null;
|
||||||
|
|
||||||
const xVal = xFieldFmt(xField!.values.get(focusedPointIdx)).text;
|
let xVal = xFieldFmt(xField!.values.get(focusedPointIdx)).text;
|
||||||
|
|
||||||
if (!renderTooltip) {
|
if (!renderTooltip) {
|
||||||
// when interacting with a point in single mode
|
// when interacting with a point in single mode
|
||||||
@ -185,8 +183,10 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dataIdx = focusedPointIdxs?.[focusedSeriesIdx] ?? focusedPointIdx;
|
||||||
|
xVal = xFieldFmt(xField!.values.get(dataIdx)).text;
|
||||||
const fieldFmt = field.display || getDisplayProcessor({ field, timeZone, theme });
|
const fieldFmt = field.display || getDisplayProcessor({ field, timeZone, theme });
|
||||||
const display = fieldFmt(field.values.get(focusedPointIdx));
|
const display = fieldFmt(field.values.get(dataIdx));
|
||||||
|
|
||||||
tooltip = (
|
tooltip = (
|
||||||
<SeriesTable
|
<SeriesTable
|
||||||
@ -247,7 +247,7 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function isCursourOutsideCanvas({ left, top }: uPlot.Cursor, canvas: DOMRect) {
|
function isCursorOutsideCanvas({ left, top }: uPlot.Cursor, canvas: DOMRect) {
|
||||||
if (left === undefined || top === undefined) {
|
if (left === undefined || top === undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -264,7 +264,7 @@ export function positionTooltip(u: uPlot, bbox: DOMRect) {
|
|||||||
const cL = u.cursor.left || 0;
|
const cL = u.cursor.left || 0;
|
||||||
const cT = u.cursor.top || 0;
|
const cT = u.cursor.top || 0;
|
||||||
|
|
||||||
if (isCursourOutsideCanvas(u.cursor, bbox)) {
|
if (isCursorOutsideCanvas(u.cursor, bbox)) {
|
||||||
const idx = u.posToIdx(cL);
|
const idx = u.posToIdx(cL);
|
||||||
// when cursor outside of uPlot's canvas
|
// when cursor outside of uPlot's canvas
|
||||||
if (cT < 0 || cT > bbox.height) {
|
if (cT < 0 || cT > bbox.height) {
|
||||||
|
Loading…
Reference in New Issue
Block a user