main.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. /****************************************************************************
  2. *
  3. * MODULE: d.linegraph
  4. * AUTHOR(S): Chris Rewerts, Agricultural Engineering, Purdue University (original contributor)
  5. * Markus Neteler <neteler itc.it>
  6. * Roberto Flor <flor itc.it>, Bernhard Reiter <bernhard intevation.de>,
  7. * Huidae Cho <grass4u gmail.com>, Glynn Clements <glynn gclements.plus.com>,
  8. * Hamish Bowman <hamish_b yahoo.com>
  9. * PURPOSE:
  10. * COPYRIGHT: (C) 1999-2007 by the GRASS Development Team
  11. *
  12. * This program is free software under the GNU General Public
  13. * License (>=v2). Read the file COPYING that comes with GRASS
  14. * for details.
  15. *
  16. *****************************************************************************/
  17. /* Chris Rewerts
  18. rewerts@ecn.purdue.edu
  19. Agricultural Engineering, Purdue University
  20. February 1992
  21. program: d.linegraph
  22. This program is based on Raghaven Srinivasan's modification
  23. of the programs written by Dave Johnson for d.histogram.
  24. Will read files containing a column of numbers and create line
  25. graphs. One file can be used for the X axis, up to 10 for the
  26. Y axis. Each numerical x,y file should be a single column of
  27. numbers.
  28. */
  29. #include <stdlib.h>
  30. #include <math.h>
  31. #include <grass/gis.h>
  32. #include <grass/display.h>
  33. #include <grass/colors.h>
  34. #include <grass/glocale.h>
  35. #include "linegraph.h"
  36. #define MAX(x,y) ((x) > (y) ? (x) : (y))
  37. #define MIN(x,y) ((x) < (y) ? (x) : (y))
  38. /* the default order of precedence of colors to use for Y lines */
  39. int default_y_colors[] = {
  40. 0,
  41. RED, GREEN, VIOLET, BLUE, ORANGE,
  42. GRAY, BROWN, MAGENTA, WHITE, INDIGO
  43. };
  44. static double rem(long int x, long int y)
  45. {
  46. long int d = x / y;
  47. return ((double)(x - y * d));
  48. }
  49. int main(int argc, char **argv)
  50. {
  51. double xoffset; /* offset for x-axis */
  52. double yoffset; /* offset for y-axis */
  53. double text_height;
  54. double text_width;
  55. int i;
  56. int j;
  57. int c;
  58. int tic_every;
  59. int max_tics;
  60. int title_color;
  61. int num_y_files;
  62. int tic_unit;
  63. double t, b, l, r;
  64. double tt, tb, tl, tr;
  65. double prev_x, prev_y[11];
  66. double new_x, new_y[11];
  67. int line;
  68. double x_line[3];
  69. double y_line[3];
  70. int err;
  71. struct in_file
  72. {
  73. int num_pnts; /* number of lines in file */
  74. int color; /* color to use for y lines */
  75. float max; /* maximum value in file */
  76. float min; /* minimum value in file */
  77. float value; /* current value read in */
  78. char name[1024]; /* name of file */
  79. char full_name[1024]; /* path/name of file */
  80. FILE *fp; /* pointer to file */
  81. };
  82. struct in_file in[12];
  83. struct GModule *module;
  84. float max_y;
  85. float min_y;
  86. float height, width;
  87. float xscale;
  88. float yscale;
  89. char txt[1024], xlabel[512];
  90. char tic_name[1024];
  91. char *name;
  92. char color_name[20];
  93. FILE *fopen();
  94. struct Option *dir_opt, *x_opt, *y_opt;
  95. struct Option *y_color_opt;
  96. struct Option *title[3];
  97. struct Option *t_color_opt;
  98. /* Initialize the GIS calls */
  99. G_gisinit(argv[0]);
  100. /* Set description */
  101. module = G_define_module();
  102. G_add_keyword(_("display"));
  103. G_add_keyword(_("cartography"));
  104. module->description =
  105. _("Generates and displays simple line graphs in the active graphics monitor display frame.");
  106. x_opt = G_define_option();
  107. x_opt->key = "x_file";
  108. x_opt->description = _("Name of data file for X axis of graph");
  109. x_opt->type = TYPE_STRING;
  110. x_opt->required = YES;
  111. y_opt = G_define_option();
  112. y_opt->key = "y_file";
  113. y_opt->description = _("Name of data file(s) for Y axis of graph");
  114. y_opt->type = TYPE_STRING;
  115. y_opt->required = YES;
  116. y_opt->multiple = YES;
  117. dir_opt = G_define_option();
  118. dir_opt->key = "directory";
  119. dir_opt->description = _("Path to file location");
  120. dir_opt->type = TYPE_STRING;
  121. dir_opt->required = NO;
  122. /* Remove answer because create problem with full path */
  123. /* dir_opt->answer = "."; */
  124. y_color_opt = G_define_option();
  125. y_color_opt->key = "y_color";
  126. y_color_opt->description = _("Color for Y data");
  127. y_color_opt->type = TYPE_STRING;
  128. y_color_opt->required = NO;
  129. y_color_opt->multiple = YES;
  130. y_color_opt->gisprompt = "old_color,color,color";
  131. y_color_opt->answers = NULL;
  132. t_color_opt = G_define_option();
  133. t_color_opt->key = "title_color";
  134. t_color_opt->description = _("Color for axis, tics, numbers, and title");
  135. t_color_opt->type = TYPE_STRING;
  136. t_color_opt->required = NO;
  137. t_color_opt->gisprompt = "old_color,color,color";
  138. t_color_opt->answer = DEFAULT_FG_COLOR;
  139. title[0] = G_define_option();
  140. title[0]->key = "x_title";
  141. title[0]->description = _("Title for X data");
  142. title[0]->type = TYPE_STRING;
  143. title[0]->required = NO;
  144. title[0]->answer = "";
  145. title[1] = G_define_option();
  146. title[1]->key = "y_title";
  147. title[1]->description = _("Title for Y data");
  148. title[1]->type = TYPE_STRING;
  149. title[1]->required = NO;
  150. title[1]->answer = "";
  151. title[2] = G_define_option();
  152. title[2]->key = "title";
  153. title[2]->description = _("Title for Graph");
  154. title[2]->type = TYPE_STRING;
  155. title[2]->required = NO;
  156. title[2]->answer = "";
  157. if (G_parser(argc, argv))
  158. exit(EXIT_FAILURE);
  159. for (i = 0; i < 3; i++) {
  160. for (j = 0; j < strlen(title[i]->answer); j++)
  161. if (title[i]->answer[j] == '_')
  162. title[i]->answer[j] = ' ';
  163. }
  164. /* build path to X data file and open for reading
  165. notice that in[0] will be the X file, and in[1-10]
  166. will be the Y file(s) */
  167. if (dir_opt->answer != NULL) {
  168. sprintf(in[0].full_name, "%s/%s", dir_opt->answer, x_opt->answer);
  169. } else {
  170. sprintf(in[0].full_name, "%s", x_opt->answer);
  171. }
  172. sprintf(in[0].name, "%s", x_opt->answer);
  173. if ((in[0].fp = fopen(in[0].full_name, "r")) == NULL)
  174. G_fatal_error(_("Unable to open input file <%s>"), in[0].full_name);
  175. num_y_files = 0;
  176. /* open all Y data files */
  177. for (i = 0, j = 1; (name = y_opt->answers[i]); i++, j++) {
  178. if (dir_opt->answer != NULL) {
  179. sprintf(in[j].full_name, "%s/%s", dir_opt->answer, name);
  180. } else {
  181. sprintf(in[j].full_name, "%s", name);
  182. }
  183. sprintf(in[j].name, "%s", name);
  184. if ((in[j].fp = fopen(in[j].full_name, "r")) == NULL)
  185. G_fatal_error(_("Unable to open input file <%s>"),
  186. in[j].full_name);
  187. num_y_files++;
  188. if (num_y_files > 10)
  189. G_fatal_error(_("Maximum of 10 Y data files exceeded"));
  190. }
  191. /* set colors */
  192. title_color = D_translate_color(t_color_opt->answer);
  193. /* I had an argument with the parser, and couldn't get a neat list of
  194. the input colors as I thought I should. I did a quick hack to get
  195. my list from the answer var, which gives us the colors input
  196. separated by commas. at least we know that they have been checked against
  197. the list of possibles */
  198. c = 0;
  199. j = 1;
  200. if (y_color_opt->answer != NULL) {
  201. for (i = 0; i <= (strlen(y_color_opt->answer)); i++) {
  202. if ((y_color_opt->answer[i] == ',') ||
  203. (i == (strlen(y_color_opt->answer)))) {
  204. color_name[c] = '\0';
  205. in[j].color = D_translate_color(color_name);
  206. j++;
  207. c = 0;
  208. }
  209. else {
  210. color_name[c++] = y_color_opt->answer[i];
  211. }
  212. }
  213. /* this is lame. I could come up with a color or prompt for one or something */
  214. if (j < num_y_files)
  215. G_fatal_error(_("Only <%d> colors given for <%d> lines"), j,
  216. num_y_files);
  217. }
  218. else
  219. /* no colors given on command line, use default list */
  220. {
  221. for (i = 1; i <= num_y_files; i++) {
  222. in[i].color = default_y_colors[i];
  223. }
  224. }
  225. /* get coordinates of current screen window, in pixels */
  226. if (D_open_driver() != 0)
  227. G_fatal_error(_("No graphics device selected. "
  228. "Use d.mon to select graphics device."));
  229. D_setup_unity(0);
  230. D_get_src(&t, &b, &l, &r);
  231. /* create axis lines, to be drawn later */
  232. height = b - t;
  233. width = r - l;
  234. x_line[0] = x_line[1] = l + (ORIGIN_X * width);
  235. x_line[2] = l + (XAXIS_END * width);
  236. y_line[0] = b - (YAXIS_END * height);
  237. y_line[1] = y_line[2] = b - (ORIGIN_Y * height);
  238. text_height = (b - t) * TEXT_HEIGHT;
  239. text_width = (r - l) * TEXT_WIDTH;
  240. D_text_size(text_width, text_height);
  241. /* read thru each data file in turn, find max and min values for
  242. each, count lines, find x min and max, find overall y min and
  243. max */
  244. max_y = -99999.9;
  245. min_y = 99999.9;
  246. for (i = 0; i <= num_y_files; i++) {
  247. in[i].min = 99999.9;
  248. in[i].max = -99999.9;
  249. in[i].value = 0.0;
  250. in[i].num_pnts = 0;
  251. while ((err = fscanf(in[i].fp, "%f", &in[i].value)) != EOF) {
  252. in[i].num_pnts++;
  253. in[i].max = MAX(in[i].max, in[i].value);
  254. in[i].min = MIN(in[i].min, in[i].value);
  255. if (i > 0) { /* if we have a y file */
  256. min_y = MIN(min_y, in[i].value);
  257. max_y = MAX(max_y, in[i].value);
  258. }
  259. }
  260. if ((i > 0) && (in[0].num_pnts != in[i].num_pnts)) {
  261. G_warning(_("Y input file <%s> contains %s data points than the X input file"),
  262. in[i].name,
  263. ((in[i].num_pnts < in[0].num_pnts) ? "fewer" : "more"));
  264. if (in[i].num_pnts > in[0].num_pnts)
  265. G_message(_("The last %d point(s) will be ignored"),
  266. (in[i].num_pnts - in[0].num_pnts));
  267. }
  268. }
  269. /* close all files */
  270. for (i = 0; i <= num_y_files; i++)
  271. fclose(in[i].fp);
  272. /* figure scaling factors and offsets */
  273. xscale = ((double)(x_line[2] - x_line[1]) / (double)(in[0].num_pnts));
  274. yscale = ((double)(y_line[1] - y_line[0]) / (max_y - min_y));
  275. yoffset = (double)(y_line[1]);
  276. xoffset = (double)(x_line[1]);
  277. /* figure tic_every and tic_units for the x-axis of the bar-chart.
  278. tic_every tells how often to place a tic-number. tic_unit tells
  279. the unit to use in expressing tic-numbers. */
  280. if (xscale < XTIC_DIST) {
  281. max_tics = (x_line[2] - x_line[1]) / XTIC_DIST;
  282. i = 1;
  283. while (((in[0].max - in[0].min) / tics[i].every) > max_tics)
  284. i++;
  285. tic_every = tics[i].every;
  286. tic_unit = tics[i].unit;
  287. strcpy(tic_name, tics[i].name);
  288. }
  289. else {
  290. tic_every = 1;
  291. tic_unit = 1;
  292. strcpy(tic_name, "");
  293. }
  294. /* open all the data files again */
  295. for (i = 0; i <= num_y_files; i++) {
  296. if ((in[i].fp = fopen(in[i].full_name, "r")) == NULL) {
  297. D_close_driver();
  298. G_fatal_error(_("Unable to open input file <%s>"), in[i].full_name);
  299. }
  300. }
  301. /* loop through number of lines in x data file,
  302. then loop thru for each y file, drawing a piece of each line and a
  303. legend bar on each iteration evenly divisible, a tic-mark
  304. on those evenly divisible by tic_unit, and a tic_mark
  305. number on those evenly divisible by tic_every */
  306. /* read the info from the inputs */
  307. for (line = 0; line < in[0].num_pnts; line++) {
  308. /* scan in an X value */
  309. err = fscanf(in[0].fp, "%f", &in[0].value);
  310. /* didn't find a number or hit EOF before our time */
  311. if ((err != 1) || (err == EOF)) {
  312. D_close_driver();
  313. G_fatal_error(_("Problem reading X data file at line %d"), line);
  314. }
  315. /* for each Y data file, get a value and compute where to draw it */
  316. for (i = 1; i <= num_y_files; i++) {
  317. /* check to see that we do indeed have data for this point */
  318. if (line < in[i].num_pnts) {
  319. err = fscanf(in[i].fp, "%f", &in[i].value);
  320. if ((in[i].num_pnts >= line) && (err != 1)) {
  321. D_close_driver();
  322. G_fatal_error(_("Problem reading <%s> data file at line %d"),
  323. in[i].name, line);
  324. }
  325. /* in case the Y file has fewer lines than the X file, we will skip
  326. trying to draw when we run out of data */
  327. /* draw increment of each Y file's data */
  328. D_use_color(in[i].color);
  329. /* find out position of where Y should be drawn. */
  330. /* if our minimum value of y is not negative, this is easy */
  331. if (min_y >= 0)
  332. new_y[i] =
  333. (yoffset - yscale * (in[i].value - min_y));
  334. /* if our minimum value of y is negative, then we have two
  335. cases: our current value to plot is pos or neg */
  336. else {
  337. if (in[i].value < 0)
  338. new_y[i] = (yoffset - yscale * (-1 *
  339. (min_y -
  340. in[i].value)));
  341. else
  342. new_y[i] = (yoffset - yscale * (in[i].value +
  343. (min_y * -1)));
  344. }
  345. new_x = xoffset + (line * xscale);
  346. if (line == 0) {
  347. prev_x = xoffset;
  348. prev_y[i] = yoffset;
  349. }
  350. D_line_abs(prev_x, prev_y[i], new_x, new_y[i]);
  351. prev_y[i] = new_y[i];
  352. }
  353. }
  354. prev_x = new_x;
  355. /* draw x-axis tic-marks and numbers */
  356. if (rem((long int)in[0].value, tic_every) == 0.0) {
  357. /* draw a numbered tic-mark */
  358. D_use_color(title_color);
  359. D_begin();
  360. D_move_abs(xoffset + line * xscale, b - ORIGIN_Y * (b - t));
  361. D_cont_rel(0, BIG_TIC * (b - t));
  362. D_end();
  363. D_stroke();
  364. if ((in[0].value >= 1) || (in[0].value <= -1) ||
  365. (in[0].value == 0))
  366. sprintf(txt, "%.0f", (in[0].value / tic_unit));
  367. else
  368. sprintf(txt, "%.2f", (in[0].value));
  369. text_height = (b - t) * TEXT_HEIGHT;
  370. text_width = (r - l) * TEXT_WIDTH;
  371. D_text_size(text_width, text_height);
  372. D_get_text_box(txt, &tt, &tb, &tl, &tr);
  373. while ((tr - tl) > XTIC_DIST) {
  374. text_width *= 0.75;
  375. text_height *= 0.75;
  376. D_text_size(text_width, text_height);
  377. D_get_text_box(txt, &tt, &tb, &tl, &tr);
  378. }
  379. D_pos_abs((xoffset + (line * xscale - (tr - tl) / 2)),
  380. (b - XNUMS_Y * (b - t)));
  381. D_text(txt);
  382. }
  383. else if (rem(line, tic_unit) == 0.0) {
  384. /* draw a tic-mark */
  385. D_use_color(title_color);
  386. D_begin();
  387. D_move_abs(xoffset + line * xscale,
  388. b - ORIGIN_Y * (b - t));
  389. D_cont_rel(0, SMALL_TIC * (b - t));
  390. D_end();
  391. D_stroke();
  392. }
  393. }
  394. /* close all input files */
  395. for (i = 0; i <= num_y_files; i++) {
  396. fclose(in[i].fp);
  397. }
  398. /* draw the x-axis label */
  399. if ((strcmp(title[0]->answer, "") == 0) && (strcmp(tic_name, "") == 0))
  400. *xlabel = '\0';
  401. else
  402. sprintf(xlabel, "X: %s %s", title[0]->answer, tic_name);
  403. text_height = (b - t) * TEXT_HEIGHT;
  404. text_width = (r - l) * TEXT_WIDTH * 1.5;
  405. D_text_size(text_width, text_height);
  406. D_get_text_box(xlabel, &tt, &tb, &tl, &tr);
  407. D_pos_abs((l + (r - l) / 2 - (tr - tl) / 2),
  408. (b - LABEL_1 * (b - t)));
  409. D_use_color(title_color);
  410. D_text(xlabel);
  411. /* DRAW Y-AXIS TIC-MARKS AND NUMBERS
  412. first, figure tic_every and tic_units for the x-axis of the bar-chart.
  413. tic_every tells how often to place a tic-number. tic_unit tells
  414. the unit to use in expressing tic-numbers. */
  415. if (yscale < YTIC_DIST) {
  416. max_tics = (y_line[1] - y_line[0]) / YTIC_DIST;
  417. i = 1;
  418. while (((max_y - min_y) / tics[i].every) > max_tics)
  419. i++;
  420. tic_every = tics[i].every;
  421. tic_unit = tics[i].unit;
  422. strcpy(tic_name, tics[i].name);
  423. }
  424. else {
  425. tic_every = 1;
  426. tic_unit = 1;
  427. strcpy(tic_name, "");
  428. }
  429. /* Y-AXIS LOOP */
  430. for (i = (int)min_y; i <= (int)max_y; i += tic_unit) {
  431. if (rem(i, tic_every) == 0.0) {
  432. /* draw a tic-mark */
  433. D_begin();
  434. D_move_abs(x_line[0], yoffset - yscale * (i - min_y));
  435. D_cont_rel(-(r - l) * BIG_TIC, 0);
  436. D_end();
  437. D_stroke();
  438. /* draw a tic-mark number */
  439. sprintf(txt, "%d", (i / tic_unit));
  440. text_height = (b - t) * TEXT_HEIGHT;
  441. text_width = (r - l) * TEXT_WIDTH;
  442. D_text_size(text_width, text_height);
  443. D_get_text_box(txt, &tt, &tb, &tl, &tr);
  444. while ((tt - tb) > YTIC_DIST) {
  445. text_width *= 0.75;
  446. text_height *= 0.75;
  447. D_text_size(text_width, text_height);
  448. D_get_text_box(txt, &tt, &tb, &tl, &tr);
  449. }
  450. D_pos_abs(l + (r - l) * YNUMS_X - (tr - tl) / 2,
  451. yoffset - (yscale * (i - min_y) + 0.5 * (tt - tb)));
  452. D_text(txt);
  453. }
  454. else if (rem(i, tic_unit) == 0.0) {
  455. /* draw a tic-mark */
  456. D_begin();
  457. D_move_abs(x_line[0], (yoffset - yscale * (i - min_y)));
  458. D_cont_rel(-(r - l) * SMALL_TIC, 0);
  459. D_end();
  460. D_stroke();
  461. }
  462. }
  463. /* draw the y-axis label */
  464. if ((strcmp(title[1]->answer, "") == 0) && (strcmp(tic_name, "") == 0))
  465. *xlabel = '\0';
  466. else
  467. sprintf(xlabel, "Y: %s %s", title[1]->answer, tic_name);
  468. text_height = (b - t) * TEXT_HEIGHT;
  469. text_width = (r - l) * TEXT_WIDTH * 1.5;
  470. D_text_size(text_width, text_height);
  471. D_get_text_box(xlabel, &tt, &tb, &tl, &tr);
  472. D_pos_abs(l + (r - l) / 2 - (tr - tl) / 2, b - LABEL_2 * (b - t));
  473. D_use_color(title_color);
  474. D_text(xlabel);
  475. /* top label */
  476. sprintf(xlabel, "%s", title[2]->answer);
  477. text_height = (b - t) * TEXT_HEIGHT;
  478. text_width = (r - l) * TEXT_WIDTH * 2.0;
  479. D_text_size(text_width, text_height);
  480. D_get_text_box(xlabel, &tt, &tb, &tl, &tr);
  481. /*
  482. D_move_abs((int)(((r-l)/2)-(tr-tl)/2),
  483. (int) (t+ (b-t)*.07) );
  484. */
  485. D_pos_abs(l + (r - l) / 2 - (tr - tl) / 2, t + (b - t) * .07);
  486. D_use_color(title_color);
  487. D_text(xlabel);
  488. /* draw x and y axis lines */
  489. D_use_color(title_color);
  490. D_polyline_abs(x_line, y_line, 3);
  491. D_save_command(G_recreate_command());
  492. D_close_driver();
  493. exit(EXIT_SUCCESS);
  494. }