1 /*============================================================================
2 BMPGraphing, a library for graphing.
3 Copyright (C) 2005-2017 by Zack T Smith.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
19 The author may be reached at 1@zsmith.co.
20 *===========================================================================*/
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <math.h>
26
27 #include "BMP.h"
28 #include "BMPGraphing.h"
29
30 //----------------------------------------------------------------------------
31 // Name: BMPGraphing_draw_labels_log2
32 // Purpose: Draw the labels and ticks.
33 //----------------------------------------------------------------------------
34 void
BMPGraphing_draw_labels_log2(BMPGraph * graph)35 BMPGraphing_draw_labels_log2 (BMPGraph* graph)
36 {
37 if (!graph || !graph->image)
38 return;
39
40 //----------------------------------------
41 // Horizontal
42 //
43 // Establish min & max x values.
44 //
45 int i = 0;
46 Value min_x = 0x4000000000000000;
47 Value max_x = 0;
48 for (i = 0; i < graph->data_index; i += 2) {
49 Value type = graph->data[i];
50 Value value = graph->data[i+1];
51 if (type == DATUM_X) {
52 if (value < min_x)
53 min_x = value;
54 if (value > max_x)
55 max_x = value;
56 }
57 }
58 graph->min_x = (long long) log2 (min_x);
59 graph->max_x = (long long) ceil (log2 (max_x));
60
61 for (i = graph->min_x; i <= graph->max_x; i++) {
62 char str [200];
63 int x = graph->left_margin +
64 ((i-graph->min_x) * graph->x_span) /
65 (graph->max_x - graph->min_x);
66 int y = graph->height - graph->margin + 10;
67
68 unsigned long y2 = 1 << i;
69 if (y2 < 1536)
70 snprintf (str, 199, "%ld B", y2);
71 else if (y2 < (1<<20)) {
72 snprintf (str, 199, "%ld kB", y2 >> 10);
73 }
74 else {
75 Value j = y2 >> 20;
76 switch ((y2 >> 18) & 3) {
77 case 0: snprintf (str, 199, "%lld MB", (unsigned long long) j); break;
78 case 1: snprintf (str, 199, "%lld.25 MB", (unsigned long long) j); break;
79 case 2: snprintf (str, 199, "%lld.5 MB", (unsigned long long) j); break;
80 case 3: snprintf (str, 199, "%lld.75 MB", (unsigned long long) j); break;
81 }
82 }
83
84 BMP_vline (graph->image, x, y, y - 10, RGB_BLACK);
85 BMP_draw_mini_string (graph->image, str, x - 10, y + 8, RGB_BLACK);
86 }
87
88 //----------------------------------------
89 // Vertical
90 //
91 // Establish min & max y values.
92 //
93 Value min_y = 0x4000000000000000;
94 Value max_y = 0;
95 for (i = 0; i < graph->data_index; i += 2) {
96 Value type = graph->data[i];
97 Value value = graph->data[i+1];
98 if (type == DATUM_Y) {
99 if (value < min_y)
100 min_y = value;
101 if (value > max_y)
102 max_y = value;
103 }
104 }
105 graph->min_y = min_y;
106 graph->max_y = max_y;
107
108 int font_height = 10;
109 int available_height = graph->y_span;
110 int max_labels = available_height / font_height;
111 int preferred_n_labels = graph->max_y/10000;
112 int actual_n_labels;
113 float multiplier = 1;
114 if (preferred_n_labels < max_labels) {
115 actual_n_labels = preferred_n_labels;
116 } else {
117 actual_n_labels = max_labels;
118 multiplier = preferred_n_labels / (float) actual_n_labels;
119 }
120
121 for (i = 0; i <= actual_n_labels; i++) {
122 char str [200];
123 int x = graph->left_margin - 10;
124 int y = graph->height - graph->margin - (i * graph->y_span) / (float)actual_n_labels;
125
126 BMP_hline (graph->image, x, x+10, y, RGB_BLACK);
127
128 int value = (int) (i * multiplier);
129 snprintf (str, 199, "%d GB/s", value);
130 BMP_draw_mini_string (graph->image, str, x - 40, y - MINIFONT_HEIGHT/2, RGB_BLACK);
131 }
132 }
133
134 BMPGraph *
BMPGraphing_new(int w,int h,int x_axis_mode)135 BMPGraphing_new (int w, int h, int x_axis_mode)
136 {
137 if (x_axis_mode != MODE_X_AXIS_LINEAR && x_axis_mode != MODE_X_AXIS_LOG2)
138 return NULL;
139
140 BMPGraph *graph = (BMPGraph*) malloc (sizeof(BMPGraph));
141 if (!graph)
142 return NULL;
143
144 bzero (graph, sizeof(BMPGraph));
145
146 graph->x_axis_mode = x_axis_mode;
147
148 if (w <= 0 || h <= 0) {
149 w = 1920;
150 h = 1080;
151 }
152
153 graph->width = w;
154 graph->height = h;
155 graph->image = BMP_new (w, h);
156 graph->margin = 40;
157 graph->left_margin = 80;
158
159 BMP_clear (graph->image, RGB_WHITE);
160
161 BMP_hline (graph->image, graph->left_margin, graph->width - graph->margin, graph->height - graph->margin, RGB_BLACK);
162 BMP_vline (graph->image, graph->left_margin, graph->margin, graph->height - graph->margin, RGB_BLACK);
163
164 graph->x_span = graph->width - (graph->margin + graph->left_margin);
165 graph->y_span = graph->height - 2 * graph->margin;
166
167 graph->legend_y = graph->margin;
168
169 return graph;
170 }
171
BMPGraphing_set_title(BMPGraph * graph,const char * title)172 void BMPGraphing_set_title (BMPGraph* graph, const char *title)
173 {
174 if (!graph || !title)
175 return;
176
177 if (graph->title)
178 free (graph->title);
179 graph->title = strdup (title);
180
181 BMP_draw_string (graph->image, graph->title, graph->left_margin, graph->margin/2, RGB_BLACK);
182 }
183
184 void
BMPGraphing_new_line(BMPGraph * graph,const char * str,RGB color)185 BMPGraphing_new_line (BMPGraph *graph, const char *str, RGB color)
186 {
187 if (!graph || !graph->image)
188 return;
189
190 BMP_draw_string (graph->image, str, graph->width - graph->margin - 370, graph->legend_y, 0xffffff & color);
191
192 graph->legend_y += 17;
193
194 graph->fg = 0;
195 graph->last_x = graph->last_y = -1;
196
197 if (graph->data_index >= MAX_GRAPH_DATA-2)
198 return; // error ("Too many graph data.");
199
200 graph->data [graph->data_index++] = DATUM_COLOR;
201 graph->data [graph->data_index++] = color;
202 }
203
204 //----------------------------------------------------------------------------
205 // Name: BMPGraphing_add_point
206 // Purpose: Adds a point to this list to be drawn.
207 //----------------------------------------------------------------------------
208 void
BMPGraphing_add_point(BMPGraph * graph,Value x,Value y)209 BMPGraphing_add_point (BMPGraph *graph, Value x, Value y)
210 {
211 if (!graph || !graph->image)
212 return;
213
214 if (graph->data_index >= MAX_GRAPH_DATA-4)
215 return; // error ("Too many graph data.");
216
217 graph->data [graph->data_index++] = DATUM_X;
218 graph->data [graph->data_index++] = x;
219 graph->data [graph->data_index++] = DATUM_Y;
220 graph->data [graph->data_index++] = y;
221 }
222
223 //----------------------------------------------------------------------------
224 // Name: BMPGraphing_plot_log2
225 // Purpose: Plots a point on the current graph.
226 //----------------------------------------------------------------------------
227
228 void
BMPGraphing_plot_log2(BMPGraph * graph,Value x,Value y)229 BMPGraphing_plot_log2 (BMPGraph *graph, Value x, Value y)
230 {
231 if (!graph || !graph->image)
232 return;
233
234 //----------------------------------------
235 // Plot the point. The x axis is
236 // logarithmic, base 2.
237 //
238 double tmp = log2 (x);
239 tmp -= (double) graph->min_x;
240 tmp *= (double) graph->x_span;
241 tmp /= (double) (graph->max_x - graph->min_x);
242
243 int x2 = graph->left_margin + (int) tmp;
244 int y2 = graph->height - graph->margin - (y * graph->y_span) / graph->max_y;
245
246 if (graph->last_x != -1 && graph->last_y != -1) {
247 if (graph->fg & DASHED)
248 BMP_line_dashed (graph->image, graph->last_x, graph->last_y, x2, y2, graph->fg & 0xffffff);
249 else
250 BMP_line (graph->image, graph->last_x, graph->last_y, x2, y2, graph->fg);
251 }
252
253 graph->last_x = x2;
254 graph->last_y = y2;
255 }
256
257 //----------------------------------------------------------------------------
258 // Name: BMPGraphing_plot_linear
259 // Purpose: Plots a point on the current graph.
260 //----------------------------------------------------------------------------
261
262 void
BMPGraphing_plot_linear(BMPGraph * graph,Value x,Value y,Value max_y)263 BMPGraphing_plot_linear (BMPGraph *graph, Value x, Value y, Value max_y)
264 {
265 if (!graph || !graph->image)
266 return;
267
268 //----------------------------------------
269 // Plot the point. The x axis is
270 // logarithmic, base 2. The units of the
271 // y value is kB.
272 //
273 double tmp = 10. + log2 (x);
274 tmp -= (double) XVALUE_MIN;
275 tmp *= (double) graph->x_span;
276 tmp /= (double) (XVALUE_MAX - XVALUE_MIN);
277 int x2 = graph->left_margin + (int) tmp;
278 int y2 = graph->height - graph->margin - (y * graph->y_span) / max_y;
279
280 //printf ("\tx=%d, y=%d\n",x,y); fflush(stdout);
281
282 if (graph->last_x != -1 && graph->last_y != -1) {
283 if (graph->fg & DASHED)
284 BMP_line_dashed (graph->image, graph->last_x, graph->last_y, x2, y2, graph->fg & 0xffffff);
285 else
286 BMP_line (graph->image, graph->last_x, graph->last_y, x2, y2, graph->fg);
287 }
288
289 graph->last_x = x2;
290 graph->last_y = y2;
291 }
292
293 //----------------------------------------------------------------------------
294 // Name: BMPGraphing_make_log2
295 // Purpose: Plots all lines.
296 //----------------------------------------------------------------------------
297
298 static void
BMPGraphing_make_log2(BMPGraph * graph)299 BMPGraphing_make_log2 (BMPGraph *graph)
300 {
301 if (!graph || !graph->image)
302 return;
303
304 BMPGraphing_draw_labels_log2 (graph);
305
306 //----------------------------------------
307 // OK, now draw the lines.
308 //
309 int i;
310 int x = -1, y = -1;
311 for (i = 0; i < graph->data_index; i += 2)
312 {
313 Value type = graph->data[i];
314 Value value = graph->data[i+1];
315
316 switch (type) {
317 case DATUM_Y: y = value; break;
318 case DATUM_X: x = value; break;
319 case DATUM_COLOR:
320 graph->fg = (unsigned long) value;
321 graph->last_x = -1;
322 graph->last_y = -1;
323 break;
324 }
325
326 if (x != -1 && y != -1) {
327 BMPGraphing_plot_log2 (graph, x, y);
328 x = y = -1;
329 }
330 }
331 }
332
333 //----------------------------------------------------------------------------
334 // Name: BMPGraphing_make_linear
335 // Purpose: Plots all lines for the network test graph.
336 //----------------------------------------------------------------------------
337
338 static void
BMPGraphing_make_linear(BMPGraph * graph)339 BMPGraphing_make_linear (BMPGraph *graph)
340 {
341 if (!graph || !graph->image)
342 return;
343
344 int i;
345
346 // No data
347 if (!graph->data_index)
348 return;
349
350 //----------------------------------------
351 // Get the maximum bandwidth in order to
352 // properly scale the graph vertically.
353 //
354 int max_y = 0;
355 for (i = 0; i < graph->data_index; i += 2) {
356 if (graph->data[i] == DATUM_Y) {
357 int y = graph->data [i+1];
358 if (y > max_y)
359 max_y = y;
360 }
361 }
362
363 int range = max_y > 10000 ? 2 : (max_y > 1000 ? 1 : 0);
364 int y_spacing = 1;
365 switch (range) {
366 case 2:
367 // Round up to the next 100.00 MB/sec. (=10000).
368 y_spacing = 10000;
369 break;
370 case 1:
371 // Round up to the next 10.00 MB/sec.
372 y_spacing = 1000;
373 break;
374 case 0:
375 // Round up to the next 1.00 MB/sec.
376 y_spacing = 100;
377 break;
378 }
379 max_y /= y_spacing;
380 max_y *= y_spacing;
381 max_y += y_spacing;
382
383 //----------------------------------------
384 // Draw the axes, ticks & labels.
385 //
386 // X axis:
387 if (XVALUE_MIN < 10)
388 return; // error ("Minimum y is too small.");
389
390 for (i = XVALUE_MIN; i <= XVALUE_MAX; i++) {
391 char str[200];
392 unsigned long y2 = 1 << (i-10); // XX XVALUE_MIN>=10
393 if (y2 < 1024)
394 snprintf (str, 199, "%u kB", (unsigned int) y2);
395 else
396 snprintf (str, 199, "%lu MB", (unsigned long) (y2 >> 10));
397
398 int x = graph->left_margin + ((i - XVALUE_MIN) * graph->x_span) / (XVALUE_MAX - XVALUE_MIN);
399 int y = graph->height - graph->margin + 10;
400
401 BMP_vline (graph->image, x, y, y-10, RGB_BLACK);
402 BMP_draw_mini_string (graph->image, str, x - 10, y+8, RGB_BLACK);
403 }
404
405 //----------
406 // Y axis:
407 // Decide what the tick spacing will be.
408 for (i = 0; i <= max_y; i += y_spacing) {
409 char str[200];
410 unsigned long whole = i / 100;
411 unsigned long frac = i % 100;
412 snprintf (str, 199, "%lu.%02lu MB/s", whole, frac);
413
414 int x = graph->left_margin - 10;
415 int y = graph->height - graph->margin - (i * graph->y_span) / max_y;
416
417 BMP_hline (graph->image, x, x+10, y, RGB_BLACK);
418 BMP_draw_mini_string (graph->image, str, x - 60, y - MINIFONT_HEIGHT/2, RGB_BLACK);
419 }
420
421 //----------------------------------------
422 // Draw the data lines.
423 //
424 int x = -1, y = -1;
425 graph->last_x = -1;
426 graph->last_y = -1;
427 for (i = 0; i < graph->data_index; i += 2)
428 {
429 int type = graph->data[i];
430 long value = graph->data[i+1];
431
432 switch (type) {
433 case DATUM_Y: y = value; break;
434 case DATUM_X: x = value; break;
435 case DATUM_COLOR:
436 graph->fg = (unsigned long) value;
437 graph->last_x = -1;
438 graph->last_y = -1;
439 break;
440 }
441
442 if (x != -1 && y != -1) {
443 BMPGraphing_plot_linear (graph, x, y, max_y);
444 x = y = -1;
445 }
446 }
447 }
448
449 void
BMPGraphing_make(BMPGraph * graph)450 BMPGraphing_make (BMPGraph *graph)
451 {
452 if (!graph)
453 return; // XX silent error
454
455 switch (graph->x_axis_mode) {
456 case MODE_X_AXIS_LOG2:
457 BMPGraphing_make_log2 (graph);
458 break;
459 case MODE_X_AXIS_LINEAR:
460 BMPGraphing_make_linear (graph);
461 break;
462 default:
463 fprintf (stderr, "Invalid graph mode %d.\n", graph->x_axis_mode);
464 break;
465 }
466 }
467
468 void
BMPGraphing_destroy(BMPGraph * graph)469 BMPGraphing_destroy (BMPGraph *graph)
470 {
471 if (!graph)
472 return;
473
474 if (graph->title) {
475 free (graph->title);
476 graph->title = NULL;
477 }
478 if (graph->image) {
479 BMP_destroy (graph->image);
480 graph->image = NULL;
481 }
482
483 free (graph);
484 }
485