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 zcmbWf2{_d2|2{r5m?oK+vdu_Dr$mcow4gE43~h`S``$8x3Y8GUh^AsJb+l(4ODB7B zh%7}?N5*m@9cvUKOQtZw|9Q`-%sKr&-|PCH>zb>}nfLoyU(ai~@B5j{6f+aFI8Gb} zgQ0hs?%WT9i73Nh@TVvw_&;)ovueOU@YDNEcEGahRt$hYM5#vmj9{?5=q2A=#K7N+ zy-aOR!(dA*p%47I=e;8^m~r*4okj=!96z;hfAHH2%TLoy$bLjk;&lYtU;U}&0OB<) z9!d9mt6L&YUSzgBhPW=j1>Rfyr@GCS!;%TJT8I62PI9Lt^Air8^o=eUFY1eu4}Dzt z(R^;Eb8bc-^a*S0HrIpxg;^yrpnr=LoGn^YYggc|4rvt+fh>B1I0tO?1ue*$(N0dNwqlpX{j1f&C!u-GoR$&6#G=SRe zQk9m1tpp#~C`KFwlLQ~wBxs7U=zlb4(AVM=T>7^|Cv}nUr)#s=OPA(*^p9dTwMKQC zly3=YEG@noG;(RakC?v4#sdj+-}?T*4-GF1%t;RLd46jr84h2@>vIY!iJM&s8wy2y zzFYEic;k}^^F?8>w(ZteWfMtka_E+iXQJWu7ysKp*D+gl1N@On z6Aq$c1$IeIOUsRc?Y{Qu6Ln|S43!0$>!p34i0-L1s(oH5)MZ#P(W0BzFPYO4tEt>f z*xla?82xW*+K#hbCO$dDp0Rg(YyQ!fQVPw}J=WfIz^-LU3C_#pe74M4D^ywWBHowZ&*a!rPklWun4Y37 zEIutnp3`8cL9_&2GOG)F+NN6yzEG=Qo4$F?@o&)GT{$fN#A3Wb6aR2zXXSd=Cc_dUUOJ|) zO=f;Xf^Bow$2I$=z-0sK4gVra|KfWET%&K=6PbVp!+$D1g7*C{HfF3Sj}G$umJ;Ce z+e`<>f+!Ww9AA%r=xWHLDq72cHNu%uO9rQhy0S|f4Mm^_t|LDm$7i}a@u*7H!1q8d zpqnPe*=?-;HQjJYg}T6Jpo~TQ&f?6jT%0`)GY8fST#K_m?(tYCbAK<}P^032;4$ml z%#+_zT%@-Ra3n@W1+L|Kii^T#G`nU`Fq5Hg!S_YE(&#s|CMofLjv zZ`!j09bXY4E43T-y^Sn9vF9|7aOIAyWUArs6%qo+*G3N2`A`oFZ+qxv&804%lom|< zMpFgr5uTy4X#;LMFJJsFNidF7{QAQLbW+W(zn-bh+!Ct;ZWYYGYZ>mUw<0>ZW>>_8 z8?zE3EAyAsm6{Tu^lf$Q&*MpyVC#21?B zv-pQn2BZW`%?4L^-s8SApK6L`55y@N)(zTv?DJS*EhZ-FfIE}727Uj{@3J9l2l5gA zU~JbXcoT-DJv{S>I>qwl&XOfyQ|Ev_ zSs8Tt3MHE+?c$|HI5E*fIIq0T=!`cJ$hm9%`65$0zt6Yl?c42HW3nERLCm1pn%fQD zU2f7+H}P$*bewgb9C{|Bfr>hH*^&q|h(Me_kF={w43U|x&n;J{Usr}=BlwgxP$I1H zMXr)Ku2mXGx|b+ec_n;}${KX`C3BG=X&&oIhu{>b?O6Y=aWwo5F6 zu1bS%7AZXwv<7@Oqq&#?okan~_ZhXCHe zQeU-87_c`AVyjo7v))87ZT_n9c~-1be(`oQZbqUsGp{8z;q*5HMg1UVRPgS~>5W{Q zFuEX9xZ02#K0~|tMUVN>vVxF*^2p;YTZ*f18%DhjaO`*fvXwwGy? zXYu&@bBe}38qNteTeLgwC^#H`rSv@WFWVOF;yPoI=_qb%*red;UBEob{@+iZj75Hl z8_*HY6F7=x^^EXRw|ght97?AqeC*Q`I&UjH;;tMFQuKUfcR1evy+da*e7gwUa@9uu zKGl4{-*pYf9k;W!@|>kBwhZ(YejslweO~PJ+-|)2Ce_iurI5Cjvm?Rg(q;axJkUa? z)fiW1J*V9xWP7wX-Oj*1N-wOwU09vzJ=hqKIbGLCiH{_dNJ;WN#E}Cy(<>23`{)J= z-M*zbUDrzIl(}c5u4kl8Pjl*o^+a51h=D4dxk^?r944NnqJ%D5$xh-GW&4Y;hyrM~w%y6f+la0T4I}FA*+rO&WUaH$1cp}p` zr=Z%lFBQHbHs;Pahx2gcB(NkgPg6=;y?VyKvv#|=)=vAv-KT| z(5JA2#<>`;SJJZk)N>(>L&DOy|ruQ{zp| ziK`S&@M3#=qW=9wySG4@fmweN=sN|P-+A?b$h*JAGJsD$*x&pGvDs^~K7P~>s$ zK)&Q4IJsxqy=SHq@&_jLf;j00Gc#A@#8%%+_WE1?$(cjXDedBKsLheAi4Wu!>+$D# zZ*x2bH!{MxP`m_d#+ z3D?8LUMd)}Bp&p%A8FxaMn(tzS?vC8GGM>X^AI_4QzGe!yo>1HuUGu~aF~f`#=v`s{56 zJ$k5y#s$^T(3D9@TPsn2nA3iqt&B*)8bK!|GP^~YMWtN=X>IGJJ`3W@;QMd=JcjR& zHIE#6?(X5yqstzQ5fNK`DcLmeDeAt)R$*`P;{dsX;1q<5*Sbhh9m}MN?QBtODWCJV zw<}54&35m};@oRzdqfXj`?9UzOY@r{@2(4oWjifY2uH(Sr~hl^?G85=`3!7trt9LE z-kZ>`Zki-q@w0r0IL|4aklPCEoM^G~nD&mr15ruEGP#^g1?HQ&LFNtp0*m?y==FD?coCt$*a zJ>CheVxkowHbtxpuV`WsK_FY*qldfXZAedh2UUS9rDHBgcL&Rm?~&HyVzm;GE(RtE zSw$-aL&uQ%ogy*rn2XG#wW)AcW^c>Lj&=u+zl!f9``z+F~`dnsMOoXXn> zHrKdvV>6p$Z!cK!&xtC3n;Z#q$lO(_Lx2+WM(U;yf;`mV9%|eI>moPU0E^_%YMvEy zTO+3CyIQv+@C>=@nJq{H4bVl_34~BBIsUAA;UvG#3z&0xOVJHmO+q)ktEXyxNaE*p z28pO)eXaArB^lxRX!l4fc6HWMC3#54{Kt3B*m#kJu8Z4dT`xn&j?(b$Z^3!$Y~(9hEqUFSwW6NS`L{cv~Hz7WPB^1>zqU8 zZku^jNFi>t4D080E#(lJY9k`y|zz#y9v(RYxChV~@1 zj|!$>e-G7`i(Q3nX-JH`iaa>9@8>rus8*WJd2no86n;bBdy_z@>=xv!k4!PS4b-rs zwdpPjoor96p?g7DglAmvtXRn-L9cKf7}@w!tD)NR!QgW5<5bDxxl>}<4J5(>hE zzqF$Ghf&)Glja?TxvdqA^rR3*LkUe<%`BJq%u0p42ba8*aB3-ceyTU72Q!M4kar`A} zYWH%#2CBhZ!Q%cd?SgI5JddwyP>#RCH0#=zFh&!s&R$B`5&OSTO`}bk@sUU~;z0gN zA3?)#%M^!E8P!smS*b$oHzZqI+-A>P6kTw4)Ha7F6HF}w#Z4nZzdYly#sfX?_X>y(lyD}K=Lk*JD&C=yzRjoYYf`SI8TdECH zIYF_Rh|hE1a^_$R!~XFQ#Hsz5z`UiZw8}I%or+UW&_3mmH(WcClvY4CJlyM^1)>yv z?(L)mZ)t^Z0U~)2)@uyEbq@uOJ?2k~#+Wi4bgVfR{enGc>z-htp!B+AZfmEVNBd#A zgTi=i@QzOSBv_Y3CPLc+GpFJziRHF4~bw zFn&(0_l1>#<-eE2*t=iUA@dcgV{%s|?YU#aR9{i1fWBg`@W^10Oqdt~Gb;SB)$r8749`Z-Qs=XO zLcK3MC|S@C1^7UhFwfW!`dXZ_mF6k`=E%U|)=uMnp0B)vdK;&|k(sx3|x8Si5*r)}t#v1!Em;{kMAp zJwEl@e{yd>Ekj@1b+KLiQdClJYkJFD!?`IAs_JyvcIlYK;lv0(mQ?&L=2r)r7pqG? z(axnYY>^^_FrH73Q7 z?8%_^51n3$$CJG!>a#T4Z!6eXDL{dATU}wZ?P=A+JRi@%&Gan{#MLWQuxMj`b%g** zOH!#)*{sTa@`s_Twe6(hYw-D}>DS-ZK^3A1V{0#d3 zF@{f3FwS|4RAZ@ZI6)g70W$FUpvqtG?f1{VZXTFC&O2G5E7n`+6m)C1VfnFRqi=65 z=siT`q3b5H8fT|*;6a?U_lD9lvrEnC!E}=&Cj6=X=!%H3&QnsoNlh{$sG~0zU3b(A zzL;KCUZ(nh&FJlET`O*C5I_DYi4mBH%6RiTrcS3`S?LOLXV-3CR817(YV^-Of#L7Z z+oGB{VpbWr6DJjtF;JqSBW_BFrzbP2dbV}06tCGuB0igCK{h_#K+_Z*P8GAc%X#@W9rt}) zcPU}}dk=?wQFP0V-eJ7TYb~96IgMw(Fq4yky9MfsB0Z>xP~@?aLs$k)J>aEb6_VF+;5+!P!WOY@J)mVAH~5y;a{SvDsD=zEvAN1KP@Lu43t_fdD4z|X1{_=S}I+| z58+8>bcP^j4!OZZ<5NbWSZ6mW^9p63mzUN%wKNx2Z^(KiV?BM)7=HQr{3~Y=CS*pm zS?Fvy{u|u9$=8HjA}1AVFun8pBNtVUBXr3z(Yq`8k7(5Unr@bk2^4zb2x0^IX0&PL ztUI0y&-m37&WP>g#Avd}+;03RT%+L^ZP+7JSrnzq%-L(SE)mP*k(lGcTOS%uS?nkO zgs==ft5Ki3DATm_w_6nA=dq#XVyjnjroK{$NSTmjquS@`(zw}#v{jPU*UI*2Cka`p zg!u~i2K>60`gtpZEoKcJsDMpmw687zy8{F?I(!lvBZ{v$F zA#2u7L`7Ffkn5eUU1zLys}QkFABkNrJYbRVE>x{+*o=(ZV!v>tzA}QIe#B_rkwD0c zV9e4cRrzR+cc>t~p9+TVWTcrTw`^Wi*FF~Z6q$* z$sV~bA@JFXB%hv;KTUAt_hveQ;Ay|lY?19s>jk=rR6rq^DAE0xsh-xf~-Eg)8TZDFGf zNK2kx!&ktsfEGf3cwpO^k-g3*_#OiB%hpy2xxn$u{uHZqWTkwg=YX#(9fn*(uLlWYHcKQCx?7VFznU!hAN9N59DOdH$%u zN42N)QG;%&zlX!8MxPDr!A8ySr-vSHtxa5oesitN<1ASy68xct=vb`U;vLtKbDBL! z=GW2Z#^ZfT{T&8v63$!5W+%RPL89F%vVYa>u$la$s6lQ}oPvV@?`pa;kvaHk(3Zo) zvz}_+xqi{yjVN+9ZRB>GkUN){A>P}C|?K_XK$@dCcRsardAB|=TS4^G}!6Ltqvf!w7*yoR}MZEkt)o$3*P?9&1C zcc-NuuI#%*W3^J>v7GOlSqQL~ftFHlc@|=9-G=2xeFg8~TJr~6q=-UsD>d$-(n&0~Bl z+@`H<&0T#^K-<|Z{H4LextV-`2 z3K#Ogu4?CTxf*P1R^zD#_9}^*-JRP?l7+mze0U6Us8&r*^`Y}a8camH35-M7MKe_r z;Uzla{#Y&GDH#z3{!l{csSJGvb$!8(_cj2Lu%spd&fP2pERjf(OIE6YG?54`6TjV}k+)=*|{)eC_}d z?+m{;-x<#nu_JESA+qD5Ja+{EW5EuIC59sqll}Tss}dP0MdKGxn$7FHRq>Xe>T1y{ z5R`Gr^0{%(@^7>MY=V&oz>|De&b7^$yD&z>CyvzdQyF9I16bgMp4wD&V0N5?@g6( zA#Og}P2s6*5=W6lHGholNAhHht!7BPylTPTGY8i3Y#MmUKE>BW- zuDpvx4f(&u{E~qjN?R|>=8DZ)#zxAjLTCB=S}f?Vnc)z7;i<7x$9*;2cJfw{v%gvd znuv-9@KC&We@crT4#U(l;-FEL$i?L3Y-$P^E;1Y0Zd}gEdGaq|i*_E6y<|QR z@z4I)C6cQ5;QEcgh_t1p6cujWc^@^>eRP3ilQ?=)Bv~z@Qi9i*y$+moOf#chFkmR7 zF71z;U}cy5z92)^icMN-ZDS*Utm($tJr?S9nmZJ7mw*jYH-<>DsKGOP4b#6A)D2A7 zUKV-_E@^FHBb}YNX(&E<~m+GFpCMRB!u@vs?tn(LSmLcoE7onO6G zQIOl6WsVvZT`kKrk^b%70Ww2GMc^X7XbrB+$)9j=JCo4ip<8dlhSPodIg9+u`b7qR ztVjAb-1u@SnON&83MEf{L+%rZAx=LsG4ZDCCEDm_Potdld#fy zs+gySHp8g zT-UBOVW+yJC8G>pp~5QSelNgDJNEVBZ2p~B(EQwx0Qeha2qqQpppn@iCS z3z(%#m~odoG`y2yqKD6nxN`|riG6;uVkyZI2{nI6d@89=_PVArnNtlC?n6+-j{_O= zBQ<68QF+qRqt=~QWG`e$hXj+zl&Tp3B(?HCP_oOHa{Dj`e~l&%w<&>JcK!4xcsQ zr}A!UmFizDkmKtgu4qd_O=9HXr$|3pP;&NLy_w90k<^}ietIee1C=UM`=QF8+AJ#j zS=UBVQFImRs>KQs9}W9?lG5gYLd$g2pnC!}av;$2)mo5$m&A!*n!UrIqz72BCsrZ1 zKhKAz%^(>ZMRE2^lx8D|ZP#v0!uQ&Km z`=JsA!V#dl;lZRh{!GtWer=`q(mBHCT9Sex4ePf9^FdVy@uA7K#oKYDX+HbCqN{qN zgFW%lFVeU4Vws(GqEPy9CU%wF;5v~lWlh?nVJcN7)UevM1=&51JJRP9U{w!5m70va z9>a~CljN6B5K8pD;2%%}fINkH{LMp@K`LsrMDytf^{9|ynQQ^k;@@oTJ~1HRXLO(% zNkjY+p_7oB;H0DD_o;SaJE5E650w!q>A!Wk0TBK&Mla#4#NSz7^kYy0T$f+Hzr_4` z)}E2P;XAuPP4=&vICho%gt`+zn8d)_w9ZJ3LBbiKx05FZTAKY%?DHI64rm2#8}!`$EXH_*AI(WaeQ1@3%S<==?plKd&|ze4Tx!A} zPB|3|(tio{P<-_@NxHo;aEal>_$wHQ0Jw2v2YhPQW9H7)j-p2InR8+bs*WQR?EMBS z!Vn;tcp4fAe@n9SGe-bF>;f5;KL;@HH-pRu2YWzPZJxg%rw)DTg|?ewJl~G*&B;W) zYsCWjQau zMJfdP#UsY>Kca0W{NhSScJl*bcd!_)c9vnWhAkeRP35!28CH4F?zRko!)H9GOYFh=tH~&icmmZbk zqy&2P_~SmRu0w3u4al<4 zUv{l@m?Bp5@HHlf!aP_yJ2o{Z0pLuyP*@wgig5e$D%Mtfm5C)ptUQt92;jkmL2~&^ zqLR^67s&wE#nwRR)@khkZ1o5$6I31Mx3G`Te)x%(-lZ_7uU%Uu>+QzqGy;4~Jj7iA zY?352(QSW7@HEzi0+CBsS%k{Y9v!lBHJ$5l1uImf^rmQYa}p)8-h?v0#!16*r{y8$ z$(MBOd!7uJ^AG^mDcau*QzbIqL^Hp(OLt?(eXBz)$`J=Ck4f1zI9H1EW;emY2m)so;-L>uvJOD0fCRq~<$&Ep}TgdOkKR}njT(2Hpn5)ZY{$;h-$j=A~x1Jw9b58}*{-IwsLO9)h^x z6cYe#+pHj1Fe$yMAmt(vtGBZ>(S{Iqj!29GnE8X9cP*Iay=mPBp@8*qgjgT?7Uvv9 z^4uEbzXyJnIyIt4ljK1V?;?fz%aW7<3k0}7Au3=25Vet5y7s3QrtFsQF74H&u z{O()b=pdjn_Cg5aah@!p7FC9%rZgAPA(TfDcy!C^bcI zbRD_}D+AbH9R!ez@R<)jT#gdknII`0vs`WEWGr)x6+Rl_Bj2qc9djL&zf-|O)2yJN zSG8RQI#3C!rpyaWWZX>&Stg%qWRR~i>AHc8;_T*AJOX50T8d7f5X~AXi&>3Q@iu(M z2T%$lu)uj3|NDU4?un9VFitiKh&nMZbsG4T&%yyo(uzcF@qv}#B;CQ(>eN#Wch3W6 z04xWz)n_>RCNYBP`Hd20pUFWD=r{f6iB;%Z43hDdAoZsk-f2GNodiVOKf{np?=LO{ ze*|#qM|1{HQ~8L&4tPVK2p~{!cz9}vLZ?iUY@R&8(g6XtK>*t1XHV9)@;x+#$ekj> zr(&4;|-OR>dFO_&!>&)O!sjLwv1*Igo!U>YsGe;J%MS3;+#Q(GA5bT)t6=r?9 zq6n9E^JG@WyznJzJgWe-=X0V~2k5{Q=p-6wE(YlK{$zVjAkJTf*cyRNt!b3vJv_`< ziEhgBDR}f20@_}AmlZVIrt@*ebiDQ9y?`28t2&xPPWaS6pWn}0_X|9WFd(03QhCP1 z;lxiu4S=JmgSO-rMYns^MJo4PJ;|d^A$<0Ep*62*BA&a^{Q6F?({pI%Kl}bu*VmqX zSXVV69mrhmF_b(*Hq2mO!hpQ7z-P|ex?DZw+{OR-zHbUp5Rr(arDTlXc|I+^u>XyT zPq^#YaK8R*!QcgK;5i@{x~pp+UYq0FC zvd7yFG})cLs{|y2hf3TBfzk}9N329C6>4amfqaD^A(wDU3!QlnLfgBhW^s45OfZCy zEo_6p-9RgGkss6@J#@QV1z^7c(OBanM56nZl_ROO7GY*YEPz=d$;7`!e)njh&n?z)x}W^)J*LjI__bpqzvz|NOM_J;W3LJ z%D+!GNobe^(ggrw^%~(^F~0+GQ99}tG1>y}&c#M@fNm2+qLpbbvUxOObwRk-VwSe} zqW3+vKx_c&ATdbA0`x#xDhClO$zzFu_SE$M#uxe{hXO!X3ke<`q&%Vm1jGA_bExly zD<<$RMDAyfG!^p|r7fK19FHGT2di|BW34cGY!kzD%rr6vIgrbPbeXGmr-6ZWG}$%X zDQq6t&UZrPD)bNUaKghEa~pkz!;AK8!>I+>u6m^F`(AGZs5*c06qd<+mD56Q`GFd` z97wmF4fco}HP={O_yG{&rRjxfTRgKe9EzbzQ=MxQF(_Oz$fm6VN}|ySU0UvOESQ+> zxk1|-*#*N*-X04y2$o44Yx@YHB=vzN`l%li>6B?ydGxQE_ckq!b}hwcvV0Pv?8ZSs znUjzTd8tBidi6Wo{{vH7Z&s-}oSGyzSnuWEtVQIYny7@sK=KMC44x+(|S^Oy2Bu$PUvLEZhtEIBqpffy@m6b;@&S~DB(MVMv zHsaSM|3q{Y-H*(vT2RP{CPBF(q(ZMZ=#fvPh7IT>XaR{XaQ20kHiJHU%ZDR#b|j=0 zng8!P#Ryz_DZux;?!#^704;;iKn^vZQ-p|-jC$(2;rsx2p7qg6TvI@7cHVGHj+G{# z#`tQLvdTb*pJE{zVpxN;`e(QC$K!#d0$S0i!Az&ZjWd~V`)s42C1$%@orsIdAtodh z$$_gt;C>BzkUJ>Dg4hfuAY4(LS_l)4amK3wX%r|9kaZIC=gOo%fBD2erHuYg+ZA(P zcLY1wb>ggw)BYUZwV+sAEsy@|<{pua&FN20`$FQN_}N)iZtLY~Ku!Uu;s@i5Xsicn zJxCXC0H+^&hZVNuMEq88mEt%i6iQk78p?$>yX+>Cb~imq)|&<+Ir3Ol06AHxbO>>S zG*#}H2&6@C3da=Cz(OzIMY>J8-2|1!&MVObg)sx+!W)UFRGL(-f7bxgZ0@Z~y26IP zIVlNum@mXnsWDEtVEz0Tuv;Xp2#BA60RM^fG$i=lAyk*NUP2y!=*9rf*euVGj&$81 zJn>hw8sJ+l*{+{ha_dV|vs1$y@{`CkU# zzP1IDdERWn7zW&Qkfi_V0*To`L=c5UY%X7xLr%~mE2Ew5ep(53udyY>!~vDglqfN zX^?cxbOet}j2^U|`1^yqfXtq1F6N7-hLe(Hwfe!e2*B3OPTlBCYvknos{&u0{)8^~ zznVzxmi1_&E`KMCT_>UdE-Y30j}|f7p4!a6L9neZ+WuVheTM=cz2Q`#hHIoQ`zlb3 zea8ZyC;{9cb(J)oFEpH-#huDqLzi_?5;`_g-<)tCR2!wj36L7gDliybY#mJq%>~l) z1tKYMvxca28A&0E0Gxuea!8PBeP_qPZ5%-61|o3h@{sfo&gTC|_^C+g4)Vbngz%;P z1drStL6|xAs3*X0A$@Vst;XpZq|^i&{#1_qP$c5>Obf42u5>KE!N+$Y2i)ISQI12$ zRa{{@x0xrbt68;>yzzMjkgA34N;K4$i=*!_8Z}t~1NoxjBJ! z=f?)F6)1l-__0BAZ{=JQo`#SGO6_QZam*6F_wVufTBaeOvVrnRiu00B2o}*61M$wj zisU5uiF)dy&#l5ve+28`yLN%F0I_8Q!n_`&k5DAz-U_+`9@& zY3?=#*X*e9QDN=H6TGeLu_oeTKblu{A9OQK7{bzwlf8(=??pJ`;=SW%Yei&*N47CZ z?A-y}8u%tixgFdba$7^Z=D@#{EJdvAm-6wZ2&&snX7nseMA)HA*_$o7a1m+RwH$X( ziyWL5f)O^{wjB}A)X*^%Yw^}>^Wf_t7W}UPLHiwe5-n^1Bz-pqU>+V12RNEtd3Nw8 z@(WZ$Pd+`QS>v}N@@X~XUBSmfbmp1qE{yHgSSHz77X8npS+HdY2$GQK)Gt<6^5vrJ zv=9Zue$JZFb$liTwbBF*cE~UEbBKo^!OxX=xo9{UZlE!P6+I@9o;t6?Nrz~F`YOeINnFt-vFXO>g&y^uqKuzxrPGjf`M_(x>e<2%vQw)tO;)J~+$|Mx%)0ki4 z`oVS3FtAU6_5SA|Z%X%HT7j?>mt~`k^<_(DY~b<2q1lLrQ-@2UN3INTkl-eukn8v} z@yRmzq*Vl!85@|TK#KOmLs{aT5gUO2X)v6w04uyO6?p?rQ6f2X<|CJ*m#hA-CevJ& zQ*fhJslk7Sy#7C=JLjeGXAqv?LLeL1h?;;l*56}zpxkkxP|`>pM}VWhWz-8U`OcHY z8j2uQbY6fu$6N=AQEKWd5q=>(G1O7FAp_@rbcaff=VT$g|-V!~V`|AX> z-ZveP!+a;hm`h5M_NT2XikQWUp54Jm%(?w~0MF+`<^Y+*^|Sd>B2i5m@ma@1dHY>+ znedXg!Vcy?S*0kgJ~5o`W^r@h+;Mmb$Uzf(e*(g=P<*cICR_Pq^@hg8HNxksOB9`R z$^5TM8y;1xMpy-d2s8}^D7{K|Qt%oeE74w%oH8zh0#$Z{?1pn+A%XY8N)jg5;1%)3 z?FHprHSDACW>@S3HQ#pWrOtvd|6N*#8ij!yWFn~|H~appZb9Y3&`*n^?2j`mhOpSYMbLRv__#)Z_rTGx}ekw3PE{+#A8q926da z!!^OZlnNz0x6>$C6807RgadTB7Y1t*fXv{>8_w`EH}I1UngR+H{O-eE0W#+jp zW(S1FLcb7U4Sx0k9FqV1FhsOFd@5&-m*Wb$B|`Twti^g?Z=dCk%*~EG0|Q{3yRaBG zTHjZb5%6umY3_?t#9|ou@8&0`+4oLCb4}n!E;{FCmFAw5S1N%2U*Y2z*E2FZJOaLg zo}QbHo|}w*27b2!yz~C`w%Lz4vmYl6XWoO?M}nsdgQuUvf|o;IJEvJa_w$J?pSn{% zH3LSVro1Vqe6jz`wVJ^>hvvb#)2O+#>!D_-kBh4^4m>V?mT~y;?1!FBv+p*|_AAaU Yx?Iq9N5S^}2i|)>+z(>b+SA%=ui>}WIkC}) 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 u9m472jNeo*p8eIJ7=K`s^qxYWUh?&YA-{7*Ay5