Click here to Skip to main content
15,887,027 members
Articles / Web Development / ASP.NET

A simple thermometer chart for ASP.NET

Rate me:
Please Sign up or sign in to vote.
3.72/5 (10 votes)
15 May 2007Ms-PL1 min read 78.1K   1.5K   41   12
A simple thermometer chart for ASP.NET.

Thermometer Chart

Introduction

jThermometer is a class for creating thermometer charts of the type used generally to show progress towards a fund raising goal. It arose out of my involvement in a charity walkathon. The organizer wanted to have a thermometer-type chart on a the walkathon's home page to indicate progress towards fund raising goals. I was unable to find anything free that seemed appropriate for the site, so I decided to create my own and share it here on CodeProject.

The chart uses GDI+ to compose an image and stream it out to the browser.

Using the code

For the purposes of portability, I've separated the chart into two parts:

  • jThermometer is a class that creates a bitmap using GDI+ and streams it out to the current request as a GIF image.
  • Thermometer.aspx is a web page that accepts various parameters controlling the appearance of the Thermometer chart and that can be referenced by an <IMG> tag on another web page.

Thermometer.aspx uses these query string values:

MIN:Value displayed at the bottom of the thermometer.
MAX:Value displayed at the top of the thermometer
IV:Value indicated on the thermometer.
T:Title displayed at the top of the thermometer
VT:Type of value (1=currency, 2=decimal, 3=integer)

Further work

At present, the thermometer does not scale. I'd like to have it automatically scale based on user-provided dimensions. There are also a lot of appearance-related aspects that should be controllable via properties, such as text color, indicator color, and the color of the mercury.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Web Developer
United States United States
Joe is a software engineer. He lives in Pennsylvania where he develops software for manufacturing and human services when he is not at the park with his three boys.

