Skip to main content
Email Password   helpLost your password?

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.

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:

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:

Other "bar-like" types are also available:

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:

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):

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

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralLicense agreement?
jtwomley29
10hrs 32mins ago 
If I want to use this on my webpage what are the restrictions?
Please let me know.
Sign In·View Thread·PermaLink
GeneralLine Graph starting in (0,0) even thought my values don't
romias
18hrs 20mins ago 
I'm using a Linear XAxis and the provided x,y values never start in 0,0... but my chart always do. I mean, if I draw just 1 point... I suppose I would get a point in the graph, but instead it draws a line from (0,0) to my point. It is not a problem with the MIN scale property. Do you have any ideas what can I be doing wrong? Thanks!
Sign In·View Thread·PermaLink
QuestionIs it possible to change the Base in AxisType.Log?
stefan++
23:38 13 Dec '09  
Hello,

so far i saw (and googled), the logarithmical axistype is fixed to base 10, is there any implementation to change the base?

Greets, Stefan
Sign In·View Thread·PermaLink
GeneralHow to make two graphpanes auto scaling Y axis while scrolling
truebilly
7:38 7 Dec '09  
I am writing a stock chart graph, and I have two graphpanes stacked with the same X axis(time )
The question is, although I can make the lower graphpane autoscale while scrolling, I cannot make the upper pane's Y axis auto scale to the data displayed.
I've tried to use IsBoundedRanges and catching ScrollProgress event, but I could only get the lower graphpane autoscale.
Anyone has the same problem?
Sign In·View Thread·PermaLink
Generalhow to show value and hold it??
storm1111
8:11 6 Dec '09  
hi John,

i have some question about the zedgraph. Is it possible to hold a value of a point in the graph by simple click on the point???
Sign In·View Thread·PermaLink1.00/5
Questionfrom xyscale coordinate to masterPane coords
LucaRamundo
2:55 3 Dec '09  
Is there a way to get the coordinates of a point (expressed in XY scales of a graphPane) represented in the graph in the MASTER pane corresponding coordinates? P.s. I can not get through a mathematical proportions
Sign In·View Thread·PermaLink
Questioncustom scale: a linear half and a exponentialt half
LucaRamundo
0:06 22 Nov '09  
Hello to all .. I need your help to solve my problem! I need to create a single graph pane with linear scale, logarithmic or mixed; in this last case, a mid-scale on x is linear and the remainder is exponential. Same goes for the axis of y. The user should be able to move at will the dividing point of the type of scale at runtime. I saw that this thing is not provided in ZedGraph .. how can I do? who can suggest something? It's very important for me.. thanks a lot...

p.s. Is vey difficult add a class to do this?
Sign In·View Thread·PermaLink
AnswerRe: custom scale: a linear half and a exponentialt half
stefan++
23:49 13 Dec '09  
Just have a look at this Demo http://www.zedgraph.org/wiki/index.php?title=Multi-Y_Demo[^]

It's possible to show different axistype in one pane by adding an other axis. Just connect the linear graph with the one and exponential graph with the other axis. This should work for you.

Greets, Stefan
Sign In·View Thread·PermaLink
GeneralDynamic xaxis with current date entered in a rolling pairlist
Peter Kilby
8:28 17 Nov '09  
I have just started using zedgraph and i am trying to generate a rolling graph of a set of 3 phase currents. I want to log the value of the three currents (as int)and add the current time to the point data.
I have got the curve ploting but it is not adding the date text to the Xaxis scale, yet if you hover on a curve point it's data is shown correctly.

Please help if possible.
Im my code below Phase1,Phase2,Phase3 are values of the phase currents.
Dim Phase1list As New RollingPointPairList(2976)
Dim Phase2list As New RollingPointPairList(2976)
Dim Phase3list As New RollingPointPairList(2976)
Dim TimeRange As Double
ans the above are declared as globle for use elsewhere.


Thank You

'Start of graph code
Dim MinTime, MaxTime, RangeAsseX As Double

Private Sub CreateGraph2(ByVal zgc As ZedGraphControl)

Dim myPane As GraphPane = ZedGraphControl1.GraphPane
myPane.Title.Text = "Current log resolution " & TypeOfLogging
myPane.XAxis.Title.Text = "Time/Date"
myPane.YAxis.Title.Text = "Amps"
myPane.XAxis.Type = AxisType.Date
myPane.XAxis.Scale.Format = "ss" 'Format
'myPane.XAxis.Scale.FontSpec.Angle = 65

