Click here to Skip to main content
15,886,788 members
Articles / Web Development / ASP.NET
Article

Create and display funnel charts on a web page

Rate me:
Please Sign up or sign in to vote.
4.58/5 (19 votes)
7 Oct 2008CPOL1 min read 56K   375   55   14
How to create and display funnel charts on a web page with C# and ASP.NET.

Introduction

Sometimes, it is required in to display data (financial) in funnel charts for better user experience, and it might be the case that the developer will have to code the funnel display from scratch. The following article helps in writing the code for funnel charts and display them on a web page.

Using the code

While creating the funnel chart, we divide it into different slices. Each slice represents a different stage. In the example below, every slice is displayed in a different color. Go through the code given below. The namespaces System.Drawing and System.Drawing.Imaging are used in the code to draw the funnel slices and to group them together to form a funnel.

FunnelChartCode

C#
// This structure holds the details of each of the slices 
public struct Slice
{
    public string stageName;
    public int value;
    public double dollars;
    public Point[] coordinates;
    public Color color;
}

private Color[] colorPalette = new Color[]
                            {Color.LightSkyBlue,Color.LightGreen,
                             Color.PaleVioletRed,Color.SteelBlue};
// Following constants are used to initialize
// the funnel maximum height, width, slice gap 
private const int FUNNEL_HEIGHT = 106;
private const int IMAGE_WIDTH = 291;
private const int IMAGE_HEIGHT = 160;
private const int SLICE_GAP = 1; 
private Slice[] funnelSlices;


