Skip to content

Commit 831e9e3

Browse files
authored
Add legend
1 parent 1a6d61c commit 831e9e3

File tree

3 files changed

+241
-123
lines changed

3 files changed

+241
-123
lines changed

umplot.c

Lines changed: 132 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
5462
typedef 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+
77128
static 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

154205
static 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

199249
static 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

269321
static 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+
308409
static 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

Comments
 (0)