diff --git a/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx b/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx index d7c2259b01a..f397bb8b739 100644 --- a/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx +++ b/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx @@ -146,20 +146,23 @@ export const TooltipPlugin2 = ({ let _plot = plot; let _isHovering = isHovering; + let _someSeriesIdx = false; let _isPinned = isPinned; let _style = style; + const updateHovering = () => { + _isHovering = closestSeriesIdx != null || (hoverMode === TooltipHoverMode.xAll && _someSeriesIdx); + }; + let offsetX = 0; let offsetY = 0; - let htmlEl = document.documentElement; - let winWidth = htmlEl.clientWidth - 16; - let winHeight = htmlEl.clientHeight - 16; - - window.addEventListener('resize', (e) => { - winWidth = htmlEl.clientWidth - 16; - winHeight = htmlEl.clientHeight - 16; - }); + let containRect = { + lft: 0, + top: 0, + rgt: screen.width, + btm: screen.height, + }; let selectedRange: TimeRange2 | null = null; let seriesIdxs: Array = plot?.cursor.idxs!.slice()!; @@ -290,6 +293,51 @@ export const TooltipPlugin2 = ({ } } }); + + const haltAncestorId = 'pageContent'; + const scrollbarWidth = 16; + + // if we're in a container that can clip the tooltip, we should try to stay within that rather than window edges + u.over.addEventListener( + 'mouseenter', + () => { + // clamp to viewport bounds + let htmlEl = document.documentElement; + let winWid = htmlEl.clientWidth - scrollbarWidth; + let winHgt = htmlEl.clientHeight - scrollbarWidth; + + let lft = 0, + top = 0, + rgt = winWid, + btm = winHgt; + + // find nearest scrollable container where overflow is not visible, (stop at #pageContent) + let par: HTMLElement | null = u.root; + + while (par != null && par.id !== haltAncestorId) { + let style = getComputedStyle(par); + let overflowX = style.getPropertyValue('overflow-x'); + let overflowY = style.getPropertyValue('overflow-y'); + + if (overflowX !== 'visible' || overflowY !== 'visible') { + let rect = par.getBoundingClientRect(); + lft = Math.max(rect.x, lft); + top = Math.max(rect.y, top); + rgt = Math.min(lft + rect.width, rgt); + btm = Math.min(top + rect.height, btm); + break; + } + + par = par.parentElement; + } + + containRect.lft = lft; + containRect.top = top; + containRect.rgt = rgt; + containRect.btm = btm; + }, + { capture: true } + ); }); config.addHook('setSelect', (u) => { @@ -372,44 +420,22 @@ export const TooltipPlugin2 = ({ yDrag = false; }); - // fires on data value hovers/unhovers (before setSeries) - config.addHook('setLegend', (u) => { - seriesIdxs = _plot?.cursor!.idxs!.slice()!; - - let hoveredSeriesIdx = seriesIdxs.findIndex((v, i) => i > 0 && v != null); - let _isHoveringNow = hoveredSeriesIdx !== -1; - - // setSeries may not fire if focus.prox is not set, so we set closestSeriesIdx here instead - if (hoverMode === TooltipHoverMode.xyOne) { - closestSeriesIdx = hoveredSeriesIdx; - } - - if (_isHoveringNow) { - // create - if (!_isHovering) { - _isHovering = true; - } - } else { - // destroy...TODO: debounce this - if (_isHovering) { - _isHovering = false; - } - } - - scheduleRender(); - }); - // fires on series focus/proximity changes // e.g. to highlight the hovered/closest series // TODO: we only need this for multi/all mode? config.addHook('setSeries', (u, seriesIdx) => { - // don't jiggle focused series styling when there's only one series - // const isMultiSeries = u.series.length > 2; - - // if (hoverModeRef.current === TooltipHoverMode.xAll && closestSeriesIdx !== seriesIdx) { closestSeriesIdx = seriesIdx; + updateHovering(); + scheduleRender(); + }); + + // fires on data value hovers/unhovers + config.addHook('setLegend', (u) => { + seriesIdxs = _plot?.cursor!.idxs!.slice()!; + _someSeriesIdx = seriesIdxs.some((v, i) => i > 0 && v != null); + + updateHovering(); scheduleRender(); - // } }); // fires on mousemoves @@ -435,25 +461,25 @@ export const TooltipPlugin2 = ({ let clientY = u.rect.top + top; if (offsetY !== 0) { - if (clientY + height < winHeight || clientY - height < 0) { + if (clientY + height < containRect.btm || clientY - height < 0) { offsetY = 0; } else if (offsetY !== -height) { offsetY = -height; } } else { - if (clientY + height > winHeight && clientY - height >= 0) { + if (clientY + height > containRect.btm && clientY - height >= 0) { offsetY = -height; } } if (offsetX !== 0) { - if (clientX + width < winWidth || clientX - width < 0) { + if (clientX + width < containRect.rgt || clientX - width < 0) { offsetX = 0; } else if (offsetX !== -width) { offsetX = -width; } } else { - if (clientX + width > winWidth && clientX - width >= 0) { + if (clientX + width > containRect.rgt && clientX - width >= 0) { offsetX = -width; } }