From 8e4a8901499a2c8ae05f24d77fbd78290dca638f Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Sat, 20 Jun 2026 19:40:33 +0200 Subject: [PATCH 1/3] feat: add toggleable labeles for charts values --- .../charts/components/ChartLegend.tsx | 25 +++++- .../charts/components/ChartSurface.tsx | 23 +++++- .../charts/components/TelemetryChart.tsx | 15 ++++ .../{components => plugins}/tooltipPlugin.ts | 0 .../charts/plugins/valueLabelsPlugin.ts | 80 +++++++++++++++++++ 5 files changed, 139 insertions(+), 4 deletions(-) rename frontend/testing-view/src/features/charts/{components => plugins}/tooltipPlugin.ts (100%) create mode 100644 frontend/testing-view/src/features/charts/plugins/valueLabelsPlugin.ts diff --git a/frontend/testing-view/src/features/charts/components/ChartLegend.tsx b/frontend/testing-view/src/features/charts/components/ChartLegend.tsx index eab336026..5e08548b6 100644 --- a/frontend/testing-view/src/features/charts/components/ChartLegend.tsx +++ b/frontend/testing-view/src/features/charts/components/ChartLegend.tsx @@ -1,4 +1,4 @@ -import { X } from "@workspace/ui/icons"; +import { Eye, EyeOff, X } from "@workspace/ui/icons"; import { COLORS } from "../constants/chartsColors"; import type { WorkspaceChartSeries } from "../types/charts"; import { ChartSettings } from "./ChartSettings"; @@ -7,7 +7,9 @@ interface ChartLegendProps { chartId: string; series: WorkspaceChartSeries[]; disabledVariables: Set; + hiddenValueLabels: Set; onToggle: (seriesKey: string) => void; + onToggleValueLabel: (seriesKey: string) => void; onRemove: (variable: string) => void; } @@ -15,7 +17,9 @@ export const ChartLegend = ({ chartId, series, disabledVariables, + hiddenValueLabels, onToggle, + onToggleValueLabel, onRemove, }: ChartLegendProps) => (
@@ -38,6 +42,25 @@ export const ChartLegend = ({ /> {p.variable} +
); diff --git a/frontend/testing-view/src/features/charts/components/tooltipPlugin.ts b/frontend/testing-view/src/features/charts/plugins/tooltipPlugin.ts similarity index 100% rename from frontend/testing-view/src/features/charts/components/tooltipPlugin.ts rename to frontend/testing-view/src/features/charts/plugins/tooltipPlugin.ts diff --git a/frontend/testing-view/src/features/charts/plugins/valueLabelsPlugin.ts b/frontend/testing-view/src/features/charts/plugins/valueLabelsPlugin.ts new file mode 100644 index 000000000..80794a6dd --- /dev/null +++ b/frontend/testing-view/src/features/charts/plugins/valueLabelsPlugin.ts @@ -0,0 +1,80 @@ +import { COLORS } from "../constants/chartsColors"; +import type { WorkspaceChartSeries } from "../types/charts"; + +/** + * Pins a small badge with each series' latest value to the right edge of + * the chart, at the height matching that series' current y position. + * + * `hiddenLabelsRef` is read on every draw, so toggling which series show a + * value label doesn't require recreating the uPlot instance. + */ +export const createValueLabelsPlugin = ( + series: WorkspaceChartSeries[], + hiddenLabelsRef: { current: Set }, +) => { + let labels: HTMLDivElement[]; + + return { + hooks: { + init: (u: uPlot) => { + labels = series.map((_, i) => { + const label = document.createElement("div"); + label.className = + "pointer-events-none absolute z-10 hidden -translate-y-1/2 whitespace-nowrap rounded-sm px-1 py-0.5 text-[9px] font-bold tabular-nums text-white shadow-sm"; + label.style.background = COLORS[i % COLORS.length]; + label.style.right = "2px"; + u.root.querySelector(".u-over")?.appendChild(label); + return label; + }); + }, + destroy: () => labels?.forEach((label) => label.remove()), + draw: (u: uPlot) => { + series.forEach((p, i) => { + const label = labels[i]; + const seriesIdx = i + 1; + const data = u.data[seriesIdx]; + + if ( + !u.series[seriesIdx]?.show || + hiddenLabelsRef.current.has(p.variable) || + !data?.length + ) { + label.classList.add("hidden"); + return; + } + + let rawVal: number | null | undefined; + for (let j = data.length - 1; j >= 0; j--) { + if (data[j] != null) { + rawVal = data[j]; + break; + } + } + + if (rawVal == null) { + label.classList.add("hidden"); + return; + } + + const top = u.valToPos(rawVal, "y"); + if ( + top == null || + Number.isNaN(top) || + top < 0 || + top > u.over.clientHeight + ) { + label.classList.add("hidden"); + return; + } + + label.textContent = p.enumOptions?.length + ? (p.enumOptions[Math.round(rawVal)] ?? String(rawVal)) + : rawVal.toFixed(2); + + label.style.top = `${top}px`; + label.classList.remove("hidden"); + }); + }, + }, + }; +}; From ce025e0faaed87f69182a678b7500120f92140a7 Mon Sep 17 00:00:00 2001 From: Maxim <74974283+maximka76667@users.noreply.github.com> Date: Sat, 20 Jun 2026 21:55:23 +0200 Subject: [PATCH 2/3] feat: remove labeled values and add them in the legend --- .../charts/components/ChartLegend.tsx | 33 +++-- .../charts/components/ChartSurface.tsx | 15 ++- .../charts/components/TelemetryChart.tsx | 13 +- .../charts/plugins/valueLabelsPlugin.ts | 114 +++++++----------- 4 files changed, 82 insertions(+), 93 deletions(-) diff --git a/frontend/testing-view/src/features/charts/components/ChartLegend.tsx b/frontend/testing-view/src/features/charts/components/ChartLegend.tsx index 5e08548b6..6872ff719 100644 --- a/frontend/testing-view/src/features/charts/components/ChartLegend.tsx +++ b/frontend/testing-view/src/features/charts/components/ChartLegend.tsx @@ -7,7 +7,8 @@ interface ChartLegendProps { chartId: string; series: WorkspaceChartSeries[]; disabledVariables: Set; - hiddenValueLabels: Set; + visibleValueLabels: Set; + valueLabelRefs: { current: Map }; onToggle: (seriesKey: string) => void; onToggleValueLabel: (seriesKey: string) => void; onRemove: (variable: string) => void; @@ -17,7 +18,8 @@ export const ChartLegend = ({ chartId, series, disabledVariables, - hiddenValueLabels, + visibleValueLabels, + valueLabelRefs, onToggle, onToggleValueLabel, onRemove, @@ -41,24 +43,33 @@ export const ChartLegend = ({ style={{ background: COLORS[i % COLORS.length] }} /> {p.variable} + {visibleValueLabels.has(p.variable) && ( + { + if (el) valueLabelRefs.current.set(p.variable, el); + else valueLabelRefs.current.delete(p.variable); + }} + className="text-muted-foreground text-[11px] tabular-nums normal-case" + /> + )}