plt - Software for 2D Plots 2.5

File: <base>/plt/src/axis.c (12,792 bytes)
/* file: axis.c		Paul Albrecht		September 1984
			Last revised:	      14 November 2002
Axis and grid functions for plt

Copyright (C) Paul Albrecht 1988

Recent changes (by George Moody, george@mit.edu):
  12 April 2001: "-g ygrid -s x" now works; general cleanup
  21 October 2002: moved formerly global variables here from plt.h
  14 November 2002: fixed missing cast in azmem call
_______________________________________________________________________________
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.  See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA.

You may contact the maintainer by e-mail (george@mit.edu) or postal mail
(MIT Room E25-505A, Cambridge, MA 02139 USA).  For updates to this software,
please visit PhysioNet (http://www.physionet.org/).
_______________________________________________________________________________
*/

#include "plt.h"

#define	NO_TICK		0
#define	IN_OUT_TICK	01
#define	IN_TICK		02
#define	OUT_TICK		03
#define	IN_OUT_MASK	03
#define	GRID		04
#define	SYMMETRIC	010
#define	SUB_GRID	020
#define	XGRID		040
#define	YGRID		0100

typedef	struct {
    AxisPtr a;
    double tick;
    double scl;
    char *lbl;
} *TickPtr;

static int gtype = OUT_TICK;

/* Prototypes of functions defined in this module */
void SetupAxes(void);
void AxisInit(Mode mode);
void XAxisDraw(void);
void YAxisDraw(void);
void TickDef(AxisPtr a, double tick, char *lbl, double scl, Boolean override);

static void AxisInitOne(AxisPtr a);
static void MinmaxSetup(AxisPtr a);
static void AxisSetup(AxisPtr a, AxisPtr oa);
static void LinearTickSetup(AxisPtr a);
static void LogTickSetup(AxisPtr a);
static void GridSetup(void);
static void GridDraw(AxisPtr a, AxisPtr oa);
static Ptype TickDrawAxis(AxisPtr a);
static int TickDraw(TickPtr t, Ptype ticklen);
static void TickLabel(TickPtr t, int off);

/* Public functions */

void SetupAxes(void)
{
    char xtitle[200];

    xwmin = p->xfull * ((xwmins == DEFAULT) ? p->xwmins : xwmins);
    xwmax = p->xfull * ((xwmaxs == DEFAULT) ? p->xwmaxs : xwmaxs);
    ywmin = p->yfull * ((ywmins == DEFAULT) ? p->ywmins : ywmins);
    ywmax = p->yfull * ((ywmaxs == DEFAULT) ? p->ywmaxs : ywmaxs);

    xfract = (double)(xwmax - xwmin)/p->xfull/(p->xwmaxs-p->xwmins);
    yfract = (double)(ywmax - ywmin)/p->yfull/(p->ywmaxs-p->ywmins);
    if (xfract > yfract) yfract = 0.1*xfract + 0.9*yfract;
    else	         xfract = 0.9*xfract + 0.1*yfract;

    MinmaxSetup(&xa);
    MinmaxSetup(&ya);

    xa.scl = (xwmax-xwmin)/(xa.max-xa.min);
    xa.off = xwmin - xa.min*xa.scl + 0.5;
    ya.scl = (ywmax-ywmin)/(ya.max-ya.min);
    ya.off = ywmin - ya.min*ya.scl + 0.5;

    AxisSetup(&xa, &ya);
    AxisSetup(&ya, &xa);
    GridSetup();

    if (xa.lbl == 0) {
	sprintf(xtitle, "%sX is %.5g to %.5g,  %sY is %.5g to %.5g",
		xa.logflg ? "Log " : "", xmin, xmax,
		ya.logflg ? "Log " : "", ymin, ymax);
	xa.lbl = StringSave(xtitle);
    }
}

void AxisInit(Mode mode)
{
    xa.name = 'x';
    AxisInitOne(&xa);
    ya.name = 'y';
    AxisInitOne(&ya);
    xmin = ymin = FHUGE;
    xmax = ymax = -FHUGE;
    xwmins = xwmaxs = ywmins = ywmaxs = FDEFAULT;
    gridfg  = "P12,W.1,Ldot";
    gridtype = "out";
}

