Melbourne Web Solutions|Design - Providing Web Design and Development for Brevard County, Florida
Contact Search Archive portfolio quotesubmit DesignVsDev AboutTheProcess Services

Drawing on an Image to Create a Progress/Status Indicator

by Nicole Tuesday, January 11, 2011 1:16 PM

One of my current projects is an online application system driven by checklist items. The checklist items are very important, as the application work flow cannot continue until the respective checklist items for an online application are complete.

I wanted to give the administrative users a visual way to see the completion status of an application. I was thinking something of a progress indicator bar that would be filled based on the percentage completion of the application. I broke the issue down into two parts:

  • Determining the completion percentage of the online application.
  • Showing that percentage visually on an image in a way that it corresponded correctly to the completion percentage.

I have a long and rich background in Adobe Photoshop, so I decided to tackle the more creative and visual portion of this mini project first. After consulting Google Images for a rather fruitless search on progress indicator images, I fully created my own:

My attempt at a custom progress indicator image.

Okay, perhaps it's not the best, but I'm much more of a developer nowadays than graphic artist! The next part was to dynamically draw a rectangle onto the progress indicator image, with the width varying based on the application completion percentage.

So, to figure out the completion percentage, I went back to grade school with the following formula:

(Completed # Items / Total # Items) = Completed Percentage


I the coded the following method to return the completion status of an application.

  1.         public static decimal GetApplicationPercentCompleted(string applicationID)
  2.         {
  3.             decimal percentCompleted = 0;
  4.             OracleCommand comm = new OracleCommand();
  5.             DataTable dt = new DataTable();
  6.  
  7.             try
  8.             {
  9.                 // Oracle SQL/Command to return completion percentage
  10.                 // Omitted to stay on my DBA's good side :-)
  11.                 // But you have the formula!
  12.             }
  13.             catch (Exception ex)
  14.             {
  15.                 EmailError.EmailHandledError(ex);
  16.                 return percentCompleted;
  17.             }
  18.             finally
  19.             {
  20.                 comm.Dispose();
  21.  
  22.                 if (dt != null)
  23.                 {
  24.                     dt.Dispose();
  25.                 }
  26.             }
  27.  
  28.             return percentCompleted;
  29.         }



Next, I had to take that percentage number and create a rectangle on the progress image that visually corresponded to the percentage amount. I generate images with dynamic string text written on them often, but I had never given the great opportunity to explore the world of drawing shapes. I started with creating a new ASPX page called ApplicationStatus.aspx.

ASP.NET has several namespaces for dealing with images besides the most widely known System.Drawing. The following usings are required in the ApplicationStatus.aspx.cs code-behind file:

  1. using System;
  2. using System.Drawing;
  3. using System.Drawing.Drawing2D;
  4. using System.Drawing.Imaging;
  5. using System.IO;
  6. using System.Web;


To dynamically draw a rectangle onto the image, I have to know the correct X and Y coordinates on the image to place the upper left corner of the rectangle. To get these coordinates, I load the image into an image mapping program and map the applicable coordinates. Currently, I'm using Trellian Image Mapper. It's very basic, but it gets the job done, and best of all, it's free!

After I mapped my coordinates using the image mapper program, I coded the following on the ApplicationStatus.aspx Page_Load event.

  1.         protected void Page_Load(object sender, EventArgs e)
  2.         {
  3.             if (!IsPostBack)
  4.             {
  5.                 if (Request["ID"] != null) // Check to make sure that the ID query string is there
  6.                 {
  7.                     //Get an instance of the progress image
  8.                     System.Drawing.Image img = new System.Drawing.Bitmap(Server.MapPath("~/images/application_status_indicator.png"));
  9.  
  10.                     // Create an instance of the graphics class from the image
  11.                     Graphics graphicImage = Graphics.FromImage(img);  
  12.                     graphicImage.SmoothingMode = SmoothingMode.HighQuality;
  13.  
  14.                     // Create a new brush with a specified color
  15.                     SolidBrush brush = new SolidBrush(Color.FromArgb(149, 189, 56));
  16.  
  17.                     // Call my method to determine the application completion percentage
  18.                     decimal percentCompleted = EventApplication.GetApplicationPercentCompleted(Request["ID"]);
  19.                    
  20.                     // Inside of progress indicator block is 274 pixels wide, get corresponding width percentage
  21.                     decimal barWidth = (274 * percentCompleted);
  22.  
  23.                     // Draw the rectangle onto the image using coordinates from an image mapping program
  24.                     graphicImage.FillRectangle(brush, 16, 5, Convert.ToInt32(barWidth), 28);
  25.  
  26.                     if (percentCompleted > new decimal(.01))
  27.                     {
  28.                         // Write the rounded percentage as a string onto the rectangle (provides greater detail for the user)
  29.                         graphicImage.DrawString(Math.Round((percentCompleted * 100), 2).ToString() + "%",
  30.                                                 new Font("Verdana", 12, FontStyle.Bold), SystemBrushes.WindowText, new Point(21, 12));
  31.                     }
  32.  
  33.                     // Save image to new memory stream
  34.                     MemoryStream ms = newMemoryStream();
  35.                     img.Save(ms, ImageFormat.Png);
  36.  
  37.                     // Specific the content type (I'm using a PNG image)
  38.                     Response.ContentType = "image/png";
  39.  
  40.                     // Write memory stream to the response output stream and the image appears on the page
  41.                     ms.WriteTo(HttpContext.Current.Response.OutputStream);
  42.                     graphicImage.Dispose();
  43.  
  44.                     // Clean up
  45.                     HttpContext.Current.Response.Flush();
  46.                     HttpContext.Current.Response.Close();
  47.                     HttpContext.Current.Response.End();
  48.                 }
  49.             }
  50.         }



Here's a break down of what the code above is doing, in addition to the code comments.

  • Line 5: I'm passing the online application ID as a query string, so first I check to make sure that's not null. If it is, the request isn't coming from the right place and I'm not doing anything with it.
  • Line 8: I'm creating a new image of the System.Drawing.Image class from the progress bar image file I created and saved in the images folder of my application's directory.
  • Line 11: I'm creating a new instances of the Graphics class so that I can dynamically "draw" on the image instance.
  • Line 15: I'm creating a new SolidBrush to use to "draw" the rectangle. I'm specifying a color From RGB, but you can also do something like Color.AliceBlue and use a system defined color.
  • Line 18: I'm calling my static method to recent my application completion percentage. I pass in the "ID" query string, which is why I need to make sure it is not null on Line 5.
  • Line 21: The inside of the progress indicator image that I created is 274 pixels, so I'm multiplying that by the percentCompleted decimal to get the corresponding with percentage to "draw".
  • Line 24: Here I call the Graphics.FillRectangle method to draw the rectangle onto the image. This method takes in the brush I created on line 15, the X and Y coordinates to start drawing the upper left corner of the rectangle, the barWidth decimal from line 21 converted to an integer, and the height of the rectangle (which I determined in Photoshop to be 28 pixels). This will draw a rectangle at the starting coordinates that is 28 pixels tall and barWidth wide, whatever that is determined to be.
  • Lines 26-31: I take it a step further and draw the rounded exact percentage onto the rectangle bar for further detail. This uses the Graphics.DrawString method which takes in the string value of the completion percentage, which I'm rounding using Math.Round. This method also takes in a Point, which is another X and Y coordinate that I determined to be 21 and 12, respectively.
  • Lines 34-47: To actually produce the image on the screen, I'm saving the image instance to a new MemoryStream and then writing that memory stream to the pages response OutputStream, which will then write the image to the page.


Writing the image to pages OutputStream will only produce the image on the page and no other content, so I'm using the ApplicationStatus.aspx page that generates the image on an iframe window on the parent page that allows administrators to update an online application's status. The HTML/ASPX code for the iframe is as follows:

  1.         <table cellpadding="2" cellspacing="2" style="width: 100%; padding-bottom:10px; padding-top:10px;">
  2.             <tr>
  3.                 <td align="center">
  4.                     <b>Application Checklist Completion Status:</b>
  5.                     <br />
  6.                     <iframe runat="server" id="ifAppStatusImage" height="100px" width="366px" title="Application Status"
  7.                        scrolling="no" frameborder="0" align="middle" />
  8.                 </td>
  9.             </tr>
  10.         </table>



The last thing to do is to dynamically assign the src attribute of the iframe window on the parent page load, so that the "ID" query string value can be assigned correctly. Check out line 12 for that:

  1.         protected void Page_Load(object sender, EventArgs e)
  2.         {
  3.             if (!IsPostBack)
  4.             {
  5.                 if (Request.QueryString["ID"] != null)
  6.                 {
  7.                     if (Regex.IsMatch(Request.QueryString["ID"].ToString(), "^\\d{1,10}$"))
  8.                     {
  9.                         string eventAppID = Request.QueryString["ID"].ToString();
  10.                        
  11.                         // Assign iframe src and query string
  12.                         ifAppStatusImage.Attributes["src"] = "ApplicationStatus.aspx?ID=" + eventAppID;
  13.                        
  14.                          // Other code omitted
  15.                     }
  16.                 }
  17.             }
  18.         }



So, what does the final result look like? Here are a couple examples:

Progress Indicator Example 1

Progress Indicator Example 2