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:
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)