void XAxisDraw(void)
{
    Ptype min, max, off, off2, xsize, ysize;

    if ((gtype & (GRID|XGRID)) && (xa.mode & GRIDMARKS))
	GridDraw(&xa, &ya);

    FontGroupSelect("a", xa.numfg);
    if (xa.mode & AXIS) {
	min = X(xa.min);
	max = X(xa.max);
	line(min, xa.lo, max, xa.lo);
	if (gtype & SYMMETRIC)
	    line(min, xa.hi, max, xa.hi);
    }

    off = TickDrawAxis(&xa);
    if (xa.lbl && (xa.mode & TITLES)) {
	strsize(xa.lbl, &xsize, &ysize, 0.0);
	off2 = yfract*(yinch/4 + ysize/2) - ysize/3;
	off -= xa.rev ? -off2 : off2;
	FontGroupSelect("t", xa.lblfg);
	alabel(xa.rev ? "XTR" : "XT", xa.lbl, (xwmin+xwmax)/2, off);
    }
}

void YAxisDraw(void)
{
    Ptype min, max, off, off2, xsize, ysize;

    if ((gtype & (GRID|YGRID)) && (ya.mode & GRIDMARKS))
	GridDraw(&ya, &xa);

    FontGroupSelect("a", ya.numfg);
    if (ya.mode & AXIS) {
	min = Y(ya.min);
	max = Y(ya.max);
	line(ya.lo, min, ya.lo, max);
	if (gtype & SYMMETRIC)
	    line( ya.hi, min, ya.hi, max);
    }

    off = TickDrawAxis(&ya);
    if (ya.lbl && (ya.mode & TITLES)) {
	strsize(ya.lbl, &xsize, &ysize, 90.0);
	off2 = xfract*(xinch/4 + xsize/2);
	off -= ya.rev ? -off2 : off2;
	FontGroupSelect("t", ya.lblfg);
	alabel(ya.rev ? "YTR" : "YT", ya.lbl, off, (ywmin+ywmax)/2);
    }
}

static TickPtr ticks;
static Uint maxTicks, nTicks;

void TickDef(AxisPtr a, double tick, char *lbl, double scl, Boolean override)
{
    TickPtr t;
    double delta, tmp;
    short n;

    if (a->min != DEFAULT && a->max != DEFAULT)
	delta = (a->max - a->min)/1e3;
    else {
	tmp = fabs(tick);
	delta = (tmp < 1e-3) ? 1e-3 : tmp/1e3;
    }

    for (n = 0; n < nTicks; n++) {
	t = &ticks[n];
	tmp = fabs(tick - t->tick);
	if (a->name == (t->a)->name && tmp < delta)
	    break;
    }

    if (n == nTicks) {
	if (nTicks == maxTicks)
	    ticks = (TickPtr)azmem(ticks, (size_t *)&maxTicks, 12,
				   sizeof(*ticks));
	t = &ticks[nTicks++];
    }
    else {
	if (!override)
	    return;
    }

    t->a = a;
    t->tick = tick;
    t->lbl = (lbl == 0) ? lbl : StringSave(lbl);
    t->scl = (scl == DEFAULT) ? 1.0 : scl;
    if (a->rev)
	t->scl = -(t->scl);
}

/* Private functions (used only within this module) */

static void AxisInitOne(AxisPtr a)
{
    a->min = a->max = a->cr = FDEFAULT;
    a->aoff = a->mlt = a->tick = a->tmark = FDEFAULT;
    a->tscl = 1;
    a->skp = DEFAULT;
    a->pfm = a->lbl = a->base = 0;	
    a->mode = 077777;
    a->logflg = a->rev = NO;
    a->scl = a->off = 0;
    a->acchi = a->acclo = 0.2;
    a->numfg = a->lblfg = a->extra = 0;
    a->lo = a->hi = 0;
    a->this = (a->name == 'x') ? X : Y;
    a->other = (a->name == 'x') ? Y : X;
}


static void MinmaxSetup(AxisPtr a)
{
    if (a->min != DEFAULT && a->max != DEFAULT && a->min > a->max)
	err(YES, "%c axis reversed", a->name);
    if (a->min != DEFAULT)
	a->acchi = 0;
    if (a->max != DEFAULT)
	a->acclo = 0;
    if (a->logflg)
	LogTickSetup(a);
    else {
	LinearTickLogic(a);
	LinearTickSetup(a);
    }
}

