diff --git a/frontend/frontend-kit/ui/src/icons/math.ts b/frontend/frontend-kit/ui/src/icons/math.ts index acc9dbdd8..583bec966 100644 --- a/frontend/frontend-kit/ui/src/icons/math.ts +++ b/frontend/frontend-kit/ui/src/icons/math.ts @@ -1 +1 @@ -export { Plus, X } from "lucide-react"; +export { Plus } from "lucide-react"; diff --git a/frontend/testing-view/src/features/charts/components/ChartLegend.tsx b/frontend/testing-view/src/features/charts/components/ChartLegend.tsx index eab336026..6872ff719 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,10 @@ interface ChartLegendProps { chartId: string; series: WorkspaceChartSeries[]; disabledVariables: Set; + visibleValueLabels: Set; + valueLabelRefs: { current: Map }; onToggle: (seriesKey: string) => void; + onToggleValueLabel: (seriesKey: string) => void; onRemove: (variable: string) => void; } @@ -15,7 +18,10 @@ export const ChartLegend = ({ chartId, series, disabledVariables, + visibleValueLabels, + valueLabelRefs, onToggle, + onToggleValueLabel, onRemove, }: ChartLegendProps) => (
@@ -37,6 +43,34 @@ 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" + /> + )} + +
); 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..15e6daf37 --- /dev/null +++ b/frontend/testing-view/src/features/charts/plugins/valueLabelsPlugin.ts @@ -0,0 +1,52 @@ +import type { WorkspaceChartSeries } from "../types/charts"; + +/** + * Writes each series' latest value into the corresponding legend element + * (registered by `ChartLegend` in `valueLabelRefs`), instead of touching + * React state on every draw. + * + * `visibleLabelsRef` is read on every draw, so toggling which series show a + * value in the legend doesn't require recreating the uPlot instance. Value + * labels are opt-in, so a series only renders one once it's in the set. + */ +export const createValueLabelsPlugin = ( + series: WorkspaceChartSeries[], + visibleLabelsRef: { current: Set }, + valueLabelRefs: { current: Map }, +) => ({ + hooks: { + draw: (u: uPlot) => { + series.forEach((p, i) => { + const el = valueLabelRefs.current.get(p.variable); + if (!el) return; + + const seriesIdx = i + 1; + const data = u.data[seriesIdx]; + + if ( + !u.series[seriesIdx]?.show || + !visibleLabelsRef.current.has(p.variable) || + !data?.length + ) { + el.textContent = ""; + return; + } + + let rawVal: number | null | undefined; + for (let j = data.length - 1; j >= 0; j--) { + if (data[j] != null) { + rawVal = data[j]; + break; + } + } + + el.textContent = + rawVal == null + ? "" + : p.enumOptions?.length + ? (p.enumOptions[Math.round(rawVal)] ?? String(rawVal)) + : rawVal.toFixed(2); + }); + }, + }, +});