The Graph2D Class

The Graph2D Class

The class that I created to encapsulate the graphing methodology is called Graph2D. Three steps are required to use the Graph2D class: instantiate the class, add data, and render the graph. The following code snippet shows the bare necessities for creating a graph image that saves to disk. More detail for these methods will be given shortly.

 Graph2D graph = new Graph2D(); String strFilename; graph.AddPair( "Rick", 50 ); graph.AddPair( "Sam", 60 ); strFilename = graph.Render( Request.MapPath( "" ) ); 
 Dim graph as new Graph2D() Dim strFilename as String graph.AddPair("Rick", 50) graph.AddPair("Sam", 60) strFilename = graph.Render(Request.MapPath("")) 

The presentation code must use the file name that's returned from the Render() method. The file name can either be assigned to an ASP.NET server object, such as the Image or ImageButton objects, or output to the HTML stream with a call to the Response.Write() method.

I've written an application that uses the Graph2D class. The application lets users type in data pairs (column name and value), and then render and display the graph. You can see the text box into which users enter data in Figure 10.3.

Figure 10.3. The Application Allows You to Enter Data That Will Then Be Rendered Graphically.


Once the user clicks the Render the Graph button, the graph is rendered, saved to disk, and displayed at the top of the page. You can see the rendered graph in Figure 10.4.

Figure 10.4. The Graph Presents an Easy-to-Understand Depiction of the Data.


Two namespaces must be imported with the Imports (or using for C#) directive. These are the System.Collections.Specialized namespace (for the StringCollection class) and the System.Drawing.Imaging namespace (for the PixelFormat enumerator). The following shows the code that must be added to applications:

 using System.Collections.Specialized; using System.Drawing.Imaging; 
 Imports System.Collections.Specialized Imports System.Drawing.Imaging 

All the methods for the GraphAppVB application and the Graph2D class can be found in Table 10.3. This table lists the method names, the code module in which the methods are contained, the listing number in this chapter, and a short description. This information will help you as you read through the code, and especially when you reference the methods.

Table 10.3. The Graph2D Methods

Method Name

Code Module

Listing Number





This method adds data to the lists.




This method creates a Bitmap object and calls the helper methods, which all together render the graph.




This method draws the graph title.




This method draws the graph legend.




This method draws the bound lines of the graph.




This method draws the data and represents the values as graph bars.




This method draws the graph scale at the left side of the graph.

Width(), Height(), BackgroundColor(), TitleColor(), TitleSize(), Title()



These are the property methods for the Graph2D class.




This method outputs the link for the rendered graph image.




This method responds to a button click and populates the text box with data.




This method instantiates a Graph2D class, sets its properties, and calls its Render() method.

Member Variables and Default Values

A number of Graph2D member variables all maintain the state of the instantiated object. These variables such as m_strTitle (for the graph title), m_BackgroundColor (for the graph background color), and m_nCategoryCount (for the count of categories). include information about how the graph will be drawn.

Two very important member variables contain the data: the column name and value. These member variables are m_DataNames and m_dDataValues. Table 10.4 is a handy reference to the member variables. The table lists the variable type, name, description, and default value. Many of the member variables can be set using the Graph2D properties.

Listing 10.2 contains all the code that declares and initializes the Graph2D member variables.

Listing 10.2 The Graph2D Member Variables and Their Initialization
 ' Global colors with which we'll draw the graph bars. Dim m_Colors() As Color = { _     Color.Red, Color.Blue, Color.Green, Color.Magenta, _     Color.Cyan, Color.Brown, Color.Yellow, Color.OliveDrab, _     Color.Salmon, Color.Orange, Color.Gray, Color.Indigo, _     Color.Lime, Color.PaleGoldenrod, Color.SteelBlue, _     Color.YellowGreen} ' The next two members will contain the data element names and ' values. Dim m_DataNames As New StringCollection() Dim m_dDataValues() As Double ' Information with which we'll draw the title. Dim m_strTitle As String = "Title" Dim m_TitleColor As Color = Color.Red Dim m_nTitleSize As Integer = 20 Dim m_strTitleFontFamily As String = "Times New Roman" ' Overall image width and height. Dim m_nWidth As Integer = 780 Dim m_nHeight As Integer = 400 ' Information about the legend position and size. Dim m_nLegendX As Integer = 0 Dim m_nLegendBoxWidth As Integer = 0 Dim m_objLegendFont As Font Dim m_nLegendTextHeight As Integer = 0 ' Black pen and brush for use throughtout. Dim m_objBlackPen As New Pen(Color.Black) Dim m_objBlackBrush As New SolidBrush(Color.Black) ' The default background color. Dim m_BackgroundColor As Color = Color.White ' Number of categories and maximum data value; Dim m_nCategoryCount As Integer = 0 Dim m_dMaxValue As Double = 0 

Table 10.4. The Graph2D Member Variables







This array determines the colors with which the rectangles will be drawn. There are 16 colors in the array, so the code must ensure that only values from 0 to 15 are used.

As show in Listing 10.3



This collection contains the name for each data element.

No default data



This array contains the actual data values for each element.

No default data



This is the string that will be used to draw the graph title.




This is the color with which the title will be drawn.




This contains the size with which the title font will be created.




This string contains the name of the font family with which the title font will be created.

"Times New Roman"



The width of the chart in pixels.




The height of the chart in pixels.




The x-position (within the chart) of the left side of the legend.




The width in pixels of the legend box.




The Font object that is used to draw legend text.

No default creation



A black pen that is used to draw lines in the graph.

New Pen (Color.Black)



A black brush that is used to draw rectangles in the graph. Black)

