Article Options
Premium Sponsor
Premium Sponsor

 »  Home  »  .NET Newbie  »  GDI+ Chart Success Part 6: Dynamic Line Chart  »  A More Generic Version
 »  Home  »  Windows Development  »  Graphics  »  GDI+ Chart Success Part 6: Dynamic Line Chart  »  A More Generic Version
 »  Home  »  Windows Development  »  Win Forms  »  GDI+ Chart Success Part 6: Dynamic Line Chart  »  A More Generic Version
GDI+ Chart Success Part 6: Dynamic Line Chart
by Ged Mead | Published  08/22/2006 | .NET Newbie Graphics Win Forms | Rating:
A More Generic Version

Versatile Guidelines
We have made life relatively easy for ourselves so far by keeping to predefined and easy-to-use values in the demonstrations. While this is fine for the purposes of this article, I'm sure you will want to know how you can translate the logic in a way that allows you to create these kinds of charts for other ranges of values and other sizes of display.


That's what we will look at now.

Display Guidelines
Instead of using a procedure which has the values hard coded into it, we will move on to one that takes parameters. We change from a Sub to a Function as this allows us to pass in the parameters needed and to receive in return a newly minted Bitmap object.

Here is the replacement Function:

Private Function DisplayGuidelines(ByVal PicBox As PictureBox, ByVal chunks As Integer) As Bitmap
' Step 1
' Create a bitmap to draw on and grab its Graphics Object
Dim bm As New Bitmap(PicBox.Width, PicBox.Height)
Dim gr As Graphics = Graphics.FromImage(bm)

' Step 2
' Draw guidelines on main chart.
' Get the total height available and split it into chunks
Dim total As Integer = PicBox.Height
Dim chunk As Single = total / chunks

' Step 3
For i As Single = chunk To total Step chunk
gr.DrawLine(Pens.WhiteSmoke, 0, i, PicBox.Width, i)
Next i

' Step 4
' return the results.
Return bm

' Step 5
gr.Dispose()
End Function

Let's take a look at the key changes.

Step 2 calculates the vertical space between each line. This is simply done by dividing the total height of the PictureBox by the number of lines (chunks) requested.

Step 3 is a generic version of the original loop that draws the lines; the values no longer hard coded.
Although I have used the Pens.WhiteSmoke Pen in the procedure, you could of course add a third parameter to it and allow the client code to set a pen color of choice.

Step 4 Returns a Bitmap object. This is slightly different to the original Sub, where we assigned the Bitmap to a PictureBox whose name was hard coded. In this replacement Function, we can assign the Bitmap in any way we like to any control that can use it.

We can call this function on Form_Load:

Private Sub _3rd_More_Generic_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
picGraph.Image = DisplayGuidelines(picGraph, 12)
End Sub

which in this example, creates 12 Guidelines and draws them on the PictureBox named picGraph

Guidelines and Plotted Values
In the same vein, let's now look at making the procedure that plots the values more widely usable.


Here's the replacement procedure - again it's now a Function, not a Sub.

Private Function DisplayGuidelinesAndChart(ByVal PicBox As PictureBox, ByVal chunks As Integer, _ ByVal XMove As Integer, ByVal NewValue As Single, ByVal Min As Single, ByVal Max As Single) As Bitmap

' Step 1
' Grab the current image (the latest version of the chart)
Dim bm As New Bitmap(PicBox.Width, PicBox.Height)
Dim gr As Graphics = Graphics.FromImage(bm)


' Step 2
' Get the total height available and split it into chunks

Dim total As Integer = PicBox.Height
Dim chunk As Single = total / chunks
' Tack missing guidelines to right hand side on the Graphics object.
For i As Single = chunk To total Step chunk
gr.DrawLine(Pens.WhiteSmoke, PicBox.Width - XMove, i, PicBox.Width, i)
Next i


' Step 3
' Draw this grabbed image, placing it XMove pixels to the left

If Not IsNothing(PicBox.Image) Then
gr.DrawImage(PicBox.Image, -XMove, 0)
End If


