From 82d254af082a653970af5e4de50e98a121c1e0c4 Mon Sep 17 00:00:00 2001 From: Yury Molodov <yurymolodov@gmail.com> Date: Mon, 21 Nov 2022 23:26:53 +0100 Subject: [PATCH] vmui: sticky tooltip (#3376) * feat: add ability to make tooltip "sticky" * vmui: add ability to make tooltip "sticky" --- .../Chart/ChartTooltip/ChartTooltip.tsx | 181 ++++++++++++++++++ .../components/Chart/ChartTooltip/style.scss | 77 ++++++++ .../components/Chart/LineChart/LineChart.tsx | 124 +++++++++--- .../src/components/Chart/LineChart/style.scss | 41 +--- .../src/components/Main/Button/Button.tsx | 3 + .../vmui/src/components/Main/Icons/index.tsx | 9 + .../packages/vmui/src/styles/variables.scss | 3 +- .../packages/vmui/src/utils/uplot/tooltip.ts | 36 ---- .../packages/vmui/src/utils/uplot/types.ts | 17 -- docs/CHANGELOG.md | 1 + 10 files changed, 372 insertions(+), 120 deletions(-) create mode 100644 app/vmui/packages/vmui/src/components/Chart/ChartTooltip/ChartTooltip.tsx create mode 100644 app/vmui/packages/vmui/src/components/Chart/ChartTooltip/style.scss delete mode 100644 app/vmui/packages/vmui/src/utils/uplot/tooltip.ts diff --git a/app/vmui/packages/vmui/src/components/Chart/ChartTooltip/ChartTooltip.tsx b/app/vmui/packages/vmui/src/components/Chart/ChartTooltip/ChartTooltip.tsx new file mode 100644 index 000000000..099771946 --- /dev/null +++ b/app/vmui/packages/vmui/src/components/Chart/ChartTooltip/ChartTooltip.tsx @@ -0,0 +1,181 @@ +import React, { FC, useEffect, useMemo, useRef, useState } from "preact/compat"; +import uPlot, { Series } from "uplot"; +import { MetricResult } from "../../../api/types"; +import { formatPrettyNumber, getColorLine, getLegendLabel } from "../../../utils/uplot/helpers"; +import dayjs from "dayjs"; +import { DATE_FULL_TIMEZONE_FORMAT } from "../../../constants/date"; +import ReactDOM from "react-dom"; +import get from "lodash.get"; +import Button from "../../Main/Button/Button"; +import { CloseIcon, DragIcon } from "../../Main/Icons"; +import classNames from "classnames"; +import { MouseEvent as ReactMouseEvent } from "react"; +import "./style.scss"; + +export interface ChartTooltipProps { + id: string, + u: uPlot, + metrics: MetricResult[], + series: Series[], + unit?: string, + isSticky?: boolean, + tooltipOffset: { left: number, top: number }, + tooltipIdx: { seriesIdx: number, dataIdx: number }, + onClose?: (id: string) => void +} + +const ChartTooltip: FC<ChartTooltipProps> = ({ + u, + id, + unit = "", + metrics, + series, + tooltipIdx, + tooltipOffset, + isSticky, + onClose +}) => { + const tooltipRef = useRef<HTMLDivElement>(null); + + const [position, setPosition] = useState({ top: -999, left: -999 }); + const [moving, setMoving] = useState(false); + const [moved, setMoved] = useState(false); + + const [seriesIdx, setSeriesIdx] = useState(tooltipIdx.seriesIdx); + const [dataIdx, setDataIdx] = useState(tooltipIdx.dataIdx); + + const targetPortal = useMemo(() => u.root.querySelector(".u-wrap"), [u]); + + const value = useMemo(() => get(u, ["data", seriesIdx, dataIdx], 0), [u, seriesIdx, dataIdx]); + const valueFormat = useMemo(() => formatPrettyNumber(value), [value]); + const dataTime = useMemo(() => u.data[0][dataIdx], [u, dataIdx]); + const date = useMemo(() => dayjs(new Date(dataTime * 1000)).format(DATE_FULL_TIMEZONE_FORMAT), [dataTime]); + + const color = useMemo(() => getColorLine(series[seriesIdx]?.label || ""), [series, seriesIdx]); + + const name = useMemo(() => { + const metricName = (series[seriesIdx]?.label || "").replace(/{.+}/gmi, "").trim(); + return getLegendLabel(metricName); + }, []); + + const fields = useMemo(() => { + const metric = metrics[seriesIdx - 1]?.metric || {}; + const fields = Object.keys(metric).filter(k => k !== "__name__"); + return fields.map(key => `${key}="${metric[key]}"`); + }, [metrics, seriesIdx]); + + const handleClose = () => { + onClose && onClose(id); + }; + + const handleMouseDown = (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => { + setMoved(true); + setMoving(true); + const { clientX, clientY } = e; + setPosition({ top: clientY, left: clientX }); + }; + + const handleMouseMove = (e: MouseEvent) => { + if (!moving) return; + const { clientX, clientY } = e; + setPosition({ top: clientY, left: clientX }); + }; + + const handleMouseUp = () => { + setMoving(false); + }; + + const calcPosition = () => { + if (!tooltipRef.current) return; + + const topOnChart = u.valToPos((value || 0), series[seriesIdx]?.scale || "1"); + const leftOnChart = u.valToPos(dataTime, "x"); + const { width: tooltipWidth, height: tooltipHeight } = tooltipRef.current.getBoundingClientRect(); + const { width, height } = u.over.getBoundingClientRect(); + + const margin = 10; + const overflowX = leftOnChart + tooltipWidth >= width ? tooltipWidth + (2 * margin) : 0; + const overflowY = topOnChart + tooltipHeight >= height ? tooltipHeight + (2 * margin) : 0; + + setPosition({ + top: topOnChart + tooltipOffset.top + margin - overflowY, + left: leftOnChart + tooltipOffset.left + margin - overflowX + }); + }; + + useEffect(calcPosition, [u, value, dataTime, seriesIdx, tooltipOffset, tooltipRef]); + + useEffect(() => { + setSeriesIdx(tooltipIdx.seriesIdx); + setDataIdx(tooltipIdx.dataIdx); + }, [tooltipIdx]); + + useEffect(() => { + if (moving) { + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + } + + return () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + }, [moving]); + + if (!targetPortal || tooltipIdx.seriesIdx < 0 || tooltipIdx.dataIdx < 0) return null; + + return ReactDOM.createPortal(( + <div + className={classNames({ + "vm-chart-tooltip": true, + "vm-chart-tooltip_sticky": isSticky, + "vm-chart-tooltip_moved": moved + + })} + ref={tooltipRef} + style={position} + > + <div className="vm-chart-tooltip-header"> + <div className="vm-chart-tooltip-header__date">{date}</div> + {isSticky && ( + <> + <Button + className="vm-chart-tooltip-header__drag" + variant="text" + size="small" + startIcon={<DragIcon/>} + onMouseDown={handleMouseDown} + /> + <Button + className="vm-chart-tooltip-header__close" + variant="text" + size="small" + startIcon={<CloseIcon/>} + onClick={handleClose} + /> + </> + )} + </div> + <div className="vm-chart-tooltip-data"> + <div + className="vm-chart-tooltip-data__marker" + style={{ background: color }} + /> + <p> + {name}: + <b className="vm-chart-tooltip-data__value">{valueFormat}</b> + {unit} + </p> + </div> + {!!fields.length && ( + <div className="vm-chart-tooltip-info"> + {fields.map((f, i) => ( + <div key={`${f}_${i}`}>{f}</div> + ))} + </div> + )} + </div> + ), targetPortal); +}; + +export default ChartTooltip; diff --git a/app/vmui/packages/vmui/src/components/Chart/ChartTooltip/style.scss b/app/vmui/packages/vmui/src/components/Chart/ChartTooltip/style.scss new file mode 100644 index 000000000..9c8cedc94 --- /dev/null +++ b/app/vmui/packages/vmui/src/components/Chart/ChartTooltip/style.scss @@ -0,0 +1,77 @@ +@use "src/styles/variables" as *; +$chart-tooltip-width: 300px; +$chart-tooltip-icon-width: 25px; +$chart-tooltip-date-width: $chart-tooltip-width - (2*$chart-tooltip-icon-width) - (2*$padding-global) - $padding-small; +$chart-tooltip-x: -1 * ($padding-small + $padding-global + $chart-tooltip-date-width + ($chart-tooltip-icon-width/2)); +$chart-tooltip-y: -1 * ($padding-small + ($chart-tooltip-icon-width/2)); + +.vm-chart-tooltip { + position: absolute; + display: grid; + gap: $padding-global; + width: $chart-tooltip-width; + padding: $padding-small; + border-radius: $border-radius-medium; + background: $color-background-tooltip; + color: $color-white; + font-size: $font-size-small; + font-weight: normal; + line-height: 150%; + word-wrap: break-word; + font-family: $font-family-monospace; + z-index: 98; + user-select: text; + pointer-events: none; + + &_sticky { + background-color: $color-dove-gray; + pointer-events: auto; + z-index: 99; + } + + &_moved { + position: fixed; + margin-top: $chart-tooltip-y; + margin-left: $chart-tooltip-x; + } + + &-header { + display: grid; + grid-template-columns: 1fr $chart-tooltip-icon-width $chart-tooltip-icon-width; + gap: $padding-small; + align-items: center; + justify-content: center; + min-height: 25px; + + &__close { + color: $color-white; + } + + &__drag { + color: $color-white; + cursor: move; + } + } + + &-data { + display: flex; + flex-wrap: wrap; + align-items: center; + + &__value { + padding: 4px; + font-weight: bold; + } + + &__marker { + width: 12px; + height: 12px; + margin-right: $padding-small; + } + } + + &-info { + display: grid; + grid-gap: 4px; + } +} diff --git a/app/vmui/packages/vmui/src/components/Chart/LineChart/LineChart.tsx b/app/vmui/packages/vmui/src/components/Chart/LineChart/LineChart.tsx index b8c37503f..1e7e4ff7c 100644 --- a/app/vmui/packages/vmui/src/components/Chart/LineChart/LineChart.tsx +++ b/app/vmui/packages/vmui/src/components/Chart/LineChart/LineChart.tsx @@ -1,9 +1,15 @@ -import React, { FC, useCallback, useEffect, useRef, useState } from "preact/compat"; -import uPlot, { AlignedData as uPlotData, Options as uPlotOptions, Series as uPlotSeries, Range, Scales, Scale } from "uplot"; +import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from "preact/compat"; +import uPlot, { + AlignedData as uPlotData, + Options as uPlotOptions, + Series as uPlotSeries, + Range, + Scales, + Scale, +} from "uplot"; import { defaultOptions } from "../../../utils/uplot/helpers"; import { dragChart } from "../../../utils/uplot/events"; import { getAxes, getMinMaxBuffer } from "../../../utils/uplot/axes"; -import { setTooltip } from "../../../utils/uplot/tooltip"; import { MetricResult } from "../../../api/types"; import { limitsDurations } from "../../../utils/time"; import throttle from "lodash.throttle"; @@ -13,6 +19,7 @@ import { YaxisState } from "../../../state/graph/reducer"; import "uplot/dist/uPlot.min.css"; import "./style.scss"; import classNames from "classnames"; +import ChartTooltip, { ChartTooltipProps } from "../ChartTooltip/ChartTooltip"; export interface LineChartProps { metrics: MetricResult[]; @@ -24,21 +31,30 @@ export interface LineChartProps { setPeriod: ({ from, to }: {from: Date, to: Date}) => void; container: HTMLDivElement | null } + enum typeChartUpdate {xRange = "xRange", yRange = "yRange", data = "data"} -const LineChart: FC<LineChartProps> = ({ data, series, metrics = [], - period, yaxis, unit, setPeriod, container }) => { - +const LineChart: FC<LineChartProps> = ({ + data, + series, + metrics = [], + period, + yaxis, + unit, + setPeriod, + container +}) => { const uPlotRef = useRef<HTMLDivElement>(null); const [isPanning, setPanning] = useState(false); const [xRange, setXRange] = useState({ min: period.start, max: period.end }); const [uPlotInst, setUPlotInst] = useState<uPlot>(); const layoutSize = useResize(container); - const tooltip = document.createElement("div"); - tooltip.className = "u-tooltip"; - const tooltipIdx: {seriesIdx: number | null, dataIdx: number | undefined} = { seriesIdx: null, dataIdx: undefined }; - const tooltipOffset = { left: 0, top: 0 }; + const [showTooltip, setShowTooltip] = useState(false); + const [tooltipIdx, setTooltipIdx] = useState({ seriesIdx: -1, dataIdx: -1 }); + const [tooltipOffset, setTooltipOffset] = useState({ left: 0, top: 0 }); + const [stickyTooltips, setStickyToolTips] = useState<ChartTooltipProps[]>([]); + const tooltipId = useMemo(() => `${tooltipIdx.seriesIdx}_${tooltipIdx.dataIdx}`, [tooltipIdx]); const setScale = ({ min, max }: { min: number, max: number }): void => { setPeriod({ from: new Date(min * 1000), to: new Date(max * 1000) }); @@ -54,12 +70,13 @@ const LineChart: FC<LineChartProps> = ({ data, series, metrics = [], const onReadyChart = (u: uPlot) => { const factor = 0.9; - tooltipOffset.left = parseFloat(u.over.style.left); - tooltipOffset.top = parseFloat(u.over.style.top); - u.root.querySelector(".u-wrap")?.appendChild(tooltip); + setTooltipOffset({ + left: parseFloat(u.over.style.left), + top: parseFloat(u.over.style.top) + }); u.over.addEventListener("mousedown", e => { - const { ctrlKey, metaKey } = e; - const leftClick = e.button === 0; + const { ctrlKey, metaKey, button } = e; + const leftClick = button === 0; const leftClickWithMeta = leftClick && (ctrlKey || metaKey); if (leftClickWithMeta) { // drag pan @@ -98,21 +115,37 @@ const LineChart: FC<LineChartProps> = ({ data, series, metrics = [], } }; - const setCursor = (u: uPlot) => { - if (tooltipIdx.dataIdx === u.cursor.idx) return; - tooltipIdx.dataIdx = u.cursor.idx || 0; - if (tooltipIdx.seriesIdx !== null && tooltipIdx.dataIdx !== undefined) { - setTooltip({ u, tooltipIdx, metrics, series, tooltip, tooltipOffset, unit }); + const handleClick = () => { + const id = `${tooltipIdx.seriesIdx}_${tooltipIdx.dataIdx}`; + const props = { + id, + unit, + series, + metrics, + tooltipIdx, + tooltipOffset, + }; + + if (!stickyTooltips.find(t => t.id === id)) { + const tooltipProps = JSON.parse(JSON.stringify(props)); + setStickyToolTips(prev => [...prev, tooltipProps]); } }; - const seriesFocus = (u: uPlot, sidx: (number | null)) => { - if (tooltipIdx.seriesIdx === sidx) return; - tooltipIdx.seriesIdx = sidx; - sidx && tooltipIdx.dataIdx !== undefined - ? setTooltip({ u, tooltipIdx, metrics, series, tooltip, tooltipOffset, unit }) - : tooltip.style.display = "none"; + const handleUnStick = (id:string) => { + setStickyToolTips(prev => prev.filter(t => t.id !== id)); }; + + const setCursor = (u: uPlot) => { + const dataIdx = u.cursor.idx ?? -1; + setTooltipIdx(prev => ({ ...prev, dataIdx })); + }; + + const seriesFocus = (u: uPlot, sidx: (number | null)) => { + const seriesIdx = sidx ?? -1; + setTooltipIdx(prev => ({ ...prev, seriesIdx })); + }; + const getRangeX = (): Range.MinMax => [xRange.min, xRange.max]; const getRangeY = (u: uPlot, min = 0, max = 1, axis: string): Range.MinMax => { if (yaxis.limits.enable) return yaxis.limits.range[axis]; @@ -168,6 +201,8 @@ const LineChart: FC<LineChartProps> = ({ data, series, metrics = [], useEffect(() => setXRange({ min: period.start, max: period.end }), [period]); useEffect(() => { + setStickyToolTips([]); + setTooltipIdx({ seriesIdx: -1, dataIdx: -1 }); if (!uPlotRef.current) return; const u = new uPlot(options, data, uPlotRef.current); setUPlotInst(u); @@ -187,6 +222,17 @@ const LineChart: FC<LineChartProps> = ({ data, series, metrics = [], useEffect(() => updateChart(typeChartUpdate.xRange), [xRange]); useEffect(() => updateChart(typeChartUpdate.yRange), [yaxis]); + useEffect(() => { + const show = tooltipIdx.dataIdx !== -1 && tooltipIdx.seriesIdx !== -1; + setShowTooltip(show); + + if (show) window.addEventListener("click", handleClick); + + return () => { + window.removeEventListener("click", handleClick); + }; + }, [tooltipIdx, stickyTooltips]); + return ( <div className={classNames({ @@ -194,7 +240,31 @@ const LineChart: FC<LineChartProps> = ({ data, series, metrics = [], "vm-line-chart_panning": isPanning })} > - <div ref={uPlotRef}/> + <div + className="vm-line-chart__u-plot" + ref={uPlotRef} + /> + {uPlotInst && showTooltip && ( + <ChartTooltip + unit={unit} + u={uPlotInst} + series={series} + metrics={metrics} + tooltipIdx={tooltipIdx} + tooltipOffset={tooltipOffset} + id={tooltipId} + /> + )} + + {uPlotInst && stickyTooltips.map(t => ( + <ChartTooltip + {...t} + isSticky + u={uPlotInst} + key={t.id} + onClose={handleUnStick} + /> + ))} </div> ); }; diff --git a/app/vmui/packages/vmui/src/components/Chart/LineChart/style.scss b/app/vmui/packages/vmui/src/components/Chart/LineChart/style.scss index d1b3b26de..30186608f 100644 --- a/app/vmui/packages/vmui/src/components/Chart/LineChart/style.scss +++ b/app/vmui/packages/vmui/src/components/Chart/LineChart/style.scss @@ -7,45 +7,8 @@ &_panning { pointer-events: none; } -} -.u-tooltip { - position: absolute; - display: none; - grid-gap: $padding-global; - max-width: 300px; - padding: $padding-small; - border-radius: $border-radius-medium; - background: $color-background-tooltip; - color: $color-white; - font-size: $font-size-small; - font-weight: normal; - line-height: 1.4; - word-wrap: break-word; - font-family: monospace; - pointer-events: none; - z-index: 100; - - &-data { - display: flex; - flex-wrap: wrap; - align-items: center; - line-height: 150%; - - &__value { - padding: 4px; - font-weight: bold; - } - } - - &__info { - display: grid; - grid-gap: 4px; - } - - &__marker { - width: 12px; - height: 12px; - margin-right: $padding-small; + &__u-plot { + position: relative; } } diff --git a/app/vmui/packages/vmui/src/components/Main/Button/Button.tsx b/app/vmui/packages/vmui/src/components/Main/Button/Button.tsx index 09d5c9edd..ad54f6168 100644 --- a/app/vmui/packages/vmui/src/components/Main/Button/Button.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Button/Button.tsx @@ -14,6 +14,7 @@ interface ButtonProps { children?: ReactNode className?: string onClick?: (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => void + onMouseDown?: (e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => void } const Button: FC<ButtonProps> = ({ @@ -27,6 +28,7 @@ const Button: FC<ButtonProps> = ({ className, disabled, onClick, + onMouseDown, }) => { const classesButton = classNames({ @@ -45,6 +47,7 @@ const Button: FC<ButtonProps> = ({ className={classesButton} disabled={disabled} onClick={onClick} + onMouseDown={onMouseDown} > <> {startIcon && <span className="vm-button__start-icon">{startIcon}</span>} diff --git a/app/vmui/packages/vmui/src/components/Main/Icons/index.tsx b/app/vmui/packages/vmui/src/components/Main/Icons/index.tsx index 68ab7a9bf..cd7ea24dc 100644 --- a/app/vmui/packages/vmui/src/components/Main/Icons/index.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Icons/index.tsx @@ -300,3 +300,12 @@ export const CopyIcon = () => ( ></path> </svg> ); + +export const DragIcon = () => ( + <svg + viewBox="0 0 24 24" + fill="currentColor" + > + <path d="M20 9H4v2h16V9zM4 15h16v-2H4v2z"></path> + </svg> +); diff --git a/app/vmui/packages/vmui/src/styles/variables.scss b/app/vmui/packages/vmui/src/styles/variables.scss index 094acc677..a3c55ae3d 100644 --- a/app/vmui/packages/vmui/src/styles/variables.scss +++ b/app/vmui/packages/vmui/src/styles/variables.scss @@ -18,6 +18,7 @@ $color-text-secondary: rgba($color-text, 0.6); $color-text-disabled: rgba($color-text, 0.4); $color-black: #110f0f; +$color-dove-gray: #616161; $color-silver: #C4C4C4; $color-alto: #D8D8D8; $color-white: #ffffff; @@ -30,7 +31,7 @@ $color-tropical-blue: #C9E3F6; $color-background-body: var(--color-background-body); $color-background-block: var(--color-background-block); $color-background-modal: rgba($color-black, 0.7); -$color-background-tooltip: rgba(97, 97, 97, 0.92); +$color-background-tooltip: rgba($color-dove-gray, 0.92); /************* padding *************/ diff --git a/app/vmui/packages/vmui/src/utils/uplot/tooltip.ts b/app/vmui/packages/vmui/src/utils/uplot/tooltip.ts deleted file mode 100644 index 970da5b5e..000000000 --- a/app/vmui/packages/vmui/src/utils/uplot/tooltip.ts +++ /dev/null @@ -1,36 +0,0 @@ -import dayjs from "dayjs"; -import { SetupTooltip } from "./types"; -import { getColorLine, formatPrettyNumber, getLegendLabel } from "./helpers"; -import { DATE_FULL_TIMEZONE_FORMAT } from "../../constants/date"; - -// TODO create jsx component -export const setTooltip = ({ u, tooltipIdx, metrics, series, tooltip, tooltipOffset, unit = "" }: SetupTooltip): void => { - const { seriesIdx, dataIdx } = tooltipIdx; - if (seriesIdx === null || dataIdx === undefined) return; - const dataSeries = u.data[seriesIdx][dataIdx]; - const dataTime = u.data[0][dataIdx]; - const metric = metrics[seriesIdx - 1]?.metric || {}; - const selectedSeries = series[seriesIdx]; - const color = getColorLine(selectedSeries.label || ""); - - const { width, height } = u.over.getBoundingClientRect(); - const top = u.valToPos((dataSeries || 0), series[seriesIdx]?.scale || "1"); - const lft = u.valToPos(dataTime, "x"); - const { width: tooltipWidth, height: tooltipHeight } = tooltip.getBoundingClientRect(); - const overflowX = lft + tooltipWidth >= width; - const overflowY = top + tooltipHeight >= height; - - tooltip.style.display = "grid"; - tooltip.style.top = `${tooltipOffset.top + top + 10 - (overflowY ? tooltipHeight + 10 : 0)}px`; - tooltip.style.left = `${tooltipOffset.left + lft + 10 - (overflowX ? tooltipWidth + 20 : 0)}px`; - const metricName = (selectedSeries.label || "").replace(/{.+}/gmi, "").trim(); - const name = getLegendLabel(metricName); - const date = dayjs(new Date(dataTime * 1000)).format(DATE_FULL_TIMEZONE_FORMAT); - const info = Object.keys(metric).filter(k => k !== "__name__").map(k => `<div><b>${k}</b>: ${metric[k]}</div>`).join(""); - const marker = `<div class="u-tooltip__marker" style="background: ${color}"></div>`; - tooltip.innerHTML = `<div>${date}</div> - <div class="u-tooltip-data"> - ${marker}${name}: <b class="u-tooltip-data__value">${formatPrettyNumber(dataSeries)}</b> ${unit} - </div> - <div class="u-tooltip__info">${info}</div>`; -}; diff --git a/app/vmui/packages/vmui/src/utils/uplot/types.ts b/app/vmui/packages/vmui/src/utils/uplot/types.ts index 604b86549..e46850228 100644 --- a/app/vmui/packages/vmui/src/utils/uplot/types.ts +++ b/app/vmui/packages/vmui/src/utils/uplot/types.ts @@ -1,21 +1,4 @@ import uPlot, { Series } from "uplot"; -import { MetricResult } from "../../api/types"; - -export interface SetupTooltip { - u: uPlot, - metrics: MetricResult[], - series: Series[], - tooltip: HTMLDivElement, - unit?: string, - tooltipOffset: { - left: number, - top: number - }, - tooltipIdx: { - seriesIdx: number | null, - dataIdx: number | undefined - } -} export interface HideSeriesArgs { hideSeries: string[], diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 51149a0dc..8f922c3a1 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -24,6 +24,7 @@ The following tip changes can be tested by building VictoriaMetrics components f * FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add the ability to upload/paste JSON to investigate the trace. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3308) and [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3310). * FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): reduce JS bundle size from 200Kb to 100Kb. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3298). * FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add the ability to hide results of a particular query by clicking the `eye` icon. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3359). +* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add the ability to "stick" a tooltip on the chart by clicking on a data point. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3321) and [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3376) * FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert.html): add default alert list for vmalert's metrics. See [alerts-vmalert.yml](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/alerts-vmalert.yml). * BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): properly return an empty result from [limit_offset](https://docs.victoriametrics.com/MetricsQL.html#limit_offset) if the `offset` arg exceeds the number of inner time series. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3312).