A Tale of a .Net Component (Part III) ByDevSource 2007-03-01
Article Rating: / 0
Rate This Article:
Add This Article To:
In the third installment of this series, Peter continues developing his component by adding properties that will help keep track of the minimum and maximum values of the chart.
This is the third article in a series that covers all the details of developing a .Net component. By following this process from start to finish, you may learn aspects of .NET development that are not provided in more specialized articles.
ADVERTISEMENT
In the previous article I promised that this in this installment I would be drawing the legend and axis labels and testing the control. It seems I was getting a bit ahead of myself—there are a few more matters that need attention before we get to those. This is the seemingly minor but actually very important way in which the chart scales its axes to accommodate the minimum and maximum X and Y values.
More on the XYTrace Class
The previous article explained how and why I decided that each trace, or data series, on the chart would be represented by an instance of the XYTrace class, with all the traces contained in a strongly typed collection. You saw how each XYTrace class contained a dynamic array of type PointF to hold the actual data and properties for the trace name, plot color, and a visible flag. This class needs a few more members before it is ready to use, and I decided to complete it before continuing with the XY chart component itself.
An XY chart needs to know the minimum and maximum X and Y values that it will display—in other words, what ranges of X and Y need to be encompassed by the chart axes? One approach is to scan through the data and determine what precisely these values are, and then scale the X and Y axes to exactly these values. As I'll get to in a moment, this is not always the best approach, but you should have it available as an option. Because the data are represented in the XYTrace class, it makes sense that this class should have the ability to report on the maximum and minimum values of X and Y for the data it contains.
To do so, I implemented 4 read-only properties called MaxX, MinX, MaxY, and MinY—the names should be self-explanatory. These are different from ordinary properties in that the value is not held in the class but rather is determined by code when the property is accessed. This is another advantage of property procedures over public variables—they are not limited to returning an existing value but can run code to find or calculate the property value.
For MaxX, the first step is to create a variable and set it to the smallest possible value for the data type, which you will remember is Single for the data to be plotted. To do this I took advantage of the data type's ability to report its own minimum possible value (and maximum too, as we'll see soon):
Dim max As Single = Single.MinValue
Why do I do this? By setting max to the smallest possible value, any actual data value will be larger than its initial value. Then, loop through all the data, which is held in the array pXYValues, and compare each X value with max, setting the maximum equal to any value that is larger than its current value:
Dim i As Integer
For i = 0 To pXYValues.GetUpperBound(0)
If pXYValues(i).X > max Then max = pXYValues(i).X
Next
Finally, return the final value of max:
Return max
The property procedures for these four properties are shown here. Place this code in the XYTrace.vb module following the private variable declarations that you added from the previous article.:
ReadOnly Property MaxX() As Single
Get
Dim max As Single = Single.MinValue
Dim i As Integer
For i = 0 To pXYValues.GetUpperBound(0)
If pXYValues(i).X > max Then max = pXYValues(i).X
Next
Return max
End Get
End Property
ReadOnly Property MaxY() As Single
Get
Dim max As Single = Single.MinValue
Dim i As Integer
For i = 0 To pXYValues.GetUpperBound(0)
If pXYValues(i).Y > max Then max = pXYValues(i).Y
Next
Return max
End Get
End Property
ReadOnly Property MinX() As Single
Get
Dim min As Single = Single.MaxValue
Dim i As Integer
For i = 0 To pXYValues.GetUpperBound(0)
If pXYValues(i).X < min Then min = pXYValues(i).X
Next
Return min
End Get
End Property
ReadOnly Property MinY() As Single
Get
Dim min As Single = Single.MaxValue
Dim i As Integer
For i = 0 To pXYValues.GetUpperBound(0)
If pXYValues(i).Y < min Then min = pXYValues(i).Y
Next
Return min
End Get
End Property
Dealing with Data Minimums and Maximums in the Chart Class
Now that we have added members to the XYTrace class to get the actual minimum and maximum for both X and Y, it's time to turn to the chart itself and determine how it will handle scaling the chart axes. Setting this scale to the precise minimum and maximum values may be appropriate for some situation, but it is not for others. For example:
If the axis scales are set to the precise minimum and maximum values, then the traces will touch the axes on all 4 sides. This may be okay, or it may not be.
Some charts are easier to read if the minimum Y axis value is 0 even if the minimum data value is not, because the relative differences between the multiple traces are easier to interpret.
Even if you use the automatic calculation of the maximum Y value, you may want the Y axis maximum to fall on an even boundary for ease of reading the chart. For example, if the maximum Y value is 27.8 you might want the maximum Y axis value to be 30.
To take these considerations into account, I decided that the XY chart should have the following capabilities:
To turn off automatic detection individually for all four quantities (maximum and minimum for both X and Y) and to manually set each axis limit.
To specify that the Y maximum, when automatically detected, would be adjusted up to the next multiple of a specified value. For example, if you specify a multiple of 5, then a Y maximum of 27.45 will be adjusted to 30, 3.5 will be adjusted to 5, and so on.
To implement these features I needed nine properties: four Boolean values that would indicate whether the corresponding maximum or minimum is determined automatically, four Single values that would hold the values (either determined internally or set from outside), and finally an Integer that would determine the multiple to which the maximum Y value is rounded to. The code for these properties is shown here. By now you know where to put this code in the ctlXY_Chart module.
' Default values for data ranges min/max if auto not used.
Private pMax_X As Double = 100
Private pMax_Y As Double = 100
Private pMin_X As Double = 0
Private pMin_Y As Double = 0
' If true, calculate X and Y max/min values from data.
' If false, use the corresponding properties.
Private pAutoX_Max As Boolean = True
Private pAutoY_Max As Boolean = True
Private pAutoX_Min As Boolean = True
Private pAutoY_Min As Boolean = True
' If non-zero, MaxY is adjusted to fall on the
' next multiple of this value.
Private pMaxY_Multiple As Integer = 0
Property MaxY_Multiple() As Integer
Get
Return pMaxY_Multiple
End Get
Set(ByVal value As Integer)
pMaxY_Multiple = value
End Set
End Property
Property AutoX_Max() As Boolean
Get
Return pAutoX_Max
End Get
Set(ByVal value As Boolean)
pAutoX_Max = value
End Set
End Property
Property AutoY_Max() As Boolean
Get
Return pAutoY_Max
End Get
Set(ByVal value As Boolean)
pAutoY_Max = value
End Set
End Property
Property AutoX_Min() As Boolean
Get
Return pAutoX_Min
End Get
Set(ByVal value As Boolean)
pAutoX_Min = value
End Set
End Property
Property AutoY_Min() As Boolean
Get
Return pAutoY_Min
End Get
Set(ByVal value As Boolean)
pAutoY_Min = value
End Set
End Property
Property Max_X() As Double
Get
Return pMax_X
End Get
Set(ByVal value As Double)
pMax_X = value
pAutoX_Max = False
End Set
End Property
Property Max_Y() As Double
Get
Return pMax_Y
End Get
Set(ByVal value As Double)
pMax_Y = value
pAutoY_Max = False
End Set
End Property
Property Min_X() As Double
Get
Return pMin_X
End Get
Set(ByVal value As Double)
pMin_X = value
pAutoX_Min = False
End Set
End Property
Property Min_Y() As Double
Get
Return pMin_Y
End Get
Set(ByVal value As Double)
pMin_Y = value
pAutoY_Min = False
End Set
End Property
Note that all of these properties have default values. This is usually a good idea so your component behaves in a predictable way even if the user does not explicitly save those properties. Note also that if any of the 4 max/min properties are explicitly set, the corresponding Auto property is set to False. After all, if the user explicitly sets a value for, say, MaxY, they surely do not want it determined automatically.
Stay Tuned...
We now have even more of the foundation of the XY chart component in place. It may not seem obvious to you yet, but many of the design decisions that have been made so far will have an impact on what happens next. If I have been careful in my design and implementation, that impact will be marked by the absence of problems.