DevCity.NET - http://devcity.net
GDI+ Chart Success Part 6: Dynamic Line Chart
http://devcity.net/Articles/255/1/article.aspx
Ged Mead

Ged Mead (XTab) is a Microsoft Visual Basic MVP who has been working on computer software and design for more than 25 years. His journey has taken him through many different facets of IT. These include training as a Systems Analyst, working in a mainframe software development environment, creating financial management systems and a short time spent on military laptop systems in the days when it took two strong men to carry a 'mobile' system.

Based in an idyllic lochside location in the West of Scotland, he is currently involved in an ever-widening range of VB.NET, WPF and Silverlight development projects. Now working in a consultancy environment, his passion however still remains helping students and professional developers to take advantage of the ever increasing range of sophisticated tools available to them.

Ged is a regular contributor to forums on vbCity and authors articles for DevCity. He is a moderator on VBCity and the MSDN Tech Forums and spends a lot of time answering technical questions there and in several other VB forum sites. Senior Editor for DevCity.NET, vbCity Developer Community Leader and Admin, and DevCity.NET Newsletter Editor. He has written and continues to tutor a number of free online courses for VB.NET developers.

 
by Ged Mead
Published on 8/22/2006
 

   The static line chart we created in Part 5 could be useful in lots of situations, but what happens when you want the chart to be continuously updated and the line redrawn in real time?

   This article looks at some ways of doing this.

 


Introduction

   There are some quite advanced features that can be incorporated into a dynamic chart, sometimes depending on where the data is coming from.   In this article, we are going to be begin with a fairly simple demo.   Then we will move on to one that is slightly more sophisticated and finally we will look at further enhancements that could be incorporated into this kind of graph.

   So, let me set the scene.  What do we want to do here?   Well, the first dynamic line chart I plan to create is one that takes some simple user input and displays the values as they are chosen on the chart.   As each new value is selected, the chart will be extended and redrawn to show the continuous changes that have been made.  When the screen width is "full", the earliest values are lost from view as the graphic display continues to scroll from right to left.

  One quick and easy demo is to create put a scrollbar on a form and use the changing scrollbar values as the data for the chart.   Take a look at the sample form below.

 

Form Controls    

    The silver grey area is a PictureBox.  As the values in the ScrollBar change, so the graph represents the new values.  I have used a Vertical ScrollBar here, but you can just as easily use a Horizontal ScrollBar.  In either event, the coding logic will be the same.

   Here are the steps to create the form.  

  1. Add a ScrollBar control to the form.  Set its Minimum property to 0, its Maximum to 104.  (If you are wondering why I've picked that Maximum value, see the note at the bottom this page) .  Set the LargeChange property to 5; leave the SmallChange property as 1.  Change the control's name to SBUserValue to reflect its purpose - scroll bar to detect user's chosen values.
  2. Add a PictureBox and name it picGraph.   Set its height to exactly 400.   Why ?   This is really just a way of keeping the scaling code as easy as possible.   The user values are going to be numbers between 0 and 100, so it will be very easy to multiply the value by 4 to automatically scale it vertically on the picturebox graph.     Real life almost certainly won't be this easy, but it will help us concentrate on the important drawing stuff for this first demonstration.  Change the PictureBox'sname to picGraph and set its backcolor to Gainsborough (or any other color that you prefer)
  3. Finally,add a label to display the changing selection value.  This label plays no part in the chart creation; I've included it so you can check that any given value is correctly represented on the chart itself.   Name it lblValue.
  4. The remaining label controls shown on the screenshot above are entirely optional and are not referred to in any of the code that follows.

   Initial Code

   Place this Imports statement at the top of the form:

 Imports

System.Drawing.Drawing2D

    As you will know if you have read the earlier articles in this series, this allows us to use any of the  Drawing.Drawing2D class methods and properties in code without having to keep writing out the fully qualified names each time.  

   In the next step we will build the code that does all the drawing in the chart, dynamically changing the display each time a new value is selected via the ScrollBar.

----------------------------------------------------------------------------------------------------------------

   Note:

     The reason for setting a Maximum value of 104 (when what we really want is 0 to 100 on the scale) is due to what seems to me to be a small foible with the ScrollBar control.     If you set the Maximum to 100 and the Large Change to 5 then the highest value you will be able to physically obtain by moving the slider or clicking on the ScrollBar's arrow will be 96.  There may be an official fix for this, but I find that offsetting the Maximum by a figure of (LargeChange - 1) seems to do the trick.

  

 

     

   

 

Basic Chart

Drawing the Chart Guidelines
The next step is to draw the horizontal guidelines on the chart. These are drawn as soon as the form is first loaded and (as you will see shortly) are redrawn each time the chart is updated with new values.

In this first example, we will use hard coded values in the procedure that draws the guidelines. This is only because it makes the code simpler and the explanations shorter. Later in the article we will use a more flexible, modular and more realistic approach .

In the Code Window for the form, add the following procedure:

Private Sub DrawGuidelines(ByVal PicBox As PictureBox)
' Create a bitmap to draw on and grab its Graphics Object
Dim bmp As New Bitmap(PicBox.Width, PicBox.Height)
Dim gr As Graphics = Graphics.FromImage(bmp)

' Draw guidelines on this graphics object .
For i As Integer = 40 To 400 Step 40
gr.DrawLine(Pens.WhiteSmoke, 0, i, PicBox.Width, i)
Next i

' Assign the bitmap back to the PictureBox as its Image.
PicBox.Image = bmp
End Sub

How It Works
 Let's analyse what this code actually does. (If you have read the previous articles this will be familiar to you, but if you have jumped in here at Part 6 some explanation of what is going on will be useful, I'm sure).

