Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/PlanViewer.App/App.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://PlanViewer.App/Themes/DarkTheme.axaml"/>
<ResourceInclude Source="avares://PlanViewer.App/Themes/BarChartConfig.axaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Expand Down
28 changes: 28 additions & 0 deletions src/PlanViewer.App/Controls/BarChartCell.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="PlanViewer.App.Controls.BarChartCell">
<Grid RowDefinitions="Auto,4" Margin="2,1">
<!-- Numeric value text -->
<TextBlock x:Name="ValueText"
Grid.Row="0"
FontSize="11"
VerticalAlignment="Center"
Foreground="{DynamicResource ForegroundBrush}"/>

<!-- Bar row: background track + coloured fill -->
<Grid Grid.Row="1" ColumnDefinitions="*">
<Border x:Name="BarTrack"
Height="3"
CornerRadius="1"
Background="{DynamicResource BackgroundLightBrush}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"/>
<Border x:Name="BarFill"
Height="3"
CornerRadius="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
MinWidth="0"/>
</Grid>
</Grid>
</UserControl>
140 changes: 140 additions & 0 deletions src/PlanViewer.App/Controls/BarChartCell.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
using Avalonia;
using System;
using Avalonia.Controls;
using Avalonia.Media;

namespace PlanViewer.App.Controls;

/// <summary>
/// A DataGrid cell control that shows a text value above a proportional bar.
/// The bar colour is resolved from application resources using the priority:
/// BarChart.&lt;ColumnId&gt; → BarChart.Sorted (when isSorted) → BarChart.Default
/// </summary>
public partial class BarChartCell : UserControl
{
// ── Avalonia properties ────────────────────────────────────────────────

public static readonly StyledProperty<string> DisplayTextProperty =
AvaloniaProperty.Register<BarChartCell, string>(nameof(DisplayText), string.Empty);

/// <summary>Value in [0..1] that controls the bar width.</summary>
public static readonly StyledProperty<double> RatioProperty =
AvaloniaProperty.Register<BarChartCell, double>(nameof(Ratio), 0.0);

/// <summary>Column identifier used to look up per-column brush overrides.</summary>
public static readonly StyledProperty<string> ColumnIdProperty =
AvaloniaProperty.Register<BarChartCell, string>(nameof(ColumnId), string.Empty);

/// <summary>When true the "Sorted" brush is preferred over "Default".</summary>
public static readonly StyledProperty<bool> IsSortedColumnProperty =
AvaloniaProperty.Register<BarChartCell, bool>(nameof(IsSortedColumn), false);

// ── CLR accessors ──────────────────────────────────────────────────────

public string DisplayText
{
get => GetValue(DisplayTextProperty);
set => SetValue(DisplayTextProperty, value);
}

public double Ratio
{
get => GetValue(RatioProperty);
set => SetValue(RatioProperty, value);
}

public string ColumnId
{
get => GetValue(ColumnIdProperty);
set => SetValue(ColumnIdProperty, value);
}

public bool IsSortedColumn
{
get => GetValue(IsSortedColumnProperty);
set => SetValue(IsSortedColumnProperty, value);
}

// ── Constructor ────────────────────────────────────────────────────────

public BarChartCell()
{
InitializeComponent();
}

// ── Property-change overrides ──────────────────────────────────────────

protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

if (change.Property == DisplayTextProperty)
UpdateText();
else if (change.Property == RatioProperty || change.Property == BoundsProperty)
UpdateBar();
else if (change.Property == ColumnIdProperty || change.Property == IsSortedColumnProperty)
UpdateBarColor();
}

protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
UpdateText();
UpdateBar();
UpdateBarColor();
}

// ── Helpers ────────────────────────────────────────────────────────────

private void UpdateText()
{
if (ValueText != null)
ValueText.Text = DisplayText;
}

private void UpdateBar()
{
if (BarTrack == null || BarFill == null) return;

var ratio = global::System.Math.Max(0.0, global::System.Math.Min(1.0, Ratio));
var trackWidth = BarTrack.Bounds.Width;

// Fall back to control width when layout hasn't run yet
if (trackWidth <= 0)
trackWidth = Bounds.Width - 4; // subtract Margin="2,1" × 2

BarFill.Width = trackWidth > 0 ? ratio * trackWidth : 0;
}