static void AxisSetup(AxisPtr a, AxisPtr oa)
{
    if (a->cr == DEFAULT) {
	a->cr = a->rev ? oa->max : oa->min;
	if (a->aoff != DEFAULT)
	    a->cr -= (a->rev ? -1 : 1)*a->aoff*(oa->max-oa->min);
    }
    a->lo = (*a->other)(a->cr);
    a->hi = (*a->other)(oa->max+oa->min-a->cr);
}

static void LinearTickSetup(AxisPtr a)
{
    double tick, tend, tscl = a->tscl;
    short k, n;
    char *tlbl = 0;

    switch (a->mode & (TICKMARKS|TICKNUMS)) {
    case NOTHING: return;
    case TICKMARKS: tlbl = ""; break;
    case TICKNUMS: tscl = 0; break;
    case TICKMARKS+TICKNUMS: break;
    }
    n = ceil((a->min - a->tmark)/a->tick - 0.0001);
    tick = a->tmark + n*a->tick;
    tend = a->max + 0.001*a->tick;
    for (k = 0; tick < tend; k++) {
	TickDef(a, tick, ((k+n)%a->skp) ? "" : tlbl, tscl, NO);
	tick += a->tick;
    }
}

static void LogTickSetup(AxisPtr a)
{
    double tick, tbeg, tend, tscl, sfract, ftmp, subtick[20];
    int k, n, nsubtick;
    char *tlbl;

    if (a->logflg && a->base == 0)
	a->base = "10";
    if(a->min == DEFAULT)
	a->min = floor((a->name == 'x' ? xmin : ymin) + 0.001);
    if(a->max == DEFAULT)
	a->max = ceil((a->name == 'x' ? xmax : ymax) - 0.001);
    if(a->tmark == DEFAULT)
	a->tmark = 0;
    if(a->pfm == 0)
	a->pfm = "%g";

    tlbl = 0;
    tscl = a->tscl;	
    switch (a->mode & (TICKMARKS|TICKNUMS)) {
    case NOTHING: return;
    case TICKMARKS: tlbl = ""; break;
    case TICKNUMS: tscl = 0; break;
    case TICKMARKS+TICKNUMS: break;
    }

    n  = ceil((a->min - a->tmark) - 0.0001);
    tbeg = a->tmark + n;
    tend = a->max;
    sfract = ((a->name == 'x') ? (double)(xwmax-xwmin)/p->xfull :
	      (double)(ywmax-ywmin)/p->yfull)/(tend-tbeg);

    if (ticklogic)
	err(NO, "tbeg, tend, sfract: %g %g %g\n", tbeg, tend, sfract);
    if (a->extra == NULL)
	a->extra = (sfract < 0.07) ? "n" : "y";
    nsubtick = 0;
    if (a->extra[0] == 'y' && (n=atoi(a->base)) > 2) {
	if (a->skp == DEFAULT)
	    a->skp = 1 + 0.05/sfract;
	ftmp = 1/log((double)n);
	for (k = 2; k < n; k++)
	    subtick[nsubtick++] = log((double)k)*ftmp;
    }
    else if (a->skp == DEFAULT)
	a->skp = 1 + 0.05/sfract;

    for (tick = tbeg; tick <= tend; tick += a->skp) {
	TickDef(a, tick, tlbl, tscl, NO);
	for (n = 0; n < a->skp;  n++) {
	    if (n > 0 && tick+n-.01 < tend)
		TickDef(a, tick+n, "", tscl, NO);
	    if (a->extra[0] == 'y' && tick+n+0.99 < tend) {
		for (k = 0; k < nsubtick; k++)
		    TickDef(a, tick+n+subtick[k], "", tscl, NO);
	    }
	}
    }
}

static void GridSetup(void)
{
    char *spec;

    if (*gridtype >= '0' && *gridtype <= '9') {
	gtype = LongNum(gridtype);	/* old-time compatibility */
	return;
    }

    while (*gridtype) {
	if (!getstr(&gridtype, &spec))
	    continue;			

	if (strcmp(spec,"out") == 0)
	    gtype = (gtype & ~IN_OUT_MASK) + OUT_TICK;
	else if(strcmp(spec,"in") == 0)
	    gtype = (gtype & ~IN_OUT_MASK) + IN_TICK;
	else if(strcmp(spec,"out") == 0)
	    gtype = (gtype & ~IN_OUT_MASK) + OUT_TICK;
	else if(strcmp(spec,"both") == 0)
	    gtype = (gtype & ~IN_OUT_MASK) + IN_OUT_TICK;
	else if(strncmp(spec,"no",2) == 0)
	    gtype = (gtype & ~IN_OUT_MASK);
	else if(strncmp(spec,"sym",3) == 0)
	    gtype |= SYMMETRIC;
	else if(strcmp(spec,"grid") == 0)
	    gtype |= GRID;
	else if(strcmp(spec,"xgrid") == 0)
	    gtype |= XGRID;
	else if(strcmp(spec,"ygrid") == 0)
	    gtype |= YGRID;
	else if(strncmp(spec,"sub",3) == 0)
	    gtype |= SUB_GRID;
	else
	    err(YES, "Unrecognized tick spec `%s'", spec);
	while(*gridtype == ',')
	    gridtype++;
    }
}

