Have you ever used Visual Basics graphing controls before? If so, you will have noticed the huge amount of functionality packed in: 7 different types of graph, different colours for each axis, colouring for each part of the graph, special text and font styles and many more features. Now, if you have ever tried distributing the MS Chart control then you will have found that the OCX adds over 900KB to your setup package, not something that most of us want. Find out inside
Make a light chart control
So, when you only need a simple set of display capabilities, where do you turn? Do you go out and buy a third party control which could be bulky and expensive or do you write your own in VB, allowing great customisation? In this article, you will learn how to write your own light version of the MS Chart control.
This light version is only 44KB when complied, a mere 1/20th of the size of the MS Chart control. You might thinking that this control only includes limited functionality, but your wrong. This control supports both line and bar graphs, point plotting, a legend and up to 15 data series (with different colours for each one).
Well, where do we begin? Properties would be a good start. VB makes adding properties to a usercontrol easy by providing us with the ActiveX Control Interface Wizard. To load this, click Project, Add User Control and select VB ActiveX Control Interface Wizard. Follow the steps through and add the following properties:
Backcolor OLE_COLOR (Get and Let)
DisplayLegend BOOLEAN (Get and Let)
Enabled BOOLEAN (Get and Let)
Font FONT (Get and Let)
ForeColor OLE_COLOR (Get and Let)
HighScale DOUBLE (Get and Let)
HorizontalTickFrequency VARIANT (Get and Let)
LowScale DOUBLE (Get and Let)
PlotPoints BOOLEAN (Get and Let)
Title STRING (Get and Let)
TitleFont FONT (Get and Let)
VerticalTickInterval VARIANT (Get and Let)
You will also need to manually add the following Get and Let procedures:
If you arent sure what these procedures look like, here is the one for the ChartType property:
Public Property Get ChartType() As ChartTypes'Property Code HereEnd PropertyPublic Property Let ChartType(ByVal New_ChartType As ChartTypes)'Property Code HereEnd Property
(NOTE: Dont worry about the Property Code here comments, as you will learn later what you need to place in there.)
OK. Now the properties have been built, we need to write some methods to actually perform the drawing of the graph. But before we can do that, we need to declare some variables in our control.The first thing we need to do is to add an enumeration of the available chart types. Add the following code to the General Declarations procedure of the user control:
Enum ChartTypesBar = 0 Line = 1End Enum
We now need to add an internal reference to this enumeration by the means of a variable. Add this declaration just below the ChartTypes enum:
Dim m_ChartType As ChartTypes
We now need some events, but fortunately these are standard ones. Just load up the ActiveX Control Interface Wizard again (Project, Add User Control, VB ActiveX Control Interface Wizard) and add the following events:
Theres two more things we need to do. First, we need to change some of the default values for the properties we made earlier. The HighScale property needs changing to 100, the HorizontalTickFrequency to 1 and the VerticalTickInterval to 1. The complete set of default variables should now look like this:
'Default Property Values:Const m_def_DisplayLegend = 0Const m_def_HighScale = 100Const m_def_LowScale = 0Const m_def_PlotPoints = 0Const m_def_Title = "0"Const m_def_VerticalTickInterval = 1Const m_def_HorizontalTickFrequency = 1Const m_def_ChartType = 0
(Notice the addition of the ChartType default.)
Secondly, we have to add a few of our own internal variables to help our methods pass data to each other. I will explain what each variable does after you have coded them:
Dim PlotData() As VariantDim PlotColors(15) As LongDim Legends(15) As LongDim ChartWidth As LongDim ChartHeight As LongDim TitleOffset As Long
Right. The first is a variant array that gets set when a call to the RegisterData method. This array becomes a two dimensional array holding information about each piece of data. The PlotColors and Legends variables simply hold the number of items each can hold (this is 15 because we use colours from the QBColor function). ChartWidth and ChartHeight specify the width and height of the chart, and TitleOffset holds the distance that the title is from the chart. Simple.
As I have already mentioned the RegisterData method, it would make sense to move onto that next. When calling the RegisterData method you will need to pass a two dimensional variant array. The array should look something like this:
Redim MyData(2, 20) As VariantMyData(0, 1) = 8
This sets the first point on the data series 0, to have a value of 8. You can use a loop to easily set up the array:
For i = 0 To 20MyData(0, i) = (Rnd 0.5) * 10 + INext I
This generates one data series with some 20 random values in it. You pass the array like this:
The RegisterData procedure does some clever things with the array, and stores it in the internal array variable.
You can easily manipulate different parts of the graph control using the properties built in. You can change the title and its font, using the default Font property to set the style for everything else (the legend and axis measurements). You can change the colour and description of a data series on the legend using the SetSeriesOptions procedure.
Well, apart from some extra code in the Write and Read Properties methods, the only code left is to actually draw the graph.
The main method is PlotChart, although it is called internally from the Refresh method. Mostly the Line method is used for drawing the graph, and some clever maths calculations are performed to work out where everything goes. The code gets quite involved here, so I wont try and explain how it all works. Take a look at the procedure below and see what you make of it:
Private Sub PlotChart()Dim Columns As LongDim n As Long, i As Long, d As DoubleDim x As Long, y As LongDim x1 As Long, y1 As LongDim PlotTop As Long, PlotBottom As Long, _ PlotLeft As Long, _ PlotRight As LongDim TickString As StringDim LowTick As DoubleDim BarWidth As Intege
n Error GoTo PlotError'determine horizontal extentColumns = UBound(PlotData, 2) + 1'adjust vertical scale if necessaryFor n = 1 To UBound(PlotData) For i = 0 To UBound(PlotData, 2) If m_HighScale < PlotData(n, i) Then m_HighScale = PlotData(n, i) If m_LowScale > PlotData(n, i) Then m_LowScale = PlotData(n, i) Next iNext n'define plot areaPlotLeft = 120 'may be overridden laterPlotRight = ChartWidthPlotTop = TitleOffsetPlotBottom = UserControl.ScaleHeight - (UserControl.TextHeight("X") * 2)'determine vertical tick scaleIf m_LowScale / m_VerticalTickInterval = Int(m_LowScale / m_VerticalTickInterval) Then LowTick = m_LowScaleElse LowTick = Int(m_LowScale / m_VerticalTickInterval) * m_VerticalTickIntervalEnd If'determine left spacing'check vertical captionsFor d = LowTick To HighScale Step m_VerticalTickInterval If PlotLeft < (UserControl.TextWidth(Format$(d)) + 120) Then PlotLeft = (UserControl.TextWidth(Format$(d)) + 120) End IfNext d'check caption for first horizontal tickIf PlotLeft < (UserControl.TextWidth(PlotData(0, 0)) / 2) + 60 Then PlotLeft = (UserControl.TextWidth(PlotData(0, 0)) / 2) + 60End If'draw row ticksFor d = LowTick To HighScale Step m_VerticalTickInterval y = PlotBottom - (PlotBottom - PlotTop) * ((d - LowTick) / (m_HighScale - LowTick)) UserControl.Line (PlotLeft, y)-Step(60, 0) UserControl.CurrentX = PlotLeft - (UserControl.TextWidth(Format$(d)) + 60) UserControl.CurrentY = y - (UserControl.TextHeight("X") * 0.5) UserControl.Print Format$(d)Next d'draw plot boxUserControl.Line (PlotLeft, PlotTop)-(PlotRight, PlotBottom), , B'draw column ticks and captionsFor i = 0 To Columns - 1 Step m_HorizontalTickFrequency x = PlotLeft + (((PlotRight - PlotLeft) / (Columns - 1)) * i) UserControl.Line (x, PlotBottom)-Step(0, -60) UserControl.CurrentX = x - (UserControl.TextWidth(PlotData(0, i)) / 2) UserControl.CurrentY = PlotBottom + (UserControl.TextHeight("X") * 0.5) UserControl.Print PlotData(0, i)Next i'base barwidth on series and pointsIf m_ChartType = Bar Then BarWidth = (PlotRight - (PlotLeft + 60)) / (Columns * UBound(PlotData)) - 30 If BarWidth <= 15 Then BarWidth = 30End If'plot graphFor n = 1 To UBound(PlotData) For i = 0 To UBound(PlotData, 2) 'determine coordinates x = PlotLeft + (((PlotRight - PlotLeft) / (Columns - 1)) * i) - 15 If PlotData(n, i) = LowTick Then y = PlotBottom - 15 Else y = (PlotBottom - ((PlotData(n, i) - LowTick) / (m_HighScale - LowTick) _ * (PlotBottom - PlotTop))) - 15 End If Select Case m_ChartType Case Bar 'adjust x for series x = PlotLeft + (((PlotRight - PlotLeft) / Columns) * i) - 15 x = x + 30 + ((n - 1) * (BarWidth + 30)) UserControl.Line (x, y)-(x + BarWidth, PlotBottom - 15), PlotColors(n - 1), BF Case Line 'draw data point If m_PlotPoints Then UserControl.Line (x, y)-Step(30, 30), PlotColors(n - 1), BF End If 'draw data graph If i <> 0 Then UserControl.Line (x + 15, y + 15)-(x1 + 15, y1 + 15), PlotColors(n - 1) End If x1 = x y1 = y End Select Next iNext nPlotExit: Exit Sub PlotError: If Err = 9 Then 'fail silently, not initialized yet Else Err.Raise 32007, "GraphLiteProject.GraphLite", _ "Error " & Err & " plotting graph: " & Error$(Err) End If Resume PlotExit End Sub
Gets rather confusing eh? Oh well, the DrawTitle method shouldnt be too bad. Basically after the position of the text has been calculated using the ScaleWidth and TextWidth properties, all thats left to do is to use the Print function to apply the text:
Private Sub DrawTitle() Dim f As Font Set f = UserControl.Font Set UserControl.Font = m_TitleFont UserControl.CurrentX = (UserControl.ScaleWidth - UserControl.TextWidth(m_Title)) / 2 UserControl.Print m_Title TitleOffset = UserControl.CurrentY Set UserControl.Font = fEnd Sub
The DrawLegend method is a bit more complicated, although it works on much the same principle of calculating the positions, and using the Line and Print methods to apply the data.
Well, that concludes the steps for making a very lightweight graphing control. You should now be able to drop this straight into your VB apps with ease, set a few properties and youre off! If you feel up to it, you could always add a few more properties and graph types, but dont go too far otherwise youll be competing with the MS Chart control in terms of size, which is why this control was written in the first place!
But if you do make any nice changes, please let me know so I can post an updated version. All thats left now to do is download a full working source code copy we are so generous :)