SIGN UP MEMBER LOGIN:    
ARTICLE

Latest XY-Plot Control

Posted by Mike Gold Articles | GDI+ & Graphics February 28, 2008
Tags: C#, Chart, Free, GDI+, Graph, Plot, XY
This is an rewrite of the XY-Plot control. It fixes some issues such as the y-axis label, smoother plotting of points, axis with tick mark values that make sense, and more. (Updated for VS2008)
Reader Level:
Download Files:
 

 1.jpg

Figure 1 - Updated XY-Plot User Control
 
Introduction
This update of the xy-plot  user control adds some significant improvements and aesthetic enhancements to the graphing code.  All points are moved into a generic list instead of an object array making the code more readable.  Properties are added for a title, coloring of data and axis, the number of ticks per axis, maximum and minimum values for the x and y axis and more.  The graph is now double-buffered and anti-aliased to produce a smoother curve.  The y-axis text is now rotated the correct way.  There is an algorithm for making sure the tick values on the axis have reasonable values.  Since the graph is more or less open source, feel free to e-mail suggestions at mike@c-sharpcorner.com to improve the control.
Storing Points in Generics:
The power of templates allow us to create a collection of points for our control.  We could have used the existing value type PointF  in our List collection, but we chose to create our own reference type called PointFloat giving us more flexibility in using our coordinate information.  The collection of points declaration is shown below:

private List<PointFloat> m_points = new List<PointFloat>();

Our previous XY-Plot control used an ArrayList which stores objects rather than the type in which we are interested.
private ArrayList  m_points = new ArrayList();

The difference between using the generic collection List<PointFloat> and the object collection ArrayList is that every time we want a PointFloat object out of an ArrayList we have to unbox it. This is an extra step that the program needs to take.  In order to understand, let's look at an example:
Using an ArrayList:
PointFloat nextPoint = (PointFloat)m_points[i];  // (1) get the point at index i and (2) unbox it

Using a generic List<PointFloat>
PointFloat nextPoint = m_points[i];   // get the point at index i

As you can see, the generic list allows us to avoid the object class layer and go directly to manipulating the PointFloat object.
Algorithm for Creating Tics on the Axis
The axis are marked with values indicating a measure of where the point is located.  In a way, an axis is like a ruler, so it should have value increments that "make sense" like a ruler.  That means that the values should have precision values of 1, 2, or 5 in the last decimal spot because these values will eventually increment to a uniform unit.  The algorithm below is an attempt to make this happen.  First it calculates a raw increment based on the maximum and minimum axis values.  Then it separates out the value to the right of the decimal.  It then finds the most logical precision number that comes closest to the raw precision value and substitutes this value into the increment.
Listing 1 - Calculating the Closest logical increment for the Graph
/// <summary>
///
Calculate an increment value that makes sense
/// </summary>
///
<param name="min"></param>
///
<param name="max"></param>
///
<returns></returns>
private float CalculateIncrement(float min, float max)
{
 
// figure out a raw increment value to come close to
 
float increment = (max - min) /TicksPerAxis ;

 
// get the precision value on the right side of the decimal
  
float precision = increment - ((int) increment);
// round this value to a whole number, and track
// it's multiple
 
float multiple = 1;
 
while (precision < 1f)
   {
  
  precision *= 10;
    
multiple *= 10;
   }
// these are the precision values we will allow on
// the right side of the decimal for our graph

 
float[] allowablePrecisions = new float[]{1, 2, 5};

  // find the closest precision (1,2, or 5) to the precision
  // value of our raw increment.

  float minimumPrecision = allowablePrecisions[0];
  foreach (float nextPrecision in allowablePrecisions)
   {
  
 if (Math.Abs(precision - minimumPrecision) >    Math.Abs(precision - nextPrecision))
      {
         minimumPrecision = nextPrecision;
      }
  }
// determine a new precision value
precision = minimumPrecision/multiple;
  // calculate the new increment by adding the precision
  // to the integer value
 
increment = ((int) increment) + precision;

 return increment;
}

Smoothing the Data Line
The Graphics class comes with a SmoothingMode property that allows you to antialias your graphics line.   There are also HighQuality and HighSpeed modes, but I think HighQuality is the same as Anti-alias mode and HighSpeed is the same as None.
SmoothingMode Description
Antialias Provides antialias rendering
None No Antialiasing

Table 1 - Smoothing Mode Enumeration Values

What is antialiasing?  Anti-aliasing is a way to fool the eye into thinking a line is smooth instead of having jagged edges.  The way anti-aliasing fools the eye is by putting shading around the jagged edges.  The shading effect makes the jags look slightly fuzzy giving the illusion that the lines and curves are more "smooth".
Smoothing is accomplished in the Paint Event handler as shown in listing 2:
Listing 2 - Activating Smoothing in the Graphics Object

