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 0000000..f85dd59
Binary files /dev/null and b/src/PlanViewer.Core/Resources/PlanIcons/parallelism_distribute_streams.png differ
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 0000000..14078f7
Binary files /dev/null and b/src/PlanViewer.Core/Resources/PlanIcons/parallelism_gather_streams.png differ
diff --git a/src/PlanViewer.Core/Resources/PlanIcons/parallelism_repartition_streams.png b/src/PlanViewer.Core/Resources/PlanIcons/parallelism_repartition_streams.png
new file mode 100644
index 0000000..bda974d
Binary files /dev/null and b/src/PlanViewer.Core/Resources/PlanIcons/parallelism_repartition_streams.png differ
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))