private void UpdateBarColor()
{
if (BarFill == null) return;

// 1. Per-column override
if (!string.IsNullOrEmpty(ColumnId) &&
Application.Current!.TryFindResource($"BarChart.{ColumnId}", out var colRes) &&
colRes is IBrush colBrush)
{
BarFill.Background = colBrush;
return;
}

// 2. Sorted vs Default
var key = IsSortedColumn ? "BarChart.Sorted" : "BarChart.Default";
if (Application.Current!.TryFindResource(key, out var res) && res is IBrush brush)
BarFill.Background = brush;
else
BarFill.Background = IsSortedColumn
? new SolidColorBrush(Color.FromRgb(0x2E, 0xAE, 0xF1))
: new SolidColorBrush(Color.FromRgb(0x3A, 0x6E, 0xA8));
}

// ── Layout override to update bar after measure ─────────────────────--

protected override Size ArrangeOverride(Size finalSize)
{
var result = base.ArrangeOverride(finalSize);
UpdateBar();
return result;
}
}
158 changes: 145 additions & 13 deletions src/PlanViewer.App/Controls/QueryStoreGridControl.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
FontSize="11"
Background="{DynamicResource BackgroundDarkBrush}"
BorderThickness="0"
Sorting="ResultsGrid_Sorting"
ScrollViewer.HorizontalScrollBarVisibility="Auto">
<DataGrid.ContextMenu>
<ContextMenu>
Expand All @@ -96,19 +97,150 @@
<DataGridTextColumn Header="QueryId" Binding="{ReflectionBinding QueryId}" Width="90"/>
<DataGridTextColumn Header="PlanId" Binding="{ReflectionBinding PlanId}" Width="80"/>
<DataGridTextColumn Header="Last Executed (Local)" Binding="{ReflectionBinding LastExecutedLocal}" Width="160"/>
<DataGridTextColumn Header="Executions" Binding="{ReflectionBinding ExecsDisplay}" SortMemberPath="ExecsSort" Width="100"/>
<DataGridTextColumn Header="Total CPU (ms)" Binding="{ReflectionBinding TotalCpuDisplay}" SortMemberPath="TotalCpuSort" Width="120"/>
<DataGridTextColumn Header="Avg CPU (ms)" Binding="{ReflectionBinding AvgCpuDisplay}" SortMemberPath="AvgCpuSort" Width="110"/>
<DataGridTextColumn Header="Total Duration (ms)" Binding="{ReflectionBinding TotalDurDisplay}" SortMemberPath="TotalDurSort" Width="150"/>
<DataGridTextColumn Header="Avg Duration (ms)" Binding="{ReflectionBinding AvgDurDisplay}" SortMemberPath="AvgDurSort" Width="140"/>
<DataGridTextColumn Header="Total Reads" Binding="{ReflectionBinding TotalReadsDisplay}" SortMemberPath="TotalReadsSort" Width="100"/>
<DataGridTextColumn Header="Avg Reads" Binding="{ReflectionBinding AvgReadsDisplay}" SortMemberPath="AvgReadsSort" Width="100"/>
<DataGridTextColumn Header="Total Writes" Binding="{ReflectionBinding TotalWritesDisplay}" SortMemberPath="TotalWritesSort" Width="100"/>
<DataGridTextColumn Header="Avg Writes" Binding="{ReflectionBinding AvgWritesDisplay}" SortMemberPath="AvgWritesSort" Width="100"/>
<DataGridTextColumn Header="Total Physical Reads" Binding="{ReflectionBinding TotalPhysReadsDisplay}" SortMemberPath="TotalPhysReadsSort" Width="155"/>
<DataGridTextColumn Header="Avg Physical Reads" Binding="{ReflectionBinding AvgPhysReadsDisplay}" SortMemberPath="AvgPhysReadsSort" Width="140"/>
<DataGridTextColumn Header="Total Memory (MB)" Binding="{ReflectionBinding TotalMemDisplay}" SortMemberPath="TotalMemSort" Width="140"/>
<DataGridTextColumn Header="Avg Memory (MB)" Binding="{ReflectionBinding AvgMemDisplay}" SortMemberPath="AvgMemSort" Width="130"/>

<!-- Numeric columns with inline bar chart -->
<DataGridTemplateColumn Header="Executions" SortMemberPath="ExecsSort" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="local:QueryStoreRow">
<local:BarChartCell DisplayText="{Binding ExecsDisplay}"
Ratio="{Binding ExecsRatio}"
ColumnId="Executions"
IsSortedColumn="{Binding IsSortedColumn_Executions}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

<DataGridTemplateColumn Header="Total CPU (ms)" SortMemberPath="TotalCpuSort" Width="120">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="local:QueryStoreRow">
<local:BarChartCell DisplayText="{Binding TotalCpuDisplay}"
Ratio="{Binding TotalCpuRatio}"
ColumnId="TotalCpu"
IsSortedColumn="{Binding IsSortedColumn_TotalCpu}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

