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