From the February 2002 issue of MSDN Magazine

MSDN Magazine

ASP.NET

Create Snazzy Web Charts and Graphics On the Fly with the .NET Framework

Scott Mitchell
This article assumes you're familiar with Visual Basic .NET and ASP.NET
Level of Difficulty     1   2   3 
Download the code for this article: ASPDraw.exe (45KB)

SUMMARY Knowledge workers can understand data more effectively when raw numbers are presented in a graphical format. This is especially true when displaying database information on a Web page, where a simple chart can make the difference between a dry presentation and a vivid data source. In the past, creating dynamic, data-based charts on the fly in ASP required purchasing a third-party, image-generating COM component. Now with ASP.NET, developers can access the .NET Framework's drawing classes directly with C# to create dynamic images and charts.

One of the many factors that contributed to the phenomenal success of ASP was the ease with which developers could create data-driven Web pages. A very common data-driven Web page, especially in an intranet setting, is one that generates reports. Such a page often pulls figures from a database and presents the information to the visitor in an easy-to-digest manner. While displaying textual report information through an ASP page is simple enough, rendering such information in the form of graphical charts is another challenge entirely.
      I first started creating ASP pages back in January 1998 when asked to develop an intranet reporting system for the consulting company I worked for. At the end of each day, each consultant would spend a few minutes on the company intranet entering the hours he or she had worked and for what project. The managers of each consulting group wanted to be able to look at reports that showed past revenue by individual consultant and group. While displaying the raw figures in text form was one way to present the information, the managers requested various charts and graphs illustrating past and expected revenues.
      To do this I chose a client-side solution, the Microsoft® MSChart ActiveX® control, which was a hefty 750KB download and, at the time, wouldn't work on any browser software but the latest version of Microsoft Internet Explorer. Since I was developing in a controlled environment, these shortcomings weren't an issue, but they would have been if I had been working on an Internet solution.
      A more client-independent approach would have been to generate the graphs and charts in the ASP page as a GIF or JPG image file, and then send the file to the client's browser. Such a task is impossible to do in ASP without using a COM component. Fortunately for ASP developers, there are a number of third-party COM components that can be used to create server-side graphs. I found links to 16 such components on ASPIn.com (http://www.aspin.com/home/components/graphics/charts), which catalogs ASP resources, components, and information.
      With the advent of ASP.NET, however, you can create dynamic charts and graphs without a third-party COM component. The Microsoft .NET Framework contains an abundance of classes in the System.Drawing namespace that can be used—in a standalone Windows®-based application or on an ASP.NET Web page—to create and edit images in a variety of formats. Using these classes (I use C# in this article) you can quite easily create an ASP.NET Web page that, when requested, generates a chart based on database information. You can then send this image to the requesting client's browser.
      Before tackling a complete dynamic chart, let's first take a look at how you can create some simple images using code for an ASP.NET Web page.

Displaying a Simple Image using Code

      The System.Drawing namespace in the .NET Framework contains all of the classes you'll need to create and edit images. When creating images, you'll use two classes: the Bitmap class and the Graphics class. Think of the Bitmap class as your canvas and the Graphics class as your paintbrush. I'll show you how to use the Bitmap class to create a palette to draw on, and, when I've completed drawing, I'll use the Save method of this class to either save the drawing to the file system or send it directly to a stream. The Graphics class contains all of the methods that you'll need for drawing images, shapes, and strings.
      To create the canvas, I simply need to create a new instance of the Bitmap class, like so:
Bitmap myBitmap = new Bitmap(width, height);
Now that I have a canvas, I need to create an instance of the Graphics class, create the paintbrush, and specify a canvas to use. I can accomplish this using the static Graphics method FromImage, which takes an Image instance as a single parameter and returns a resulting Graphics instance. (I can pass in the instance of the Bitmap class, since it is derived from the Image class.)
Graphics myGraphics = Graphics.FromImage(myBitmap);
      At this point, I'm ready to start creating images, shapes, and strings on the canvas. Take a moment to fire up the .NET Framework documentation and examine the Graphics class. There you'll find a vast array of methods for drawing any shape you'll ever need. Notice that most of these methods come in two varieties: a DrawShape and a FillShape, such as DrawEllipse and FillEllipse, respectively. The Draw variant simply draws the outline of the shape, while the Fill variant draws the shape and fills the contents. For example, if you wanted to create a standard 468×60 pixel advertising banner like the one in Figure 1, you could use the code in Figure 2 in your Page_Load event handler.

Figure 1 Standard Ad Banner
Figure 1 Standard Ad Banner

Viewing the Image through an ASP.NET Web Page

      The code in Figure 2 is still missing one thing: a way to save the image. At the conclusion of the code, the MSDN® Magazine advertising banner is stored in-memory, but it does no good there since I want to be able to view this image from a Web page.
      In outputting an image to a Web page, there are two approaches you can take:
  1. Save the image to the Web server's file system and use an HTML img tag to display the created image.
  2. Stream the binary contents of the image directly to the OutputStream of the Response object.
Both approaches have advantages and disadvantages. If you are creating an image that is fairly static (such as the advertising banner example), you can employ the first option and persist the dynamically created image to a file. For future requests to the image-generating ASP.NET Web page, the image can be served from the Web server's file system, rather than recreating the image each time the Web page is requested. This approach, however, quickly loses its appeal if the images being created change over time, or if they can differ based on user input. Since each image generated needs a unique file name, as more and more images are generated, the file system would become inundated by these temporary images. For a more thorough discussion of this approach, check out the article "Charting with Office Web Components" by Bret Hern. In the next two sections, I'll examine both options more closely.
      To save an image to the file system (the first option), use the Save method of the Bitmap class (which is inherited from the Image class). When using this method, you should pass in two parameters: the physical path and file name of where to save the image to, and the format in which you want to save the image. To see the available image formats, view the ImageFormat members in the .NET Framework documentation. You'll note that the major image formats are available: GIF, ICON, JPEG, PNG, BMP, TIFF, and so on. If you add the following line of code to the last line of the Page_Load event handler in Figure 2, the MSDN Magazine advertising banner would be saved to C:\Inetpub\wwwroot\images\MSDNBanner.jpg:
// Save the image as a Jpeg
objBitmap.Save("C:\\Inetpub\\wwwroot\\images\\MSDNBanner.jpg",
ImageFormat.Jpeg);
Since the backslash is the escape character in C#, you must use two successive backslashes in a string to insert a literal backslash.
      In order to display this banner from an ASP.NET Web page, all you need to do is add an img tag that references the image saved by the ASP.NET Web page:
<img src="/images/MSDNBanner.jpg" />
Since the image will be generated and saved to the Web server's file system before the HTML content is sent to the browser, the image downloaded will be the one created by the ASP.NET page visited, assuming other users aren't visiting the same page simultaneously.
      Figure 3 illustrates the complete code for an ASP.NET Web page that uses the first option for creating an image and displaying it in the browser. The output of the code is shown in Figure 4.

Figure 4 Displaying Saved Images
Figure 4 Displaying Saved Images

      The second option, streaming the image output directly to the Response object's OutputStream property, comes in handy if you are generating a high volume of images. Imagine if an image-generating Web page generated different graphics based on, say, values input via a WebForm, and that you were employing the first option to display the dynamic image to the user. Rather than saving the image to some constant, hardcoded file name, you'd need to create unique file names for each image generated. This way, if a number of users were visiting the page concurrently and having different images constructed, each user would be guaranteed to receive his specific image. While creating unique file names is not difficult, you eventually end up with a number of old image files floating out on the Web server's file system.
      When using the second option, you do not need to concern yourself with saving the image to the file system; rather, you can send it directly to the user's browser. Since this option sends the binary data of the image file to the browser, you cannot include other content in the ASP.NET Web page. That is, you cannot stream the image and send along textual HTML from the same ASP.NET Web page. Rather, with this technique, you'll create an ASP.NET Web page that does nothing but display an image. Then, in order to display this image from a Web page, you reference this image-generating ASP.NET Web page using an img tag, like so:
<img src="GenerateImage.aspx" />
      In order to stream the image to the Response object's OutputStream, you need to use an alternate version of the Save method. To save an image's output to a stream, use the following version of the Save method:
objBitmap.Save(stream, imageFormat);
So, to stream the MSDN Magazine advertising banner to the OutputStream of the Response object as a JPEG, you'd simply use:
objBitmap.Save(Response.OutputStream, ImageFormat.Jpeg);
      The code in Figure 5 shows an ASP.NET Web page thats sole purpose is to send the binary content of a dynamically generated image to the client. This Web page cannot send both binary and textual content—rather, the Web page's sole task is to send the binary content of the MSDN Magazine banner image, which is created on the fly each time the page is requested.
      Note that in the first line of code in Figure 5 (the Page_Load event handler) I set the Response object's ContentType property to image/jpeg. Since I am sending the raw, binary content of the image, this setting tells the browser how to render this binary information. If you forget to explicitly set the ContentType, you won't notice this when viewing the page through Internet Explorer; Netscape will display the binary information as text, so be sure to set the ContentType for maximum cross-browser compatability.
      There are two ways to view the image that's output using this option. You can visit the ASP.NET page directly through your browser or you can reference the ASP.NET page in an img tag.
      The code in Figure 6 shows a simple HTML page that displays the MSDN Magazine advertising banner; the output of this HTML page looks just like the output in Figure 4.

Creating Pie Charts from Database Data

      Now that you know how to create a simple image from an ASP.NET Web page, you can create more complex (and useful) images. For the remainder of this article I'll look at how to use the .NET Framework drawing classes to create a pie chart from database information. I'll build all of this functionality into a set of functions in an ASP.NET Web page that will end up streaming the dynamically created pie chart's binary content to the Response object's OutputStream.
      While creating a set of page-level functions to display a pie chart will accomplish the task at hand, a more reusable solution would be to encapsulate this functionality into a custom-defined ASP.NET Web control or compiled custom control. One disadvantage of such an approach, though, would be that the custom-defined ASP.NET Web control or compiled custom control would have to save the image's file to the Web server's file system and then render it from an appropriate img tag. While this isn't difficult to accomplish, you'll have to deal with the disadvantages I mentioned earlier, including the fact that each time a chart is generated you'll keep adding to the list of images on the Web server's file system.
      Of course, there are techniques you could employ to limit the number of image files stored, such as having the control traverse through the directory of saved pie chart images and delete all files older than, say, 15 minutes. While this approach isn't difficult to implement, it (and the creation of a custom-defined ASP.NET Web control or compiled control) is beyond the scope of this article. However, I encourage you to research these topics. You can read more about retroactively deleting old files from Bret Hern's article, "Charting with Office Web Components" (mentioned earlier). For more information on creating and using Web pagelets or creating custom, compiled controls, review the documentation at http://www.gotdotnet.com/quickstart/aspplus/doc/webpagelets.aspx and http://www.gotdotnet.com/quickstart/aspplus/doc/webctrlauthoring.aspx.

The CreatePieChart Function

      To help make the pie chart generation process as generic as possible, I'll create a single function, CreatePieChart, with the following definition:
void CreatePieChart(String tablename, String dataColumnName, 
    String labelColumnName, String title, int width)
The first three input parameters specify the database information that I want to chart: tablename should be set to the name of the database table from which I will pull the data. The dataColumnName parameter specifies the name of the column from which I will pull the data for the pie chart. The labelColumnName parameter indicates the name of the database column from which the label for each data point should be extracted. The fourth parameter, title, specifies the chart's title. Width indicates how many pixels wide you want the pie chart to be.
      To better understand how to use these three parameters, imagine that you had a database table named SalesFromAssociates that tracked sales per employee. In addition, say that this table contained columns named SalesAssociateName and TotalSales, where the SalesAssociateName listed the name of each sales associate, and TotalSales listed the total revenue generated by a particular sales associate. If you wanted to create a pie chart showing the distribution of sales for each associate, with a title of "Sales Breakdown" and a width of 400 pixels, you would call the CreatePieChart function like so:
CreatePieChart("SalesFromAssociates", "SalesAssociateName", 
               "TotalSales", "Sales Breakdown", 400);
      That's all there is to it. The CreatePieChart function would then query the database, construct the pie chart image, and stream it out to the Response object's OutputStream.
      I am not passing database connection information into the CreatePieChart function, but you may want to add this as an optional parameter to increase the flexibility of CreatePieChart. In the implementation that I'll be examining, a default database connection is assumed and used in the function.
      Notice also that I am not specifying the height for the pie chart, only the width. This is because the height of the chart will depend on other factors, such as the size of the title and legend. (The size of the legend depends on the number of rows in the database table you are graphing.) The actual pie chart maintains a one-to-one pixel ratio for the width and the height. That is, if you specify a width parameter of 400, the pie chart itself will be 400 pixels tall. The overall image generated by CreatePieChart, though, will be larger to account for the title and legend.
      There are a number of potential enhancements you could make to increase the flexibility and usefulness of the CreatePieChart function, but I'll discuss these after I've examined the code. The CreatePieChart function begins by connecting to the data source to retrieve the information for the pie chart. Since I need to be able to move through the database data a number of times, I need to use a DataSet. The code in Figure 7 shows how to connect to a database, fill a DataSet, and then disconnect from the database.
      At this point I have a filled DataSet and have closed the connection to the database. Since the size of each wedge of a pie chart is equal to its value in proportion to the entire sum of the plotted data, I first need to calculate the total for the data I am plotting. This is accomplished in a simple for loop:
// find the total of the numeric data
float total = 0.0F, tmp;
int iLoop;
for (iLoop=0; iLoop < ds.Tables[0].Rows.Count; iLoop++)
{
    tmp = Convert.ToSingle(ds.Tables[0].Rows[iLoop][dataColumnName]);
    total += tmp;
}
      Next, I create the Font objects for the title and legend fonts. Once I have these Font objects created, I can determine how many pixels the legend and title will require:
// we need to create fonts for our legend and title
Font fontLegend = new Font("Verdana", 10),
    fontTitle = new Font("Verdana", 15, FontStyle.Bold);
      Now I need to calculate the amount of space needed for the title and legend. The space, in pixels, required by the title is equal to the number of pixels the fontTitle Font object requires. The Font object contains a Height property that provides this information. The height needed for the legend depends on how many data points I have, which is equal to the number of rows in the DataSet.
      Once I know both the legend and title heights, I can figure out the entire height for the image (the specified width plus the legend height plus the title height). Remember that the height of the pie chart is equal to the specified width, thereby maintaining a one-to-one pixel ratio.
// We need to create a legend and title, how big do these need to be?
// Also, we need to resize the height for the pie chart, respective
// to the height of the legend and title
const int bufferSpace = 15;
int legendHeight = fontLegend.Height * (ds.Tables[0].Rows.Count+1) + 
                   bufferSpace;
int titleHeight = fontTitle.Height + bufferSpace;
int height = width + legendHeight + titleHeight + bufferSpace;
int pieHeight = width;    // maintain a one-to-one ratio
      Once I know the heights for the various pieces of the image, I am ready to start setting parameters for the image. First, though, I should create a Rectangle object, specifying the coordinates within which to draw the pie chart:
// Create a rectange for drawing our pie
   Rectangle pieRect = new Rectangle(0,titleHeight,width,pieHeight);
      Additionally, I need to specify the colors to use for each wedge of the pie chart. One option would be to allow the user to pass in an ArrayList of predefined colors. The option I chose to use for this article was to create an ArrayList in the CreatePieChart function and populate it with random colors. This ArrayList, colors, contains as many items as there are rows in the DataSet:
// Create our pie chart, start by creating an ArrayList of colors
ArrayList colors = new ArrayList();
Random rnd = new Random();
for (iLoop = 0; iLoop < ds.Tables[0].Rows.Count; iLoop++)
        colors.Add(new SolidBrush(Color.FromArgb(rnd.Next(255), 
                   rnd.Next(255), rnd.Next(255))));
      Next I can (finally) begin creating the actual image. The Graphics class contains a FillPie method that draws a wedge of a pie chart. This method contains the definition:
FillPie(Brush, Rectangle, Single, Single);
I will be calling FillPie for each of the wedges I intend to draw in the pie chart. Before looking at the code that will perform the drawing of the pie chart, let's examine what the input parameters for the FillPie method should be. The brush parameter indicates the brush to use to fill the pie wedge. I'll create a new solid brush instance for each separate wedge, based on the corresponding element in the colors ArrayList.
      The rectangle parameter specifies the bounds within which I am drawing the pie chart (you'll remember that I already have defined the bounds via the Rectangle object pieRect). The first single parameter specifies the start degree of the pie wedge, while the second single parameter indicates how many degrees the wedge should consume.
      Each pie wedge will consume some percentage of the 360 degree circle. To find this percentage, I need to divide the wedge's value by the total value of all of the wedges (which I have in a local variable called total). This ratio, multiplied by 360, will yield the number of degrees a particular wedge should consume. Figure 8 shows the code used to draw the actual pie chart.
      To construct the pie chart, this code loops through each row in the DataSet and calls the FillPie method, starting from the currentDegree degree and sweeping over the needed number of degrees (the current wedge's data point divided by the sum of the data points, times 360). Once I've drawn the current wedge, I increment currentDegree the appropriate number of degrees.
      At the conclusion of the for loop that is shown in Figure 8, the pie chart has been drawn. However, I still need to place the title and legend of the pie chart. Drawing the title is simple enough—as I discussed in the first example of this article, the Graphics class contains a DrawString method for displaying text:
// Create the title, centered
StringFormat stringFormat = new StringFormat();
stringFormat.Alignment = StringAlignment.Center;
stringFormat.LineAlignment = StringAlignment.Center;

objGraphics.DrawString(title, fontTitle, blackBrush, 
             new Rectangle(0, 0, width, titleHeight), stringFormat);
      Displaying the legend, however, requires a bit more work. I begin by drawing a border around the legend space using the DrawRectangle method. Next, a for loop steps through each row in the DataSet. In each iteration of the loop, a small, filled rectangle is drawn with the data point's wedge's color. Next to this rectangle, the DrawString method is used to display the name of the data point (taken from the developer-specified labelColumnName input parameter) along with its data point value. Finally, at the conclusion of the for loop, a total of the data is displayed, as you can see in Figure 9.
      At this point in the process, all that remains for me to do is send the image's binary content out to the browser. You may recall that to accomplish this I first set the Response object's ContentType property to the appropriate MIME type ("image/jpeg" in this case) and then used the Save method of the Bitmap class to write out the image to the Response object's OutputStream property.
// Since we are outputting a Jpeg, set the 
// ContentType appropriately
Response.ContentType = "image/jpeg";

// Save the image to the OutputStream
objBitmap.Save(Response.OutputStream, 
               ImageFormat.Jpeg);
      That's it! An entire ASP.NET Web page that utilizes this function could consist of the code in Figure 10. This pie chart could be viewed directly from a browser or included in a Web page as an img tag. Figure 11 shows the resulting ASP.NET Web page directly through a browser.

Figure 11 The Chart in ASP
Figure 11 The Chart in ASP

Possible Enhancements

      The CreatePieChart function lacks the bang and pizzazz that a third-party graphing component may provide, but this function was created in less than 15 minutes, costs nothing (except my time), and, best of all, has the source code readily available for any future changes or enhancements you may be interested in making.
      One possible enhancement for the CreatePieChart function would be to add the ability to pass in a SQL string as opposed to a database table name. In its current state, the CreatePieChart graph can only create pie charts for databases that have very simple data models. Being able to specify a SQL string means you could create graphs where the data came from multiple tables, or graph only certain rows from a database table by specifying a WHERE clause.
      Another enhancement would be to either allow the developer to specify what colors to use for each pie wedge or have an XML file of possible colors. In the current implementation of CreatePieChart, the colors for each wedge are chosen randomly, which may lead to very similarly colored wedges in the chart. The Fruity Pops and Super Sugar Strike wedges in Figure 11, for example, are both a very similar shade of purple. By providing a default set of colors or allowing you to choose the color arrangement, you could ensure that the pie charts contain unique colors.

Conclusion

      Because ASP.NET allows you to use the classes from the .NET Framework, with a little bit of code you can create your own dynamic images from a Web page. These images can either be saved to the Web server's file system or streamed directly to the browser. All of the image-generation routines you will ever need are included in the .NET Framework. The charts and graphs you can create are limited only by your imagination.
      In this article I have looked at how to create a dynamic pie chart. In a book I coauthored, ASP.NET: Tips, Tutorials, and Code (see http://www.samspublishing.com/detail_sams.cfm?item=0672321432), I examine how to use similar techniques to create a dynamic bar chart based on the data from two ArrayLists (which can easily be populated with data from a database). Creating dynamic line graphs, scatter plots, and other common graphs are also possible. With ASP.NET it is now easy to present database information in the form of a graph.
For related articles see:
Charting with Office Web Components
Bitmap Class - .NET Framework Class Library
Graphics Class - .NET Framework Class Library
Obtaining Font Metrics - Platform SDK: GDI+ For background information see:
The ASP.NET QuickStart Tutorial
Scott Mitchell is the founder of 4GuysFromRolla.com and has been working with Microsoft Web technologies for the past four years. He has authored two ASP books and one ASP.NET book. Scott can be reached at Mitchell@4guysfromrolla.com.
Show: