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:
kay delaney 2021-12-17 10:38:08 +00:00 committed by GitHub
parent 7c5d5c0128
commit 4855fe78f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 25 additions and 25 deletions

View File

@ -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!;
} }
} }
} }

View File

@ -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) {