Friday, February 21, 2014

Draw Pie Chart

In today's post I want to show how to draw pie charts (also called circle diagrams) "manually", meaning by drawing via the Graphics class, in C#. I got the idea because I wanted to draw one in an Android app, but in the Xamarin Studio there was no Chart control as opposed to in the Visual Studio.
Drawing is done by the class PieChart with the function Draw(). This expects a list of tuples as parameter, where the first value always describes the name of the corresponding category and the second the actual value.
The single pieces of the pie are then drawn by the function FillPie(), to which the starting and ending angle can be passed.
The function DetermineColor() determines the color of the pie arc, depending on the current angle. I tried to picture a complete color circle and for this approximted quadratic functions for the colors red, green and blue with a doman of 240° each. So that adjacent pieces have a high color contrast from each other, in turns to the current angle either 0°, 120° or 240° are added. I have to admit I am not really satisfied with this coloring function. If someone writes an interesting function for this, I would be very happy about a comment.
The first values of the tuples, the names of the categories, are not used in this class, because I thought that a possible labelling will anyway be very individual. For suggestions though I recommend a look at the post about the app linked above, there I print the labels below the chart.
Now the code:

    public class PieChart
    {
        int PosCounter = 0;

        private Color DetermineColor(float angle)
        {
            angle += PosCounter * 120;
            while (angle > 360)
                angle -= 360;

            PosCounter++;
            if (PosCounter > 2)
                PosCounter = 0;

            int Red;
            if (angle >= 270)
                Red = (int)((-0.000069 * Math.Pow((angle - 360), 2) + 1) * 255);
            else
                Red = (int)((-0.000069 * Math.Pow((angle), 2) + 1) * 255);
            Red = (Red > 0) ? Red : 0;
            Red = (Red > 255) ? 255 : Red;

            int Green = (int)((-0.000114 * Math.Pow((angle), 2) + 0.030682 * angle + -1.04545) * 255);
            Green = (Green > 0) ? Green : 0;
            Green = (Green > 255) ? 255 : Green;

            int Blue = (int)((-0.000069 * Math.Pow(((angle)), 2) + 0.033333 * angle - 3) * 255);
            Blue = (Blue > 0) ? Blue : 0;
            Blue = (Blue > 255) ? 255 : Blue;

            return Color.FromArgb(Red, Green, Blue);
        }

        public Bitmap Draw(List<Tuple<string,float>> data)
        {
            int PieSize = 400;
            float PrevStart = 0;

            Bitmap Result = new Bitmap(PieSize, PieSize);
            Graphics G = Graphics.FromImage(Result);

            float TotalValue = 0;
            for (int i = 0; i < data.Count; i++)
            {
                TotalValue += data[i].Item2;
            }

            for (int i = 0; i < data.Count; i++)
            {
                G.FillPie(new SolidBrush(DetermineColor(PrevStart)), new Rectangle(0, 0, PieSize, PieSize), PrevStart, (data[i].Item2 / TotalValue) * 360);
                PrevStart += (data[i].Item2 / TotalValue) * 360;
            }

            return Result;
        }
    }

A sample call could look as follows:
        private void pictureBox1_Click(object sender, EventArgs e)
        {
            PieChart A = new PieChart();
            List<Tuple<string, float>> B = new List<Tuple<string, float>>();

            B.Add(new Tuple<string, float>("A", 3));
            B.Add(new Tuple<string, float>("A", 2));
            B.Add(new Tuple<string, float>("A", 1));
            B.Add(new Tuple<string, float>("A", 2));
            B.Add(new Tuple<string, float>("A", 4));
            B.Add(new Tuple<string, float>("A", 3));

            pictureBox1.Image = A.Draw(B);
        }

The result then looks as follows:


No comments:

Post a Comment