← ziglang.inraylib-zigGitHub

pie chart

★★★☆by Gideon Serfontein (@GideonSerf)

There is no browser build of this example yet.

Is it the same as C?

//! raylib [shapes] example - pie chart
//!
//! Zig-API translation of examples/shapes/shapes_pie_chart.c (raylib 6.2).
//! Example contributed by Gideon Serfontein (@GideonSerf) and reviewed by Ramon Santamaria (@raysan5)
//! Example licensed under an unmodified zlib/libpng license.
//! Copyright (c) 2025 Gideon Serfontein (@GideonSerf)

const std = @import("std");
const rl = @import("raylib");
const gui = @import("raygui");

const max_pie_slices = 10; // Max pie slices

/// The color of slice `i` out of `count`.
fn sliceColor(i: usize, count: usize) rl.Color {
    const hue = @as(f32, @floatFromInt(i)) / @as(f32, @floatFromInt(count)) * 360.0;
    return rl.colorFromHSV(hue, 0.75, 0.9);
}

pub fn main() void {
    // Initialization
    const screen_width = 800;
    const screen_height = 450;

    rl.initWindow(screen_width, screen_height, "raylib [shapes] example - pie chart");
    defer rl.closeWindow(); // Close window and OpenGL context

    var slice_count: i32 = 7;
    var donut_inner_radius: f32 = 25.0;
    var values: [max_pie_slices]f32 = .{ 300.0, 100.0, 450.0, 350.0, 600.0, 380.0, 750.0, 0, 0, 0 }; // Initial slice values
    var editing_label: [max_pie_slices]bool = @splat(false);

    var show_values = true;
    var show_percentages = false;
    var show_donut = false;
    var scroll_content_offset: rl.Vector2 = .{ .x = 0, .y = 0 };
    var view: rl.Rectangle = .{ .x = 0, .y = 0, .width = 0, .height = 0 };

    // UI layout parameters
    const panel_width = 270;
    const panel_margin = 5;

    // UI Panel top-left anchor
    const panel_pos: rl.Vector2 = .{ .x = screen_width - panel_margin - panel_width, .y = panel_margin };

    // UI Panel rectangle
    const panel_rect: rl.Rectangle = .{
        .x = panel_pos.x,
        .y = panel_pos.y,
        .width = panel_width,
        .height = screen_height - 2.0 * panel_margin,
    };

    // Pie chart geometry
    const canvas: rl.Rectangle = .{ .x = 0, .y = 0, .width = panel_pos.x, .height = screen_height };
    const center: rl.Vector2 = .{ .x = canvas.width / 2.0, .y = canvas.height / 2.0 };
    const radius: f32 = 205.0;

    rl.setTargetFPS(60);

    // Main game loop
    while (!rl.windowShouldClose()) {
        // Update
        const count: usize = @intCast(slice_count);
        const slices = values[0..count];

        // Calculate total value for percentage calculations
        var total_value: f32 = 0.0;
        for (slices) |value| total_value += value;

        // Check for mouse hover over slices
        var hovered_slice: ?usize = null;
        const mouse_pos = rl.getMousePosition();

        if (rl.checkCollisionPointRec(mouse_pos, canvas)) { // Only check if mouse is inside the canvas
            const dx = mouse_pos.x - center.x;
            const dy = mouse_pos.y - center.y;
            const distance = @sqrt(dx * dx + dy * dy);

            if (distance <= radius) { // Inside the pie radius
                var angle = std.math.atan2(dy, dx) * rl.rad2deg;
                if (angle < 0) angle += 360;

                var current_angle: f32 = 0.0;
                for (slices, 0..) |value, i| {
                    const sweep = if (total_value > 0) (value / total_value) * 360.0 else 0.0;

                    if ((angle >= current_angle) and (angle < (current_angle + sweep))) {
                        hovered_slice = i;
                        break;
                    }

                    current_angle += sweep;
                }
            }
        }

        // Draw
        rl.beginDrawing();
        defer rl.endDrawing();

        rl.clearBackground(.ray_white);

        // Draw the pie chart on the canvas
        var start_angle: f32 = 0.0;
        for (slices, 0..) |value, i| {
            const sweep_angle = if (total_value > 0) (value / total_value) * 360.0 else 0.0;
            const mid_angle = start_angle + sweep_angle / 2.0; // Middle angle for label positioning

            // Make the hovered slice pop out by adding 20 pixels to its radius
            const current_radius = if (hovered_slice == i) radius + 20.0 else radius;

            // Draw the pie slice using raylib's DrawCircleSector function
            rl.drawCircleSector(center, current_radius, start_angle, start_angle + sweep_angle, 120, sliceColor(i, count));

            // Draw the label for the current slice
            if (value > 0) {
                const percentage = (value / total_value) * 100.0;
                var label_buffer: [64]u8 = undefined;
                const label_text = (if (show_values and show_percentages)
                    std.fmt.bufPrintSentinel(&label_buffer, "{d:.1} ({d:.0}%)", .{ value, percentage }, 0)
                else if (show_values)
                    std.fmt.bufPrintSentinel(&label_buffer, "{d:.1}", .{value}, 0)
                else if (show_percentages)
                    std.fmt.bufPrintSentinel(&label_buffer, "{d:.0}%", .{percentage}, 0)
                else
                    std.fmt.bufPrintSentinel(&label_buffer, "", .{}, 0)) catch unreachable;

                const text_size = rl.measureTextEx(rl.getFontDefault(), label_text, 20, 1);
                const label_radius = radius * 0.7;
                const label_pos: rl.Vector2 = .{
                    .x = center.x + @cos(mid_angle * rl.deg2rad) * label_radius - text_size.x / 2.0,
                    .y = center.y + @sin(mid_angle * rl.deg2rad) * label_radius - text_size.y / 2.0,
                };
                rl.drawText(label_text, @intFromFloat(label_pos.x), @intFromFloat(label_pos.y), 20, .white);
            }

            // Draw inner circle to create donut effect
            // TODO: This is a hacky solution, better use DrawRing()
            if (show_donut) rl.drawCircleV(center, donut_inner_radius, .ray_white);

            start_angle += sweep_angle;
        }

        // UI control panel
        rl.drawRectangleRec(panel_rect, rl.fade(.light_gray, 0.5));
        rl.drawRectangleLinesEx(panel_rect, 1.0, .gray);

        _ = gui.spinner(.{ .x = panel_pos.x + 95, .y = panel_pos.y + 12, .width = 125, .height = 25 }, "Slices ", &slice_count, 1, max_pie_slices, false);
        _ = gui.checkBox(.{ .x = panel_pos.x + 20, .y = panel_pos.y + 12 + 40, .width = 20, .height = 20 }, "Show Values", &show_values);
        _ = gui.checkBox(.{ .x = panel_pos.x + 20, .y = panel_pos.y + 12 + 70, .width = 20, .height = 20 }, "Show Percentages", &show_percentages);
        _ = gui.checkBox(.{ .x = panel_pos.x + 20, .y = panel_pos.y + 12 + 100, .width = 20, .height = 20 }, "Make Donut", &show_donut);

        if (show_donut) gui.disable();
        _ = gui.sliderBar(.{ .x = panel_pos.x + 80, .y = panel_pos.y + 12 + 130, .width = panel_rect.width - 100, .height = 30 }, "Inner Radius", null, &donut_inner_radius, 5.0, radius - 10.0);
        gui.enable();

        _ = gui.line(.{ .x = panel_pos.x + 10, .y = panel_pos.y + 12 + 170, .width = panel_rect.width - 20, .height = 1 }, null);

        // Scrollable area for slice editors
        const scroll_panel_bounds: rl.Rectangle = .{
            .x = panel_pos.x + panel_margin,
            .y = panel_pos.y + 12 + 190,
            .width = panel_rect.width - panel_margin * 2,
            .height = panel_rect.y + panel_rect.height - panel_pos.y + 12 + 190 - panel_margin,
        };
        const content_height: f32 = @floatFromInt(slice_count * 35);

        _ = gui.scrollPanel(scroll_panel_bounds, null, .{ .x = 0, .y = 0, .width = panel_rect.width - 25, .height = content_height }, &scroll_content_offset, &view);

        const content_x = view.x + scroll_content_offset.x; // Left of content
        const content_y = view.y + scroll_content_offset.y; // Top of content

        rl.beginScissorMode(@intFromFloat(view.x), @intFromFloat(view.y), @intFromFloat(view.width), @intFromFloat(view.height));
        defer rl.endScissorMode();

        // The slice count may have changed in the spinner above
        const row_count: usize = @intCast(slice_count);
        for (values[0..row_count], editing_label[0..row_count], 0..) |*value, *editing, i| {
            const row_y: i32 = @intFromFloat(content_y + 5 + @as(f32, @floatFromInt(i * 35)));
            const row_top: f32 = @floatFromInt(row_y);

            // Color indicator
            rl.drawRectangle(@intFromFloat(content_x + 15), row_y + 5, 20, 20, sliceColor(i, row_count));

            // Label textbox
            var label_buffer: [32]u8 = undefined;
            _ = std.fmt.bufPrintSentinel(&label_buffer, "Slice {d:0>2}", .{i + 1}, 0) catch unreachable;
            if (gui.textBox(.{ .x = content_x + 45, .y = row_top, .width = 75, .height = 30 }, &label_buffer, editing.*) != 0) editing.* = !editing.*;

            _ = gui.sliderBar(.{ .x = content_x + 130, .y = row_top, .width = 110, .height = 30 }, null, null, value, 0.0, 1000.0);
        }
    }
}
/*******************************************************************************************
*
*   raylib [shapes] example - pie chart
*
*   Example complexity rating: [★★★☆] 3/4
*
*   Example originally created with raylib 5.5, last time updated with raylib 5.6
*
*   Example contributed by Gideon Serfontein (@GideonSerf) and reviewed by Ramon Santamaria (@raysan5)
*
*   Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
*   BSD-like license that allows static linking with closed source software
*
*   Copyright (c) 2025 Gideon Serfontein (@GideonSerf)
*
********************************************************************************************/

