Click here to Skip to main content
15,867,594 members
Articles / Desktop Programming / WPF

WPF Charting using MVVM Pattern

Rate me:
Please Sign up or sign in to vote.
4.95/5 (15 votes)
19 Oct 2010CPOL8 min read 328.3K   5.4K   72   32
Shows how to implement a WPF Chart in .NET 4.0 using MVVM Pattern

Introduction

There are tons of articles on the MVVM (Model-View-ViewModel) design pattern, and rightfully so. It's an easy pattern to understand (after you've studied it for a while) and it very nicely separates concerns. Where better to separate concerns than with a Chart and its data? This article shows you how to use the .NET Framework's (free) Datavisualization Chart component using the MVVM model.

Background

This sample was built using Visual Studio 2010 running .NET Framework 4.0. It also uses the WPF Toolkit version 3.5 which has the System.Windows.Controls.DataVisualization namespace. You can download this toolkit from Codeplex here. This solution is new to CodeProject as the previous examples of the DataVisualization namespace were either all for ASPX Charting or WinForms Charting. This particular project is for WPF and includes the entire current set of ChartTypes found in the Datavisualization Namespace.

Using the Code

The zip file for this article (minus the DataVisualization component) is an entire project that allows you to compile and run a sample of every chart type currently supported in WPF. Simply run the project in debug mode, once it comes up, just change the SeriesTypes as shown below on the left, to change the view. Notice below that the Title, Legend Title are also editable. This allows one to see how it was done and add other editable attributes to the Charting component.

ColumnSeries.png

Points of Interest

The View

Views in the MVVM pattern are just the markup in XAML needed for displaying content and style. Be careful when putting code function in the "code-behind" of the View. One typically wants to limit that code to View Only logic such as Animation. The content of the View is handled in the ViewModel via WPF Binding. The MainWindow ("Shell") XAML is shown below:

XML
<Window
    x:Class="WPFCharting.Views.MainWindowView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"          
    xmlns:cmd="clr-namespace:WPFCharting.Commands" 
    xmlns:ViewModels="clr-namespace:WPFCharting.ViewModels" 
    xmlns:Views="clr-namespace:WPFCharting.Views" 
    Title="MainWindowView" 
    MinHeight="400"
    MinWidth="800"
    Height="Auto" Width="Auto">
    <Window.Resources>
        <ViewModels:MainWindowVM x:Key="VM"></ViewModels:MainWindowVM>
        <cmd:ShowAlterData x:Key="SAD"></cmd:ShowAlterData>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="21*"></ColumnDefinition>
            <ColumnDefinition Width="80*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Border Padding="2" >
            <Border.Background>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,.7">
                    <GradientStop Color="White" Offset="0" />
                    <GradientStop Color="Black" Offset="1" />
                </LinearGradientBrush>
            </Border.Background>
            <Border Padding="1" >
                <Border.Background>
                    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                        <GradientStop Color="Black" Offset="0" />
                        <GradientStop Color="White" Offset="1" />
                    </LinearGradientBrush>
                </Border.Background>
                <StackPanel Grid.Column="0" Margin="5,5,5,0" 
                    DataContext="{Binding Source={StaticResource VM}}">
                    <StackPanel.Background>
                        <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                            <GradientStop Color="Black" Offset="0" />
                            <GradientStop Color="White" Offset="1" />
                        </LinearGradientBrush>
                    </StackPanel.Background>
                    <Views:UcTitle x:Name="XTitle" ></Views:UcTitle>
                    <Views:UcLegendTitle x:Name="XLegendTitle"/>
                    <Views:UcSeriesTitle x:Name="XSeriesTitle" />
                    <Views:UcSeriesTypes x:Name="XSeriesTypes"/>
                    <Border Padding="2,5,2,5" Background="Black">
                        <StackPanel>
                            <Label Foreground="LightBlue" 
				HorizontalContentAlignment="Center">Edit</Label>
                            <Button Command="{Binding Source={StaticResource SAD}}">
				Show/Alter Data</Button>
                            <Button>Reset To Original</Button>
                        </StackPanel>
                    </Border>
                </StackPanel>
            </Border>
        </Border>
        <ContentControl  Grid.Column="1" 
            DataContext="{Binding Source={StaticResource VM}}" 
            Content="{Binding Source={StaticResource VM}, Path=MainChart}" />
    </Grid>
    <Window.Background>
        <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
            <GradientStop Color="Black" Offset="0.016" />
            <GradientStop Color="White" Offset="0" />
            <GradientStop Color="Black" Offset="0.205" />
            <GradientStop Color="Black" Offset="0.079" />
            <GradientStop Color="White" Offset="0.212" />
        </LinearGradientBrush>
    </Window.Background>
