Add plot legend with floating top-right overlay

Renders a small panel inside the plot showing each visible series as [color swatch] [sensor title] [latest value + unit]. Panel width fits the longest sensor name and redraws when sensors are added or removed from the plot (RefreshPlotSeries already fires PlotInvalidated). Falls back to ellipsizing only if the label column would push the panel past the plot bounds. Capped at 12 rows with a +N more footer; auto-hides when plot pane is narrower than 200px. Theme-aware translucent background + border match the plot frame.

Persisted as showPlotLegend (default true) and toggleable from View menu and plot context menu. The View menu item is hidden when ShowPlot is off, but the setting itself is preserved across plot enable/disable and app restarts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-06-02 14:26:11 -05:00
co-authored by Copilot
parent ac65b63a8f
commit 73a81138a7
3 changed files with 177 additions and 0 deletions
@@ -103,6 +103,7 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable
private bool _showMaxColumn;
private bool _showMinColumn;
private bool _showPlotAxisLabels;
private bool _showPlotLegend;
private bool _showPlot;
private bool _showValueColumn;
private bool _throttleAtaUpdate;
@@ -148,6 +149,7 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable
_plotStrokeThickness = Math.Clamp(settings.GetValue("plotStroke", 1) + 1, 1, 4);
_plotStackedAxes = settings.GetValue("stackedAxes", true);
_showPlotAxisLabels = settings.GetValue("showAxesLabels", true);
_showPlotLegend = settings.GetValue("showPlotLegend", true);
_plotTimeAxisZoomEnabled = settings.GetValue("timeAxisEnableZoom", true);
_plotValueAxesZoomEnabled = settings.GetValue("yAxesEnableZoom", true);
int plotTimeWindowDefault = GetPlotTimeWindowIndex(settings.GetValue("plotPanel.MaxTimeSpan", 10.0f * 60));
@@ -187,6 +189,8 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable
public Visibility PlotVisibility => ShowPlot && PlotLocation != PlotLocation.Window ? Visibility.Visible : Visibility.Collapsed;
public Visibility ShowPlotLegendMenuVisibility => ShowPlot ? Visibility.Visible : Visibility.Collapsed;
public ObservableCollection<SensorTreeItemViewModel> RootItems { get; } = [];
public SensorTreeItemViewModel? SelectedItem
@@ -601,6 +605,19 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable
}
}
public bool ShowPlotLegend
{
get => _showPlotLegend;
set
{
if (!SetProperty(ref _showPlotLegend, value))
return;
Settings.SetValue("showPlotLegend", value);
PlotInvalidated?.Invoke(this, EventArgs.Empty);
}
}
public bool ShowPlot
{
get => _showPlot;
@@ -611,6 +628,7 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable
Settings.SetValue("plotMenuItem", value);
OnPropertyChanged(nameof(PlotVisibility));
OnPropertyChanged(nameof(ShowPlotLegendMenuVisibility));
OnPropertyChanged(nameof(IsPlotWindowVisible));
NotifyPlotLayoutChanged();
PlotInvalidated?.Invoke(this, EventArgs.Empty);