The first two lines of code in the procedure:

Dim bmp As New Bitmap(PicBox.Width, PicBox.Height)
Dim gr As Graphics = Graphics.FromImage(bmp)

can be broken down as follows:

  1. Dim bmp As New Bitmap: Creates a Bitmap object.
  2. (PicBox.Width, PicBox.Height): Sets the size of this Bitmap to be the same size as the PictureBox on which we will draw the graph.
  3. Dim gr As Graphics: Creates a Graphics Object.
    (A Graphics object can be thought of as a kind of "canvas" that is laid over a control and onto which lines and shapes can be drawn).
  4. = Graphics.FromImage(bmp): Specifically assign the Graphics object from the Bitmap that we have just created. We will draw the lines on this Graphics object

We've made a start. Let's check it out.
In the Form's Load event insert the following code which will call the above procedure and draw ten evenly spaced lines across the PictureBox:

Private Sub _1stDemo_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
' Draw horizontal lines on the picturebox
DrawGuidelines(Me.picGraph)
End Sub

Try It Out


Try out the code so far. You should see something like this:


Guidelines



Charting the Values
The next part of the code is the key to the dynamism of the chart. Whenever the user changes the recorded value by scrolling the ScrollBar, here's what we will do:

  1. Take a 'snapshot' of the previous display by grabbing the current image from the PictureBox in which we are creating the chart.
  2. Extend the guidelines at the right hand side of the drawing. (You'll see why in a minute).
  3. 'Paste' the snapshot of the previous display back on to the Bitmap's Graphics Object, BUT place it slightly to the left so that some of this snapshot falls outside the viewable area of the PictureBox.
    (There would an empty gap at the right hand side now if we hadn't extended the guidelines in the last step).
  4. Calculate the point on the Y (vertical axis) where the currently selected value should be marked.
    Note that we have to scale this to make full use of the available height of the PictureBox. In our current example, where we have carefully set the PictureBox height to 400 and the ScrollBar maximum to 100, we therefore have a scale of 4:1 (that is, each increment of 1 in the ScrollBar value is represented by 4 vertical pixels in the PictureBox.)
  5. Move the start point from the default position at the Top Left of the PictureBox, so that the drawing will now start at the Bottom Left corner of the PictureBox.
    The key thing to understand is that when you draw lines in a PictureBox, the point of origin - the Point with values (0,0)- is always in the top left hand corner.
    Then, when you draw a line from that point - to Point (50, 100) for example - then the pen will move 50 pixels across and 100 pixels down.
    This is fine for most drawing tasks, but in a chart we expect the line to move upwards as values increase, not downwards. Therefore we want our chart line to begin in the bottom left corner to represent a value of zero (Point 0,0) and increasing values to be represented by an upward moving line.
  6. Draw the next line segment. This line will extend from the point where the last value plotted ended to the point required to represent the current value.
    Remembering what was said above about lines being drawn from top to bottom, we also have to flip the line so that it becomes drawn from bottom to top (otherwise it would be drawn in an area out of view).
    In this example, this is achieved very simply by plotting the negative equivalents of the values.
  7. All the drawing tasks for the latest value are now complete, so we can assign our freshly redrawn bitmap as the Image property of the PictureBox.
    This will update the display and the user will see the results of our actions above.
  8. Finally, good housekeeping - we dispose of the Graphics object as we have finished with it for this cycle of actions.

The following code will do the job:-

We need to set up a few variables. The first one will represent the length of each segment of the graph - that is, the number of pixels used horizontally on the screen as each new part of the graph is added. Add the code below to the top of the form:

Dim Xmove As Integer = 3

Because we are going to be drawing the graph of an ever changing set of values, we will need to be able to refer to the last used value as well as the current one. The two variables shown below will be used for this task:

Private OldValue As Single = 0
Private NewValue As Single = 0
Now for the actual procedure which produces the goods. It is named DrawGuidelinesAndChart:
Private Sub DrawGuidelinesAndChart(ByVal PicBox As PictureBox)
' 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
' Tack missing guidelines to right hand side on the Graphics object.

For i As Integer = 40 To 400 Step 40
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 of the
' image area. (that is, the first few pixels will be invisible because
' they are now outside the picturebox's viewable area)
gr.DrawImage(PicBox.Image, -Xmove, 0)
' Note that we are still drawing on the Graphics object.
' No change will appear on the PictureBox yet.

' Step 4
' Plot the new value. That is, calculate where on the Y axis (the
' vertical plane) this new value should be marked.
' This calculation takes into account the scale of the min/max values
' range in relation to the total height of the PictureBox.
' In this example this is 4:1 (PictureBox Height is 400:Max value on the ScrollBar is 100)
' so we multiply the selected value by a scale of 4
NewValue = SBUserValue.Value * 4
' 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.
' Note that the NEGATIVE values of OldValue and NewValue
' are used for the Y positions.
' This will reverse the position of those points and make them
' appear in the PictureBox. If you don't do this, they will be
' drawn down below the bottom of the PictureBox and therefore
' out of view of the user.

gr.DrawLine(Pens.Black, _
picGraph.Width - 1 - Xmove, -OldValue, _
picGraph.Width - 1, -NewValue)
OldValue = NewValue

' Step 7
' Display the results by assigning this edited Bitmap as the
' Image of the PictureBox.
picGraph.Image = bm
' Step 8
' All done

gr.Dispose()
' For confirmation purposes, display the currently selected value in a label
lblValue.Text = SBUserValue.Value.ToString
End Sub

Using the Vertical ScrollBar
As mentioned earlier, the vertical ScrollBar used in this demonstration is just an easy way to generate a set of fast-changing values to show on the graph. What we need to do now is write some code that kicks in each time the value of the ScrollBar is changed by the user.
The place to do this in the ScrollBar's ValueChanged event.

Private Sub SBUserValue_ValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles SBUserValue.ValueChanged
DrawGuidelinesAndChart(picGraph)
End Sub

Test It

Now run the project and move the ScrollBar. Each movement of the ScrollBar should result in a new value being plotted on the chart. You can check what value was last selected by viewing the value shown in the label.
As you'll see, the Vertical ScrollBar creates higher values as it is moved downwards; lower values as it is moved upwards. If you find this confusing, you can easily replace the Vertical ScrollBar with a Horizontal one. In that case, lower values are selected as you scroll to the left and higher values to the right, which you may think is more logical.

 

First Demonstration Summary
With relatively little code, we have created a useful dynamic line chart. You can replace the ScrollBar that I have used with any other means of taking values that would be relevant for your projects, whether this be user input, system produced information, external data, time based data, and so on.

Of course, you can't always guarantee that the values used will fall neatly into the 1 to 100 range we used for this first example, so we will shortly look at how we can edit the code to make it more flexible.
First though, I want to look at a couple of ways of producing some interesting alternative displays.

 

Basic Chart Special Effects

Shaded Graph
If you want a chart that looks like this:

then you can achieve this effect with a minor code tweak. Replace Step 6 in the DrawGuidelinesAndChart procedure with the code block below:

' Step 6

' Draw the next line segment on the Graphics object.

gr.DrawLine(Pens.Black, _
picGraph.Width - 1 - Xmove, -OldValue, _
picGraph.Width - 1, -NewValue)
OldValue = NewValue
' Draw another line segment, but this time leave the Y value of
' OldValue as positive.

gr.DrawLine(Pens.LightBlue, _
(picGraph.Width - 1) - Xmove, OldValue, _
picGraph.Width - 1, -NewValue)
OldValue = NewValue

As you can see, the only change is a second sub block which uses Drawline again to draw a second line segment on the Graphics object. The difference here is that the start point of this segment is the positive value of OldValue, which you will recall means that it is located down below the bottom of the PictureBox. So drawing this second line has the effect of creating a line that runs from the bottom of the PictureBox up until it meets the point where we plotted the negative value of NewValue.
Sounds complicated at first, I know, but if you step through it a line at a time I hope it will make sense.

Or something a bit more striking:

Again, this requires just a small edit to Step 6. This time though, instead of drawing lines, we draw a polygon. The top of the polygon is a line that is exactly the same line as we have drawn in the previous examples; the remainder of the polygon is created by drawing two vertical lines down from either end of this line to the bottom of the PictureBox, plus a horizontal line along the bottom.
The FillPolygon method takes these four points and draws a polygon shape filled with our choice of color.

Here's the replacement Step 6:

' Step 6
' Draw Polygon to fill areas below the plotted line.

Dim topLeft As New Point((picGraph.Width - 1) - Xmove, -OldValue)
Dim topRight As New Point((picGraph.Width - 1), -NewValue)
Dim bottomRight As New Point(picGraph.Width - 1, picGraph.Height)
Dim bottomLeft As New Point((picGraph.Width - 1) - Xmove, picGraph.Height)
Dim PolyPoints As Point() = {topLeft, topRight, bottomRight, bottomleft}
gr.FillPolygon(Brushes.Red, PolyPoints)

OldValue = NewValue


 

One final variation for you to try out:

The amended Step 6 looks like this:

' Step 6
' Draw Polygon to fill areas below the plotted line.
Dim topLeft As New Point((picGraph.Width - 1) - Xmove, -OldValue)
Dim topRight As New Point((picGraph.Width - 1), -NewValue)
Dim bottomRight As New Point(picGraph.Width - 1, picGraph.Height)
Dim bottomLeft As New Point((picGraph.Width - 1) - Xmove, picGraph.Height)
Dim PolyPoints As Point() = {topLeft, topRight, bottomRight, bottomLeft}
Dim hb As New Drawing.Drawing2D.HatchBrush(Drawing2D.HatchStyle.Trellis, Color.Black, Color.White)


' Alternative: Uncomment this line for a more subtle version: 
' It changes the ForeColor and BackColor properties for more variations
gr.FillPolygon(hb, PolyPoints)
OldValue = NewValue

You can play around with variations of these for hours! If you want a less jagged effect on the plotted lines, try increasing the value of XMove.

 

Displaying Values On The Chart

Vertical Axis Values
So far, the user has been able to see the current value displayed in a label, which is useful but not always realistic. However, on the chart itself the visual clue to the current value has been limited to the grey horizontal lines. In most cases we will want something more definite than this. Specifically, it would be better to have a value displayed at the end of each of the guidelines that our users can refer to.

Although as always there are several ways of achieving this, one of the easiest is simply to create a second PictureBox which contains these values and place it to the side of the picGraph PictureBox.

 

Add a second PictureBox to the form. Place it to the left hand side of the original picGraph PictureBox. Set its Size to 30, 400. This will make it the same height as the picGraph PictureBox. Name it picValues.

The easy way to line up the two PictureBoxes is to use the Format menu in the Visual Studio IDE. You probably already know all about this, but just in case:

  • Click the left mouse button on one of the PictureBoxes.
  • Hold down the Control key .
  • Click on the second PictureBox, still holding the Control key down.
  • Select Format - Align - Tops from the IDE Menu:
  • Next, Select Format - Horizontal Spacing - Remove:

  • If necessary, you can manually drag both PictureBoxes together to any suitable position on the form.
  • Finally click anywhere on the form's main area (i.e. not in either of the PictureBoxes) to unlock the selection.

    Code for the Vertical Values
    The procedure used to put guidelines and values in the new PictureBox is called DrawVerticalValues. This is it:

    Private Sub DrawVerticalValues(ByVal PB As PictureBox)
    ' Draw vertical values (again using double buffering, ' although it's not really necessary)
    ' 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

    For i As Integer = 40 To 400 Step 40
    gv.DrawLine(Pens.WhiteSmoke, 0, i, PB.Width, i)
    Next i


    ' Step 3
    Dim NextMarker As Integer = 100
    For i As Integer = 0 To 360 Step 40
    gv.DrawString(CStr(NextMarker), New Font("Verdana", 8, FontStyle.Regular), Brushes.Black, 1, i)
    NextMarker -= 10

    Next


    ' Step 4
    PB.Image = bmp
    ' Step 5
    gv.Dispose()
    End Sub

    Step 1 and Step 2 are the same as we have previously discussed, so I won't go through those again.

    Step 3 has the task of populating values into the PictureBox.

    • First, it creates a variable named NextMarker and assigns it an initial value of 100. This value reflects the Maximum value of the ScrollBar.
    • The second line in Step 3 sets up a loop. Because of the values of the Step used in the loop, this effectively means that the loop will run ten times.
    • Within the loop, DrawString is used to write the next marker value onto the Graphics object of the PictureBox. The first value to be written is 100, the next will be 90, then 80, etc. The reason for writing the values in descending order is of course that this procedure is working its way from the top to the bottom of the PictureBox and therefore we need the values to decrease with each step.
    • The next line of code simply reduces the value that we are going to display next. In this example we are reducing the value by 10 each time.
    • The loop ends with the Next keyword.

    Steps 4 and 5 are again simply repeats of the logic we used in previous drawing procedures.

    So there you have a fairly simple way of inserting a vertical list showing values.   Next, we need to consider a more realistic situation where the range of values that may need to be charted don't slot neatly into a preset and pre-scaled PictureBox.  

     

    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.


     

     

    Summary

    Possible Enhancements

    The next logical step would be to create a Chart Class and a Class Library to make your code more portable between applications.


    Following that, if you are expecting to have to use many of these kinds of charts many times in the future then the best long term plan might well be to create your own custom control and add it to your Visual Studio Toolbox.

    For the time being though, we will settle for this set of generic line chart procedures. The Class Library and User Control will have to wait for a later date.


    Nevertheless, we have covered quite a lot of ground in this article and have produced a useful way of creating dynamic line charts. We have certainly come a long way from our first simple Pie Chart back in Part 1!

    Topics Covered


    In this article, we covered the following topics:

    • Brushes
    • Dispose method
    • Double Buffering
    • DrawLine method
    • DrawImage method
    • DrawString
    • FillPolygon method
    • Font object
    • Format menu item
    • Graphics.FromImage method
    • Pens
    • Scaling
    • TranslateTransform method

     

    Demonstration Sample Solution

    All the code described is included in the attached sample solution, which was created using  VB.NET 2003. 

    Series Summary

     It's taken me a while to complete all six articles in this set, but I hope that they have helped you go from the very basic out-of-the-box pie chart to the potentially quite sophisticated dynamic line charts.   Of course, there are lots of other charts we could consider and many enhancements to what we have done so far, but I'll leave you to take it on from here.  If you have any questions about any of the techniques don't hesitate to post them up in the VB.NET Forum and I'll do my best to help out.

    Enjoy!

    ___________________________________________________________________________________


    Acknowledgements:  I would like to thank Rod Stephens of VBHelper for the original idea of cutting and pasting a Bitmap to an offset position to create the illusion of chart movement.

    Also many thanks to the generous folks at Techsmith for gifting me a copy of their excellent Snagit utility which made the creation and editing of screenshots so easy.