// TODO: Make this obey blending rules properly public unsafe void DrawPieChart(uint* pxls, int width, int height, PieChartSection[] sections, uint color) { if (0 == sections.Length) { throw new ArgumentException("Pie chart sections array cannot be empty"); } // We need at least a 5x5 image if (width < 5 || height < 5) { return; } // If the image is not square, we want non-zero starting points int ystart = 0; int yend = height - 1; // Inclusive upper bound if (height > width) { ystart = (height - width) / 2; yend = height - ystart; } float xcenter = (float)width / 2.0f; float ycenter = (float)height / 2.0f; double radius = Math.Min(xcenter, ycenter) - 2.0; // Make a circle object for the pie chart area Circle2D circle = new Circle2D(xcenter, ycenter, (float)radius); // Compute the sum of all values in the pie sections double sum = 0.0; foreach (PieChartSection pcs in sections) { sum += pcs.Value; } // Make an angle table. This table stores the ending angle of each category. Also // make a color table. double[] angles = new double[sections.Length]; uint[] clrLookup = new uint[sections.Length]; int i = 0; double angle = 0.0; foreach (PieChartSection pcs in sections) { clrLookup[i] = pcs.Color; angles[i] = pcs.Value / sum * (Math.PI * 2.0) + angle; angle = pcs.Value / sum * (Math.PI * 2.0) + angle; i++; } while (ystart <= yend) { // Compute the intersections of the scan line with the pie chart float minx, maxx; circle.HLineIntersection((float)ystart, out minx, out maxx); // Clear the row if there isn't an intersection if (float.IsNaN(minx) || float.IsNaN(maxx)) { DrawHorizontalLine(0, width - 1, ystart, 0, pxls, width, height, BlendMode.None); ystart++; continue; } int xstart = (int)Math.Floor(minx); int xend = (int)Math.Min(Math.Ceiling(maxx), (float)(width - 1)); uint* row = &pxls[ystart * width + xstart]; for (int x = xstart; x <= xend; x++) { // For each pixel, we want to start by computing the distance from the center Vector2D pos = new Vector2D((float)x - xcenter, ycenter - (float)ystart); double dist = pos.Length; // Set the pixel to 0 if we're outside of the pie chart area if (dist >= radius) { *row++ = 0; continue; } // Compute the angle so we know what category of the pie we're in angle = pos.GetCCWAngle(); i = 0; while (angle > angles[i]) { i++; } // Advance to the next pixel *row++ = clrLookup[i]; } ystart++; } // Draw lines between sections if (sections.Length > 1) { List<Stadium> stadiums = new List<Stadium>(); foreach (double tempAngle in angles) { stadiums.Add(new Stadium(circle.Center, new Vector2D(xcenter + (float)(radius * Math.Cos(tempAngle)), ycenter - (float)(radius * Math.Sin(tempAngle))), 1.75f)); } FillStadiums(new SimpleImage(width, height, pxls), color, stadiums.ToArray(), 2f); } // Draw a circular border around the pie chart FrameCircle_Composite(circle, 3.5f, color, pxls, width, height); }