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;
}
}
Loading