</Window>

Notice there's really not much there, as it is really just a "Shell". There are just two "ContentControls" in the Shell, one is the StackPanel containing the Left side Editing UserControls and the other is the Content Control which "houses" the charts. One note of interest here is that the DataContext of the ChartControl had to be set explicitly as shown (as well as the Content and the Path) statements. Setting the DataContext anywhere else in this project failed. For example, it didn't work in the Grid. Perhaps I misspelled something, but it worked in the ContentControl so I left it there.

The ViewModel which contains (among other things) property getters and setters has a number of private/public properties which are for the WPF MainWindow (Shell). The call to PropChanged is how the ViewModel notifies the View. Those property getters/setters take on a look like this:

C#
private ContentControl _mainChart;
      /// <summary>
      /// When swapping ChartTypes this is the content which is injected to view
      /// </summary>
      public ContentControl MainChart {
          get { return _mainChart; }
          set {
              PreviousViewList.Add(_mainChart);
              _mainChart = value;
              PropChanged("MainChart");
          }
      }

So in the code above, we see the "pattern" of MVVM property "getters/setters". The private variable encapsulates the public get and set methods, the set method will then update the private variable and call PropChanged.

One question that arises is, how do we boot up a MVVM application. In this solution, you will find it being done in the App.cs file using this code:

C#
public partial class App : Application
{
  protected override void OnStartup(StartupEventArgs e)
  {
    MainWindow mw = new MainWindow();
    mw.Show();
 }

The OnStartup event is overridden and the MainWindow is instantiated and shown. From there, the Main Window will instantiate the MainWindowViewModel as shown next. The MainWindow is shown, but how do we start the MainWindowViewModel?

In the MainWindow, we see the MainWindowViewModel's class instantiation happening via XAML. This is done in the Window.Resouces section of the MainWindow.Xaml file. The MVVM pattern states "The View knows the View-Model, and the View-Model knows the Model".

XML
<Window.Resources>
<local:MainWindowVM x:Key="VM"></local:MainWindowVM>
</Window.Resources>

Note however that Binding between the View and the Viewmodel is done via the WPF Binding system so that when a project is run, the getter methods are called at View.Show() time. It (the ViewModel) then becomes a static object, which the charting Series UserControls (of this project) use to bind data. We gave it a name of "VM" for View Model as shown above.

Static Resources and WPF Binding is not a topic for this article; however, it pays to know how they work because certain aspects of the MVVM pattern use XAML based resources as shown above. You can read these articles on MVVM Resources.

Ahh! And now for the Markup that controls which type of ChartSeries is shown. Each of the UserControls in this project represent a different ChartView (Such as this Area control User Control). For Charting, a chart markup in XAML can contain a type of Chart Series. In this case (see Markup below), the PieSeries chart type is shown. NOTE: What is lacking in the Charting Framework; however, is an Generic Series object of which to bind to at design time. For example, there is no ChartingToolKit:Series generic type which would allow chart series types to be injected at run time changing the views of data. So as an alternative to this limitation, this project has 1 UserControl per chart type. This was the only way known to the author to do this in XAML.

XML
<chartingToolkit:LineSeriesx:Name="XPieSeries"
DataContext="{Binding}"
DependentValueBinding="{Binding Path=Value}"
IndependentValueBinding="{Binding Path=Key}"
ItemsSource="{Binding Path=data}"/>

If you are experimenting with Chart Series types (see XAML above), make sure to set up the Dependent and Independent Value Bindings to the correct Paths. This author lost a considerable amount of time with charts not showing up simply because the Value, and Key paths were bound to the wrong Axis. Notice also the generic Binding being used, there is no Source specified in these UserControls. This is due to WPFs nice DataContext inheritance effect. The MainWindowView sets the DataContext, so no explicit DataContext statements are needed in these UserControls which inherit this DataContext.

In the most current iteration of this project, there are 7 different UserControls found in the Views folders which are used to swap the content of the MainWindow Shell. These UserControls are the charttype implementations e.g. BarSeries, PieSeries, ColumnSeries, etc. Driven by the command pattern of the MVVM architecture, each of these classes use a static getInstance() getter, setter method to ensure the fastest possible swapping of the Chart Views.

You will find the implementation of the Command to swap the views in the Command folder. It is just a wrapper class invokable from the MainWindow View. This class "ShowSeries" is simply a select statement that gets the current instance of the Charttype. It then injects the Chart view into the MainWindow. While this is not a formalized IOC or Dependency Injection pattern, it simulates the same thing. In particular, adding the concept of Singleton Pattern (getInstance) for the sake of speed. BTW you'll find these Views are able to be injected rather fast due to the Singleton getInstance method.

The View Model

So what's easier than using an ObservableCollection of something when working with WPF? It's a wrapper class made just for exposing data to the View. What's really nice is that it is a generic collection that allows you to define the type (to be displayed). These three lines of code is all you need to wire-up a Chart in the View Model. The code in the zip file will show you how to notify the WPF layer of any changes.

C#
private ObservableCollection<KeyValuePair<string, int>> _data;
   public ObservableCollection<KeyValuePair<string,int>> data {
          get { return _data; }          
 }

The XAML above shows how the ViewModel delivers the data to the chart.

On the left hand side of the MainWindow, there is a "controller" that allows you to edit content within the Chart itself. It uses the ViewModel as does the UserControls (ChartTypes) to get and set the properties. This is an instance whereby multiple controls are all bound to the same ViewModel and demonstrates how the ViewModel can host separate concerns. Imagine a Webbrowser application that has navigation links on the left. The ViewModel can actually serve multiple ContentControls at the same time, without violating Separation of Concerns. However, keep in mind that the adage of "One Time and One Place" an "Each Object is responsible for itself" will guide you in determining how much is too much code within one class. This is why the Command interface is nice because it moves implementation out of the ViewModel. In this example, the Commands back-end the setter properties of the View Model.

The Model

Finally, we come to the point where we implement the Data Layer in the Model for the class. Nothing more than a concrete class of Observable collection of a KeyValuePair<string,int> type:

C#
public class MainWindowModel : ObservableCollection<KeyValuePair<string, int>>
 {
    public MainWindowModel()
      {
          init();
       }
    public void init()
    {
        Add(new KeyValuePair<string, int>("Dog", 30));
        Add(new KeyValuePair<string, int>("Cat", 25));
        Add(new KeyValuePair<string, int>("Rat", 5));
        Add(new KeyValuePair<string, int>("Hampster", 8));
        Add(new KeyValuePair<string, int>("Rabbit", 12));
     }
     public ObservableCollection<KeyValuePair<string, int>> getData()
     {
        return this;
        }
    }

Voila! That's all there is to this. Now imagine that you want to have SQL Connections or XML Readers providing content. As you can see, you have many options. You can create new methods within this class above, or you can create other back-end classes that update this class. The good news is that if you are able to follow this contract of a KeyValuePair<string,int> then the implementation is hidden from the View. You could have many different getData methods in this class.

History

