Click here to Skip to main content
15,867,488 members
Articles / Web Development / HTML

A simple C# library for graph plotting

Rate me:
Please Sign up or sign in to vote.
4.90/5 (252 votes)
4 Sep 2014CPOL2 min read 948.7K   86.6K   578   165
An easy to use C# library for quick and simple graph plotting.

Introduction

In our application, we had to display the output of a multichannel ECG (Electro Cardiograph) device. I had a look at some commercial libraries, but none of them met by demands. So, I decided to design a simple solution by myself.

This is my attempt to design a flexible, easy to use library for drawing graphs.

The library is capable of displaying multiple graphs in different layouts. Right now, five modes of display are possible:

  • Normal: means that all data sources are displayed in one graph window, with separate ordinates.
  • Stacked: means that all data sources are displayed in one graph window, stacked vertically, with shared ordinate and shared abscissa.
  • Vertical aligned: means that the graph windows are displayed vertically aligned, with separate ordinates and shared abscissa.
  • Tiled horizontal: means that the data sources are displayed in tiled windows (preferred alignment direction is horizontal).
  • Tiled vertically: means that the data sources are displayed in tiled windows (preferred alignment direction is vertical).

Graphs can be displayed unscaled or auto-scaled. In the auto-scale mode, the visible graph is automatically fit to the visible area.

The following images show examples for the different display modes:

Normal:

GraphPlotting/graph_normal.png

Stacked:

GraphPlotting/graph_stacked.png

Tiled horizontal:

GraphPlotting/graph_tiled_horizontal.png

Tiled vertical:

GraphPlotting/graph_tiled_vertically.png

Vertical aligned:

GraphPlotting/graph_vertically_aligned.png

Autoscaled X-Axis

GraphPlotting/graph_fixed_x_range.png

The following images show a sample of an ECG application, where eight data sources are displayed vertically tiled and auto-scaled.

GraphPlotting/sample_2.jpg

Using the Code

The control is very simple to use. Just have a look at the sample application. The following code shows how the part in the demo application where the graphs for the different examples are generated:

C#
protected void CalcDataGraphs()
{

    this.SuspendLayout();

    display.DataSources.Clear();
    display.SetDisplayRangeX(0, 400);

    for (int j = 0; j < NumGraphs; j++)
    {
        display.DataSources.Add(new DataSource());
        display.DataSources[j].Name = "Graph " + (j + 1);
        display.DataSources[j].OnRenderXAxisLabel += RenderXLabel;

        switch (CurExample)
        {
            case  "NORMAL":
                this.Text = "Normal Graph";
                display.DataSources[j].Length = 5800;
                display.PanelLayout = PlotterGraphPaneEx.LayoutMode.NORMAL;
                display.DataSources[j].AutoScaleY = false;
                display.DataSources[j].SetDisplayRangeY(-300, 300);
                display.DataSources[j].SetGridDistanceY(100);
                display.DataSources[j].OnRenderYAxisLabel = RenderYLabel;
                CalcSinusFunction_0(display.DataSources[j], j);
                break;

            case "NORMAL_AUTO":
                this.Text = "Normal Graph Autoscaled";
                display.DataSources[j].Length = 5800;
                display.PanelLayout = PlotterGraphPaneEx.LayoutMode.NORMAL;
                display.DataSources[j].AutoScaleY = true;
                display.DataSources[j].SetDisplayRangeY(-300, 300);
                display.DataSources[j].SetGridDistanceY(100);
                display.DataSources[j].OnRenderYAxisLabel = RenderYLabel;
                CalcSinusFunction_0(display.DataSources[j], j);
                break;

            case "STACKED":
                this.Text = "Stacked Graph";
                display.PanelLayout = PlotterGraphPaneEx.LayoutMode.STACKED;
                display.DataSources[j].Length = 5800;
                display.DataSources[j].AutoScaleY = false;
                display.DataSources[j].SetDisplayRangeY(-250, 250);
                display.DataSources[j].SetGridDistanceY(100);
                CalcSinusFunction_1(display.DataSources[j], j);
                break;

            case "VERTICAL_ALIGNED":
                this.Text = "Vertical aligned Graph";
                display.PanelLayout =
                    PlotterGraphPaneEx.LayoutMode.VERTICAL_ARRANGED;
                display.DataSources[j].Length = 5800;
                display.DataSources[j].AutoScaleY = false;
                display.DataSources[j].SetDisplayRangeY(-300, 300);
                display.DataSources[j].SetGridDistanceY(100);
                CalcSinusFunction_2(display.DataSources[j], j);
                break;

            case "VERTICAL_ALIGNED_AUTO":
                this.Text = "Vertical aligned Graph autoscaled";
                display.PanelLayout =
                    PlotterGraphPaneEx.LayoutMode.VERTICAL_ARRANGED;
                display.DataSources[j].Length = 5800;
                display.DataSources[j].AutoScaleY = true;
                display.DataSources[j].SetDisplayRangeY(-300, 300);
                display.DataSources[j].SetGridDistanceY(100);
                CalcSinusFunction_2(display.DataSources[j], j);
                break;

            case "TILED_VERTICAL":
                this.Text = "Tiled Graphs (vertical prefered)";
                display.PanelLayout = PlotterGraphPaneEx.LayoutMode.TILES_VER;
                display.DataSources[j].Length = 5800;
                display.DataSources[j].AutoScaleY = false;
                display.DataSources[j].SetDisplayRangeY(-300, 600);
                display.DataSources[j].SetGridDistanceY(100);
                CalcSinusFunction_2(display.DataSources[j], j);
                break;

            case "TILED_VERTICAL_AUTO":
                this.Text = "Tiled Graphs (vertical prefered) autoscaled";
                display.PanelLayout = PlotterGraphPaneEx.LayoutMode.TILES_VER;
                display.DataSources[j].Length = 5800;
                display.DataSources[j].AutoScaleY = true;
                display.DataSources[j].SetDisplayRangeY(-300, 600);
                display.DataSources[j].SetGridDistanceY(100);
                CalcSinusFunction_2(display.DataSources[j], j);
                break;

            case "TILED_HORIZONTAL":
                this.Text = "Tiled Graphs (horizontal prefered)";
                display.PanelLayout = PlotterGraphPaneEx.LayoutMode.TILES_HOR;
                display.DataSources[j].Length = 5800;
                display.DataSources[j].AutoScaleY = false;
                display.DataSources[j].SetDisplayRangeY(-300, 600);
                display.DataSources[j].SetGridDistanceY(100);
                CalcSinusFunction_2(display.DataSources[j], j);
                break;

            case "TILED_HORIZONTAL_AUTO":
                this.Text = "Tiled Graphs (horizontal prefered) autoscaled";
                display.PanelLayout = PlotterGraphPaneEx.LayoutMode.TILES_HOR;
                display.DataSources[j].Length = 5800;
                display.DataSources[j].AutoScaleY = true;
                display.DataSources[j].SetDisplayRangeY(-300, 600);
                display.DataSources[j].SetGridDistanceY(100);
                CalcSinusFunction_2(display.DataSources[j], j);
                break;

            case "ANIMATED_AUTO":

                this.Text = "Animated graphs fixed x range";
                display.PanelLayout = PlotterGraphPaneEx.LayoutMode.TILES_HOR;
                display.DataSources[j].Length = 402;
                display.DataSources[j].AutoScaleY = false;
                display.DataSources[j].AutoScaleX = true;
                display.DataSources[j].SetDisplayRangeY(-300, 500);
                display.DataSources[j].SetGridDistanceY(100);
                display.DataSources[j].XAutoScaleOffset = 50;
                CalcSinusFunction_3(display.DataSources[j], j, 0);
                display.DataSources[j].OnRenderYAxisLabel = RenderYLabel;
                break;
        }
    }

    ApplyColorSchema();

    this.ResumeLayout();
    display.Refresh();

}

