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

Drawing a Line Graph in GDI+

0.00/5 (No votes)
10 Apr 2011 1  
Drawing a line graph in Cartesian coordinates (VB.NET and GDI+)

Introduction

Several years ago, I used a sample code of VB 6.0 project, Fast graphs without an ActiveX / OCX control by Felix Haas. There he proposed a relatively simple but effective way to construct Cartesian coordinates with grid lines using GDI.

I used this code as a template for my own project in VB 6.0 when I needed to draw line charts based on manually input values, on data queried from database or on real time data. Things changed dramatically for Graphics related code after the introduction of VB.NET and GDI+.

The sample code is an adaptation of initial VB 6.0 code to GDI+ classes and upgrade to VB 2010.

Background

Windows GDI/GDI+ uses a mirrored first quadrant of Cartesian coordinates with origin (0,0) in the upper-left corner instead of bottom-left origin in Cartesian model. The reason that Windows coordinates are drawn this way comes from the initial GDI mapping modes (the attribute of the device context that governs how logical coordinates are translated into device coordinates).
From Programming Windows with MFC by Jeff Prosise, O Reilly, 1999, chapter The Windows GDI:

In GDI when you draw in the MM_TEXT mapping mode, you're using the coordinate system where the origin is in the upper left corner of the window, the positive x axis points to the right, the positive y axis points downward, and 1 unit equals 1 pixel. You switch to one of the "metric" mapping modes- MM_LOENGLISH, MM_HIENGLISH, MM_LOMETRIC, MM_HIMETRIC, or MM_TWIPS - the y-axis flips so that positive y points upward and logical units are scaled to represent real distances rather than raw pixel counts.

 

Text we draw on the screen down from the upper-left corner with number of lines increasing.
And the default GDI mapping mode is MM_TEXT which is a bit confusing for image drawing.
In Visual Studio, C++ and GDI, you could apply metric mapping modes or user-defined mapping modes to get charting capabilities.

In VB 6.0 and GDI, you could use methods Scale, Line and properties AutoRedraw, CurrentX, CurrentY to construct the coordinates and draw the chart.
In VB.NET and GDI+, you have to use Transforms (Translate, Scale, etc.) to get similar results.

Using the Code

The project consists of two modules frmChart.vb (main form with drawing procedure) and modSettings.vb (initial settings for the chart, conversion functions and public variables). In Visual Basic 6, the default graphics units were twips (thus the initial project was based on them).

In VB.NET, they are replaced by pixels, and for upgrade we use points units available in VB.NET because it was easier to recalculate. To redraw the graphics objects when form is repainted (that was done with AutoRedraw in VB 6.0), the Form.Paint event is used.

In VB 6.0, we could use Scale method to mirror the second Quadrant and to set the origin in the bottom-left corner. In VB.NET, this graphics method was removed and we have to use Transforms to obtain similar charting capabilities.

From MSDN Transforms Overview:

Transform defines how to map, or transform, points from one coordinate space to another coordinate space.
This mapping is described by a transformation Matrix, which is a collection of three rows with three columns of Double values
.

Here is the corresponding code:

Dim graphicsObj As Graphics = eventArg.Graphics

'For this simple chart we actually don't need a Matrix object, 
'but I left here as it can be needed for more complex tasks:
Dim chartMatrix As New Drawing2D.Matrix()
graphicsObj.Transform = chartMatrix

Notice that we just defined the graphics object in this code without transforms for labels (numbers in this case) to be drawn correctly.

'Draw labels and markers at X-axis:
For I = Xmin To Xmax 
    graphicsObj.DrawString(Trim(Str(I)), drawFont, drawBrush, _
    XinPoints(I) - 2 * Len(Trim(Str(I))), YinPoints(0) + 100)
Next

'Draw labels and markers at Y-axis:
For I = Ymin To Ymax ' Y-Axis
    graphicsObj.DrawString(Trim(Str(-scaleFactorY * I + Ymax + Ymin)), _
    drawFont, drawBrush, XinPoints(scaleFactorX) - 10 - _
    2 * Len(Trim(Str(scaleFactorY * I))), YinPoints(scaleFactorY * I) - 12)
Next

Now we can apply Transforms to the graphics object:

'Move the origin to the bottom left corner 
'(position it inside Picture Box = Picture Height + border from the top).
graphicsObj.TranslateTransform(0.0F, PictureHeight + 20)

graphicsObj.ScaleTransform(1, -1) 	' Scales an element by the specified 
				' ScaleX and ScaleY amounts
             			' multiplying the transformation matrix 
				' by a diagonal matrix whose elements are (1,-1)

Now, we construct the coordinates with grid lines using graphicsObj.DrawLine method (here is only for X-axis).

'Construct X-axis and Y-axis:
'X axis:
' use DrawLine(Pen, Single, Single, Single, Single) 
' Draws a line connecting the two points specified by the coordinate pairs. 
graphicsObj.DrawLine(blackPen, XinPoints(Xmin) - 100, _
YinPoints(0), XinPoints(Xmax) + 5, YinPoints(0))

'Add arrow to X+ axis:
graphicsObj.DrawLine(blackPen, XinPoints(Xmax) + 5, _
YinPoints(0), XinPoints(Xmax), YinPoints(0) - 5)
graphicsObj.DrawLine(blackPen, XinPoints(Xmax) + 5, _
YinPoints(0), XinPoints(Xmax), YinPoints(0) + 5)

'Gridlines at X-axis:
For I = Xmin To Xmax ' X-axis
                
'Create a dashed line:
blackPen.DashStyle = Drawing2D.DashStyle.Dash
    If I <> 0 Then
        ' Gridlines vertical:
        graphicsObj.DrawLine(blackPen, XinPoints(I), _
        YinPoints(Ymin), XinPoints(I), YinPoints(Ymax))
    End If
Next 

And finally – draw the test lines on the coordinates.

'Draw test line from 0,0
x = 0
y = 0
graphicsObj.DrawLine(bluePen, XinPoints(1), YinPoints(6), _
XinPoints((Xmax - Xmin) / 20 * x + Xmin), YinPoints(y))
x = 1
y = 6
graphicsObj.DrawLine(bluePen, XinPoints(2), YinPoints(4), _
XinPoints((Xmax - Xmin) / 20 * x + Xmin), YinPoints(y))

This is only a simple chart drawn in Cartesian coordinates. If you want to draw a graph of real time data or data queried from database, you would have to solve much harder tasks.

References

History

  • 08.2007 - Built in Visual Basic 6.0
  • 04.2011 - Upgraded to Visual Basic 2010

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