protected override void OnPaint( PaintEventArgs pe )
{
 
Graphics g = pe.Graphics;
 
g.SmoothingMode = SmoothingMode.AntiAlias// smooth all lines and curves painted in this graphics object
...

Rotating the Text Along the Y-Axis
Originally I tried rotating the text by calling DrawString with a property in StringFormatFlags called DirectiionVertical as shown in listing 3:
Listing 3 - Rotating Text for the Y-Axis Label  using StringFormat in the Paint Event Handler
StringFormat theFormat = new StringFormat(StringFormatFlags.DirectionVertical);
g.DrawString(m_LabelY, GraphFont, Brushes.Blue, new PointF(ClientRectangle.Left , ClientRectangle.Top + 100), theFormat);

Unfortunately, the text showed up reflected flipped from the way I wanted it to appear on the graph.  Instead of using StringFormat, I resorted to drawing the string into a bitmap in memory,  rotating the graphic surface of the bitmap, and drawing the bitmap onto the Graphics object of the canvas as shown in listing 4:
Listing 4 - Rotating the y-axis label and drawing it on the Graph
SizeF labelSize = g.MeasureString(m_LabelY, GraphFont);
// create a bitmap with the dimensions of the y-axis label
Bitmap stringmap = new Bitmap((int)labelSize.Height + 1, (int)labelSize.Width + 1);
// get a graphics object that will draw to the y-axis bitmap
Graphics gbitmap = Graphics.FromImage(stringmap);
// translate the graphics surface width of the the label text
gbitmap.TranslateTransform(0, labelSize.Width);
// rotate the graphics surface back 90 degrees
gbitmap.RotateTransform(-90);
// draw the string into the transformed graphics surface
gbitmap.DrawString(m_LabelY, GraphFont, Brushes.Blue, new PointF(0 , 0), new StringFormat(StringFormatFlags.NoClip));
// draw the bitmap containing the rotated string to the graph
g.DrawImage(stringmap, (float)ClientRectangle.Left, (float)ClientRectangle.Top + 100);

Using the Control
Below is an example of using the control to draw a graph of a spiral.  The first line of code resets the points in the graph.  Then the next set of lines of code set up the properties and parameters such as the title of the graph, the axis titles and the max and min values of the axis.  Finally we loop through the radians of a circle and calculate the x and y coordinates to add to the graph as shown in listing 5.
Listing 5 - Drawing the Spiral in the XY-Plot Control
private void btnSpiral_Click(object sender, System.EventArgs e)
{
 
// clear the graph
 
xyGraphControl1.Reset();
// set up the graph parameters and labels
 xyGraphControl1.LabelX = "Sine of Angle";
 
xyGraphControl1.LabelY = "Cosine of Angle";
 
xyGraphControl1.XMinimum = 0f;
 
xyGraphControl1.XMaximum = 3f;
 
xyGraphControl1.YMinimum = 0f;
 
xyGraphControl1.YMaximum = 3f;
 
xyGraphControl1.Title = "Spiral";
 
// add the data into the graph
 
for (float i = 0; i < 6.28 * 7; i += 6.28f/500f)
  {
    
xyGraphControl1.AddPoint((float)Math.Sin((double)i) *(1- i/50.0f) + 1.5f, (float)Math.Cos((double)i)* (1 - i/50.0f) + 1.5f);
  }
// force the graph to redraw
xyGraphControl1.Invalidate();
}

Conclusion
You are free to use the xy plot control in any of your projects to display your data.  This control does not have some of the popular charting effects such as zooming, scrolling, real-time effects and more, but you are free to enhance it to drop in these features.  The user control also will not work on the web.  If you want a more professional charting tool with some of these bonus features you should check out Dundas Chart which is bound to give you what you need without the headache of trying to develop these complex features.  Anyway, if you are carefully plotting your next project, consider doing so with the help of C# and GDI+.

Login to add your contents and source code to this article
share this article :
post comment
 

Hey Josh,

That's cool!  Let me know the link, and I'll take a look when I get a chance.

best,

-Mike

Posted by Mike Gold Jul 07, 2010

Well done. There are changes I needed to make though (especially because I started with the older version). One thing I needed was for the control to update itself, when properties change. If you are interested, I'm working on an enhanced version of this control for my project "LUA Calculator" (on SourceForge). I've added a variety of different features, such as plotting to a graph-paper like background, and plot "line styles". I'm using gnuplot for ideas on adding features.

Also PointFloat class could be replaced by the .NET PointF class, and the adding of (0, 0) point in the constructor, seems "point-less". :)

Posted by Josh Guyette Jun 22, 2010
Nevron Gauge for SharePoint
Become a Sponsor
PREMIUM SPONSORS
  • Finally – a virtual platform that delivers next-generation Windows Server 2008 Hyper-V virtualization technology from a managed hosting partner you can truly depend on. Visit www.maximumasp.com/max for a FREE 30 day trial. Hurry offer ends soon. Climb aboard the MaxV platform and take advantage of High Availability, Intelligent Monitoring, Recurrent Backups, and Scalability – with no hassle or hidden fees. As a managed hosting partner focused solely on Microsoft technologies since 2000, MaximumASP is uniquely qualified to provide the superior support that our business is built on. Unparalleled expertise with Microsoft technologies lead to working directly with Microsoft as first to offer IIS 7 and SQL 2008 betas in a hosted environment; partnering in the Go Live Program for Hyper-V; and product co-launches built on WS 2008 with Hyper-V technology.
    Get 2 Months Free of ASP.NET Hosting for Only $4.95/month! Receive FREE MS SQL and MySQL Databases Including ASP.NET 4/3.5, MVC 3.0, Silverlight 4, Windows 2008/IIS 7.0 Plus FREE IIS 7 Modules. Host UNLIMITED ASP.NET Web Sites - Click Here!
6 Months Free & No Setup Fees ASP.NET Hosting!
Become a Sponsor