123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588 |
- /****************************************************************************
- *
- * MODULE: d.linegraph
- * AUTHOR(S): Chris Rewerts, Agricultural Engineering, Purdue University (original contributor)
- * Markus Neteler <neteler itc.it>
- * Roberto Flor <flor itc.it>, Bernhard Reiter <bernhard intevation.de>,
- * Huidae Cho <grass4u gmail.com>, Glynn Clements <glynn gclements.plus.com>,
- * Hamish Bowman <hamish_b yahoo.com>
- * PURPOSE:
- * COPYRIGHT: (C) 1999-2007 by the GRASS Development Team
- *
- * This program is free software under the GNU General Public
- * License (>=v2). Read the file COPYING that comes with GRASS
- * for details.
- *
- *****************************************************************************/
- /* Chris Rewerts
- rewerts@ecn.purdue.edu
- Agricultural Engineering, Purdue University
- February 1992
- program: d.linegraph
- This program is based on Raghaven Srinivasan's modification
- of the programs written by Dave Johnson for d.histogram.
- Will read files containing a column of numbers and create line
- graphs. One file can be used for the X axis, up to 10 for the
- Y axis. Each numerical x,y file should be a single column of
- numbers.
- */
- #include <stdlib.h>
- #include <math.h>
- #include <grass/gis.h>
- #include <grass/display.h>
- #include <grass/colors.h>
- #include <grass/glocale.h>
- #include "linegraph.h"
- #define MAX(x,y) ((x) > (y) ? (x) : (y))
- #define MIN(x,y) ((x) < (y) ? (x) : (y))
- /* the default order of precedence of colors to use for Y lines */
- int default_y_colors[] = {
- 0,
- RED, GREEN, VIOLET, BLUE, ORANGE,
- GRAY, BROWN, MAGENTA, WHITE, INDIGO
- };
- static double rem(long int x, long int y)
- {
- long int d = x / y;
- return ((double)(x - y * d));
- }
- int main(int argc, char **argv)
- {
- double xoffset; /* offset for x-axis */
- double yoffset; /* offset for y-axis */
- double text_height;
- double text_width;
- int i;
- int j;
- int c;
- int tic_every;
- int max_tics;
- int title_color;
- int num_y_files;
- int tic_unit;
- double t, b, l, r;
- double tt, tb, tl, tr;
- double prev_x, prev_y[11];
- double new_x, new_y[11];
- int line;
- double x_line[3];
- double y_line[3];
- int err;
- struct in_file
- {
- int num_pnts; /* number of lines in file */
- int color; /* color to use for y lines */
- float max; /* maximum value in file */
- float min; /* minimum value in file */
- float value; /* current value read in */
- char name[1024]; /* name of file */
- char full_name[1024]; /* path/name of file */
- FILE *fp; /* pointer to file */
- };
- struct in_file in[12];
- struct GModule *module;
- float max_y;
- float min_y;
- float height, width;
- float xscale;
- float yscale;
- char txt[1024], xlabel[512];
- char tic_name[1024];
- char *name;
- char color_name[20];
- FILE *fopen();
- struct Option *dir_opt, *x_opt, *y_opt;
- struct Option *y_color_opt;
- struct Option *title[3];
- struct Option *t_color_opt;
- /* Initialize the GIS calls */
- G_gisinit(argv[0]);
- /* Set description */
- module = G_define_module();
- G_add_keyword(_("display"));
- G_add_keyword(_("cartography"));
- module->description =
- _("Generates and displays simple line graphs in the active graphics monitor display frame.");
- x_opt = G_define_option();
- x_opt->key = "x_file";
- x_opt->description = _("Name of data file for X axis of graph");
- x_opt->type = TYPE_STRING;
- x_opt->required = YES;
- y_opt = G_define_option();
- y_opt->key = "y_file";
- y_opt->description = _("Name of data file(s) for Y axis of graph");
- y_opt->type = TYPE_STRING;
- y_opt->required = YES;
- y_opt->multiple = YES;
- dir_opt = G_define_option();
- dir_opt->key = "directory";
- dir_opt->description = _("Path to file location");
- dir_opt->type = TYPE_STRING;
- dir_opt->required = NO;
- /* Remove answer because create problem with full path */
- /* dir_opt->answer = "."; */
- y_color_opt = G_define_option();
- y_color_opt->key = "y_color";
- y_color_opt->description = _("Color for Y data");
- y_color_opt->type = TYPE_STRING;
- y_color_opt->required = NO;
- y_color_opt->multiple = YES;
- y_color_opt->gisprompt = "old_color,color,color";
- y_color_opt->answers = NULL;
- t_color_opt = G_define_option();
- t_color_opt->key = "title_color";
- t_color_opt->description = _("Color for axis, tics, numbers, and title");
- t_color_opt->type = TYPE_STRING;
- t_color_opt->required = NO;
- t_color_opt->gisprompt = "old_color,color,color";
- t_color_opt->answer = DEFAULT_FG_COLOR;
- title[0] = G_define_option();
- title[0]->key = "x_title";
- title[0]->description = _("Title for X data");
- title[0]->type = TYPE_STRING;
- title[0]->required = NO;
- title[0]->answer = "";
- title[1] = G_define_option();
- title[1]->key = "y_title";
- title[1]->description = _("Title for Y data");
- title[1]->type = TYPE_STRING;
- title[1]->required = NO;
- title[1]->answer = "";
- title[2] = G_define_option();
- title[2]->key = "title";
- title[2]->description = _("Title for Graph");
- title[2]->type = TYPE_STRING;
- title[2]->required = NO;
- title[2]->answer = "";
- if (G_parser(argc, argv))
- exit(EXIT_FAILURE);
- for (i = 0; i < 3; i++) {
- for (j = 0; j < strlen(title[i]->answer); j++)
- if (title[i]->answer[j] == '_')
- title[i]->answer[j] = ' ';
- }
- /* build path to X data file and open for reading
- notice that in[0] will be the X file, and in[1-10]
- will be the Y file(s) */
- if (dir_opt->answer != NULL) {
- sprintf(in[0].full_name, "%s/%s", dir_opt->answer, x_opt->answer);
- } else {
- sprintf(in[0].full_name, "%s", x_opt->answer);
- }
- sprintf(in[0].name, "%s", x_opt->answer);
- if ((in[0].fp = fopen(in[0].full_name, "r")) == NULL)
- G_fatal_error(_("Unable to open input file <%s>"), in[0].full_name);
- num_y_files = 0;
- /* open all Y data files */
- for (i = 0, j = 1; (name = y_opt->answers[i]); i++, j++) {
-
- if (dir_opt->answer != NULL) {
- sprintf(in[j].full_name, "%s/%s", dir_opt->answer, name);
- } else {
- sprintf(in[j].full_name, "%s", name);
- }
- sprintf(in[j].name, "%s", name);
- if ((in[j].fp = fopen(in[j].full_name, "r")) == NULL)
- G_fatal_error(_("Unable to open input file <%s>"),
- in[j].full_name);
- num_y_files++;
- if (num_y_files > 10)
- G_fatal_error(_("Maximum of 10 Y data files exceeded"));
- }
- /* set colors */
- title_color = D_translate_color(t_color_opt->answer);
- /* I had an argument with the parser, and couldn't get a neat list of
- the input colors as I thought I should. I did a quick hack to get
- my list from the answer var, which gives us the colors input
- separated by commas. at least we know that they have been checked against
- the list of possibles */
- c = 0;
- j = 1;
- if (y_color_opt->answer != NULL) {
- for (i = 0; i <= (strlen(y_color_opt->answer)); i++) {
- if ((y_color_opt->answer[i] == ',') ||
- (i == (strlen(y_color_opt->answer)))) {
- color_name[c] = '\0';
- in[j].color = D_translate_color(color_name);
- j++;
- c = 0;
- }
- else {
- color_name[c++] = y_color_opt->answer[i];
- }
- }
- /* this is lame. I could come up with a color or prompt for one or something */
- if (j < num_y_files)
- G_fatal_error(_("Only <%d> colors given for <%d> lines"), j,
- num_y_files);
- }
- else
- /* no colors given on command line, use default list */
- {
- for (i = 1; i <= num_y_files; i++) {
- in[i].color = default_y_colors[i];
- }
- }
- /* get coordinates of current screen window, in pixels */
- if (D_open_driver() != 0)
- G_fatal_error(_("No graphics device selected. "
- "Use d.mon to select graphics device."));
-
- D_setup_unity(0);
- D_get_src(&t, &b, &l, &r);
- /* create axis lines, to be drawn later */
- height = b - t;
- width = r - l;
- x_line[0] = x_line[1] = l + (ORIGIN_X * width);
- x_line[2] = l + (XAXIS_END * width);
- y_line[0] = b - (YAXIS_END * height);
- y_line[1] = y_line[2] = b - (ORIGIN_Y * height);
- text_height = (b - t) * TEXT_HEIGHT;
- text_width = (r - l) * TEXT_WIDTH;
- D_text_size(text_width, text_height);
- /* read thru each data file in turn, find max and min values for
- each, count lines, find x min and max, find overall y min and
- max */
- max_y = -99999.9;
- min_y = 99999.9;
- for (i = 0; i <= num_y_files; i++) {
- in[i].min = 99999.9;
- in[i].max = -99999.9;
- in[i].value = 0.0;
- in[i].num_pnts = 0;
- while ((err = fscanf(in[i].fp, "%f", &in[i].value)) != EOF) {
- in[i].num_pnts++;
- in[i].max = MAX(in[i].max, in[i].value);
- in[i].min = MIN(in[i].min, in[i].value);
- if (i > 0) { /* if we have a y file */
- min_y = MIN(min_y, in[i].value);
- max_y = MAX(max_y, in[i].value);
- }
- }
- if ((i > 0) && (in[0].num_pnts != in[i].num_pnts)) {
- G_warning(_("Y input file <%s> contains %s data points than the X input file"),
- in[i].name,
- ((in[i].num_pnts < in[0].num_pnts) ? "fewer" : "more"));
- if (in[i].num_pnts > in[0].num_pnts)
- G_message(_("The last %d point(s) will be ignored"),
- (in[i].num_pnts - in[0].num_pnts));
- }
- }
- /* close all files */
- for (i = 0; i <= num_y_files; i++)
- fclose(in[i].fp);
- /* figure scaling factors and offsets */
- xscale = ((double)(x_line[2] - x_line[1]) / (double)(in[0].num_pnts));
- yscale = ((double)(y_line[1] - y_line[0]) / (max_y - min_y));
- yoffset = (double)(y_line[1]);
- xoffset = (double)(x_line[1]);
- /* figure tic_every and tic_units for the x-axis of the bar-chart.
- tic_every tells how often to place a tic-number. tic_unit tells
- the unit to use in expressing tic-numbers. */
- if (xscale < XTIC_DIST) {
- max_tics = (x_line[2] - x_line[1]) / XTIC_DIST;
- i = 1;
- while (((in[0].max - in[0].min) / tics[i].every) > max_tics)
- i++;
- tic_every = tics[i].every;
- tic_unit = tics[i].unit;
- strcpy(tic_name, tics[i].name);
- }
- else {
- tic_every = 1;
- tic_unit = 1;
- strcpy(tic_name, "");
- }
- /* open all the data files again */
- for (i = 0; i <= num_y_files; i++) {
- if ((in[i].fp = fopen(in[i].full_name, "r")) == NULL) {
- D_close_driver();
- G_fatal_error(_("Unable to open input file <%s>"), in[i].full_name);
- }
- }
- /* loop through number of lines in x data file,
- then loop thru for each y file, drawing a piece of each line and a
- legend bar on each iteration evenly divisible, a tic-mark
- on those evenly divisible by tic_unit, and a tic_mark
- number on those evenly divisible by tic_every */
- /* read the info from the inputs */
- for (line = 0; line < in[0].num_pnts; line++) {
- /* scan in an X value */
- err = fscanf(in[0].fp, "%f", &in[0].value);
- /* didn't find a number or hit EOF before our time */
- if ((err != 1) || (err == EOF)) {
- D_close_driver();
- G_fatal_error(_("Problem reading X data file at line %d"), line);
- }
- /* for each Y data file, get a value and compute where to draw it */
- for (i = 1; i <= num_y_files; i++) {
- /* check to see that we do indeed have data for this point */
- if (line < in[i].num_pnts) {
- err = fscanf(in[i].fp, "%f", &in[i].value);
- if ((in[i].num_pnts >= line) && (err != 1)) {
- D_close_driver();
- G_fatal_error(_("Problem reading <%s> data file at line %d"),
- in[i].name, line);
- }
- /* in case the Y file has fewer lines than the X file, we will skip
- trying to draw when we run out of data */
- /* draw increment of each Y file's data */
- D_use_color(in[i].color);
- /* find out position of where Y should be drawn. */
- /* if our minimum value of y is not negative, this is easy */
- if (min_y >= 0)
- new_y[i] =
- (yoffset - yscale * (in[i].value - min_y));
- /* if our minimum value of y is negative, then we have two
- cases: our current value to plot is pos or neg */
- else {
- if (in[i].value < 0)
- new_y[i] = (yoffset - yscale * (-1 *
- (min_y -
- in[i].value)));
- else
- new_y[i] = (yoffset - yscale * (in[i].value +
- (min_y * -1)));
- }
- new_x = xoffset + (line * xscale);
- if (line == 0) {
- prev_x = xoffset;
- prev_y[i] = yoffset;
- }
- D_line_abs(prev_x, prev_y[i], new_x, new_y[i]);
- prev_y[i] = new_y[i];
- }
- }
- prev_x = new_x;
- /* draw x-axis tic-marks and numbers */
- if (rem((long int)in[0].value, tic_every) == 0.0) {
- /* draw a numbered tic-mark */
- D_use_color(title_color);
- D_begin();
- D_move_abs(xoffset + line * xscale, b - ORIGIN_Y * (b - t));
- D_cont_rel(0, BIG_TIC * (b - t));
- D_end();
- D_stroke();
- if ((in[0].value >= 1) || (in[0].value <= -1) ||
- (in[0].value == 0))
- sprintf(txt, "%.0f", (in[0].value / tic_unit));
- else
- sprintf(txt, "%.2f", (in[0].value));
- text_height = (b - t) * TEXT_HEIGHT;
- text_width = (r - l) * TEXT_WIDTH;
- D_text_size(text_width, text_height);
- D_get_text_box(txt, &tt, &tb, &tl, &tr);
- while ((tr - tl) > XTIC_DIST) {
- text_width *= 0.75;
- text_height *= 0.75;
- D_text_size(text_width, text_height);
- D_get_text_box(txt, &tt, &tb, &tl, &tr);
- }
- D_pos_abs((xoffset + (line * xscale - (tr - tl) / 2)),
- (b - XNUMS_Y * (b - t)));
- D_text(txt);
- }
- else if (rem(line, tic_unit) == 0.0) {
- /* draw a tic-mark */
- D_use_color(title_color);
- D_begin();
- D_move_abs(xoffset + line * xscale,
- b - ORIGIN_Y * (b - t));
- D_cont_rel(0, SMALL_TIC * (b - t));
- D_end();
- D_stroke();
- }
- }
- /* close all input files */
- for (i = 0; i <= num_y_files; i++) {
- fclose(in[i].fp);
- }
- /* draw the x-axis label */
- if ((strcmp(title[0]->answer, "") == 0) && (strcmp(tic_name, "") == 0))
- *xlabel = '\0';
- else
- sprintf(xlabel, "X: %s %s", title[0]->answer, tic_name);
- text_height = (b - t) * TEXT_HEIGHT;
- text_width = (r - l) * TEXT_WIDTH * 1.5;
- D_text_size(text_width, text_height);
- D_get_text_box(xlabel, &tt, &tb, &tl, &tr);
- D_pos_abs((l + (r - l) / 2 - (tr - tl) / 2),
- (b - LABEL_1 * (b - t)));
- D_use_color(title_color);
- D_text(xlabel);
- /* DRAW Y-AXIS TIC-MARKS AND NUMBERS
- first, figure tic_every and tic_units for the x-axis of the bar-chart.
- tic_every tells how often to place a tic-number. tic_unit tells
- the unit to use in expressing tic-numbers. */
- if (yscale < YTIC_DIST) {
- max_tics = (y_line[1] - y_line[0]) / YTIC_DIST;
- i = 1;
- while (((max_y - min_y) / tics[i].every) > max_tics)
- i++;
- tic_every = tics[i].every;
- tic_unit = tics[i].unit;
- strcpy(tic_name, tics[i].name);
- }
- else {
- tic_every = 1;
- tic_unit = 1;
- strcpy(tic_name, "");
- }
- /* Y-AXIS LOOP */
- for (i = (int)min_y; i <= (int)max_y; i += tic_unit) {
- if (rem(i, tic_every) == 0.0) {
- /* draw a tic-mark */
- D_begin();
- D_move_abs(x_line[0], yoffset - yscale * (i - min_y));
- D_cont_rel(-(r - l) * BIG_TIC, 0);
- D_end();
- D_stroke();
- /* draw a tic-mark number */
- sprintf(txt, "%d", (i / tic_unit));
- text_height = (b - t) * TEXT_HEIGHT;
- text_width = (r - l) * TEXT_WIDTH;
- D_text_size(text_width, text_height);
- D_get_text_box(txt, &tt, &tb, &tl, &tr);
- while ((tt - tb) > YTIC_DIST) {
- text_width *= 0.75;
- text_height *= 0.75;
- D_text_size(text_width, text_height);
- D_get_text_box(txt, &tt, &tb, &tl, &tr);
- }
- D_pos_abs(l + (r - l) * YNUMS_X - (tr - tl) / 2,
- yoffset - (yscale * (i - min_y) + 0.5 * (tt - tb)));
- D_text(txt);
- }
- else if (rem(i, tic_unit) == 0.0) {
- /* draw a tic-mark */
- D_begin();
- D_move_abs(x_line[0], (yoffset - yscale * (i - min_y)));
- D_cont_rel(-(r - l) * SMALL_TIC, 0);
- D_end();
- D_stroke();
- }
- }
- /* draw the y-axis label */
- if ((strcmp(title[1]->answer, "") == 0) && (strcmp(tic_name, "") == 0))
- *xlabel = '\0';
- else
- sprintf(xlabel, "Y: %s %s", title[1]->answer, tic_name);
- text_height = (b - t) * TEXT_HEIGHT;
- text_width = (r - l) * TEXT_WIDTH * 1.5;
- D_text_size(text_width, text_height);
- D_get_text_box(xlabel, &tt, &tb, &tl, &tr);
- D_pos_abs(l + (r - l) / 2 - (tr - tl) / 2, b - LABEL_2 * (b - t));
- D_use_color(title_color);
- D_text(xlabel);
- /* top label */
- sprintf(xlabel, "%s", title[2]->answer);
- text_height = (b - t) * TEXT_HEIGHT;
- text_width = (r - l) * TEXT_WIDTH * 2.0;
- D_text_size(text_width, text_height);
- D_get_text_box(xlabel, &tt, &tb, &tl, &tr);
- /*
- D_move_abs((int)(((r-l)/2)-(tr-tl)/2),
- (int) (t+ (b-t)*.07) );
- */
- D_pos_abs(l + (r - l) / 2 - (tr - tl) / 2, t + (b - t) * .07);
- D_use_color(title_color);
- D_text(xlabel);
- /* draw x and y axis lines */
- D_use_color(title_color);
- D_polyline_abs(x_line, y_line, 3);
- D_save_command(G_recreate_command());
- D_close_driver();
-
- exit(EXIT_SUCCESS);
- }
|