Click here to Skip to main content
15,887,485 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 954.4K   86.7K   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

 
GeneralAnimating graph Pin
linkvirtual28-Jul-09 22:07
linkvirtual28-Jul-09 22:07 
GeneralRe: Animating graph Pin
Zimmermann Stephan29-Jul-09 21:17
Zimmermann Stephan29-Jul-09 21:17 
Generalecg program info Pin
hadlerz24-Jul-09 22:28
hadlerz24-Jul-09 22:28 
GeneralRe: ecg program info Pin
Gregory Chandran11-Oct-09 15:37
Gregory Chandran11-Oct-09 15:37 
GeneralRe: ecg program info Pin
wayneo200030-Jan-10 16:57
wayneo200030-Jan-10 16:57 
GeneralQuestion about mtimer in demo Pin
Bill Dreschel13-May-09 5:59
Bill Dreschel13-May-09 5:59 
GeneralRe: Question about mtimer in demo Pin
Zimmermann Stephan17-May-09 21:08
Zimmermann Stephan17-May-09 21:08 
QuestionUsing this graph for real time update in an ECG circuit? Pin
Kenneth Galea8-Apr-09 3:05
Kenneth Galea8-Apr-09 3:05 
First of all thanks for this wonderful application using C#.

I am using a USB interface module, for an ECG application, which acts like a noramal serial port in C#. I'm reading a very large number of bytes of data per second which was set using the following instruction:
serialPort.BaudRate = 38400;

Intially I was reading these numbers i.e. (from 0 to 255) in a simple textbox which was reset once it was filled with numbers. Now I was trying to use these values in your graph library. I already added it in my form (using the .dll file).
Now here are my 2 questions:
1) Can u please tell me how to append to the current array of points without losing previous data ?? since I'm having problems with appending data to the current array of points without losing previous data!!

2) I am trying to increase the length but when doing so the previous pts are being reset to 0. Is it possible to to do not reset these points?

the only reason I'm asking you this is because I want to diplay my ECG graph in real time which does not have any limits or specified lengths when recording data!!

I am using the following code:

private void DisplayText(List <byte> rxbytes)
{

// InvokeRequired required compares the thread ID of the

// calling thread to the thread ID of the creating thread.

// If these threads are different, it returns true.

if (this.textbox.InvokeRequired)
{

DisplayTextCallback d = new DisplayTextCallback(DisplayText);

this.Invoke(d, new object[] { rxbytes });

}

else
{
string[] strArr = BitConverter.ToString(rxbytes.ToArray()).Split('-');


int oldLength = plotterDisplayEx1.DataSources[0].Length;
int counter = oldLength;

plotterDisplayEx1.DataSources[0].Length = plotterDisplayEx1.DataSources[0].Length + strArr.Length;


foreach (string s in strArr)
{
int myData = int.Parse(s, System.Globalization.NumberStyles.HexNumber);

plotterDisplayEx1.DataSources[0].Samples[counter].x = counter;
plotterDisplayEx1.DataSources[0].Samples[counter].y = myData;
plotterDisplayEx1.DataSources[0].OnRenderYAxisLabel = RenderYLabel;

counter++;
}


}
}
As you can see I'm not using cpoint in this case.

Hope to hear from you and thanks again
Kenneth
AnswerRe: Using this graph for real time update in an ECG circuit? Pin
Zimmermann Stephan19-Apr-09 9:05
Zimmermann Stephan19-Apr-09 9:05 
GeneralRe: Using this graph for real time update in an ECG circuit? Pin
Mabchour 198923-Apr-12 12:37
Mabchour 198923-Apr-12 12:37 
GeneralVery good work Pin
sotirakis.net8-Mar-09 6:50
sotirakis.net8-Mar-09 6:50 
Questionpart of an application Pin
Tomáš Kučera2-Mar-09 8:20
Tomáš Kučera2-Mar-09 8:20 
AnswerRe: part of an application Pin
Zimmermann Stephan5-Mar-09 9:14
Zimmermann Stephan5-Mar-09 9:14 
Questionwhat was takin you so long? Pin
mbyamukama17-Feb-09 10:39
mbyamukama17-Feb-09 10:39 
Generalexception Pin
User 431396916-Feb-09 23:02
User 431396916-Feb-09 23:02 
GeneralRe: exception Pin
Zimmermann Stephan17-Feb-09 19:30
Zimmermann Stephan17-Feb-09 19:30 
QuestionPrint Button Pin
User 431396914-Feb-09 20:09
User 431396914-Feb-09 20:09 
AnswerRe: Print Button Pin
Zimmermann Stephan15-Feb-09 21:34
Zimmermann Stephan15-Feb-09 21:34 
GeneralRe: Print Button Pin
User 431396916-Feb-09 22:09
User 431396916-Feb-09 22:09 
GeneralGreat! Pin
S. J. Mirkamali12-Feb-09 20:24
S. J. Mirkamali12-Feb-09 20:24 
GeneralECG Hardware... Pin
Adam L. Stevenson11-Feb-09 11:25
Adam L. Stevenson11-Feb-09 11:25 
GeneralRe: ECG Hardware... Pin
Zimmermann Stephan12-Feb-09 4:55
Zimmermann Stephan12-Feb-09 4:55 
GeneralExcellent Pin
Sergey Morenko11-Feb-09 11:06
professionalSergey Morenko11-Feb-09 11:06 
QuestionReal-time update? Pin
dim.grey4-Feb-09 1:52
dim.grey4-Feb-09 1:52 
AnswerRe: Real-time update? Pin
Zimmermann Stephan4-Feb-09 4:09
Zimmermann Stephan4-Feb-09 4:09 

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.