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