StateTimeline: Refactor hover markers (#75326)

pull/74744/head^2
Leon Sorokin 2 years ago committed by GitHub
parent e72b5c54f8
commit 0668820259
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 160
      public/app/core/components/TimelineChart/timeline.ts
  2. 1
      public/app/core/components/TimelineChart/utils.ts
  3. 11
      public/app/plugins/panel/barchart/bars.ts

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

@ -182,7 +182,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<UPlotConfigOptions> = (
builder.addHook('init', coreConfig.init);
builder.addHook('drawClear', coreConfig.drawClear);
builder.addHook('setCursor', coreConfig.setCursor);
// 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

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

Loading…
Cancel
Save