Line Chart Component [GDI+]
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
- Main Area
1 Comments:
At 9:25 AM, 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