' Initially,curves are added with no data points (list is empty)
' Colours are as required, and there will be no symbols
Dim curve1 As LineItem = myPane.AddCurve("Phase 1", Phase1list, Color.Gray, SymbolType.None)
curve1.Line.Width = 2
Dim curve2 As LineItem = myPane.AddCurve("Phase 2", Phase2list, Color.Black, SymbolType.None)
curve2.Line.Width = 2
Dim curve3 As LineItem = myPane.AddCurve("Phase 3", Phase3list, Color.Brown, SymbolType.None)
curve3.Line.Width = 2

' For testing set range to 30 sec's
TimeRange = 30

DateTimeCurrent = System.DateTime.Now
MinTime = New XDate(DateTimeCurrent)
MaxTime = New XDate(DateTimeCurrent.AddSeconds(TimeRange))
RangeAsseX = MaxTime - MinTime

' Manually control the X axis range so it scrolls continuously
' instead of discrete step-sized jumps

myPane.XAxis.Scale.Min = MinTime
myPane.XAxis.Scale.Max = MaxTime
myPane.XAxis.Scale.MinorStep = RangeAsseX / TimeRange
myPane.XAxis.Scale.MajorStep = RangeAsseX / (TimeRange / 5)
myPane.XAxis.Scale.MajorUnit = DateUnit.Second 'MajorUnit
myPane.XAxis.Scale.MinorUnit = DateUnit.Second 'MajorUnit


' Scale the axes
ZedGraphControl1.AxisChange()

End Sub



Private Sub tmrChart2Update_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrChart2Update.Tick

' Make sure that the curvelist has at least one curve
If ZedGraphControl1.GraphPane.CurveList.Count <= 0 Then Return

DateTimeCurrent = System.DateTime.Now
Dim time As Double = New XDate(DateTimeCurrent)

' Check the data
Phase1list.Add(time, Phase1)
Phase2list.Add(time, Phase2)
Phase3list.Add(time, Phase3)

' Keep the X scale at a rolling interval, with one
' major step between the max X value and the end of the axis

Dim xScale As Scale = ZedGraphControl1.GraphPane.XAxis.Scale
If time > xScale.Max - xScale.MajorStep Then
xScale.Max = time + xScale.MajorStep
xScale.Min = xScale.Max - RangeAsseX '30.0
End If

' Make sure the Y axis is rescaled to accommodate actual data
ZedGraphControl1.AxisChange()
' Force a redraw
ZedGraphControl1.Invalidate()

End Sub


