pie chart
There is no browser build of this example yet.
Is it the same as C?
- Desktop:verified The Zig version renders a final frame that is byte-identical to the C example.
- Browser:verified In headless Chrome, the Zig version gives a final frame byte-identical to the C example built for the web.
//! 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.