From e77743f90003fbca27d66275c33a30a6296f6d2a Mon Sep 17 00:00:00 2001 From: Gideon Serfontein Date: Thu, 2 Oct 2025 17:30:09 +0200 Subject: [PATCH] Added some interactivity to the example --- examples/shapes/shapes_pie_chart.c | 220 ++++++++++++++++++++++----- examples/shapes/shapes_pie_chart.png | Bin 7899 -> 17457 bytes 2 files changed, 178 insertions(+), 42 deletions(-) diff --git a/examples/shapes/shapes_pie_chart.c b/examples/shapes/shapes_pie_chart.c index 9e0d4e11f..2db567d2a 100644 --- a/examples/shapes/shapes_pie_chart.c +++ b/examples/shapes/shapes_pie_chart.c @@ -1,21 +1,22 @@ /******************************************************************************************* -* -* raylib [shapes] example - pie chart -* -* Example complexity rating: [★☆☆☆] 1/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) -* -********************************************************************************************/ + * + * raylib [shapes] example - interactive pie chart + * + * Example complexity rating: [★★☆☆] 2/4 + * + * This example has been created using raylib 5.5 (www.raylib.com) + * raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) + * + * Copyright (c) 2025 Ramon Santamaria (@raysan5) and Gideon Serfontein (@GideonSerf) + * + ********************************************************************************************/ #include "raylib.h" +#include +#include + +#define RAYGUI_IMPLEMENTATION +#include "raygui.h" //------------------------------------------------------------------------------------ // Program main entry point @@ -26,51 +27,186 @@ int main(void) //-------------------------------------------------------------------------------------- const int screenWidth = 800; const int screenHeight = 450; + const int MAX_SLICES = 10; - InitWindow(screenWidth, screenHeight, "raylib [shapes] example - pie chart"); + InitWindow(screenWidth, screenHeight, "raylib [shapes] example - interactive pie chart"); - // Pie slice angles (must sum to 360) - const float angles[] = { 30.0f, 10.0f, 45.0f, 35.0f, 60.0f, 38.0f, 75.0f, 67.0f }; - const int anglesCount = (int)(sizeof(angles)/sizeof(angles[0])); +#define MAX_SLICES 10 + int sliceCount = 7; + float values[MAX_SLICES] = {300.0f, 100.0f, 450.0f, 350.0f, 600.0f, 380.0f, 750.0f}; //initial slice values + char labels[MAX_SLICES][32]; + bool editingLabel[MAX_SLICES] = {false}; - const float radius = 150.0f; - const Vector2 center = { screenWidth/2.0f, screenHeight/2.0f }; + for (int i = 0; i < MAX_SLICES; i++) + snprintf(labels[i], 32, "Slice %i", i + 1); + + bool showValues = true; + bool showPercentages = false; + int hoveredSlice = -1; + Rectangle scrollPanelBounds = {0}; + Vector2 scrollContentOffset = {0}; + Rectangle view = {0}; - // Set our game to run at 60 frames-per-second SetTargetFPS(60); //-------------------------------------------------------------------------------------- // Main game loop - while (!WindowShouldClose()) // Detect window close button or ESC key + while (!WindowShouldClose()) { + // Update + //---------------------------------------------------------------------------------- + //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; + + // Calculate total value for percentage calculations + float 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); - 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 - float lastAngleDeg = 0.0f; + Color color = ColorFromHSV((float)i / sliceCount * 360.0f, 0.75f, 0.9f); + float currentRadius = radius; - // Number of radial segments for sector drawing - const int segments = 100; + // Make the hovered slice pop out by adding 5 pixels to its radius + if (i == hoveredSlice) + currentRadius += 5.0f; - for (int i = 0; i < anglesCount; i++) + // 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) { - float hue = (i/(float)anglesCount)*360.0f; - float sat = 0.75f; - float val = 0.95f; + char labelText[64]; + 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'; - // Convert HSV to RGBA - Color col = ColorFromHSV(hue, sat, val); - - // Draw pie piece using DrawCircleSector() - DrawCircleSector(center, radius, lastAngleDeg, lastAngleDeg + angles[i], - segments, col); - - lastAngleDeg += angles[i]; + Vector2 textSize = MeasureTextEx(GetFontDefault(), labelText, 18, 1); + float labelRadius = radius * 0.7f; + Vector2 labelPos = { + center.x + cosf(midAngle * DEG2RAD) * labelRadius - textSize.x / 2, + center.y + sinf(midAngle * DEG2RAD) * labelRadius - textSize.y / 2}; + DrawText(labelText, (int)labelPos.x, (int)labelPos.y, 18, WHITE); } - DrawText("Pie chart drawing example", 260, 400, 20, LIGHTGRAY); + startAngle += sweepAngle; + } + //------------------------------------------------------------------------------ + + // UI control panel + //------------------------------------------------------------------------------ + DrawRectangleRec(panelRect, Fade(LIGHTGRAY, 0.5f)); + DrawRectangleLinesEx(panelRect, 1.0f, GRAY); + + int currentY = (int)panelPos.y + 12; // Start a bit lower for margin + + GuiSpinner((Rectangle){ panelPos.x + 95, (float)currentY, 125, 25 }, "Slices ", &sliceCount, 1, MAX_SLICES, false); + currentY += 40; + + GuiCheckBox((Rectangle){ panelPos.x + 20, (float)currentY, 20, 20 }, "Show Values", &showValues); + currentY += 30; + + GuiCheckBox((Rectangle){ panelPos.x + 20, (float)currentY, 20, 20 }, "Show Percentages", &showPercentages); + currentY += 40; + + GuiLine((Rectangle){ panelPos.x + 10, (float)currentY, panelRect.width - 20, 1 }, NULL); + currentY += 20; + + // Scrollable area for slice editors + scrollPanelBounds = (Rectangle){ panelPos.x+panelMargin, (float)currentY, panelRect.width-panelMargin*2, panelRect.y + panelRect.height - currentY - panelMargin }; + int contentHeight = sliceCount * 35; + + GuiScrollPanel(scrollPanelBounds, NULL, + (Rectangle){ 0, 0, panelRect.width - 20, (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}, labels[i], 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(); //---------------------------------------------------------------------------------- @@ -78,8 +214,8 @@ int main(void) // De-Initialization //-------------------------------------------------------------------------------------- - CloseWindow(); // Close window and OpenGL context + CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- return 0; -} +} \ No newline at end of file diff --git a/examples/shapes/shapes_pie_chart.png b/examples/shapes/shapes_pie_chart.png index f18854df98e36244d5323bc6dc5eb62937a4de1c..bad7960f476c0d66e32df5d38b7aded5a81b5a75 100644 GIT binary patch literal 17457 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 literal 7899 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 Pfq}u()z4*}Q$iB}FhG0s