Private Sub frmChart2_Resize(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Resize
SetSize()

End Sub


Private Sub SetSize()
ZedGraphControl1.Location = New Point(10, 10)
' Leave a small margin around the outside of the control and space for buttons below
ZedGraphControl1.Size = New Size(ClientRectangle.Width - 20, ClientRectangle.Height - 65)

End Sub

Cheers Pete
Sign In·View Thread·PermaLink
QuestionHiLowBarItems of varying width? Help please
Juraj Huska
6:40 15 Nov '09  
Hello, I have been trying to set up highlow bars of varying width in the same pane but it always seems to come down to one fixed width for all bars. I looked at previous posts regarding this and even the actual source code (the latest version of the library-Version 5.1.4) and seems to me that bar widths are determined by bar type alone, not for each bar individually. One of the posts claims you can do "HiLowBarItem.Size = ..." but this property doesn't seem to be available in the library. Please help me with how to code bars of varying width in the same graph (bar chart). Ideally, I would like to be able to code up graphs that look like this: http://www.elitetrader.com/vb/showthread.php?s=f687754a2e29bff91c16f522e3e88578&threadid=127338&perpage=6&pagenumber=3[^]

(scroll down and see picture posted by "bthomas").
Sign In·View Thread·PermaLink
GeneralLarge among of data
newspicy
2:23 13 Nov '09  
Hi!

I try to find a way to handle millions! of datapoints for this zedframework.

The problem seems maby to be a question how to filter the data and how to store it in the memory.

Could Hash table or B-tree be used on this?

Does anyone has some better idea how to handle large data in this charts?

Have already try FilterPointList, but that not seems to work on for this kind of task
im pretty sure it could be more efficient.

Kind of a frustated situation

Most grateful for all answer
B
Sign In·View Thread·PermaLink2.00/5
Generalchanging line colour if line is above otehr line
yemo110
12:35 9 Nov '09  
hi,

I'm having a problem and i was wondering if anyone can help me. What my problem is that i have 3 lines in my graph(green, orange, blue) and what i want to do is that if the blue line is above the orange line then i want it to be red only for the part that is above the orange line else stay blue...is it making sense or have i lost you? lol! oh and i might have to mention that the coordinates for the blue line are read from an XML file and the coordinates for the orange line are read from a database.
I have looked into the wiki and i cant seem to find anything that would help me.

Thanks in advance :D
Sign In·View Thread·PermaLink
GeneralRe: changing line colour if line is above otehr line
stefan++
0:01 14 Dec '09  
Just have a look at this demo http://zedgraph.org/wiki/index.php?title=Use_a_gradient_fill_to_individually_color_line_segments[^]

Greets, Stefan
Sign In·View Thread·PermaLink
GeneralI need some help!!!
zonaari
15:39 2 Nov '09  
Does anyone know how to set x axis value to more than Zero whenever zooming, panning and so on?
Name of X axis value is distance which should not have negative value, so the minimum value of x axis should be ZERO~
How can I do that?
Sign In·View Thread·PermaLink1.00/5
GeneralProblem on display certain type of chart
phoebuspf
10:28 2 Nov '09  
Hi,all. I'm now using this.zedGraphControl1.GraphPane.XAxis.Type = ZedGraph.AxisType.DateAsOrdinal; to set the graph.
But I find bugs. When I use ordinary XAxis,I can add two points as a curve correctly. But when I draw it under the AxisType of DateAsOrdinal. The two points just stay at the left of the pane but not the place where it should be.Confused

Any one help me? Great thanks.
Sign In·View Thread·PermaLink2.00/5
GeneralDynamically change the number of curves.
deffer81
9:34 1 Nov '09  
Hi,
Thank you for a great tool.
Sadly, I am having a problem figuring out how to dynamically change the number of Curves or Lines.

I need to be able to set the number of Lines according to how many files with data it reads in.
If I have two files that contains a number of data and I want One Line represent file(1) and another to represent file(2).

I want that to be dynamically because the number of files will change.

I hope somebody can help me figure this out.

John.
Sign In·View Thread·PermaLink1.00/5
GeneralThank you for providing ZedGraph
zonaari
21:03 29 Oct '09  
I think ZedGraph is easy to use when I need graph stuff.
By the way, I am from Seoul. I have been working as S/W development team using C# Window Forms in Daejeon for more than 3 years.
Have you visited Seoul? I hope you will visit in the future.
Anyway thank you again.
Sign In·View Thread·PermaLink
Questionhow can I do that each graph line should be drawn on each graph every one second?
zonaari
21:13 28 Oct '09  
First of all, thanks again.
May I ask you some question?
Like Animation, I saved several text data files in such a List<> to draw Line Graph.
Then, each of those files should be drawn on each graph every one second.
In other words, one item should be shown and another item should be shown after one second and so on.
But right now all files are drawn on one graph.
So how can I do that each file should be drawn on each graph?
I am using two forms: one is GraphForm including ZedGraph control and the other is InputFolderForm for selecting folder and text data files.

GraphPane aniPane = null;//Animation GraphPane
PointPairList pointList = null;
List<PointPairList> fileList = null;

private int index; //index
private bool m_Auto; //Animation Start/Stop
private Timer time; //Timer

//Ready Animation
private void miAniReady_Click(object sender, EventArgs e)
{
folderForm = new InputFolderForm();

if (folderForm.ShowDialog() == DialogResult.OK)
ReadyAnimation(m_ucGraph, folderForm);

m_ucGraph.Invalidate();
}

//Ready Animation
private void ReadyAnimation(ZedGraphControl m_ucGraph, InputFolderForm folderForm)
{
fileList = new List<PointPairList>();
aniPane = m_ucGraph.GraphPane;

if (fileList != null)
fileList.Clear();

for (int i = 0; i < folderForm.Indices.Count; i++)
{
double xCount = 0.0;
double yData;
pointList = new PointPairList();

foreach (string strData in System.IO.File.ReadAllLines(StrFilesFullName[i]))
{
if (strData != "")
{
string[] tmp = strData.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
yData = Double.Parse(tmp[2]);
pointList.Add(xCount, yData);
xCount += 0.5f;
}
}

string fileName = System.IO.Path.GetFileNameWithoutExtension(DTSDTSS.StrFiles[i]);
string caption = DateTime.ParseExact(fileName, "yyyyMMddHHmm", null).ToString("yyyy\\/MM\\/dd HH:mm");
LineItem myCurve = aniPane.AddCurve(caption, pointList, Color.Red, SymbolType.None);

fileList.Add(pointList);
}

fileList[0] = pointList;
m_ucGraph.AxisChange();
}

#region [Tick Event]
//Tick Event
private void Timer_Tick(object obj, EventArgs ea)
{
index++;
if (index > StrFiles.Length - 1) index = 0;
pointList = fileList[index];
m_ucGraph.Refresh();
}
#endregion
}



