diff --git a/package-lock.json b/package-lock.json index 1d93359..96536ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ioai/rosview", - "version": "1.5.0", + "version": "1.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ioai/rosview", - "version": "1.5.0", + "version": "1.5.1", "license": "MIT", "devDependencies": { "@eslint/js": "^9.39.4", diff --git a/package.json b/package.json index f03350e..f794939 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ioai/rosview", - "version": "1.5.0", + "version": "1.5.1", "description": "High-performance robotics data visualization for MCAP, ROS bag, ROS2 db3, HDF5 and BVH — embeddable React component and standalone SPA", "keywords": [ "ros", diff --git a/src/features/panels/Plot/PlotLegendSettings.tsx b/src/features/panels/Plot/PlotLegendSettings.tsx index a673f86..9ceeabc 100644 --- a/src/features/panels/Plot/PlotLegendSettings.tsx +++ b/src/features/panels/Plot/PlotLegendSettings.tsx @@ -1,36 +1,46 @@ import React, { useEffect, useMemo, useRef } from 'react'; import { useIntl } from 'react-intl'; -import { SettingsSection } from '../framework/settings/SettingsPrimitives'; import { ScrollArea } from '@/shared/ui/scroll-area'; import type { PlotConfig } from './defaults'; import { isPlotLegendVisible, plotLegendSelectionState, - setAllPlotLegendVisible, + setPlotLegendGroupVisible, setPlotLegendVisible, } from './plotLegendVisibility'; import { usePlotLegendEntries } from './plotPanelRuntimeStore'; interface PlotLegendSettingsProps { panelId: string; + seriesId: string; config: PlotConfig; setConfig: (next: PlotConfig | ((prev: PlotConfig) => PlotConfig)) => void; } -function legendInputId(panelId: string, index: number): string { - return `plot-legend-${panelId}-${index}`; +function legendKeyPrefix(seriesId: string): string { + return `${seriesId}:`; +} + +function legendInputId(panelId: string, seriesId: string, index: number): string { + return `plot-legend-${panelId}-${seriesId}-${index}`; } export function PlotLegendSettings({ panelId, + seriesId, config, setConfig, }: PlotLegendSettingsProps): React.ReactNode { const { formatMessage } = useIntl(); - const entries = usePlotLegendEntries(panelId); + const allEntries = usePlotLegendEntries(panelId); + const prefix = legendKeyPrefix(seriesId); + const entries = useMemo( + () => allEntries.filter((entry) => entry.key.startsWith(prefix)), + [allEntries, prefix], + ); const hiddenKeys = config.hiddenLegendKeys; const selectAllRef = useRef(null); - const selectAllId = `plot-legend-${panelId}-all`; + const selectAllId = `plot-legend-${panelId}-${seriesId}-all`; const selection = useMemo( () => plotLegendSelectionState(entries, hiddenKeys), @@ -48,6 +58,10 @@ export function PlotLegendSettings({ input.indeterminate = selection === 'partial'; }, [selection]); + if (entries.length <= 1) { + return null; + } + const setHiddenKeys = (next: string[]) => { setConfig((prev) => ({ ...prev, hiddenLegendKeys: next })); }; @@ -57,21 +71,25 @@ export function PlotLegendSettings({ }; const toggleAll = (visible: boolean) => { - setHiddenKeys(setAllPlotLegendVisible(entries.map((entry) => entry.key), visible)); + setHiddenKeys( + setPlotLegendGroupVisible( + hiddenKeys, + entries.map((entry) => entry.key), + visible, + ), + ); }; return ( - - {entries.length === 0 ? ( -

- {formatMessage({ id: 'panels.plot.settings.legend.empty' })} -

- ) : ( - <> -
+
+

+ {formatMessage({ id: 'panels.plot.settings.series.legend.title' })} +

+

+ {formatMessage({ id: 'panels.plot.settings.legend.description' })} +

+ <> +
- +
{entries.map((entry, index) => { - const inputId = legendInputId(panelId, index); + const inputId = legendInputId(panelId, seriesId, index); const visible = isPlotLegendVisible(hiddenKeys, entry.key); return ( @@ -119,8 +140,7 @@ export function PlotLegendSettings({ })}
- - )} - + +
); } diff --git a/src/features/panels/Plot/PlotPanel.tsx b/src/features/panels/Plot/PlotPanel.tsx index aba5931..e90f710 100644 --- a/src/features/panels/Plot/PlotPanel.tsx +++ b/src/features/panels/Plot/PlotPanel.tsx @@ -31,6 +31,9 @@ import { usePlotPanelData } from './usePlotPanelData'; import { usePlotTopicDetection } from './usePlotTopicDetection'; import { timeToSec } from '@/core/analysis/timeSeries'; +/** Stable empty array for activeTopics when no topics are configured. */ +const EMPTY_TOPICS: string[] = []; + interface PlotPanelProps { player: Player; panelId: string; @@ -87,10 +90,14 @@ export const PlotPanel: React.FC = ({ player, panelId, config, s const plottableTopics = useMemo(() => filterPlottableTopics(topics), [topics]); const topicByName = useMemo(() => buildTopicByName(topics), [topics]); - const activeTopics = useMemo( - () => selectActivePlotTopics(config, topicByName), + const activeTopicsKey = useMemo( + () => selectActivePlotTopics(config, topicByName).join('\n'), [config, topicByName], ); + const activeTopics = useMemo( + () => (activeTopicsKey === '' ? EMPTY_TOPICS : activeTopicsKey.split('\n')), + [activeTopicsKey], + ); const hasPlotPaths = useMemo(() => hasConfiguredPlotPaths(config), [config]); const hasEnabledSeries = useMemo(() => hasEnabledPlotPaths(config), [config]); const primary = selectPrimarySeries(config); @@ -216,7 +223,10 @@ export const PlotPanel: React.FC = ({ player, panelId, config, s const handleChartClick = (event: React.MouseEvent) => { const chart = uplotRef.current; if (!chart || config.xAxisMode !== 'timestamp') return; - const rect = chart.root.getBoundingClientRect(); + // posToVal expects a position relative to the plotting area (the `over` + // element), which is what hover/cursor uses. Using `root` here would add the + // left Y-axis width as a fixed offset, shifting the seek target to the right. + const rect = chart.over.getBoundingClientRect(); const x = chart.posToVal(event.clientX - rect.left, 'x'); if (Number.isFinite(x)) { player.seek(secToTime(x)); diff --git a/src/features/panels/Plot/PlotPanelSettings.tsx b/src/features/panels/Plot/PlotPanelSettings.tsx index f5dcefe..a737930 100644 --- a/src/features/panels/Plot/PlotPanelSettings.tsx +++ b/src/features/panels/Plot/PlotPanelSettings.tsx @@ -142,7 +142,6 @@ export function PlotPanelSettings({ return (
- @@ -353,6 +352,12 @@ export function PlotPanelSettings({ } /> +
))}