Using C1Chart in MVVM Part II
In one of my earlier blog posts I dealt with using C1Chart in MVVM. I was asked if we could dynamically add and remove data series from C1Chart. My answer to that is, yes there is way and I decided to post this as another blog post since this would be a common scenario for many. If we inspect the various bindings available to us in the C1Chart model we will find that the ChartData accepts a binding. This is the answer to the above question. We can simply have a C1ChartData property exposed from the ViewModel and at run-time add and remove series from the ChartData. This will be more clear as we walk through the implementation. Let's start by creating a new WPF project inside Visual Studio. I am going to show data based on "Sales" class. So let's first create this class. Here is the code:
public class Sales:INotifyPropertyChanged
{
public Sales(string product,double salevalue,double volume,string shipcity,double discount)
{
_product = product;
_salevalue = salevalue;
_volume = volume;
_shipcity = shipcity;
_discount = discount;
\_variance = (\_volume*1000/_salevalue);
}
#region privateFields
string _product;
double _salevalue;
double _volume;
double _discount;
string _shipcity;
double _variance;
#endregion
#region publicProperties
public string Product
{
get { return _product; }
set { _product = value; }
}
public double SaleValue
{
get { return _salevalue; }
set { _salevalue = value; OnPropertyChanged("SaleValue"); }
}
public double Volume
{
get { return _volume; }
set { _volume = value; OnPropertyChanged("Volume"); }
}
public double Discount
{
get { return _discount; }
set { _discount = value; OnPropertyChanged("Discount"); }
}
public double Variance
{
get { return (this.Volume*1000/ this.SaleValue); }
}
public string ShipCity
{
get { return _shipcity; }
set { _shipcity = value; OnPropertyChanged("ShipCity"); }
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
#endregion
}
In this class we have six fields: Products, Salevalue (sales value of a product), Volume (volume of product sold), Discount (discount given on that product), ShipCity (cities where the products where sold), and Variance (a hypothetical variance). Next we will create a base ViewModel which will implement the INotifyPropertyChanged interface. Our ChartViewModel will inherit the base viewmodel. Here is the code for the base viewmodel.
public class ViewModelBase:INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
#endregion
public ViewModelBase()
{
}
}
Now lets create the ChartViewModel. Here is the code.
public class ChartViewModel:ViewModelBase
{
#region Ctor..
public ChartViewModel()
{
_saleslist = new ObservableCollection<Sales>();
LoadData();
_axisvalues = new ObservableCollection<string>();
_axisvalues.Add("SaleValue");
_axisvalues.Add("Volume");
_axisvalues.Add("VolumeVsCity");
_axisvalues.Add("ProductDiscount");
_axisvalues.Add("VolumeTotal");
\_axisview = new CollectionView(\_axisvalues);
\_axisview.CurrentChanged += new EventHandler(\_axisview_CurrentChanged);
_chartdata = new ChartData();
ChartDataView.ItemNameBinding = new Binding("Product");
DataSeries ds1 = new DataSeries();
ds1.Label = "SaleValue";
ds1.ValueBinding = new Binding("SaleValue");
DataSeries ds2 = new DataSeries();
ds2.Label = "Volume";
ds2.ValueBinding = new Binding("Volume");
ChartDataView.ItemsSource = SalesList;
ChartDataView.Children.Add(ds1);
ChartDataView.Children.Add(ds2);
}
#endregion
#region private Fields
ObservableCollection<Sales> _saleslist;
ObservableCollection<string> _axisvalues;
CollectionView _axisview;
ChartData _chartdata;
#endregion
#region publicProperties
public ObservableCollection<Sales> SalesList
{
get { return _saleslist; }
set { _saleslist = value; }
}
public CollectionView AxisView
{
get { return _axisview; }
set { _axisview = value; OnPropertyChanged("AxisView"); }
}
public ChartData ChartDataView
{
get { return _chartdata; }
set { _chartdata = value; OnPropertyChanged("ChartDataView"); }
}
#endregion
#region privateMethods
void LoadData()
{
_saleslist.Add(new Sales("Confectionaries", 2500.00,300000.00,"NewYork",25.50));
_saleslist.Add(new Sales("Plastics", 3500.00,720000.00,"Newark",15.75));
_saleslist.Add(new Sales("Electronics", 7500.00,800000.00,"GeorgeTown",20.65));
_saleslist.Add(new Sales("Produces", 800.00,100000.00,"Houston",30.35));
}
void \_axisview\_CurrentChanged(object sender, EventArgs e)
{
if (_axisview.CurrentItem.ToString() == "SaleValue")
{
_chartdata.Children.Clear();
DataSeries ds1 = new DataSeries();
ds1.Label = "SaleValue";
ds1.ValueBinding = new Binding("SaleValue");
ds1.ItemsSource = SalesList;
_chartdata.Children.Add(ds1);
_chartdata.ItemNameBinding = new Binding("Product");
}
else if (_axisview.CurrentItem.ToString() == "Volume")
{
_chartdata.Children.Clear();
DataSeries ds2 = new DataSeries();
ds2.ValueBinding = new Binding("Volume");
ds2.Label = "Volume";
ds2.ItemsSource = SalesList;
_chartdata.Children.Add(ds2);
_chartdata.ItemNameBinding = new Binding("Product");
}
else if (_axisview.CurrentItem.ToString() == "VolumeVsCity")
{
_chartdata.Children.Clear();
DataSeries ds1 = new DataSeries();
ds1.Label="Volume";
ds1.ValueBinding = new Binding("Volume");
ds1.ItemsSource = SalesList;
_chartdata.Children.Add(ds1);
_chartdata.ItemNameBinding = new Binding("ShipCity");
}
else if (_axisview.CurrentItem.ToString() == "ProductDiscount")
{
_chartdata.Children.Clear();
DataSeries ds1 = new DataSeries();
ds1.Label = "Discount";
ds1.ValueBinding = new Binding("Discount");
ds1.ItemsSource = SalesList;
_chartdata.Children.Add(ds1);
_chartdata.ItemNameBinding = new Binding("Product");
}
else if (_axisview.CurrentItem.ToString() == "VolumeTotal")
{
_chartdata.Children.Clear();
DataSeries ds1 = new DataSeries();
ds1.Label = "Volume";
ds1.ValueBinding = new Binding("Volume");
ds1.ItemsSource = SalesList;
DataSeries ds2 = new DataSeries();
ds2.Label = "Variance";
ds2.ValueBinding = new Binding("Variance");
ds2.ItemsSource = SalesList;
_chartdata.Children.Add(ds1);
_chartdata.Children.Add(ds2);
_chartdata.ItemNameBinding = new Binding("Product");
}
}
#endregion
}
Ok, lets inspect the above code. There is a LoadData() method which loads data to ObservableCollection
<Window x:Class="C1ChartinMVVMPart2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="336" Width="790" xmlns:c1chart="http://schemas.componentone.com/xaml/c1chart">
<Grid x:Name="LayoutRoot" >
<c1chart:C1Chart Height="276" HorizontalAlignment="Left" Margin="153,9,0,0" Name="c1Chart1" VerticalAlignment="Top" Width="603" Data="{Binding ChartDataView}" Theme="{DynamicResource {ComponentResourceKey ResourceId=Vista, TypeInTargetAssembly=c1chart:C1Chart}}">
<c1chart:C1ChartLegend DockPanel.Dock="Right" />
</c1chart:C1Chart>
<ListBox Height="100" ItemsSource="{Binding AxisView}" HorizontalAlignment="Left" Margin="27,98,0,0" Name="listBox1" VerticalAlignment="Top" Width="120" Background="{Binding ElementName=c1Chart1,Path=Background}"/>
</Grid>
</Window>
As we can see I have exposed the AxisView collectionview to the ListBox. The ListBox would show all the available data series and upon selection the relevant data series would be drawn inside the chart. Add the following code inside the Application startup event:
MainWindow win = new MainWindow();
C1ChartinMVVMPart2.ViewModel.ChartViewModel vm = new ViewModel.ChartViewModel();
win.LayoutRoot.DataContext = vm;
win.Show();
Run the application and observe the behavior. Download Sample Thanks for reading!