|
|
|
@ -22,6 +22,8 @@ |
|
|
|
#define RAYGUI_IMPLEMENTATION |
|
|
|
#include "raygui.h" |
|
|
|
|
|
|
|
#define MAX_PIE_SLICES 10 // Max pie slices |
|
|
|
|
|
|
|
//------------------------------------------------------------------------------------ |
|
|
|
// Program main entry point |
|
|
|
//------------------------------------------------------------------------------------ |
|
|
|
@ -34,15 +36,14 @@ int main(void) |
|
|
|
|
|
|
|
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - pie chart"); |
|
|
|
|
|
|
|
#define MAX_SLICES 10 |
|
|
|
int sliceCount = 7; |
|
|
|
float donutInnerRadius = 25.0f; |
|
|
|
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}; |
|
|
|
float values[MAX_PIE_SLICES] = { 300.0f, 100.0f, 450.0f, 350.0f, 600.0f, 380.0f, 750.0f }; // Initial slice values |
|
|
|
char labels[MAX_PIE_SLICES][32] = { 0 }; |
|
|
|
bool editingLabel[MAX_PIE_SLICES] = { 0 }; |
|
|
|
|
|
|
|
for (int i = 0; i < MAX_SLICES; i++) |
|
|
|
snprintf(labels[i], 32, "Slice %i", i + 1); |
|
|
|
for (int i = 0; i < MAX_PIE_SLICES; i++) |
|
|
|
snprintf(labels[i], 32, "Slice %02i", i + 1); |
|
|
|
|
|
|
|
bool showValues = true; |
|
|
|
bool showPercentages = false; |
|
|
|
@ -50,7 +51,32 @@ int main(void) |
|
|
|
int hoveredSlice = -1; |
|
|
|
Rectangle scrollPanelBounds = {0}; |
|
|
|
Vector2 scrollContentOffset = {0}; |
|
|
|
Rectangle view = {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); |
|
|
|
//-------------------------------------------------------------------------------------- |
|
|
|
@ -60,32 +86,9 @@ int main(void) |
|
|
|
{ |
|
|
|
// 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]; |
|
|
|
totalValue = 0.0f; |
|
|
|
for (int i = 0; i < sliceCount; i++) totalValue += values[i]; |
|
|
|
|
|
|
|
// Check for mouse hover over slices |
|
|
|
hoveredSlice = -1; // Reset hovered slice |
|
|
|
@ -94,23 +97,24 @@ int main(void) |
|
|
|
{ |
|
|
|
float dx = mousePos.x - center.x; |
|
|
|
float dy = mousePos.y - center.y; |
|
|
|
float distance = sqrtf(dx * dx + dy * dy); |
|
|
|
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 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)) |
|
|
|
float sweep = (totalValue > 0)? (values[i]/totalValue)*360.0f : 0.0f; |
|
|
|
|
|
|
|
if ((angle >= currentAngle) && (angle < (currentAngle + sweep))) |
|
|
|
{ |
|
|
|
hoveredSlice = i; |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
currentAngle += sweep; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -120,116 +124,96 @@ int main(void) |
|
|
|
// 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 |
|
|
|
// 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; |
|
|
|
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 += 5.0f; |
|
|
|
// 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 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]; |
|
|
|
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, 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); |
|
|
|
} |
|
|
|
// 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); |
|
|
|
} |
|
|
|
|
|
|
|
if(showDonut) |
|
|
|
{ |
|
|
|
// Draw inner circle to create donut effect |
|
|
|
DrawCircle(center.x, center.y, donutInnerRadius, RAYWHITE); |
|
|
|
} |
|
|
|
|
|
|
|
startAngle += sweepAngle; |
|
|
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
|
|
|
|
|
|
// UI control panel |
|
|
|
//------------------------------------------------------------------------------ |
|
|
|
DrawRectangleRec(panelRect, Fade(LIGHTGRAY, 0.5f)); |
|
|
|
DrawRectangleLinesEx(panelRect, 1.0f, GRAY); |
|
|
|
// TODO: This is a hacky solution, better use DrawRing() |
|
|
|
if (showDonut) DrawCircle(center.x, center.y, donutInnerRadius, RAYWHITE); |
|
|
|
|
|
|
|
int currentY = (int)panelPos.y + 12; // Start a bit lower for margin |
|
|
|
startAngle += sweepAngle; |
|
|
|
} |
|
|
|
|
|
|
|
GuiSpinner((Rectangle){ panelPos.x + 95, (float)currentY, 125, 25 }, "Slices ", &sliceCount, 1, MAX_SLICES, false); |
|
|
|
currentY += 40; |
|
|
|
// UI control panel |
|
|
|
DrawRectangleRec(panelRect, Fade(LIGHTGRAY, 0.5f)); |
|
|
|
DrawRectangleLinesEx(panelRect, 1.0f, GRAY); |
|
|
|
|
|
|
|
GuiCheckBox((Rectangle){ panelPos.x + 20, (float)currentY, 20, 20 }, "Show Values", &showValues); |
|
|
|
currentY += 30; |
|
|
|
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); |
|
|
|
|
|
|
|
GuiCheckBox((Rectangle){ panelPos.x + 20, (float)currentY, 20, 20 }, "Show Percentages", &showPercentages); |
|
|
|
currentY += 30; |
|
|
|
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(); |
|
|
|
|
|
|
|
GuiCheckBox((Rectangle){ panelPos.x + 20, (float)currentY, 20, 20 }, "Make Donut", &showDonut); |
|
|
|
currentY += 30; |
|
|
|
GuiLine((Rectangle){ panelPos.x + 10, (float)panelPos.y + 12 + 170, panelRect.width - 20, 1 }, NULL); |
|
|
|
|
|
|
|
if(showDonut) |
|
|
|
{ |
|
|
|
GuiSliderBar((Rectangle){ panelPos.x + 80, (float)currentY, panelRect.width - 100, 30 }, |
|
|
|
"Inner Radius", NULL, &donutInnerRadius, 5.0f, radius - 10.0f); |
|
|
|
currentY += 40; |
|
|
|
} |
|
|
|
// 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; |
|
|
|
|
|
|
|
GuiLine((Rectangle){ panelPos.x + 10, (float)currentY, panelRect.width - 20, 1 }, NULL); |
|
|
|
currentY += 20; |
|
|
|
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 |
|
|
|
|
|
|
|
// 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; |
|
|
|
BeginScissorMode((int)view.x, (int)view.y, (int)view.width, (int)view.height); |
|
|
|
|
|
|
|
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 |
|
|
|
for (int i = 0; i < sliceCount; i++) |
|
|
|
{ |
|
|
|
const int rowY = (int)(contentY + 5 + i*35); |
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
// 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]; |
|
|
|
|
|
|
|
// 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); |
|
|
|
} |
|
|
|
|
|
|
|
GuiSliderBar((Rectangle){contentX + 130, (float)rowY, 110, 30}, |
|
|
|
NULL, NULL, &values[i], 0.0f, 1000.0f); |
|
|
|
} |
|
|
|
EndScissorMode(); |
|
|
|
EndScissorMode(); |
|
|
|
|
|
|
|
EndDrawing(); |
|
|
|
//---------------------------------------------------------------------------------- |
|
|
|
|