The functions CalcSinusFunction_0 CalcSinusFunction_1 and CalcSinusFunction_2 are used to calculate the different sinus functions for the graphs:

C#
protected void CalcSinusFunction_0(DataSource src, int idx)
{
    for (int i = 0; i  < src.Length; i++)
    {
        src.Samples[i].x = i;
        src.Samples[i].y = (float)(((float)200 * Math.Sin((idx + 1) *(
            i + 1.0) * 48 / src.Length)));
    }    
}

The functions RenderYLabel and RenderXLabel are used to render the X and Y legends of the graph.

C#
private String RenderXLabel(DataSource s, int idx)
{
    if (s.AutoScaleX)
    {         
        int Value = (int)(s.Samples[idx].x );
        return "" + Value;     
    }
    else
    {
        int Value = (int)(s.Samples[idx].x / 200);
        String Label = "" + Value + "\"";
        return Label;
    }    
}

private String RenderYLabel(DataSource s, float value)
{ 
    return String.Format("{0:0.0}", value);
}

Summary

There are lots of parameters that can be twisted which are not explained here - just look at the code. This library is far from being finished, but it is a good point to start from. The code is simple and self-explaining. From here, it will be simple to adopt the code for your needs.

I got so much inputs from here, and I wanted to give something back. So, here is my first article on The Code Project. I hope you like it.

To Do

  • code clean ups.

Version History

  • 04.09.2014 - First update in 4 years. Found back into my code ;-) Upgraded solution to VS 2013 Express.  Cleaned up some magic numbers. More to follow soon ;-)
  • 12.07.2009 - Updated article.
  • 12.02.2009 - Implemented x autoscaling.
  • 28.01.2009 - Some more cleanups. Implemented Print Form.
  • 27.01.2009 - New display mode, some cleanups.
  • 25.01.2009 - Initial release.

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)
Austria Austria
I have started programming at the age of 13 on the commodore 64.

Ever since then I have been programming on many systems in many languages.

During the last 12 years I have been working as professional programmer in different companies and different areas.

Now I am working as freelancer programmer / consultant

