Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Using a Chart With Multiple Lines From A Collection

4.93/5 (7 votes)
2 Sep 2015CPOL3 min read 28.2K   1.3K  
Sometimes, a chart can make data a lot easier to understand - but why does the Chart control make it so difficult to display multiple lines from a collection of objects?

Introduction

Sometimes, a chart can make data a lot easier to understand - but why does the Chart control make it so difficult to display multiple lines from a collection of objects?

If you drop a DataGridView on your app, and set its DataSource to an IEnumerable collection of your class instances, it displays it instantly in a sensible way:

Image 1

But... that's pretty hard to work out any real detail.

Adding charts makes it a lot easier to: visualize:

Image 2

So Just Set the DataSource, Yes?

No - you can do it, and call DataBind, but then you need to tell it what properties to use for what.

Doing a single line graph is easy: set the Series.XValueMember to the name of the property in your class you want along the X axis, and the Series.YValueMembers to the Y axis property - and bingo, it's done.

So You Just Set the YValueMembers to Multiple Names, Right?

You'd think so: MSDN says:

Property Value
Type: System.String

The member columns of the chart data source used to bind data to the Y-values of the series

Remarks
This property accepts a comma separated string that contains the names or ordinal values of the columns.

So, you just say:

C#
s.YValueMembers = "Delta,Average";

And it displays two sets of lines... try it. Go on.

Do you get two lines?

No - you get an exception that it doesn't accept multiples... :sigh:

Anyway, after an afternoon of swearing, trying again, swearing some more, and eventually getting the stupid thing working... I had it working.

So I threw it all away, and used what I'd learned to create a "generic" UserControl derived from Chart that accepted an IEnumerable collection of class instances and worked it all out for itself.

How It Works

It's actually pretty simple, it only needs a single method which examines the properties:

C#
public void Load<T>(IEnumerable<T> data, string xColumn = null,
SeriesChartType chartType = SeriesChartType.Line)
    {
    Series.Clear();
    PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
    if (properties.Count == 0) return;
    if (xColumn == null) xColumn = properties[0].Name;
    Series s;
    foreach (PropertyDescriptor property in properties)
        {
        if (property.Name != xColumn)
            {
            bool include = true;
            foreach (Attribute attribute in property.Attributes)
                {
                DescriptionAttribute desc = attribute as DescriptionAttribute;
                if (desc != null && desc.Description == "No Chart")
                    {
                    include = false;
                    break;
                    }
                }
            if (include)
                {
                s = new Series();
                s.XValueMember = xColumn;
                s.YValueMembers = property.Name;
                s.ChartType = chartType;
                s.LegendText = property.DisplayName;
                Series.Add(s);
                }
            }
        }
    DataSource = data;
    DataBind();
    }

All it does is create a new Series for each chartable property in the class - there are some simple "tweaks" so that you can say "don't chart this" for text, and unwanted properties, but other than that, it's pretty obvious.

It just reads all the property names from the type, checks the Description attribute for each to see if it should be ignored, and creates a series for each of all the others.

There is a demo on the download which shows this - it's based on a real class, and real code so it's a little convoluted (in the real world, I load the data from a DB, so it comes already packaged as DataTables, and I need to work with the multiple data sets separately - if I didn't work from that, the example would be a lot clearer).

But the basic class that created the DGV and charts above was pretty simple:

C#
public class NasEntry
    {
    private User _User;
    private DateTime _Date;
    private int _Hits;
    private int _Delta;
    private int _Average;
    private int _D10MovingAverage;
    private int _D30MovingAverage;

    public DateTime Date
        {
        get { return _Date; }
        set { _Date = value; }
        }
    [DescriptionAttribute("No Chart")]
    public User User
        {
        get { return _User; }
        set { _User = value; }
        }
    [DescriptionAttribute("No Chart")]
    public int Hits
        {
        get { return _Hits; }
        set { _Hits = value; }
        }
    public int Delta
        {
        get { return _Delta; }
        set { _Delta = value; }
        }
    public int Average
        {
        get { return _Average; }
        set { _Average = value; }
        }
    [DisplayName("10 Day Av")]
    public int D10MovingAverage
        {
        get { return _D10MovingAverage; }
        set { _D10MovingAverage = value; }
        }
    [DisplayName("30 Day Av")]
    public int D30MovingAverage
        {
        get { return _D30MovingAverage; }
        set { _D30MovingAverage = value; }
        }

You can see that the "User" and "Hits" properties are decorated with a DescriptionAttribute of "No Chart" which prevents them being included as chartable items. Also note the decoration with DisplayName values which overrides the property name as the chart legend (and the DVG column header text).

That's all you have to do: create your class, create an IEnumerable collection of the instances containing the values, and call the EnumerableChart.Load method:

C#
myChart.Load(myListOfItems);

History

  • 2015-09-02 First version

License

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