While developing user interfaces for applications it is often required to show data in either a tabular form or by using charts. And it is not unusual to show data in a combination of both chart and grid inside one container. An example is showing a progress value in a chart that is inside a datagrid cell. In this blog post I will show you how to create a chart inside a C1DataGrid using template columns. This can be done in both XAML and code behind. The example we are going to talk about has a little twist in it. Imagine we have a class called Sales and another class called SalesChange. For each product in Sales we have a corresponding list of "Change" and "Time" value in SalesChange. We want to show data from Sales inside a datagrid but we also want to show data from SalesChange in the chart. Now that we have our scenario in black and white, let's first throw some code for both the above mentioned class. Sales class:
public class Sales : INotifyPropertyChanged
{
public Sales() { }
public Sales(int id,string product, double sale)
{
productid = id;
_product = product;
_sales = sale;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
#endregion
int productid;
public int ProductId
{
get { return productid; }
set { productid = value; }
}
string _product;
public string Product
{
get { return _product; }
set { _product = value; }
}
double _sales;
public double Sale
{
get { return _sales; }
set { _sales = value; }
}
ObservableCollection _changelist=new ObservableCollection();
public ObservableCollection ChangeList
{
get { return _changelist; }
set { _changelist = value; }
}
}
SalesChange class:
public class SalesChange:INotifyPropertyChanged
{
public SalesChange() { }
public SalesChange(int id,double change,string time)
{
id = productid;
_change = change;
_time = time;
}
int productid;
public int ProductId
{
get { return productid; }
set { productid = value; }
}
double _change;
public double Change
{
get { return _change; }
set { _change = value; OnPropertyChanged(“Change”); }
}
string _time;
public string Time
{
get { return _time; }
set
{
_time = value;
OnPropertyChanged(“Time”);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Lets say we have a list of sales object available. I am going to first show you the code behind way of showing chart in a C1DataGrid. Declare the template column and add it to the grid as follows:
C1.WPF.DataGrid.DataGridTemplateColumn tmpcol;
tmpcol = new C1.WPF.DataGrid.DataGridTemplateColumn();
tmpcol.Header = "Progress";
this.c1DataGrid1.Columns.Add(tmpcol);
tmpcol.Width = new C1.WPF.DataGrid.DataGridLength(400);
Now the scenario says that each Sales object has a SalesChange list associated to it and we have to show the data inside a chart for this list. In LoadedCellPresenter of the C1DataGrid we have access to the Cell.Row.DataItem which we can cast as sales object and get a association for the SalesChange list. Further the template column takes a data-template inside its cell template. Hence we have to declare a DataTemplate and a FrameworkElementFactory of type chart. and then assign the FrameworkElementFactory to the DataTemplate visual tree and at last assign the template to the template columns cell template. Below is the code listing:
void c1DataGrid1_LoadedCellPresenter(object sender, C1.WPF.DataGrid.DataGridCellEventArgs e)
{
Sales _sale = e.Cell.Row.DataItem as Sales;
DataTemplate dt = new DataTemplate();
C1.WPF.C1Chart.ChartData chart = new C1.WPF.C1Chart.ChartData();
DataSeries ds = new DataSeries();
ds.Label = “Change”;
ds.ChartType = ChartType.Line;
ds.ValueBinding = new Binding(“Change”);
ds.ItemsSource = _sale.ChangeList;
chart.ItemNameBinding = new Binding(“Time”);
chart.Children.Add(ds);
Binding chartbinding = new Binding();
chartbinding.Source = chart;
FrameworkElementFactory fmr = new FrameworkElementFactory(typeof(C1Chart));
fmr.SetBinding(C1Chart.DataProperty, chartbinding);
dt.VisualTree = fmr;
dt.Seal();
tmpcol.CellTemplate = dt;
}
So that was the code behind part. How about somewhat cleaner and manageable code. Lets do the above in MVVM way. We can declare the template column in XAML and have the chart created inside it. We could also set binding according to the ViewModel. It is as easy as said, really :). Let's create a view model which will load data to our Sales and SalesChange class.
class SalesViewModel
{
ObservableCollection _saleslist;
public SalesViewModel()
{
_saleslist = new ObservableCollection();
LoadData();
DispatcherTimer tmr = new DispatcherTimer();
tmr.Interval = new TimeSpan(0, 0, 5);
tmr.Tick = new EventHandler(tmr_Tick);
tmr.Start();
}
void tmr_Tick(object sender, EventArgs e)
{
RefreshData();
}
public ObservableCollection SalesList
{
get { return _saleslist; }
set { _saleslist = value; }
}
void LoadData()
{
_saleslist.Add(new Sales(1124, “Electronics”, 5000));
_saleslist.Add(new Sales(1125,”Vehicles”, 75000));
_saleslist.Add(new Sales(1126,”Produces”, 55000));
}
void RefreshData()
{
Random rn=new Random();
foreach (Sales item in SalesList)
{
if (item.Product == “Electronics”)
{
item.ChangeList.Add(new SalesChange(item.ProductId, rn.Next(50, 150), DateTime.Now.AddMinutes(10).Minute “:” DateTime.Now.AddSeconds(10).Second));
}
else if (item.Product == “Vehicles”)
{
item.ChangeList.Add(new SalesChange(item.ProductId, rn.Next(50, 150), DateTime.Now.AddMinutes(12).Minute “:” DateTime.Now.AddSeconds(15).Second));
}
else
{
item.ChangeList.Add(new SalesChange(item.ProductId, rn.Next(50, 150), DateTime.Now.AddMinutes(14).Minute “:” DateTime.Now.AddSeconds(20).Second));
}
if (item.ChangeList.Count > 8)
{
item.ChangeList.RemoveAt(1);
}
}
}
}
In this class we simply add data to SalesList and SalesChange. SalesChange is updated every 5 seconds. I plan to show only last 8 updates in the chart. Create a text column, one numeric column and a template column inside the grid. Add a DataTemplate to the template column and drag and drop a C1Chart inside the DataTemplate. Bind the text column to "Product" and numeric column to "Sales". Set the ItemNameBinding of the C1Chart to "Time" and ItemSource to SalesList, set the path to "ChangeList". Add a DataSeries and set its ValueBinding to "Change" Under App.cs we can have the Application start-up set as follows:
private void Application_Startup(object sender, StartupEventArgs e)
{
SalesViewModel vm = new SalesViewModel();
MainWindow win = new MainWindow();
win.DataContext = vm;
win.Show();
}
Run the sample and observe the chart inside the grid how its updated automatically.