I have a horizontal bar chart in a ViewModel displaying the top 10 clients in the past 6 or 12 months (default 6). I want the data to switch to 6 or 12 months when the user clicks on the buttons. I just can't seem to figure out how to get the chart to update. I've implemented the INotifyPropertyChanged
interface but I must have done it wrong. I've read multiple posts on the subject: still no clue what is wrong.
ReportClientModel
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace ReportXpress.Models
{
public class ReportClientModel : INotifyPropertyChanged
{
private string name;
public string Name
{
get => this.name;
set
{
this.name = value;
OnPropertyChanged();
}
}
public double? sales;
public double? Sales
{
get => this.sales;
set
{
this.sales = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName=null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
ViewModelBase
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
namespace ReportXpress.ViewModels
{
// Abstract : can only be used via inheritance
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
ClientViewModel
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using SkiaSharp;
using System.Collections.ObjectModel;
using System.Drawing;
using LiveChartsCore.Defaults;
using ReportXpress.Models;
using ReportXpress.Repositories;
using Org.BouncyCastle.Tls;
using System.Windows.Input;
using System.ComponentModel;
namespace ReportXpress.ViewModels
{
public class ClientViewModel : ViewModelBase
{
//Fields
private ObservableCollection<ISeries> clientsSalesSeries;
public ReportClientModel[] topClients6Months;
public ReportClientModel[] topClients12Months;
//Properties
public ObservableCollection<ISeries> ClientsSalesSeries
{ get
{
return clientsSalesSeries;
}
set
{
clientsSalesSeries = value;
OnPropertyChanged(nameof(ClientsSalesSeries));
}
}
public string[] Labels { get; set; }
public LiveChartsCore.Measure.Margin Margin { get; set; } = new LiveChartsCore.Measure.Margin(25,0,150,50);
//Events
public new event PropertyChangedEventHandler PropertyChanged;
//--> Commands
public ICommand ShowLast6Months { get; }
public ICommand ShowLast12Months { get; }
public ClientViewModel()
{
//Initialize commands
ShowLast6Months = new ViewModelCommand(ExecuteShowLast6Months);
ShowLast12Months = new ViewModelCommand(ExecuteShowLast12Months);
topClients6Months = new ReportClientModel[]
{
new ReportClientModel { Name = "Client 10 name", Sales=30689 },
new ReportClientModel { Name = "Client 9 name", Sales=31558 },
new ReportClientModel { Name = "Client 8 name", Sales=38937 },
new ReportClientModel { Name = "Client 7 name", Sales=42139 },
new ReportClientModel { Name = "Client 6 name", Sales=49860 },
new ReportClientModel { Name = "Client 5 name", Sales=51561 },
new ReportClientModel { Name = "Client 4 name", Sales=69554 },
new ReportClientModel { Name = "Client 3 name", Sales=70524 },
new ReportClientModel { Name = "Client 2 name", Sales=276170 },
new ReportClientModel { Name = "Client 1 name", Sales=345835 }
};
topClients12Months = new ReportClientModel[]
{
new ReportClientModel { Name = "Client 10 name", Sales=56664 },
new ReportClientModel { Name = "Client 9 name", Sales=79135 },
new ReportClientModel { Name = "Client 8 name", Sales=80280 },
new ReportClientModel { Name = "Client 7 name", Sales=83675 },
new ReportClientModel { Name = "Client 6 name", Sales=92612 },
new ReportClientModel { Name = "Client 5 name", Sales=126429 },
new ReportClientModel { Name = "Client 4 name", Sales=158313 },
new ReportClientModel { Name = "Client 3 name", Sales=163430 },
new ReportClientModel { Name = "Client 2 name", Sales=442796 },
new ReportClientModel { Name = "Client 1 name", Sales=592028 }
};
//Default values : last 6 months
ExecuteShowLast6Months(null);
Formatter = value => value.ToString("0") " k$";
}
protected virtual new void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void ExecuteShowLast12Months(object obj)
{
ClientsSalesSeries = new ObservableCollection<ISeries>
{
new RowSeries<ReportClientModel>
{
Values = topClients12Months,
Mapping = (client, point) =>
{
// use the Sales property in this series
point.PrimaryValue = (float)client.Sales;
// use the Index of the item
point.SecondaryValue = point.Context.Entity.EntityIndex;
},
Fill = new SolidColorPaint(SKColors.SkyBlue),
TooltipLabelFormatter = (point) => point.PrimaryValue.ToString("C2"),
DataLabelsPaint = new SolidColorPaint(SKColors.White),
DataLabelsSize = 14,
DataLabelsPosition = LiveChartsCore.Measure.DataLabelsPosition.End,
DataLabelsFormatter = (point) => topClients6Months[point.Context.Entity.EntityIndex].Name,
}
};
}
private void ExecuteShowLast6Months(object obj)
{
ClientsSalesSeries = new ObservableCollection<ISeries>
{
new RowSeries<ReportClientModel>
{
Values = topClients6Months,
Mapping = (client, point) =>
{
// use the Sales property in this series
point.PrimaryValue = (float)client.Sales;
// use the Index of the item
point.SecondaryValue = point.Context.Entity.EntityIndex;
},
Fill = new SolidColorPaint(SKColors.SkyBlue),
TooltipLabelFormatter = (point) => point.PrimaryValue.ToString("C2"),
DataLabelsPaint = new SolidColorPaint(SKColors.White),
DataLabelsSize = 14,
DataLabelsPosition = LiveChartsCore.Measure.DataLabelsPosition.End,
DataLabelsFormatter = (point) => topClients6Months[point.Context.Entity.EntityIndex].Name,
}
};
}
public Axis[] XAxesAmounts { get; set; } =
{
new Axis
{
LabelsPaint = new SolidColorPaint{ Color= SKColors.White},
Labeler = Labelers.Currency,
NameTextSize=10,
ShowSeparatorLines=true,
SeparatorsPaint = new SolidColorPaint { Color = SKColor.Parse("#33ffffff")},
TextSize=10
}
};
public Axis[] YAxesClients { get; set; } =
{
new Axis
{
Name = "",
NamePaint = new SolidColorPaint{ Color=SKColors.White},
LabelsPaint = new SolidColorPaint{ Color= SKColors.White},
Labeler = null,
IsVisible = true,
NameTextSize=10,
LabelsRotation=0,
ShowSeparatorLines = false,
MinLimit=-0.5,
TextSize=10
}
};
}
}
ClientView (chart section only)
<!--Chart for Top Clients-->
<lvc:CartesianChart Grid.Row="1" Margin="0,0,0,15"
Name="chartClientSales"
Series="{Binding ClientsSalesSeries}"
XAxes="{Binding XAxesAmounts}"
YAxes="{Binding YAxesClients}"
DrawMargin="{Binding Margin}"
TooltipPosition="Right"
ZoomMode="Both"
/>
Adding a breakpoint in ExecuteShowLast12Months
confirms the data in the series is changed, but the chart is not updated to reflect the new data. What am I doing wrong? I haven't seen any other posts that use a custom class in a RowSeries.
CodePudding user response:
You have two options
- Update the series by clearing the ObservableCollection and adding the new data to it directly (this way you don't have to implement INotifyPropertyChanged)
public ObservableCollection<ISeries> ClientsSalesSeries {set; get;} = new();
// ...
private void ExecuteShowLast12Months(object obj)
{
ClientsSalesSeries.Clear();
ClientsSalesSeries.Add(new RowSeries<ReportClientModel>{ });
}
// ...
private void ExecuteShowLast6Months(object obj)
{
ClientsSalesSeries.Clear();
ClientsSalesSeries.Add(new RowSeries<ReportClientModel>{ });
}
- Set binding mode of
Series
to TwoWay (you can use any collection, ex. List)
Series="{Binding ClientsSalesSeries, Mode=TwoWay}"
The first option is preferred if you want to take benefit of ObservableCollection.