Kunle Smart

let us make software development easier... together

Draw Pie Chart in iTextSharp with PdfContentByte, c#

Introduction
iText is a PDF library that allows you to CREATE, ADAPT, INSPECT and MAINTAIN documents in the Portable Document Format (PDF). iTextSharp is the csharp ported version on iText (originally based in java).

Creating pdf with iTextSharp could be very dauting depending on what is required to be present in the document. I have been in that route a while ago when a client of mine contracted me to develop a report that to some daunting specifications. The report was to include some pie and bar charts. I googled around to find a library I could use out of box to create these charts but unfortunately could not find any. So I decided to create my charts from scratch. Here is the journey:

What is pie chart?
A pie chart (or a circle chart) is a circular statistical graphic, which is divided into sectors to illustrate numerical proportion. Below is a sample pie chart we should be able to create at the end of this exercise:

Creating Pie Chart
According to itext, PdfContentByte is a class that consists of a series of methods that map to every operator and operand available in Adobe's imaging model. It is a low-level api. For the purpose of creating the pie chart, we will employ this class in creating our pie chart.

First step in creating a pie chart is drawing a circle of radius r, of a particular length depending on how wide we want our pie chart to be. Let us name our function that create pie cart as CreatePieChart. This function will take a parameter PieChart which will be defined in terms on values (double[]) for the chart, captions (string[]) and colors(System.Drawing.Color[]) for the chart. CreatePieChart will also take xValue and yValue to indicate where we want the pie chart located on the document. So let us begin:

PieChart class:
 public class PieChart
    {
        private readonly double[] _values;
        private double[] _angles;
        private readonly string[] _captions;
        private readonly int _length;
        private readonly BaseColor[] _chartcolors;
        private double _totalValues;

        /// <summary>
        /// Calculates angles based on the values for the chart
        /// </summary>
        private void CalculateAngles()
        {
            _angles = new double[_values.Length];

            _totalValues = 0;
            foreach (var v in _values)
                _totalValues += v;

            var counter = 0;
            foreach (var v in _values)
                _angles[counter++] = v * 360 / _totalValues;
        }

        /// <summary>
        /// Expects chart values and captions for the chart
        /// </summary>
        /// <param name="values">chart values</param>
        /// <param name="captions">captions (label) for the various segments in order of values</param>
        public PieChart(double[] values, string[] captions)
        {
            if (values.Length != captions.Length)
            {
                throw (new Exception("Length of values must be equal to the length of captions of chart."));
            }

            if (values.Length > 10)
            {
                throw (new Exception("Pie chart does not support items more than 10."));
            }

            _length = values.Length;
            _values = values;
            _captions = captions;

            CalculateAngles();

            _chartcolors = new BaseColor[_length];
            _chartcolors[0] = BaseColor.RED;
            if (_length > 1) _chartcolors[1] = BaseColor.GREEN;
            if (_length > 2) _chartcolors[2] = BaseColor.BLUE;
            if (_length > 3) _chartcolors[3] = BaseColor.BLACK;
            if (_length > 4) _chartcolors[4] = BaseColor.YELLOW;
            if (_length > 5) _chartcolors[5] = BaseColor.ORANGE;
            if (_length > 6) _chartcolors[6] = BaseColor.CYAN;
            if (_length > 7) _chartcolors[7] = BaseColor.MAGENTA;
            if (_length > 8) _chartcolors[8] = BaseColor.PINK;
            if (_length > 9) _chartcolors[9] = BaseColor.LIGHT_GRAY;
        }

        /// <summary>
        /// Expects chart values, captions and colors for the chart
        /// </summary>
        /// <param name="values">chart values</param>
        /// <param name="captions">captions (label) for the various segments in order of values</param>
        /// <param name="chartcolors">colors to be used for the charts in order of values and captions</param>
        public PieChart(double[] values, string[] captions, Color[] chartcolors)
        {
            if (chartcolors == null)
            {
                throw (new Exception("Chart colors cannot be null."));
            }

            if (values.Length != captions.Length || values.Length != chartcolors.Length)
            {
                throw (new Exception("Length of values, chart colors must be equal to the length of captions of chart."));
            }

            _length = values.Length;
            _values = values;
            _captions = captions;
            _chartcolors = Array.ConvertAll(chartcolors, new Converter<Color, BaseColor>(DoubleToFloat));

            CalculateAngles();
        }

        /// <summary>
        /// Expects chart values, captions and colors for the chart
        /// </summary>
        /// <param name="values">chart values</param>
        /// <param name="captions">captions (label) for the various segments in order of values</param>
        /// <param name="schartcolors">colors to be used for the charts in order of values and captions</param>
        public PieChart(double[] values, string[] captions, BaseColor[] schartcolors)
        {
            if (schartcolors == null)
            {
                throw (new Exception("Chart colors cannot be null."));
            }

            if (values.Length != captions.Length || values.Length != schartcolors.Length)
            {
                throw (new Exception("Length of values, chart colors must be equal to the length of captions of chart."));
            }

            _length = values.Length;
            _values = values;
            _captions = captions;
            _chartcolors = schartcolors;

            CalculateAngles();
        }

        private static BaseColor DoubleToFloat(Color c)
        {
            return new BaseColor(c);
        }

        public double[] Values { get { return _values; } }
        public string[] Captions { get { return _captions; } }
        public BaseColor[] ChartColors { get { return _chartcolors; } }
        public int Length { get { return _length; } }
        public double TotalValues { get { return _totalValues; } }
        public double[] Angles { get { return _angles; } }
    }


PdfContentByteExtensions class:

using iTextSharp.text;
using iTextSharp.text.pdf;
using System;

namespace Byaxiom.iTextSharpExtensions {
    public static class PdfContentByteExtensions {
        /// <summary>
        /// Draws pie chart on docuent
        /// </summary>
        public static void DrawPieChart(this PdfContentByte canvas,
                   PieChart chart,
                   float x0,
                   float y0,
                   float r = 50f,
                   bool showCaption = true,
                   Font font = null) {

            if (chart.Values.Length != chart.Captions.Length) {
                return;
            }

            if (font == null) {
                font = FontFactory.GetFont(FontFactory.TIMES, 8);
            }

            canvas.SetLineWidth(0f);

            canvas.SetLineWidth(1f);
            var cRadius = (float)(r + 0.5);
            canvas.Circle(x0, y0, cRadius);
            canvas.SetColorStroke(BaseColor.GRAY);
            canvas.Stroke();

            canvas.SetLineWidth(0f);
            var rectX1 = x0 - r;
            var rectY1 = y0 - r;

            var xPoint = x0 + r;
            var yPoint = y0 + r;

            double startAngleDouble = 0;
            double angle = 0;

            var captionY = y0 + (chart.Values.Length - 1) * 6;

            for (var counter = 0; counter < chart.Values.Length; counter++) {
                double percentage = 0;
                if (chart.TotalValues > 0)
                    percentage = chart.Angles[counter] * 100 / 360;

                if (showCaption) {
                    //captions from here
                    canvas.SetColorStroke(chart.ChartColors[counter]);
                    canvas.SetColorFill(chart.ChartColors[counter]);
                    canvas.Rectangle(x0 + r + 10, captionY, 7, 7);
                    canvas.ClosePathFillStroke();

                    var percentageCaption = string.Format("{0:N}", percentage);
                    var text2 = new ColumnText(canvas);
                    var phrase = new Phrase(string.Format("{0} ({1}%)", chart.Captions[counter], percentageCaption), font);
                    text2.SetSimpleColumn(phrase, x0 + r + 20, captionY, x0 + r + 200, captionY, 0f, 0);
                    text2.Go();

                    captionY -= 12;
                    if ((int)percentage == 0) {
                        continue;
                    }
                    //end of caption
                }

                if (chart.TotalValues <= 0)
                    continue;

                double y1Double;
                double x1Double;
                float startAngle = 0;
                double x2Double;
                double y2Double;
                float x1;
                float y1;
                float x2;
                float y2;
                if (percentage <= 50) {
                    //get coordinate on circle
                    x1Double = x0 + r * Math.Cos(startAngleDouble * Math.PI / 180);
                    y1Double = y0 + r * Math.Sin(startAngleDouble * Math.PI / 180);

                    x1 = (float)x1Double;
                    y1 = (float)y1Double;

                    angle += chart.Angles[counter];
                    x2Double = x0 + r * Math.Cos(angle * Math.PI / 180);
                    y2Double = y0 + r * Math.Sin(angle * Math.PI / 180);

                    x2 = (float)x2Double;
                    y2 = (float)y2Double;

                    startAngle = (float)startAngleDouble;

                    //set the colors to be used
                    canvas.SetColorStroke(chart.ChartColors[counter]);
                    canvas.SetColorFill(chart.ChartColors[counter]);

                    //draw the triangle within the circle
                    canvas.MoveTo(x0, y0);
                    canvas.LineTo(x1, y1);
                    canvas.LineTo(x2, y2);
                    canvas.LineTo(x0, y0);
                    canvas.ClosePathFillStroke();
                    //draw the arc
                    canvas.Arc(rectX1, rectY1, xPoint, yPoint, startAngle, (float)chart.Angles[counter]);
                    canvas.ClosePathFillStroke();
                    startAngleDouble += chart.Angles[counter];
                }
                else {
                    //DO THE FIRST PART
                    //get coordinate on circle
                    x1Double = x0 + r * Math.Cos(startAngleDouble * Math.PI / 180);
                    y1Double = y0 + r * Math.Sin(startAngleDouble * Math.PI / 180);
                    x1 = (float)x1Double;
                    y1 = (float)y1Double;

                    angle += 180;
                    x2Double = x0 + r * Math.Cos(angle * Math.PI / 180);
                    y2Double = y0 + r * Math.Sin(angle * Math.PI / 180);
                    x2 = (float)x2Double;
                    y2 = (float)y2Double;

                    startAngle = (float)startAngleDouble;

                    //set the colors to be used
                    canvas.SetColorStroke(chart.ChartColors[counter]);
                    canvas.SetColorFill(chart.ChartColors[counter]);

                    //draw the triangle within the circle
                    canvas.MoveTo(x0, y0);
                    canvas.LineTo(x1, y1);
                    canvas.LineTo(x2, y2);
                    canvas.LineTo(x0, y0);
                    canvas.ClosePathFillStroke();
                    //draw the arc
                    canvas.Arc(rectX1, rectY1, xPoint, yPoint, startAngle, 180);
                    canvas.ClosePathFillStroke();

                    //DO THE SECOND PART
                    //get coordinate on circle
                    x1Double = x0 + r * Math.Cos((startAngleDouble + 180) * Math.PI / 180);
                    y1Double = y0 + r * Math.Sin((startAngleDouble + 180) * Math.PI / 180);
                    x1 = (float)x1Double;
                    y1 = (float)y1Double;

                    angle += chart.Angles[counter] - 180;
                    x2Double = x0 + r * Math.Cos(angle * Math.PI / 180);
                    y2Double = y0 + r * Math.Sin(angle * Math.PI / 180);
                    x2 = (float)x2Double;
                    y2 = (float)y2Double;

                    startAngle = (float)startAngleDouble;

                    //set the colors to be used
                    canvas.SetColorStroke(chart.ChartColors[counter]);
                    canvas.SetColorFill(chart.ChartColors[counter]);

                    //draw the triangle within the circle
                    canvas.MoveTo(x0, y0);
                    canvas.LineTo(x1, y1);
                    canvas.LineTo(x2, y2);
                    canvas.LineTo(x0, y0);
                    canvas.ClosePathFillStroke();
                    //draw the arc
                    canvas.Arc(rectX1, rectY1, xPoint, yPoint, startAngle + 180, (float)(chart.Angles[counter] - 180));
                    canvas.ClosePathFillStroke();

                    startAngleDouble += chart.Angles[counter];
                }

            }
        }
    }
}

Usage:
i. Create a sample project;
ii. Add itextSharp library to your project;
iii. Call function to create pie chart like this:

 public static bool Generate(string filename)
        {
            var document = new Document(PageSize.A4, 0f, 0f, 0f, 0f);
            var writer = PdfWriter.GetInstance(document, new FileStream(filename, FileMode.Create));
            document.Open();

            string[] captions = { "ATM", "CASH", "WEB", "MOBILE","POS" };
            double[] values = { 5, 60, 35, 10,20 };
            var chartColors = new []{ Color.DarkBlue, Color.YellowGreen, Color.Green, Color.SteelBlue, Color.Red };
            var chart = new PieChart(values, captions, chartColors);
          
            var canvas = writer.DirectContent;
            canvas.DrawPieChart(chart, 410, 730, 60);

            document.Close();
            return true;
        }

If you have any problems using the code, kindly let me know in comments.

Kunle Smart | Welcome to Kunle Smart Thoughts...

Kunle Smart

let us make software development easier... together

Welcome to Kunle Smart Thoughts...

Thanks for Stopping By


I am very pleased to welcome you to Kunle Smart Thoughts... majorly on software development.

I am passionately interested in ranting about ways to solve problems. I have been a professional developer for about a decade now and I know that software solution development is not easy to say the least. The world is very dynamic and so is software development.

I started development with pascal, then c, then c++, visual basic, c#, java, etc. Currently I am learning android with the hope to be able to development mobile application one day and write about my discoveries some other time, ultimately.

Meanwhile, I have been a very huge fan of  Microsoft eco-system. I will possibly write more about Microsoft technologies than anything else. Please forgive me. However, I love jquery, angularjs, tdd, etc. You may see all these reflecting in me soonest than later.

Finally, I urge you to visit me once in a week and don't hesitate to tell me your mind either in comments of by sending email directly to me at kunle.smart@hotmail.com.

Thank you for stopping by. I appreciate.

Kunle Smart.
blog comments powered by Disqus