diff --git a/src/PlanViewer.App/Controls/QueryStoreHistoryControl.axaml.cs b/src/PlanViewer.App/Controls/QueryStoreHistoryControl.axaml.cs index 45637c1..a3910c3 100644 --- a/src/PlanViewer.App/Controls/QueryStoreHistoryControl.axaml.cs +++ b/src/PlanViewer.App/Controls/QueryStoreHistoryControl.axaml.cs @@ -59,16 +59,19 @@ public partial class QueryStoreHistoryControl : UserControl private static readonly SolidColorBrush ActiveButtonFg = new(Avalonia.Media.Color.FromRgb(0x11, 0x12, 0x17)); private static readonly SolidColorBrush InactiveButtonFg = new(Avalonia.Media.Color.FromRgb(0x9D, 0xA5, 0xB4)); + // Validated colorblind-safe categorical ramp (dark surface), in fixed CVD + // order — shared with the Multi QS Overview database palette so the two + // Query Store views read as one system. private static readonly ScottPlot.Color[] PlanColors = { - ScottPlot.Color.FromHex("#4FC3F7"), - ScottPlot.Color.FromHex("#FF7043"), - ScottPlot.Color.FromHex("#66BB6A"), - ScottPlot.Color.FromHex("#AB47BC"), - ScottPlot.Color.FromHex("#FFA726"), - ScottPlot.Color.FromHex("#26C6DA"), - ScottPlot.Color.FromHex("#F06292"), - ScottPlot.Color.FromHex("#A1887F"), + ScottPlot.Color.FromHex("#3987E5"), + ScottPlot.Color.FromHex("#199E70"), + ScottPlot.Color.FromHex("#C98500"), + ScottPlot.Color.FromHex("#008300"), + ScottPlot.Color.FromHex("#9085E9"), + ScottPlot.Color.FromHex("#E66767"), + ScottPlot.Color.FromHex("#D55181"), + ScottPlot.Color.FromHex("#D95926"), }; // Map grid orderBy tags to history metric tags diff --git a/src/PlanViewer.App/Controls/QueryStoreOverviewControl.Donut.cs b/src/PlanViewer.App/Controls/QueryStoreOverviewControl.Donut.cs index 69144e2..d69e683 100644 --- a/src/PlanViewer.App/Controls/QueryStoreOverviewControl.Donut.cs +++ b/src/PlanViewer.App/Controls/QueryStoreOverviewControl.Donut.cs @@ -81,12 +81,15 @@ private void DrawDonut() var labelR = (radius + innerRadius) / 2; var lx = cx + labelR * Math.Cos(midAngle); var ly = cy + labelR * Math.Sin(midAngle); + // Pick label ink by arc luminance (same rule as the bar cards) so the + // percentage stays readable on light fills instead of white-on-light. + var labelLuminance = 0.299 * color.R + 0.587 * color.G + 0.114 * color.B; var pctLabel = new TextBlock { Text = $"{pct:F0}%", FontSize = 10, FontWeight = FontWeight.SemiBold, - Foreground = Brushes.White, + Foreground = new SolidColorBrush(labelLuminance > 128 ? Colors.Black : Colors.White), }; pctLabel.Measure(Size.Infinity); Canvas.SetLeft(pctLabel, lx - pctLabel.DesiredSize.Width / 2); diff --git a/src/PlanViewer.App/Controls/QueryStoreOverviewControl.WaitStatsChart.cs b/src/PlanViewer.App/Controls/QueryStoreOverviewControl.WaitStatsChart.cs index 0583adc..917551d 100644 --- a/src/PlanViewer.App/Controls/QueryStoreOverviewControl.WaitStatsChart.cs +++ b/src/PlanViewer.App/Controls/QueryStoreOverviewControl.WaitStatsChart.cs @@ -89,6 +89,7 @@ private void DrawWaitStatsChart() var stepX = w / n; var barGap = Math.Min(2.0, Math.Max(0.5, stepX * 0.1)); + const double segGap = 1.0; // surface gap between stacked segments so bands stay distinct for (int i = 0; i < n; i++) { @@ -121,7 +122,7 @@ private void DrawWaitStatsChart() var rect = new Rectangle { Width = Math.Max(1, stepX - barGap), - Height = Math.Max(0.5, segH), + Height = Math.Max(0.5, segH - segGap), Fill = new SolidColorBrush(color), }; Canvas.SetLeft(rect, x); @@ -139,7 +140,7 @@ private void DrawWaitStatsChart() var rect = new Rectangle { Width = Math.Max(1, stepX - barGap), - Height = Math.Max(0.5, segH), + Height = Math.Max(0.5, segH - segGap), Fill = new SolidColorBrush(OthersColor), }; Canvas.SetLeft(rect, x); diff --git a/src/PlanViewer.App/Services/AppSettingsService.cs b/src/PlanViewer.App/Services/AppSettingsService.cs index 6c730cb..0c558aa 100644 --- a/src/PlanViewer.App/Services/AppSettingsService.cs +++ b/src/PlanViewer.App/Services/AppSettingsService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; @@ -77,6 +78,12 @@ public static AppSettings Load() settings.MultiQsTopDbCount = Math.Clamp(settings.MultiQsTopDbCount, 2, 20); settings.QueryHistoryMaxPlans = Math.Clamp(settings.QueryHistoryMaxPlans, 1, 100); + // Migrate installs still on the old default palette to the validated + // colorblind-safe ramp. Only touches settings that exactly match the + // legacy defaults, so any user-customized colors are left untouched. + if (settings.MultiQsTopDbColors.SequenceEqual(LegacyTopDbColors, StringComparer.OrdinalIgnoreCase)) + settings.MultiQsTopDbColors = new List(DefaultTopDbColors); + _cached = settings; return settings; } @@ -174,9 +181,23 @@ public static void RemoveRecentPlan(AppSettings settings, string filePath) } /// - /// Default color palette for Multi QS Overview top databases. + /// Default color palette for Multi QS Overview top databases. Eight hues from + /// the validated colorblind-safe categorical ramp (dark surface), in fixed CVD + /// order. Databases beyond the eighth fold into the neutral "Others" fill, so + /// no invented ninth hue is ever needed. /// internal static readonly List DefaultTopDbColors = new() + { + "#3987E5", "#199E70", "#C98500", "#008300", + "#9085E9", "#E66767", "#D55181", "#D95926", + }; + + /// + /// The pre-dataviz default palette. Retained only so can + /// detect an installation still on the old defaults and migrate it to + /// without clobbering a user's custom colors. + /// + private static readonly List LegacyTopDbColors = new() { "#2EAEF1", "#F2994A", "#27AE60", "#9B51E0", "#EB5757", "#F2C94C", "#56CCF2", "#BB6BD9", "#E91E63", "#00BCD4",