/// <summary>
/// This method initializes the funnel
/// </summary>
public void InitFunnel(int count)
{
    try
    {
        maxSlice = count;
        funnelSlices = new Slice[count];

        chartBMP = new Bitmap(IMAGE_WIDTH, IMAGE_HEIGHT, 
                              PixelFormat.Format32bppArgb);
        graphicsObj = Graphics.FromImage(chartBMP);
        // Draw the background
        graphicsObj.FillRectangle(new SolidBrush(Color.White), 
                                  0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
   
    }
    catch (Exception ex)
    {
        throw new Exception("Error initialising funnel", ex);
    }
}
//    0--------------3
//     \            /
//      \          /
//      1\_______/2

/// <summary>
/// Calculates the coordinates of the slice
/// </summary>
private void GetCoordinates(int sliceIndex)
{
    int sliceHeight;

    // Calculate the top points ie, 0 & 3
    if (sliceIndex == 0)
    {
        // The top 2 points are same as the funnel
        funnelSlices[sliceIndex].coordinates[0] = FUNNEL_BOUNDS[0];
        funnelSlices[sliceIndex].coordinates[3] = FUNNEL_BOUNDS[3];
    }
    else
    {
        // The top 2 points are same
        // as the bottom 2 points of the previous slice
        funnelSlices[sliceIndex].coordinates[0] = 
          funnelSlices[sliceIndex - 1].coordinates[1];
        funnelSlices[sliceIndex].coordinates[3] = 
          funnelSlices[sliceIndex - 1].coordinates[2];
    }


    // Calculate the bottom 2 points ie, 1 & 2
    if (sliceIndex == funnelSlices.Length - 1)
    {
        // The bottom 2 points are same as the funnel
        funnelSlices[sliceIndex].coordinates[1] = FUNNEL_BOUNDS[1];
        funnelSlices[sliceIndex].coordinates[2] = FUNNEL_BOUNDS[2];
    }
    else
    {

        sliceHeight = GetHeight(sliceIndex);
        // Calculate the other 2 points using the 2 point equation
        // Use Point 0 & 1 of the funnel to calculate point 1 of the slice
        funnelSlices[sliceIndex].coordinates[1].Y = 
          funnelSlices[sliceIndex].coordinates[0].Y + sliceHeight;
        funnelSlices[sliceIndex].coordinates[1].X =
            GetX(
            FUNNEL_BOUNDS[0].X, FUNNEL_BOUNDS[0].Y,
            FUNNEL_BOUNDS[1].X, FUNNEL_BOUNDS[1].Y,
            funnelSlices[sliceIndex].coordinates[1].Y);

        // Use Point 2 & 3 of the funnel to calculate point 2 of the slice
        funnelSlices[sliceIndex].coordinates[2].Y = 
          funnelSlices[sliceIndex].coordinates[0].Y + sliceHeight;
        funnelSlices[sliceIndex].coordinates[2].X =
            GetX(
            FUNNEL_BOUNDS[2].X, FUNNEL_BOUNDS[2].Y,
            FUNNEL_BOUNDS[3].X, FUNNEL_BOUNDS[3].Y,
            funnelSlices[sliceIndex].coordinates[2].Y);

    }
}


/// <summary>
/// This method returns the x coordinate on the line defined by
/// (x1, y1) and (x2, y2) for a given y. 
/// </summary>
private int GetX(int x1, int y1, int x2, int y2, int y)
{
    return (x2 - x1) * (y - y1) / (y2 - y1) + x1;
}


/// <summary>
/// Adds required slice with the name, value, associated revenue and color
/// </summary>
public void AddSlice(string name, int value, double dollars, Color sliceClr)
{
    // Throw exception if it exceeds the max
    // Create a new slice object and set the values
    Slice newSlice = new Slice();
    newSlice.coordinates = new Point[4];
    newSlice.stageName = name;
    newSlice.value = value;
    newSlice.dollars = dollars;
    newSlice.color = sliceClr;

    // Place it in the Slice[] array at appropriate location
    funnelSlices[curSlice++] = newSlice;

    // Update the total value
    totalVal += value;
}
/// <summary>
/// This method adds the gaps between the slices
/// </summary>
public void AddGaps(int i)
{
    funnelSlices[i].coordinates[0].Y = 
      funnelSlices[i].coordinates[0].Y + i * SLICE_GAP;
    funnelSlices[i].coordinates[1].Y = 
      funnelSlices[i].coordinates[1].Y + i * SLICE_GAP;
    funnelSlices[i].coordinates[2].Y = 
      funnelSlices[i].coordinates[2].Y + i * SLICE_GAP;
    funnelSlices[i].coordinates[3].Y = 
      funnelSlices[i].coordinates[3].Y + i * SLICE_GAP;

}


/// <summary>
/// This method plots the graph.
/// To be called after all the values have been initialized
/// </summary>
public void PlotGraph()
{
    int i;
    SizeF strSz;
    string label;

    labelTop = FUNNEL_BOUNDS[0].Y;
    // Throw exception if all slices not initialised
    // Repeat the following steps for each of the slices
    for (i = 0; i < maxSlice; i++)
    {
        // Get the coordinates of the slice
        GetCoordinates(i);
    }

    // Get the height of the font
    int labelHeight = (int)graphicsObj.MeasureString("B",labelFont).Height;
    for (i = 0; i < maxSlice; i++)
    {
        // Add gaps between slices
        AddGaps(i);

        // Calculate label top (and bottom for next loop)
        labelTop = (funnelSlices[i].coordinates[0].Y > labelBottom + 1 ? 
                    funnelSlices[i].coordinates[0].Y : labelBottom + 1);
        labelBottom = labelTop + labelHeight;

        // Plot the graph
        graphicsObj.FillPolygon(new SolidBrush(funnelSlices[i].color), 
                                funnelSlices[i].coordinates);
        
        // Add the stage name label on the left
        strSz = graphicsObj.MeasureString(funnelSlices[i].stageName, labelFont);
        graphicsObj.DrawString(funnelSlices[i].stageName, labelFont, 
                               fontBrush, funnelSlices[i].coordinates[0].X - 
                               strSz.Width, labelTop);
        
        

        // Add the value label on the right
        label = ((funnelSlices[i].dollars)).ToString("C0") + "(" + 
                  funnelSlices[i].value.ToString() + ")";
        graphicsObj.DrawString(label, labelFont, fontBrush, 
                    funnelSlices[i].coordinates[3].X,labelTop);

        SummationValue += float.Parse(funnelSlices[i].dollars.ToString());
        TotalDealsOpps += int.Parse(funnelSlices[i].value.ToString());
        
    }
    label = (SummationValue).ToString("C0") + "(" + 
             TotalDealsOpps.ToString() + ")";



    strSz = graphicsObj.MeasureString(label, labelFont);

    int funnelWidth = (FUNNEL_BOUNDS[3].X - FUNNEL_BOUNDS[0].X);
    int labelX = FUNNEL_BOUNDS[0].X + (int)(funnelWidth / 2 - strSz.Width / 2);
    int labelY = (int)(IMAGE_HEIGHT - strSz.Height );

    
    graphicsObj.DrawString(label, labelFont, fontBrush, labelX, labelY);
    // Saves the created funnel as an image in Response.OutStream 
    Response.ContentType = IMAGE_FORMAT;
    chartBMP.Save(Response.OutputStream, ImageFormat.Jpeg);
}

All the above code should be placed in an ASPX page code-behind. The page load event of this page will load the data to be displayed. Please go through the code given above and also the attached files. To display the funnel in a web page, the following code can be used:

HTML
<div class="blockBody">
   <table cellpadding="0" cellspacing="0" border="0">
       <tr>
           <td align="center">
               <img src="FunnelChart.aspx" /> 
               <!-- FunnelChart.aspx is the page where the above 
                    code is placed as code behind -->
           </td>
       </tr>
    </table>
</div>

Points of interest

I went through many already available funnel chart code, but found coding and displaying it in a web page is much more fun and easy. This work was much appreciated by my clients when we went live.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
India India
Software developer(Senior) working on .Net Applications.
Domains worked on:
-Finance
-Insurance
-Digital Asset Management

Applications include both web and windows.

Completed Computer Science & Engineering from B.V.B.College of Engineering and Technology, Hubli.

Comments and Discussions

 
QuestionNice article. Pin
Malli_S12-Sep-12 1:38
Malli_S12-Sep-12 1:38 
AnswerRe: Nice article. Pin
Vani Kulkarni1-Jan-13 18:40
professionalVani Kulkarni1-Jan-13 18:40 
Thanks Smile | :)
Thanks & Regards,
Vani Kulkarni

