(Print this page)

The poor man charting option
Published date: Sunday, August 28, 2011
On: Moer and Éric Moreau's web site

When all you need is a simple line chart and you don’t need complex options, it is very easy to draw charts by yourself using nothing else then a regular PictureBox control and a couple of graphical methods.

This is what I will show you today. This article explains how to create a line chart from a list values (like stock price for example).

The Demo application

This month, the demo application is available for download in both VB and C#. It was created using Visual Studio 2010 but would be exactly the same using Visual Studio 2008 (and surely even 2005).

Figure 1: The demo application in action

Creating the UI

As you can see from Figure 1, the UI of this application is really simple. Only 4 controls are needed:

 

  • A Label. Only change its Text property to SymBols.
  • A TextBox. Set its name to txtSymbols and set the Anchor property to enlarge automatically (Top, Left, Right).
  • A Button. Set its name to btnChart and set the Anchor property to keep the button in the upper right corner (Top, Right).
  • A PictureBox. Set its name to picChart and set the Anchor property has to be changed to use most of the screen (Top, Bottom, Left, Right).

 

Writing the code

As you will see later (see the Anchoring the PictureBox section), even if the PictureBox is correctly anchored, its content does not get redraw automatically. It’s true that you can set the SizeMode property to StrecthImage but give it a quick test and you will see that the image after a resize is all blurry.

For this reason, we need to keep the original values to be able to redraw the chart when need. So the first thing we will need is to declare 2 variables at the class level so they are visible everywhere in the class (the 3rd variable is only to have access to random values more easily):

' List of symbols
Private mlstSymbols As List(Of String) = Nothing
' List of current values
Private mlstValues() As List(Of Single) = Nothing
'To provide random values
Private mobjRnd As New Random()

Now that we have these variables declared, we are ready to write the code behind the Click event of the button:

'Validate if we have symbols
 If String.IsNullOrEmpty(txtSymbols.Text) Then
     MessageBox.Show("Enter some symbols first",
                     "Missing information",
                     MessageBoxButtons.OK,
                     MessageBoxIcon.Error)
     Return
 End If

 Me.Cursor = Cursors.WaitCursor

 Try
     'Get the list of symbols
     mlstSymbols = New List(Of String)()
     For Each strX As String In txtSymbols.Text.Split(","c)
         If Not String.IsNullOrEmpty(strX) Then mlstSymbols.Add(strX.Trim)
     Next

     'Get the value data
     ReDim mlstValues(0 To mlstSymbols.Count - 1)
     For i As Integer = 0 To mlstSymbols.Count - 1
         mlstValues(i) = GetValues(mlstSymbols(i))
     Next i

     ' Graph it
     DrawChart()

 Catch ex As Exception
     MessageBox.Show("Something happened:" + Environment.NewLine + ex.Message,
                     "Error",
                     MessageBoxButtons.OK,
                     MessageBoxIcon.Error)
 Finally
     Me.Cursor = Cursors.Default
 End Try

After a quick validation to ensure we have symbols (otherwise there is nothing to do), we fill the list of symbols structure in the memory level variable (mlstSymbols).

Then we are ready to loop through these symbols to get the values for each one of those. The values are retrieved in the GetValues (explained later) method.

Now that we have the list of symbols and the values for each one, we are ready to call the DrawChart method (also explained later).

For my demo application, I am providing random values for each symbol. Your application surely already have values you want to draw. This is where you are going to modify code to replace my values. My random values are filled with this code:

Private Function GetValues(ByVal pSymbol As String) As List(Of Single)
    Dim lstValues As New List(Of Single)()

    For i As Integer = 1 To 20
        lstValues.Add(Convert.ToSingle(mobjRnd.NextDouble * 100))
    Next

    Return lstValues
End Function

If you want to retrieve stock price from a provider, check my article of March 2004 titled “Using the WebRequest object to retrieve Yahoo stock quotes”.

The other method I have to go through is the main one: the DrawChart method which reads like this:

Private Sub DrawChart()
    If (mlstValues Is Nothing) Then Return

    'Make the bitmap.
    Dim objBM As New Bitmap(picChart.ClientSize.Width, picChart.ClientSize.Height)
    Using objGR As Graphics = Graphics.FromImage(objBM)
        objGR.Clear(Color.White)
        objGR.SmoothingMode = SmoothingMode.AntiAlias

        'Get the largest Values
        Dim sngMaxValue As Single = 10
        For Each arrSymbolValues As List(Of Single) In mlstValues
            Dim sngNewMax As Single = arrSymbolValues.Max()
            If (sngMaxValue < sngNewMax) Then sngMaxValue = sngNewMax
        Next

        'Scale and translate the chart
        Dim sngScaleX As Single = -picChart.ClientSize.Width / CSng(mlstValues(0).Count)
        Dim sngScaleY As Single = Convert.ToSingle(-picChart.ClientSize.Height / sngMaxValue)
        objGR.ScaleTransform(sngScaleX, sngScaleY)
        objGR.TranslateTransform(
            picChart.ClientSize.Width,
            picChart.ClientSize.Height,
            System.Drawing.Drawing2D.MatrixOrder.Append)

        'Draw the grid lines.
        Using objThinPen As New Pen(Color.Gray, 0)
            For y As Integer = 0 To CInt(sngMaxValue) Step 10
                objGR.DrawLine(objThinPen, 0, y, mlstValues(0).Count, y)
            Next y
            For x As Integer = 0 To mlstValues(0).Count - 1 Step 7
                objGR.DrawLine(objThinPen, x, 0, x, 2)
            Next x
        End Using

        ' Draw each symbol's values.
        Dim arrColors() As Color = {Color.Black,
                                    Color.Red,
                                    Color.Green,
                                    Color.Blue,
                                    Color.Orange,
                                    Color.Purple}
        For intSymbolNum As Integer = 0 To mlstValues.Length - 1
            Dim arrSymbolValues As List(Of Single) = mlstValues(intSymbolNum)

            ' Make the data points.
            Dim objPoints(0 To arrSymbolValues.Count - 1) As PointF
            For i As Integer = 0 To arrSymbolValues.Count - 1
                objPoints(i) = New PointF(i, arrSymbolValues(i))
            Next i

            ' Draw the points.
            Dim objColor As Color = arrColors(intSymbolNum Mod arrColors.Length)
            Using objPen As New Pen(objColor, 0)
                objGR.DrawLines(objPen, objPoints)
            End Using

            ' Draw the symbol's name.
            DrawSymbolName(objGR,
                           mlstSymbols(intSymbolNum),
                           arrSymbolValues(arrSymbolValues.Count - 1),
                           objColor)
        Next intSymbolNum
    End Using

    ' Display the result.
    picChart.Image = objBM
End Sub

Reading this method is straightforward. It starts by creating a new bitmap object of the same size as the PictureBox control. Then it goes through all the values to find the largest values to ensure we are using a proper ratio. Lines are then drawn on the background to ease the values comparison. Finally, we loop through the values to draw lines that go from one value to another. Lastly, the DrawSymbolName takes care of writing the name of the symbol on the chart so we can see which line means what.

Anchoring the PictureBox

One strange behavior I found with the PictureBox is that even if the control itself is docked or anchored correctly and the control itself is getting resized properly, the content is not getting resized.

This is why, in my scenario, I had to handle the SizeChanged event of the PictureBox to recreate the content when needed.

Going further

Using the same techniques, it wouldn’t be too complex to had values on the axis but I will let it as an exercise to the readers. If you find yourself spending more than a day or two, better look around for the various component.

Conclusion

A picture is worth a thousand words. You don’t always need a complex graphical component to display values you have in an array. A simple method like the one you found here is often all you need!

If you don’t absolutely need something special about charting, you can use the free charting control as I explained in January 2011 in an article titled “The Chart control in Visual Studio 2010 (and 2008 to some extents)”. (http://emoreau.com/Entries/Articles/2011/01/The-Chart-control-in-Visual-Studio-2010-and-2008-to-some-extents.aspx)


(Print this page)