Comments and Discussions

 
QuestionThanks, it worked for me with modifications Pin
Binary620-Mar-12 22:01
Binary620-Mar-12 22:01 
GeneralThank You!! This library is awesome!!! Pin
masterdima11-Mar-12 13:51
masterdima11-Mar-12 13:51 
Questionhi Pin
aimzol25-Jan-12 20:53
aimzol25-Jan-12 20:53 
Questionhelp!! Pin
Member 852456521-Jan-12 18:35
Member 852456521-Jan-12 18:35 
QuestionHow Can I add zoom in / out capability to this library Pin
rah.you10-Jan-12 4:17
rah.you10-Jan-12 4:17 
GeneralMy vote of 3 Pin
Stein Gregory31-Dec-11 2:28
Stein Gregory31-Dec-11 2:28 
GeneralRe: My vote of 3 Pin
Stein Gregory31-Dec-11 18:27
Stein Gregory31-Dec-11 18:27 
QuestionNice library, but has several bugs Pin
MediaThrall221-Dec-11 5:32
MediaThrall221-Dec-11 5:32 
This is a nice library, and the graphs it displays look great. The library works very well for the examples demonstrated in the demo. However, trying to use it to graph my own data, I ran into several issues. I'm still grappling with whether I want to try and fix the library myself or find another solution. Regardless of the bugs listed below, I'd like to thank the author for the time and effort of putting the library together and making it publicly available.

Here are the issues I've identified while working with the lib:

1) The library enters an infinite loop if you SetGridDistanceY == 0.

2) The library does not support drawing lines between points if both of the points are not within the current display range of the graph (works fine if you have enough data points close together, but for very simple graphs consisting of only a few points, the lines won't be drawn at all). I suspect there's an optimization to just skill line draw calls if the points aren't on screen, but it probably needs to identify if the points are on opposite sides of the screen, and draw it in that case.

3) Attempting to graph a single line of 2 points (DataSource.length = 2) fails. It seems the length must be set to at least 3, even if the third point is never filled out? Doesn't seem to matter if both points are in the display range or not.

4) For the extended panels, you can only set the X dimension view range for the entire panel, not each individual graph. This constrains all displayed graphs within the pane to the same range, even if it's not practical for the other graphs (may have a graph with 8 points and another with 8000 points - since you can only set the view range based on how many data points you want to see, no matter what you set, one of those graphs will be invisible).

5) It would be nice if you could specify a view range that was based on "how much of X" you want to view, not just a specific number of data points. A % of the total view range would be very nice.

6) The library also lacks the ability to draw anything else on the graphs (plot larger points to identify specific data points, draw additional lines, etc). I've added these things to the library myself though, and it was fairly easy to work with.
AnswerRe: Nice library, but has several bugs Pin
MediaThrall221-Dec-11 8:25
MediaThrall221-Dec-11 8:25 
GeneralRe: Nice library, but has several bugs Pin
Maryami.bagheri12-Jul-13 22:44
Maryami.bagheri12-Jul-13 22:44 
GeneralMy vote of 4 Pin
robindegen22-Nov-11 22:17
robindegen22-Nov-11 22:17 
GeneralMy vote of 5 Pin
supermachine2-Nov-11 9:53
supermachine2-Nov-11 9:53 
QuestionThank you for such a great lib Pin
Sadahar28-Sep-11 8:06
Sadahar28-Sep-11 8:06 
QuestionThanks Pin
CloudAtlas217-Sep-11 7:00
CloudAtlas217-Sep-11 7:00 
GeneralMy vote of 5 Pin
uday148911-Sep-11 3:31
uday148911-Sep-11 3:31 
QuestionCannot implement with real data Pin
m_466027-Jul-11 0:21
m_466027-Jul-11 0:21 
BugFirst and last Samples are not being plotted Pin
Member 795576724-Jun-11 22:13
Member 795576724-Jun-11 22:13 
GeneralA fisheye.... Pin
boblogan16-Jun-11 11:55
boblogan16-Jun-11 11:55 
GeneralMy vote of 5. Pin
Filip D'haene23-May-11 1:52
Filip D'haene23-May-11 1:52 
GeneralMaking a FIFO buffer to use is it as a scope Pin
sdecorme8-Feb-11 3:53
sdecorme8-Feb-11 3:53 
GeneralRe: Making a FIFO buffer to use is it as a scope Pin
boblogan16-Jun-11 12:35
boblogan16-Jun-11 12:35 
GeneralMy vote of 5 Pin
Kanasz Robert25-Jan-11 4:15
professionalKanasz Robert25-Jan-11 4:15 
GeneralAbout the labels for axis Y Pin
isalsfy8-Jan-11 0:00
isalsfy8-Jan-11 0:00 
GeneralMy vote of 5 Pin
maybehelios21-Oct-10 21:29
maybehelios21-Oct-10 21:29 
QuestionProvide documentation Pin
Nasir Mahmud18-Oct-10 17:12
Nasir Mahmud18-Oct-10 17:12 

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.