/* 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)); } }