' Step 4
' Plot the new value.
' Calculate the scaling required to make full use of the height of
' the PictureBox

Dim ValueRange As Single = Max - Min
Dim vScale As Single = PicBox.Height / ValueRange

' Apply the scale to the current value
NewValue *= vScale

' Step 5
' Shift start point from top left to bottom left.

gr.TranslateTransform(0, picGraph.Height)

' Step 6
' Draw the next line segment on the Graphics object.
' If Min is > 0 then you need to shift the drawing down once again,
' this time to put the Min value on the horizontal axis
If Min > 0 Then gr.TranslateTransform(0, Min * vScale)

' Step 7
' Return the Bitmap .

Return bm

' Step 8
' All done
gr.Dispose()
End Function

We have extended the parameters further and now take in client values for XMove, NewValue, Min and Max. XMove and NewValue have the same role as in all the previous examples; passing them in as parameters just makes the procedure more generalised.
We used the 'chunks' integer in the previous function and its role is the same in this one.

In Step 3 I have added some code to catch an intermittent bug that caused me problems. It would throw a "Value cannot be null" Exception occasionally; the problem being centered on the PicBox.Image at Form_Load . I wasn't able to satisfactorily pin down the cause of this problem because of its intermittent nature. An alternative solution is to place the DrawImage method in a Try Catch block; this will handle the Exception without crashing the application, but I found that there was an unwanted delay before the new form appeared, so I prefer the IsNothing fix.

In Step 4, parameters Min and Max are also both new. These are introduced to give you the ability to use ranges of values where the minimum isn't hard coded or assumed to be zero. Similarly,the maximum can be any figure that is dictated by the datasource used. Having these two values as parameters allows the function to calculate the scaling needed to fit into the available height of the PictureBox.

Also in Step 4, we calculate an appropriate scaling based on the available height of the PictureBox and the Maximum range to be covered. In the earlier examples, you may remember that we hard coded this scale to 4:1. Now however, we have much more flexibility.

In Step 6, as should be clear from the commenting, we now need to shift the drawing so that the minimum value (whatever it happens to be) is placed exactly in line with the horizontal axis. The TranslateTransform method simply moves the startpoint of the drawing by the appropriate scaled amount.

Other than that, there isn't much new to take in.

The Vertical Values
Finally, we need to apply the same sort of thinking to the procedure that populates the picValues PictureBox with Guidelines and Numbers. Although the code below, with its variables in place of the previous hard coded values, looks more complex, the code logic is in fact exactly the same. The commenting and variable names hopefully will help you see how this version reflects the hard coded values version.

Private Function DisplayVerticalValues(ByVal PB As PictureBox, ByVal HowManyChunks As Single, _
ByVal MinValue As Single, ByVal MaxValue As Single) As Bitmap


' Step 1
Dim bmp As New Bitmap(PB.Width, PB.Height)
Dim gv As Graphics = Graphics.FromImage(bmp)


' Step 2
' Draw guidelines on values strip
' Get the total height available and split it into chunks
' This value represents a number of pixels

Dim TotalPixels As Integer = PB.Height
Dim SingleChunk As Single = TotalPixels / HowManyChunks

For i As Single = SingleChunk To TotalPixels Step SingleChunk
gv.DrawLine(Pens.WhiteSmoke, 0, i, PB.Width, i)
Next i


' Step 3
' Draw Numbers as Text, correctly spaced vertically
' Begin with the highest value allowed

Dim NextMarker As Integer = MaxValue
Dim ValueRange As Single = MaxValue - MinValue


' Draw the numbers, decrementing values proportionately each time through the loop
For i As Single = 0 To TotalPixels Step SingleChunk
gv.DrawString(CStr(NextMarker), New Font("Verdana", 8, FontStyle.Regular), Brushes.Black, 1, i)


NextMarker -= (ValueRange / HowManyChunks)
Next


' Step 4
Return bmp

' Step 5
gv.Dispose()

End Function

So there you have the various changes and improvements needed to make the original idea much more flexible.    You can now extend the general approach for whatever particular source of data suits your purposes.


 

Sponsored Links