GeneralMy vote of 4 Pin
Malli_S12-Sep-12 1:38
Malli_S12-Sep-12 1:38 
QuestionMy 4 Pin
Malli_S12-Sep-12 1:36
Malli_S12-Sep-12 1:36 
Questionfunnel chart in mvc3 application? Pin
chandru910-Aug-12 21:25
chandru910-Aug-12 21:25 
GeneralMy vote of 5 Pin
Pankaj Nikam19-Jun-12 2:14
professionalPankaj Nikam19-Jun-12 2:14 
GeneralRe: My vote of 5 Pin
Vani Kulkarni25-Jun-12 19:02
professionalVani Kulkarni25-Jun-12 19:02 
GeneralMy vote of 1 Pin
whizMos7-Sep-10 0:57
whizMos7-Sep-10 0:57 
QuestionNo other way? Pin
GravityGuy6-Apr-09 13:14
GravityGuy6-Apr-09 13:14 
GeneralGood effert but... Pin
Nagaraj Kulkarni13-Oct-08 21:07
professionalNagaraj Kulkarni13-Oct-08 21:07 
GeneralVery Nice Pin
Abhijit Jana7-Oct-08 20:26
professionalAbhijit Jana7-Oct-08 20:26 
GeneralRe: Very Nice Pin
Vani Kulkarni8-Oct-08 2:55
professionalVani Kulkarni8-Oct-08 2:55 
GeneralVery Interesting Pin
Sam T Mathew7-Oct-08 19:10
Sam T Mathew7-Oct-08 19:10 
GeneralRe: Very Interesting Pin
Vani Kulkarni7-Oct-08 20:08
professionalVani Kulkarni7-Oct-08 20:08 

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.