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)