From 11d2b6ee67da9ac6ceedc1216b41c1d6ea766001 Mon Sep 17 00:00:00 2001 From: rferraton <16419423+rferraton@users.noreply.github.com> Date: Sun, 26 Apr 2026 18:36:26 +0200 Subject: [PATCH 1/4] =?UTF-8?q?AppSettings=20(AppSettingsService.cs)=20?= =?UTF-8?q?=E2=80=A2=20Added=20AccuracyRatioDivergenceLimit=20property=20(?= =?UTF-8?q?default:=2010),=20persisted=20as=20"accuracy=5Fratio=5Fdivergen?= =?UTF-8?q?ce=5Flimit"=20in=20the=20JSON=20settings=20file.=20PlanViewerCo?= =?UTF-8?q?ntrol=20(PlanViewerControl.axaml.cs)=20=E2=80=A2=206=20new=20li?= =?UTF-8?q?nk=20color=20brushes=20for=20dark=20theme:=20=E2=80=A2=20Undere?= =?UTF-8?q?stimated=20(more=20actual=20than=20estimated):=20Blue=20?= =?UTF-8?q?=E2=86=92=20Light=20Blue=20=E2=86=92=20Fluo=20Blue=20=E2=80=A2?= =?UTF-8?q?=20Overestimated=20(fewer=20actual=20than=20estimated):=20Light?= =?UTF-8?q?=20Orange=20=E2=86=92=20Fluo=20Orange=20=E2=86=92=20Fluo=20Red?= =?UTF-8?q?=20=E2=80=A2=20GetLinkColorBrush(PlanNode,=20double)=20method?= =?UTF-8?q?=20=E2=80=94=20maps=20accuracy=20ratio=20to=20a=20color=20band:?= =?UTF-8?q?=20=E2=80=A2=20[0,=201/(limit=C3=97100))=20=E2=86=92=20Fluo=20B?= =?UTF-8?q?lue=20=E2=80=A2=20[1/(limit=C3=97100),=201/(limit=C3=9710))=20?= =?UTF-8?q?=E2=86=92=20Light=20Blue=20=E2=80=A2=20[1/(limit=C3=9710),=201/?= =?UTF-8?q?limit)=20=E2=86=92=20Blue=20=E2=80=A2=20[1/limit,=20limit]=20?= =?UTF-8?q?=E2=86=92=20Default=20edge=20color=20(neutral)=20=E2=80=A2=20(l?= =?UTF-8?q?imit,=20limit=C3=9710)=20=E2=86=92=20Light=20Orange=20=E2=80=A2?= =?UTF-8?q?=20[limit=C3=9710,=20limit=C3=97100)=20=E2=86=92=20Fluo=20Orang?= =?UTF-8?q?e=20=E2=80=A2=20[limit=C3=97100,=20+=E2=88=9E)=20=E2=86=92=20Fl?= =?UTF-8?q?uo=20Red=20=E2=80=A2=20CreateElbowConnector(PlanNode,=20PlanNod?= =?UTF-8?q?e)=20now=20uses=20GetLinkColorBrush(PlanNode,=20double)=20inste?= =?UTF-8?q?ad=20of=20the=20static=20EdgeBrush,=20but=20only=20colors=20lin?= =?UTF-8?q?ks=20differently=20for=20actual=20plans=20(estimated=20plans=20?= =?UTF-8?q?keep=20the=20default).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controls/PlanViewerControl.axaml.cs | 49 ++++++++++++++++++- .../Services/AppSettingsService.cs | 7 +++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs b/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs index 75fb26f..160c735 100644 --- a/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs +++ b/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs @@ -93,6 +93,14 @@ public partial class PlanViewerControl : UserControl private static readonly SolidColorBrush OrangeBrush = new(Colors.Orange); private static readonly SolidColorBrush MinimapExpensiveNodeBgBrush = new(Color.FromArgb(0x60, 0xE5, 0x73, 0x73)); + // Link accuracy coloring brushes (Dark theme) + private static readonly SolidColorBrush LinkFluoBlueBrush = new(Color.FromRgb(0x00, 0xE5, 0xFF)); + private static readonly SolidColorBrush LinkLightBlueBrush = new(Color.FromRgb(0x64, 0xB5, 0xF6)); + private static readonly SolidColorBrush LinkBlueBrush = new(Color.FromRgb(0x42, 0x8B, 0xCA)); + private static readonly SolidColorBrush LinkLightOrangeBrush = new(Color.FromRgb(0xFF, 0xB7, 0x4D)); + private static readonly SolidColorBrush LinkFluoOrangeBrush = new(Color.FromRgb(0xFF, 0x8C, 0x00)); + private static readonly SolidColorBrush LinkFluoRedBrush = new(Color.FromRgb(0xFF, 0x17, 0x44)); + // Track all property section grids for synchronized column resize private readonly List _sectionLabelColumns = new(); @@ -730,6 +738,42 @@ private void RenderEdges(PlanNode node) } } + /// + /// Returns a color brush for a link based on the accuracy ratio of the child node. + /// Only applies to actual plans; estimated plans use the default edge brush. + /// + private static IBrush GetLinkColorBrush(PlanNode child, double divergenceLimit) + { + if (!child.HasActualStats) + return EdgeBrush; + + var estRows = child.EstimateRows; + var accuracyRatio = estRows > 0 + ? child.ActualRows / estRows + : (child.ActualRows > 0 ? double.MaxValue : 1.0); + + // Within the neutral band — keep default color + if (accuracyRatio >= 1.0 / divergenceLimit && accuracyRatio <= divergenceLimit) + return EdgeBrush; + + // Underestimated bands (accuracyRatio > 1 means more actual rows than estimated) + if (accuracyRatio > divergenceLimit) + { + if (accuracyRatio >= divergenceLimit * 100) + return LinkFluoRedBrush; + if (accuracyRatio >= divergenceLimit * 10) + return LinkFluoOrangeBrush; + return LinkLightOrangeBrush; + } + + // Overestimated bands (accuracyRatio < 1 means fewer actual rows than estimated) + if (accuracyRatio < 1.0 / (divergenceLimit * 100)) + return LinkFluoBlueBrush; + if (accuracyRatio < 1.0 / (divergenceLimit * 10)) + return LinkLightBlueBrush; + return LinkBlueBrush; + } + private AvaloniaPath CreateElbowConnector(PlanNode parent, PlanNode child) { var parentRight = parent.X + PlanLayoutEngine.NodeWidth; @@ -754,10 +798,13 @@ private AvaloniaPath CreateElbowConnector(PlanNode parent, PlanNode child) figure.Segments.Add(new LineSegment { Point = new Point(childLeft, childCenterY) }); geometry.Figures!.Add(figure); + var settings = AppSettingsService.Load(); + var linkBrush = GetLinkColorBrush(child, settings.AccuracyRatioDivergenceLimit); + var path = new AvaloniaPath { Data = geometry, - Stroke = EdgeBrush, + Stroke = linkBrush, StrokeThickness = thickness, StrokeJoin = PenLineJoin.Round }; diff --git a/src/PlanViewer.App/Services/AppSettingsService.cs b/src/PlanViewer.App/Services/AppSettingsService.cs index afff7fa..3dc456f 100644 --- a/src/PlanViewer.App/Services/AppSettingsService.cs +++ b/src/PlanViewer.App/Services/AppSettingsService.cs @@ -119,4 +119,11 @@ internal sealed class AppSettings /// [JsonPropertyName("query_store_slicer_days")] public int QueryStoreSlicerDays { get; set; } = 30; + + /// + /// Divergence limit for accuracy ratio coloring on plan links. Default 10. + /// Links with accuracy ratio between 1/limit and limit keep the default edge color. + /// + [JsonPropertyName("accuracy_ratio_divergence_limit")] + public double AccuracyRatioDivergenceLimit { get; set; } = 10; } From b0e8da42079224bfa3fc264bec16c191d9b069eb Mon Sep 17 00:00:00 2001 From: rferraton <16419423+rferraton@users.noreply.github.com> Date: Sun, 26 Apr 2026 18:45:51 +0200 Subject: [PATCH 2/4] minimap colored links --- src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs b/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs index 160c735..4aa65ad 100644 --- a/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs +++ b/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs @@ -3698,10 +3698,13 @@ private void RenderMinimapEdges(PlanNode node, double scale) figure.Segments.Add(new LineSegment { Point = new Point(childLeft, childCenterY) }); geometry.Figures!.Add(figure); + var settings = AppSettingsService.Load(); + var linkBrush = GetLinkColorBrush(child, settings.AccuracyRatioDivergenceLimit); + var path = new AvaloniaPath { Data = geometry, - Stroke = EdgeBrush, + Stroke = linkBrush, StrokeThickness = thickness, StrokeJoin = PenLineJoin.Round }; From fa7da536b0d8d1cf1b36c154f933ca605b231252 Mon Sep 17 00:00:00 2001 From: rferraton <16419423+rferraton@users.noreply.github.com> Date: Sun, 26 Apr 2026 18:51:35 +0200 Subject: [PATCH 3/4] Internal Code review and some fixes --- .../Controls/PlanViewerControl.axaml.cs | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs b/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs index 4aa65ad..f4a3e4b 100644 --- a/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs +++ b/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs @@ -403,7 +403,8 @@ private void RenderStatement(PlanStatement statement) PlanCanvas.Height = height; // Render edges first (behind nodes) - RenderEdges(statement.RootNode); + var divergenceLimit = Math.Max(2.0, AppSettingsService.Load().AccuracyRatioDivergenceLimit); + RenderEdges(statement.RootNode, divergenceLimit); // Render nodes — pass total warning count to root node for badge var allWarnings = new List(); @@ -656,10 +657,11 @@ private Border CreateNodeVisual(PlanNode node, int totalWarningCount = -1) HorizontalAlignment = HorizontalAlignment.Center }); - // Actual rows of Estimated rows (accuracy %) -- red if off by 10x+ + // Actual rows of Estimated rows (accuracy %) -- red if off by divergence limit var estRows = node.EstimateRows; var accuracyRatio = estRows > 0 ? node.ActualRows / estRows : (node.ActualRows > 0 ? double.MaxValue : 1.0); - IBrush rowBrush = (accuracyRatio < 0.1 || accuracyRatio > 10.0) ? OrangeRedBrush : fgBrush; + var nodeDivLimit = Math.Max(2.0, AppSettingsService.Load().AccuracyRatioDivergenceLimit); + IBrush rowBrush = (accuracyRatio < 1.0 / nodeDivLimit || accuracyRatio > nodeDivLimit) ? OrangeRedBrush : fgBrush; var accuracy = estRows > 0 ? $" ({accuracyRatio * 100:F0}%)" : ""; @@ -727,14 +729,14 @@ private Border CreateNodeVisual(PlanNode node, int totalWarningCount = -1) #region Edge Rendering - private void RenderEdges(PlanNode node) + private void RenderEdges(PlanNode node, double divergenceLimit) { foreach (var child in node.Children) { - var path = CreateElbowConnector(node, child); + var path = CreateElbowConnector(node, child, divergenceLimit); PlanCanvas.Children.Add(path); - RenderEdges(child); + RenderEdges(child, divergenceLimit); } } @@ -747,6 +749,7 @@ private static IBrush GetLinkColorBrush(PlanNode child, double divergenceLimit) if (!child.HasActualStats) return EdgeBrush; + divergenceLimit = Math.Max(2.0, divergenceLimit); var estRows = child.EstimateRows; var accuracyRatio = estRows > 0 ? child.ActualRows / estRows @@ -774,7 +777,7 @@ private static IBrush GetLinkColorBrush(PlanNode child, double divergenceLimit) return LinkBlueBrush; } - private AvaloniaPath CreateElbowConnector(PlanNode parent, PlanNode child) + private AvaloniaPath CreateElbowConnector(PlanNode parent, PlanNode child, double divergenceLimit) { var parentRight = parent.X + PlanLayoutEngine.NodeWidth; var parentCenterY = parent.Y + PlanLayoutEngine.GetNodeHeight(parent) / 2; @@ -798,8 +801,7 @@ private AvaloniaPath CreateElbowConnector(PlanNode parent, PlanNode child) figure.Segments.Add(new LineSegment { Point = new Point(childLeft, childCenterY) }); geometry.Figures!.Add(figure); - var settings = AppSettingsService.Load(); - var linkBrush = GetLinkColorBrush(child, settings.AccuracyRatioDivergenceLimit); + var linkBrush = GetLinkColorBrush(child, divergenceLimit); var path = new AvaloniaPath { @@ -3612,7 +3614,8 @@ private void RenderMinimap() RenderMinimapBranches(_currentStatement.RootNode, scale); // Render edges - RenderMinimapEdges(_currentStatement.RootNode, scale); + var minimapDivergenceLimit = Math.Max(2.0, AppSettingsService.Load().AccuracyRatioDivergenceLimit); + RenderMinimapEdges(_currentStatement.RootNode, scale, minimapDivergenceLimit); // Render nodes RenderMinimapNodes(_currentStatement.RootNode, scale); @@ -3676,7 +3679,7 @@ private static void CollectSubtreeBounds(PlanNode node, ref double minX, ref dou CollectSubtreeBounds(child, ref minX, ref minY, ref maxX, ref maxY); } - private void RenderMinimapEdges(PlanNode node, double scale) + private void RenderMinimapEdges(PlanNode node, double scale, double divergenceLimit) { foreach (var child in node.Children) { @@ -3698,8 +3701,7 @@ private void RenderMinimapEdges(PlanNode node, double scale) figure.Segments.Add(new LineSegment { Point = new Point(childLeft, childCenterY) }); geometry.Figures!.Add(figure); - var settings = AppSettingsService.Load(); - var linkBrush = GetLinkColorBrush(child, settings.AccuracyRatioDivergenceLimit); + var linkBrush = GetLinkColorBrush(child, divergenceLimit); var path = new AvaloniaPath { @@ -3710,7 +3712,7 @@ private void RenderMinimapEdges(PlanNode node, double scale) }; MinimapCanvas.Children.Add(path); - RenderMinimapEdges(child, scale); + RenderMinimapEdges(child, scale, divergenceLimit); } } From c457200eb73237b4f8e896c01ad2e5d7ed745730 Mon Sep 17 00:00:00 2001 From: rferraton <16419423+rferraton@users.noreply.github.com> Date: Sun, 26 Apr 2026 22:06:41 +0200 Subject: [PATCH 4/4] AppSettingsService.Load().AccuracyRatioDivergenceLimit is no more done per node but only once --- .../Controls/PlanViewerControl.axaml.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs b/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs index f4a3e4b..f8d024a 100644 --- a/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs +++ b/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs @@ -409,7 +409,7 @@ private void RenderStatement(PlanStatement statement) // Render nodes — pass total warning count to root node for badge var allWarnings = new List(); CollectWarnings(statement.RootNode, allWarnings); - RenderNodes(statement.RootNode, allWarnings.Count); + RenderNodes(statement.RootNode, divergenceLimit, allWarnings.Count); // Update banners ShowMissingIndexes(statement.MissingIndexes); @@ -434,18 +434,18 @@ private void RenderStatement(PlanStatement statement) #region Node Rendering - private void RenderNodes(PlanNode node, int totalWarningCount = -1) + private void RenderNodes(PlanNode node, double divergenceLimit, int totalWarningCount = -1) { - var visual = CreateNodeVisual(node, totalWarningCount); + var visual = CreateNodeVisual(node, divergenceLimit, totalWarningCount); Canvas.SetLeft(visual, node.X); Canvas.SetTop(visual, node.Y); PlanCanvas.Children.Add(visual); foreach (var child in node.Children) - RenderNodes(child); + RenderNodes(child, divergenceLimit); } - private Border CreateNodeVisual(PlanNode node, int totalWarningCount = -1) + private Border CreateNodeVisual(PlanNode node, double divergenceLimit, int totalWarningCount = -1) { var isExpensive = node.IsExpensive; @@ -660,8 +660,7 @@ private Border CreateNodeVisual(PlanNode node, int totalWarningCount = -1) // Actual rows of Estimated rows (accuracy %) -- red if off by divergence limit var estRows = node.EstimateRows; var accuracyRatio = estRows > 0 ? node.ActualRows / estRows : (node.ActualRows > 0 ? double.MaxValue : 1.0); - var nodeDivLimit = Math.Max(2.0, AppSettingsService.Load().AccuracyRatioDivergenceLimit); - IBrush rowBrush = (accuracyRatio < 1.0 / nodeDivLimit || accuracyRatio > nodeDivLimit) ? OrangeRedBrush : fgBrush; + IBrush rowBrush = (accuracyRatio < 1.0 / divergenceLimit || accuracyRatio > divergenceLimit) ? OrangeRedBrush : fgBrush; var accuracy = estRows > 0 ? $" ({accuracyRatio * 100:F0}%)" : "";