diff --git a/Dashboard/Controls/MemoryContent.xaml.cs b/Dashboard/Controls/MemoryContent.xaml.cs index 56ffdec8..b9c7c9e6 100644 --- a/Dashboard/Controls/MemoryContent.xaml.cs +++ b/Dashboard/Controls/MemoryContent.xaml.cs @@ -62,6 +62,13 @@ public partial class MemoryContent : UserControl // Legend panel references for edge-based legends (ScottPlot issue #4717 workaround) private Dictionary _legendPanels = new(); + // Chart hover tooltips + private Helpers.ChartHoverHelper? _memoryStatsOverviewHover; + private Helpers.ChartHoverHelper? _memoryGrantsHover; + private Helpers.ChartHoverHelper? _memoryClerksHover; + private Helpers.ChartHoverHelper? _planCacheHover; + private Helpers.ChartHoverHelper? _memoryPressureEventsHover; + // No DataGrids with filters - all tabs are chart-only public MemoryContent() @@ -69,6 +76,12 @@ public MemoryContent() InitializeComponent(); SetupChartContextMenus(); Loaded += OnLoaded; + + _memoryStatsOverviewHover = new Helpers.ChartHoverHelper(MemoryStatsOverviewChart, "MB"); + _memoryGrantsHover = new Helpers.ChartHoverHelper(MemoryGrantsChart, "MB"); + _memoryClerksHover = new Helpers.ChartHoverHelper(MemoryClerksChart, "MB"); + _planCacheHover = new Helpers.ChartHoverHelper(PlanCacheChart, "MB"); + _memoryPressureEventsHover = new Helpers.ChartHoverHelper(MemoryPressureEventsChart, "events"); } private void OnLoaded(object sender, RoutedEventArgs e) @@ -183,6 +196,7 @@ private void LoadMemoryStatsOverviewChart(List memoryData, int _legendPanels[MemoryStatsOverviewChart] = null; } MemoryStatsOverviewChart.Plot.Clear(); + _memoryStatsOverviewHover?.Clear(); TabHelpers.ApplyDarkModeToChart(MemoryStatsOverviewChart); var dataList = memoryData?.OrderBy(d => d.CollectionTime).ToList() ?? new List(); @@ -216,24 +230,28 @@ private void LoadMemoryStatsOverviewChart(List memoryData, int totalScatter.MarkerSize = 5; totalScatter.Color = TabHelpers.ChartColors[9]; totalScatter.LegendText = "Total Memory"; + _memoryStatsOverviewHover?.Add(totalScatter, "Total Memory"); var bufferScatter = MemoryStatsOverviewChart.Plot.Add.Scatter(bufferXs, bufferYs); bufferScatter.LineWidth = 2; bufferScatter.MarkerSize = 5; bufferScatter.Color = TabHelpers.ChartColors[0]; bufferScatter.LegendText = "Buffer Pool"; + _memoryStatsOverviewHover?.Add(bufferScatter, "Buffer Pool"); var cacheScatter = MemoryStatsOverviewChart.Plot.Add.Scatter(cacheXs, cacheYs); cacheScatter.LineWidth = 2; cacheScatter.MarkerSize = 5; cacheScatter.Color = TabHelpers.ChartColors[1]; cacheScatter.LegendText = "Plan Cache"; + _memoryStatsOverviewHover?.Add(cacheScatter, "Plan Cache"); var availScatter = MemoryStatsOverviewChart.Plot.Add.Scatter(availXs, availYs); availScatter.LineWidth = 2; availScatter.MarkerSize = 5; availScatter.Color = TabHelpers.ChartColors[2]; availScatter.LegendText = "Available Physical"; + _memoryStatsOverviewHover?.Add(availScatter, "Available Physical"); _legendPanels[MemoryStatsOverviewChart] = MemoryStatsOverviewChart.Plot.ShowLegend(ScottPlot.Edge.Bottom); MemoryStatsOverviewChart.Plot.Legend.FontSize = 12; @@ -389,6 +407,7 @@ private void LoadMemoryGrantsChart(IEnumerable data, int h _legendPanels[MemoryGrantsChart] = null; } MemoryGrantsChart.Plot.Clear(); + _memoryGrantsHover?.Clear(); TabHelpers.ApplyDarkModeToChart(MemoryGrantsChart); var dataList = data?.OrderBy(d => d.CollectionTime).ToList() ?? new List(); @@ -422,12 +441,14 @@ private void LoadMemoryGrantsChart(IEnumerable data, int h grantedScatter.MarkerSize = 5; grantedScatter.Color = TabHelpers.ChartColors[0]; grantedScatter.LegendText = "Granted MB"; + _memoryGrantsHover?.Add(grantedScatter, "Granted MB"); var targetScatter = MemoryGrantsChart.Plot.Add.Scatter(targetXs, targetYs); targetScatter.LineWidth = 2; targetScatter.MarkerSize = 5; targetScatter.Color = TabHelpers.ChartColors[2]; targetScatter.LegendText = "Target MB"; + _memoryGrantsHover?.Add(targetScatter, "Target MB"); _legendPanels[MemoryGrantsChart] = MemoryGrantsChart.Plot.ShowLegend(ScottPlot.Edge.Bottom); MemoryGrantsChart.Plot.Legend.FontSize = 12; @@ -488,6 +509,7 @@ private void LoadMemoryClerksChart(List data, int hoursBack, D _legendPanels[MemoryClerksChart] = null; } MemoryClerksChart.Plot.Clear(); + _memoryClerksHover?.Clear(); TabHelpers.ApplyDarkModeToChart(MemoryClerksChart); var dataList = data ?? new List(); @@ -521,7 +543,9 @@ private void LoadMemoryClerksChart(List data, int hoursBack, D scatter.LineWidth = 2; scatter.MarkerSize = 5; scatter.Color = colors[colorIndex % colors.Length]; - scatter.LegendText = clerkType.Length > 20 ? clerkType.Substring(0, 20) + "..." : clerkType; + var label = clerkType.Length > 20 ? clerkType.Substring(0, 20) + "..." : clerkType; + scatter.LegendText = label; + _memoryClerksHover?.Add(scatter, label); colorIndex++; } } @@ -623,6 +647,7 @@ private void LoadPlanCacheChart(IEnumerable data, int hoursB _legendPanels[PlanCacheChart] = null; } PlanCacheChart.Plot.Clear(); + _planCacheHover?.Clear(); TabHelpers.ApplyDarkModeToChart(PlanCacheChart); var dataList = data?.ToList() ?? new List(); @@ -650,6 +675,7 @@ private void LoadPlanCacheChart(IEnumerable data, int hoursB singleScatter.MarkerSize = 5; singleScatter.Color = TabHelpers.ChartColors[3]; singleScatter.LegendText = "Single-Use"; + _planCacheHover?.Add(singleScatter, "Single-Use"); // Multi-Use series with gap filling var (multiXs, multiYs) = TabHelpers.FillTimeSeriesGaps( @@ -661,6 +687,7 @@ private void LoadPlanCacheChart(IEnumerable data, int hoursB multiScatter.MarkerSize = 5; multiScatter.Color = TabHelpers.ChartColors[1]; multiScatter.LegendText = "Multi-Use"; + _planCacheHover?.Add(multiScatter, "Multi-Use"); _legendPanels[PlanCacheChart] = PlanCacheChart.Plot.ShowLegend(ScottPlot.Edge.Bottom); PlanCacheChart.Plot.Legend.FontSize = 12; @@ -762,6 +789,7 @@ private void LoadMemoryPressureEventsChart(IEnumerable _legendPanels[MemoryPressureEventsChart] = null; } MemoryPressureEventsChart.Plot.Clear(); + _memoryPressureEventsHover?.Clear(); TabHelpers.ApplyDarkModeToChart(MemoryPressureEventsChart); // Only chart HIGH severity events @@ -788,6 +816,7 @@ private void LoadMemoryPressureEventsChart(IEnumerable highScatter.MarkerSize = 5; highScatter.Color = TabHelpers.ChartColors[3]; highScatter.LegendText = "High Pressure Events"; + _memoryPressureEventsHover?.Add(highScatter, "High Pressure Events"); _legendPanels[MemoryPressureEventsChart] = MemoryPressureEventsChart.Plot.ShowLegend(ScottPlot.Edge.Bottom); MemoryPressureEventsChart.Plot.Legend.FontSize = 12; diff --git a/Dashboard/Controls/QueryPerformanceContent.xaml.cs b/Dashboard/Controls/QueryPerformanceContent.xaml.cs index 34509c4e..e903040d 100644 --- a/Dashboard/Controls/QueryPerformanceContent.xaml.cs +++ b/Dashboard/Controls/QueryPerformanceContent.xaml.cs @@ -93,12 +93,23 @@ public partial class QueryPerformanceContent : UserControl // Legend panel references for edge-based legends (ScottPlot issue #4717 workaround) private Dictionary _legendPanels = new(); + // Chart hover tooltips + private Helpers.ChartHoverHelper? _queryDurationHover; + private Helpers.ChartHoverHelper? _procDurationHover; + private Helpers.ChartHoverHelper? _qsDurationHover; + private Helpers.ChartHoverHelper? _execTrendsHover; + public QueryPerformanceContent() { InitializeComponent(); SetupChartSaveMenus(); Loaded += OnLoaded; Unloaded += OnUnloaded; + + _queryDurationHover = new Helpers.ChartHoverHelper(QueryPerfTrendsQueryChart, "ms/sec"); + _procDurationHover = new Helpers.ChartHoverHelper(QueryPerfTrendsProcChart, "ms/sec"); + _qsDurationHover = new Helpers.ChartHoverHelper(QueryPerfTrendsQsChart, "ms/sec"); + _execTrendsHover = new Helpers.ChartHoverHelper(QueryPerfTrendsExecChart, "/sec"); } private void OnUnloaded(object sender, RoutedEventArgs e) @@ -243,9 +254,9 @@ await Task.WhenAll( QueryStoreNoDataMessage.Visibility = queryStore.Count == 0 ? Visibility.Visible : Visibility.Collapsed; // Populate charts from time-series data - LoadDurationChart(QueryPerfTrendsQueryChart, await queryDurationTrendsTask, _perfTrendsHoursBack, _perfTrendsFromDate, _perfTrendsToDate, "Duration (ms/sec)", TabHelpers.ChartColors[0]); - LoadDurationChart(QueryPerfTrendsProcChart, await procDurationTrendsTask, _perfTrendsHoursBack, _perfTrendsFromDate, _perfTrendsToDate, "Duration (ms/sec)", TabHelpers.ChartColors[1]); - LoadDurationChart(QueryPerfTrendsQsChart, await qsDurationTrendsTask, _perfTrendsHoursBack, _perfTrendsFromDate, _perfTrendsToDate, "Duration (ms/sec)", TabHelpers.ChartColors[4]); + LoadDurationChart(QueryPerfTrendsQueryChart, await queryDurationTrendsTask, _perfTrendsHoursBack, _perfTrendsFromDate, _perfTrendsToDate, "Duration (ms/sec)", TabHelpers.ChartColors[0], _queryDurationHover); + LoadDurationChart(QueryPerfTrendsProcChart, await procDurationTrendsTask, _perfTrendsHoursBack, _perfTrendsFromDate, _perfTrendsToDate, "Duration (ms/sec)", TabHelpers.ChartColors[1], _procDurationHover); + LoadDurationChart(QueryPerfTrendsQsChart, await qsDurationTrendsTask, _perfTrendsHoursBack, _perfTrendsFromDate, _perfTrendsToDate, "Duration (ms/sec)", TabHelpers.ChartColors[4], _qsDurationHover); LoadExecChart(await execTrendsTask, _perfTrendsHoursBack, _perfTrendsFromDate, _perfTrendsToDate); } catch (Exception ex) @@ -890,7 +901,7 @@ private void LongRunningQueryPatternsNumericFilterTextBox_TextChanged(object sen /// Renders a duration trend chart from time-series data (per-collection_time aggregation). /// Replaces the old per-query-summary approach that produced too few data points. /// - private void LoadDurationChart(WpfPlot chart, IEnumerable trendData, int hoursBack, DateTime? fromDate, DateTime? toDate, string legendText, ScottPlot.Color color) + private void LoadDurationChart(WpfPlot chart, IEnumerable trendData, int hoursBack, DateTime? fromDate, DateTime? toDate, string legendText, ScottPlot.Color color, Helpers.ChartHoverHelper? hover = null) { try { @@ -905,6 +916,7 @@ private void LoadDurationChart(WpfPlot chart, IEnumerable tre _legendPanels[chart] = null; } chart.Plot.Clear(); + hover?.Clear(); TabHelpers.ApplyDarkModeToChart(chart); var dataList = (trendData ?? Enumerable.Empty()) @@ -920,6 +932,7 @@ private void LoadDurationChart(WpfPlot chart, IEnumerable tre scatter.MarkerSize = 5; scatter.Color = color; scatter.LegendText = legendText; + hover?.Add(scatter, legendText); if (xs.Length == 0) { @@ -959,6 +972,7 @@ private void LoadExecChart(IEnumerable execTrends, int hours _legendPanels[QueryPerfTrendsExecChart] = null; } QueryPerfTrendsExecChart.Plot.Clear(); + _execTrendsHover?.Clear(); TabHelpers.ApplyDarkModeToChart(QueryPerfTrendsExecChart); var dataList = (execTrends ?? Enumerable.Empty()) @@ -974,6 +988,7 @@ private void LoadExecChart(IEnumerable execTrends, int hours scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[0]; scatter.LegendText = "Executions/sec"; + _execTrendsHover?.Add(scatter, "Executions/sec"); if (xs.Length == 0) { diff --git a/Dashboard/Controls/ResourceMetricsContent.xaml.cs b/Dashboard/Controls/ResourceMetricsContent.xaml.cs index 8d986440..08e9bcde 100644 --- a/Dashboard/Controls/ResourceMetricsContent.xaml.cs +++ b/Dashboard/Controls/ResourceMetricsContent.xaml.cs @@ -99,6 +99,12 @@ public partial class ResourceMetricsContent : UserControl private Helpers.ChartHoverHelper? _fileIoWriteHover; private Helpers.ChartHoverHelper? _perfmonHover; private Helpers.ChartHoverHelper? _waitStatsHover; + private Helpers.ChartHoverHelper? _tempdbStatsHover; + private Helpers.ChartHoverHelper? _tempDbLatencyHover; + private Helpers.ChartHoverHelper? _serverTrendsCpuHover; + private Helpers.ChartHoverHelper? _serverTrendsTempdbHover; + private Helpers.ChartHoverHelper? _serverTrendsMemoryHover; + private Helpers.ChartHoverHelper? _serverTrendsPerfmonHover; // Column filter popup and state private Popup? _filterPopup; private ColumnFilterPopup? _filterPopupContent; @@ -130,6 +136,12 @@ public ResourceMetricsContent() _fileIoWriteHover = new Helpers.ChartHoverHelper(UserDbWriteLatencyChart, "ms"); _perfmonHover = new Helpers.ChartHoverHelper(PerfmonCountersChart, ""); _waitStatsHover = new Helpers.ChartHoverHelper(WaitStatsDetailChart, "ms/sec"); + _tempdbStatsHover = new Helpers.ChartHoverHelper(TempdbStatsChart, "MB"); + _tempDbLatencyHover = new Helpers.ChartHoverHelper(TempDbLatencyChart, "ms"); + _serverTrendsCpuHover = new Helpers.ChartHoverHelper(ServerUtilTrendsCpuChart, "%"); + _serverTrendsTempdbHover = new Helpers.ChartHoverHelper(ServerUtilTrendsTempdbChart, "MB"); + _serverTrendsMemoryHover = new Helpers.ChartHoverHelper(ServerUtilTrendsMemoryChart, "MB"); + _serverTrendsPerfmonHover = new Helpers.ChartHoverHelper(ServerUtilTrendsPerfmonChart, "/sec"); } private void OnLoaded(object sender, RoutedEventArgs e) @@ -491,6 +503,7 @@ private void LoadCombinedTempDbLatencyChart(List da _legendPanels[TempDbLatencyChart] = null; } TempDbLatencyChart.Plot.Clear(); + _tempDbLatencyHover?.Clear(); TabHelpers.ApplyDarkModeToChart(TempDbLatencyChart); if (data != null && data.Count > 0) @@ -516,6 +529,7 @@ private void LoadCombinedTempDbLatencyChart(List da readScatter.MarkerSize = 5; readScatter.Color = TabHelpers.ChartColors[0]; readScatter.LegendText = "Read Latency"; + _tempDbLatencyHover?.Add(readScatter, "Read Latency"); // Write Latency series var (writeXs, writeYs) = TabHelpers.FillTimeSeriesGaps( @@ -526,6 +540,7 @@ private void LoadCombinedTempDbLatencyChart(List da writeScatter.MarkerSize = 5; writeScatter.Color = TabHelpers.ChartColors[2]; writeScatter.LegendText = "Write Latency"; + _tempDbLatencyHover?.Add(writeScatter, "Write Latency"); // Store legend panel reference for removal on refresh (ScottPlot issue #4717) _legendPanels[TempDbLatencyChart] = TempDbLatencyChart.Plot.ShowLegend(ScottPlot.Edge.Bottom); @@ -562,6 +577,7 @@ private void LoadTempdbStatsChart(IEnumerable data, int hoursBa _legendPanels[TempdbStatsChart] = null; } TempdbStatsChart.Plot.Clear(); + _tempdbStatsHover?.Clear(); TabHelpers.ApplyDarkModeToChart(TempdbStatsChart); var dataList = data?.OrderBy(d => d.CollectionTime).ToList() ?? new List(); @@ -576,6 +592,7 @@ private void LoadTempdbStatsChart(IEnumerable data, int hoursBa userScatter.MarkerSize = 5; userScatter.Color = TabHelpers.ChartColors[0]; userScatter.LegendText = "User Objects"; + _tempdbStatsHover?.Add(userScatter, "User Objects"); // Version Store series var (versionXs, versionYs) = TabHelpers.FillTimeSeriesGaps( @@ -586,6 +603,7 @@ private void LoadTempdbStatsChart(IEnumerable data, int hoursBa versionScatter.MarkerSize = 5; versionScatter.Color = TabHelpers.ChartColors[1]; versionScatter.LegendText = "Version Store"; + _tempdbStatsHover?.Add(versionScatter, "Version Store"); // Internal Objects series var (internalXs, internalYs) = TabHelpers.FillTimeSeriesGaps( @@ -596,6 +614,7 @@ private void LoadTempdbStatsChart(IEnumerable data, int hoursBa internalScatter.MarkerSize = 5; internalScatter.Color = TabHelpers.ChartColors[2]; internalScatter.LegendText = "Internal Objects"; + _tempdbStatsHover?.Add(internalScatter, "Internal Objects"); // Unallocated (free space) series var (unallocXs, unallocYs) = TabHelpers.FillTimeSeriesGaps( @@ -608,6 +627,7 @@ private void LoadTempdbStatsChart(IEnumerable data, int hoursBa unallocScatter.MarkerSize = 5; unallocScatter.Color = TabHelpers.ChartColors[9]; unallocScatter.LegendText = "Unallocated"; + _tempdbStatsHover?.Add(unallocScatter, "Unallocated"); } // Top Task Total MB series (worst session's usage) @@ -979,6 +999,7 @@ private void LoadServerTrendsCpuChart(IEnumerable cpuData, int hou _legendPanels[ServerUtilTrendsCpuChart] = null; } ServerUtilTrendsCpuChart.Plot.Clear(); + _serverTrendsCpuHover?.Clear(); TabHelpers.ApplyDarkModeToChart(ServerUtilTrendsCpuChart); var dataList = cpuData?.OrderBy(d => d.EventTime).ToList() ?? new List(); @@ -993,6 +1014,7 @@ private void LoadServerTrendsCpuChart(IEnumerable cpuData, int hou sqlScatter.MarkerSize = 5; sqlScatter.Color = TabHelpers.ChartColors[0]; sqlScatter.LegendText = "SQL CPU"; + _serverTrendsCpuHover?.Add(sqlScatter, "SQL CPU"); // Other CPU series var (otherXs, otherYs) = TabHelpers.FillTimeSeriesGaps( @@ -1003,6 +1025,7 @@ private void LoadServerTrendsCpuChart(IEnumerable cpuData, int hou otherScatter.MarkerSize = 5; otherScatter.Color = TabHelpers.ChartColors[2]; otherScatter.LegendText = "Other CPU"; + _serverTrendsCpuHover?.Add(otherScatter, "Other CPU"); _legendPanels[ServerUtilTrendsCpuChart] = ServerUtilTrendsCpuChart.Plot.ShowLegend(ScottPlot.Edge.Bottom); ServerUtilTrendsCpuChart.Plot.Legend.FontSize = 12; @@ -1038,6 +1061,7 @@ private void LoadServerTrendsTempdbChart(IEnumerable tempdbData _legendPanels[ServerUtilTrendsTempdbChart] = null; } ServerUtilTrendsTempdbChart.Plot.Clear(); + _serverTrendsTempdbHover?.Clear(); TabHelpers.ApplyDarkModeToChart(ServerUtilTrendsTempdbChart); var dataList = tempdbData?.OrderBy(d => d.CollectionTime).ToList() ?? new List(); @@ -1056,12 +1080,14 @@ private void LoadServerTrendsTempdbChart(IEnumerable tempdbData userScatter.MarkerSize = 5; userScatter.Color = TabHelpers.ChartColors[1]; userScatter.LegendText = "User Objects"; + _serverTrendsTempdbHover?.Add(userScatter, "User Objects"); var versionScatter = ServerUtilTrendsTempdbChart.Plot.Add.Scatter(versionXs, versionYs); versionScatter.LineWidth = 2; versionScatter.MarkerSize = 5; versionScatter.Color = TabHelpers.ChartColors[2]; versionScatter.LegendText = "Version Store"; + _serverTrendsTempdbHover?.Add(versionScatter, "Version Store"); _legendPanels[ServerUtilTrendsTempdbChart] = ServerUtilTrendsTempdbChart.Plot.ShowLegend(ScottPlot.Edge.Bottom); ServerUtilTrendsTempdbChart.Plot.Legend.FontSize = 12; @@ -1097,6 +1123,7 @@ private void LoadServerTrendsMemoryChart(IEnumerable memoryData _legendPanels[ServerUtilTrendsMemoryChart] = null; } ServerUtilTrendsMemoryChart.Plot.Clear(); + _serverTrendsMemoryHover?.Clear(); TabHelpers.ApplyDarkModeToChart(ServerUtilTrendsMemoryChart); var dataList = memoryData?.OrderBy(d => d.CollectionTime).ToList() ?? new List(); @@ -1151,12 +1178,14 @@ private void LoadServerTrendsMemoryChart(IEnumerable memoryData bufferScatter.MarkerSize = 5; bufferScatter.Color = TabHelpers.ChartColors[4]; bufferScatter.LegendText = "Buffer Pool"; + _serverTrendsMemoryHover?.Add(bufferScatter, "Buffer Pool"); var cacheScatter = ServerUtilTrendsMemoryChart.Plot.Add.Scatter(cacheXs, cacheYs); cacheScatter.LineWidth = 2; cacheScatter.MarkerSize = 5; cacheScatter.Color = TabHelpers.ChartColors[5]; cacheScatter.LegendText = "Plan Cache"; + _serverTrendsMemoryHover?.Add(cacheScatter, "Plan Cache"); _legendPanels[ServerUtilTrendsMemoryChart] = ServerUtilTrendsMemoryChart.Plot.ShowLegend(ScottPlot.Edge.Bottom); ServerUtilTrendsMemoryChart.Plot.Legend.FontSize = 12; @@ -1200,6 +1229,7 @@ private void LoadServerTrendsPerfmonChart(IEnumerable perfmonD _legendPanels[ServerUtilTrendsPerfmonChart] = null; } ServerUtilTrendsPerfmonChart.Plot.Clear(); + _serverTrendsPerfmonHover?.Clear(); TabHelpers.ApplyDarkModeToChart(ServerUtilTrendsPerfmonChart); var allData = (perfmonData ?? Enumerable.Empty()).ToList(); @@ -1234,6 +1264,7 @@ private void LoadServerTrendsPerfmonChart(IEnumerable perfmonD scatter.MarkerSize = 5; scatter.Color = color; scatter.LegendText = counterName.Replace("/sec", "", StringComparison.Ordinal); + _serverTrendsPerfmonHover?.Add(scatter, counterName); linesAdded++; } } diff --git a/Dashboard/Controls/SystemEventsContent.xaml.cs b/Dashboard/Controls/SystemEventsContent.xaml.cs index f9070bd9..5773c094 100644 --- a/Dashboard/Controls/SystemEventsContent.xaml.cs +++ b/Dashboard/Controls/SystemEventsContent.xaml.cs @@ -102,12 +102,53 @@ public partial class SystemEventsContent : UserControl // Legend panel references for edge-based legends (ScottPlot issue #4717 workaround) private Dictionary _legendPanels = new(); + // Chart hover tooltips + private Helpers.ChartHoverHelper? _badPagesHover; + private Helpers.ChartHoverHelper? _dumpRequestsHover; + private Helpers.ChartHoverHelper? _accessViolationsHover; + private Helpers.ChartHoverHelper? _writeAccessViolationsHover; + private Helpers.ChartHoverHelper? _nonYieldingTasksHover; + private Helpers.ChartHoverHelper? _latchWarningsHover; + private Helpers.ChartHoverHelper? _sickSpinlocksHover; + private Helpers.ChartHoverHelper? _cpuComparisonHover; + private Helpers.ChartHoverHelper? _severeErrorsHover; + private Helpers.ChartHoverHelper? _ioIssuesHover; + private Helpers.ChartHoverHelper? _longestPendingIoHover; + private Helpers.ChartHoverHelper? _schedulerIssuesHover; + private Helpers.ChartHoverHelper? _memoryConditionsHover; + private Helpers.ChartHoverHelper? _cpuTasksHover; + private Helpers.ChartHoverHelper? _memoryBrokerHover; + private Helpers.ChartHoverHelper? _memoryBrokerRatioHover; + private Helpers.ChartHoverHelper? _memoryNodeOomHover; + private Helpers.ChartHoverHelper? _memoryNodeOomUtilHover; + private Helpers.ChartHoverHelper? _memoryNodeOomMemoryHover; + public SystemEventsContent() { InitializeComponent(); SetupChartContextMenus(); Loaded += OnLoaded; Unloaded += OnUnloaded; + + _badPagesHover = new Helpers.ChartHoverHelper(BadPagesChart, "events"); + _dumpRequestsHover = new Helpers.ChartHoverHelper(DumpRequestsChart, "events"); + _accessViolationsHover = new Helpers.ChartHoverHelper(AccessViolationsChart, "events"); + _writeAccessViolationsHover = new Helpers.ChartHoverHelper(WriteAccessViolationsChart, "events"); + _nonYieldingTasksHover = new Helpers.ChartHoverHelper(NonYieldingTasksChart, "events"); + _latchWarningsHover = new Helpers.ChartHoverHelper(LatchWarningsChart, "events"); + _sickSpinlocksHover = new Helpers.ChartHoverHelper(SickSpinlocksChart, "backoffs"); + _cpuComparisonHover = new Helpers.ChartHoverHelper(CpuComparisonChart, "%"); + _severeErrorsHover = new Helpers.ChartHoverHelper(SevereErrorsChart, "events"); + _ioIssuesHover = new Helpers.ChartHoverHelper(IOIssuesChart, "events"); + _longestPendingIoHover = new Helpers.ChartHoverHelper(LongestPendingIOChart, "ms"); + _schedulerIssuesHover = new Helpers.ChartHoverHelper(SchedulerIssuesChart, "ms"); + _memoryConditionsHover = new Helpers.ChartHoverHelper(MemoryConditionsChart, "events"); + _cpuTasksHover = new Helpers.ChartHoverHelper(CPUTasksChart, "workers"); + _memoryBrokerHover = new Helpers.ChartHoverHelper(MemoryBrokerChart, ""); + _memoryBrokerRatioHover = new Helpers.ChartHoverHelper(MemoryBrokerRatioChart, ""); + _memoryNodeOomHover = new Helpers.ChartHoverHelper(MemoryNodeOOMChart, "events"); + _memoryNodeOomUtilHover = new Helpers.ChartHoverHelper(MemoryNodeOOMUtilChart, "%"); + _memoryNodeOomMemoryHover = new Helpers.ChartHoverHelper(MemoryNodeOOMMemoryChart, "MB"); } private void OnUnloaded(object sender, RoutedEventArgs e) @@ -409,6 +450,7 @@ private void LoadCorruptionEventsCharts(List data, bool hasData = orderedData.Count > 0; // Bad Pages Detected Chart BadPagesChart.Plot.Clear(); + _badPagesHover?.Clear(); TabHelpers.ApplyDarkModeToChart(BadPagesChart); if (hasData) { @@ -419,6 +461,7 @@ private void LoadCorruptionEventsCharts(List data, scatter.LineWidth = 2; scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[3]; + _badPagesHover?.Add(scatter, "Bad Pages"); } else { @@ -437,6 +480,7 @@ private void LoadCorruptionEventsCharts(List data, // Interval Dump Requests Chart DumpRequestsChart.Plot.Clear(); + _dumpRequestsHover?.Clear(); TabHelpers.ApplyDarkModeToChart(DumpRequestsChart); if (hasData) { @@ -447,6 +491,7 @@ private void LoadCorruptionEventsCharts(List data, scatter.LineWidth = 2; scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[2]; + _dumpRequestsHover?.Add(scatter, "Dump Requests"); } else { @@ -465,6 +510,7 @@ private void LoadCorruptionEventsCharts(List data, // Access Violations Chart AccessViolationsChart.Plot.Clear(); + _accessViolationsHover?.Clear(); TabHelpers.ApplyDarkModeToChart(AccessViolationsChart); if (hasData) { @@ -475,6 +521,7 @@ private void LoadCorruptionEventsCharts(List data, scatter.LineWidth = 2; scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[4]; + _accessViolationsHover?.Add(scatter, "Access Violations"); } else { @@ -493,6 +540,7 @@ private void LoadCorruptionEventsCharts(List data, // Write Access Violations Chart WriteAccessViolationsChart.Plot.Clear(); + _writeAccessViolationsHover?.Clear(); TabHelpers.ApplyDarkModeToChart(WriteAccessViolationsChart); if (hasData) { @@ -503,6 +551,7 @@ private void LoadCorruptionEventsCharts(List data, scatter.LineWidth = 2; scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[0]; + _writeAccessViolationsHover?.Add(scatter, "Write Access Violations"); } else { @@ -533,6 +582,7 @@ private void LoadContentionEventsCharts(List data, bool hasData = orderedData.Count > 0; // Non-Yielding Tasks Chart NonYieldingTasksChart.Plot.Clear(); + _nonYieldingTasksHover?.Clear(); TabHelpers.ApplyDarkModeToChart(NonYieldingTasksChart); if (hasData) { @@ -543,6 +593,7 @@ private void LoadContentionEventsCharts(List data, scatter.LineWidth = 2; scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[3]; + _nonYieldingTasksHover?.Add(scatter, "Non-Yielding Tasks"); } else { @@ -561,6 +612,7 @@ private void LoadContentionEventsCharts(List data, // Latch Warnings Chart LatchWarningsChart.Plot.Clear(); + _latchWarningsHover?.Clear(); TabHelpers.ApplyDarkModeToChart(LatchWarningsChart); if (hasData) { @@ -571,6 +623,7 @@ private void LoadContentionEventsCharts(List data, scatter.LineWidth = 2; scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[2]; + _latchWarningsHover?.Add(scatter, "Latch Warnings"); } else { @@ -594,6 +647,7 @@ private void LoadContentionEventsCharts(List data, _legendPanels[SickSpinlocksChart] = null; } SickSpinlocksChart.Plot.Clear(); + _sickSpinlocksHover?.Clear(); TabHelpers.ApplyDarkModeToChart(SickSpinlocksChart); if (hasData) { @@ -624,6 +678,7 @@ private void LoadContentionEventsCharts(List data, scatter.MarkerSize = 5; scatter.Color = colors[colorIndex % colors.Length]; scatter.LegendText = spinlockType ?? "Unknown"; + _sickSpinlocksHover?.Add(scatter, spinlockType ?? "Unknown"); colorIndex++; } } @@ -656,6 +711,7 @@ private void LoadContentionEventsCharts(List data, _legendPanels[CpuComparisonChart] = null; } CpuComparisonChart.Plot.Clear(); + _cpuComparisonHover?.Clear(); TabHelpers.ApplyDarkModeToChart(CpuComparisonChart); if (hasData) { @@ -668,6 +724,7 @@ private void LoadContentionEventsCharts(List data, sysScatter.MarkerSize = 5; sysScatter.Color = TabHelpers.ChartColors[0]; sysScatter.LegendText = "System CPU %"; + _cpuComparisonHover?.Add(sysScatter, "System CPU %"); // SQL CPU series var (sqlXs, sqlYs) = TabHelpers.FillTimeSeriesGaps( @@ -678,6 +735,7 @@ private void LoadContentionEventsCharts(List data, sqlScatter.MarkerSize = 5; sqlScatter.Color = TabHelpers.ChartColors[1]; sqlScatter.LegendText = "SQL CPU %"; + _cpuComparisonHover?.Add(sqlScatter, "SQL CPU %"); _legendPanels[CpuComparisonChart] = CpuComparisonChart.Plot.ShowLegend(ScottPlot.Edge.Bottom); CpuComparisonChart.Plot.Legend.FontSize = 12; @@ -750,6 +808,7 @@ private void LoadSevereErrorsChart(IEnumerable data _legendPanels[SevereErrorsChart] = null; } SevereErrorsChart.Plot.Clear(); + _severeErrorsHover?.Clear(); TabHelpers.ApplyDarkModeToChart(SevereErrorsChart); var dataList = data?.ToList() ?? new List(); @@ -775,6 +834,7 @@ private void LoadSevereErrorsChart(IEnumerable data scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[3]; scatter.LegendText = "Error Count"; + _severeErrorsHover?.Add(scatter, "Error Count"); _legendPanels[SevereErrorsChart] = SevereErrorsChart.Plot.ShowLegend(ScottPlot.Edge.Bottom); SevereErrorsChart.Plot.Legend.FontSize = 12; @@ -894,6 +954,7 @@ private void LoadIOIssuesChart(IEnumerable data, int ho _legendPanels[IOIssuesChart] = null; } IOIssuesChart.Plot.Clear(); + _ioIssuesHover?.Clear(); TabHelpers.ApplyDarkModeToChart(IOIssuesChart); var dataList = data?.Where(d => d.EventTime.HasValue).OrderBy(d => d.EventTime).ToList() ?? new List(); @@ -921,6 +982,7 @@ private void LoadIOIssuesChart(IEnumerable data, int ho scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[3]; scatter.LegendText = "Latch Timeouts"; + _ioIssuesHover?.Add(scatter, "Latch Timeouts"); } if (longIos.Any(c => c > 0)) @@ -931,6 +993,7 @@ private void LoadIOIssuesChart(IEnumerable data, int ho scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[2]; scatter.LegendText = "Long IOs"; + _ioIssuesHover?.Add(scatter, "Long IOs"); } _legendPanels[IOIssuesChart] = IOIssuesChart.Plot.ShowLegend(ScottPlot.Edge.Bottom); @@ -969,6 +1032,7 @@ private void LoadLongestPendingIOChart(IEnumerable data _legendPanels[LongestPendingIOChart] = null; } LongestPendingIOChart.Plot.Clear(); + _longestPendingIoHover?.Clear(); TabHelpers.ApplyDarkModeToChart(LongestPendingIOChart); var dataList = data?.Where(d => d.EventTime.HasValue && !string.IsNullOrEmpty(d.LongestPendingRequestsFilePath)).ToList() ?? new List(); @@ -1022,6 +1086,7 @@ private void LoadLongestPendingIOChart(IEnumerable data scatter.MarkerSize = 5; scatter.Color = colors[colorIndex % colors.Length]; scatter.LegendText = fileName; + _longestPendingIoHover?.Add(scatter, fileName); colorIndex++; } } @@ -1091,6 +1156,7 @@ private void LoadSchedulerIssuesChart(IEnumerable d.EventTime.HasValue).ToList() ?? new List(); @@ -1123,6 +1189,7 @@ long ParseNonYield(string? value) scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[2]; scatter.LegendText = "Total Non-Yield Time"; + _schedulerIssuesHover?.Add(scatter, "Total Non-Yield Time"); _legendPanels[SchedulerIssuesChart] = SchedulerIssuesChart.Plot.ShowLegend(ScottPlot.Edge.Bottom); SchedulerIssuesChart.Plot.Legend.FontSize = 12; @@ -1244,6 +1311,7 @@ private void LoadMemoryConditionsChart(IEnumerable d.EventTime.HasValue).ToList() ?? new List(); @@ -1270,6 +1338,7 @@ private void LoadMemoryConditionsChart(IEnumerable data, int h _legendPanels[CPUTasksChart] = null; } CPUTasksChart.Plot.Clear(); + _cpuTasksHover?.Clear(); TabHelpers.ApplyDarkModeToChart(CPUTasksChart); var dataList = data?.Where(d => d.EventTime.HasValue).ToList() ?? new List(); @@ -1368,6 +1438,7 @@ private void LoadCPUTasksChart(IEnumerable data, int h scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[0]; scatter.LegendText = "Workers Created"; + _cpuTasksHover?.Add(scatter, "Workers Created"); // Max Workers threshold line (horizontal) var maxWorkersValue = dataList.Max(d => d.MaxWorkers ?? 0); @@ -1549,6 +1620,8 @@ private void LoadMemoryBrokerChart(IEnumerable dat double xMax = rangeEnd.ToOADate(); /* Clear both charts */ + _memoryBrokerHover?.Clear(); + _memoryBrokerRatioHover?.Clear(); foreach (var chart in new[] { MemoryBrokerChart, MemoryBrokerRatioChart }) { if (_legendPanels.TryGetValue(chart, out var existingPanel) && existingPanel != null) @@ -1589,7 +1662,9 @@ private void LoadMemoryBrokerChart(IEnumerable dat scatter.LineWidth = 2; scatter.MarkerSize = 5; scatter.Color = colors[colorIndex % colors.Length]; - scatter.LegendText = brokerGroup.Key.Length > 25 ? brokerGroup.Key.Substring(0, 25) + "..." : brokerGroup.Key; + var brokerLabel = brokerGroup.Key.Length > 25 ? brokerGroup.Key.Substring(0, 25) + "..." : brokerGroup.Key; + scatter.LegendText = brokerLabel; + _memoryBrokerHover?.Add(scatter, brokerLabel); colorIndex++; } } @@ -1616,6 +1691,7 @@ private void LoadMemoryBrokerChart(IEnumerable dat scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[0]; scatter.LegendText = "Memory Ratio"; + _memoryBrokerRatioHover?.Add(scatter, "Memory Ratio"); } if (overallData.Count >= 1) @@ -1630,6 +1706,7 @@ private void LoadMemoryBrokerChart(IEnumerable dat scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[2]; scatter.LegendText = "Overall"; + _memoryBrokerRatioHover?.Add(scatter, "Overall"); } if (hasRatioData) @@ -1773,6 +1850,7 @@ private void LoadMemoryNodeOOMChart(IEnumerable d _legendPanels[MemoryNodeOOMChart] = null; } MemoryNodeOOMChart.Plot.Clear(); + _memoryNodeOomHover?.Clear(); TabHelpers.ApplyDarkModeToChart(MemoryNodeOOMChart); var dataList = data?.Where(d => d.EventTime.HasValue).ToList() ?? new List(); @@ -1797,6 +1875,7 @@ private void LoadMemoryNodeOOMChart(IEnumerable d scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[3]; scatter.LegendText = "OOM Event Count"; + _memoryNodeOomHover?.Add(scatter, "OOM Event Count"); _legendPanels[MemoryNodeOOMChart] = MemoryNodeOOMChart.Plot.ShowLegend(ScottPlot.Edge.Bottom); MemoryNodeOOMChart.Plot.Legend.FontSize = 12; @@ -1833,6 +1912,7 @@ private void LoadMemoryNodeOOMUtilChart(IEnumerable d.EventTime.HasValue && d.MemoryUtilizationPct.HasValue).ToList() ?? new List(); @@ -1849,6 +1929,7 @@ private void LoadMemoryNodeOOMUtilChart(IEnumerable d.EventTime.HasValue).ToList() ?? new List(); @@ -1900,6 +1982,7 @@ private void LoadMemoryNodeOOMMemoryChart(IEnumerable _legendPanels = new(); + // Chart hover tooltips + private Helpers.ChartHoverHelper? _resourceOverviewCpuHover; + private Helpers.ChartHoverHelper? _resourceOverviewMemoryHover; + private Helpers.ChartHoverHelper? _resourceOverviewIoHover; + private Helpers.ChartHoverHelper? _resourceOverviewWaitHover; + private Helpers.ChartHoverHelper? _lockWaitStatsHover; + private Helpers.ChartHoverHelper? _blockingEventsHover; + private Helpers.ChartHoverHelper? _blockingDurationHover; + private Helpers.ChartHoverHelper? _deadlocksHover; + private Helpers.ChartHoverHelper? _deadlockWaitTimeHover; + public ServerTab(ServerConnection serverConnection, int utcOffsetMinutes = 0) { InitializeComponent(); + _resourceOverviewCpuHover = new Helpers.ChartHoverHelper(ResourceOverviewCpuChart, "%"); + _resourceOverviewMemoryHover = new Helpers.ChartHoverHelper(ResourceOverviewMemoryChart, "MB"); + _resourceOverviewIoHover = new Helpers.ChartHoverHelper(ResourceOverviewIoChart, "ms"); + _resourceOverviewWaitHover = new Helpers.ChartHoverHelper(ResourceOverviewWaitChart, "ms/sec"); + _lockWaitStatsHover = new Helpers.ChartHoverHelper(LockWaitStatsChart, "ms/sec"); + _blockingEventsHover = new Helpers.ChartHoverHelper(BlockingStatsBlockingEventsChart, "events"); + _blockingDurationHover = new Helpers.ChartHoverHelper(BlockingStatsDurationChart, "ms"); + _deadlocksHover = new Helpers.ChartHoverHelper(BlockingStatsDeadlocksChart, "events"); + _deadlockWaitTimeHover = new Helpers.ChartHoverHelper(BlockingStatsDeadlockWaitTimeChart, "ms"); + _serverConnection = serverConnection; UtcOffsetMinutes = utcOffsetMinutes; _credentialService = new CredentialService(); @@ -1642,6 +1663,7 @@ private void LoadBlockingStatsCharts(List data, int h // Get all unique time points for consistent X-axis across all charts // Blocking Events Delta Chart BlockingStatsBlockingEventsChart.Plot.Clear(); + _blockingEventsHover?.Clear(); ApplyDarkModeToChart(BlockingStatsBlockingEventsChart); var (blockingXs, blockingYs) = TabHelpers.FillTimeSeriesGaps( orderedData.Select(d => d.CollectionTime), @@ -1652,6 +1674,7 @@ private void LoadBlockingStatsCharts(List data, int h scatter.LineWidth = 2; scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[0]; + _blockingEventsHover?.Add(scatter, "Blocking Events"); } else { @@ -1670,6 +1693,7 @@ private void LoadBlockingStatsCharts(List data, int h // Blocking Duration Delta Chart BlockingStatsDurationChart.Plot.Clear(); + _blockingDurationHover?.Clear(); ApplyDarkModeToChart(BlockingStatsDurationChart); var (durationXs, durationYs) = TabHelpers.FillTimeSeriesGaps( orderedData.Select(d => d.CollectionTime), @@ -1680,6 +1704,7 @@ private void LoadBlockingStatsCharts(List data, int h scatter.LineWidth = 2; scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[2]; + _blockingDurationHover?.Add(scatter, "Blocking Duration"); } else { @@ -1698,6 +1723,7 @@ private void LoadBlockingStatsCharts(List data, int h // Deadlock Count Delta Chart BlockingStatsDeadlocksChart.Plot.Clear(); + _deadlocksHover?.Clear(); ApplyDarkModeToChart(BlockingStatsDeadlocksChart); var (deadlockXs, deadlockYs) = TabHelpers.FillTimeSeriesGaps( orderedData.Select(d => d.CollectionTime), @@ -1708,6 +1734,7 @@ private void LoadBlockingStatsCharts(List data, int h scatter.LineWidth = 2; scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[3]; + _deadlocksHover?.Add(scatter, "Deadlocks"); } else { @@ -1726,6 +1753,7 @@ private void LoadBlockingStatsCharts(List data, int h // Deadlock Wait Time Chart BlockingStatsDeadlockWaitTimeChart.Plot.Clear(); + _deadlockWaitTimeHover?.Clear(); ApplyDarkModeToChart(BlockingStatsDeadlockWaitTimeChart); var (deadlockWaitXs, deadlockWaitYs) = TabHelpers.FillTimeSeriesGaps( orderedData.Select(d => d.CollectionTime), @@ -1736,6 +1764,7 @@ private void LoadBlockingStatsCharts(List data, int h scatter.LineWidth = 2; scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[4]; + _deadlockWaitTimeHover?.Add(scatter, "Deadlock Wait Time"); } else { @@ -1767,6 +1796,7 @@ private void LoadLockWaitStatsChart(List data, int hoursBack, _legendPanels[LockWaitStatsChart] = null; } LockWaitStatsChart.Plot.Clear(); + _lockWaitStatsHover?.Clear(); ApplyDarkModeToChart(LockWaitStatsChart); // Get all unique time points across all wait types for gap filling @@ -1789,7 +1819,9 @@ private void LoadLockWaitStatsChart(List data, int hoursBack, scatter.LineWidth = 2; scatter.MarkerSize = 5; scatter.Color = colors[colorIndex % colors.Length]; - scatter.LegendText = waitType.Replace("LCK_M_", "").Replace("LCK_", ""); + var lockLabel = waitType.Replace("LCK_M_", "").Replace("LCK_", ""); + scatter.LegendText = lockLabel; + _lockWaitStatsHover?.Add(scatter, lockLabel); colorIndex++; } } @@ -2201,6 +2233,7 @@ private void LoadResourceOverviewCpuChart(IEnumerable cpuData, int _legendPanels[ResourceOverviewCpuChart] = null; } ResourceOverviewCpuChart.Plot.Clear(); + _resourceOverviewCpuHover?.Clear(); ApplyDarkModeToChart(ResourceOverviewCpuChart); var dataList = cpuData?.OrderBy(d => d.SampleTime).ToList() ?? new List(); @@ -2217,6 +2250,7 @@ private void LoadResourceOverviewCpuChart(IEnumerable cpuData, int scatter.MarkerSize = 5; scatter.Color = TabHelpers.ChartColors[0]; scatter.LegendText = "SQL CPU %"; + _resourceOverviewCpuHover?.Add(scatter, "SQL CPU %"); _legendPanels[ResourceOverviewCpuChart] = ResourceOverviewCpuChart.Plot.ShowLegend(ScottPlot.Edge.Bottom); ResourceOverviewCpuChart.Plot.Legend.FontSize = 12; @@ -2252,6 +2286,7 @@ private void LoadResourceOverviewMemoryChart(IEnumerable memory _legendPanels[ResourceOverviewMemoryChart] = null; } ResourceOverviewMemoryChart.Plot.Clear(); + _resourceOverviewMemoryHover?.Clear(); ApplyDarkModeToChart(ResourceOverviewMemoryChart); var dataList = memoryData?.OrderBy(d => d.CollectionTime).ToList() ?? new List(); @@ -2272,12 +2307,14 @@ private void LoadResourceOverviewMemoryChart(IEnumerable memory bufferScatter.MarkerSize = 5; bufferScatter.Color = TabHelpers.ChartColors[4]; bufferScatter.LegendText = "Buffer Pool"; + _resourceOverviewMemoryHover?.Add(bufferScatter, "Buffer Pool"); var grantsScatter = ResourceOverviewMemoryChart.Plot.Add.Scatter(grantsXs, grantsYs); grantsScatter.LineWidth = 2; grantsScatter.MarkerSize = 5; grantsScatter.Color = TabHelpers.ChartColors[2]; grantsScatter.LegendText = "Memory Grants"; + _resourceOverviewMemoryHover?.Add(grantsScatter, "Memory Grants"); _legendPanels[ResourceOverviewMemoryChart] = ResourceOverviewMemoryChart.Plot.ShowLegend(ScottPlot.Edge.Bottom); ResourceOverviewMemoryChart.Plot.Legend.FontSize = 12; @@ -2313,6 +2350,7 @@ private void LoadResourceOverviewIoChart(IEnumerable ioData, in _legendPanels[ResourceOverviewIoChart] = null; } ResourceOverviewIoChart.Plot.Clear(); + _resourceOverviewIoHover?.Clear(); ApplyDarkModeToChart(ResourceOverviewIoChart); var dataList = ioData?.OrderBy(d => d.CollectionTime).ToList() ?? new List(); @@ -2348,12 +2386,14 @@ private void LoadResourceOverviewIoChart(IEnumerable ioData, in readScatter.MarkerSize = 5; readScatter.Color = TabHelpers.ChartColors[1]; readScatter.LegendText = "Read ms"; + _resourceOverviewIoHover?.Add(readScatter, "Read ms"); var writeScatter = ResourceOverviewIoChart.Plot.Add.Scatter(writeXs, writeYs); writeScatter.LineWidth = 2; writeScatter.MarkerSize = 5; writeScatter.Color = TabHelpers.ChartColors[2]; writeScatter.LegendText = "Write ms"; + _resourceOverviewIoHover?.Add(writeScatter, "Write ms"); _legendPanels[ResourceOverviewIoChart] = ResourceOverviewIoChart.Plot.ShowLegend(ScottPlot.Edge.Bottom); ResourceOverviewIoChart.Plot.Legend.FontSize = 12; @@ -2390,6 +2430,7 @@ private void LoadResourceOverviewWaitChart(IEnumerable waitD _legendPanels[ResourceOverviewWaitChart] = null; } ResourceOverviewWaitChart.Plot.Clear(); + _resourceOverviewWaitHover?.Clear(); ApplyDarkModeToChart(ResourceOverviewWaitChart); var dataList = waitData?.OrderBy(d => d.CollectionTime).ToList() ?? new List(); @@ -2422,7 +2463,9 @@ private void LoadResourceOverviewWaitChart(IEnumerable waitD scatter.LineWidth = 2; scatter.MarkerSize = 5; scatter.Color = colors[colorIndex % colors.Length]; - scatter.LegendText = waitType.Length > 15 ? waitType.Substring(0, 15) + "..." : waitType; + var waitLabel = waitType.Length > 15 ? waitType.Substring(0, 15) + "..." : waitType; + scatter.LegendText = waitLabel; + _resourceOverviewWaitHover?.Add(scatter, waitLabel); colorIndex++; }