  • 4th Update: Fixed Links in main article 10/19/2010
  • 3rd Update: Clarified the View's Code Behind Statement, added links to Browse Code. Clarified other content. 10/15/2010
  • 2nd Edition: Included all Chart Types, MVVM Commanding, View Injection as well as good examples of Binding, to show how to change content in a chart after it shows data. Lots of comments in the code. 10/12/2010 XZZ0195
  • Kenny Rogers and the 1st Edition: 10/12/2010 XZZ0195

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
ex: Mr Javaman

Comments and Discussions

 
QuestionA bug Pin
Tae-Sung Shin31-Jul-13 8:07
Tae-Sung Shin31-Jul-13 8:07 
Questionhow to use two chart in the same view or the same user control in many views. Pin
Daimroc19-Feb-12 1:29
Daimroc19-Feb-12 1:29 
AnswerRe: how to use two chart in the same view or the same user control in many views. Pin
Your Display Name Here24-Mar-12 11:23
Your Display Name Here24-Mar-12 11:23 
QuestionControl the color for chart Pin
Member 787310329-Sep-11 20:12
Member 787310329-Sep-11 20:12 
QuestionError when reselecting a series. Pin
Member 798362728-Jul-11 4:27
Member 798362728-Jul-11 4:27 
AnswerRe: Error when reselecting a series. Pin
Your Display Name Here29-Jul-11 17:26
Your Display Name Here29-Jul-11 17:26 
GeneralFiltering Data in using Datatables to build ObservableCollection<keyvaluepair<string,int> Pin
Your Display Name Here2-Nov-10 13:17
Your Display Name Here2-Nov-10 13:17 
GeneralRe: Filtering Data in using Datatables to build ObservableCollection Pin
Member 798362728-Jul-11 5:43
Member 798362728-Jul-11 5:43 
GeneralRe: Filtering Data in using Datatables to build ObservableCollection Pin
Your Display Name Here29-Jul-11 17:26
Your Display Name Here29-Jul-11 17:26 
GeneralWPF Chart animation Pin
Boško Stupar31-Oct-10 22:13
Boško Stupar31-Oct-10 22:13 
GeneralRe: WPF Chart animation Pin
Your Display Name Here29-Jul-11 17:34
Your Display Name Here29-Jul-11 17:34 
GeneralRe: WPF Chart animation Pin
Your Display Name Here3-Aug-11 4:28
Your Display Name Here3-Aug-11 4:28 
GeneralMy vote of 5 Pin
Sparkling_ouc18-Oct-10 21:42
Sparkling_ouc18-Oct-10 21:42 
GeneralMy vote of 3 Pin
Maxim Maximov15-Oct-10 12:57
Maxim Maximov15-Oct-10 12:57 
GeneralMessage Closed Pin
15-Oct-10 13:13
Your Display Name Here15-Oct-10 13:13 
Message Closed
GeneralRe: My vote of 3 Pin
Maxim Maximov15-Oct-10 22:16
Maxim Maximov15-Oct-10 22:16 
GeneralRe: My vote of 3 Pin
Your Display Name Here16-Oct-10 0:15
Your Display Name Here16-Oct-10 0:15 
GeneralRe: My vote of 3 Pin
Pete O'Hanlon15-Oct-10 21:20
subeditorPete O'Hanlon15-Oct-10 21:20 
GeneralCode has been scanned by Resharper... Pin
Your Display Name Here16-Oct-10 1:16
Your Display Name Here16-Oct-10 1:16 
GeneralA "dangerous" statement. Pin
Pete O'Hanlon14-Oct-10 23:57
subeditorPete O'Hanlon14-Oct-10 23:57 
GeneralRe: A "dangerous" statement. [modified] Pin
Your Display Name Here15-Oct-10 4:11
Your Display Name Here15-Oct-10 4:11 
GeneralRe: A "dangerous" statement. Pin
Pete O'Hanlon15-Oct-10 4:47
subeditorPete O'Hanlon15-Oct-10 4:47 
GeneralRe: A "dangerous" statement. Pin
Your Display Name Here15-Oct-10 5:48
Your Display Name Here15-Oct-10 5:48 
GeneralRe: A "dangerous" statement. Pin
Pete O'Hanlon15-Oct-10 10:27
subeditorPete O'Hanlon15-Oct-10 10:27 
GeneralRe: A "dangerous" statement. Pin
Your Display Name Here15-Oct-10 11:02
Your Display Name Here15-Oct-10 11:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.