@@ -30,6 +30,7 @@ typedef struct
3030{
3131 Point * points ;
3232 int64_t numPoints ;
33+ char * name ;
3334 Style style ;
3435} Series ;
3536
@@ -38,8 +39,8 @@ typedef struct
3839{
3940 int64_t xNumLines , yNumLines ;
4041 uint32_t color ;
41- bool labelled ;
4242 int64_t fontSize ;
43+ bool visible , labelled ;
4344} Grid ;
4445
4546
@@ -48,15 +49,23 @@ typedef struct
4849 char * x , * y , * graph ;
4950 uint32_t color ;
5051 int64_t fontSize ;
52+ bool visible ;
5153} Titles ;
5254
5355
56+ typedef struct
57+ {
58+ bool visible ;
59+ } Legend ;
60+
61+
5462typedef struct
5563{
5664 Series * series ;
5765 int64_t numSeries ;
5866 Grid grid ;
5967 Titles titles ;
68+ Legend legend ;
6069} Plot ;
6170
6271
@@ -67,13 +76,55 @@ typedef struct
6776} ScreenTransform ;
6877
6978
70- static Rectangle getClientRect ()
79+ static Rectangle getClientRectWithLegend ()
7180{
7281 const int width = GetScreenWidth (), height = GetScreenHeight ();
7382 return (Rectangle ){0.15 * width , 0.05 * height , 0.8 * width , 0.8 * height };
7483}
7584
7685
86+ static Rectangle getLegendRect (const Plot * plot )
87+ {
88+ const int dashLength = 20 , margin = 20 ;
89+ Rectangle legendRect = {0 };
90+
91+ if (!plot -> legend .visible )
92+ return legendRect ;
93+
94+ for (int iSeries = 0 ; iSeries < plot -> numSeries ; iSeries ++ )
95+ {
96+ const int labelWidth = MeasureText (plot -> series [iSeries ].name , plot -> grid .fontSize );
97+ if (labelWidth > legendRect .width )
98+ legendRect .width = labelWidth ;
99+ }
100+
101+ legendRect .width += dashLength + 2 * margin ;
102+
103+ const Rectangle clientRectWithLegend = getClientRectWithLegend ();
104+
105+ if (legendRect .width > clientRectWithLegend .width / 2 )
106+ legendRect .width = clientRectWithLegend .width / 2 ;
107+
108+ legendRect .height = clientRectWithLegend .height ;
109+ legendRect .x = clientRectWithLegend .x + clientRectWithLegend .width - legendRect .width ;
110+ legendRect .y = clientRectWithLegend .y ;
111+
112+ return legendRect ;
113+ }
114+
115+
116+ static Rectangle getClientRect (const Plot * plot )
117+ {
118+ const Rectangle clientRectWithLegend = getClientRectWithLegend ();
119+ const Rectangle legendRect = getLegendRect (plot );
120+
121+ Rectangle clientRect = clientRectWithLegend ;
122+ clientRect .width -= legendRect .width ;
123+
124+ return clientRect ;
125+ }
126+
127+
77128static Vector2 getScreenPoint (const Point point , const ScreenTransform * transform )
78129{
79130 return (Vector2 ){transform -> xScale * (point .x - transform -> dx ), transform -> yScale * (point .y - transform -> dy )};
@@ -86,9 +137,9 @@ static Point getGraphPoint(const Vector2 point, const ScreenTransform *transform
86137}
87138
88139
89- static void setTransformToMinMax (ScreenTransform * transform , const Point * minPt , const Point * maxPt )
140+ static void setTransformToMinMax (const Plot * plot , ScreenTransform * transform , const Point * minPt , const Point * maxPt )
90141{
91- Rectangle rect = getClientRect ();
142+ Rectangle rect = getClientRect (plot );
92143
93144 transform -> xScale = (maxPt -> x > minPt -> x ) ? (rect .width / (maxPt -> x - minPt -> x )) : 1.0 ;
94145 transform -> yScale = (maxPt -> y > minPt -> y ) ? - (rect .height / (maxPt -> y - minPt -> y )) : 1.0 ;
@@ -98,7 +149,7 @@ static void setTransformToMinMax(ScreenTransform *transform, const Point *minPt,
98149}
99150
100151
101- static void resetTransform (ScreenTransform * transform , const Plot * plot )
152+ static void resetTransform (const Plot * plot , ScreenTransform * transform )
102153{
103154 Point minPt = (Point ){ DBL_MAX , DBL_MAX };
104155 Point maxPt = (Point ){- DBL_MAX , - DBL_MAX };
@@ -116,50 +167,49 @@ static void resetTransform(ScreenTransform *transform, const Plot *plot)
116167 }
117168 }
118169
119- setTransformToMinMax (transform , & minPt , & maxPt );
170+ setTransformToMinMax (plot , transform , & minPt , & maxPt );
120171}
121172
122173
123- static void resizeTransform (ScreenTransform * transform , const Rectangle * rect )
174+ static void resizeTransform (const Plot * plot , ScreenTransform * transform , const Rectangle * rect )
124175{
125176 const Point minPt = getGraphPoint ((Vector2 ){rect -> x , rect -> y + rect -> height }, transform );
126177 const Point maxPt = getGraphPoint ((Vector2 ){rect -> x + rect -> width , rect -> y }, transform );
127178
128- setTransformToMinMax (transform , & minPt , & maxPt );
179+ setTransformToMinMax (plot , transform , & minPt , & maxPt );
129180}
130181
131182
132- static void panTransform (ScreenTransform * transform , const Vector2 * delta )
183+ static void panTransform (const Plot * plot , ScreenTransform * transform , const Vector2 * delta )
133184{
134185 transform -> dx -= delta -> x / transform -> xScale ;
135186 transform -> dy -= delta -> y / transform -> yScale ;
136187}
137188
138189
139- static void zoomTransform (ScreenTransform * transform , const Plot * plot , const Rectangle * zoomRect )
190+ static void zoomTransform (const Plot * plot , ScreenTransform * transform , const Rectangle * zoomRect )
140191{
141192 if (zoomRect -> width == 0 && zoomRect -> height == 0 )
142193 return ;
143194
144195 if (zoomRect -> width < 0 || zoomRect -> height < 0 )
145196 {
146- resetTransform (transform , plot );
197+ resetTransform (plot , transform );
147198 return ;
148199 }
149200
150- resizeTransform (transform , zoomRect );
201+ resizeTransform (plot , transform , zoomRect );
151202}
152203
153204
154205static void drawGraph (const Plot * plot , const ScreenTransform * transform )
155206{
156- Rectangle clientRect = getClientRect ();
207+ Rectangle clientRect = getClientRect (plot );
157208 BeginScissorMode (clientRect .x , clientRect .y , clientRect .width , clientRect .height );
158209
159210 for (int iSeries = 0 ; iSeries < plot -> numSeries ; iSeries ++ )
160211 {
161212 Series * series = & plot -> series [iSeries ];
162-
163213 switch (series -> style .kind )
164214 {
165215 case STYLE_LINE :
@@ -198,10 +248,13 @@ static void drawGraph(const Plot *plot, const ScreenTransform *transform)
198248
199249static void drawGrid (const Plot * plot , const ScreenTransform * transform , int * maxYLabelWidth )
200250{
251+ if (maxYLabelWidth )
252+ * maxYLabelWidth = 0 ;
253+
201254 if (plot -> grid .xNumLines <= 0 || plot -> grid .yNumLines <= 0 )
202255 return ;
203256
204- const Rectangle clientRect = getClientRect ();
257+ const Rectangle clientRect = getClientRect (plot );
205258
206259 const double xSpan = clientRect .width / transform -> xScale ;
207260 const double ySpan = - clientRect .height / transform -> yScale ;
@@ -224,7 +277,8 @@ static void drawGrid(const Plot *plot, const ScreenTransform *transform, int *ma
224277 for (int i = 0 , x = startPtScreen .x ; x < clientRect .x + clientRect .width ; i ++ , x = startPtScreen .x + i * xStep * transform -> xScale )
225278 {
226279 // Line
227- DrawLineEx ((Vector2 ){x , clientRect .y }, (Vector2 ){x , clientRect .y + clientRect .height }, 1 , * (Color * )& plot -> grid .color );
280+ if (plot -> grid .visible )
281+ DrawLineEx ((Vector2 ){x , clientRect .y }, (Vector2 ){x , clientRect .y + clientRect .height }, 1 , * (Color * )& plot -> grid .color );
228282
229283 // Label
230284 if (plot -> grid .labelled )
@@ -240,13 +294,11 @@ static void drawGrid(const Plot *plot, const ScreenTransform *transform, int *ma
240294 }
241295
242296 // Horizontal grid
243- if (maxYLabelWidth )
244- * maxYLabelWidth = 0 ;
245-
246297 for (int j = 0 , y = startPtScreen .y ; y > clientRect .y ; j ++ , y = startPtScreen .y + j * yStep * transform -> yScale )
247298 {
248299 // Line
249- DrawLineEx ((Vector2 ){clientRect .x , y }, (Vector2 ){clientRect .x + clientRect .width , y }, 1 , * (Color * )& plot -> grid .color );
300+ if (plot -> grid .visible )
301+ DrawLineEx ((Vector2 ){clientRect .x , y }, (Vector2 ){clientRect .x + clientRect .width , y }, 1 , * (Color * )& plot -> grid .color );
250302
251303 // Label
252304 if (plot -> grid .labelled )
@@ -268,7 +320,10 @@ static void drawGrid(const Plot *plot, const ScreenTransform *transform, int *ma
268320
269321static void drawTitles (const Plot * plot , const ScreenTransform * transform , int maxYLabelWidth )
270322{
271- Rectangle clientRect = getClientRect ();
323+ if (!plot -> titles .visible )
324+ return ;
325+
326+ Rectangle clientRect = getClientRect (plot );
272327
273328 // Horizontal axis
274329 if (plot -> titles .x && TextLength (plot -> titles .x ) > 0 )
@@ -305,6 +360,52 @@ static void drawTitles(const Plot *plot, const ScreenTransform *transform, int m
305360}
306361
307362
363+ static void drawLegend (const Plot * plot )
364+ {
365+ if (!plot -> legend .visible )
366+ return ;
367+
368+ const int dashLength = 20 , margin = 20 ;
369+
370+ const Rectangle legendRect = getLegendRect (plot );
371+
372+ for (int iSeries = 0 ; iSeries < plot -> numSeries ; iSeries ++ )
373+ {
374+ Series * series = & plot -> series [iSeries ];
375+
376+ // Legend mark
377+ switch (series -> style .kind )
378+ {
379+ case STYLE_LINE :
380+ {
381+ Vector2 dashPt1 = (Vector2 ){legendRect .x + margin , legendRect .y + plot -> grid .fontSize / 2 + iSeries * (plot -> grid .fontSize + margin )};
382+ Vector2 dashPt2 = dashPt1 ;
383+ dashPt2 .x += dashLength ;
384+
385+ DrawLineEx (dashPt1 , dashPt2 , series -> style .width , * (Color * )& series -> style .color );
386+ break ;
387+ }
388+
389+ case STYLE_SCATTER :
390+ {
391+ Vector2 pt = (Vector2 ){legendRect .x + margin + dashLength / 2 , legendRect .y + plot -> grid .fontSize / 2 + iSeries * (plot -> grid .fontSize + margin )};
392+
393+ DrawCircleV (pt , series -> style .width , * (Color * )& series -> style .color );
394+ break ;
395+ }
396+
397+ default : break ;
398+ }
399+
400+ // Legend text
401+ const int labelX = legendRect .x + dashLength + 2 * margin ;
402+ const int labelY = legendRect .y + iSeries * (plot -> grid .fontSize + margin );
403+
404+ DrawText (series -> name , labelX , labelY , plot -> grid .fontSize , * (Color * )& plot -> grid .color );
405+ }
406+ }
407+
408+
308409static void drawZoomRect (Rectangle zoomRect , const ScreenTransform * transform )
309410{
310411 if (zoomRect .width < 0 )
@@ -331,12 +432,12 @@ void umplot_plot(UmkaStackSlot *params, UmkaStackSlot *result)
331432 InitWindow (640 , 480 , "UmPlot" );
332433 SetTargetFPS (30 );
333434
334- Rectangle clientRect = getClientRect ();
435+ Rectangle clientRect = getClientRect (plot );
335436 Rectangle zoomRect = clientRect ;
336437 bool showZoomRect = false;
337438
338439 ScreenTransform transform ;
339- resetTransform (& transform , plot );
440+ resetTransform (plot , & transform );
340441
341442 while (!WindowShouldClose ())
342443 {
@@ -347,8 +448,8 @@ void umplot_plot(UmkaStackSlot *params, UmkaStackSlot *result)
347448 // Resizing
348449 if (IsWindowResized ())
349450 {
350- resizeTransform (& transform , & clientRect );
351- clientRect = zoomRect = getClientRect ();
451+ resizeTransform (plot , & transform , & clientRect );
452+ clientRect = zoomRect = getClientRect (plot );
352453 }
353454
354455 // Zooming
@@ -366,15 +467,15 @@ void umplot_plot(UmkaStackSlot *params, UmkaStackSlot *result)
366467
367468 if (IsMouseButtonReleased (MOUSE_BUTTON_LEFT ))
368469 {
369- zoomTransform (& transform , plot , & zoomRect );
370- zoomRect = getClientRect ();
470+ zoomTransform (plot , & transform , & zoomRect );
471+ zoomRect = getClientRect (plot );
371472 showZoomRect = false;
372473 }
373474
374475 // Panning
375476 if (IsMouseButtonDown (MOUSE_BUTTON_RIGHT ) && CheckCollisionPointRec (pos , clientRect ))
376477 {
377- panTransform (& transform , & delta );
478+ panTransform (plot , & transform , & delta );
378479 }
379480
380481 // Draw
@@ -394,6 +495,9 @@ void umplot_plot(UmkaStackSlot *params, UmkaStackSlot *result)
394495 // Titles
395496 drawTitles (plot , & transform , maxYLabelWidth );
396497
498+ // Legend
499+ drawLegend (plot );
500+
397501 // Zoom rectangle
398502 if (showZoomRect )
399503 drawZoomRect (zoomRect , & transform );
0 commit comments