summaryrefslogtreecommitdiffstats
path: root/BMPGraphing.c
diff options
context:
space:
mode:
Diffstat (limited to 'BMPGraphing.c')
-rwxr-xr-xBMPGraphing.c486
1 files changed, 486 insertions, 0 deletions
diff --git a/BMPGraphing.c b/BMPGraphing.c
new file mode 100755
index 0000000..61ae0d7
--- /dev/null
+++ b/BMPGraphing.c
@@ -0,0 +1,486 @@
+/*============================================================================
+ BMPGraphing, a library for graphing.
+ Copyright (C) 2005-2014 by Zack T Smith.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+ The author may be reached at veritas@comcast.net.
+ *===========================================================================*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "BMP.h"
+#include "BMPGraphing.h"
+
+//----------------------------------------------------------------------------
+// Name: BMPGraphing_draw_labels_log2
+// Purpose: Draw the labels and ticks.
+//----------------------------------------------------------------------------
+void
+BMPGraphing_draw_labels_log2 (BMPGraph* graph)
+{
+ if (!graph || !graph->image)
+ return;
+
+ //----------------------------------------
+ // Horizontal
+ //
+ // Establish min & max x values.
+ //
+ int i = 0;
+ Value min_x = 0x4000000000000000;
+ Value max_x = 0;
+ for (i = 0; i < graph->data_index; i += 2) {
+ Value type = graph->data[i];
+ Value value = graph->data[i+1];
+ if (type == DATUM_X) {
+ if (value < min_x)
+ min_x = value;
+ if (value > max_x)
+ max_x = value;
+ }
+ }
+ graph->min_x = (long long) log2 (min_x);
+ graph->max_x = (long long) ceil (log2 (max_x));
+
+ for (i = graph->min_x; i <= graph->max_x; i++) {
+ char str [200];
+ int x = graph->left_margin +
+ ((i-graph->min_x) * graph->x_span) /
+ (graph->max_x - graph->min_x);
+ int y = graph->height - graph->margin + 10;
+
+ unsigned long y2 = 1 << i;
+ if (y2 < 1536)
+ snprintf (str, 199, "%ld B", y2);
+ else if (y2 < (1<<20)) {
+ snprintf (str, 199, "%ld kB", y2 >> 10);
+ }
+ else {
+ Value j = y2 >> 20;
+ switch ((y2 >> 18) & 3) {
+ case 0: snprintf (str, 199, "%lld MB", j); break;
+ case 1: snprintf (str, 199, "%lld.25 MB", j); break;
+ case 2: snprintf (str, 199, "%lld.5 MB", j); break;
+ case 3: snprintf (str, 199, "%lld.75 MB", j); break;
+ }
+ }
+
+ BMP_vline (graph->image, x, y, y - 10, RGB_BLACK);
+ BMP_draw_mini_string (graph->image, str, x - 10, y + 8, RGB_BLACK);
+ }
+
+ //----------------------------------------
+ // Vertical
+ //
+ // Establish min & max y values.
+ //
+ Value min_y = 0x4000000000000000;
+ Value max_y = 0;
+ for (i = 0; i < graph->data_index; i += 2) {
+ Value type = graph->data[i];
+ Value value = graph->data[i+1];
+ if (type == DATUM_Y) {
+ if (value < min_y)
+ min_y = value;
+ if (value > max_y)
+ max_y = value;
+ }
+ }
+ graph->min_y = min_y;
+ graph->max_y = max_y;
+
+ int font_height = 10;
+ int available_height = graph->y_span;
+ int max_labels = available_height / font_height;
+ int preferred_n_labels = graph->max_y/10000;
+ int actual_n_labels;
+ float multiplier = 1;
+ if (preferred_n_labels < max_labels) {
+ actual_n_labels = preferred_n_labels;
+ } else {
+ actual_n_labels = max_labels;
+ multiplier = preferred_n_labels / (float) actual_n_labels;
+ }
+
+ for (i = 0; i <= actual_n_labels; i++) {
+ char str [200];
+ int x = graph->left_margin - 10;
+ int y = graph->height - graph->margin - (i * graph->y_span) / (float)actual_n_labels;
+
+ BMP_hline (graph->image, x, x+10, y, RGB_BLACK);
+
+ int value = (int) (i * multiplier);
+ snprintf (str, 199, "%d GB/s", value);
+ BMP_draw_mini_string (graph->image, str, x - 40, y - MINIFONT_HEIGHT/2, RGB_BLACK);
+ }
+}
+
+BMPGraph *
+BMPGraphing_new (int w, int h, int x_axis_mode)
+{
+ if (x_axis_mode != MODE_X_AXIS_LINEAR && x_axis_mode != MODE_X_AXIS_LOG2)
+ return NULL;
+
+ BMPGraph *graph = (BMPGraph*) malloc (sizeof(BMPGraph));
+ if (!graph)
+ return NULL;
+
+ bzero (graph, sizeof(BMPGraph));
+
+ graph->x_axis_mode = x_axis_mode;
+
+ if (w <= 0 || h <= 0) {
+ w = 1920;
+ h = 1080;
+ }
+
+ graph->width = w;
+ graph->height = h;
+ graph->image = BMP_new (w, h);
+ graph->margin = 40;
+ graph->left_margin = 80;
+
+ BMP_clear (graph->image, RGB_WHITE);
+
+ BMP_hline (graph->image, graph->left_margin, graph->width - graph->margin, graph->height - graph->margin, RGB_BLACK);
+ BMP_vline (graph->image, graph->left_margin, graph->margin, graph->height - graph->margin, RGB_BLACK);
+
+ graph->x_span = graph->width - (graph->margin + graph->left_margin);
+ graph->y_span = graph->height - 2 * graph->margin;
+
+ graph->legend_y = graph->margin;
+
+ return graph;
+}
+
+void BMPGraphing_set_title (BMPGraph* graph, const char *title)
+{
+ if (!graph || !title)
+ return;
+
+ if (graph->title)
+ free (graph->title);
+ graph->title = strdup (title);
+
+ BMP_draw_string (graph->image, graph->title, graph->left_margin, graph->margin/2, RGB_BLACK);
+}
+
+void
+BMPGraphing_new_line (BMPGraph *graph, char *str, RGB color)
+{
+ if (!graph || !graph->image)
+ return;
+
+ BMP_draw_string (graph->image, str, graph->width - graph->margin - 320, graph->legend_y, 0xffffff & color);
+
+ graph->legend_y += 17;
+
+ graph->fg = 0;
+ graph->last_x = graph->last_y = -1;
+
+ if (graph->data_index >= MAX_GRAPH_DATA-2)
+ return; // error ("Too many graph data.");
+
+ graph->data [graph->data_index++] = DATUM_COLOR;
+ graph->data [graph->data_index++] = color;
+}
+
+//----------------------------------------------------------------------------
+// Name: BMPGraphing_add_point
+// Purpose: Adds a point to this list to be drawn.
+//----------------------------------------------------------------------------
+void
+BMPGraphing_add_point (BMPGraph *graph, Value x, Value y)
+{
+ if (!graph || !graph->image)
+ return;
+
+ if (graph->data_index >= MAX_GRAPH_DATA-4)
+ return; // error ("Too many graph data.");
+
+ graph->data [graph->data_index++] = DATUM_X;
+ graph->data [graph->data_index++] = x;
+ graph->data [graph->data_index++] = DATUM_Y;
+ graph->data [graph->data_index++] = y;
+}
+
+//----------------------------------------------------------------------------
+// Name: BMPGraphing_plot_log2
+// Purpose: Plots a point on the current graph.
+//----------------------------------------------------------------------------
+
+void
+BMPGraphing_plot_log2 (BMPGraph *graph, Value x, Value y)
+{
+ if (!graph || !graph->image)
+ return;
+
+ int i = 0;
+
+ //----------------------------------------
+ // Plot the point. The x axis is
+ // logarithmic, base 2.
+ //
+ double tmp = log2 (x);
+ tmp -= (double) graph->min_x;
+ tmp *= (double) graph->x_span;
+ tmp /= (double) (graph->max_x - graph->min_x);
+
+ int x2 = graph->left_margin + (int) tmp;
+ int y2 = graph->height - graph->margin - (y * graph->y_span) / graph->max_y;
+
+ if (graph->last_x != -1 && graph->last_y != -1) {
+ if (graph->fg & DASHED)
+ BMP_line_dashed (graph->image, graph->last_x, graph->last_y, x2, y2, graph->fg & 0xffffff);
+ else
+ BMP_line (graph->image, graph->last_x, graph->last_y, x2, y2, graph->fg);
+ }
+
+ graph->last_x = x2;
+ graph->last_y = y2;
+}
+
+//----------------------------------------------------------------------------
+// Name: BMPGraphing_plot_linear
+// Purpose: Plots a point on the current graph.
+//----------------------------------------------------------------------------
+
+void
+BMPGraphing_plot_linear (BMPGraph *graph, Value x, Value y, Value max_y)
+{
+ if (!graph || !graph->image)
+ return;
+
+ //----------------------------------------
+ // Plot the point. The x axis is
+ // logarithmic, base 2. The units of the
+ // y value is kB.
+ //
+ double tmp = 10. + log2 (x);
+ tmp -= (double) XVALUE_MIN;
+ tmp *= (double) graph->x_span;
+ tmp /= (double) (XVALUE_MAX - XVALUE_MIN);
+ int x2 = graph->left_margin + (int) tmp;
+ int y2 = graph->height - graph->margin - (y * graph->y_span) / max_y;
+
+//printf ("\tx=%d, y=%d\n",x,y); fflush(stdout);
+
+ if (graph->last_x != -1 && graph->last_y != -1) {
+ if (graph->fg & DASHED)
+ BMP_line_dashed (graph->image, graph->last_x, graph->last_y, x2, y2, graph->fg & 0xffffff);
+ else
+ BMP_line (graph->image, graph->last_x, graph->last_y, x2, y2, graph->fg);
+ }
+
+ graph->last_x = x2;
+ graph->last_y = y2;
+}
+
+//----------------------------------------------------------------------------
+// Name: BMPGraphing_make_log2
+// Purpose: Plots all lines.
+//----------------------------------------------------------------------------
+
+static void
+BMPGraphing_make_log2 (BMPGraph *graph)
+{
+ if (!graph || !graph->image)
+ return;
+
+ BMPGraphing_draw_labels_log2 (graph);
+
+ //----------------------------------------
+ // OK, now draw the lines.
+ //
+ int i;
+ int x = -1, y = -1;
+ for (i = 0; i < graph->data_index; i += 2)
+ {
+ Value type = graph->data[i];
+ Value value = graph->data[i+1];
+
+ switch (type) {
+ case DATUM_Y: y = value; break;
+ case DATUM_X: x = value; break;
+ case DATUM_COLOR:
+ graph->fg = (unsigned long) value;
+ graph->last_x = -1;
+ graph->last_y = -1;
+ break;
+ }
+
+ if (x != -1 && y != -1) {
+ BMPGraphing_plot_log2 (graph, x, y);
+ x = y = -1;
+ }
+ }
+}
+
+//----------------------------------------------------------------------------
+// Name: BMPGraphing_make_linear
+// Purpose: Plots all lines for the network test graph.
+//----------------------------------------------------------------------------
+
+static void
+BMPGraphing_make_linear (BMPGraph *graph)
+{
+ if (!graph || !graph->image)
+ return;
+
+ int i;
+
+ // No data
+ if (!graph->data_index)
+ return;
+
+ //----------------------------------------
+ // Get the maximum bandwidth in order to
+ // properly scale the graph vertically.
+ //
+ int max_y = 0;
+ for (i = 0; i < graph->data_index; i += 2) {
+ if (graph->data[i] == DATUM_Y) {
+ int y = graph->data [i+1];
+ if (y > max_y)
+ max_y = y;
+ }
+ }
+
+ int range = max_y > 10000 ? 2 : (max_y > 1000 ? 1 : 0);
+ int y_spacing = 1;
+ switch (range) {
+ case 2:
+ // Round up to the next 100.00 MB/sec. (=10000).
+ y_spacing = 10000;
+ break;
+ case 1:
+ // Round up to the next 10.00 MB/sec.
+ y_spacing = 1000;
+ break;
+ case 0:
+ // Round up to the next 1.00 MB/sec.
+ y_spacing = 100;
+ break;
+ }
+ max_y /= y_spacing;
+ max_y *= y_spacing;
+ max_y += y_spacing;
+
+ //----------------------------------------
+ // Draw the axes, ticks & labels.
+ //
+ // X axis:
+ if (XVALUE_MIN < 10)
+ return; // error ("Minimum y is too small.");
+
+ for (i = XVALUE_MIN; i <= XVALUE_MAX; i++) {
+ char str[200];
+ unsigned long y2 = 1 << (i-10); // XX XVALUE_MIN>=10
+ if (y2 < 1024)
+ snprintf (str, 199, "%u kB", (unsigned int) y2);
+ else
+ snprintf (str, 199, "%lu MB", (unsigned long) (y2 >> 10));
+
+ int x = graph->left_margin + ((i - XVALUE_MIN) * graph->x_span) / (XVALUE_MAX - XVALUE_MIN);
+ int y = graph->height - graph->margin + 10;
+
+ BMP_vline (graph->image, x, y, y-10, RGB_BLACK);
+ BMP_draw_mini_string (graph->image, str, x - 10, y+8, RGB_BLACK);
+ }
+
+ //----------
+ // Y axis:
+ // Decide what the tick spacing will be.
+ for (i = 0; i <= max_y; i += y_spacing) {
+ char str[200];
+ unsigned long whole = i / 100;
+ unsigned long frac = i % 100;
+ snprintf (str, 199, "%lu.%02lu MB/s", whole, frac);
+
+ int x = graph->left_margin - 10;
+ int y = graph->height - graph->margin - (i * graph->y_span) / max_y;
+
+ BMP_hline (graph->image, x, x+10, y, RGB_BLACK);
+ BMP_draw_mini_string (graph->image, str, x - 60, y - MINIFONT_HEIGHT/2, RGB_BLACK);
+ }
+
+ //----------------------------------------
+ // Draw the data lines.
+ //
+ int x = -1, y = -1;
+ graph->last_x = -1;
+ graph->last_y = -1;
+ for (i = 0; i < graph->data_index; i += 2)
+ {
+ int type = graph->data[i];
+ long value = graph->data[i+1];
+
+ switch (type) {
+ case DATUM_Y: y = value; break;
+ case DATUM_X: x = value; break;
+ case DATUM_COLOR:
+ graph->fg = (unsigned long) value;
+ graph->last_x = -1;
+ graph->last_y = -1;
+ break;
+ }
+
+ if (x != -1 && y != -1) {
+ BMPGraphing_plot_linear (graph, x, y, max_y);
+ x = y = -1;
+ }
+ }
+}
+
+void
+BMPGraphing_make (BMPGraph *graph)
+{
+ if (!graph)
+ return; // XX silent error
+
+ switch (graph->x_axis_mode) {
+ case MODE_X_AXIS_LOG2:
+ BMPGraphing_make_log2 (graph);
+ break;
+ case MODE_X_AXIS_LINEAR:
+ BMPGraphing_make_linear (graph);
+ break;
+ default:
+ fprintf (stderr, "Invalid graph mode %d.\n", graph->x_axis_mode);
+ break;
+ }
+}
+
+void
+BMPGraphing_destroy (BMPGraph *graph)
+{
+ if (!graph)
+ return;
+
+ if (graph->title) {
+ free (graph->title);
+ graph->title = NULL;
+ }
+ if (graph->image) {
+ BMP_destroy (graph->image);
+ graph->image = NULL;
+ }
+
+ free (graph);
+}
OpenPOWER on IntegriCloud