1 /* ======================================================= *
2  * Copyright 1998-2008 Stephen C. Grubb                    *
3  * http://ploticus.sourceforge.net                         *
4  * Covered by GPL; see the file ./Copyright for details.   *
5  * ======================================================= */
6 
7 /* PROC LINEPLOT - draw a lineplot */
8 
9 #include "pl.h"
10 #define MAXALT 200
11 #define MOVE 0
12 #define LINE 1
13 #define PATH 2
14 
15 static int dblcompare( const void *a, const void *b );
16 static int placenum();
17 
18 int
PLP_lineplot()19 PLP_lineplot()
20 {
21 char attr[NAMEMAXLEN], *line, *lineval;
22 int lvp, first;
23 
24 char buf[256], numstr[40], symcode[80];
25 int i, j, k, stat, align, accum;
26 int npoints, result, nalt, altlist[MAXALT+2], anyvalid, realrow, sortopt;
27 int yfield, xfield, ptlabelfield;
28 int stairstep, donumbers, dopoints, instancemode, gapmissing, ingap, clipping, firstpt, groupmode, relax_xrange, fillmode;
29 
30 char *linedetails, *label, *labeldetails, *shownums, *numstrfmt, *pointsym, *ptlabeldetails, *fillcolor;
31 char *legendlabel, *selectex, *forcelastx, *legsamptyp, *altsym, *altwhen;
32 
33 double adjx, adjy, linestart, linestop, x, y, lastseglen, lastx, lasty, radius, ptlblstart, ptlblstop, sob, linxstart;
34 double f, sum, cr, firstx, firsty, typical_interval;
35 double fillbleed;
36 
37 TDH_errprog( "pl proc lineplot" );
38 
39 /* initialize */
40 yfield = -1; xfield = -1;
41 ptlabelfield = 0;
42 accum = 0; stairstep = 0; fillmode = 0; instancemode = 0; groupmode = 0;
43 gapmissing = 0; clipping = 0; firstpt = 0; sortopt = 0; relax_xrange = 0;
44 label = ""; labeldetails = ""; shownums = ""; ptlabeldetails = ""; linedetails = ""; pointsym = "";
45 legendlabel = ""; selectex = ""; forcelastx = ""; altsym = ""; altwhen = "";
46 numstrfmt = "%g";
47 fillcolor = "gray(0.8)";
48 legsamptyp = "symbol";
49 linestart = EDXlo; linestop = EDXhi;
50 ptlblstart = EDXlo; ptlblstop = EDXhi;
51 linxstart = EDXlo;
52 sob = 0.0;
53 lastseglen = 0.0;
54 typical_interval = -99.0;
55 fillbleed = 0.0;
56 
57 
58 
59 /* get attributes.. */
60 first = 1;
61 while( 1 ) {
62 	line = getnextattr( first, attr, &lvp );
63 	if( line == NULL ) break;
64 	first = 0;
65 	lineval = &line[lvp];
66 
67 	if( strcmp( attr, "yfield" )==0 ) yfield = fref( lineval ) - 1;
68 	else if( strcmp( attr, "xfield" )==0 ) xfield = fref( lineval ) - 1;
69 	else if( strcmp( attr, "ptlabelfield" )==0 ) ptlabelfield = fref( lineval ) - 1;
70 	else if( strcmp( attr, "linedetails" )==0 ) linedetails = lineval;
71 	else if( strcmp( attr, "label" )==0 ) { label = lineval; convertnl( label ); }
72 	else if( strcmp( attr, "labeldetails" )==0 ) labeldetails = lineval;
73 	else if( strcmp( attr, "legendlabel" )==0 ) legendlabel = lineval;
74 	else if( strcmp( attr, "linerange" )==0 ) getrange( lineval, &linestart, &linestop, 'x', EDXlo, EDXhi );
75 	else if( strcmp( attr, "xstart" )==0 ) { linxstart = Econv( X, lineval ); if( Econv_error() ) linxstart = EDXlo; }
76 	else if( strcmp( attr, "firstpoint" )==0 ) {
77 		char xstr[80], ystr[80];
78 		sscanf( lineval, "%s %s", xstr, ystr );
79 		firstpt = 1;
80 		firstx = Econv( X, xstr); if( Econv_error() ) firstpt = 0;
81 		firsty = Econv( X, ystr ); if( Econv_error() ) firstpt = 0;
82 		}
83 	else if( strcmp( attr, "accum" )==0 ) accum = getyn( lineval );
84 	else if( strcmp( attr, "stairstep" )==0 ) stairstep = getyn( lineval );
85 	else if( strcmp( attr, "instancemode" )==0 ) instancemode = getyn( lineval );
86 	else if( strcmp( attr, "groupmode" )==0 ) groupmode = getyn( lineval );
87 	else if( strcmp( attr, "gapmissing" )==0 ) {
88 		if( strncmp( lineval, "y", 1 )==0 ) gapmissing = 1;
89 		else if( strcmp( lineval, "small" )==0 ) gapmissing = 2;
90 		else if( strcmp( lineval, "auto" )==0 ) gapmissing = 3;
91 		else if( strcmp( lineval, "autosmall" )==0 ) gapmissing = 4;
92 		else if( strcmp( lineval, "autozero" )==0 ) gapmissing = 5;
93 		else gapmissing = 0;
94 		}
95 	else if( strcmp( attr, "clip" )==0 ) clipping = getyn( lineval );
96 	else if( strcmp( attr, "sort" )==0 ) sortopt = getyn( lineval );
97 	else if( strcmp( attr, "relax_xrange" ) == 0 ) relax_xrange = getyn( lineval );
98 	else if( strcmp( attr, "lastseglen" )==0 ) Elenex( lineval, X, &lastseglen );
99 	else if( strcmp( attr, "numbers" )==0 ) shownums = lineval;
100 	else if( strcmp( attr, "numbersformat" )==0 ) numstrfmt = lineval;
101 	else if( strcmp( attr, "select" )==0 ) selectex = lineval;
102 	else if( strcmp( attr, "altsymbol" )==0 ) altsym = lineval;
103 	else if( strcmp( attr, "altwhen" )==0 ) altwhen = lineval;
104 	else if( strcmp( attr, "lastx" )==0 ) forcelastx = lineval;
105 	else if( strcmp( attr, "pointsymbol" )==0 ) pointsym = lineval;
106 	else if( strcmp( attr, "ptlabeldetails" )==0 ) ptlabeldetails = lineval;
107 	else if( strcmp( attr, "ptlabelrange" )==0 ) {
108 		if( strcmp( lineval, "last" )==0 ) ptlblstart = EDXhi-0.1;
109 		else if( strcmp( lineval, "first" )==0 ) { ptlblstart = EDXlo; ptlblstop = EDXhi-0.000001; }
110 		else getrange( lineval, &ptlblstart, &ptlblstop, 'x', EDXlo, EDXhi );
111 		}
112 	else if( strcmp( attr, "stairoverbars" )==0 ) {
113 		if( strncmp( lineval, "y", 1 )==0 ) { sob = 0.5; stairstep = 1; /* implied */ }
114 		else sob = 0.0;
115 		}
116 	else if( strcmp( attr, "fill" )==0 ) {
117 		fillcolor = lineval;
118 		if( fillcolor[0] != '\0' ) fillmode = 1;
119 		else fillmode = 0;
120 		}
121 	else if( strcmp( attr, "fillbleed" )==0 ) fillbleed = ftokncpy( lineval );
122 	else if( strcmp( attr, "legendsampletype" )==0 ) legsamptyp = lineval;
123 	else Eerr( 1, "attribute not recognized", attr );
124 	}
125 
126 
127 /* overrides and degenerate cases */
128 /* -------------------------- */
129 if( Nrecords < 1 ) return( Eerr( 17, "No data has been read yet w/ proc getdata", "" ) );
130 if( !scalebeenset() )
131          return( Eerr( 51, "No scaled plotting area has been defined yet w/ proc areadef", "" ) );
132 
133 
134 if( (yfield < 0 || yfield >= Nfields ) && !instancemode ) return( Eerr( 601, "yfield out of range", "" ) );
135 if( xfield >= Nfields ) return( Eerr( 601, "xfield out of range", "" ) );
136 if( yfield >= 0 && instancemode ) {
137 	Eerr( 4729, "warning, turning instancemode off since yfield specified", "" );
138 	instancemode = 0;
139 	}
140 
141 if( groupmode && ptlabelfield ) {
142 	Eerr( 4729, "warning, turning ptlabelfield off since groupmode specified", "" );
143 	ptlabelfield = 0;
144 	}
145 
146 if( strncmp( legendlabel, "#usefname", 9 )==0 ) {
147 	if( instancemode ) getfname( xfield+1, legendlabel );
148 	else getfname( yfield+1, legendlabel );
149 	}
150 
151 
152 
153 /* now do the plotting work.. */
154 /* -------------------------- */
155 
156 donumbers = 1;
157 if( GL_slmember( shownums, "no*" ) || shownums[0] == '\0' ) donumbers = 0;
158 
159 dopoints = 1;
160 if( GL_slmember( pointsym, "no*" ) || pointsym[0] == '\0' ) dopoints = 0;
161 
162 
163 /* put all values into PLV vector, doing accumulation if required.. */
164 j = 0;
165 f = linxstart;
166 nalt = 0;
167 
168 for( i = 0; i < Nrecords; i++ ) {
169 
170 	if( selectex[0] != '\0' ) { /* process against selection condition if any.. */
171                 stat = do_select( selectex, i, &result );
172                 if( stat != 0 ) { Eerr( stat, "Select error", selectex ); continue; }
173                 if( result == 0 ) continue; /* reject */
174                 }
175 	if( altwhen[0] != '\0' ) { /* check altwhen condition.. */
176                 stat = do_select( altwhen, i, &result );
177                 if( stat != 0 ) { Eerr( stat, "Select error", altwhen ); continue; }
178                 if( result == 1 && nalt < MAXALT ) {
179 			/* altlist[nalt] = j/2; */
180 			altlist[nalt] = j/3;
181 			nalt++;
182 			}
183                 }
184 
185 
186 
187 	/* X */
188 	if( xfield < 0 ) {
189 		PLV[j] = f + sob;
190 		f += 1.0;
191 		}
192 	else 	{
193 		PLV[j] = fda( i, xfield, X ) + sob;
194 		if( Econv_error() ) {
195 			conv_msg( i, xfield, "xfield" );
196 			continue;
197 			}
198 		}
199 
200 	j++;
201 
202 	/* Y */
203 	if( instancemode ) PLV[j] = 1.0;
204 	else 	{
205 		PLV[j] = fda( i, yfield, Y );
206 		if( Econv_error() ) {
207 			conv_msg( i, yfield, "yfield" );
208 			PLV[j] = NEGHUGE;
209 			/* continue; removed scg 5/19/99 */
210 			}
211 		}
212 
213 	j++;
214 
215 	PLV[j] = (double)i;
216 	j++;
217 
218 	if( j >= PLVsize-1 ) {
219 		Eerr( 3579, "Sorry, too many curve points, curve truncated (raise using -maxvector)", "" );
220 		break;
221 		}
222 	}
223 
224 npoints = j / 3;
225 
226 
227 /* sort if required.. */  /* added 4/22/02 */
228 if( sortopt ) {
229         if( PLS.debug ) fprintf( PLS.diagfp, "sorting points for line\n" );
230         qsort( PLV, npoints, sizeof(double)*3, dblcompare );
231         }
232 
233 
234 
235 /* process for groupmode.. */
236 if( groupmode && xfield >= 0 ) for( i = 0; i < npoints; i++ ) {
237 
238 	for( k = i+1; k < npoints; k++ ) {
239 
240 		if( dat3d(i,0) == dat3d(k,0) ) {
241 			if( instancemode ) y = 1.0;
242 			else y = dat3d( k, 1 );
243 			dat3d( k, 1 ) = NEGHUGE; /* rub out the additional instance.. */
244 			if( y > NEGHUGE+1 ) (dat3d( i, 1 )) += (y);
245 			}
246 		else 	{
247 			i = k-1;     /* back off.. */
248 			break;
249 			}
250 		}
251 	}
252 /* fprintf( stderr, "after grouping\n" );
253  * for( i = 0; i < npoints; i++ ) fprintf( stderr, "%g %g %g\n", dat3d(i,0), dat3d(i,1), dat3d(i,2 ) );
254  */
255 
256 
257 /* process for accum.. */
258 if( accum ) {
259 	sum = 0.0;
260 	for( i = 0; i < npoints; i++ ) {
261 
262 		if( dat3d( i, 1 ) > (NEGHUGE+1) ) {
263 			sum += (dat3d( i, 1 ));
264 			(dat3d( i, 1 )) = sum;
265 			}
266 		}
267    	}
268 
269 /* fprintf( stderr, "after accum\n" );
270  * for( i = 0; i < npoints; i++ ) fprintf( stderr, "%g %g %g\n", dat3d(i,0), dat3d(i,1), dat3d(i,2 ) );
271  */
272 
273 
274 
275 /* draw the curve.. */
276 /* ---------------- */
277 
278 /* set line parameters */
279 linedet( "linedetails", linedetails, 1.0 );
280 
281 if( fillmode ) {
282 	Ecolor( fillcolor ); /* scg 6/18/04 */
283 	}
284 
285 
286 first = 1;
287 lasty = 0.0;
288 lastx = 0.0;
289 anyvalid = 0;
290 ingap = 0;
291 cr = Elimit( Y, 'l', 's' );
292 for( i = 0; i < npoints; i++ ) {
293 	if( !first && (y > (NEGHUGE+1) && x > (NEGHUGE+1) ) ) { lasty = y; lastx = x; }
294 
295 	if( first && firstpt ) {
296 		x = firstx;
297 		y = firsty;
298 		}
299 	else	{
300 		x = dat3d(i,0);
301 		y = dat3d(i,1);
302 		}
303 
304 	if( x < (NEGHUGE+1) || y < (NEGHUGE+1) ) {
305 		if( gapmissing ) ingap = 1;
306 		continue; /* skip bad values */
307 		}
308 
309 	if( x < linestart && !relax_xrange ) continue; /* X out of range - lo */
310 	if( x > linestop && !relax_xrange ) {          /* X out of range - hi */
311 		x = lastx; /* back up to last in-range point so last stairtstep is correct*/
312 		y = lasty;
313 		break;
314 		}
315 	if( !first && ( gapmissing == 3 || gapmissing == 4 || gapmissing == 5 )) {
316 		if( typical_interval < 0.0 ) typical_interval = (x - lastx)*1.01;
317 		else if( x - lastx > typical_interval ) {
318 			/* if( gapmissing == 5 ) { Elinu( lastx+typical_interval, 0.0 ); Elinu( x-typical_interval, 0.0 ); } */
319 			if( gapmissing == 5 ) { Elinu( lastx, 0.0 ); Elinu( x, 0.0 ); Elinu( x, y ); }  /* changed, scg 11/20/07 */
320 			else ingap = 1;
321 			}
322 		}
323 
324 	if( !anyvalid && !Ef_inr( Y, y ) ) continue; /* 1/9/03 scg - anyvalid should not become 1 if out of range in Y */
325 
326 	anyvalid = 1;
327 	if( first ) {
328 		Emovu( x, y );
329 		setfloatvar( "XSTART", x, "%g" );
330 		setfloatvar( "YSTART", y, "%g" );
331 		first = 0;
332 		ingap = 0;
333 		continue;
334 		}
335 	if( !first && fillmode && !ingap ) {
336 		Emovu( x, cr );
337 		Epath( Eax( lastx )-fillbleed, Eay( cr ) );  /* -0.005 added to remove tiny gap artifact seen on EPS ... scg 1/9/08 */
338 		Epath( Eax( lastx )-fillbleed, Eay( lasty ) );
339 		/* was: Epathu( lastx, cr );  */
340 		/*      Epathu( lastx, lasty );  */
341 
342 		if( stairstep ) Epathu( x, lasty );
343 		else Epathu( x, y );
344 		Ecolor( fillcolor ); /* added scg 11/2/07 - color bug related to gapmissing=2 and fill=1 */
345 		Efill();
346 		}
347 	if( ( gapmissing == 2 || gapmissing ==4 ) && ingap ) {        /* do a quarter-length nib at previous location */
348 		double nib;
349 		Emovu( lastx, lasty );
350 		nib = (x-lastx) * 0.25;
351 		if( fillmode ) Ecblock( Eax(lastx), Eay(cr), Eax(lastx+nib), Eay(lasty), fillcolor, 0 );
352 		else Elinu( lastx+nib, lasty );
353 		}
354 	if( ! fillmode ) {
355 		if( ingap ) Emovu( x, y );
356 		if( stairstep && x > linestart && !ingap ) Elinu( x, lasty );
357 		if( stairstep && x == linestart ) Emovu( x, y );
358 		if( clipping && !ingap && !stairstep ) {
359 			double cx1, cy1, cx2, cy2;
360 			cx1 = lastx; cy1 = lasty; cx2 = x; cy2 = y;
361 			stat = Elineclip( &cx1, &cy1, &cx2, &cy2, EDXlo, EDYlo, EDXhi, EDYhi );
362 			if( !stat ) { Emovu( cx1, cy1 ); Elinu( cx2, cy2 ); }
363 			}
364 		else Elinu( x, y );
365 		}
366 	ingap = 0;
367 	}
368 
369 if( !anyvalid ) { /* no plottable data points.. exit */
370 	/* Ecolor( oldcolor ); */ /* don't do color chg - scg 5/10/05 */
371 	return( 0 );
372 	}
373 
374 /* if last point was invalid, back up to most recent valid point.. */
375 if( x < (NEGHUGE+1) || y < (NEGHUGE+1) ) { x = lastx; y = lasty; }
376 
377 
378 /* handle last segment of stairstep.. */
379 /* if( stairstep ) { */ /* } changed to allow lastseglen to be used anytime, scg 12/13/01 */
380 if( lastseglen > 0.0 ) {
381 	if( x < (NEGHUGE+1) || y < (NEGHUGE+1) ) { x = lastx; y = lasty; }
382 	lastx = Eax( x ) + lastseglen;
383 	if( fillmode ) {
384 		Emov( Eax(x), Eay(cr) ); Epath( lastx, Eay(cr) );
385 		Epath( lastx, Eay(y) ); Epath( Eax(x), Eay(y) );
386 		/* Ecolorfill( fillcolor ); */ /* using Efill .. scg 6/18/04 */
387 		Efill();
388 		}
389 	else 	Elin( lastx, Eay( y ) );
390 	}
391 else lastx = Eax(x);
392 
393 if( forcelastx[0] != '\0' ) {
394 	lastx = Eax( Econv( X, forcelastx ) );
395 	if( !Econv_error() && anyvalid ) Elin( lastx, Eay( y ) );
396 	}
397 
398 
399 /* set YFINAL and XFINAL */
400 Euprint( numstr, Y, y, numstrfmt );
401 setcharvar( "YFINAL", numstr );
402 Euprint( numstr, X, lastx, numstrfmt );
403 setcharvar( "XFINAL", numstr );
404 
405 
406 
407 
408 
409 
410 /* do points, labels, etc. */
411 /* ----------------------- */
412 for( i = 0; i < npoints; i++ ) {
413 	x = dat3d(i,0);
414 	y = dat3d(i,1);
415 
416 
417 	if( x < (NEGHUGE+1) || y < (NEGHUGE+1) ) continue; /* skip bad values */
418 
419 	if( x < linestart && !relax_xrange ) continue; /* out of range - lo */
420 	if( x > linestop && !relax_xrange ) {          /* out of range - hi */
421 		break;
422 		}
423 
424 	if( clipping && !stairstep && !fillmode ) {
425 		/* if clipping, suppress points or labels that are outside the plotting area */
426 		if( x < EDXlo || x > EDXhi || y < EDYlo || y > EDYhi ) continue;
427 		}
428 
429 	lasty = y;
430 
431 	if( x >= ptlblstart && x <= ptlblstop ) {
432 		if( donumbers && stairstep ) {
433 			if( i == npoints-1 || GL_close_to( x, linestop, 0.01 ) ) {
434 				double xls;
435 				xls = Edx(lastseglen) - Edx(0.0);
436 				placenum( shownums, x, x + xls, y, numstrfmt, linedetails );
437 				}
438 			else placenum( shownums, x, dat3d(i+1,0), y, numstrfmt, linedetails );
439 			}
440 		else if( donumbers && !stairstep )
441 			  placenum( shownums, x, x, y, numstrfmt, linedetails );
442 		if( donumbers && fillmode ) Ecolor( fillcolor ); /* scg 6/18/04 */
443 		}
444 
445 	if( dopoints ) {
446 		int jj;
447 		/* see if this is one of the alternates.. if so use altsym rather
448 			than the regular symbol */
449 		for( jj = 0; jj < nalt; jj++ ) if( i == altlist[jj] ) break;
450 		if( jj != nalt ) symdet( "altsym", altsym, symcode, &radius );
451 		else symdet( "pointsymbol", pointsym, symcode, &radius );
452 		Emark( Eax(x), Eay(y), symcode, radius );
453 		}
454 	realrow = (int) dat3d(i,2);
455 	if( ptlabelfield && x >= ptlblstart && x <= ptlblstop ) {
456 		textdet( "ptlabeldetails", ptlabeldetails, &align, &adjx, &adjy, -4, "R", 1.0 );
457 		if( align == '?' ) align = 'C';
458 		Emov( Eax( x ) + adjx, Eay( y ) + adjy );
459 		Edotext( da( realrow, ptlabelfield ), align );
460 		}
461 	}
462 
463 
464 if( label[0] != '\0' ) {
465         GL_varsub( label, "@YFINAL", numstr );
466 	PL_do_subst( buf, label, realrow, 0 );  /* also allow substitution of any @field on the last-plotted data row.. added scg 1/29/07 */
467         textdet( "labeldetails", labeldetails, &align, &adjx, &adjy, -2, "R", 1.0 );
468         if( align == '?' ) align = 'L';
469         Emov( lastx+0.05+adjx, (Eay( lasty )-(Ecurtextheight*.35))+adjy );
470         Edotext( buf, align );
471         }
472 
473 if( legendlabel[0] != '\0' ) {
474 	if( fillmode ) PL_add_legent( LEGEND_COLOR, legendlabel, "", fillcolor, "", "" );
475 	else if( pointsym[0] != '\0' && strcmp( pointsym, "none" )!= 0 ) {
476 		if( legsamptyp[0] == 's' && strlen( legsamptyp ) <= 6 )
477 			PL_add_legent( LEGEND_SYMBOL, legendlabel, "", pointsym, "", "" );
478 		else
479 			PL_add_legent( LEGEND_LINE+LEGEND_SYMBOL, legendlabel, "", linedetails, pointsym, "" );
480 		}
481 	else PL_add_legent( LEGEND_LINE, legendlabel, "", linedetails, "", "" );
482 	}
483 
484 /* if( fillmode ) Ecolor( oldcolor ); */ /* restore */
485 return( 0 );
486 }
487 
488 /* ----------------------- */
489 /* place one line plot number  */
490 static int
placenum(shownums,x1,x2,y,numstrfmt,linedetails)491 placenum( shownums, x1, x2, y, numstrfmt, linedetails )
492 char *shownums;
493 double x1, x2, y;
494 char *numstrfmt, *linedetails;
495 {
496 char numstr[20];
497 int align;
498 double adjx, adjy;
499 
500 /* change to text color, size, etc.. */
501 textdet( "numbers", shownums, &align, &adjx, &adjy, -4, "R", 1.0 );
502 if( align == '?' ) align = 'C';
503 Emov( Eax( (x1+x2)/2.0 ) + adjx, Eay(y)+0.02+adjy );
504 /* sprintf( numstr, numstrfmt, y ); */
505 Euprint( numstr, 'y', y, numstrfmt );
506 Edotext( numstr, align );
507 linedet( "linedetails", linedetails, 1.0 );
508 Emovu( x1, y ); /* restore old position for drawing the curve.. */
509 return( 0 );
510 }
511 
512 /* ------------------------- */
513 static int
dblcompare(a,b)514 dblcompare( a, b )
515 const void *a, *b;
516 {
517 double *f, *g;
518 f = (double *)a;
519 g = (double *)b;
520 
521 if( *f > *g ) return( 1 );
522 if( *f < *g ) return( -1 );
523 return( 0 );
524 }
525 
526 
527 /* ======================================================= *
528  * Copyright 1998-2005 Stephen C. Grubb                    *
529  * http://ploticus.sourceforge.net                         *
530  * Covered by GPL; see the file ./Copyright for details.   *
531  * ======================================================= */
532