#include "raylib.h"

#define RAYGUI_IMPLEMENTATION
#include "raygui.h"

#include <math.h>
#include <stdio.h>

#define MAX_PIE_SLICES  10       // Max pie slices

//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
int main(void)
{
    // Initialization
    //--------------------------------------------------------------------------------------
    const int screenWidth = 800;
    const int screenHeight = 450;

    InitWindow(screenWidth, screenHeight, "raylib [shapes] example - pie chart");

    int sliceCount = 7;
    float donutInnerRadius = 25.0f;
    float values[MAX_PIE_SLICES] = { 300.0f, 100.0f, 450.0f, 350.0f, 600.0f, 380.0f, 750.0f }; // Initial slice values
    bool editingLabel[MAX_PIE_SLICES] = { 0 };

    bool showValues = true;
    bool showPercentages = false;
    bool showDonut = false;
    int hoveredSlice = -1;
    Rectangle scrollPanelBounds = { 0 };
    Vector2 scrollContentOffset = { 0 };
    Rectangle view = { 0 };

    // UI layout parameters
    const int panelWidth = 270;
    const int panelMargin = 5;

    // UI Panel top-left anchor
    const Vector2 panelPos = {
        (float)screenWidth - panelMargin - panelWidth,
        (float)panelMargin
    };

    // UI Panel rectangle
    const Rectangle panelRect = {
        panelPos.x, panelPos.y,
        (float)panelWidth,
        (float)screenHeight - 2.0f*panelMargin
    };

    // Pie chart geometry
    const Rectangle canvas = { 0, 0, panelPos.x, (float)screenHeight };
    const Vector2 center = { canvas.width/2.0f, canvas.height/2.0f };
    const float radius = 205.0f;

    // Total value for percentage calculations
    float totalValue = 0.0f;

    SetTargetFPS(60);
    //--------------------------------------------------------------------------------------

    // Main game loop
    while (!WindowShouldClose())
    {
        // Update
        //----------------------------------------------------------------------------------
        // Calculate total value for percentage calculations
        totalValue = 0.0f;
        for (int i = 0; i < sliceCount; i++) totalValue += values[i];

        // Check for mouse hover over slices
        hoveredSlice = -1; // Reset hovered slice
        Vector2 mousePos = GetMousePosition();

        if (CheckCollisionPointRec(mousePos, canvas)) // Only check if mouse is inside the canvas
        {
            float dx = mousePos.x - center.x;
            float dy = mousePos.y - center.y;
            float distance = sqrtf(dx*dx + dy*dy);

            if (distance <= radius) // Inside the pie radius
            {
                float angle = atan2f(dy, dx)*RAD2DEG;
                if (angle < 0) angle += 360;

                float currentAngle = 0.0f;
                for (int i = 0; i < sliceCount; i++)
                {
                    float sweep = (totalValue > 0)? (values[i]/totalValue)*360.0f : 0.0f;

                    if ((angle >= currentAngle) && (angle < (currentAngle + sweep)))
                    {
                        hoveredSlice = i;
                        break;
                    }

                    currentAngle += sweep;
                }
            }
        }
        //----------------------------------------------------------------------------------

        // Draw
        //----------------------------------------------------------------------------------
        BeginDrawing();
            ClearBackground(RAYWHITE);

            // Draw the pie chart on the canvas
            float startAngle = 0.0f;
            for (int i = 0; i < sliceCount; i++)
            {
                float sweepAngle = (totalValue > 0)? (values[i]/totalValue)*360.0f : 0.0f;
                float midAngle = startAngle + sweepAngle/2.0f; // Middle angle for label positioning

                Color color = ColorFromHSV((float)i/sliceCount*360.0f, 0.75f, 0.9f);
                float currentRadius = radius;

                // Make the hovered slice pop out by adding 5 pixels to its radius
                if (i == hoveredSlice) currentRadius += 20.0f;

                // Draw the pie slice using raylib's DrawCircleSector function
                DrawCircleSector(center, currentRadius, startAngle, startAngle + sweepAngle, 120, color);

                // Draw the label for the current slice
                if (values[i] > 0)
                {
                    char labelText[64] = { 0 };
                    if (showValues && showPercentages) snprintf(labelText, 64, "%.1f (%.0f%%)", values[i], (values[i]/totalValue)*100.0f);
                    else if (showValues) snprintf(labelText, 64, "%.1f", values[i]);
                    else if (showPercentages) snprintf(labelText, 64, "%.0f%%", (values[i]/totalValue)*100.0f);
                    else labelText[0] = '\0';

                    Vector2 textSize = MeasureTextEx(GetFontDefault(), labelText, 20, 1);
                    float labelRadius = radius*0.7f;
                    Vector2 labelPos = { center.x + cosf(midAngle*DEG2RAD)*labelRadius - textSize.x/2.0f,
                        center.y + sinf(midAngle*DEG2RAD)*labelRadius - textSize.y/2.0f };
                    DrawText(labelText, (int)labelPos.x, (int)labelPos.y, 20, WHITE);
                }

                // Draw inner circle to create donut effect
                // TODO: This is a hacky solution, better use DrawRing()
                if (showDonut) DrawCircleV(center, donutInnerRadius, RAYWHITE);

                startAngle += sweepAngle;
            }

            // UI control panel
            DrawRectangleRec(panelRect, Fade(LIGHTGRAY, 0.5f));
            DrawRectangleLinesEx(panelRect, 1.0f, GRAY);

            GuiSpinner((Rectangle){ panelPos.x + 95, (float)panelPos.y + 12, 125, 25 }, "Slices ", &sliceCount, 1, MAX_PIE_SLICES, false);
            GuiCheckBox((Rectangle){ panelPos.x + 20, (float)panelPos.y + 12 + 40, 20, 20 }, "Show Values", &showValues);
            GuiCheckBox((Rectangle){ panelPos.x + 20, (float)panelPos.y + 12 + 70, 20, 20 }, "Show Percentages", &showPercentages);
            GuiCheckBox((Rectangle){ panelPos.x + 20, (float)panelPos.y + 12 + 100, 20, 20 }, "Make Donut", &showDonut);

            if (showDonut) GuiDisable();
            GuiSliderBar((Rectangle){ panelPos.x + 80, (float)panelPos.y + 12 + 130, panelRect.width - 100, 30 },
                "Inner Radius", NULL, & donutInnerRadius, 5.0f, radius - 10.0f);
            GuiEnable();

            GuiLine((Rectangle){ panelPos.x + 10, (float)panelPos.y + 12 + 170, panelRect.width - 20, 1 }, NULL);

            // Scrollable area for slice editors
            scrollPanelBounds = (Rectangle){
                panelPos.x + panelMargin,
                (float)panelPos.y + 12 + 190,
                panelRect.width - panelMargin*2,
                panelRect.y + panelRect.height - panelPos.y + 12 + 190 - panelMargin
            };
            int contentHeight = sliceCount*35;

            GuiScrollPanel(scrollPanelBounds, NULL, (Rectangle){ 0, 0, panelRect.width - 25, (float)contentHeight },
                &scrollContentOffset, &view);

            const float contentX = view.x + scrollContentOffset.x; // Left of content
            const float contentY = view.y + scrollContentOffset.y; // Top of content

            BeginScissorMode((int)view.x, (int)view.y, (int)view.width, (int)view.height);

                for (int i = 0; i < sliceCount; i++)
                {
                    const int rowY = (int)(contentY + 5 + i*35);

                    // Color indicator
                    Color color = ColorFromHSV((float)i/sliceCount*360.0f, 0.75f, 0.9f);
                    DrawRectangle((int)(contentX + 15), rowY + 5, 20, 20, color);

                    // Label textbox
                    if (GuiTextBox((Rectangle){ contentX + 45, (float)rowY, 75, 30 }, TextFormat("Slice %02i", i + 1), 32, editingLabel[i]))
                        editingLabel[i] = !editingLabel[i];

                    GuiSliderBar((Rectangle){ contentX + 130, (float)rowY, 110, 30 }, NULL, NULL, &values[i], 0.0f, 1000.0f);
                }

            EndScissorMode();

        EndDrawing();
        //----------------------------------------------------------------------------------
    }

    // De-Initialization
    //--------------------------------------------------------------------------------------
    CloseWindow(); // Close window and OpenGL context
    //--------------------------------------------------------------------------------------

    return 0;
}

From raylib 6.2. The original on GitHub.