<DataGridTemplateColumn Header="Avg CPU (ms)" SortMemberPath="AvgCpuSort" Width="110">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="local:QueryStoreRow">
<local:BarChartCell DisplayText="{Binding AvgCpuDisplay}"
Ratio="{Binding AvgCpuRatio}"
ColumnId="AvgCpu"
IsSortedColumn="{Binding IsSortedColumn_AvgCpu}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

<DataGridTemplateColumn Header="Total Duration (ms)" SortMemberPath="TotalDurSort" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="local:QueryStoreRow">
<local:BarChartCell DisplayText="{Binding TotalDurDisplay}"
Ratio="{Binding TotalDurRatio}"
ColumnId="TotalDuration"
IsSortedColumn="{Binding IsSortedColumn_TotalDuration}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

<DataGridTemplateColumn Header="Avg Duration (ms)" SortMemberPath="AvgDurSort" Width="140">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="local:QueryStoreRow">
<local:BarChartCell DisplayText="{Binding AvgDurDisplay}"
Ratio="{Binding AvgDurRatio}"
ColumnId="AvgDuration"
IsSortedColumn="{Binding IsSortedColumn_AvgDuration}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

<DataGridTemplateColumn Header="Total Reads" SortMemberPath="TotalReadsSort" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="local:QueryStoreRow">
<local:BarChartCell DisplayText="{Binding TotalReadsDisplay}"
Ratio="{Binding TotalReadsRatio}"
ColumnId="TotalReads"
IsSortedColumn="{Binding IsSortedColumn_TotalReads}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

<DataGridTemplateColumn Header="Avg Reads" SortMemberPath="AvgReadsSort" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="local:QueryStoreRow">
<local:BarChartCell DisplayText="{Binding AvgReadsDisplay}"
Ratio="{Binding AvgReadsRatio}"
ColumnId="AvgReads"
IsSortedColumn="{Binding IsSortedColumn_AvgReads}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

<DataGridTemplateColumn Header="Total Writes" SortMemberPath="TotalWritesSort" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="local:QueryStoreRow">
<local:BarChartCell DisplayText="{Binding TotalWritesDisplay}"
Ratio="{Binding TotalWritesRatio}"
ColumnId="TotalWrites"
IsSortedColumn="{Binding IsSortedColumn_TotalWrites}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

<DataGridTemplateColumn Header="Avg Writes" SortMemberPath="AvgWritesSort" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="local:QueryStoreRow">
<local:BarChartCell DisplayText="{Binding AvgWritesDisplay}"
Ratio="{Binding AvgWritesRatio}"
ColumnId="AvgWrites"
IsSortedColumn="{Binding IsSortedColumn_AvgWrites}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

<DataGridTemplateColumn Header="Total Physical Reads" SortMemberPath="TotalPhysReadsSort" Width="155">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="local:QueryStoreRow">
<local:BarChartCell DisplayText="{Binding TotalPhysReadsDisplay}"
Ratio="{Binding TotalPhysReadsRatio}"
ColumnId="TotalPhysReads"
IsSortedColumn="{Binding IsSortedColumn_TotalPhysReads}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

<DataGridTemplateColumn Header="Avg Physical Reads" SortMemberPath="AvgPhysReadsSort" Width="140">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="local:QueryStoreRow">
<local:BarChartCell DisplayText="{Binding AvgPhysReadsDisplay}"
Ratio="{Binding AvgPhysReadsRatio}"
ColumnId="AvgPhysReads"
IsSortedColumn="{Binding IsSortedColumn_AvgPhysReads}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

<DataGridTemplateColumn Header="Total Memory (MB)" SortMemberPath="TotalMemSort" Width="140">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="local:QueryStoreRow">
<local:BarChartCell DisplayText="{Binding TotalMemDisplay}"
Ratio="{Binding TotalMemRatio}"
ColumnId="TotalMemory"
IsSortedColumn="{Binding IsSortedColumn_TotalMemory}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

<DataGridTemplateColumn Header="Avg Memory (MB)" SortMemberPath="AvgMemSort" Width="130">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="local:QueryStoreRow">
<local:BarChartCell DisplayText="{Binding AvgMemDisplay}"
Ratio="{Binding AvgMemRatio}"
ColumnId="AvgMemory"
IsSortedColumn="{Binding IsSortedColumn_AvgMemory}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Query Text" Width="300" MinWidth="300">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="local:QueryStoreRow">
Expand Down
Loading
Loading