Plunging into .NET Development

Weblog Pieter Gheysens
Microsoft .NET Development - C# - Enterprise Library - Visual Studio 2005 Team System - Compuware DevPartner - ...
 


Sunday, February 20

Line Chart Component [GDI+]

Well, this is how it looks like ...



It is not completely finished but it has already some functionality I would like to show you. As you can see, you can set all margins and the colors of the chart-components at run-time.

I will explain some functionality in more detail [note that source-code is better viewed on my blog-site instead of in a rss-reader] :
  • Curve class
    A curve class is created that will contain the X-values (DateTime array) and Y-values (array for doubles) for the line-chart. In the constructor the maximum and minimum are set.

    using System;

    using System.Drawing;

     

    namespace GraphComponent

    {

        public class Curve

        {

            #region Variables

            protected DateTime[] xValues;

            protected double[] yValues;

            private Color color;

            private System.Drawing.Drawing2D.DashStyle dashStyle;

            private double minimum = 0;

            private double maximum = 0;

            #endregion

     

            public Curve(DateTime[] newXValues, double[] newYValues)

            {

                this.xValues = newXValues;

                this.yValues = newYValues;

     

                //Max and Min

                this.setMaximum();

                this.setMinimum();

            }

     

            private void setMaximum()

            {

                for (int i=0; i < this.yValues.Length; i++)

                {

                    if (this.yValues[i] > this.maximum)

                    {

                        this.maximum = this.yValues[i];

                    }

                }

            }

     

            private void setMinimum()

            {

                for (int i=0; i < this.yValues.Length; i++)

                {

                    if (this.yValues[i] < this.maximum)

                    {

                        this.minimum = this.yValues[i];

                    }

                }

            }

        }

    }



  • Usercontrol Graph
    A usercontrol is created for displaying the graph-object. In the constructor of the usercontrol, the curve object is used as a parameter. In the paint-event of the graph-component, the width and height are set and the PaintEventArgs are passed to draw the graph.

            private void graph_Paint(object sender, System.Windows.Forms.PaintEventArgs e)

            {

                this.graph.UserControlWidth = ((GraphComponent.Graph)sender).Width;

                this.graph.UserControlHeight = ((GraphComponent.Graph)sender).Height;

     

                this.graph.DrawGraph(e);

            }


    The method DrawGraph is the most important method of the usercontrol because it's responsible for drawing all the chart-components on the display surface. The method is shown below without some regions that will be explained later ...

            public void DrawGraph(PaintEventArgs e)

            {

                //Variables - region left out

     

                //no drawing if no curve available

                if (this.currentCurve == null)

                    return;

     

                this.stepYValue = this.setScaling(this.currentCurve.Maximum);

     

                //Set Graphics-object

                Graphics grfx = e.Graphics;

     

                //Define maxWidth and maxHeight of graph

                this.maxWidth = this.userControlWidth - marginLeft - marginRight;

                this.maxHeight = this.userControlHeight - marginTop - marginBottom;

     

                //Set Font and Brush

                Font usedFont = new Font("Arial", 8.25F, FontStyle.Bold, GraphicsUnit.Point);

     

                //Main Area - region left out

     

                //Scales - region left out

     

                //Labels - region left out

     

                //Curves - region left out

            }


    The Graphics object is set via the PaintEventArgs and provides methods for drawing objects to the display surface. All default margins and colors are set in the constructor of the usercontrol and are read from the config-file. For filling graphical shapes, the SolidBrush class is used and this class inherits from the Brush class.

            public Graph(Curve newCurve)

            {

                InitializeComponent();   

     

                //set marges

                this.marginTop = Convert.ToInt32(ConfigurationSettings.AppSettings["marginTop"].ToString());

                this.marginBottom = Convert.ToInt32(ConfigurationSettings.AppSettings["marginBottom"].ToString());

                this.marginLeft = Convert.ToInt32(ConfigurationSettings.AppSettings["marginLeft"].ToString());

                this.marginRight = Convert.ToInt32(ConfigurationSettings.AppSettings["marginRight"].ToString());

     

                //set number of points on Y-axis

                this.numberOfYPoints = Convert.ToInt32(ConfigurationSettings.AppSettings["NumberOfYPoints"].ToString());

     

                this.currentCurve = newCurve;

     

                //Set default color and style

                this.currentCurve.Color = Color.FromName(ConfigurationSettings.AppSettings["LineColor"].ToString());

                this.currentCurve.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;

     

                //Set brushes

                this.brushMainArea = new SolidBrush(Color.FromName(ConfigurationSettings.AppSettings["BrushMainArea"].ToString()));

                this.brushChartArea = new SolidBrush(Color.FromName(ConfigurationSettings.AppSettings["BrushGraphArea"].ToString()));

                this.brushGrid = new SolidBrush(Color.FromName(ConfigurationSettings.AppSettings["GridColor"].ToString()));   

            }



    More details follow now on the regions that were left out in the DrawGraph method.
    • Main Area
      The main area consists of two rectangles. The second rectangle is part of the first one. The first rectangle is drawn from (0, 0) while the second one has to incorporate the specific margins. Both rectangles are filled with a brushColor.

                  #region Main Area

                  //Fill usercontrol

                  grfx.FillRectangle(this.brushMainArea, 0, 0, this.userControlWidth, this.userControlHeight);

       

                  //rectangle for graph

                  rectangle = new Rectangle(marginLeft, marginTop, this.maxWidth, this.maxHeight);

                  grfx.DrawRectangle(SystemPens.WindowText, rectangle);

                  grfx.FillRectangle(this.brushChartArea, rectangle);

                  #endregion


    • Scales/Grid
      The grid is created by drawing the X- and Y-lines. The Pen object with a certain color and a dashstyle is used for doing this task. A line is drawn from point (x1, y1) to point (x2, y2). The used margins, number of x-points, number of y-points, the width and height of the control stand central in the calculations of the different points.

                  #region Scales

                  Pen cPen = new Pen(this.brushGrid);

                  cPen.DashStyle = DashStyle.Dash;

                  int tempX1, tempY1, tempX2, tempY2;

       

                  for (int i=0; i < this.currentCurve.XValues.Length - 1; i++)

                  {

                      tempX1 = marginLeft + (int) (((this.maxWidth) / (this.currentCurve.XValues.Length)) * (i + 1));

                      tempY1 = marginTop;

                      tempX2 = marginLeft + (int) (((this.maxWidth) / (this.currentCurve.XValues.Length)) * (i + 1));

                      tempY2 = marginTop + this.maxHeight;

       

                      //Draw line for XValues

                      grfx.DrawLine(cPen, tempX1, tempY1, tempX2, tempY2);

                  }

       

                  for (int i=1; i < this.numberOfYPoints; i++)

                  {

                      tempX1 = marginLeft;

                      tempY1 = marginTop + i * ((this.userControlHeight - marginTop - marginBottom) / this.numberOfYPoints);

                      tempX2 = marginLeft + (this.userControlWidth - marginLeft - marginRight);

                      tempY2 = marginTop + i * ((this.userControlHeight - marginTop - marginBottom) / this.numberOfYPoints);

       

                      //Draw line for YValues

                      grfx.DrawLine(cPen, tempX1, tempY1, tempX2, tempY2);

                  }

       

                  cPen.Dispose();

                  #endregion


    • Labels
      For setting the labels a floating rectangle is used. This structure stores a set of four floating-point numbers that represents the location and size of a rectangle. The structure PointF represents an ordered pair of floating point x- and y-coordinates that defines a point in 2-dimensional plane. Naturally the margins, width and height are again important for the calculation of the locations.

                  #region Labels

                  RectangleF rectangleForLabel;

                  PointF pointFLocation;

                  //X

                  for (int i=0; i < this.currentCurve.XValues.Length; i++)

                  {

                      string label = this.currentCurve.XValues[i].Day + "/" + this.currentCurve.XValues[i].Month;

       

                      SizeF strSize = new SizeF(grfx.MeasureString(label, usedFont));

                      pointFLocation = new PointF(marginLeft + ((this.maxWidth) / (this.currentCurve.XValues.Length)) * i + - (strSize.Width / 2), this.maxHeight + marginTop + (strSize.Height / 2));

       

                      rectangleForLabel = new RectangleF(pointFLocation, strSize);

                      grfx.DrawString(label, usedFont, SystemBrushes.ControlText, rectangleForLabel);

                  }

       

                  //Y

                  for (int i=0; i <= this.numberOfYPoints; i++)

                  {

                      double label = (this.stepYValue * this.numberOfYPoints - ((this.stepYValue * this.numberOfYPoints - this.currentCurve.Minimum) / this.numberOfYPoints) * i);

       

                      string labelText = label.ToString();

       

                      SizeF strSize =  new SizeF(grfx.MeasureString(labelText, usedFont));

                      pointFLocation = new PointF(marginLeft - (strSize.Width * 1.2F), (marginTop + i * ((this.userControlHeight - marginTop - marginBottom) / this.numberOfYPoints)) - strSize.Height/2);

       

                      rectangleForLabel = new RectangleF(pointFLocation, strSize);

                      grfx.DrawString(labelText, usedFont, SystemBrushes.ControlText, rectangleForLabel);

                  }

                  #endregion


    • Curve
      Drawing the curve is nothing more than drawing lines from point (x1, y1) to point (x2, y2). These points are stored in a point array and are calculated from the value-points in the yValues-array, keeping in mind the margins, the width and height of the control, the number of x-points and the number of y-points.

                  #region Curves

                  //Set the color fo the pen

                  cPen = new Pen(this.currentCurve.Color, 3);

                  //Set the style of the curve (solid, dash, ...)

                  cPen.DashStyle = this.currentCurve.DashStyle;

                  //Set the Brush

                  cBrush = new SolidBrush(this.currentCurve.Color);                   

       

                  ptsList = new Point[this.currentCurve.XValues.Length];

       

                  int pointX = 0;

                  int pointY = 0;

       

                  Pen cPenExtra = new Pen(Color.Black);

                  cPenExtra.DashStyle = DashStyle.Solid;

       

                  for (int i=0; i < this.currentCurve.XValues.Length; i++)

                  {

                      pointX = marginLeft + (this.maxWidth / this.currentCurve.XValues.Length) * i;

                      pointY = (int) (marginTop + (this.maxHeight - ((this.currentCurve.YValues[i]) / (this.stepYValue * this.numberOfYPoints)) * this.maxHeight));

       

                      ptsList[i] = new Point(pointX, pointY);

       

                      if (i > 0)

                      {

                          //Draw line

                          grfx.DrawLine(cPen, ptsList[i-1], ptsList[i]);

                      }

                  }

                  #endregion

In a next post I will focus more on refreshing the chart with the margin- and color-values. I will also explain how the scaling (y-values) is done ...

1 Comments:

  • At 9:25 AM, Blogger cien cien said…

    hi :D
    fiuuhh thanks for your article, may it help my project..
    urkk it's very hard to find free component in C#.
    i'll try to find chart component.

     

Post a Comment

<< Home