mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
StateTimeline: Refactor hover markers (#75326)
This commit is contained in:
parent
e72b5c54f8
commit
0668820259
@ -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,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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';
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user