StateTimeline: Refactor hover markers (#75326)

This commit is contained in:
Leon Sorokin 2023-09-22 21:35:17 -05:00 committed by GitHub
parent e72b5c54f8
commit 0668820259
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 63 additions and 111 deletions

View File

@ -1,4 +1,4 @@
import uPlot, { Cursor, Series } from 'uplot'; import uPlot, { Series } from 'uplot';
import { GrafanaTheme2, TimeRange } from '@grafana/data'; import { GrafanaTheme2, TimeRange } from '@grafana/data';
import { alpha } from '@grafana/data/src/themes/colorManipulator'; import { alpha } from '@grafana/data/src/themes/colorManipulator';
@ -83,17 +83,6 @@ export function getConfig(opts: TimelineCoreOptions) {
let qt: Quadtree; let qt: Quadtree;
const hoverMarks = Array(numSeries)
.fill(null)
.map(() => {
let mark = document.createElement('div');
mark.classList.add('bar-mark');
mark.style.position = 'absolute';
mark.style.background = 'rgba(255,255,255,0.2)';
mark.style.pointerEvents = 'none';
return mark;
});
// Needed for to calculate text positions // Needed for to calculate text positions
let boxRectsBySeries: TimelineBoxRect[][]; let boxRectsBySeries: TimelineBoxRect[][];
@ -105,6 +94,7 @@ export function getConfig(opts: TimelineCoreOptions) {
const font = `500 ${Math.round(12 * devicePixelRatio)}px ${theme.typography.fontFamily}`; const font = `500 ${Math.round(12 * devicePixelRatio)}px ${theme.typography.fontFamily}`;
const hovered: Array<Rect | null> = Array(numSeries).fill(null); const hovered: Array<Rect | null> = Array(numSeries).fill(null);
let hoveredAtCursor: Rect | null = null;
const size = [colWidth, Infinity]; const size = [colWidth, Infinity];
const gapFactor = 1 - size[0]; const gapFactor = 1 - size[0];
@ -378,8 +368,8 @@ export function getConfig(opts: TimelineCoreOptions) {
pxPerChar += 2.5; pxPerChar += 2.5;
over.style.overflow = 'hidden'; over.style.overflow = 'hidden';
hoverMarks.forEach((m) => { u.root.querySelectorAll<HTMLDivElement>('.u-cursor-pt').forEach((el) => {
over.appendChild(m); el.style.borderRadius = '0';
}); });
}; };
@ -396,112 +386,77 @@ export function getConfig(opts: TimelineCoreOptions) {
}); });
}; };
function setHoverMark(i: number, o: Rect | null) { function setHovered(cx: number, cy: number, cys: number[]) {
let h = hoverMarks[i]; hovered.fill(null);
hoveredAtCursor = null;
let pxRatio = uPlot.pxRatio; if (cx < 0) {
return;
if (o) {
h.style.display = '';
h.style.left = round(o.x / pxRatio) + 'px';
h.style.top = round(o.y / pxRatio) + 'px';
h.style.width = round(o.w / pxRatio) + 'px';
h.style.height = round(o.h / pxRatio) + 'px';
} else {
h.style.display = 'none';
} }
hovered[i] = o; for (let i = 0; i < cys.length; i++) {
let cy2 = cys[i];
qt.get(cx, cy2, 1, 1, (o) => {
if (pointWithin(cx, cy2, o.x, o.y, o.x + o.w, o.y + o.h)) {
hovered[o.sidx] = o;
if (Math.abs(cy - cy2) <= o.h / 2) {
hoveredAtCursor = o;
}
}
});
}
} }
let hoveredAtCursor: Rect | undefined; const hoverMulti = mode === TimelineMode.Changes;
function hoverMulti(cx: number, cy: number) { const cursor: uPlot.Cursor = {
let foundAtCursor: Rect | undefined; x: mode === TimelineMode.Changes,
y: false,
for (let i = 0; i < numSeries; i++) { dataIdx: (u, seriesIdx) => {
let found: Rect | undefined; if (seriesIdx === 1) {
// if quadtree is empty, fill it
if (cx >= 0) { if (qt.o.length === 0 && qt.q == null) {
let cy2 = yMids[i]; for (const seriesRects of boxRectsBySeries) {
for (const rect of seriesRects) {
qt.get(cx, cy2, 1, 1, (o) => { rect && qt.add(rect);
if (pointWithin(cx, cy2, o.x, o.y, o.x + o.w, o.y + o.h)) {
found = o;
if (Math.abs(cy - cy2) <= o.h / 2) {
foundAtCursor = o;
} }
} }
});
}
if (found) {
if (found !== hovered[i]) {
setHoverMark(i, found);
} }
} else if (hovered[i] != null) {
setHoverMark(i, null);
}
}
if (foundAtCursor) { let cx = u.cursor.left! * uPlot.pxRatio;
if (foundAtCursor !== hoveredAtCursor) { let cy = u.cursor.top! * uPlot.pxRatio;
hoveredAtCursor = foundAtCursor;
onHover(foundAtCursor.sidx, foundAtCursor.didx, foundAtCursor);
}
} else if (hoveredAtCursor) {
hoveredAtCursor = undefined;
onLeave();
}
}
function hoverOne(cx: number, cy: number) { let prevHovered = hoveredAtCursor;
let foundAtCursor: Rect | undefined;
qt.get(cx, cy, 1, 1, (o) => { setHovered(cx, cy, hoverMulti ? yMids : [cy]);
if (pointWithin(cx, cy, o.x, o.y, o.x + o.w, o.y + o.h)) {
foundAtCursor = o;
}
});
if (foundAtCursor) { if (hoveredAtCursor != null) {
setHoverMark(0, foundAtCursor); if (hoveredAtCursor !== prevHovered) {
onHover(hoveredAtCursor.sidx, hoveredAtCursor.didx, hoveredAtCursor);
if (foundAtCursor !== hoveredAtCursor) { }
hoveredAtCursor = foundAtCursor; } else if (prevHovered != null) {
onHover(foundAtCursor.sidx, foundAtCursor.didx, foundAtCursor); onLeave();
}
} else if (hoveredAtCursor) {
setHoverMark(0, null);
hoveredAtCursor = undefined;
onLeave();
}
}
const doHover = mode === TimelineMode.Changes ? hoverMulti : hoverOne;
const setCursor = (u: uPlot) => {
let cx = round(u.cursor.left! * uPlot.pxRatio);
let cy = round(u.cursor.top! * uPlot.pxRatio);
// if quadtree is empty, fill it
if (!qt.o.length && qt.q == null) {
for (const seriesRects of boxRectsBySeries) {
for (const rect of seriesRects) {
rect && qt.add(rect);
} }
} }
}
doHover(cx, cy); return hovered[seriesIdx]?.didx;
}; },
points: {
fill: 'rgba(255,255,255,0.2)',
bbox: (u, seriesIdx) => {
let hRect = hovered[seriesIdx];
let isHovered = hRect != null;
// hide y crosshair & hover points return {
const cursor: Partial<Cursor> = { left: isHovered ? hRect!.x / uPlot.pxRatio : -10,
y: false, top: isHovered ? hRect!.y / uPlot.pxRatio : -10,
x: mode === TimelineMode.Changes, width: isHovered ? hRect!.w / uPlot.pxRatio : 0,
points: { show: false }, height: isHovered ? hRect!.h / uPlot.pxRatio : 0,
};
},
},
}; };
const yMids: number[] = Array(numSeries).fill(0); const yMids: number[] = Array(numSeries).fill(0);
@ -576,7 +531,6 @@ export function getConfig(opts: TimelineCoreOptions) {
// hooks // hooks
init, init,
drawClear, drawClear,
setCursor,
}; };
} }

