2
/****************************************************************************
5
* AUTHOR(S): Chris Rewerts, Agricultural Engineering, Purdue University (original contributor)
6
* Markus Neteler <neteler itc.it>
7
* Roberto Flor <flor itc.it>, Bernhard Reiter <bernhard intevation.de>,
8
* Huidae Cho <grass4u gmail.com>, Glynn Clements <glynn gclements.plus.com>,
9
* Hamish Bowman <hamish_b yahoo.com>
11
* COPYRIGHT: (C) 1999-2007 by the GRASS Development Team
13
* This program is free software under the GNU General Public
14
* License (>=v2). Read the file COPYING that comes with GRASS
17
*****************************************************************************/
19
rewerts@ecn.purdue.edu
20
Agricultural Engineering, Purdue University
24
This program is based on Raghaven Srinivasan's modification
25
of the programs written by Dave Johnson for d.histogram.
27
Will read files containing a column of numbers and create line
28
graphs. One file can be used for the X axis, up to 10 for the
29
Y axis. Each numerical x,y file should be a single column of
35
#include <grass/gis.h>
36
#include <grass/raster.h>
37
#include <grass/display.h>
38
#include <grass/colors.h>
39
#include <grass/glocale.h>
40
#include "linegraph.h"
42
#define MAX(x,y) ((x) > (y) ? (x) : (y))
43
#define MIN(x,y) ((x) < (y) ? (x) : (y))
45
/* the default order of precedence of colors to use for Y lines */
46
int default_y_colors[] = {
48
RED, GREEN, VIOLET, BLUE, ORANGE,
49
GRAY, BROWN, MAGENTA, WHITE, INDIGO
52
int main(int argc, char **argv)
54
int xoffset; /* offset for x-axis */
55
int yoffset; /* offset for y-axis */
68
int prev_x, prev_y[11];
77
int num_pnts; /* number of lines in file */
78
int color; /* color to use for y lines */
79
float max; /* maximum value in file */
80
float min; /* minimum value in file */
81
float value; /* current value read in */
82
char name[1024]; /* name of file */
83
char full_name[1024]; /* path/name of file */
84
FILE *fp; /* pointer to file */
87
struct in_file in[12];
88
struct GModule *module;
96
char txt[1024], xlabel[512];
103
struct Option *dir_opt, *x_opt, *y_opt;
104
struct Option *y_color_opt;
105
struct Option *title[3];
106
struct Option *t_color_opt;
108
/* Initialize the GIS calls */
111
/* Set description */
112
module = G_define_module();
113
module->keywords = _("display, cartography");
114
module->description =
115
_("Generates and displays simple line graphs in the active graphics monitor display frame.");
117
x_opt = G_define_option();
118
x_opt->key = "x_file";
119
x_opt->description = _("Name of data file for X axis of graph");
120
x_opt->type = TYPE_STRING;
121
x_opt->required = YES;
123
y_opt = G_define_option();
124
y_opt->key = "y_file";
125
y_opt->description = _("Name of data file(s) for Y axis of graph");
126
y_opt->type = TYPE_STRING;
127
y_opt->required = YES;
128
y_opt->multiple = YES;
130
dir_opt = G_define_option();
131
dir_opt->key = "directory";
132
dir_opt->description = _("Path to file location");
133
dir_opt->type = TYPE_STRING;
134
dir_opt->required = NO;
135
dir_opt->answer = ".";
137
y_color_opt = G_define_option();
138
y_color_opt->key = "y_color";
139
y_color_opt->description = _("Color for Y data");
140
y_color_opt->type = TYPE_STRING;
141
y_color_opt->required = NO;
142
y_color_opt->multiple = YES;
143
y_color_opt->answers = NULL;
144
y_color_opt->options = D_COLOR_LIST;
146
t_color_opt = G_define_option();
147
t_color_opt->key = "title_color";
148
t_color_opt->description = _("Color for axis, tics, numbers, and title");
149
t_color_opt->type = TYPE_STRING;
150
t_color_opt->required = NO;
151
t_color_opt->answer = DEFAULT_FG_COLOR;
152
t_color_opt->options = D_COLOR_LIST;
154
title[0] = G_define_option();
155
title[0]->key = "x_title";
156
title[0]->description = _("Title for X data");
157
title[0]->type = TYPE_STRING;
158
title[0]->required = NO;
159
title[0]->answer = "";
161
title[1] = G_define_option();
162
title[1]->key = "y_title";
163
title[1]->description = _("Title for Y data");
164
title[1]->type = TYPE_STRING;
165
title[1]->required = NO;
166
title[1]->answer = "";
168
title[2] = G_define_option();
169
title[2]->key = "title";
170
title[2]->description = _("Title for Graph");
171
title[2]->type = TYPE_STRING;
172
title[2]->required = NO;
173
title[2]->answer = "";
176
if (G_parser(argc, argv))
179
for (i = 0; i < 3; i++) {
180
for (j = 0; j < strlen(title[i]->answer); j++)
181
if (title[i]->answer[j] == '_')
182
title[i]->answer[j] = ' ';
185
/* build path to X data file and open for reading
186
notice that in[0] will be the X file, and in[1-10]
187
will be the Y file(s) */
189
sprintf(in[0].full_name, "%s/%s", dir_opt->answer, x_opt->answer);
190
sprintf(in[0].name, "%s", x_opt->answer);
192
if ((in[0].fp = fopen(in[0].full_name, "r")) == NULL)
193
G_fatal_error(_("Unable to open input file <%s>"), in[0].full_name);
197
/* open all Y data files */
199
for (i = 0, j = 1; (name = y_opt->answers[i]); i++, j++) {
200
sprintf(in[j].full_name, "%s/%s", dir_opt->answer, name);
201
sprintf(in[j].name, "%s", name);
203
if ((in[j].fp = fopen(in[j].full_name, "r")) == NULL)
204
G_fatal_error(_("Unable to open input file <%s>"),
208
if (num_y_files > 10)
209
G_fatal_error(_("Maximum of 10 Y data files exceeded"));
214
title_color = D_translate_color(t_color_opt->answer);
216
/* I had an argument with the parser, and couldn't get a neat list of
217
the input colors as I thought I should. I did a quick hack to get
218
my list from the answer var, which gives us the colors input
219
separated by commas. at least we know that they have been checked against
220
the list of possibles */
223
if (y_color_opt->answer != NULL) {
224
for (i = 0; i <= (strlen(y_color_opt->answer)); i++) {
225
if ((y_color_opt->answer[i] == ',') ||
226
(i == (strlen(y_color_opt->answer)))) {
227
color_name[c] = '\0';
228
in[j].color = D_translate_color(color_name);
233
color_name[c++] = y_color_opt->answer[i];
236
/* this is lame. I could come up with a color or prompt for one or something */
238
G_fatal_error(_("Only <%d> colors given for <%d> lines"), j,
242
/* no colors given on command line, use default list */
244
for (i = 1; i <= num_y_files; i++) {
245
in[i].color = default_y_colors[i];
249
/* get coordinates of current screen window, in pixels */
250
if (R_open_driver() != 0)
251
G_fatal_error(_("No graphics device selected"));
252
D_get_screen_window(&t, &b, &l, &r);
253
R_set_window(t, b, l, r);
255
/* create axis lines, to be drawn later */
258
x_line[0] = x_line[1] = l + (int)(ORIGIN_X * width);
259
x_line[2] = l + (int)(XAXIS_END * width);
260
y_line[0] = b - (int)(YAXIS_END * height);
261
y_line[1] = y_line[2] = b - (int)(ORIGIN_Y * height);
262
text_height = (int)(b - t) * TEXT_HEIGHT;
263
text_width = (int)(r - l) * TEXT_WIDTH;
264
R_text_size(text_width, text_height);
266
/* read thru each data file in turn, find max and min values for
267
each, count lines, find x min and max, find overall y min and
273
for (i = 0; i <= num_y_files; i++) {
276
in[i].max = -99999.9;
280
while ((err = fscanf(in[i].fp, "%f", &in[i].value)) != EOF) {
282
in[i].max = MAX(in[i].max, in[i].value);
283
in[i].min = MIN(in[i].min, in[i].value);
284
if (i > 0) { /* if we have a y file */
285
min_y = MIN(min_y, in[i].value);
286
max_y = MAX(max_y, in[i].value);
289
if ((i > 0) && (in[0].num_pnts != in[i].num_pnts)) {
290
G_warning(_("Y input file <%s> contains %s data points than the X input file"),
292
((in[i].num_pnts < in[0].num_pnts) ? "fewer" : "more"));
294
if (in[i].num_pnts > in[0].num_pnts)
295
G_message(_("The last %d point(s) will be ignored"),
296
(in[i].num_pnts - in[0].num_pnts));
300
/* close all files */
302
for (i = 0; i <= num_y_files; i++)
305
/* figure scaling factors and offsets */
307
xscale = ((double)(x_line[2] - x_line[1]) / (double)(in[0].num_pnts));
308
yscale = ((double)(y_line[1] - y_line[0]) / (max_y - min_y));
309
yoffset = (double)(y_line[1]);
310
xoffset = (double)(x_line[1]);
312
/* figure tic_every and tic_units for the x-axis of the bar-chart.
313
tic_every tells how often to place a tic-number. tic_unit tells
314
the unit to use in expressing tic-numbers. */
316
if (xscale < XTIC_DIST) {
317
max_tics = (x_line[2] - x_line[1]) / XTIC_DIST;
319
while (((in[0].max - in[0].min) / tics[i].every) > max_tics)
321
tic_every = tics[i].every;
322
tic_unit = tics[i].unit;
323
strcpy(tic_name, tics[i].name);
328
strcpy(tic_name, "");
332
/* open all the data files again */
334
for (i = 0; i <= num_y_files; i++) {
335
if ((in[i].fp = fopen(in[i].full_name, "r")) == NULL) {
336
sprintf(txt, "Could not open input file <%s>.", in[i].full_name);
341
/* loop through number of lines in x data file,
342
then loop thru for each y file, drawing a piece of each line and a
343
legend bar on each iteration evenly divisible, a tic-mark
344
on those evenly divisible by tic_unit, and a tic_mark
345
number on those evenly divisible by tic_every */
347
/* read the info from the inputs */
349
for (line = 0; line < in[0].num_pnts; line++) {
350
/* scan in an X value */
351
err = fscanf(in[0].fp, "%f", &in[0].value);
353
/* didn't find a number or hit EOF before our time */
354
if ((err != 1) || (err == EOF)) {
355
sprintf(txt, _("Problem reading X data file at line %d"), line);
359
/* for each Y data file, get a value and compute where to draw it */
360
for (i = 1; i <= num_y_files; i++) {
361
/* check to see that we do indeed have data for this point */
362
if (line < in[i].num_pnts) {
363
err = fscanf(in[i].fp, "%f", &in[i].value);
364
if ((in[i].num_pnts >= line) && (err != 1)) {
366
_("Problem reading <%s> data file at line %d"),
371
/* in case the Y file has fewer lines than the X file, we will skip
372
trying to draw when we run out of data */
374
/* draw increment of each Y file's data */
376
R_standard_color(in[i].color);
378
/* find out position of where Y should be drawn. */
379
/* if our minimum value of y is not negative, this is easy */
383
(int)(yoffset - yscale * (in[i].value - min_y));
385
/* if our minimum value of y is negative, then we have two
386
cases: our current value to plot is pos or neg */
390
new_y[i] = (int)(yoffset - yscale * (-1 *
394
new_y[i] = (int)(yoffset - yscale * (in[i].value +
398
new_x = xoffset + (line * xscale);
403
R_move_abs(prev_x, prev_y[i]);
404
R_cont_abs(new_x, new_y[i]);
405
prev_y[i] = new_y[i];
410
/* draw x-axis tic-marks and numbers */
412
if (rem((long int)in[0].value, tic_every) == (float)0) {
414
/* draw a numbered tic-mark */
416
R_standard_color(title_color);
417
R_move_abs((int)(xoffset + line * xscale),
418
(int)(b - ORIGIN_Y * (b - t)));
419
R_cont_rel((int)0, (int)(BIG_TIC * (b - t)));
420
if ((in[0].value >= 1) || (in[0].value <= -1) ||
422
sprintf(txt, "%.0f", (in[0].value / tic_unit));
424
sprintf(txt, "%.2f", (in[0].value));
425
text_height = (b - t) * TEXT_HEIGHT;
426
text_width = (r - l) * TEXT_WIDTH;
427
R_text_size(text_width, text_height);
428
R_get_text_box(txt, &tt, &tb, &tl, &tr);
429
while ((tr - tl) > XTIC_DIST) {
432
R_text_size(text_width, text_height);
433
R_get_text_box(txt, &tt, &tb, &tl, &tr);
435
R_move_abs((int)(xoffset + (line * xscale - (tr - tl) / 2)),
436
(int)(b - XNUMS_Y * (b - t)));
439
else if (rem(line, tic_unit) == (float)0) {
441
/* draw a tic-mark */
443
R_standard_color(title_color);
444
R_move_abs((int)(xoffset + line * xscale),
445
(int)(b - ORIGIN_Y * (b - t)));
446
R_cont_rel((int)0, (int)(SMALL_TIC * (b - t)));
450
/* close all input files */
451
for (i = 0; i <= num_y_files; i++) {
455
/* draw the x-axis label */
456
if ((strcmp(title[0]->answer, "") == 0) && (strcmp(tic_name, "") == 0))
459
sprintf(xlabel, "X: %s %s", title[0]->answer, tic_name);
460
text_height = (b - t) * TEXT_HEIGHT;
461
text_width = (r - l) * TEXT_WIDTH * 1.5;
462
R_text_size(text_width, text_height);
463
R_get_text_box(xlabel, &tt, &tb, &tl, &tr);
464
R_move_abs((int)(l + (r - l) / 2 - (tr - tl) / 2),
465
(int)(b - LABEL_1 * (b - t)));
466
R_standard_color(title_color);
469
/* DRAW Y-AXIS TIC-MARKS AND NUMBERS
470
first, figure tic_every and tic_units for the x-axis of the bar-chart.
471
tic_every tells how often to place a tic-number. tic_unit tells
472
the unit to use in expressing tic-numbers. */
474
if (yscale < YTIC_DIST) {
475
max_tics = (y_line[1] - y_line[0]) / YTIC_DIST;
477
while (((max_y - min_y) / tics[i].every) > max_tics)
479
tic_every = tics[i].every;
480
tic_unit = tics[i].unit;
481
strcpy(tic_name, tics[i].name);
486
strcpy(tic_name, "");
491
for (i = (int)min_y; i <= (int)max_y; i += tic_unit) {
492
if (rem(i, tic_every) == (float)0) {
493
/* draw a tic-mark */
495
R_move_abs((int)x_line[0], (int)(yoffset - yscale * (i - min_y)));
496
R_cont_rel((int)(-(r - l) * BIG_TIC), (int)0);
498
/* draw a tic-mark number */
500
sprintf(txt, "%d", (i / tic_unit));
501
text_height = (b - t) * TEXT_HEIGHT;
502
text_width = (r - l) * TEXT_WIDTH;
503
R_text_size(text_width, text_height);
504
R_get_text_box(txt, &tt, &tb, &tl, &tr);
505
while ((tt - tb) > YTIC_DIST) {
508
R_text_size(text_width, text_height);
509
R_get_text_box(txt, &tt, &tb, &tl, &tr);
511
R_move_abs((int)(l + (r - l) * YNUMS_X - (tr - tl) / 2),
513
(yscale * (i - min_y) + 0.5 * (tt - tb))));
516
else if (rem(i, tic_unit) == (float)0) {
517
/* draw a tic-mark */
519
R_move_abs((int)x_line[0], (int)(yoffset - yscale * (i - min_y)));
520
R_cont_rel((int)(-(r - l) * SMALL_TIC), (int)0);
524
/* draw the y-axis label */
525
if ((strcmp(title[1]->answer, "") == 0) && (strcmp(tic_name, "") == 0))
528
sprintf(xlabel, "Y: %s %s", title[1]->answer, tic_name);
529
text_height = (b - t) * TEXT_HEIGHT;
530
text_width = (r - l) * TEXT_WIDTH * 1.5;
531
R_text_size(text_width, text_height);
532
R_get_text_box(xlabel, &tt, &tb, &tl, &tr);
533
R_move_abs((int)(l + (r - l) / 2 - (tr - tl) / 2),
534
(int)(b - LABEL_2 * (b - t)));
535
R_standard_color(title_color);
539
sprintf(xlabel, "%s", title[2]->answer);
540
text_height = (b - t) * TEXT_HEIGHT;
541
text_width = (r - l) * TEXT_WIDTH * 2.0;
542
R_text_size(text_width, text_height);
543
R_get_text_box(xlabel, &tt, &tb, &tl, &tr);
545
R_move_abs((int)(((r-l)/2)-(tr-tl)/2),
546
(int) (t+ (b-t)*.07) );
548
R_move_abs((int)(l + (r - l) / 2 - (tr - tl) / 2),
549
(int)(t + (b - t) * .07));
550
R_standard_color(title_color);
553
/* draw x and y axis lines */
554
R_standard_color(title_color);
555
R_polyline_abs(x_line, y_line, 3);
562
float rem(long int x, long int y)
566
return ((float)(x - y * d));
570
/* a function for making an exit after the R_driver is open */
571
int death(char *gasp)
575
G_fatal_error("%s", gasp);