From 06688202595159aeb2fb21056042b67218775ef4 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Fri, 22 Sep 2023 21:35:17 -0500 Subject: [PATCH] StateTimeline: Refactor hover markers (#75326) --- .../core/components/TimelineChart/timeline.ts | 160 +++++++----------- .../core/components/TimelineChart/utils.ts | 1 - public/app/plugins/panel/barchart/bars.ts | 11 +- 3 files changed, 62 insertions(+), 110 deletions(-) diff --git a/public/app/core/components/TimelineChart/timeline.ts b/public/app/core/components/TimelineChart/timeline.ts index b9a6007600c..18ecfdbf33d 100644 --- a/public/app/core/components/TimelineChart/timeline.ts +++ b/public/app/core/components/TimelineChart/timeline.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 = 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('.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 = { - 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, }; } diff --git a/public/app/core/components/TimelineChart/utils.ts b/public/app/core/components/TimelineChart/utils.ts index ecbf7c8d46f..02048cca457 100644 --- a/public/app/core/components/TimelineChart/utils.ts +++ b/public/app/core/components/TimelineChart/utils.ts @@ -182,7 +182,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ( 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 diff --git a/public/app/plugins/panel/barchart/bars.ts b/public/app/plugins/panel/barchart/bars.ts index b20eb53c424..5d270cd0651 100644 --- a/public/app/plugins/panel/barchart/bars.ts +++ b/public/app/plugins/panel/barchart/bars.ts @@ -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('.u-cursor-pt').forEach((el) => { + el.style.borderRadius = '0'; + + if (opts.fullHighlight) { + el.style.zIndex = '-1'; } }); };