View File

@ -182,7 +182,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<UPlotConfigOptions> = (
builder.addHook('init', coreConfig.init); builder.addHook('init', coreConfig.init);
builder.addHook('drawClear', coreConfig.drawClear); builder.addHook('drawClear', coreConfig.drawClear);
builder.addHook('setCursor', coreConfig.setCursor);
// in TooltipPlugin, this gets invoked and the result is bound to a setCursor hook // in TooltipPlugin, this gets invoked and the result is bound to a setCursor hook
// which fires after the above setCursor hook, so can take advantage of hoveringOver // which fires after the above setCursor hook, so can take advantage of hoveringOver

View File

@ -445,12 +445,11 @@ export function getConfig(opts: BarsOptions, theme: GrafanaTheme2) {
const init = (u: uPlot) => { const init = (u: uPlot) => {
let over = u.over; let over = u.over;
over.style.overflow = 'hidden'; over.style.overflow = 'hidden';
u.root.querySelectorAll('.u-cursor-pt').forEach((el) => { u.root.querySelectorAll<HTMLDivElement>('.u-cursor-pt').forEach((el) => {
if (el instanceof HTMLElement) { el.style.borderRadius = '0';
el.style.borderRadius = '0';
if (opts.fullHighlight) { if (opts.fullHighlight) {
el.style.zIndex = '-1'; el.style.zIndex = '-1';
}
} }
}); });
}; };