@@ -93,6 +93,14 @@ public partial class PlanViewerControl : UserControl
9393 private static readonly SolidColorBrush OrangeBrush = new ( Colors . Orange ) ;
9494 private static readonly SolidColorBrush MinimapExpensiveNodeBgBrush = new ( Color . FromArgb ( 0x60 , 0xE5 , 0x73 , 0x73 ) ) ;
9595
96+ // Link accuracy coloring brushes (Dark theme)
97+ private static readonly SolidColorBrush LinkFluoBlueBrush = new ( Color . FromRgb ( 0x00 , 0xE5 , 0xFF ) ) ;
98+ private static readonly SolidColorBrush LinkLightBlueBrush = new ( Color . FromRgb ( 0x64 , 0xB5 , 0xF6 ) ) ;
99+ private static readonly SolidColorBrush LinkBlueBrush = new ( Color . FromRgb ( 0x42 , 0x8B , 0xCA ) ) ;
100+ private static readonly SolidColorBrush LinkLightOrangeBrush = new ( Color . FromRgb ( 0xFF , 0xB7 , 0x4D ) ) ;
101+ private static readonly SolidColorBrush LinkFluoOrangeBrush = new ( Color . FromRgb ( 0xFF , 0x8C , 0x00 ) ) ;
102+ private static readonly SolidColorBrush LinkFluoRedBrush = new ( Color . FromRgb ( 0xFF , 0x17 , 0x44 ) ) ;
103+
96104
97105 // Track all property section grids for synchronized column resize
98106 private readonly List < ColumnDefinition > _sectionLabelColumns = new ( ) ;
@@ -395,12 +403,13 @@ private void RenderStatement(PlanStatement statement)
395403 PlanCanvas . Height = height ;
396404
397405 // Render edges first (behind nodes)
398- RenderEdges ( statement . RootNode ) ;
406+ var divergenceLimit = Math . Max ( 2.0 , AppSettingsService . Load ( ) . AccuracyRatioDivergenceLimit ) ;
407+ RenderEdges ( statement . RootNode , divergenceLimit ) ;
399408
400409 // Render nodes — pass total warning count to root node for badge
401410 var allWarnings = new List < PlanWarning > ( ) ;
402411 CollectWarnings ( statement . RootNode , allWarnings ) ;
403- RenderNodes ( statement . RootNode , allWarnings . Count ) ;
412+ RenderNodes ( statement . RootNode , divergenceLimit , allWarnings . Count ) ;
404413
405414 // Update banners
406415 ShowMissingIndexes ( statement . MissingIndexes ) ;
@@ -425,18 +434,18 @@ private void RenderStatement(PlanStatement statement)
425434
426435 #region Node Rendering
427436
428- private void RenderNodes ( PlanNode node , int totalWarningCount = - 1 )
437+ private void RenderNodes ( PlanNode node , double divergenceLimit , int totalWarningCount = - 1 )
429438 {
430- var visual = CreateNodeVisual ( node , totalWarningCount ) ;
439+ var visual = CreateNodeVisual ( node , divergenceLimit , totalWarningCount ) ;
431440 Canvas . SetLeft ( visual , node . X ) ;
432441 Canvas . SetTop ( visual , node . Y ) ;
433442 PlanCanvas . Children . Add ( visual ) ;
434443
435444 foreach ( var child in node . Children )
436- RenderNodes ( child ) ;
445+ RenderNodes ( child , divergenceLimit ) ;
437446 }
438447
439- private Border CreateNodeVisual ( PlanNode node , int totalWarningCount = - 1 )
448+ private Border CreateNodeVisual ( PlanNode node , double divergenceLimit , int totalWarningCount = - 1 )
440449 {
441450 var isExpensive = node . IsExpensive ;
442451
@@ -648,10 +657,10 @@ private Border CreateNodeVisual(PlanNode node, int totalWarningCount = -1)
648657 HorizontalAlignment = HorizontalAlignment . Center
649658 } ) ;
650659
651- // Actual rows of Estimated rows (accuracy %) -- red if off by 10x+
660+ // Actual rows of Estimated rows (accuracy %) -- red if off by divergence limit
652661 var estRows = node . EstimateRows ;
653662 var accuracyRatio = estRows > 0 ? node . ActualRows / estRows : ( node . ActualRows > 0 ? double . MaxValue : 1.0 ) ;
654- IBrush rowBrush = ( accuracyRatio < 0.1 || accuracyRatio > 10.0 ) ? OrangeRedBrush : fgBrush ;
663+ IBrush rowBrush = ( accuracyRatio < 1.0 / divergenceLimit || accuracyRatio > divergenceLimit ) ? OrangeRedBrush : fgBrush ;
655664 var accuracy = estRows > 0
656665 ? $ " ({ accuracyRatio * 100 : F0} %)"
657666 : "" ;
@@ -719,18 +728,55 @@ private Border CreateNodeVisual(PlanNode node, int totalWarningCount = -1)
719728
720729 #region Edge Rendering
721730
722- private void RenderEdges ( PlanNode node )
731+ private void RenderEdges ( PlanNode node , double divergenceLimit )
723732 {
724733 foreach ( var child in node . Children )
725734 {
726- var path = CreateElbowConnector ( node , child ) ;
735+ var path = CreateElbowConnector ( node , child , divergenceLimit ) ;
727736 PlanCanvas . Children . Add ( path ) ;
728737
729- RenderEdges ( child ) ;
738+ RenderEdges ( child , divergenceLimit ) ;
739+ }
740+ }
741+
742+ /// <summary>
743+ /// Returns a color brush for a link based on the accuracy ratio of the child node.
744+ /// Only applies to actual plans; estimated plans use the default edge brush.
745+ /// </summary>
746+ private static IBrush GetLinkColorBrush ( PlanNode child , double divergenceLimit )
747+ {
748+ if ( ! child . HasActualStats )
749+ return EdgeBrush ;
750+
751+ divergenceLimit = Math . Max ( 2.0 , divergenceLimit ) ;
752+ var estRows = child . EstimateRows ;
753+ var accuracyRatio = estRows > 0
754+ ? child . ActualRows / estRows
755+ : ( child . ActualRows > 0 ? double . MaxValue : 1.0 ) ;
756+
757+ // Within the neutral band — keep default color
758+ if ( accuracyRatio >= 1.0 / divergenceLimit && accuracyRatio <= divergenceLimit )
759+ return EdgeBrush ;
760+
761+ // Underestimated bands (accuracyRatio > 1 means more actual rows than estimated)
762+ if ( accuracyRatio > divergenceLimit )
763+ {
764+ if ( accuracyRatio >= divergenceLimit * 100 )
765+ return LinkFluoRedBrush ;
766+ if ( accuracyRatio >= divergenceLimit * 10 )
767+ return LinkFluoOrangeBrush ;
768+ return LinkLightOrangeBrush ;
730769 }
770+
771+ // Overestimated bands (accuracyRatio < 1 means fewer actual rows than estimated)
772+ if ( accuracyRatio < 1.0 / ( divergenceLimit * 100 ) )
773+ return LinkFluoBlueBrush ;
774+ if ( accuracyRatio < 1.0 / ( divergenceLimit * 10 ) )
775+ return LinkLightBlueBrush ;
776+ return LinkBlueBrush ;
731777 }
732778
733- private AvaloniaPath CreateElbowConnector ( PlanNode parent , PlanNode child )
779+ private AvaloniaPath CreateElbowConnector ( PlanNode parent , PlanNode child , double divergenceLimit )
734780 {
735781 var parentRight = parent . X + PlanLayoutEngine . NodeWidth ;
736782 var parentCenterY = parent . Y + PlanLayoutEngine . GetNodeHeight ( parent ) / 2 ;
@@ -754,10 +800,12 @@ private AvaloniaPath CreateElbowConnector(PlanNode parent, PlanNode child)
754800 figure . Segments . Add ( new LineSegment { Point = new Point ( childLeft , childCenterY ) } ) ;
755801 geometry . Figures ! . Add ( figure ) ;
756802
803+ var linkBrush = GetLinkColorBrush ( child , divergenceLimit ) ;
804+
757805 var path = new AvaloniaPath
758806 {
759807 Data = geometry ,
760- Stroke = EdgeBrush ,
808+ Stroke = linkBrush ,
761809 StrokeThickness = thickness ,
762810 StrokeJoin = PenLineJoin . Round
763811 } ;
@@ -3565,7 +3613,8 @@ private void RenderMinimap()
35653613 RenderMinimapBranches ( _currentStatement . RootNode , scale ) ;
35663614
35673615 // Render edges
3568- RenderMinimapEdges ( _currentStatement . RootNode , scale ) ;
3616+ var minimapDivergenceLimit = Math . Max ( 2.0 , AppSettingsService . Load ( ) . AccuracyRatioDivergenceLimit ) ;
3617+ RenderMinimapEdges ( _currentStatement . RootNode , scale , minimapDivergenceLimit ) ;
35693618
35703619 // Render nodes
35713620 RenderMinimapNodes ( _currentStatement . RootNode , scale ) ;
@@ -3629,7 +3678,7 @@ private static void CollectSubtreeBounds(PlanNode node, ref double minX, ref dou
36293678 CollectSubtreeBounds ( child , ref minX , ref minY , ref maxX , ref maxY ) ;
36303679 }
36313680
3632- private void RenderMinimapEdges ( PlanNode node , double scale )
3681+ private void RenderMinimapEdges ( PlanNode node , double scale , double divergenceLimit )
36333682 {
36343683 foreach ( var child in node . Children )
36353684 {
@@ -3651,16 +3700,18 @@ private void RenderMinimapEdges(PlanNode node, double scale)
36513700 figure . Segments . Add ( new LineSegment { Point = new Point ( childLeft , childCenterY ) } ) ;
36523701 geometry . Figures ! . Add ( figure ) ;
36533702
3703+ var linkBrush = GetLinkColorBrush ( child , divergenceLimit ) ;
3704+
36543705 var path = new AvaloniaPath
36553706 {
36563707 Data = geometry ,
3657- Stroke = EdgeBrush ,
3708+ Stroke = linkBrush ,
36583709 StrokeThickness = thickness ,
36593710 StrokeJoin = PenLineJoin . Round
36603711 } ;
36613712 MinimapCanvas . Children . Add ( path ) ;
36623713
3663- RenderMinimapEdges ( child , scale ) ;
3714+ RenderMinimapEdges ( child , scale , divergenceLimit ) ;
36643715 }
36653716 }
36663717
0 commit comments