Sign In·View Thread·PermaLink
AnswerIt worked out after I changed some.
zonaari
20:55 29 Oct '09  
Here is my revised code.

public partial class GraphForm : Form
{
InputFolderForm folderForm = null;
GraphPane aniPane = null; //Animation GraphPane
PointPairList pointList = null; //(x,y) points list
List<PointPairList> fileList = null;//several files' list
LineItem myCurve = null;

private int index;
private bool m_Auto;
private Timer time;

public GraphForm()
{
InitializeComponent();

this.m_Auto = true;
this.time = new Timer();
this.time.Interval = 1000;
this.time.Tick += new EventHandler(Timer_Tick);
}

private void GraphForm_Resize(object sender, EventArgs e)
{
m_ucGraph.Refresh();
}

//Ready Animation
private void miAniReady_Click(object sender, EventArgs e)
{
folderForm = new InputFolderForm();

if (folderForm.ShowDialog() == DialogResult.OK)
{
ReadyAnimation(folderForm);
}

m_ucGraph.Invalidate();
}

//Ready Animation
private void ReadyAnimation(InputFolderForm folderForm)
{
aniPane = m_ucGraph.GraphPane;
fileList = new List<PointPairList>();

if (aniPane != null)
aniPane.CurveList.Clear();

for (int i = 0; i < folderForm.Indices.Count; i++)
{
double xCount = 0.0;
double yData;
pointList = new PointPairList();

if (pointList != null)
pointList.Clear();

foreach (string strData in System.IO.File.ReadAllLines(DTSDTSS.StrFilesFullName[i]))
{
if (strData != "")
{
string[] tmp = strData.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
yData = Double.Parse(tmp[2]);
pointList.Add(xCount, yData);
xCount += 0.5f;
}
}
fileList.Add(pointList);
}

DisplayLegend(0);
pointList = fileList[0];

m_ucGraph.Refresh();
m_ucGraph.AxisChange();
}

//Draw Legend
public void DisplayLegend(int index)
{
string fileName = System.IO.Path.GetFileNameWithoutExtension(DTSDTSS.StrFiles[index]);
string caption = DateTime.ParseExact(fileName, "yyyyMMddHHmm", null).ToString("yyyy\\/MM\\/dd HH:mm");
myCurve = aniPane.AddCurve(caption, pointList, Color.Red, SymbolType.None);
}

#region [Tick Event]
//Tick Event
private void Timer_Tick(object obj, EventArgs ea)
{
try
{
if (aniPane != null)
aniPane.CurveList.Clear();//should clear curvelist

index++;
if (index > DTSDTSS.StrFiles.Length - 1) index = 0;

DisplayLegend(index);
pointList = fileList[index];
m_ucGraph.AxisChange();
m_ucGraph.Refresh();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
return;
}
}
#endregion
}

Sign In·View Thread·PermaLink
QuestionI want X axis value to be more than 0 all the time
zonaari
17:15 28 Oct '09  
Hello. This is Sanghoon. Thank you for providing me ZedGraph.
By the way, I want x axis value to be more than zero all the time because x axis value shows distance. In other words, below zero should not appear on my graph. How can I do that?
Sign In·View Thread·PermaLink
AnswerRe: I want X axis value to be more than 0 all the time
romias
18hrs 51mins ago 
You can set:
myPane.XAxis.Scale.Min = 0

Sign In·View Thread·PermaLink
GeneralDelete CurveItem according to a check
manzanita7
6:28 16 Oct '09  
Hi Everybody,

My problem is this:

I have several check boxes, which, once you have checked one of them, a curveitem appears in the graph. What I want to do is deleting the corresponding curveItem when I uncheck its check box.

I don't know if I explain myself. To sum up, I want a CurveItem to appear when I check a checkbox, and I want the curveitem to disappear when I uncheck the check box.

Thank you!
Sign In·View Thread·PermaLink
GeneralRe: Delete CurveItem according to a check
shulmanv
2:48 20 Oct '09  
So why not just on check create new curve and add it to curves list and on un-check search by Text of the un-checked item the curve in the curves list and remove it from the list... Don't forget to refresh the graph afterwards.
Sign In·View Thread·PermaLink2.00/5
GeneralRe: Delete CurveItem according to a check
manzanita7
4:39 20 Oct '09  
In fact, that's what I'm doing, but I don't know the command to delete the associated line.

Thank you for your answer! Smile
Sign In·View Thread·PermaLink
GeneralRe: Delete CurveItem according to a check
manzanita7
5:46 21 Oct '09  
Ok, I got it! Big Grin

Thank you very much!!
Sign In·View Thread·PermaLink


Last Updated 6 Jun 2007 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2009