Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Create and display funnel charts on a web page

4.58/5 (19 votes)
7 Oct 2008CPOL1 min read 1   375  
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)