/* ======================================================= *
* Copyright 1998-2005 Stephen C. Grubb *
* http://ploticus.sourceforge.net *
* Covered by GPL; see the file ./Copyright for details. *
* ======================================================= */
#include "pl.h"
#define MAXFIELDS 50
int
PLP_autorange( axis, specline, minval, maxval )
char axis;
char *specline; /* spec line from proc areadef.. */
char *minval; /* determined plot minima.. */
char *maxval; /* determined plot maxima.. */
{
int i, j;
int df[MAXFIELDS];
int ndf;
char nearest[80];
char buf[256];
char dfield[256];
double min, max, fmod();
char smin[80], smax[80];
double fval;
int stat;
double margin;
int ix;
char lowfix[80], hifix[80];
char unittyp[80];
char floatformat[20];
/* char datepart[40], timepart[40]; */
double incmult;
char tok[80];
char selex[256]; /* added */
int selresult; /* added */
int combomode, first;
double hiaccum, loaccum;
int goodfound;
double submin, submax;
double fabs(), floor();
int mininit, maxinit;
int ffgiven;
/* see what scaletype is being used.. */
Egetunits( axis, unittyp );
if( strcmp( unittyp, "linear" )==0 ) strcpy( nearest, "auto" );
else if ( GL_smemberi( unittyp, "date time datetime" )) strcpy( nearest, "datematic" );
else strcpy( nearest, "exact" ); /* categories? */
margin = 0.0;
strcpy( dfield, "" );
ix = 0;
strcpy( lowfix, "" );
strcpy( hifix, "" );
strcpy( floatformat, "%g" ); /* changed scg 10/1/03 .. scientific formats (e+ and e-) should be ok now */
ffgiven = 0;
incmult = 1.0;
strcpy( selex, "" ); /* added */
combomode = 0;
mininit = maxinit = 0;
while( 1 ) {
strcpy( buf, GL_getok( specline, &ix ) );
if( buf[0] == '\0' ) break;
if( strnicmp( buf, "datafields", 10 )==0 ) strcpy( dfield, &buf[11] );
else if( strnicmp( buf, "datafield", 9 )==0 ) strcpy( dfield, &buf[10] );
else if( strnicmp( buf, "incmult", 7 )==0 ) incmult = atof( &buf[8] );
else if( strnicmp( buf, "nearest", 7 )==0 ) strcpy( nearest, &buf[8] );
else if( strnicmp( buf, "margin", 6 )==0 ) margin = atof( &buf[7] );
else if( strnicmp( buf, "lowfix", 6 )==0 ) strcpy( lowfix, &buf[7] );
else if( strnicmp( buf, "hifix", 5 )==0 ) strcpy( hifix, &buf[6] );
else if( strnicmp( buf, "mininit", 7 )==0 ) { strcpy( lowfix, &buf[8] ); mininit = 1; }
else if( strnicmp( buf, "maxinit", 7 )==0 ) { strcpy( hifix, &buf[8] ); maxinit = 1; }
else if( strnicmp( buf, "numformat", 9 )==0 ) { strcpy( floatformat, &buf[10] ); ffgiven = 1; }
else if( strnicmp( buf, "combomode", 9 )==0 ) {
if( stricmp( &buf[10], "stack" )==0 ) combomode = 1;
else if( stricmp( &buf[10], "hilo" )==0 ) combomode = 2;
else combomode = 0;
}
else if( strnicmp( buf, "selectrows", 10 )==0 ) {
strcpy( selex, &buf[11] );
strcat( selex, &specline[ix] );
break;
}
else Eerr( 5702, "unrecognized autorange subattribute", buf );
}
/* fill array of df from comma-delimited list - contributed by Paul Totten <pwtotten@nortelnetworks.com> */
ndf = 0; ix = 0;
while( 1 ) {
GL_getseg( tok, dfield, &ix, "," );
if( tok[0] == '\0' ) break;
if( ndf >= (MAXFIELDS-1) ) break;
df[ndf] = fref( tok ) - 1;
if( df[ndf] < 0 || df[ndf] >= Nfields ) continue;
ndf++;
}
/* df = fref( dfield ) - 1; */
if( Nrecords < 1 ) return( Eerr( 17, "autorange: no data set has been read/specified w/ proc getdata", "" ) );
if( axis == '\0' ) return( Eerr( 7194, "autorange: axis attribute must be specified", "" ) );
if( ndf == 0 ) return( Eerr( 7194, "autorange: datafield omitted or invalid ", dfield ) );
/* ------------------ */
/* now do the work.. */
/* ----------------- */
/* override.. */
/* if( stricmp( nearest, "day" )==0 && stricmp( unittyp, "date" )==0 ) strcpy( nearest, "exact" ); */ /* removed scg 4/19/05 */
/* find data min and max.. */
/* initialize.. */
if( mininit ) min = Econv( axis, lowfix );
else min = PLHUGE;
if( maxinit ) max = Econv( axis, hifix );
else max = NEGHUGE;
for( i = 0; i < Nrecords; i++ ) {
if( selex[0] != '\0' ) { /* added scg 8/1/01 */
stat = do_select( selex, i, &selresult );
if( selresult == 0 ) continue;
}
hiaccum = 0.0; loaccum = 0.0; first = 1; goodfound = 0; submin = PLHUGE; submax = NEGHUGE;
for( j = 0; j < ndf; j++ ) { /* for all datafields to be examined.. */
fval = fda( i, df[j], axis );
if( Econv_error() ) continue;
goodfound = 1;
if( !combomode ) {
if( fval < submin ) submin = fval;
if( fval > submax ) submax = fval;
}
else {
hiaccum += fval;
if( combomode == 2 ) { /* hilo */
if( first ) { loaccum = fval; first = 0; }
else loaccum -= fval;
}
else { /* stack */
if( first ) { loaccum = fval; first = 0; }
}
}
}
if( !goodfound ) continue;
if( combomode ) {
if( loaccum < min ) min = loaccum;
if( hiaccum > max ) max = hiaccum;
}
else {
if( submin < min ) min = submin;
if( submax > max ) max = submax;
}
}
/* If user didn't specify "numformat", try to be "smart" about whether to use %g or %f for building result.. *
* %g is usually best but in certain cases (high magnitude low variance data) %f should be used. Added scg 8/10/05. */
if( !ffgiven && fabs( min ) > 100000 && fabs( max ) > 100000 && ( max - min < 1000 )) strcpy( floatformat, "%f" );
/* now convert min and max to current units and then to nearest interval.. */
if( strcmp( unittyp, "linear" )==0 ) { /* avoid using Euprint()- trouble w/ v. big or v. small #s */
sprintf( smin, floatformat, min );
sprintf( smax, floatformat, max );
}
else {
Euprint( smin, axis, min, "" );
Euprint( smax, axis, max, "" );
}
/* save data min/max.. */
if( axis == 'x' ) {
setcharvar( "DATAXMIN", smin );
setcharvar( "DATAXMAX", smax );
}
else {
setcharvar( "DATAYMIN", smin );
setcharvar( "DATAYMAX", smax );
}
/* now adjust for margin.. */
min -= margin;
max += margin;
/* degenerate case.. all data the same (bad if it happens to lie on inc boundary, eg: 0) - added scg 9/21/01 */
if( min == max ) {
/* min = min - 1.0; max = max + 1.0; */
min -= fabs(min*0.2); max += fabs(max*0.2); /* changed to work better w/ small magnitude values - scg 3/3/05 */
/* now uses fabs().. to handle degen case of a single neg. data value- scg 7/31/05 */
if( min == max ) { min = min - 1.0; max = max + 1.0; } /* this kicks in if min=0 and max=0 - scg 7/6/05 */
}
/* and do the conversion with margin.. */
if( strcmp( unittyp, "linear" )==0 ) { /* avoid using Euprint()- trouble w/ v. big or v. small #s */
sprintf( smin, floatformat, min );
sprintf( smax, floatformat, max );
}
else {
Euprint( smin, axis, min, "" );
Euprint( smax, axis, max, "" );
}
/******* handle nearest= ***********/
if( GL_slmember( nearest, "dat*matic" ) ) {
char foo1[40], foo2[40], foo3[40], foo4[40], foo5[40], foo6[40];
double dfoo1, dfoo2;
/* get an automatic reasonable "nearest" value.. */
DT_reasonable( unittyp, min, max, &dfoo1, foo1, foo2, foo3, foo4, foo5, &dfoo2, foo6, nearest );
}
if( stricmp( nearest, "exact" )==0 ) { /* exact */
sprintf( minval, "%s", smin );
sprintf( maxval, "%s", smax );
}
else if( PLP_findnearest( smin, smax, axis, nearest, minval, maxval ) );
#ifdef CUT
else if( strnicmp( nearest, "month", 5 )== 0 || strnicmp( nearest, "quarter", 7 )==0 || strnicmp( nearest, "3month", 6 )==0 ) {
/* nearest month boundary / quarter-year boundary.. */
int mon, day, yr, newmon;
long l;
if( !GL_smember( unittyp, "date datetime" ))
Eerr( 2892, "autorange 'nearest=month' or 'nearest=quarter' only valid with date or datetime scaletype", unittyp );
/* min */
stat = DT_jdate( smin, &l );
DT_getmdy( &mon, &day, &yr );
if( tolower( nearest[0] ) == 'q' || nearest[0] == '3' ) {
if( mon >= 10 ) mon = 10;
else if( mon >= 7 ) mon = 7;
else if( mon >= 4 ) mon = 4;
else if( mon >= 1 ) mon = 1;
}
DT_makedate( yr, mon, 1, "", datepart );
if( strcmp( unittyp, "datetime" )==0 ) {
DT_maketime( 0, 0, 0.0, timepart );
DT_build_dt( datepart, timepart, minval );
}
else strcpy( minval, datepart );
/* max */
stat = DT_jdate( smax, &l );
DT_getmdy( &mon, &day, &yr );
if( tolower( nearest[0] ) == 'q' || nearest[0] == '3' ) {
if( mon <= 3 ) mon = 4;
else if( mon <= 6 ) mon = 7;
else if( mon <= 9 ) mon = 10;
else if( mon <= 12 ) mon = 13;
}
else mon ++;
/* wrap around year.. */
newmon = ((mon-1) % 12 ) +1;
yr += ((mon-1) / 12);
mon = newmon;
DT_makedate( yr, mon, 1, "", datepart );
if( strcmp( unittyp, "datetime" )==0 ) {
DT_maketime( 0, 0, 0.0, timepart );
DT_build_dt( datepart, timepart, maxval );
}
else strcpy( maxval, datepart );
}
else if( strnicmp( nearest, "year", 4 )== 0 || strnicmp( nearest, "2year", 5 )==0 ||
strnicmp( nearest, "5year", 5 )==0 || strnicmp( nearest, "10year", 6 )==0 ) {
int mon, day, yr;
long l;
int yearsblock; /* 0 5 or 10 */
if( !GL_smember( unittyp, "date datetime" ))
Eerr( 2892, "autorange 'nearest=year' only valid with date or datetime scaletype", unittyp );
if( tolower( (int) nearest[0] ) != 'y' ) { /* this section scg 1/28/05 */
yearsblock = nearest[0] - '0';
if( yearsblock == 1 ) yearsblock = 10;
}
else yearsblock = 0;
/* min */
stat = DT_jdate( smin, &l );
DT_getmdy( &mon, &day, &yr );
if( yearsblock ) yr = (yr / yearsblock) * yearsblock; /* scg 1/28/05 */
DT_makedate( yr, 1, 1, "", datepart );
if( strcmp( unittyp, "datetime" )==0 ) {
DT_maketime( 0, 0, 0.0, timepart );
DT_build_dt( datepart, timepart, minval );
}
else strcpy( minval, datepart );
/* max */
stat = DT_jdate( smax, &l );
DT_getmdy( &mon, &day, &yr );
if( yearsblock ) yr = ((yr / yearsblock)+1) * yearsblock; /* scg 1/28/05 */
else yr++;
DT_makedate( yr, 1, 1, "", datepart );
if( strcmp( unittyp, "datetime" )==0 ) {
DT_maketime( 0, 0, 0.0, timepart );
DT_build_dt( datepart, timepart, maxval );
}
else strcpy( maxval, datepart );
}
else if( strnicmp( nearest, "day", 3 )== 0 || stricmp( nearest, "monday" )==0 || stricmp( nearest, "sunday" )==0 ) {
int mon, day, yr;
double days, mins;
if( !GL_smember( unittyp, "date datetime" ))
Eerr( 2892, "autorange 'nearest=day' only valid with date or datetime scaletype", unittyp );
/* min */
if( strcmp( unittyp, "datetime" )==0 ) DT_getdtparts( smin, datepart, timepart );
else strcpy( datepart, smin ); /* if and else added scg 8/10/05 */
if( tolower( nearest[0] ) == 'm' || tolower( nearest[0] ) == 's' ) { /* adjust datepart back to a monday or sunday */
int iwk; char rbuf[40];
DT_weekday( datepart, rbuf, &iwk ); /* rbuf not used */
if( tolower( nearest[0] ) == 'm' ) { if( iwk == 1 ) iwk = 8; DT_dateadd( datepart, 2 - iwk, rbuf ); }
else if( tolower( nearest[0] ) == 's' ) DT_dateadd( datepart, 1 - iwk, rbuf );
strcpy( datepart, rbuf );
}
/* this is just a way to get the dt parts (?) ...
* DT_datetime2days( smin, &days );
* DT_getmdy( &mon, &day, &yr );
* DT_makedate( yr, mon, day, "", datepart );
*/
if( strcmp( unittyp, "date" )==0 )
/* check for biz day window.. scg 7/21/04 */
mins = 0.0;
DT_frame_mins( &mins ); /* adjust to any biz day window.. */
DT_frommin( mins, timepart );
if( strcmp( unittyp, "date" )==0 ) strcpy( minval, datepart );
else DT_build_dt( datepart, timepart, minval );
/* max */
if( strcmp( unittyp, "datetime" )==0 ) DT_getdtparts( smax, datepart, timepart );
else strcpy( datepart, smax ); /* if and else added scg 8/10/05 */
if( tolower( nearest[0] ) == 'm' || tolower( nearest[0] ) == 's' ) { /* adjust datepart to next monday or sunday */
int iwk; char rbuf[40];
DT_weekday( datepart, rbuf, &iwk ); /* rbuf not used */
if( tolower( nearest[0] ) == 'm' ) { if( iwk == 1 ) iwk = 8; DT_dateadd( datepart, 9 - iwk, rbuf ); }
else if( tolower( nearest[0] ) == 's' ) DT_dateadd( datepart, 8 - iwk, rbuf );
DT_build_dt( rbuf, timepart, smax );
}
DT_datetime2days( smax, &days );
if( fabs( days - floor( days )) < 0.0001 ) ; /* avoid spurious extra day when data max is on date boundary added scg 7/21/04 */
else days++;
DT_days2datetime( days, smax );
DT_datetime2days( smax, &days ); /* set next day's date for getmdy below */
DT_getmdy( &mon, &day, &yr );
DT_makedate( yr, mon, day, "", datepart );
DT_maketime( 0, 0, 0.0, timepart );
if( strcmp( unittyp, "date" )==0 ) strcpy( maxval, datepart );
else DT_build_dt( datepart, timepart, maxval );
}
else if( strnicmp( nearest, "hour", 4 )== 0 || strnicmp( nearest, "3hour", 5 )==0 ||
strnicmp( nearest, "6hour", 5 )==0 || strnicmp( nearest, "12hour", 6 )==0 ) {
int hour, minute;
double sec;
int hoursblock; /* 0, 3, 6, or 12 */
if( !GL_smember( unittyp, "time datetime" ))
Eerr( 2892, "autorange 'nearest=hour' is incompatible with scaletype", unittyp );
if( tolower( (int) nearest[0] ) != 'h' ) { /* this section scg 1/28/05 */
hoursblock = nearest[0] - '0';
if( hoursblock == 1 ) hoursblock = 12;
}
else hoursblock = 0;
if( strcmp( unittyp, "time" )==0 ) {
/* min */
DT_tomin( smin, &sec ); /* sec not used */
DT_gethms( &hour, &minute, &sec );
if( hoursblock ) hour = (hour / hoursblock) * hoursblock; /* scg 1/28/05 */
DT_maketime( hour, 0, 0.0, minval );
/* max */
DT_tomin( smax, &sec ); /* sec not used */
DT_gethms( &hour, &minute, &sec );
if( hoursblock ) hour = ((hour / hoursblock)+1) * hoursblock; /* scg 1/28/05 */
if( minute != 0 || sec != 0.0 ) hour++; /* bug, scg 12/13/05 */
DT_maketime( hour, 0, 0.0, maxval );
}
else if( strcmp( unittyp, "datetime" )==0 ) {
double days;
int mon, day, yr;
/* min */
DT_datetime2days( smin, &days );
/* time part */
DT_gethms( &hour, &minute, &sec );
if( hoursblock ) hour = (hour / hoursblock) * hoursblock; /* scg 1/28/05 */
DT_maketime( hour, 0, 0.0, timepart );
/* date part */
DT_getmdy( &mon, &day, &yr );
DT_makedate( yr, mon, day, "", datepart );
DT_build_dt( datepart, timepart, minval );
/* max */
DT_datetime2days( smax, &days );
/* time part */
DT_gethms( &hour, &minute, &sec );
if( hour == 23 ) {
DT_days2datetime( days+1.0, smax ); /* set next day's date for getmdy below*/
DT_datetime2days( smax, &days ); /* set next day's date for getmdy below*/
DT_maketime( 0, 0, 0.0, timepart ); /* ok for any hoursblock */
}
else {
if( hoursblock ) hour = ((hour / hoursblock)+1) * hoursblock; /* scg 1/28/05 */
else hour++;
DT_maketime( hour, 0, 0.0, timepart );
}
/* date part */
DT_getmdy( &mon, &day, &yr );
DT_makedate( yr, mon, day, "", datepart );
DT_build_dt( datepart, timepart, maxval );
}
}
else if( stricmp( nearest, "minute" )==0 || stricmp( nearest, "10minute" )==0 ||
stricmp( nearest, "20minute" )==0 || stricmp( nearest, "30minute" )==0 ) { /* this section scg 1/28/05 */
int hour, minute, minblock;
double sec;
if( strcmp( unittyp, "time" )!= 0 ) Eerr( 2892, "autorange 'nearest=minute' is incompatible with scaletype", unittyp );
if( tolower( (int) nearest[0] ) != 'm' ) minblock = (nearest[0] - '0') * 10;
else minblock = 0;
/* min */
DT_tomin( smin, &sec ); /* sec not used */
DT_gethms( &hour, &minute, &sec );
if( minblock ) minute = (minute / minblock) * minblock;
DT_maketime( hour, minute, 0.0, minval );
/* max */
DT_tomin( smax, &sec ); /* sec not used */
DT_gethms( &hour, &minute, &sec );
if( minblock ) minute = ((minute / minblock)+1) * minblock;
else minute++;
if( minute >= 60 ) { minute = minute % 60; hour++; }
DT_maketime( hour, minute, 0.0, maxval );
}
#endif
else { /* this section added scg 7/5/00 */
double inc, h, fmod(), a, b;
if( strcmp( nearest, "auto" )==0 ) PL_defaultinc( min, max, &inc );
else inc = atof( nearest );
h = fmod( min, inc );
a = (min - h) - inc;
b = a - inc; /* include one extra inc on low end */
if( a >= 0.0 && b < 0.0 ) b = a; /* but don't dip below 0 */
if( min < 0.0 ) sprintf( minval, floatformat, (min - h) - (inc * incmult) ); /* include extra inc on low end - scg 11/29/00 */
else {
a = min - h;
b = a - (inc*(incmult-1.0)); /* include extra inc on low end - 11/29 */
if( a >= 0.0 && b < 0.0 ) b = a; /* but don't dip below 0 - 11/29 */
sprintf( minval, floatformat, b );
}
h = fmod( max, inc );
if( max < 0.0 ) sprintf( maxval, floatformat, (max - h) + (inc*(incmult-1.0)) ); /* include extra inc on high end - 11/29 */
else sprintf( maxval, floatformat, (max - h) + (inc * incmult) ); /* extra inc - 11/29 */
}
/* lowfix and hifix overrides.. */
if( lowfix[0] != '\0' && !mininit ) strcpy( minval, lowfix );
if( hifix[0] != '\0' && !maxinit ) strcpy( maxval, hifix );
/* be sure result makes sense.. for instance, if lowfix=0 specified but data are all negative,
min will be 0 but max will be eg. -3.... patch that up here to avoid areadef crash&burn
(automated situations) - scg 3/25/04 */
if( GL_slmember( unittyp, "linear log*" ) && atof( maxval ) <= atof( minval ) ) {
Eerr( 5709, "autorange: all data out of range", "" );
strcpy( minval, "0" );
strcpy( maxval, "1" );
}
if( PLS.debug )
fprintf( PLS.diagfp, "Autorange on %c: min=%s to max=%s\n", axis, minval, maxval);
DT_suppress_twin_warn( 1 ); /* suppress complaints about datetime outside of window
until after areadef */
return( 0 );
}
/* ======================================================= *
* Copyright 1998-2005 Stephen C. Grubb *
* http://ploticus.sourceforge.net *
* Covered by GPL; see the file ./Copyright for details. *
* ======================================================= */
syntax highlighted by Code2HTML, v. 0.9.1