--- phase: 09-storage-visualization plan: 03 subsystem: storage-visualization tags: [viewmodel, xaml, charts, livecharts2, localization] dependency_graph: requires: [09-01, 09-02] provides: [chart-ui, chart-toggle, chart-data-binding] affects: [StorageViewModel, StorageView] tech_stack: added: [LiveChartsCore.SkiaSharpView.WPF chart controls in XAML] patterns: [MultiDataTrigger visibility, ObservableCollection chart binding, top-10 aggregation] key_files: created: - SharepointToolbox/Views/Converters/BytesLabelConverter.cs modified: - SharepointToolbox/ViewModels/Tabs/StorageViewModel.cs - SharepointToolbox/Views/Tabs/StorageView.xaml - SharepointToolbox/Localization/Strings.resx - SharepointToolbox/Localization/Strings.fr.resx decisions: - "Used wrapper Grid elements with MultiDataTrigger for chart visibility instead of styling LiveCharts controls directly -- more reliable for third-party controls" - "Removed ToolTipLabelFormatter from ColumnSeries (not available in LiveCharts2 rc5); DataLabelsFormatter provides size labels on bars" - "Used XML entities for FR accented chars matching existing resx convention" metrics: duration: 573s completed: 2026-04-07 --- # Phase 09 Plan 03: ViewModel Chart Properties and View XAML Summary StorageViewModel extended with chart data binding (pie/donut + bar) using LiveCharts2, StorageView updated with split layout (DataGrid + chart panel), chart toggle radio buttons, and EN/FR localization keys. ## What Was Done ### Task 1: Extend StorageViewModel with chart data and toggle - Added LiveCharts2 using statements (LiveChartsCore, SkiaSharpView, SkiaSharp) - Added IsDonutChart toggle property (ObservableProperty, default true) - Added FileTypeMetrics ObservableCollection with property-changed notification - Added HasChartData computed property - Added PieChartSeries, BarChartSeries, BarXAxes, BarYAxes properties - Implemented UpdateChartSeries: top-10 by size with "Other" aggregation, PieSeries with configurable InnerRadius for donut mode, ColumnSeries with labeled axes - Added FormatBytes static helper for chart labels - Updated RunOperationAsync to call CollectFileTypeMetricsAsync after storage scan - Updated OnTenantSwitched to clear FileTypeMetrics - **Commit:** 70048dd ### Task 2: Update StorageView.xaml with chart panel, toggle, and localization - Restructured StorageView.xaml: right content area now uses Grid with DataGrid (top), GridSplitter, chart panel (bottom) - Chart panel contains PieChart and CartesianChart wrapped in Grid elements with MultiDataTrigger visibility (IsDonutChart + HasChartData) - Added radio button group in left panel for donut/bar chart toggle - Added "no data" placeholder TextBlock with DataTrigger visibility - Created BytesLabelConverter for chart tooltip formatting - Added 5 stor.chart.* localization keys in Strings.resx (EN) and Strings.fr.resx (FR) - **Commit:** a8d79a8 ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] Removed ToolTipLabelFormatter from ColumnSeries** - **Found during:** Task 1 - **Issue:** LiveCharts2 rc5 ColumnSeries does not have ToolTipLabelFormatter property (only PieSeries does) - **Fix:** Removed the property; DataLabelsFormatter still provides size labels on bar chart columns - **Files modified:** SharepointToolbox/ViewModels/Tabs/StorageViewModel.cs - **Commit:** 70048dd **2. [Rule 1 - Bug] Used wrapper Grid elements for chart visibility** - **Found during:** Task 2 - **Issue:** Setting Style/Visibility directly on LiveCharts WPF controls may not work reliably with third-party controls - **Fix:** Wrapped each chart in a Grid element and applied MultiDataTrigger visibility on the wrapper instead - **Files modified:** SharepointToolbox/Views/Tabs/StorageView.xaml - **Commit:** a8d79a8 **3. [Rule 1 - Bug] Used DataTrigger for no-data placeholder visibility** - **Found during:** Task 2 - **Issue:** InverseBoolConverter only returns bool, not Visibility; cannot use it with ConverterParameter=Visibility - **Fix:** Used Style with DataTrigger binding on HasChartData instead of converter approach - **Files modified:** SharepointToolbox/Views/Tabs/StorageView.xaml - **Commit:** a8d79a8 ## Verification - `dotnet build SharepointToolbox/SharepointToolbox.csproj` succeeds with 0 errors - StorageViewModel has all required properties: IsDonutChart, FileTypeMetrics, PieChartSeries, BarChartSeries, BarXAxes, BarYAxes, HasChartData - RunOperationAsync calls CollectFileTypeMetricsAsync after CollectStorageAsync - StorageView.xaml contains lvc:PieChart and lvc:CartesianChart controls - Radio buttons bind to IsDonutChart with InverseBoolConverter for bar option - Strings.resx and Strings.fr.resx have stor.chart.title, stor.chart.donut, stor.chart.bar, stor.chart.toggle, stor.chart.nodata - No data placeholder shown via DataTrigger when HasChartData is False ## Self-Check: PASSED