1 /*
2 * $Id: plot.c,v 1.28 2001/02/13 23:38:06 danny Exp $
3 *
4 * Copyright � 1998, 1999, 2000, 2001 Free Software Foundation, Inc.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2, or (at your option)
9 * any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this software; see the file COPYING. If not, write to
18 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20
21 /*
22 * $Id: plot.c,v 1.28 2001/02/13 23:38:06 danny Exp $
23 *
24 * This file contains the code to draw plots from the Oleo data
25 * layered on top of the libsciplot functions.
26 */
27
28 #ifdef HAVE_CONFIG_H
29 #include "config.h"
30 #endif
31
32 #ifdef WITH_DMALLOC
33 #include <dmalloc.h>
34 #endif
35
36 #include <ctype.h>
37 #include "global.h"
38 #include "graph.h"
39 #include "cmd.h"
40 #include "line.h"
41 #include "io-term.h"
42 #include "info.h"
43 #include "cell.h"
44 #include "regions.h"
45 #include "ref.h"
46 #include "io-utils.h"
47
48 #ifndef X_DISPLAY_MISSING
49 #include <X11/Intrinsic.h>
50 #endif
51
52 /*
53 * These are the devices supported by GNU plotutils.
54 * The first field is Oleo's representation of this file format.
55 * The second field is the string we need to pass to plotutils to select this
56 * type of device.
57 * The third field is the file name extension, should we save to a file.
58 * The last field is the string shown to the user to describe this device type.
59 */
60 struct PlotutilsDevices PlotutilsDeviceArray[] = {
61 /*
62 { GRAPH_NONE, "?", "?", "???" },
63 */
64 { GRAPH_POSTSCRIPT, "ps", "ps", "PostScript" },
65 { GRAPH_TEK, "tek", "tek", "Tektronix" },
66 { GRAPH_X, "x", "x", "X Window" },
67 { GRAPH_PNG, "png", "png", "Portable Network Graphics" },
68 { GRAPH_GIF, "gif", "gif", "GIF" },
69 { GRAPH_METAFILE, "meta", "metafile", "GNU metafile" },
70 { GRAPH_ILLUSTRATOR, "ai", "ai", "Adobe Illustrator" },
71 { GRAPH_FIG, "fig", "fig", "Fig" },
72 { GRAPH_PCL, "pcl", "pcl", "PCL" },
73 { GRAPH_HPGL, "hpgl", "hpgl", "HP GL" },
74 { GRAPH_REGIS, "regis", "regis", "ReGIS" },
75 { GRAPH_PNM, "pnm", "pnm", "pnm" },
76 { GRAPH_SVG, "svg", "svg", "svg" },
77 { GRAPH_CGM, "cgm", "cgm", "cgm" },
78 /* Don't add anything after this line */
79 { -1, NULL, NULL, NULL }
80 };
81
82 #include <stdarg.h>
83
84 typedef void (*sh) (int);
85 sh oldsig;
86
87 static RETSIGTYPE
handler(int sig)88 handler(int sig)
89 {
90 signal(sig, handler);
91
92 io_error_msg("Plotutils: got signal %d, aborting", sig);
93 }
94
95 static void
SignalInit(void)96 SignalInit(void)
97 {
98 oleo_catch_signals(&handler);
99 #if 0
100 oldsig = signal(SIGSEGV, handler);
101 #endif
102 }
103
104 void
PlotInit(void)105 PlotInit(void)
106 {
107 if (! Global->PlotGlobal) {
108 Global->PlotGlobal = (struct PlotGlobalType *)malloc(sizeof(struct PlotGlobalType));
109 memset(Global->PlotGlobal, 0, sizeof(struct PlotGlobalType));
110
111 graph_set_axis_lo('x', "0.0");
112 graph_set_axis_hi('x', "100.0");
113 graph_set_axis_lo('y', "0.0");
114 graph_set_axis_hi('y', "100.0");
115 graph_set_axis_auto(0, 1); /* X auto */
116 graph_set_axis_auto(1, 1); /* Y auto */
117 graph_set_linetooffscreen(0);
118
119 Global->PlotGlobal->img_width = PLOT_WINDOW_WIDTH;
120 Global->PlotGlobal->img_height = PLOT_WINDOW_HEIGHT;
121 }
122
123 }
124
125
126 /*
127 * Don't compile this source unless you have GNU Plotutils
128 */
129 #ifdef HAVE_LIBPLOT
130
131 #include <plot.h>
132 #include <oleo_plot.h>
133
134 #ifdef HAVE_LIBSCIPLOT
135 #include <sciplot/sciplot.h>
136 #else
137 #include <sciplot.h>
138 #endif
139
140 static plPlotter *handle;
141
142 static char *defaultcolor = "black";
143 static char *colors[] = { "yellow", "green", "blue", "red",
144 "magenta", "beige", "orange", "pink"};
145 static int ncolors = sizeof(colors) / sizeof(char *);
146
147 #ifdef HAVE_MOTIF
148 static Display *Sdpy;
149 static Window window;
150 #endif
151
152 /*
153 * Internal functions - initialize and close GNU PlotUtils
154 */
155 static void
PuOpen(const char * plotter,FILE * outfile)156 PuOpen(const char *plotter, FILE *outfile)
157 {
158 int r;
159 plPlotterParams *plotter_params;
160
161 SignalInit();
162
163 plotter_params = pl_newplparams();
164
165 #ifdef HAVE_MOTIF
166 /* This would crash plots into a file */
167 if (strcmp(plotter, "Xdrawable") == 0) {
168 pl_setplparam(plotter_params, "XDRAWABLE_DISPLAY", Sdpy);
169 pl_setplparam(plotter_params, "XDRAWABLE_DRAWABLE1", &window);
170 }
171 #endif
172
173 handle = pl_newpl_r(plotter, NULL, outfile, stderr, plotter_params);
174 r = pl_openpl_r(handle);
175 pl_deleteplparams(plotter_params);
176
177 pl_filltype_r(handle, 1);
178 pl_joinmod_r(handle, "round");
179 pl_flinewidth_r(handle, 2);
180 pl_pencolorname_r(handle, defaultcolor);
181
182 PlotInit();
183 }
184
185 #ifdef HAVE_MOTIF
186 void
PuX(Display * dpy,Window w)187 PuX(Display *dpy, Window w)
188 {
189 Sdpy = dpy;
190 window = w;
191 }
192 #endif
193
194 static void
PuClose()195 PuClose()
196 {
197 pl_deletepl_r(handle);
198 }
199
200 /*
201 * Get a float value out of a spreadsheet cell
202 */
float_cell(CELL * cp)203 static double float_cell(CELL *cp)
204 {
205 char *s;
206
207 switch (GET_TYP (cp)) {
208 case TYP_INT:
209 return (double) (cp->cell_int);
210 case TYP_FLT:
211 return cp->cell_flt;
212 case TYP_STR:
213 s = cp->cell_str;
214 return astof (&s);
215 default:
216 return 0.;
217 }
218 }
219
220 /*
221 * Simple Pie Chart
222 *
223 * This function will create a pie chart on a given plotter.
224 * Some plotters put their output on a file stream.
225 * For this you can pass a file pointer.
226 *
227 * Oleo can handle an X plotter (NULL file), or e.g. a PostScript
228 * plotter (the file pointer is where the PostScript commands
229 * are sent).
230 *
231 * This code borrows from a piechart example by Bernhard Reiter.
232 */
233 void
PuPieChart(char * plotter,FILE * outfile)234 PuPieChart(char *plotter, FILE *outfile)
235 {
236 int i, num, c;
237 char *s, **labels;
238 double curr, incr, r;
239 CELL *cp;
240 struct rng rngx;
241 Point p;
242
243 Multigrapher *mg;
244
245 PuOpen(plotter, outfile);
246 mg = sp_create_plot(handle, SP_PLOT_PIE);
247 sp_begin_plot(mg, 1.0, 0.0, 0.0);
248
249 /* Title */
250 s = graph_get_title();
251 sp_set_title(mg, s);
252
253 memset(&p, 0, sizeof(Point));
254
255 /* Get the data labels */
256 rngx = graph_get_data(0);
257 num = 0;
258 make_cells_in_range(&rngx);
259 while ((cp = next_cell_in_range()))
260 num++;
261 labels = (char **)calloc(num, sizeof(char *));
262
263 rngx = graph_get_data(0);
264 i = 0;
265 make_cells_in_range(&rngx);
266 while ((cp = next_cell_in_range())) {
267 if (GET_TYP(cp) == TYP_STR)
268 labels[i] = strdup(cp->cell_str);
269 else if (GET_TYP(cp) == TYP_FLT)
270 labels[i] = strdup(flt_to_str(cp->cell_flt));
271 else
272 labels[i] = NULL;
273 i++;
274 }
275
276 /*
277 * Get the data points
278 *
279 * Do note that we're picking up all ranges of data points here,
280 * even if doing so doesn't really make sense.
281 */
282 incr = curr = 0.0;
283 c = 0;
284 for (r = 1; r < NUM_DATASETS; r++) {
285 rngx = graph_get_data(r);
286
287 if (rngx.lr == 0 && rngx.lc == 0 && rngx.hr == 0 && rngx.hc == 0)
288 continue;
289
290 make_cells_in_range(&rngx);
291 i = 0;
292 while ((cp = next_cell_in_range())) {
293
294 p.label = labels[i];
295 p.x = float_cell(cp);
296
297 sp_plot_point(mg, &p);
298
299 i++;
300 }
301 }
302
303 for (i=0; i<num; i++)
304 if (labels[i])
305 free(labels[i]);
306 free(labels);
307
308 sp_end_plot(mg);
309 sp_destroy_plot(mg);
310
311 PuClose();
312 }
313
314 /*
315 * Stacked and unstacked bar charts
316 */
317 void
PuBarChart(char * plotter,FILE * outfile)318 PuBarChart(char *plotter, FILE *outfile)
319 {
320 int i, r;
321 struct rng rngx;
322 CELL *cp;
323 Multigrapher *mg;
324
325 PuOpen(plotter, outfile);
326
327 mg = sp_create_plot(handle, SP_PLOT_BAR);
328
329 sp_set_title(mg, graph_get_title());
330 sp_set_axis_title(mg, X_AXIS, graph_get_axis_title('x'));
331 sp_set_axis_title(mg, Y_AXIS, graph_get_axis_title('y'));
332
333 sp_begin_plot(mg, 1.0, 0.0, 0.0);
334
335 sp_first_dataset(mg);
336 for (r = 1; r < NUM_DATASETS; r++) {
337 rngx = graph_get_data(r);
338
339 if (rngx.lr == 0 && rngx.lc == 0 && rngx.hr == 0 && rngx.hc == 0)
340 continue;
341
342 sp_legend_label(mg, r, graph_get_data_title(r));
343
344 make_cells_in_range(&rngx);
345 i = 0;
346 while ((cp = next_cell_in_range())) {
347 sp_plot_point_simple(mg, 0, 0.0, float_cell(cp));
348 i++;
349 }
350 sp_next_dataset(mg);
351 }
352
353 sp_legend_draw(mg);
354 sp_end_plot(mg);
355 PuClose();
356 }
357
358 /*
359 * XY charts
360 */
361 void
PuXYChart(char * plotter,FILE * outfile)362 PuXYChart(char *plotter, FILE *outfile)
363 {
364 int i, r, num, nalloc, noxdata = 0;
365 double x, y, xmin, xmax, delta, *xes = 0,
366 ymax, ymin, oldx, oldy;
367 struct rng rngx;
368 CELL *cp;
369 Multigrapher *mg;
370
371 PuOpen(plotter, outfile);
372
373 /*
374 * Figure out whether dataset 0 has been specified.
375 * If it is, this is our X axis data; otherwise take a simple
376 * incremental counter for it. (1, 2, 3, 4, ...)
377 */
378 rngx = graph_get_data(0);
379 if (rngx.lr == NON_ROW) {
380 noxdata = 1;
381
382 /* Figure out X axis borders - use the first dataset you find */
383 for (r = 1; r < NUM_DATASETS; r++) {
384 rngx = graph_get_data(r);
385
386 if (rngx.lr != NON_ROW)
387 break;
388 }
389
390 nalloc = 0;
391 make_cells_in_range(&rngx);
392 while ((cp = next_cell_in_range()))
393 nalloc++;
394
395 xmin = 1.0;
396 xmax = nalloc;
397 xes = NULL;
398 } else {
399 /* Figure out X axis borders */
400 rngx = graph_get_data(0);
401 make_cells_in_range(&rngx);
402 cp = next_cell_in_range();
403 xmin = xmax = float_cell(cp);
404 nalloc = 0;
405 while ((cp = next_cell_in_range())) {
406 y = float_cell(cp);
407 nalloc++;
408 if (y < xmin)
409 xmin = y;
410 else if (y > xmax)
411 xmax = y;
412 }
413
414 xes = (double *)calloc(nalloc + 1, sizeof(double));
415 make_cells_in_range(&rngx);
416 i = 0;
417 while ((cp = next_cell_in_range())) {
418 if (i > nalloc)
419 abort();
420 xes[i++] = float_cell(cp);
421 }
422 }
423
424 if (graph_get_axis_auto(0)) {
425 delta = xmax - xmin;
426 xmin -= delta * 0.1;
427 xmax += delta * 0.1;
428 } else {
429 xmin = graph_get_axis_lo(0);
430 xmax = graph_get_axis_lo(1);
431 }
432
433 num = 0;
434 if (graph_get_axis_auto(1)) {
435 for (r = 1; r < NUM_DATASETS; r++) {
436 rngx = graph_get_data(r);
437
438 if (rngx.lr == 0 && rngx.lc == 0 && rngx.hr == 0 && rngx.hc == 0)
439 continue;
440
441 make_cells_in_range(&rngx);
442 while ((cp = next_cell_in_range())) {
443 y = float_cell(cp);
444 num++;
445 if (y < ymin)
446 ymin = y;
447 else if (y > ymax)
448 ymax = y;
449 }
450 }
451 } else {
452 ymin = XYyMin; /* FIX ME */
453 ymax = XYyMax;
454 }
455
456 mg = sp_create_plot(handle, SP_PLOT_XY);
457
458 sp_set_title(mg, graph_get_title());
459 sp_set_axis_title(mg, X_AXIS, graph_get_axis_title('x'));
460 sp_set_axis_title(mg, Y_AXIS, graph_get_axis_title('y'));
461
462 sp_begin_plot(mg, 1.0, 0.0, 0.0);
463 #if 0
464 sp_plot_symbol(mg, 5);
465 #endif
466
467 sp_set_axis_range(mg, X_AXIS, xmin, xmax, 0.0, 0);
468 sp_set_axis_range(mg, Y_AXIS, ymin, ymax, 0.0, 0);
469
470 if (Global->PlotGlobal->ticktype[0] == SP_TICK_STRFTIME) {
471 /* protect against empty format string */
472 if (Global->PlotGlobal->tickformat[0] == NULL || strlen(Global->PlotGlobal->tickformat[0]) == 0)
473 sp_set_axis_ticktype_date(mg, X_AXIS, 1.0, 1.0, "%c");
474 else
475 sp_set_axis_ticktype_date(mg, X_AXIS, 1.0, 1.0, Global->PlotGlobal->tickformat[0]);
476 } else if (Global->PlotGlobal->ticktype[0] == SP_TICK_PRINTF) {
477 /* FIX ME need more API for this */
478 }
479 if (Global->PlotGlobal->ticktype[1] == SP_TICK_STRFTIME) {
480 /* protect against empty format string */
481 if (Global->PlotGlobal->tickformat[1] == NULL || strlen(Global->PlotGlobal->tickformat[1]) == 0)
482 sp_set_axis_ticktype_date(mg, Y_AXIS, 1.0, 1.0, "%c");
483 else
484 sp_set_axis_ticktype_date(mg, Y_AXIS, 1.0, 1.0, Global->PlotGlobal->tickformat[1]);
485 } else if (Global->PlotGlobal->ticktype[1] == SP_TICK_PRINTF) {
486 /* FIX ME need more API for this */
487 }
488
489 sp_draw_frame(mg, 1);
490
491 for (r = 1; r < NUM_DATASETS; r++) {
492 rngx = graph_get_data(r);
493
494 if (rngx.lr == 0 && rngx.lc == 0 && rngx.hr == 0 && rngx.hc == 0)
495 continue;
496
497 #if 0
498 make_cells_in_range(&rngx);
499 while ((cp = next_cell_in_range())) {
500 y = float_cell(cp);
501 num++;
502 if (y < ymin)
503 ymin = y;
504 else if (y > ymax)
505 ymax = y;
506 }
507 #endif
508
509 sp_plot_symbol(mg, 4 + r);
510 sp_legend_label(mg, r, graph_get_data_title(r));
511
512 if (! XYyAuto) {
513 ymin = XYyMin; /* FIX ME */
514 ymax = XYyMax;
515 }
516
517 /* sp_setcolor(mg, defaultcolor); */
518
519 make_cells_in_range(&rngx);
520 i = 0;
521 while ((cp = next_cell_in_range())) {
522 if (i > nalloc) {
523 abort();
524 }
525 if (xes)
526 x = xes[i];
527 else
528 x = (double)i;
529
530 y = float_cell(cp);
531
532 if (i > 0) {
533 double x1, x2, y1, y2;
534 int out1 = 0, out2 = 0;
535
536 x1 = 10.0 * (oldx - xmin) / (xmax - xmin);
537 y1 = 10.0 * (oldy - ymin) / (ymax - ymin);
538 x2 = 10.0 * (x - xmin) / (xmax - xmin);
539 y2 = 10.0 * (y - ymin) / (ymax - ymin);
540
541 if (x1 < 0.0 || x1 > 10.0 || y1 < 0.0 || y1 > 10.0)
542 out1 = 1;
543 if (x2 < 0.0 || x2 > 10.0 || y2 < 0.0 || y2 > 10.0)
544 out2 = 1;
545
546 if (Global->PlotGlobal->LineToOffscreen /* Always draw */
547 || (out1 == 0 && out2 == 0)) {
548 sp_plot_point_simple(mg, 0, oldx, oldy);
549 sp_plot_point_simple(mg, 1, x, y);
550 }
551
552 }
553
554 oldx = x;
555 oldy = y;
556
557 i++;
558 }
559
560 sp_next_dataset(mg);
561 }
562
563 sp_legend_draw(mg);
564
565 sp_end_plot(mg);
566 sp_destroy_plot(mg);
567
568 PuClose();
569 if (xes)
570 free((void *)xes);
571 }
572
PuPlot(enum graph_type gt,enum graph_device gd,FILE * fp)573 void PuPlot(enum graph_type gt, enum graph_device gd, FILE *fp)
574 {
575 void (*f)(char *, FILE *) = PuXYChart; /* FIX ME */
576 char *pu;
577 int i;
578
579 switch (gt) {
580 case GRAPH_XY:
581 f = PuXYChart;
582 break;
583 case GRAPH_BAR:
584 f = PuBarChart;
585 break;
586 case GRAPH_PIE:
587 f = PuPieChart;
588 break;
589 };
590
591 for (i=0, pu=NULL; PlotutilsDeviceArray[i].pus; i++) {
592 if (Global->PlotGlobal->device == PlotutilsDeviceArray[i].t) {
593 pu = PlotutilsDeviceArray[i].pus;
594 break;
595 }
596 }
597
598 if (! pu)
599 return; /* FIX ME */
600
601 PlotInit();
602 (*f)(pu, fp);
603 }
604 #endif /* HAVE_LIBPLOT */
605