New Solid-Brush(Color.



The background color for the graph.




The number of categories (or bars) in the chart.




The greatest (maximum) value of all elements in the m_Data-Values array.


Adding Data

No graph in the world is any good without data. While designing the class, I went through several ideas, most of which provided a DataSource property and a DataBind() method, as do many of the databound controls. This combination was convenient in many cases, but it didn't offer the flexibility I wanted. For that reason, I elected to simply provide a method with which calling code can add data pairs one at a time.

The AddPair() method shown in Listing 10.3 takes two arguments: the element name as a string and the element value as a double. The element name is placed into the m_DataNames StringCollection object, while the element value is placed into the m_dDataValues double array.

It's easy to add a string to the StringCollection object with an Add() method that adds a string to the collection. The array of doubles, however, is another matter. In the AddPair() method, a new array of the correct size is created, its members are initialized to the values currently in the m_dDataValues array, the new value is set in the last array element, and the m_dDataValues variable is set to the newly created array.

Listing 10.3 The AddPair() Method Adds Data to the Lists.
 ' Add a pair: element name and value. Public Function AddPair(ByVal strDataName As String, _   ByVal dDataValue As Double)     m_DataNames.Add(strDataName)     m_nCategoryCount = m_nCategoryCount + 1     Dim dDataValues(m_DataNames.Count) As Double     m_DataNames.CopyTo(dDataValues, 0)     dDataValues(m_DataNames.Count - 1) = dDataValue     m_dDataValues = dDataValues End Function 


The Render() method creates a Bitmap object, and then calls the DrawTitle(), DrawLegend(), DrawBounds(), DrawData(), and DrawScale() helper functions. The last thing this method does is to save the image to disk. This section breaks the Render() method down and describes it in detail.

Creating the Bitmap

The Render() method shown in Listing 10.4 is called once the data has been added to the Graph2D class and any properties have been set. The method expects a single string argument that contains the path into which the image will be saved. For the image to be saved, the directory must have the appropriate permissions.

The first thing that happens in the Render() method is that a file name is created based on the current clock ticks. This is an easy way to create a unique file name. However, for high-volume use, you should use an Application variable to absolutely guarantee a unique number. I show the change that you'd have to make using the Application variable in the section entitled "Enhancing the Graph2D Class."

CAUTION: The directory into which the rendered graph is to be saved must have the necessary permissions so that the image can be written to disk. Consult with your network administrator, but, if possible, you should give full control for the IUSR_SERVER and IWAM_SERVER accounts. You must at a minimum have read and write permissions.

A Bitmap object is created with the width and height that are contained in the m_nWidth and m_nHeight variables. A Graphics object is obtained from the Bitmap object using the Graphics.FromImage() method. The Bitmap object is set to the background color (found in m_BackgroundColor) by drawing a rectangle.

Last, the five helper methods are called, the image is saved to disk, and the file name is returned to the calling code.

Listing 10.4 The Render() Method Creates a Bitmap and Then Calls the Helper Methods, Which All Together Render the Graph.
 ' This method renders the graph to a disk file. It requires the path '   to the directory into which the file will be saved such as '   c:\inetpub\project\images It is also required that the directory '   grants write rights to the process. Public Function Render(ByVal strSavePath As String)     ' The image name is based on the system ticks. For high volume     '   use, ensure this is unique with an Application variable.     Dim strName As String = Convert.ToString(DateTime.Now.Ticks)     ' Create the canvas bitmap, get a Graphics object, and     '   initialize to the default background color.     Dim objBitmap As New Bitmap(m_nWidth, m_nHeight, _       PixelFormat.Format24bppRgb)     Dim g As Graphics = Graphics.FromImage(objBitmap)     g.FillRectangle(New SolidBrush(m_BackgroundColor), 0, 0, _       m_nWidth, m_nHeight)     ' Call helper methods to draw the title, legend, bounding lines,     '   data, and scale.     DrawTitle(g)     DrawLegend(g)     DrawBounds(g)     DrawData(g)     DrawScale(g)     ' Save the image to disk.     objBitmap.Save(strSavePath + "\\GraphImages\\Graph" + _       strName + ".gif", ImageFormat.Gif)     ' Return the image name.     Return (strName) End Function 
Drawing the Title

Drawing the title on the graph could be as easy as making a call to the DrawString() method at the upper-left corner of the bitmap. The result wouldn't look very nice, though; what we really need to do is center the title at the top of the graph.

Conceptually, the process is simple, but I have never taught a class in which a significant percentage of the students didn't have trouble understanding. Here's my best description of the conceptual processes.

Find the midpoint of the graph along the x-axis. This can be found in the Graph2D class with the expression m_nWidth / 2. Now you must find out how many pixels wide the title text is. A method named MeasureString() returns a SizeF object, in which the width and height of a text string is contained. Once you have the width of the string, you need to calculate half of that width, because that's the x-coordinate at which you'll draw the string. You calculate half of the string width in the DrawTitle() method with the expression sz.Width / 2. Figure 10.5 shows the calculations graphically.

Listing 10.5 The DrawTitle() Method Draws the Graph Title.
 ' Draw the graph title. Private Sub DrawTitle(ByVal g As Graphics)     Dim objTitleFont As New Font(m_strTitleFontFamily, m_nTitleSize)     Dim sz As SizeF = g.MeasureString(m_strTitle, objTitleFont)     Dim x As Integer = (m_nWidth / 2) - (sz.Width / 2)     Dim y As Integer = 10     g.DrawString(m_strTitle, objTitleFont, _        New SolidBrush(m_TitleColor), x, y) End Sub 
Figure 10.5. This Figure Shows How the Calculations Allow a String to Be Centered.


Drawing the Legend

The graph legend is drawn at the right side of the graph. For each data column, a color box indicates the color with which that column was drawn. This detail helps users identify the data and what it represents. The method that draws the legend is named DrawLegend() and can be found in Listing 10.6.

The first thing that happens in this method is that a Font object is created with which the legend text will be drawn. The size will be 12 points unless there are 20 data items or more, in which case the size will be 9 points. This option lets the legend adjust for a lot of entries by making the font size smaller.

It's important to find out how long the longest string is (that is, how many pixels wide). We can't really draw the box around the legend area until we know how big the box must be. And we don't know how big the box must be until we know how wide the widest string is. Similarly to what we did in the DrawTitle() method (Listing 10.5), we use the MeasureString() method to get the pixel width of each string. The widest string is recorded in the nLegendTextWidth variable, and the string height is recorded in the m_nLegendTextHeight variable.

With the widest string width, we calculate the legend box width by adding 16 pixels and 8 pixels: 16 pixels for the colored rectangles and 8 pixels for some padding space. We also check to make sure there's a minimum size.

With the legend box width and height, we next calculate the x- and y-coordinates to which the legend box rectangle will be drawn. The legend box values are recorded in some member variables because the size and position of the legend box will affect how other things on the graph are drawn.

A simple loop goes through each category. A filled rectangle is drawn and then outlined in black, and the text string with the column name is drawn to the left of the rectangles.

Listing 10.6 The DrawLegend() Method Draws the Graph Legend and Stores Its Position so That the Rest of the Graph Can Be Correctly Drawn.
 ' Draw the graph legend. Private Sub DrawLegend(ByVal g As Graphics)     ' Create the legend font. If there are more than 20 categories,     ' we'll need to make the font smaller.     ' "Times New Roman" is contained in m_strFontName by default.     If m_nCategoryCount < 20 Then             m_objLegendFont = New Font(m_strFontName, 12)     Else         m_objLegendFont = New Font(m_strFontName, 9)     End If     ' Find the max width and for the strings     Dim nLegendTextWidth As Integer = 0     Dim i As Integer     For i = 0 To m_nCategoryCount - 1         Dim s As SizeF = _           g.MeasureString(m_DataNames(i), m_objLegendFont)         If s.Width > nLegendTextWidth Then             nLegendTextWidth = s.Width         End If         If s.Height > m_nLegendTextHeight Then             m_nLegendTextHeight = s.Height         End If     Next     ' Calculate the width of the legend box. (We add 8 and 16     '   for the margin within the legend (8) and the margin between     '   the legend and the edge of the bitmap (16).)     Dim nLegendBoxWidth As Integer = nLegendTextWidth + 8 + 16     ' Make sure the legend text height is at least 16.     If m_nLegendTextHeight < 16 Then         m_nLegendTextHeight = 16     End If     ' Calculate the height of the legend box.     Dim nLegendBoxHeight As Integer = m_nLegendTextHeight     nLegendBoxHeight *= m_nCategoryCount     nLegendBoxHeight += 8     ' Calculate the upper left corner of the legend box.     Dim nLegendX As Integer = m_nWidth - nLegendBoxWidth - 4     Dim nLegendY As Integer = (m_nHeight - nLegendBoxHeight) / 2     ' Record into a class member the left size of the legend box.     m_nLegendX = nLegendX     m_nLegendBoxWidth = nLegendBoxWidth     ' Draw the legend box.     g.DrawRectangle(New Pen(Color.Black), _         nLegendX, nLegendY, _      nLegendBoxWidth, nLegendBoxHeight)     ' Draw the category item boxes.     For i = 0 To m_nCategoryCount - 1         ' Calculate x and y.         Dim x As Integer = nLegendX + 4         Dim y As Integer = nLegendY + 4 + i * m_nLegendTextHeight         ' Draw the colored rectangle.         g.FillRectangle(New SolidBrush(m_Colors(i And 15)), _          x, y, _          16, 16)         ' Draw a black border.         g.DrawRectangle(m_objBlackPen, x, y, 16, 16)         ' Draw the text.         g.DrawString(m_DataNames(i), _           m_objLegendFont, m_objBlackBrush, (x + 18), y)     Next     If m_nCategoryCount >= 20 Then         m_nLegendX -= 20     End If End Sub 
Drawing the Bounds

The graph has two lines. One goes along the left side of the data, and the other runs below the data. These two lines help frame the data and make it easier to view. The code in Listing 10.7 shows the DrawBounds() method that draws these two lines.

Listing 10.7 Drawing the Bounding Lines Is Simple, and the Code in the DrawBounds() Method is Short.
 ' Draw the bounding lines (left and bottom). Private Sub DrawBounds(ByVal g As Graphics)     Dim nChartBottom As Integer = m_nHeight - 70     ' Draw the left and bottom lines     g.DrawLine(m_objBlackPen, 50, 45, 50, m_nHeight - 70)     g.DrawLine(m_objBlackPen, 50, nChartBottom, _       m_nLegendX, m_nHeight - 70) End Sub 
Drawing the Data

Drawing the data is the main course of the Graph2D class. It's the featured element and as such is the most prominent item, appearing in the central portion of the graph. The method that does the work is named DrawData() and can be seen in Listing 10.8.

The first thing that must be done to draw the data is to find the maximum of all data values. Finding this value is essential because the largest value should occupy the entire vertical distance available. In other words, we'll use the maximum value, and the bar that's drawn for that value will go from the bottom of the graph to the top of the graph. All other data values will then be scaled in the proper proportion.

We then need to know how wide each column should be. To find this information, we simply take the graph width (minus the legend and some other space) and divide by the number of columns.

With the bar width and maximum data values calculated, the code loops through and draws each column. First, the height of the column bar is calculated. Then the x- and y-coordinates where the rectangle will be drawn are calculated.

There might be times when the height of a rectangle is zero. That value would result when a data value is zero, or is very small in comparison with the other data values. No rectangle is drawn when that is the case, and the calculated height of the rectangle is zero. But when the rectangle has a positive height, the rectangle is drawn in the appropriate color and then outlined in black.

The text string that names the column is drawn next. The text is drawn underneath the rectangles, and a line is then drawn connecting the text to the rectangle.

A variable named nLabelY is used to draw the text strings at different y values. Doing this prevents the strings from overwriting each other, thus making the graph impossible to read. The nLabelY variable is incremented and can have a value ranging from 0 through 2.

Listing 10.8 The DrawData() Method Does the Real Work by Calculating and Drawing the Bars That Represent the Data Values.
 ' Draw the data bars. Private Sub DrawData(ByVal g As Graphics)     Dim nChartBottom As Integer = m_nHeight - 70     ' Figure out the max value.     m_dMaxValue = 0     Dim nMaxIndex As Integer = 0     Dim i As Integer     For i = 0 To m_nCategoryCount - 1         If m_dMaxValue < m_dDataValues(i) Then             m_dMaxValue = m_dDataValues(i)             nMaxIndex = i         End If     Next     ' The legend is always 48, so we pad with 2 pixels     '   and subtract 50.     Dim nBarWidth As Integer = _       ((m_nLegendX - 50) / m_nCategoryCount)     Dim nLabelY As Integer = 2     Dim dHeight As Double = m_nHeight     ' The area above the graph where the title resides plus     '   the area below the graph where the labels reside is     '   a total of 115 pixels.     Dim dHeightForBars As Double = m_nHeight   115     For i = 0 To m_nCategoryCount - 1         Dim nBarHeight As Integer = _           ((m_dDataValues(i) * _           ((dHeightForBars / dHeight) * m_nHeight)) / _           m_dMaxValue)         ' 55 pixels gives enough room for a space to the left of the         '   graph plus the graph line.         Dim x As Integer = 55 + i * nBarWidth         Dim y As Integer = nChartBottom - nBarHeight         If nBarHeight > 0 Then             ' 10 pixels is a pleasing space to have between the bars.             g.FillRectangle(New SolidBrush(m_Colors(i And 15)), _                 x, y, nBarWidth - 10, nBarHeight)             g.DrawRectangle(m_objBlackPen, _                 x, y, nBarWidth - 10, nBarHeight)         End If         ' 66 pixels allows the height to be drawn below the graph's         '   title at the top.         g.DrawString(m_DataNames(i), _             m_objLegendFont, m_objBlackBrush, _             x + (nBarWidth - 10) / 2, _             m_nHeight - 66 + nLabelY * m_nLegendTextHeight)         Dim xx As Integer = x + (nBarWidth - 10) / 2         ' The numbers 66 and 70 were found to give the best         '   appearance through testing.         g.DrawLine(m_objBlackPen, _          xx, m_nHeight - 66 + nLabelY * m_nLegendTextHeight, _          xx, m_nHeight - 70)         nLabelY = nLabelY - 1         If nLabelY < 0 Then             nLabelY = 2         End If     Next End Sub 
Drawing the Scale and Saving

To the left side of the graph is the scale. This scale indicates the relative values for the data columns so that users can quickly estimate their values. The code that draws the scale is contained in a method named DrawScale() and can be seen in Listing 10.9.

If the data value is from 0 through 999, the value will be displayed as it is. If the value is from 1,000 through 999,999, the value will be displayed in thousands (the original value divided by 1,000) and a K symbol will appear to the right of the numeric value. If the value is from 1,000,000 through 999,999,999, then the value will be displayed in millions (the original value divided by 1,000,000) and an M symbol will appear to the right of the numeric value. The same principle applies to values in the billions.

An integer variable named nStep contains the calculated height for the three scale marks that will be drawn. The value of this variable is essentially the height of the data divided by four.

A loop counts from 0 to 3 and draws the scale marks and values.

Listing 10.9 The DrawScale() Method Makes It Easy for the User to Understand the Relative Values.
 ' Draw the scale to the left. Private Sub DrawScale(ByVal g As Graphics)     Dim dDivisor As Double = 1     Dim strModifier As String = ""     Const dBillions As double = 1000000000     Const dMillions As double = 1000000     Const dThousands As double = 1000     ' Billions...     If m_dMaxValue >= dBillions Then         strModifier = "B"         If m_dMaxValue >= dBillions Then             dDivisor = dBillions         ElseIf m_dMaxValue >= dBillions Then             dDivisor = dBillions         Else             dDivisor = dBillions         End If         ' Millions...     ElseIf m_dMaxValue >= dMillions Then         strModifier = "M"         If m_dMaxValue >= dMillions Then             dDivisor = dMillions         ElseIf m_dMaxValue >= dMillions Then             dDivisor = dMillions         Else             dDivisor = dMillions         End If         ' Thousands...     ElseIf m_dMaxValue >= dThousands Then         strModifier = "K"         If m_dMaxValue >= dThousands Then             dDivisor = dThousands         ElseIf m_dMaxValue >= dThousands Then             dDivisor = dThousands         Else             dDivisor = dThousands         End If     End If     ' The values in the following calculation give the best     '   appearance to the graph.     Dim nStep As Integer = (((m_nHeight - 70) - 45) / 4)     Dim objScaleFont As New Font("Times New Roman", 9)     Dim i As Integer     For i = 0 To 3         ' Since the graph's left margin line is at pixel 50,         '   we'll draw from that point to the left 5 pixels (45-50).         '   We'll start drawing at the top at pixel 46.         g.DrawLine(m_objBlackPen, 45, 46 + nStep * i, 50, _           46 + nStep * i)         Dim dThisNumber As Double = _           ((m_dMaxValue / dDivisor) / 4) * (4 - i)         Dim strNumber As String = _           dThisNumber.ToString("0.00") + strModifier         Dim sz As SizeF = g.MeasureString(strNumber, objScaleFont)         g.DrawString(strNumber, objScaleFont, m_objBlackBrush, _           45 - sz.Width, 46 + nStep * i - (sz.Height / 2))     Next End Sub 
Graph2D Properties

A number of Graph2D properties can be used to alter the appearance of the graph. These properties can be seen in Listing 10.10, and they include Width, Height, BackgroundColor, TitleColor, TitleSize, and Title. For a more detailed description of the member variables that correspond to these properties, refer to Table 10.4.

Listing 10.10 This Listing Shows the Property Methods for the Graph2D Class.
 Property Width() As Integer     Get         Width = m_nWidth     End Get     Set(ByVal Value As Integer)         m_nWidth = Value     End Set End Property Property Height() As Integer     Get         Height = m_nHeight     End Get     Set(ByVal Value As Integer)         m_nHeight = Value     End Set End Property Property BackgroundColor() As Color     Get         BackgroundColor = m_BackgroundColor     End Get     Set(ByVal Value As Color)         m_BackgroundColor = Value     End Set End Property Property TitleColor() As Color     Get         TitleColor = m_TitleColor     End Get     Set(ByVal Value As Color)         m_TitleColor = Value     End Set End Property Property TitleSize() As Integer     Get         TitleSize = m_nTitleSize     End Get     Set(ByVal Value As Integer)         m_nTitleSize = Value     End Set End Property Property Title() As String     Get         Title = m_strTitle     End Get     Set(ByVal Value As String)         m_strTitle = Value     End Set End Property 

ASP. NET Solutions - 24 Case Studies. Best Practices for Developers
ASP. NET Solutions - 24 Case Studies. Best Practices for Developers
ISBN: 321159659
Year: 2003
Pages: 175 © 2008-2017.
If you may any questions please contact us: