Freitag, 21. Februar 2014

Kreisdiagramm zeichnen

Im heutigen Post möchte ich zeigen wie man in C# "per Hand", also durch Zeichnen über die Graphics Klasse, selber Kreisdiagramme (auch Kuchendiagramme genannt) zeichnet. Die Idee kam mir deswegen, weil ich in einer Android App ein solches zeichnen wollte, im Xamarin Studio für Android aber im Gegensatz zum Visual Studio kein Chart Steuerelement vorhanden ist.
Das Zeichnen erledigt die Klasse PieChart über die Funktion Draw(). Diese erwartet eine Liste von Tupeln als Parameter, von denen jeweils der erste Wert den Namen der entsprechenden Kategorie angibt, der zweite den Wert.
Die einzelnen Stücke des Kreises werden dann über die Funktion FillPie() gezeichnet, welcher man den Start- und Endwinkel des aktuellen Kreisbogens übergeben kann.
Die Funktion DetermineColor() bestimmt in Abhängigkeit des aktuellen Winkels die Farbe des Kreisbogens. Ich habe versucht, einen vollständigen Farbkreis darzustellen und hierzu für die Farben Rot, Grün und Blau jeweils eine quadratische Funktion approximiert, welche einen Wertebereich von 240° abdeckt. Damit sich benachbarte Kreisstücke farblich möglichst weit abheben, wird reihum zum Winkel für die Farbbestimmung 0°, 120° oder 240° dazuaddiert. Allerdings muss ich zugeben, dass ich mit dieser Farbverteilung nicht wirklich zufrieden bin, das lässt sich sicherlich besser lösen. Falls jemand eine interessante Funktion hierfür schreibt, würde ich mich sehr über einen Kommentar freuen.
Die ersten Werte der Tupel, die Namen der Kategorien, werden in der Klasse nicht benutzt, da ich mir dachte, dass eine eventuelle Beschriftung eh sehr individuell ausfallen wird. Für Anregungen kann man sich aber den Code für oben verlinkte App anschauen, dort drucke ich die Beschriftungen unter das Diagramm.
Nun der Code der Klasse:

    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;
        }
    }

Ein Beispielaufruf könnte so aussehen:
        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);
        }

Das Ergebnis sieht dann so aus:


Keine Kommentare:

Kommentar veröffentlichen