Comments and Discussions

 
QuestionC# Version Pin
cal5barton22-May-13 7:18
cal5barton22-May-13 7:18 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey18-Feb-12 3:10
professionalManoj Kumar Choubey18-Feb-12 3:10 
GeneralTYTY Pin
Peter Scholten22-Apr-09 10:33
Peter Scholten22-Apr-09 10:33 
GeneralTHANKS! Pin
Suriel702-Apr-09 10:38
Suriel702-Apr-09 10:38 
GeneralLooks good, but... Pin
MikeWardOK21-Mar-09 4:23
MikeWardOK21-Mar-09 4:23 
GeneralThanks, Joe! Pin
kwame dupre27-Jan-09 10:45
kwame dupre27-Jan-09 10:45 
GeneralMy vote of 2 Pin
Dave Kreskowiak12-Jan-09 6:37
mveDave Kreskowiak12-Jan-09 6:37 
GeneralThis is great work Pin
ivanhoe22-Oct-07 16:24
ivanhoe22-Oct-07 16:24 
GeneralNice Job! Pin
Mike Fed21-Sep-07 8:51
Mike Fed21-Sep-07 8:51 
GeneralThanks for the great work Pin
Mohamed H. Elsherif23-Jul-07 1:40
Mohamed H. Elsherif23-Jul-07 1:40 
GeneralGood Work Pin
Hassan Tariq Butt15-May-07 20:32
professionalHassan Tariq Butt15-May-07 20:32 
SuggestionRe: Good Work (Added C# ability....in a messy way) Pin
ThomasWest4-Nov-14 13:04
ThomasWest4-Nov-14 13:04 
First: Keep in mind that I spent about 30 minutes doing the conversation etc etc. I didn't clean up the code. I found it useful when creating a generic handler in asp.net as it was intent to better understand building Generic Hanlders for the Image control. Here's what I did:

I added an image control to my page in Visual Studio 2012. Then I create a Generic Handler and called it FundraisingGoalsHandler.ashx. After that, I converted the source from this project to C# and added to FundraisingGoalsHandler.ashx.

For the Image control on my page I added the following ImageUrl (pointed to the FundraisingGoalsHandler.ashx...and walla! It worked. :
C#
imgThermometer.ImageUrl = "FundraisingGoalsHandler.ashx";


Past this code into FundraisingGoalsHanlder.ashx:

XML
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.IO;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace MyProjectNamespace
{
    /// <summary>
    /// Summary description for FundraisingGoalsHandler
    /// </summary>
    public class FundraisingGoalsHandler : IHttpHandler
    {
        //---Amount of space between top of image and top of title text
        private const int MARGIN_TOP = 20;
        //---Amount of space between the bottom of the thermometer and the
        private const int MARGIN_BOTTOM = 20;
        //   bottom of the image.
        //---Left position of the actual value
        private const int MARGIN_LEFT = 20;

        //---Amount of space between top of title text and top of thermometer
        private const int TITLE_BOTTOMMARGIN = 55;

        //---Amount of space between top of image and top of thermometer
        private const int OUTERBAR_TOP = MARGIN_TOP + TITLE_BOTTOMMARGIN;

        //---Bounds of elipse for curve at top of thermometer.
        private const int OUTERBAR_WIDTH = 30;

        private const int OUTERBAR_ARCHEIGHT = 30;
        //---Bounds of elipse for thermometer bulb
        private const int OUTERBULB_WIDTH = 60;

        private const int OUTERBULB_HEIGHT = 50;
        //---Bounds of elipse for curve at top of mercury
        private const int INNERBAR_WIDTH = 18;

        private const int INNERBAR_ARCHEIGHT = 30;
        //---Amount of space between top of mercury and top of thermometer
        private const int INNERBAR_TOPMARGIN = 10;
        //---Amount of space between thermometer bulb and mercury bulb.
        private const int INNERBAR_BULBMARGIN = 10;


        //---Bounds of elipse for mercury bulb
        private const int INNERBULB_WIDTH = 40;

        private const int INNERBULB_HEIGHT = 35;
        //---Distance of the axis indicators from the thermometer
        private const int AXIS_MARGIN = 0;
        //---Width of the primary indicator
        private const int AXIS_IN1_WIDTH = 35;
        //---Width of the secondary indicator
        private const int AXIS_IN2_WIDTH = 20;

        //---Color of the title text
        private Color TitleColor = Color.Gray;
        //---Color of the actual value text
        private Color ActualValueColor = Color.Gray;
        //---Color of the indicated value text
        private Color IndicatedValueColor = Color.Gray;

        //---X position of the center of the thermometer within the image
        private int miThermoCenter = 190;
        //---Width of the image
        private int miWidth = 240;
        //---Height of the image
        private int miHeight = 400;

        //---Height of the thermometer
        private int miOuterbar_Height = 265;

        //---Maximum height of the mercury
        private int miInnerbar_Height = 240;

        //---The ValueType enum affects the formatting of value text (IE: whether decimal places and the currency sign are displayed.
        public enum ValueType : int
        {
            Currency = 1,
            Decimal = 2,
            Integer = 3
        }

        /// <summary>
        /// Draws the thermometer (border without interior mercury)
        /// </summary>
        /// <param name="oGraphic">Graphic on which to do the drawing</param>
        /// <remarks></remarks>

        private void DrawThermometer(ref Graphics oGraphic)
        {
            int iCenterX = miThermoCenter;
            int iTopY = OUTERBAR_TOP;
            int iBottomY = miHeight - MARGIN_BOTTOM;

            int iOuterX_Left = iCenterX - (OUTERBAR_WIDTH / 2);
            int iOuterX_Right = iCenterX + (OUTERBAR_WIDTH / 2);

            int iOuterYArc_Top = iTopY;
            int iOuterYArc_Bottom = iTopY + (OUTERBAR_ARCHEIGHT / 2);

            //---Draw top arc
            oGraphic.DrawArc(new Pen(Color.Black), iOuterX_Left, iTopY, OUTERBAR_WIDTH, OUTERBAR_ARCHEIGHT, 180, 180);

            //---Draw lines on each side
            oGraphic.DrawLine(new Pen(Color.Black), iOuterX_Left, iOuterYArc_Bottom, iOuterX_Left, (iOuterYArc_Bottom + (miOuterbar_Height - OUTERBAR_ARCHEIGHT)));
            oGraphic.DrawLine(new Pen(Color.Black), iOuterX_Right, iOuterYArc_Bottom, iOuterX_Right, (iOuterYArc_Bottom + (miOuterbar_Height - OUTERBAR_ARCHEIGHT)));

            //---Draw bulb at bottom
            int iBulb_Left = iCenterX - OUTERBULB_WIDTH / 2;
            int iBulb_Top = iOuterYArc_Bottom + (miOuterbar_Height - OUTERBAR_ARCHEIGHT) - 2;

            oGraphic.DrawArc(new Pen(Color.Black), iBulb_Left, iBulb_Top, OUTERBULB_WIDTH, OUTERBULB_HEIGHT, 304, 292);
        }

        /// <summary>
        /// Draws the mercury indicator inside the thermometer.
        /// </summary>
        /// <param name="oGraphic">Graphic on which to do the drawing</param>
        /// <param name="dFillPercent">Percent of the thermometer that is filled (0.0-1.0)</param>
        /// <remarks></remarks>

        private void DrawMercury(ref Graphics oGraphic, ref float dFillPercent)
        {
            int iFillHeight = Convert.ToInt16(miInnerbar_Height * dFillPercent);
            int iFillTop = miInnerbar_Height - iFillHeight + OUTERBAR_TOP + INNERBAR_TOPMARGIN;

            int iCenterX = miThermoCenter;
            int iTopY = OUTERBAR_TOP + INNERBAR_TOPMARGIN;
            int iBottomY = miHeight - MARGIN_BOTTOM;

            int iInnerX_Left = iCenterX - (INNERBAR_WIDTH / 2);
            int iInnerX_Right = iCenterX + (INNERBAR_WIDTH / 2);

            int iInnerYArc_Top = iFillTop;
            int iInnerYArc_Bottom = iFillTop + (INNERBAR_ARCHEIGHT / 2);

            //---For bulb at bottom
            int iBulb_Left = iCenterX - INNERBULB_WIDTH / 2;
            int iBulb_Top = iTopY + miInnerbar_Height;

            //---Shadow
            //---Draw top arc
            oGraphic.FillEllipse(new SolidBrush(Color.DarkRed), iInnerX_Left - 1, iFillTop, INNERBAR_WIDTH + 1, INNERBAR_ARCHEIGHT);

            oGraphic.FillEllipse(new SolidBrush(Color.DarkRed), iBulb_Left, iBulb_Top, INNERBULB_WIDTH, INNERBULB_HEIGHT);

            //---Draw Bar
            oGraphic.FillRectangle(new SolidBrush(Color.DarkRed), iInnerX_Left, Convert.ToInt32(iFillTop + (INNERBAR_ARCHEIGHT / 2)), INNERBAR_WIDTH, iFillHeight);

            //---Actual
            //---Draw top arc
            oGraphic.FillEllipse(new SolidBrush(Color.Red), iInnerX_Left + 3, iFillTop + 4, INNERBAR_WIDTH - 6, INNERBAR_ARCHEIGHT - 6);

            oGraphic.FillEllipse(new SolidBrush(Color.Red), iBulb_Left + 3, iBulb_Top + 3, INNERBULB_WIDTH - 6, INNERBULB_HEIGHT - 6);

            //---Draw Bar
            oGraphic.FillRectangle(new SolidBrush(Color.Red), iInnerX_Left + 3, Convert.ToInt32(iFillTop + (INNERBAR_ARCHEIGHT / 2)), INNERBAR_WIDTH - 6, iFillHeight);

            //---Draw bulb
            oGraphic.DrawEllipse(new Pen(Color.IndianRed), iBulb_Left + 3, iBulb_Top + 3, INNERBULB_WIDTH - 5, INNERBULB_HEIGHT - 5);
        }

        /// <summary>
        /// Draws the title text
        /// </summary>
        /// <param name="oGraphic">Graphic on which to do the drawing</param>
        /// <param name="sText">Text of title</param>
        /// <remarks></remarks>

        private void DrawTitle(ref Graphics oGraphic, string sText)
        {
            System.Drawing.SolidBrush oBrush = new System.Drawing.SolidBrush(TitleColor);
            Font oFont = new Font("Arial", 14, FontStyle.Bold);

            SizeF oSize = new SizeF();
            oSize = oGraphic.MeasureString(sText, oFont);

            float iX = 240 / 2 - oSize.Width / 2;
            float iY = 20;
            oGraphic.DrawString(sText, oFont, oBrush, iX, iY, System.Drawing.StringFormat.GenericTypographic);

        }

        /// <summary>
        /// Draws the actual value
        /// </summary>
        /// <param name="oGraphic">Graphic on which to do the drawing</param>
        /// <param name="dValue">The actual value</param>
        /// <param name="iType">The type of the actual value</param>
        /// <remarks></remarks>

        private void DrawActualValue(ref Graphics oGraphic, float dValue, ValueType iType)
        {
            string sText = dValue.ToString("C");

            System.Drawing.SolidBrush oBrush = new System.Drawing.SolidBrush(ActualValueColor);
            Font oFont = new Font("Arial", 14, FontStyle.Bold);

            SizeF oSize = new SizeF();
            oSize = oGraphic.MeasureString(sText, oFont);

            float iX = MARGIN_LEFT;
            float iY = 380 - oSize.Height;
            oGraphic.DrawString(sText, oFont, oBrush, iX, iY, System.Drawing.StringFormat.GenericTypographic);

        }

        /// <summary>
        /// Draws the indicator lines and associated values
        /// </summary>
        /// <param name="oGraphic">Graphic on which to do the drawing</param>
        /// <param name="dMin">Min value</param>
        /// <param name="dMax">Max value</param>
        /// <param name="iCount">Number of indicator values to display</param>
        /// <param name="iShowMarkerInterval">Number of sub-interval markers to display for each interval</param>
        /// <param name="iType">Type of value</param>
        /// <remarks></remarks>

        private void ShowAxisValues(ref Graphics oGraphic, float dMin, float dMax, int iCount, int iShowMarkerInterval, ValueType iType)
        {
            int iXLeft1 = miThermoCenter - OUTERBAR_WIDTH / 2 - AXIS_IN1_WIDTH;
            float iXLeft2 = miThermoCenter - OUTERBAR_WIDTH / 2 - AXIS_IN2_WIDTH;
            float iXRight = miThermoCenter - OUTERBAR_WIDTH / 2 - AXIS_MARGIN;

            float dSpace = miInnerbar_Height / iCount;
            float dSubSpace = dSpace / (iShowMarkerInterval + 1);

            float dValue = (dMax - dMin) / iCount;

            int iYTop = OUTERBAR_TOP + INNERBAR_TOPMARGIN;

            float myRefVariable;
            float myOtherVariable;

            for (int iIndicator = 0; iIndicator <= iCount; iIndicator++)
            {
                myRefVariable = dMax - (Convert.ToSingle(dValue) * iIndicator);
                oGraphic.DrawLine(new Pen(Color.Black), iXLeft1, iYTop, iXRight, iYTop);
                ShowAxisText(ref oGraphic, ref myRefVariable, ref iXLeft1, ref iYTop, iType);

                if (iIndicator < iCount)
                {
                    for (int iSubIndicator = 1; iSubIndicator <= iShowMarkerInterval; iSubIndicator++)
                    {
                        myOtherVariable = iYTop + dSubSpace * iSubIndicator;
                        oGraphic.DrawLine(new Pen(Color.Gray), iXLeft2, Convert.ToSingle(myOtherVariable), iXRight, Convert.ToInt32(iYTop) + dSubSpace * iSubIndicator);
                    }
                }

                iYTop = Convert.ToInt16(iYTop + dSpace);
            }
        }

        /// <summary>
        /// Displays the text of the indicator value.
        /// </summary>
        /// <param name="oGraphic">Graphic on which to do the drawing</param>
        /// <param name="dValue">Indicator value</param>
        /// <param name="iXRightPos">X position at which text should be right-aligned.</param>
        /// <param name="iYCenter">Y position at which text should be centered</param>
        /// <param name="iType">Type of text</param>
        /// <remarks></remarks>

        private void ShowAxisText(ref Graphics oGraphic, ref float dValue, ref int iXRightPos, ref int iYCenter, ValueType iType)
        {
            string sText = dValue.ToString("C");

            System.Drawing.SolidBrush oBrush = new System.Drawing.SolidBrush(IndicatedValueColor);
            Font oFont = new Font("Arial", 11, FontStyle.Bold);

            SizeF oSize = new SizeF();
            oSize = oGraphic.MeasureString(sText, oFont);

            float iX = iXRightPos - oSize.Width;
            float iY = iYCenter - oSize.Height / 2;

            oGraphic.DrawString(sText, oFont, oBrush, iX, iY, System.Drawing.StringFormat.GenericTypographic);
        }

        /// <summary>
        /// Formats value for display
        /// </summary>
        /// <param name="dValue">Value</param>
        /// <param name="iType">Type of value</param>
        /// <returns></returns>
        /// <remarks></remarks>
        ///

        public string DecimalToWords(float d)
        {
            //Grab a string form of your decimal value ("12.34")
            var formatted = d.ToString();

            if (formatted.Contains("."))
            {
                //If it contains a decimal point, split it into both sides of the decimal
                string[] sides = formatted.Split('.');

                //Process each side and append them with "and", "dot" or "point" etc.
                return NumberToText(Int32.Parse(sides[0])) + " and " + NumberToText(Int32.Parse(sides[1]));
            }
            else
            {
                //Else process as normal
                return NumberToText(Convert.ToInt32(d));
            }
        }

        public static string NumberToText(int number)
        {
            string words = "";

            try
            {
                if (number == 0)
                    return "zero";

                if (number < 0)
                    return "minus " + NumberToText(Math.Abs(number));

                if ((number / 1000000) > 0)
                {
                    words += NumberToText(number / 1000000) + " million ";
                    number %= 1000000;
                }

                if ((number / 1000) > 0)
                {
                    words += NumberToText(number / 1000) + " thousand ";
                    number %= 1000;
                }

                if ((number / 100) > 0)
                {
                    words += NumberToText(number / 100) + " hundred ";
                    number %= 100;
                }

                if (number > 0)
                {
                    if (words != "")
                        words += "and ";

                    var unitsMap = new[] { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" };
                    var tensMap = new[] { "zero", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety" };

                    if (number < 20)
                        words += unitsMap[Convert.ToInt16(number)];
                    else
                    {
                        words += tensMap[Convert.ToInt16(number) / 10];
                        if ((number % 10) > 0)
                            words += "-" + unitsMap[Convert.ToInt16(number) % 10];
                    }
                }
            }
            catch (Exception ex)
            {
                words = "error";
            }

            return words;
        }

        /// <summary>
        /// You will need to configure this handler in the Web.config file of your
        /// web and register it with IIS before being able to use it. For more information
        /// see the following link: http://go.microsoft.com/?linkid=8101007
        /// </summary>
        #region IHttpHandler Members

        public bool IsReusable
        {
            // Return false in case your Managed Handler cannot be reused for another request.
            // Usually this would be false in case you have some state information preserved per request.
            get { return false ; }
        }

        public void ProcessRequest(HttpContext context)
        {
            //write your handler implementation here.
            float dMin = 0;
            float dMax = Convert.ToSingle(GlobalVars.Goal);
            float dValue = Convert.ToSingle(GlobalVars.TotalRaised);
            string sTitle = "Amount Raised";
            ValueType iType = ValueType.Currency;

            float dAdjValue = dValue;
            if (dValue < dMin)
            {
                dAdjValue = dMin;
            }
            else if (dValue > dMax)
            {
                dAdjValue = dMax;
            }
            else
            {
                dAdjValue = dValue - dMin;
            }

            float dPercent = dAdjValue / (dMax - dMin);

            //---Create new image for composite
            Bitmap oImage = new Bitmap(miWidth, miHeight, PixelFormat.Format24bppRgb);

            //---Paste in the parts
            Graphics oG = Graphics.FromImage(oImage);

            //---Initialize graphic
            oG.FillRectangle(new SolidBrush(Color.White), new Rectangle(0, 0, miWidth, miHeight));

            //---Draw thermometer
            DrawThermometer(ref oG);
            DrawMercury(ref oG, ref dPercent);

            DrawTitle(ref oG, sTitle);

            ShowAxisValues(ref oG, dMin, dMax, 10, 3, iType);

            DrawActualValue(ref oG, dValue, iType);

            context.Response.ContentType = "image/jpeg";
            context.Response.BinaryWrite(BitmapDataFromBitmap((oImage), ImageFormat.Jpeg));

            oG.Dispose();
            oImage.Dispose();
        }

        static public byte[] BitmapDataFromBitmap(Bitmap objBitmap, ImageFormat imageFormat)
        {
            MemoryStream ms = new MemoryStream();

            objBitmap.Save(ms, imageFormat);

            return (ms.GetBuffer());
        }

        #endregion

    }
}

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.