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

A flexible charting library for .NET

0.00/5 (No votes)
6 Jun 2007 1K  
Looking for a way to draw 2D line graphs with C#? Here's yet another charting class library with a high degree of configurability, that is also easy to use.

Introduction

ZedGraph is a class library, Windows Forms UserControl, and ASP web-accessible control for creating 2D line, bar, and pie graphs of arbitrary datasets. The classes provide a high degree of flexibility - almost every aspect of the graph can be user-modified. At the same time, usage of the classes is kept simple by providing default values for all of the graph attributes. The classes include code for choosing appropriate scale ranges and step sizes based on the range of data values being plotted. Moreover, ZedGraph is compatible with .NET 2.0, and VS .NET 2005.

ZedGraph is maintained as an open-source development project on SourceForge. The site includes a project Wiki, documentation, interim (CVS) updates, and all release versions.

A set of sample graphs is also available on the Wiki, complete with source code (many of the samples include C# and VB code).

ZedGraph now supports both .NET 2.0 and .NET 1.1.

  • For .NET 2.0, use ZedGraph version 5.0+.
  • For .NET 1.1, use ZedGraph version 4.5+.

Background

There are many graphing libraries out there, but none seemed to fit what I needed. I found MSChart too quirky, and many of the other options did not have the flexibility I needed to achieve a polished look. Of course, most of the commercial packages would do the trick, but I needed something that was free. So, ZedGraph was born.

These classes will generate a variety of line, bar, or pie charts on a form or web page, given a location rectangle and some data points. ZedGraph handles 2D line/scatter graphs, horizontal/vertical bar graphs, stacked bar charts, stacked percent bar charts, error bar charts, open-high-low-close charts, Japanese candlestick charts, and pie charts - it does not yet handle 2.5D or 3D surfaces or charts. The charts can be dressed up with axis labels and titles, a legend, text labels and arrows (as shown in the example above), images, etc.

The ZedGraphWiki and the online class documentation provide lots of helpful tips and descriptions. Refer to them for more details - ZedGraph has a tremendous number of options that are not documented in this tutorial.

VB users

This article uses C# exclusively for examples, however, all the code samples for the tutorial, plus other examples are available for Visual Basic, on the ZedGraphWiki Sample Graphs section.

Using ZedGraph as a web control

ZedGraph now includes a class derived from the Control class that facilitates access from ASPX web page code. The demo project download above demonstrates this functionality. Also, the ZedGraphWiki includes detailed examples of web control usage for two rendering modes, RawImage and ImageTag.

Using ZedGraph as a UserControl

ZedGraph is accessible as a control from the control toolbox in Visual Studio .NET. To access ZedGraph, first launch Visual Studio .NET, and create a new Windows Application (Forms) project. Open the form design so that it appears in the current window. View the toolbox using the View/Toolbox menu command. Right-click inside the "General" or "Components" sub-pane of the tool box, and select the "Choose Items..." option. Click "Browse...", and navigate to the zedgraph.dll file. Once this file is added, you should see a ZedGraphControl option in the toolbox.

  1. In your project, under the Project menu, select the "Add Reference..." option. Use the Browse button to find ZedGraph.dll, and click OK. Repeat this for ZedGraph.Web.dll. This will include all the functionality of ZedGraph into your project.
  2. Add a using ZedGraph; entry to your main form code.
  3. In the form designer, drag the ZedGraphControl from the Toolbox over to the form, and drag/size it as desired. You now have a ZedGraph control in your form.
  4. All of the ZedGraph functionality is accessible through the ZedGraphControl.MasterPane property. A ZedGraphControl.GraphPane is also provided, which simply references the first GraphPane in the MasterPane list (this is explained below).
  5. In the Form designer, double click the form (not the ZedGraphControl). This will place a Form1_Load() template in your code file.
  6. Again, in the Form designer, with the form selected, go to Properties, pick the yellow lightning bolt to see the events, put the cursor in the box to the right of the Resize event, and hit Enter to add a Form1_Resize event method to your code file.
  7. Modify the Form1_Load() and Form1_Resize methods, and add the CreateGraph() and SetSize() methods as shown below (this code assumes the name of your control is 'zedGraphControl1'). <Note: A complete solution containing this code can be download from the ZedGraph Wiki:
    // Respond to the form 'Resize' event
    
    private void Form1_Resize( object sender, EventArgs e )
    {
       SetSize();
    }
    
    // SetSize() is separate from Resize() so we can 
    
    // call it independently from the Form1_Load() method
    
    // This leaves a 10 px margin around the outside of the control
    
    // Customize this to fit your needs
    
    private void SetSize()
    {
       zedGraphControl1.Location = new Point( 10, 10 );
       // Leave a small margin around the outside of the control
    
       zedGraphControl1.Size = new Size( ClientRectangle.Width - 20,
                               ClientRectangle.Height - 20 );
    }
    
    // Respond to form 'Load' event
    
    private void Form1_Load( object sender, EventArgs e )
    {
       // Setup the graph
    
       CreateGraph( zedGraphControl1 );
       // Size the control to fill the form with a margin
    
       SetSize();
    }
    
    // Build the Chart
    
    private void CreateGraph( ZedGraphControl zgc )
    {
       // get a reference to the GraphPane
    
       GraphPane myPane = zgc.GraphPane;
    
       // Set the Titles
    
       myPane.Title.Text = "My Test Graph\n(For CodeProject Sample)";
       myPane.XAxis.Title.Text = "My X Axis";
       myPane.YAxis.Title.Text = "My Y Axis";
    
       // Make up some data arrays based on the Sine function
    
       double x, y1, y2;
       PointPairList list1 = new PointPairList();
       PointPairList list2 = new PointPairList();
       for ( int i = 0; i < 36; i++ )
       {
          x = (double)i + 5;
          y1 = 1.5 + Math.Sin( (double)i * 0.2 );
          y2 = 3.0 * ( 1.5 + Math.Sin( (double)i * 0.2 ) );
          list1.Add( x, y1 );
          list2.Add( x, y2 );
       }
    
       // Generate a red curve with diamond
    
       // symbols, and "Porsche" in the legend
    
       LineItem myCurve = myPane.AddCurve( "Porsche",
             list1, Color.Red, SymbolType.Diamond );
    
       // Generate a blue curve with circle
    
       // symbols, and "Piper" in the legend
    
       LineItem myCurve2 = myPane.AddCurve( "Piper",
             list2, Color.Blue, SymbolType.Circle );
    
       // Tell ZedGraph to refigure the
    
       // axes since the data have changed
    
       zgc.AxisChange();
    }

    Note that the AxisChange() method call must be made any time you add or change the data. This tells ZedGraph to go ahead and recalculate all the axis ranges. (Note: This is all AxisChange() does - you can call it anytime you like, and it will update the axis ranges based on the current set of data points. You can also avoid calling AxisChange() if you do not want the axes rescaled.)

The above code will generate the following output:

Enhancing the graph

ZedGraph allows you to modify the graph attributes in a wide variety of ways. Each of the parts of the graph is encapsulated in a class structure, which has modifiable properties to control the output. The following are some of the classes provided in ZedGraph (Note that these classes are XML documented. See the ZedGraph documentation for details about each class.):

Class Description
MasterPane A class to manage multiple GraphPane objects, derived from PaneBase. Use of the MasterPane class is optional, as the GraphPane class can be used directly for a single pane. Also provides methods for layout, arrangement, and management of the individual GraphPane objects.
GraphPane The primary class for the graph, derived from PaneBase. Includes all other classes as properties. Also controls the pane title, the pane frame and axis frame, backgrounds, etc.
XAxis, YAxis, Y2Axis Children of the Axis class. These classes include many aspects of the axis display, including tics, grids, colors, pens, fonts, labels, and styles.
Scale A class instance maintained by the Axis class. Contains the scale range, step sizes, formats, and display options for the scale. Comes in variants for Linear, Log, Text, Date, Ordinal, Exponent, LinearAsOrdinal, and DateAsOrdinal scales.
Legend The class that describes the location, font, colors, etc., used to draw the legend.
CurveItem An abstract base class that contains data for a single curve. LineItem, BarItem, HiLowBarItem, ErrorBarItem, PieItem, StickItem, OHLCBarItem, and JapaneseCandleStickItem are all derived from this class.
CurveList A collection class to maintain a list of CurveItem objects. The order of the curves in the list controls the Z-Order for drawing. The last curve in the list will appear behind all other curves.
GraphObj An abstract base class that includes position information for a variety of supplemental graphic objects on a plot. TextObj, ImageObj, LineObj, ArrowObj, EllipseObj, BoxObj, and PolyObj are derived from GraphObj.
GraphObjList A collection class to maintain a list of GraphObj objects. The order of the objects in the list, plus a ZOrder property, control the Z-Order for drawing. The last item in the list will appear behind all other items with the same ZOrder value.
FontSpec A utility class that includes information about the font family, color, angle, size, style, frame, and background fill of the text on the graph. Each class that includes text information will contain one or more FontSpec objects to specifically describe the associated fonts.
Fill A utility class that includes characteristics of background color fills. Each object that has color fill capability will contain one or more Fill objects to specifically describe the associated color fill.
Border A utility class that includes characteristics of object borders. Each object that has border capability will contain one or more Border objects to specifically describe the associated border color and line properties.
Location A general class for handling the location of graphic objects on the plot.
PointPair A data struct that encapsulates a single pair of double values representing an (X,Y) data point. This is the internal data storage format for the value arrays in each CurveItem.
PointPairList A collection class to maintain a list of PointPair objects.
XDate This class encapsulates a single date-time value (stored as a System.Double), plus a wide array of methods to convert between XL date, Astronomical Julian Day number, Gregorian Calendar date, fractional year, etc. See the discussion of Date-Time axes below, for details.

The graph is modified by accessing properties in each of the above classes. For example, if you include the following lines of code in your CreateGraph() method, after the code samples shown previously, the plot will be modified accordingly:

// Change the color of the title

myPane.Title.FontSpec.FontColor = Color.Green;

// Add gridlines to the plot, and make them gray

myPane.XAxis.MajorGrid.IsVisible = true;
myPane.YAxis.MajorGrid.IsVisible = true;
myPane.XAxis.MajorGrid.Color = Color.LightGray;
myPane.YAxis.MajorGrid.Color = Color.LightGray;

// Move the legend location

myPane.Legend.Position = ZedGraph.LegendPos.Bottom;

// Make both curves thicker

myCurve.Line.Width = 2.0F;
myCurve2.Line.Width = 2.0F;

// Fill the area under the curves

myCurve.Line.Fill = new Fill( Color.White, Color.Red, 45F );
myCurve2.Line.Fill = new Fill( Color.White, Color.Blue, 45F );

// Increase the symbol sizes, and fill them with solid white

myCurve.Symbol.Size = 8.0F;
myCurve2.Symbol.Size = 8.0F;
myCurve.Symbol.Fill = new Fill( Color.White );
myCurve2.Symbol.Fill = new Fill( Color.White );

// Add a background gradient fill to the axis frame

myPane.Chart.Fill = new Fill( Color.White,
    Color.FromArgb( 255, 255, 210 ), -45F );

// Add a caption and an arrow

TextObj myText = new TextObj( "Interesting\nPoint", 230F, 70F );
myText.FontSpec.FontColor = Color.Red;
myText.Location.AlignH = AlignH.Center;
myText.Location.AlignV = AlignV.Top;
myPane.GraphObjList.Add( myText );
ArrowObj myArrow = new ArrowObj( Color.Red, 12F, 230F, 70F, 280F, 55F );
myPane.GraphObjList.Add( myArrow );

The final "dressed-up" graph will look like this:

Many more graph attributes can be modified, but listing them all in this article would be tedious. Please refer to the online documentation for more details. Also see the demo graphs on the Wiki for samples with code.

Interesting tidbits

Drawing line graphs is no big deal, but there are some aspects of the drawing classes that proved to be interesting. The flexibility afforded by the transform matrix of the GDI+ drawing library is very cool. This allowed the same code to be employed for drawing all three of the axes. The coordinate system is transformed to accommodate the axis location and orientation. In each case, the coordinate system is translated and rotated so that the axis is oriented along the X direction and the origin is located at the left edge of the axis when facing from the label side. A few "if" exceptions were required to account for the fact that the "left" side of the X and Y2 axes is the minimum value and the "left" side of the Y axis is the maximum value.

Choosing scales

Range and step size

ZedGraph is set up to automatically select appropriate scale minimum, maximum, and step size values based on the range of data values in the curves. Alternatively, you can manually set any or all of the values, and the scale picking logic will attempt to pick the appropriate values for the remaining parameters that are left in the automatic mode. The scale picking logic is based on the assumption that the most humanly palatable step sizes will be even divisors of 10. That is, step sizes should be 1, 2, or 5 times some power of 10. The heart of the scale picking logic is found in the CalcStepSize() method:

protected double CalcStepSize( double range, double targetSteps )
{
    // Calculate an initial guess at step size

    double tempStep = range / targetSteps;

    // Get the magnitude of the step size

    double mag = Math.Floor( Math.Log10( tempStep ) );
    double magPow = Math.Pow( (double) 10.0, mag );

    // Calculate most significant digit of the new step size

    double magMsd =  ( (int) (tempStep / magPow + .5) );

    // promote the MSD to either 1, 2, or 5

    if ( magMsd > 5.0 )
        magMsd = 10.0;
    else if ( magMsd > 2.0 )
        magMsd = 5.0;
    else if ( magMsd > 1.0 )
        magMsd = 2.0;

    return magMsd * magPow;
}

The initial guess at the step size is calculated using a default number of steps named targetSteps (this value is defaulted to 7), which is the typical number of major steps you want on an axis. The actual number of steps will usually end up being this number or more. A magnitude of the initial step size is determined using Floor( Log10( stepSize ) ). This magnitude is reduced to a most significant digit, which is then promoted to either 1, 2, or 5 to make the scale an even divisor of 10. In general, I find that this logic is very good at picking scales based on the data range.

Zero lever

Another aspect of the scale selection is whether or not the scale should be extended to include the zero value. For example, if the scale range is 1 to 10, you would typically want to go ahead and start it at zero. This is accomplished with the ZeroLever default parameter. The ZeroLever is the allowable fraction that the scale range can be extended to include the zero value. This applies below the scale range for positive scales, or above the scale range for negative scale values. As an example, if the ZeroLever is 0.25 and the data range is from 2.0 to 12.0, then the scale would be extended to a range of 0.0 to 12.0 since the zero value lies 20% outside the actual data range (which is within the 25% allowed).

Grace

Finally, ZedGraph includes "grace" properties that allow you to include extra space on the scale range before and after the minimum and maximum data values, respectively. The reason for this is to avoid having the minimum and/or maximum data points fall right on the axes. Note that the grace values apply only to the non-ordinal axis types (AxisType.Log, AxisType.Linear, and AxisType.Date). The grace values are controlled by Axis.Scale.MinGrace and Axis.Scale.MaxGrace. These values are expressed as a fraction of the total data range. For example, if MinGrace and MaxGrace are both set to 0.1, then 10% of the data range will be padded before and after the actual data range. For example, if the data values go from 50.0 to 150.0, then the data range is 100.0. 10% of this range is 10.0. Adding this before and after the range gives an effective data range of 40.0 to 160.0. The grace properties will not cause the range to extend across the zero point. That is, if both the min and max data values are zero or greater, then the axis will not be extended to negative values regardless of the grace setting. The default values for MinGrace and MaxGrace are handled by Scale.Default.MinGrace and Scale.Default.MaxGrace, respectively. Both values are set to 0.1 initially.

Axis types

Each axis now has a property called Type, which can be set to one of these eight values:

  • AxisType.Linear: This is the default type, which is just a regular Cartesian axis.
  • AxisType.Log: This is a base 10 logarithmic scale.
  • AxisType.Exponent: This is an exponential scale.
  • AxisType.Date: This is a date-time axis, in which the corresponding values are XDate types. See Date-Time Axes below.
  • AxisType.Text: This is an ordinal type, in which the data values for this axis are actually not used. All points or bars will be evenly spaced on the graph. The first point will have a value of 1.0, the second will be 2.0, etc. The actual value labels will be determined by an array of strings in Axis.TextLabels. See Text Axes below.
  • AxisType.Ordinal: This is another ordinal type, in which the data values for this axis are not used. All points or bars will be evenly spaced on the graph. The first point will have a value of 1.0, the second will be 2.0, etc. The actual value labels are just determined by the numeric ordinal values (1.0, 2.0, etc.).
  • AxisType.LinearAsOrdinal: This is another ordinal type, essentially the same as AxisType.Ordinal, except that the axis scale values will be displayed based on the data values (although the axis will still be treated as ordinal, with 1.0 for the first label, 2.0 for the second, etc.).
  • AxisType.DateAsOrdinal: This is another ordinal type, essentially the same as AxisType.Ordinal, except that the axis scale values will be displayed based on the data values as dates. This mode is a good choice if you have weekday values and you want to skip the weekends without leaving gaps in the data.

Date-time axes

ZedGraph includes the capability to handle date-time axes, e.g., the axis labels can be based on an encoded date-time value, displayed in anything from seconds to years. At the heart of the date-time axis is the XDate struct, which captures a time value, and handles conversions to/from a wide range of date-time formats. You're probably wondering why I reinvented the wheel on this, rather than just using the built-in DateTime class (or equivalent). The main reason I made my own class is because an XDate stores its date-time information as a System.Double value. Therefore, XDate values can just be stored in an ordinary array of doubles, just like any other array of data that is passed to ZedGraph. The XDate struct and format are described in detail in the Wiki. If you are using DateTime structs to store your data, you can convert directly to doubles which are compatible with ZedGraph, by using the DateTime.ToOADate() method.

To use a date-time axis in ZedGraph, you need to change to AxisType.Date as follows:

myPane.XAxis.Type = AxisType.Date;

ZedGraph will then assume that the X array double values are actually XDate values. XAxis.Scale.Min and XAxis.Scale.Max will also be XDate values, but the units of XAxis.Scale.MajorStep and XAxis.Scale.MinorStep will depend on the setting of XAxis.Scale.MajorUnit and XAxis.Scale.MinorUnit, respectively. XAxis.Scale.MajorUnit and XAxis.Scale.MinorUnit can be set to one of the following values: DateUnit.Year, DateUnit.Month, DateUnit.Day, DateUnit.Hour, DateUnit.Minute, DateUnit.Second, and DateUnit.Millisecond. Note again that XDate values are actually units of days (actually, days since a reference date). However, if XAxis.Scale.MajorUnit = DateUnit.Year, then a value of 0.25 for XAxis.Scale.MajorStep means that the step size is 1/4th of a year. One more extra property is used for the date-time axes: the Scale.Format. This is a string that defines the format of the major tic labels for the axis. For example, XAxis.Scale.Format = "dd-MMM-yy" would give labels like "12-Dec-95". A wide range of formatting options is available - see the Wiki for details. Note that the formatting is based on DateTimeFormatInfo, which is the same as the DateTime struct.

Assuming XAxis.Scale.MinAuto, XAxis.Scale.MajorStepAuto, and XAxis.Scale.MaxAuto are all set to true, ZedGraph will attempt to select an axis range that fits the span of the dates. This means that the auto-scaling feature will automatically set the following properties: Axis.Scale.Min, Axis.Scale.Max, Axis.Scale.MajorStep, Axis.Scale.MinorStep, Axis.Scale.MajorUnit, Axis.Scale.MinorUnit, and Axis.Scale.Format.

If you want to try this out, here's the code for an example graph, which has a data point for the first day of each month, for 30 months starting with 1-Jan-1995:

// Build the Chart

private void CreateGraph( ZedGraphControl zg1 )
{
   // Get a reference to the GraphPane

   GraphPane myPane = zg1.GraphPane;
   
   // Set the titles

   myPane.Title.Text = "My Test Date Graph";
   myPane.XAxis.Title.Text = "Date";
   myPane.XAxis.Title.Text = "My Y Axis";

   // Make up some random data points

   double x, y;
   PointPairList list = new PointPairList();
   for ( int i=0; i<36; i++ )
   {
      x = (double) new XDate( 1995, 5, i+11 );
      y = Math.Sin( (double) i * Math.PI / 15.0 );
      list.Add( x, y );
   }

   // Generate a red curve with diamond

   // symbols, and "My Curve" in the legend

   CurveItem myCurve = myPane.AddCurve( "My Curve",
         list, Color.Red, SymbolType.Diamond );

   // Set the XAxis to date type

   myPane.XAxis.Type = AxisType.Date;

   // Tell ZedGraph to refigure the axes since the data 

   // have changed

   zg1.AxisChange();
}

The above code generates the following graph:

In this case, ZedGraph selected a major step size of six days, and a minor step size of one day. You can easily adjust the selected format, if desired. For example, if you want to use 1 month for the minor step size, just set myPane.XAxis.Scale.MinorStep = 1.0 and myPane.XAxis.Scale.MinorUnit = DateUnit.Month just before the AxisChange() call. If you change the XAxis.Scale.MajorStep value, you will also have to manually set XAxis.Scale.MajorUnit, XAxis.Scale.MinorUnit, XAxis.Scale.MinorStep, and XAxis.Scale.Format. This is because XAxis.Scale.MajorStepAuto == false since you have chosen to manually select the step size.

Text axis

ZedGraph also handles a text axis. This is an axis in which the tic labels are arbitrary, user supplied text strings instead of value labels. Internally, a text axis is handled using ordinal values just like an ordinary axis. In this case, the first major label has a value of 1.0, the second major label has a value of 2.0, etc. It is permissible to use fractional values if you want to place points in-between the labels.

To make a text axis, you set Axis.Type = AxisType.Text. This informs ZedGraph to use the labels supplied by the user in Axis.Scale.TextLabels. The number of labels will determine the axis range. That is, 10 labels means the axis will be ranged from 1.0 to 10.0. Optionally, when you add a curve to ZedGraph, you can just skip any value array that is associated with a text axis. A default array of ordinal values will be generated. For example, if the XAxis is of type Text with 10 labels, you can add a curve, leaving the X array null, and an X array will be generated internally with values from 1.0 to 10.0. The bar chart below shows the usage of the AxisType.Text.

Bar charts

ZedGraph includes bar charting capability for vertical and horizontal bar charts, stacked bar charts, percent stacked bar charts, overlay bar charts, error bar charts, high-low bar charts, open-high-low-close bar charts, and Japanese candlestick bar charts. A bar chart is created similar to a line graph, except that you use GraphPane.AddBar(), GraphPane.AddErrorBar(), or GraphPane.AddHiLowBar() to create the bar instance. It is possible to mix bars, lines, and symbols on the same graph, by simply adding the different types.

Bars can be made horizontal or vertical, by setting the "base" axis to the X or Y axis. Under the ZedGraph terminology, the "base" axis determines the bar position, and the "value" axis determines the height of the bar.

Typically, bar charts would be created with XAxis.Type = AxisType.Text or XAxis.Type = AxisType.Ordinal (both types use ordinal values), such that the bars are drawn at integral values along the "base" axis, starting with 1 (e.g., the first bar cluster is at 1.0, the second is at 2.0, etc.). However, the ordinal axis type is not a requirement for bar charts. It is possible to create a bar chart that is not evenly spaced, by providing X values and using AxisType.Linear (in this case, you may need to use the GraphPane.ClusterScaleWidth property to tell ZedGraph how wide the bars should be. See this wiki page for details). For bar charts, the tic marks are typically between the bar clusters, which can be accomplished with the Axis.MajorTic.IsBetweenLabels property. However, this property is only applicable for AxisType.Text axes.

ZedGraph actually has six distinct bar types, any of which can be horizontal or vertical:

  • BarItem - is for regular bars, stacked bars, percent stacked bars, overlay bars, and sorted overlay bars.
  • ErrorBarItem - is for error bars, which are "I-Beam" bars with a symbol at each end, based on the upper and lower values that are user-defined.
  • HiLowBarItem - is for rectangular bars that have both upper and lower values that are user-defined.

Other "bar-like" types are also available:

  • OHLCBarItem - is for open-high-low-close stock charts, similar to an error bar, and showing the open, close, high, and low prices for the day.
  • JapaneseCandleStickItem - another stock chart type showing a narrow vertical line depicting the high-low range of prices for the day, plus a colored bar showing the open and close with a different color for rising and falling days.
  • StickItem - a chart with a narrow line at each value going back to the X axis.

The following is an example of a JapaneseCandleStickItem chart, used to show High-Low-Open-Close data for the stock market:

The orientation (horizontal or vertical) and the size of the BarItem bars are determined globally by GraphPane.BarSettings.Base and other GraphPane.BarSettings properties. Therefore, all BarItem bars will have similar properties, and the size of the bars is scaled automatically to fill the available space. In contrast, the size of ErrorBarItem bars and HiLowBarItem bars are controlled by individual properties for each bar item, e.g., ErrorBarItem.Bar.Size. These bar types are actually similar to symbols, since the bar width is specified in points (1/72 inch). A single plot can have a variety of different ErrorBarItems and HiLowBarItems with different sizes.

Two properties are included in the GraphPane class to control the gaps between BarItem bars; GraphPane.BarSettings.MinBarGap (default = 0.2) is the minimum size of the gap between each bar within a bar cluster (multiple bars that share the same X value), and GraphPane.BarSettings.MinClusterGap (default = 1.0) is the minimum size of the gap between the bar clusters. Both of these parameters are expressed as a fraction of the individual bar size, i.e., a value of 1.0 would make the gap the same size as the bars. Note that these properties apply only to BarItem bars (not ErrorBarItem or HiLowBarItem bars). A new Bar class has been added to the BarItem class to control the properties of the bars. This Bar class has properties for Fill, FrameColor, FrameWidth, and IsFramed. The following example generates a simple bar chart:

// Build the Chart

private void CreateGraph( ZedGraphControl zg1 )
{
   // get a reference to the GraphPane

   GraphPane myPane = zg1.GraphPane;
   
   // Set the Titles

   myPane.Title.Text = "My Test Bar Graph";
   myPane.XAxis.Title.Text = "Label";
   myPane.YAxis.Title.Text = "My Y Axis";
   
   // Make up some random data points

   string[] labels = { "Panther", "Lion", "Cheetah", 
                      "Cougar", "Tiger", "Leopard" };
   double[] y = { 100, 115, 75, 22, 98, 40 };
   double[] y2 = { 90, 100, 95, 35, 80, 35 };
   double[] y3 = { 80, 110, 65, 15, 54, 67 };
   double[] y4 = { 120, 125, 100, 40, 105, 75 };
   
   // Generate a red bar with "Curve 1" in the legend

   BarItem myBar = myPane.AddBar( "Curve 1", null, y, 
                                               Color.Red );
   myBar.Bar.Fill = new Fill( Color.Red, Color.White, 
                                               Color.Red );
   
   // Generate a blue bar with "Curve 2" in the legend

   myBar = myPane.AddBar( "Curve 2", null, y2, Color.Blue );
   myBar.Bar.Fill = new Fill( Color.Blue, Color.White, 
                                               Color.Blue );
   
   // Generate a green bar with "Curve 3" in the legend

   myBar = myPane.AddBar( "Curve 3", null, y3, Color.Green );
   myBar.Bar.Fill = new Fill( Color.Green, Color.White, 
                                               Color.Green );
   
   // Generate a black line with "Curve 4" in the legend

   LineItem myCurve = myPane.AddCurve( "Curve 4",
         null, y4, Color.Black, SymbolType.Circle );
   myCurve.Line.Fill = new Fill( Color.White, 
                         Color.LightSkyBlue, -45F );
   
   // Fix up the curve attributes a little

   myCurve.Symbol.Size = 8.0F;
   myCurve.Symbol.Fill = new Fill( Color.White );
   myCurve.Line.Width = 2.0F;
   
   // Draw the X tics between the labels instead of 

   // at the labels

   myPane.XAxis.MajorTic.IsBetweenLabels = true;
   
   // Set the XAxis labels

   myPane.XAxis.Scale.TextLabels = labels;
   // Set the XAxis to Text type

   myPane.XAxis.Type = AxisType.Text;
   
   // Fill the Axis and Pane backgrounds

   myPane.Chart.Fill = new Fill( Color.White,
         Color.FromArgb( 255, 255, 166), 90F );
   myPane.Fill = new Fill( Color.FromArgb( 250, 250, 255) );
   
   // Tell ZedGraph to refigure the

   // axes since the data have changed

   zg1.AxisChange();
}

The above code generates the following graph:

Bar types

ZedGraph can draw BarItem bar charts in a variety of types according to the GraphPane.BarSettings.Type property. This can be one of the following values:

BarType Description
BarType.Cluster This is the normal format in which various bar series are grouped together in clusters at each base value (like the first example chart above).
BarType.ClusterHiLow This format draws a hi-low (bars have a top and bottom that are user defined) in a cluster format, so multiple high-low bars can be grouped together at each base value.
BarType.Overlay In this format, the bars are drawn on top of each other, with the first BarItem drawn at the back, and the last BarItem drawn at the front.
BarType.SortedOverlay This is similar to Overlay, but the bars are sorted on value, and the highest value is drawn at the back, and the lowest value is drawn at the front.
BarType.Stack The bars are stacked on top of each other, accumulating in value.
BarType.PercentStack The bars are stacked on top of each other, and plotted as a percentile, with the total height always being 100%.

The following samples show horizontal and stacked bar types:

Pie charts

Pie charts are created in the normal fashion using the GraphPane.AddPieSlice(), which returns a PieItem. One PieItem is added for each slice of the pie. Note that, unlike the other CurveItem - derived classes, the PieItem does not use the PointPairList to store the data value. Since the pie has only a single data value, it is stored in PieItem.PieValue. The pie charts support text labels, a legend, color fills, etc. in the typical ZedGraph fashion.

Although it is technically possible to combine pie charts with line graphs on the same GraphPane, it is not recommended. If a particular GraphPane contains only PieItem objects, then the AxisChange() method will automatically make the axes invisible by setting the Axis.IsVisible property to false.

The following is an example of a ZedGraph pie chart:

The MasterPane

The MasterPane is designed to help facilitate the handling of multiple GraphPane objects on a page. This is done by maintaining a list of GraphPane objects, and providing utility functions for layout, rendering, mouse point location, etc. See the ZedGraphWiki for details on the MasterPane class. The following is an example of a chart based on the MasterPane class:

Pan/Zoom functions

The ZedGraphControl class now provides some interactive functionality for zooming and panning. To zoom, left click inside the AxisRect area, and drag out a new rectangle to indicate the scale region into which to zoom. To pan, either click with the middle mouse button or hold down the shift key, and left click to drag the graph around (the zoom/pan key combinations are user-modifiable as well). You can also add scrollbars to the control with IsShowHScrollBar and IsShowVScrollBar. The mouse-wheel can also be used for zooming. There is also a context menu that allows you to un-zoom and un-pan to prior states, to restore the scale to full auto mode, to show point value tool tips, and to copy the graph (bitmap form) to the clipboard. Event options are provided to allow for customization of pan, zoom, point values, etc.

The Fill class

The Fill class is an important addition to ZedGraph, so it deserves a special mention. This class handles solid, linear gradient, and texture fills for the pane background, the axis background, the legend background, all text backgrounds, the symbol fill, and for filling the area under a line. Each of these classes will have one or more Fill classes to control the fill properties (e.g., CurveItem.Line.Fill controls the filling of the area under the curve). The Fill class has a number of constructors to make it flexible and easy to use:

Fill( Color ) Makes a solid color fill with the specified color.
Fill( Color, Color ) Makes a linear gradient fill from color1 to color2, with a gradient angle of 0 degrees.
Fill( Color, Color, float angle ) Makes a linear gradient fill from color1 to color2, with a gradient angle as specified.
Fill( Image, WrapMode ) Specifies an image to be used for filling.
Fill( Brush ) Uses the specified brush directly, scaling the brush to fill the destination object bounding box.
Fill( Brush, bool isScaled ) Uses the specified brush, allowing you to disable the scaling.
Fill( Brush, AlignH, AlignV ) Uses the specified brush, with no scaling, but the source brush alignment is specified.

Internally, the Fill class keeps a FillType enumeration (the Type property) to determine what type of fill is used, as follows:

  • FillType.None: No filling will be done - the background will be transparent.
  • FillType.Solid: A solid color fill will be done using the SolidBrush class with the Fill.Color value.
  • FillType.Brush: A fancy fill will be done using the user-supplied brush specified in Fill.Brush. If Fill.Brush is null, then a LinearGradientBrush will be automatically created using Fill.Color and Color.White as the gradient colors, with a gradient angle of zero degrees.
  • FillType.GradientByX: This mode is intended to be used with Symbol.Fill. A linear gradient is maintained internally in the Fill class, and each symbol point is filled with a solid color that is taken from the internal gradient based on the X data value for that point. Essentially, you get a scatter plot in which each data point is colored according to its X value. The data ranges for color assignment are user assigned with the Fill.RangeMin and Fill.RangeMax properties.
  • FillType.GradientByY: This is the same as GradientByX except that the Y data values are used instead of X data values.
  • FillType.GradientByZ: This is the same as GradientByX except that the Z data values are used instead of X data values.
  • FillType.GradientByColorValue: This is the same as GradientByX except that the 'ColorValue' property values are used instead of X data values.

The following graph is an example of FillType.GradientByZ usage, in which seemingly scattered data gains visual coherency using the color attribute:

The following shows an example of using an image to fill in the bars of a bar chart:

Z-Order

The graphic items in the display are contained in two lists; GraphPane.CurveList and GraphPane.GraphObjList. CurveList contains all of the curves, including bars, lines, etc. GraphObjList contains text items, images, shapes, arrows, etc. In both lists, the Z-Order is controlled by the order of the objects in the list; the first objects in the list appear in front of the later objects in the list. You can modify the order of any object relative to other objects in the same collection (list), with the Move() method in the collection class. Further, the GraphObj class has a ZOrder property that controls the depth of each individual GraphObj relative to other, non-GraphObj objects. The ZOrder is an enumeration type with the following values:

ZOrder Description
ZOrder.A_InFront The topmost depth, in front of all other objects.
ZOrder.B_BehindLegend Drawn behind the Legend object.
ZOrder.C_BehindChartBorder Drawn behind the Chart frame border.
ZOrder.D_BehindAxis Drawn behind the Axis objects (behind the scale labels, etc.).
ZOrder.E_BehindCurves Drawn behind all the CurveItem objects.
ZOrder.F_BehindTitle Drawn behind the GraphPane title.
ZOrder.G_BehindAll Drawn behind the Chart rectangle fill (but still in front of the pane rectangle fill).

Utility methods

ZedGraph is being used as a class library in interactive parent applications that need information about the graph. The following are some utility methods that are worth mentioning (these are documented in the ZedGraph.chm doc file):

  • FindPane() is a method in the MasterPane class that, given a mouse point location, returns the GraphPane object under the mouse.
  • FindAxisRect() is a method in the MasterPane class that, given a mouse point location, returns the GraphPane object for the AxisRect in which the mouse point lies.
  • FindNearestObject() is a method in the GraphPane class that, given a mouse point location, returns the closest object and a corresponding index number for the object. This method may return Curve points, the GraphPane, GraphItems, axes, the legend, etc., depending on what was clicked.
  • FindNearestPoint() is a method in the GraphPane class that, given a mouse point location, returns the closest CurveItem and the index number of the closest point within that CurveItem. This routine will only consider points that are within Def.Pane.NearestTol pixels of the specified mouse point location. The default tolerance is 7 pixels.
  • ReverseTransform() is a method in the GraphPane class that, given a mouse point location, returns the X, Y, and Y2 axis values that correspond to that location.
  • GeneralTransform() is a method in the GraphPane class that, given an (X,Y) value pair, returns the corresponding (X,Y) screen pixel coordinates. The (X,Y) value pair can be expressed in a variety of coordinate systems, as follows:
    • CoordType.AxisXYScale: the coordinate values are from the X and left Y axes.
    • CoordType.AxisXY2Scale: the coordinate values are from the X and right Y (Y2) axes.
    • CoordType.ChartFraction: the coordinate values are expressed as a fraction of the total Chart.Rect width and height (0.5 is the center of the Chart.Rect, 1.1 is just to the right or just above the AxisRect).
    • CoordType.PaneFraction: the coordinate values are expressed as a fraction of the total PaneRect width and height. The value should be from 0.0 to 1.0, since values outside this range are clipped.
    • XChartFractionYPaneFraction: the X coordinate is a fraction of the total Chart.Rect, and the Y coordinate is a fraction of the total Pane.Rect.
    • XPaneFractionYChartFraction: the X coordinate is a fraction of the total Pane.Rect, and the Y coordinate is a fraction of the total Chart.Rect.
    • XScaleYChartFraction: the X coordinate is an X scale value, and the Y coordinate is fraction of the total Chart.Rect.
    • XChartFractionYScale: the X coordinate is a fraction of the total Chart.Rect, and the Y coordinate is a Y scale value.
    • XChartFractionY2Scale: the X coordinate is a fraction of the total Chart.Rect, and the Y coordinate is a Y2 scale value.
  • CalcChartRect() is a method in the GraphPane class that will calculate the chart rectangle (screen coordinates), given the current configuration. If, for some reason, you need to manually set the chart rectangle (to make it line up perfectly with other items, etc.), you can use this method to get the default rectangle, modify it as desired, then set GraphPane.Chart.Rect = yourRect. Changing the Chart.Rect will automatically set GraphPane.Chart.IsRectAuto to false so that the Chart.Rect will remain as you set it.

Acknowledgements

I would like to thank all those who have tested the code, reported issues, and contributed code to the ZedGraph project. I would especially like to acknowledge the contributions of the volunteer developers who made ZedGraph possible: Jerry R. Vos, Bob Kaye, Darren Martz, and Benjamin Mayrargue.

Latest update

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