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