static void GridDraw(AxisPtr a, AxisPtr oa)
{
    TickPtr t;
    Ptype n, itick, min, max;

    FontGroupSelect("a", gridfg);
    min = (*oa->this)(oa->min);
    max = (*oa->this)(oa->max);

    for (n = 0; n < nTicks; n++) {
	t = &ticks[n];
	if ((t->a)->name == a->name &&
	    (t->lbl == 0 || *t->lbl != 0 || (gtype & SUB_GRID))) {
	    itick = (*a->this)(t->tick);
	    if (a->name == 'x')
		line(itick, min, itick, max);
	    else
		line(min, itick, max, itick);
	}
    }
}

static Ptype TickDrawAxis(AxisPtr a)
{
    TickPtr t;
    double fudge;
    Ptype off, toff, ticklen, ticksize;
    short n;
    Boolean lbl;

    fudge = 0.3 * (double)chht/yinch * (3+xfract+yfract); 
    ticksize = fudge * ((a->name == 'x') ? xinch : yinch)/5.0;
    toff = a->lo;

    for (n = 0, t = ticks; n < nTicks; n++, t++) {
	if ((t->a)->name == a->name) {
	    lbl = !(t->lbl && t->lbl[0] == 0);
	    ticklen = (lbl ? (3*ticksize+1)/2 : ticksize) * t->scl;
	    off = TickDraw(t, ticklen);
	    if (lbl)
		TickLabel(t, off);
	    if (a->rev ? (toff < off) : (toff > off))
		toff = off;
	}
    }
    return (toff);
}

#define		XYTICK(A,B) 	(isx ? line(itick, A, itick, off=B)	\
					: line(A, itick, off=B, itick))

static int TickDraw(TickPtr t, Ptype ticklen)
{
    AxisPtr	a;
    int isx, itick, off;

    a = t->a;
    itick = (*a->this)(t->tick);
    isx  = (a->name == 'x');

    if (gtype & SYMMETRIC)
	switch (gtype & IN_OUT_MASK) {
	case NO_TICK: break;
	case IN_TICK: XYTICK(a->hi, a->hi-ticklen); break;
	case OUT_TICK: XYTICK(a->hi+ticklen, a->hi); break;
	case IN_OUT_TICK: XYTICK(a->hi+ticklen, a->hi-ticklen); break;
	}

    switch (gtype & IN_OUT_MASK) {
    case NO_TICK: off = a->lo; break;
    case IN_TICK: XYTICK(a->lo+ticklen, a->lo); break;
    case OUT_TICK: XYTICK(a->lo, a->lo-ticklen); break;
    case IN_OUT_TICK: XYTICK(a->lo+ticklen, a->lo-ticklen); break;
    }
    return (off);
}

static void TickLabel(TickPtr t, int off)
{
    AxisPtr a;
    Boolean rev;
    char str[60];

    a = t->a;
    if (t->lbl == 0) {
	double tmp = fabs(t->tick);
	if((a->max-a->min)*1e-6 > tmp)
	    t->tick = 0;
	sprintf(str, a->pfm, t->tick);
	t->lbl = StringSave(str);
    }

    rev = (t->scl < 0);
    if (a->logflg) {
	if (a->name == 'x')
	    elabel(rev ? "XER" : "XE", a->base, t->lbl, X(t->tick), off);
	else
	    elabel(rev ? "YER" : "YE", a->base, t->lbl, off, Y(t->tick));
    }
    else {
	if (a->name == 'x')
	    alabel(rev ? "XNR" : "XN", t->lbl, X(t->tick), off);
	else
	    alabel(rev ? "YNR" : "YN", t->lbl, off, Y(t->tick));
    }
}