/* ======================================================= *
 * Copyright 1998-2008 Stephen C. Grubb                    *
 * http://ploticus.sourceforge.net                         *
 * Covered by GPL; see the file ./Copyright for details.   *
 * ======================================================= */

/* PROC LINEPLOT - draw a lineplot */

#include "pl.h"
#define MAXALT 200
#define MOVE 0
#define LINE 1
#define PATH 2

static int dblcompare( const void *a, const void *b );
static int placenum();

int
PLP_lineplot()
{
char attr[NAMEMAXLEN], *line, *lineval;
int lvp, first;

char buf[256], numstr[100], symcode[80];
int i, j, k, stat, align, accum;
int npoints, result, nalt, altlist[MAXALT+2], anyvalid, realrow, sortopt;
int yfield, xfield, ptlabelfield;
int stairstep, donumbers, dopoints, instancemode, gapmissing, ingap, clipping, firstpt, groupmode, relax_xrange, fillmode;

char *linedetails, *labeldetails, *shownums, *numstrfmt, *pointsym, *ptlabeldetails, *fillcolor;
char *selectex, *forcelastx, *legsamptyp, *altsym, *altwhen;
char linelabel[256], legendlabel[256];

double adjx, adjy, linestart, linestop, x, y, lastseglen, lastx, lasty, radius, ptlblstart, ptlblstop, sob, linxstart;
double f, sum, cr, firstx, firsty, typical_interval;
double fillbleed;

TDH_errprog( "pl proc lineplot" );

/* initialize */
yfield = -1; xfield = -1;
ptlabelfield = 0;
accum = 0; stairstep = 0; fillmode = 0; instancemode = 0; groupmode = 0; 
gapmissing = 0; clipping = 0; firstpt = 0; sortopt = 0; relax_xrange = 0;
labeldetails = ""; shownums = ""; ptlabeldetails = ""; linedetails = ""; pointsym = "";
selectex = ""; forcelastx = ""; altsym = ""; altwhen = "";
strcpy( linelabel, "" );
strcpy( legendlabel, "" );
numstrfmt = "%g";
fillcolor = "gray(0.8)";
legsamptyp = "symbol";
linestart = EDXlo; linestop = EDXhi;
ptlblstart = EDXlo; ptlblstop = EDXhi;
linxstart = EDXlo;
sob = 0.0;
lastseglen = 0.0;
typical_interval = -99.0;
fillbleed = 0.0;



/* get attributes.. */
first = 1;
while( 1 ) {
	line = getnextattr( first, attr, &lvp );
	if( line == NULL ) break;
	first = 0;
	lineval = &line[lvp];

	if( strcmp( attr, "yfield" )==0 ) yfield = fref( lineval ) - 1;
	else if( strcmp( attr, "xfield" )==0 ) xfield = fref( lineval ) - 1;
	else if( strcmp( attr, "ptlabelfield" )==0 ) ptlabelfield = fref( lineval ) - 1; 
	else if( strcmp( attr, "linedetails" )==0 ) linedetails = lineval;
	else if( strcmp( attr, "label" )==0 ) { 
		tokncpy( linelabel, lineval, 256 ); 
		convertnl( linelabel );   /* linelabel[256] */
		}
	else if( strcmp( attr, "labeldetails" )==0 ) labeldetails = lineval;
	else if( strcmp( attr, "legendlabel" )==0 ) { strncpy( legendlabel, lineval, 255 ); legendlabel[255] = '\0'; }
	else if( strcmp( attr, "linerange" )==0 ) getrange( lineval, &linestart, &linestop, 'x', EDXlo, EDXhi );
	else if( strcmp( attr, "xstart" )==0 ) { linxstart = Econv( X, lineval ); if( Econv_error() ) linxstart = EDXlo; }
	else if( strcmp( attr, "firstpoint" )==0 ) {
		char xstr[80], ystr[80];
		sscanf( lineval, "%s %s", xstr, ystr );
		firstpt = 1;
		firstx = Econv( X, xstr); if( Econv_error() ) firstpt = 0;
		firsty = Econv( X, ystr ); if( Econv_error() ) firstpt = 0;
		}
	else if( strcmp( attr, "accum" )==0 ) accum = getyn( lineval );
	else if( strcmp( attr, "stairstep" )==0 ) stairstep = getyn( lineval );
	else if( strcmp( attr, "instancemode" )==0 ) instancemode = getyn( lineval );
	else if( strcmp( attr, "groupmode" )==0 ) groupmode = getyn( lineval );
	else if( strcmp( attr, "gapmissing" )==0 ) {
		if( strncmp( lineval, "y", 1 )==0 ) gapmissing = 1;
		else if( strcmp( lineval, "small" )==0 ) gapmissing = 2;
		else if( strcmp( lineval, "auto" )==0 ) gapmissing = 3;
		else if( strcmp( lineval, "autosmall" )==0 ) gapmissing = 4;
		else if( strcmp( lineval, "autozero" )==0 ) gapmissing = 5;
		else gapmissing = 0;
		}
	else if( strcmp( attr, "clip" )==0 ) clipping = getyn( lineval );
	else if( strcmp( attr, "sort" )==0 ) sortopt = getyn( lineval );
	else if( strcmp( attr, "relax_xrange" ) == 0 ) relax_xrange = getyn( lineval );
	else if( strcmp( attr, "lastseglen" )==0 ) Elenex( lineval, X, &lastseglen );
	else if( strcmp( attr, "numbers" )==0 ) shownums = lineval;
	else if( strcmp( attr, "numbersformat" )==0 ) numstrfmt = lineval;
	else if( strcmp( attr, "select" )==0 ) selectex = lineval;
	else if( strcmp( attr, "altsymbol" )==0 ) altsym = lineval;
	else if( strcmp( attr, "altwhen" )==0 ) altwhen = lineval;
	else if( strcmp( attr, "lastx" )==0 ) forcelastx = lineval;
	else if( strcmp( attr, "pointsymbol" )==0 ) pointsym = lineval;
	else if( strcmp( attr, "ptlabeldetails" )==0 ) ptlabeldetails = lineval;
	else if( strcmp( attr, "ptlabelrange" )==0 ) {
		if( strcmp( lineval, "last" )==0 ) ptlblstart = EDXhi-0.1;
		else if( strcmp( lineval, "first" )==0 ) { ptlblstart = EDXlo; ptlblstop = EDXhi-0.000001; }
		else getrange( lineval, &ptlblstart, &ptlblstop, 'x', EDXlo, EDXhi );
		}
	else if( strcmp( attr, "stairoverbars" )==0 ) {
		if( strncmp( lineval, "y", 1 )==0 ) { sob = 0.5; stairstep = 1; /* implied */ }
		else sob = 0.0;
		}
	else if( strcmp( attr, "fill" )==0 ) { 
		fillcolor = lineval;
		if( fillcolor[0] != '\0' ) fillmode = 1;
		else fillmode = 0;
		}
	else if( strcmp( attr, "fillbleed" )==0 ) fillbleed = ftokncpy( lineval );
	else if( strcmp( attr, "legendsampletype" )==0 ) legsamptyp = lineval;
	else Eerr( 1, "attribute not recognized", attr );
	}


/* overrides and degenerate cases */
/* -------------------------- */
if( Nrecords < 1 ) return( Eerr( 17, "No data has been read yet w/ proc getdata", "" ) );
if( !scalebeenset() ) 
         return( Eerr( 51, "No scaled plotting area has been defined yet w/ proc areadef", "" ) );


if( (yfield < 0 || yfield >= Nfields ) && !instancemode ) return( Eerr( 601, "yfield out of range", "" ) );
if( xfield >= Nfields ) return( Eerr( 601, "xfield out of range", "" ) );
if( yfield >= 0 && instancemode ) {
	Eerr( 4729, "warning, turning instancemode off since yfield specified", "" );
	instancemode = 0;
	}
 
if( groupmode && ptlabelfield ) {
	Eerr( 4729, "warning, turning ptlabelfield off since groupmode specified", "" );
	ptlabelfield = 0;
	}
	
if( strncmp( legendlabel, "#usefname", 9 )==0 ) {
	if( instancemode ) getfname( xfield+1, legendlabel );  /* legendlabel[256] */
	else getfname( yfield+1, legendlabel );  /* legendlabel[256] */
	}



/* now do the plotting work.. */
/* -------------------------- */

donumbers = 1;
if( GL_slmember( shownums, "no*" ) || shownums[0] == '\0' ) donumbers = 0;

dopoints = 1;
if( GL_slmember( pointsym, "no*" ) || pointsym[0] == '\0' ) dopoints = 0;


/* put all values into PLV vector, doing accumulation if required.. */
j = 0;
f = linxstart;
nalt = 0;

for( i = 0; i < Nrecords; i++ ) {

	if( selectex[0] != '\0' ) { /* process against selection condition if any.. */
                stat = do_select( selectex, i, &result );
                if( stat != 0 ) { Eerr( stat, "Select error", selectex ); continue; }
                if( result == 0 ) continue; /* reject */
                }
	if( altwhen[0] != '\0' ) { /* check altwhen condition.. */
                stat = do_select( altwhen, i, &result );
                if( stat != 0 ) { Eerr( stat, "Select error", altwhen ); continue; }
                if( result == 1 && nalt < MAXALT ) {
			/* altlist[nalt] = j/2; */
			altlist[nalt] = j/3;
			nalt++;
			}
                }
		


	/* X */
	if( xfield < 0 ) {
		PLV[j] = f + sob;
		f += 1.0;
		}
	else 	{
		PLV[j] = fda( i, xfield, X ) + sob;
		if( Econv_error() ) { 
			conv_msg( i, xfield, "xfield" ); 
			continue;
			}
		}

	j++; 

	/* Y */
	if( instancemode ) PLV[j] = 1.0;
	else 	{
		PLV[j] = fda( i, yfield, Y );
		if( Econv_error() ) { 
			conv_msg( i, yfield, "yfield" ); 
			PLV[j] = NEGHUGE;
			/* continue; removed scg 5/19/99 */
			}
		}

	j++;

	PLV[j] = (double)i;
	j++;

	if( j >= PLVsize-1 ) {
		Eerr( 3579, "Sorry, too many curve points, curve truncated (raise using -maxvector)", "" );
		break;
		}
	}

npoints = j / 3;


/* sort if required.. */  /* added 4/22/02 */
if( sortopt ) {
        if( PLS.debug ) fprintf( PLS.diagfp, "sorting points for line\n" );
        qsort( PLV, npoints, sizeof(double)*3, dblcompare );
        }



/* process for groupmode.. */
if( groupmode && xfield >= 0 ) for( i = 0; i < npoints; i++ ) {

	for( k = i+1; k < npoints; k++ ) {
                		
		if( dat3d(i,0) == dat3d(k,0) ) {
			if( instancemode ) y = 1.0;
			else y = dat3d( k, 1 );
			dat3d( k, 1 ) = NEGHUGE; /* rub out the additional instance.. */
			if( y > NEGHUGE+1 ) (dat3d( i, 1 )) += (y);
			}
		else 	{
			i = k-1;     /* back off.. */
			break;
			}
		}
	}
/* fprintf( stderr, "after grouping\n" );
 * for( i = 0; i < npoints; i++ ) fprintf( stderr, "%g %g %g\n", dat3d(i,0), dat3d(i,1), dat3d(i,2 ) );
 */


/* process for accum.. */
if( accum ) {
	sum = 0.0;
	for( i = 0; i < npoints; i++ ) {

		if( dat3d( i, 1 ) > (NEGHUGE+1) ) {
			sum += (dat3d( i, 1 )); 
			(dat3d( i, 1 )) = sum;
			}
		}
   	}

/* fprintf( stderr, "after accum\n" );
 * for( i = 0; i < npoints; i++ ) fprintf( stderr, "%g %g %g\n", dat3d(i,0), dat3d(i,1), dat3d(i,2 ) );
 */



/* draw the curve.. */
/* ---------------- */

/* set line parameters */
linedet( "linedetails", linedetails, 1.0 );

if( fillmode ) {
	Ecolor( fillcolor ); /* scg 6/18/04 */
	}
	

first = 1;
lasty = 0.0;
lastx = 0.0;
anyvalid = 0;
ingap = 0;
cr = Elimit( Y, 'l', 's' );
for( i = 0; i < npoints; i++ ) {
	if( !first && (y > (NEGHUGE+1) && x > (NEGHUGE+1) ) ) { lasty = y; lastx = x; }

	if( first && firstpt ) { 
		x = firstx; 
		y = firsty; 
		}
	else	{
		x = dat3d(i,0);
		y = dat3d(i,1);
		}
	
	if( x < (NEGHUGE+1) || y < (NEGHUGE+1) ) {
		if( gapmissing ) ingap = 1;
		continue; /* skip bad values */
		}
	
	if( x < linestart && !relax_xrange ) continue; /* X out of range - lo */ 
	if( x > linestop && !relax_xrange ) {          /* X out of range - hi */ 
		x = lastx; /* back up to last in-range point so last stairtstep is correct*/
		y = lasty;
		break; 
		}
	if( !first && ( gapmissing == 3 || gapmissing == 4 || gapmissing == 5 )) {
		if( typical_interval < 0.0 ) typical_interval = (x - lastx)*1.01;
		else if( x - lastx > typical_interval ) {
			/* if( gapmissing == 5 ) { Elinu( lastx+typical_interval, 0.0 ); Elinu( x-typical_interval, 0.0 ); } */
			if( gapmissing == 5 ) { Elinu( lastx, 0.0 ); Elinu( x, 0.0 ); Elinu( x, y ); }  /* changed, scg 11/20/07 */
			else ingap = 1;
			}
		}

	if( !anyvalid && !Ef_inr( Y, y ) ) continue; /* 1/9/03 scg - anyvalid should not become 1 if out of range in Y */

	anyvalid = 1;
	if( first ) {
		Emovu( x, y );
		setfloatvar( "XSTART", x, "%g" );
		setfloatvar( "YSTART", y, "%g" );
		first = 0;
		ingap = 0;
		continue;
		}
	if( !first && fillmode && !ingap ) {
		Emovu( x, cr ); 
		Epath( Eax( lastx )-fillbleed, Eay( cr ) );  /* -0.005 added to remove tiny gap artifact seen on EPS ... scg 1/9/08 */
		Epath( Eax( lastx )-fillbleed, Eay( lasty ) );
		/* was: Epathu( lastx, cr );  */
		/*      Epathu( lastx, lasty );  */ 

		if( stairstep ) Epathu( x, lasty );
		else Epathu( x, y );
		Ecolor( fillcolor ); /* added scg 11/2/07 - color bug related to gapmissing=2 and fill=1 */
		Efill();
		}
	if( ( gapmissing == 2 || gapmissing ==4 ) && ingap ) {        /* do a quarter-length nib at previous location */
		double nib;
		Emovu( lastx, lasty );
		nib = (x-lastx) * 0.25;
		if( fillmode ) Ecblock( Eax(lastx), Eay(cr), Eax(lastx+nib), Eay(lasty), fillcolor, 0 ); 
		else Elinu( lastx+nib, lasty );
		}
	if( ! fillmode ) { 
		if( ingap ) Emovu( x, y ); 	
		if( stairstep && x > linestart && !ingap ) Elinu( x, lasty ); 
		if( stairstep && x == linestart ) Emovu( x, y ); 
		if( clipping && !ingap && !stairstep ) {
			double cx1, cy1, cx2, cy2;
			cx1 = lastx; cy1 = lasty; cx2 = x; cy2 = y;
			stat = Elineclip( &cx1, &cy1, &cx2, &cy2, EDXlo, EDYlo, EDXhi, EDYhi );
			if( !stat ) { Emovu( cx1, cy1 ); Elinu( cx2, cy2 ); }
			}
		else Elinu( x, y );
		}
	ingap = 0;
	}

if( !anyvalid ) { /* no plottable data points.. exit */  
	/* Ecolor( oldcolor ); */ /* don't do color chg - scg 5/10/05 */
	return( 0 ); 
	} 

/* if last point was invalid, back up to most recent valid point.. */
if( x < (NEGHUGE+1) || y < (NEGHUGE+1) ) { x = lastx; y = lasty; }


/* handle last segment of stairstep.. */
/* if( stairstep ) { */ /* } changed to allow lastseglen to be used anytime, scg 12/13/01 */
if( lastseglen > 0.0 ) {
	if( x < (NEGHUGE+1) || y < (NEGHUGE+1) ) { x = lastx; y = lasty; }
	lastx = Eax( x ) + lastseglen;
	if( fillmode ) {
		Emov( Eax(x), Eay(cr) ); Epath( lastx, Eay(cr) ); 
		Epath( lastx, Eay(y) ); Epath( Eax(x), Eay(y) );
		/* Ecolorfill( fillcolor ); */ /* using Efill .. scg 6/18/04 */
		Efill();
		}
	else 	Elin( lastx, Eay( y ) );
	}
else lastx = Eax(x);

if( forcelastx[0] != '\0' ) {
	lastx = Eax( Econv( X, forcelastx ) );
	if( !Econv_error() && anyvalid ) Elin( lastx, Eay( y ) );
	}


/* set YFINAL and XFINAL */
Euprint( numstr, Y, y, numstrfmt ); /* numstr[100] */
setcharvar( "YFINAL", numstr );
Euprint( numstr, X, Edx(lastx), "" ); /* numstr[100] */
setcharvar( "XFINAL", numstr );
	





/* do points, labels, etc. */
/* ----------------------- */
for( i = 0; i < npoints; i++ ) {
	x = dat3d(i,0);
	y = dat3d(i,1);


	if( x < (NEGHUGE+1) || y < (NEGHUGE+1) ) continue; /* skip bad values */

	if( x < linestart && !relax_xrange ) continue; /* out of range - lo */
	if( x > linestop && !relax_xrange ) {          /* out of range - hi */
		break; 
		}

	if( clipping && !stairstep && !fillmode ) {
		/* if clipping, suppress points or labels that are outside the plotting area */
		if( x < EDXlo || x > EDXhi || y < EDYlo || y > EDYhi ) continue;
		}

	lasty = y;

	if( x >= ptlblstart && x <= ptlblstop ) {
		if( donumbers && stairstep ) {
			if( i == npoints-1 || GL_close_to( x, linestop, 0.01 ) ) {  
				double xls;
				xls = Edx(lastseglen) - Edx(0.0);
				placenum( shownums, x, x + xls, y, numstrfmt, linedetails );
				}
			else placenum( shownums, x, dat3d(i+1,0), y, numstrfmt, linedetails );
			}
		else if( donumbers && !stairstep ) 
			  placenum( shownums, x, x, y, numstrfmt, linedetails );
		if( donumbers && fillmode ) Ecolor( fillcolor ); /* scg 6/18/04 */
		}

	if( dopoints ) {
		int jj;
		/* see if this is one of the alternates.. if so use altsym rather
			than the regular symbol */
		for( jj = 0; jj < nalt; jj++ ) if( i == altlist[jj] ) break;
		if( jj != nalt ) symdet( "altsym", altsym, symcode, &radius );
		else symdet( "pointsymbol", pointsym, symcode, &radius );
		Emark( Eax(x), Eay(y), symcode, radius );
		}
	realrow = (int) dat3d(i,2);
	if( ptlabelfield && x >= ptlblstart && x <= ptlblstop ) {
		textdet( "ptlabeldetails", ptlabeldetails, &align, &adjx, &adjy, -4, "R", 1.0 );
		if( align == '?' ) align = 'C';
		Emov( Eax( x ) + adjx, Eay( y ) + adjy );
		Edotext( da( realrow, ptlabelfield ), align );
		}
	}


if( linelabel[0] != '\0' ) {
        GL_varsub( linelabel, "@YFINAL", numstr );  /* linelabel[256] */
	PL_do_subst( buf, linelabel, realrow, 0 );  /* also allow substitution of any @field on the last-plotted data row.. added scg 1/29/07 */
        textdet( "labeldetails", labeldetails, &align, &adjx, &adjy, -2, "R", 1.0 );
        if( align == '?' ) align = 'L';
        Emov( lastx+0.05+adjx, (Eay( lasty )-(Ecurtextheight*.35))+adjy );
        Edotext( buf, align );
        }
 
if( legendlabel[0] != '\0' ) {
	if( fillmode ) PL_add_legent( LEGEND_COLOR, legendlabel, "", fillcolor, "", "" );
	else if( pointsym[0] != '\0' && strcmp( pointsym, "none" )!= 0 ) {
		if( legsamptyp[0] == 's' && strlen( legsamptyp ) <= 6 )
			PL_add_legent( LEGEND_SYMBOL, legendlabel, "", pointsym, "", "" );
		else
			PL_add_legent( LEGEND_LINE+LEGEND_SYMBOL, legendlabel, "", linedetails, pointsym, "" );
		}
	else PL_add_legent( LEGEND_LINE, legendlabel, "", linedetails, "", "" );
	}

/* if( fillmode ) Ecolor( oldcolor ); */ /* restore */
return( 0 );
}

