5,696,576 members and growing! (13,039 online)
Email Password   helpLost your password?
Web Development » Charts, Graphs and Images » Charts     Intermediate License: The Code Project Open License (CPOL)

Create and display funnel charts on a web page

By Vani Kulkarni

How to create and display funnel charts on a web page with C# and ASP.NET.
C# (C# 1.0, C# 2.0, C# 3.0, C#), .NET (.NET, .NET 2.0, .NET 3.5), GDI+, ASP.NET, Dev

Posted: 7 Oct 2008
Updated: 7 Oct 2008
Views: 5,332
Bookmarked: 25 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
12 votes for this Article.
Popularity: 4.47 Rating: 4.14 out of 5
1 vote, 8.3%
1
1 vote, 8.3%
2
1 vote, 8.3%
3
2 votes, 16.7%
4
7 votes, 58.3%
5

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

// 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:

<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)

About the Author

Vani Kulkarni


I am web application developer working on C#, ASP.Net. I have completed my Computer Science & Engineering from B.V.B.College of Engineering and Technology, Hubli.
Occupation: Web Developer
Location: India India

Other popular Charts, Graphs and Images articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 5 of 5 (Total in Forum: 5) (Refresh)FirstPrevNext
GeneralGood effert but...membernakul6522:07 13 Oct '08  
GeneralVery NicememberAbhijit Jana21:26 7 Oct '08  
GeneralRe: Very NicememberVani Kulkarni3:55 8 Oct '08  
GeneralVery InterestingmemberSam T Mathew20:10 7 Oct '08  
GeneralRe: Very InterestingmemberVani Kulkarni21:08 7 Oct '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 7 Oct 2008
Editor: Smitha Vijayan
Copyright 2008 by Vani Kulkarni
Everything else Copyright © CodeProject, 1999-2008
Web17 | Advertise on the Code Project