shapes_pie_chart.c 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. /*******************************************************************************************
  2. *
  3. * raylib [shapes] example - pie chart
  4. *
  5. * Example complexity rating: [★★★☆] 3/4
  6. *
  7. * Example originally created with raylib 5.5, last time updated with raylib 5.6
  8. *
  9. * Example contributed by Gideon Serfontein (@GideonSerf) and reviewed by Ramon Santamaria (@raysan5)
  10. *
  11. * Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
  12. * BSD-like license that allows static linking with closed source software
  13. *
  14. * Copyright (c) 2025 Gideon Serfontein (@GideonSerf)
  15. *
  16. ********************************************************************************************/
  17. #include "raylib.h"
  18. #include <math.h>
  19. #include <stdio.h>
  20. #define RAYGUI_IMPLEMENTATION
  21. #include "raygui.h"
  22. #define MAX_PIE_SLICES 10 // Max pie slices
  23. //------------------------------------------------------------------------------------
  24. // Program main entry point
  25. //------------------------------------------------------------------------------------
  26. int main(void)
  27. {
  28. // Initialization
  29. //--------------------------------------------------------------------------------------
  30. const int screenWidth = 800;
  31. const int screenHeight = 450;
  32. InitWindow(screenWidth, screenHeight, "raylib [shapes] example - pie chart");
  33. int sliceCount = 7;
  34. float donutInnerRadius = 25.0f;
  35. float values[MAX_PIE_SLICES] = { 300.0f, 100.0f, 450.0f, 350.0f, 600.0f, 380.0f, 750.0f }; // Initial slice values
  36. char labels[MAX_PIE_SLICES][32] = { 0 };
  37. bool editingLabel[MAX_PIE_SLICES] = { 0 };
  38. for (int i = 0; i < MAX_PIE_SLICES; i++)
  39. snprintf(labels[i], 32, "Slice %02i", i + 1);
  40. bool showValues = true;
  41. bool showPercentages = false;
  42. bool showDonut = false;
  43. int hoveredSlice = -1;
  44. Rectangle scrollPanelBounds = {0};
  45. Vector2 scrollContentOffset = {0};
  46. Rectangle view = { 0 };
  47. // UI layout parameters
  48. const int panelWidth = 270;
  49. const int panelMargin = 5;
  50. // UI Panel top-left anchor
  51. const Vector2 panelPos = {
  52. (float)screenWidth - panelMargin - panelWidth,
  53. (float)panelMargin
  54. };
  55. // UI Panel rectangle
  56. const Rectangle panelRect = {
  57. panelPos.x, panelPos.y,
  58. (float)panelWidth,
  59. (float)screenHeight - 2.0f*panelMargin
  60. };
  61. // Pie chart geometry
  62. const Rectangle canvas = { 0, 0, panelPos.x, (float)screenHeight };
  63. const Vector2 center = { canvas.width/2.0f, canvas.height/2.0f};
  64. const float radius = 205.0f;
  65. // Total value for percentage calculations
  66. float totalValue = 0.0f;
  67. SetTargetFPS(60);
  68. //--------------------------------------------------------------------------------------
  69. // Main game loop
  70. while (!WindowShouldClose())
  71. {
  72. // Update
  73. //----------------------------------------------------------------------------------
  74. // Calculate total value for percentage calculations
  75. totalValue = 0.0f;
  76. for (int i = 0; i < sliceCount; i++) totalValue += values[i];
  77. // Check for mouse hover over slices
  78. hoveredSlice = -1; // Reset hovered slice
  79. Vector2 mousePos = GetMousePosition();
  80. if (CheckCollisionPointRec(mousePos, canvas)) // Only check if mouse is inside the canvas
  81. {
  82. float dx = mousePos.x - center.x;
  83. float dy = mousePos.y - center.y;
  84. float distance = sqrtf(dx*dx + dy*dy);
  85. if (distance <= radius) // Inside the pie radius
  86. {
  87. float angle = atan2f(dy, dx)*RAD2DEG;
  88. if (angle < 0) angle += 360;
  89. float currentAngle = 0.0f;
  90. for (int i = 0; i < sliceCount; i++)
  91. {
  92. float sweep = (totalValue > 0)? (values[i]/totalValue)*360.0f : 0.0f;
  93. if ((angle >= currentAngle) && (angle < (currentAngle + sweep)))
  94. {
  95. hoveredSlice = i;
  96. break;
  97. }
  98. currentAngle += sweep;
  99. }
  100. }
  101. }
  102. //----------------------------------------------------------------------------------
  103. // Draw
  104. //----------------------------------------------------------------------------------
  105. BeginDrawing();
  106. ClearBackground(RAYWHITE);
  107. // Draw the pie chart on the canvas
  108. float startAngle = 0.0f;
  109. for (int i = 0; i < sliceCount; i++)
  110. {
  111. float sweepAngle = (totalValue > 0)? (values[i]/totalValue)*360.0f : 0.0f;
  112. float midAngle = startAngle + sweepAngle/2.0f; // Middle angle for label positioning
  113. Color color = ColorFromHSV((float)i/sliceCount*360.0f, 0.75f, 0.9f);
  114. float currentRadius = radius;
  115. // Make the hovered slice pop out by adding 5 pixels to its radius
  116. if (i == hoveredSlice) currentRadius += 20.0f;
  117. // Draw the pie slice using raylib's DrawCircleSector function
  118. DrawCircleSector(center, currentRadius, startAngle, startAngle + sweepAngle, 120, color);
  119. // Draw the label for the current slice
  120. if (values[i] > 0)
  121. {
  122. char labelText[64] = { 0 };
  123. if (showValues && showPercentages) snprintf(labelText, 64, "%.1f (%.0f%%)", values[i], (values[i]/totalValue)*100.0f);
  124. else if (showValues) snprintf(labelText, 64, "%.1f", values[i]);
  125. else if (showPercentages) snprintf(labelText, 64, "%.0f%%", (values[i]/totalValue)*100.0f);
  126. else labelText[0] = '\0';
  127. Vector2 textSize = MeasureTextEx(GetFontDefault(), labelText, 20, 1);
  128. float labelRadius = radius*0.7f;
  129. Vector2 labelPos = { center.x + cosf(midAngle*DEG2RAD)*labelRadius - textSize.x/2.0f,
  130. center.y + sinf(midAngle*DEG2RAD)*labelRadius - textSize.y/2.0f };
  131. DrawText(labelText, (int)labelPos.x, (int)labelPos.y, 20, WHITE);
  132. }
  133. // Draw inner circle to create donut effect
  134. // TODO: This is a hacky solution, better use DrawRing()
  135. if (showDonut) DrawCircle(center.x, center.y, donutInnerRadius, RAYWHITE);
  136. startAngle += sweepAngle;
  137. }
  138. // UI control panel
  139. DrawRectangleRec(panelRect, Fade(LIGHTGRAY, 0.5f));
  140. DrawRectangleLinesEx(panelRect, 1.0f, GRAY);
  141. GuiSpinner((Rectangle){ panelPos.x + 95, (float)panelPos.y + 12, 125, 25 }, "Slices ", &sliceCount, 1, MAX_PIE_SLICES, false);
  142. GuiCheckBox((Rectangle){ panelPos.x + 20, (float)panelPos.y + 12 + 40, 20, 20 }, "Show Values", &showValues);
  143. GuiCheckBox((Rectangle){ panelPos.x + 20, (float)panelPos.y + 12 + 70, 20, 20 }, "Show Percentages", &showPercentages);
  144. GuiCheckBox((Rectangle){ panelPos.x + 20, (float)panelPos.y + 12 + 100, 20, 20 }, "Make Donut", &showDonut);
  145. if (showDonut) GuiDisable();
  146. GuiSliderBar((Rectangle){ panelPos.x + 80, (float)panelPos.y + 12 + 130, panelRect.width - 100, 30 },
  147. "Inner Radius", NULL, &donutInnerRadius, 5.0f, radius - 10.0f);
  148. GuiEnable();
  149. GuiLine((Rectangle){ panelPos.x + 10, (float)panelPos.y + 12 + 170, panelRect.width - 20, 1 }, NULL);
  150. // Scrollable area for slice editors
  151. scrollPanelBounds = (Rectangle){
  152. panelPos.x + panelMargin,
  153. (float)panelPos.y + 12 + 190,
  154. panelRect.width - panelMargin*2,
  155. panelRect.y + panelRect.height - panelPos.y + 12 + 190 - panelMargin
  156. };
  157. int contentHeight = sliceCount*35;
  158. GuiScrollPanel(scrollPanelBounds, NULL,
  159. (Rectangle){ 0, 0, panelRect.width - 25, (float)contentHeight },
  160. &scrollContentOffset, &view);
  161. const float contentX = view.x + scrollContentOffset.x; // Left of content
  162. const float contentY = view.y + scrollContentOffset.y; // Top of content
  163. BeginScissorMode((int)view.x, (int)view.y, (int)view.width, (int)view.height);
  164. for (int i = 0; i < sliceCount; i++)
  165. {
  166. const int rowY = (int)(contentY + 5 + i*35);
  167. // Color indicator
  168. Color color = ColorFromHSV((float)i/sliceCount*360.0f, 0.75f, 0.9f);
  169. DrawRectangle((int)(contentX + 15), rowY + 5, 20, 20, color);
  170. // Label textbox
  171. if (GuiTextBox((Rectangle){ contentX + 45, (float)rowY, 75, 30 }, labels[i], 32, editingLabel[i])) editingLabel[i] = !editingLabel[i];
  172. GuiSliderBar((Rectangle){ contentX + 130, (float)rowY, 110, 30 }, NULL, NULL, &values[i], 0.0f, 1000.0f);
  173. }
  174. EndScissorMode();
  175. EndDrawing();
  176. //----------------------------------------------------------------------------------
  177. }
  178. // De-Initialization
  179. //--------------------------------------------------------------------------------------
  180. CloseWindow(); // Close window and OpenGL context
  181. //--------------------------------------------------------------------------------------
  182. return 0;
  183. }