/* ----------------------- */
/* place one line plot number  */
static int
placenum( shownums, x1, x2, y, numstrfmt, linedetails )
char *shownums;
double x1, x2, y;
char *numstrfmt, *linedetails;
{
char numstr[100];
int align;
double adjx, adjy;

/* change to text color, size, etc.. */
textdet( "numbers", shownums, &align, &adjx, &adjy, -4, "R", 1.0 );
if( align == '?' ) align = 'C';
Emov( Eax( (x1+x2)/2.0 ) + adjx, Eay(y)+0.02+adjy );
/* sprintf( numstr, numstrfmt, y ); */
Euprint( numstr, 'y', y, numstrfmt ); /* numstr[100] */
Edotext( numstr, align );
linedet( "linedetails", linedetails, 1.0 );
Emovu( x1, y ); /* restore old position for drawing the curve.. */
return( 0 );
}

/* ------------------------- */
static int
dblcompare( a, b )
const void *a, *b;
{
double *f, *g;
f = (double *)a;
g = (double *)b;

if( *f > *g ) return( 1 );
if( *f < *g ) return( -1 );
return( 0 );
}


/* ======================================================= *
 * Copyright 1998-2005 Stephen C. Grubb                    *
 * http://ploticus.sourceforge.net                         *
 * Covered by GPL; see the file ./Copyright for details.   *
 * ======================================================= */
