From 990e4243dd4a9138c0055e7be067901b9b93f9b9 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Sun, 26 Apr 2026 10:34:00 -0400 Subject: [PATCH] #283: distinct icons for Parallelism subtypes; drop R/D/G overlay Replaces the temporary letter-overlay approach with three real icons contributed by @rferraton (MIT-licensed for the repo): repartition_streams, distribute_streams, and gather_streams (the latter is a horizontal mirror of distribute, which matches SSMS's convention). PlanIconMapper.GetIconName now also takes LogicalOp so the three Parallelism subtypes route to their own icon. The overlay rendering and GetParallelismGlyph helper in PlanViewerControl are removed. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Controls/PlanViewerControl.axaml.cs | 54 +----------------- .../parallelism_distribute_streams.png | Bin 0 -> 8276 bytes .../PlanIcons/parallelism_gather_streams.png | Bin 0 -> 8311 bytes .../parallelism_repartition_streams.png | Bin 0 -> 10650 bytes .../Services/PlanIconMapper.cs | 17 +++++- .../Services/ShowPlanParser.cs | 8 +-- 6 files changed, 21 insertions(+), 58 deletions(-) create mode 100644 src/PlanViewer.Core/Resources/PlanIcons/parallelism_distribute_streams.png create mode 100644 src/PlanViewer.Core/Resources/PlanIcons/parallelism_gather_streams.png create mode 100644 src/PlanViewer.Core/Resources/PlanIcons/parallelism_repartition_streams.png diff --git a/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs b/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs index 2db8609..75fb26f 100644 --- a/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs +++ b/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs @@ -495,45 +495,13 @@ private Border CreateNodeVisual(PlanNode node, int totalWarningCount = -1) var iconBitmap = IconHelper.LoadIcon(node.IconName); if (iconBitmap != null) { - var iconImage = new Image + iconRow.Children.Add(new Image { Source = iconBitmap, Width = 32, Height = 32, Margin = new Thickness(0, 0, 0, 2) - }; - - // Distinguish Parallelism subtypes (Repartition / Distribute / Gather Streams) - // by overlaying a small letter on the shared parallelism.png icon. - var parallelismGlyph = GetParallelismGlyph(node); - if (parallelismGlyph != null) - { - var iconGrid = new Grid { Margin = new Thickness(0, 0, 0, 2) }; - iconGrid.Children.Add(iconImage); - iconImage.Margin = default; - iconGrid.Children.Add(new Border - { - Width = 14, Height = 14, - HorizontalAlignment = HorizontalAlignment.Right, - VerticalAlignment = VerticalAlignment.Bottom, - Background = new SolidColorBrush(Color.FromRgb(0x33, 0x33, 0x33)), - CornerRadius = new CornerRadius(7), - Child = new TextBlock - { - Text = parallelismGlyph, - FontSize = 9, - FontWeight = FontWeight.Bold, - Foreground = Brushes.White, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - } - }); - iconRow.Children.Add(iconGrid); - } - else - { - iconRow.Children.Add(iconImage); - } + }); } // Warning indicator badge (orange triangle with !) @@ -3094,24 +3062,6 @@ private void UpdateInsightsHeader() InsightsHeader.Text = " Plan Insights"; } - /// - /// For Parallelism nodes, returns a single-letter glyph distinguishing - /// the three subtypes: R(epartition) / D(istribute) / G(ather) Streams. - /// Returns null for non-parallelism operators. - /// - private static string? GetParallelismGlyph(PlanNode node) - { - if (!string.Equals(node.PhysicalOp, "Parallelism", StringComparison.Ordinal)) - return null; - return node.LogicalOp switch - { - "Repartition Streams" => "R", - "Distribute Streams" => "D", - "Gather Streams" => "G", - _ => null - }; - } - private static string GetWaitCategory(string waitType) { if (waitType.StartsWith("SOS_SCHEDULER_YIELD") || diff --git a/src/PlanViewer.Core/Resources/PlanIcons/parallelism_distribute_streams.png b/src/PlanViewer.Core/Resources/PlanIcons/parallelism_distribute_streams.png new file mode 100644 index 0000000000000000000000000000000000000000..f85dd59f1f5131006ba3efdf14fc1f347ef3a701 GIT binary patch literal 8276 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 F{U4F4DXstj literal 0 HcmV?d00001 diff --git a/src/PlanViewer.Core/Resources/PlanIcons/parallelism_gather_streams.png b/src/PlanViewer.Core/Resources/PlanIcons/parallelism_gather_streams.png new file mode 100644 index 0000000000000000000000000000000000000000..14078f719732e95ce831c558c517a9e4c1ecf321 GIT binary patch literal 8311 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 AF#rGn literal 0 HcmV?d00001 diff --git a/src/PlanViewer.Core/Services/PlanIconMapper.cs b/src/PlanViewer.Core/Services/PlanIconMapper.cs index d329d7d..79f6361 100644 --- a/src/PlanViewer.Core/Services/PlanIconMapper.cs +++ b/src/PlanViewer.Core/Services/PlanIconMapper.cs @@ -179,10 +179,23 @@ public static class PlanIconMapper /// /// Returns the icon file name (without extension) for a given physical operator. /// When is "ColumnStore" on a *Index Scan, - /// routes to the columnstore variant. + /// routes to the columnstore variant. When is + /// "Parallelism" and identifies a stream subtype, + /// routes to the matching subtype icon. /// - public static string GetIconName(string physicalOp, string? storageType = null) + public static string GetIconName(string physicalOp, string? storageType = null, string? logicalOp = null) { + // Parallelism subtypes: PhysicalOp="Parallelism" + LogicalOp identifies which. + if (string.Equals(physicalOp, "Parallelism", StringComparison.Ordinal) && logicalOp != null) + { + switch (logicalOp) + { + case "Repartition Streams": return "parallelism_repartition_streams"; + case "Distribute Streams": return "parallelism_distribute_streams"; + case "Gather Streams": return "parallelism_gather_streams"; + } + } + // Columnstore scans surface as PhysicalOp="Clustered Index Scan" / "Index Scan" // with Storage="ColumnStore" on the Object element. Route to the columnstore icon. if (string.Equals(storageType, "ColumnStore", StringComparison.OrdinalIgnoreCase)) diff --git a/src/PlanViewer.Core/Services/ShowPlanParser.cs b/src/PlanViewer.Core/Services/ShowPlanParser.cs index 35194c5..a0ebebd 100644 --- a/src/PlanViewer.Core/Services/ShowPlanParser.cs +++ b/src/PlanViewer.Core/Services/ShowPlanParser.cs @@ -1369,10 +1369,10 @@ private static PlanNode ParseRelOp(XElement relOpEl) } } - // Map to icon — done here so columnstore scans (which surface as - // Clustered/Index Scan with Storage="ColumnStore") can be routed to - // the columnstore icon. - node.IconName = PlanIconMapper.GetIconName(node.PhysicalOp, node.StorageType); + // Map to icon — done here so columnstore scans (Clustered/Index Scan + // with Storage="ColumnStore") and Parallelism subtypes (which depend on + // LogicalOp) can be routed to their specific icons. + node.IconName = PlanIconMapper.GetIconName(node.PhysicalOp, node.StorageType, node.LogicalOp); // Recurse into child RelOps foreach (var childRelOp in FindChildRelOps(relOpEl))