Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

C# 2.0 Graphing Control

0.00/5 (No votes)
16 Dec 2006 3  
Generic multi-curve graph control

Sample Image - maximum width is 600 pixels

Introduction

This project creates a graphing control as a subclass of the standard .Net WinForms 2.0 PictureBox control. It integrates easily into the VS2005 Toolbox and supports multiple curves, multiple styles, legends, grids and other features. It can graph functions and relations. Graphs can be automatically scaled or you can keep complete control over scaling.

I did not provide a separate DLL, but you may easily build GraphHelp.cs and GraphBox.cs (included in the demonstration project) together for use in many projects.

All the colors, fonts and display characteristics can be set either from the code directly or in the Visual Studio designer.

The primary difference with this control is the supporting module, GraphHelp.cs. GraphHelp declares a set of non-generic and generic classes that provide data streams to the control. With these, the control can use any value convertible to numerics as an axis. The demo code shows a DateTime horizontal axis and a float vertical axis.

Using delegates and generics, GraphHelp will allow you graph virtually any data type against any other with minimal coding. If you don't want to use generics, you can use the non-generic base classes directly.

Additionally, the default behavior of GraphBox allows you to save the image to disk. It also supports translation of graph locations to human-readable values using a pop-up "tool tip" window which appears when the mouse button is held down on the graph.

Background

Much of the effort entailed in creating a graph in today's environments comes from translating back and forth from the "native" domain of your data and its types to the rigid demands of a GUI like GDI+. It can be cumbersome to create the bidirectonal (invertible) code necessary to support translation from a point on the graph to a point in data space.

GraphBox and GraphHelp work together to not only display the information but to allow you to create the data containers necessary for safe, reliable management of multiple datasets with a wide variety of data types.

Below is the basic hierarchy of the classes in GraphHelp. Remember that you can fully utilized GraphBox without instantiating any generic types; they just make things easier if you feel comfortable using them.

Here are the basics. Create a GraphDataset and associate it with a GraphBox; the graphing is automatic. Every GraphDataset has at least one GraphDatastream to graph; GraphDatastreams may be empty.

To see how everything fits together, run the program under Debug and breakpoint at RecreateDataset() in FormGraphDemo.cs. Watch how GraphBox calls CreateDesignTimeDataset() to built its test datasets.

GraphItem

This abstract class represents an ordered pair of X (horizontal) and Y (vertical) values.

GraphItemXY<TH,TV>

This is the generic version of GraphItem. Since it contains "raw" data (native times, e.g. DateTime), each relies on its associated GraphDimension to convert its value back and forth to 'float'.

GraphDimension

A GraphDimension is an abstract class containing a floating-point range ([minimum..maximum]); it can scale other floating-point values into and out of its range. It also knows how to format the values for printing and graph legends.

GraphDim<T>

This is the generic version of GraphDimension. It allows for easy declaration of dimensions for new datatypes. In GraphHelp, this class is used as the basis for the classes GraphDimInt, GraphDimFloat, GraphDimLong and GraphDimDateTime. If you want to declare your own dimensions, check out these classes.

GraphDatastream

A GraphDatastream is a collection of GraphItems representing a single curve or graph. GraphDatastreams also know how to "bucketize" or consolidate and sort dense datasets for rapid display.

GraphDataset

A GraphDataset is a collection of (at least one) GraphDatastreams. This is the class that GraphBox uses to create graphs. In other words, create a dataset and tell a GraphBox about it-- a graph is produced.

GraphDatastreamGenerator<TH,TV,TI>

This is a generic interface that, if implemented, creates type-safe GraphDataStreams. See the examples below for details

GraphDatasetXY<TH,TV>

This is the generic or type-safe version of GraphDataset. It has logic to automatically create scaled GraphDimensions appropriate for the data types you're using. If you create new GraphDimension classes you can inform GraphDatasetXY about them and it will use them when appropriate.

GraphHelper

A GraphHelper is a silent helper class that is the "bridge" between the GraphDataset classes and the GraphBox control. You never create one-- it appears when needed.

Using the code

To use the code, just extract the project from the ZIP file and build it. You must have an up-to-date version of Microsoft's Visual Studio 2005 or the .Net Framework toolset.

The demonstration code is a WinForms application that uses the built-in datasets that GraphBox creates, one of which it uses during "design time" (when you're building an app window).

The core requirement is to give a GraphBox a GraphDataset containing the data to graph. This can be done one data point at a time by adding GraphItems directly. Alternatively, you can call one of the function evaluators in class GraphDataset or GraphDatasetXY to build the set for you.

In this first example, a dataset is created for "ping" data, where each point represents an ICMP "ping" message happening at a specific date and time; each event takes a measured number of milliseconds to complete.

//

//  Create a GraphDataset with DateTime as the x-axis and a float 

//  (millisecond round-trip time) for "ping" data.  Give display names

//  to the dimensions

//

GraphDatasetXY<DateTime, float> gdb = 
    new GraphDatasetXY<DateTime, float>( "ping time", "ms round trip" );
//

//  Populate the default GraphDatastream in the GraphDataset

//

foreach (PingNote pn in pt.Notes)
{
    gdset.Add( 0, pn.dtEvent, pn.cmsRoundTrip );
}
//

//  Tell the GraphBox to graph it.

//

gboxTest.Dataset = gdset;

For another, more complex example, here is a function from GraphBox.cs. Its job is to create a sine curve represented by a set of points. It accomplishes this using a "delegate" or C# function pointer; in this case, it's called SineFunction.
//

//  Use GraphDatastream's "FromFunction" capability to run the SineFunction 

//  delegate the correct number of times.

//

private GraphDataset DesignTimeDatasetTestSine ( int cPoints ) 
{ 
    float fxMin = 0; 
    float fxMax = (float) (Math.PI * 2); 
    float fxInc = (fxMax - fxMin) / (float)(cPoints + 1); 
    GraphDatastream gstrm = GraphDatastream.FromFunction( SineFunction, 
                                                     fxMin, fxMax, fxInc ); 
    GraphDatasetXY<float,float> gds
                = new GraphDatasetXY < float,float >("Angle", "Sine",gstrm); 
    return gds; 
} 
private void SineFunction ( float fx, out float fxOut, out float fyOut ) 
{ 
    fxOut = fx; 
    fyOut = (float) Math.Sin( (double) fx ); 
} 

The key point is that a GraphBox needs a GraphDataset, which is a container for one or more GraphDatastreams. The code allows you to use almost any type of data for the streams.

A Note About Scaling

When a simple dataset is given to GraphBox, the items are iterated to determine the lower and upper bounds of the data. These limits are used by default (see AutoScaleDimensions() in GraphHelp.cs). However, you can call MeasureDimensions() and RescaleDimensions() to set your desired values directly. This allows for "zooming".

Points of Interest

This was my second big experience with combining standard C# (abstract classes, delegates, etc.) with the generic capability of C# 2.0. I found that everything generally worked as expected. I was able to work around some of the more challenging aspects of .Net generics, such as the lack of a standardized "numeric interface"; in this case, I relied on the IComparer interface.

History

December 15, 2006. First version of code and demonstration program.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here