Sindbad~EG File Manager
/* MATHPERT grapher, by M. Beeson*/
/* The functions in this file actually draw the graph lines
4.12.90 original date
8.23.98 modified
11.29.99 added invert_color and modified the 3 lines that call it.
1.9.00 added justpassedflag
12.2.00 modified carefulgraph for jumps.
4.6.07 changed hypot to _hypot
9.21.10 modified at dated lines
10.12.10 modified around lines 630 and 667 to improve graphing functions defined by cases.
6.12.13 modified draw_riemann to compute the correct mathematical area while using another variable for the rectangle
coordinate.
2.24.24 changed include cgraph. to svgGraph.h and svgDevice to svgDevice
and set_graphpencolor to set_graphpencolor
and set_graphbackgroundcolor to set_graphbackgroundcolor
3.22.24 added begin_path and end_path in graph_parametric
and many of them in careful_graph
6.23.24 modified draw_riemann to use g->fillcolor
7.1.24 modified draw_trapezoid and draw_simpson to use g->fillcolor
7.29.24 added lastx, lasty, and some move_to commands
7.29.24 added open_filled_circle
*/
#include <math.h>
#include <assert.h>
#include <stdio.h> // printf for debugging
#include "globals.h"
#include "graphstr.h"
#include "svgGraph.h"
#include "grapher.h"
#include "heap.h" /* my own malloc and free */
#include "eqn.h" /* solve */
#include "deval.h"
#include "sing.h" /* check_restrictions */
#include "islinear.h"
#include "pstring.h"
static int badpt(double);
static void solve_edge(double a, double b, double *xp, term f, double *y);
#define OPPOSITE_SIGN(x,y) (((x) < 0.0 && (y) > 0.0) || ((x) > 0.0 && (y) < 0.0) || ((x == 0.0 || y == 0.0) && x*y != 0.0))
/*___________________________________________________________________*/
/* world_to_pixel handles the problem of pixel coordinates > 32K
and never returns such coordinates. Hence badpt doesn't have to
check for this (as it once did).
There are three other ways of getting bad points:
(1) an error from the math library, which matherr intercepts and
substitutes BADLIMIT for,
(2) a floating-point exception, which signal() has told the
system to ignore; nothing is done about these. In theory
only overflow or underflow or TLOSS could occur, see (3)
for overflow; underflow comes to zero; and TLOSS is still
a possibility.
(3) a floating-point overflow, for which the 80x87 does not raise
a floating-point exception, but rather substitutes a specific
'overflow-indication' number for the return value.
*/
#define BADLIMIT 1.0E300 /* this value must mesh with matherr.c and deval.c */
static const double overflow = (double) 1.0e200 * (double) 1.0e200;
/* 80x87 returns this value on overflow instead of generating a floating-point
exception. This variable is also needed in graph2.c.
Compiling this line causes a warning message. This warning message
is suppressed by the pragma commands above and below. */
static int badpt(double x)
{ if( x > BADLIMIT)
return 1; /* depends on definition of matherr() */
else if(x < -BADLIMIT)
return 1; /* which returns large values for errors */
return (x==overflow) ? 1 : 0;
}
/*_____________________________________________________________________*/
/* No checking for singularities or jumps in graphparametric--so there's only one path */
/* calculates the gap between points dynamically, rather than having
npts passed in */
int graphparametric(term xf, term yf,
double *tp, /* value pointer of the independent variable */
/* has to be changed as we go so deval works right */
double tmin, double tmax,
double xmin, double xmax,
double ymin, double ymax,
int visibility,
graph *g
)
{ double x,y,nextx,nexty,farawayx,farawayy,nextx2,nexty2;
int flag,newflag;
double maxgap,gap; /* must be dynamically calculated */
double smallgapx = (xmax-xmin)/300.0; /* smallest acceptable x-jump */
double biggapx = smallgapx * 3.0; /* largest acceptable x-jump */
double smallgapy = (ymax -ymin)/300.0;
double biggapy = smallgapy * 3.0;
double actualgapx,actualgapy;
double newgap, smallgap, savetp;
int i;
// get the active parameter if there is one
double activeparameterValue = 0.0;
if(g->nparameters && g->activeparameter >= 0)
{ parameter p = g->parameters[g->activeparameter];
activeparameterValue = *(p.addr);
}
*tp = tmin;
gap = maxgap = (tmax - tmin) / 200.0; /* for starters */
deval(xf,&x);
deval(yf,&y);
begin_path3(activeparameterValue,visibility);
move_to(x,y); /*position graphics cursor*/
flag = ( badpt(y) || badpt(x) );
while(*tp < tmax)
{ *tp = *tp + gap;
if( deval(xf,&nextx) || deval(yf,&nexty))
/* that is, if either xf or yf can't be evaluated */
{ if(flag == 0)
/* try not to leave a false gap. Get a better approximation
to where it starts being undefined. */
{ smallgap = gap/10.0;
savetp = *tp;
flag = 1;
*tp -= gap;
for(i=0;i<10;i++)
{ *tp += smallgap;
if(!deval(xf,&nextx2) && !deval(yf,&nexty2) &&
nextx2 != BADVAL && nexty2 != BADVAL
)
{ x = nextx2;
y = nexty2;
line_to(x,y);
continue;
}
}
*tp = savetp;
}
continue;
}
if(flag) /* previous point was undefined but this one was ok */
{ flag = 0;
/* again try not to leave a false gap */
smallgap = gap/10.0;
savetp = *tp;
*tp -= gap;
for(i=0;i<10;i++)
{ *tp += smallgap;
if(!deval(xf,&nextx2) && !deval(yf,&nexty2) &&
nextx2 != BADVAL && nexty2 != BADVAL
)
break;
}
if(i < 10)
{ nextx = nextx2;
nexty = nexty2;
}
x = nextx;
y = nexty;
move_to(x,y);
continue;
}
actualgapx = fabs(nextx - x);
actualgapy = fabs(nexty - y);
/* If we are off-screen we don't want to waste time: */
farawayx = fabs(x - 0.5*(xmax+xmin));
farawayy = fabs(y - 0.5*(ymax+ymin));
if( (farawayx > 2.0 * (xmax-xmin) && actualgapx < 0.8 * farawayx) ||
(farawayy > 2.0 * (ymax-ymin) && actualgapy < 0.8 * farawayy)
)
{ newgap = gap * 2.0;
if(newgap <= maxgap)
gap = newgap;
x = nextx;
y = nexty;
move_to(x,y);
continue;
}
if(actualgapx > biggapx)
/* too big a jump, must recalculate */
{ *tp = *tp-gap; /* it was incremented above */
gap = gap * 0.9 * (biggapx/actualgapx); /* decrease the gap */
*tp = *tp + gap;
deval(xf,&nextx);
deval(yf,&nexty);
actualgapx = fabs(nextx - x);
actualgapy = fabs(nexty - y);
}
if(actualgapy > biggapy)
{ *tp = *tp-gap;
gap = gap * 0.9 * (biggapy/actualgapy);
*tp = *tp+gap;
deval(xf,&nextx);
deval(yf,&nexty);
actualgapx = fabs(nextx - x);
actualgapy = fabs(nexty - y);
}
if( *tp > tmax) /* don't overshoot the endpoint */
{ *tp = tmax;
deval(xf, &nextx);
deval(yf, &nexty);
}
x = nextx;
y = nexty;
/* Now at least we haven't taken too BIG a jump */
/* But for speed, we don't want the gap to be too small either,
so increase the gap if too little progress has been made;
but on the example r = cos^17 t sin(3t), this went wrong because
all the action takes place in a tiny interval of t values, so
we increase the gap several times and then skip the next action
interval entirely! So, don't increase the gap above 1/200 of the
total t-interval. */
if(actualgapx < smallgapx && actualgapy < smallgapy)
{ newgap = gap * 2.0;
if(newgap <= maxgap)
gap = newgap;
}
newflag = (badpt(x) || badpt(y));
/*flag refers to point i-1 */
if (flag == 0 && newflag==0) /* both points ok */
line_to(x,y);
else
move_to(x,y); /*without drawing*/
flag = newflag; /*now flag refers to point i */
}
end_path();
return 0;
}
/*______________________________________________________________________*/
int carefulgraph(int whichcase, graph *g, int visibility)
/* if visibility is zero, the lines should have display:none, else display:block */
/* Whichcase serves to distinguish the several situations
with regard to singularities:
1 = numerical singularities;
2 = usable symbolic but not numerical singularities;
3 = no singularities;
4 = symbolic singularities too hard to compute, reason unknown
5 = symbolic singularities computable but contain two
existential parameters
6 = symbolic singularities computable, only one existential
parameter, but too hard to solve for numerical singularities.
(In cases 4,5,6 we can't draw them right)
In case (1): Check the abscissas mentioned in the array. 'singularities'.
As such an abscissa is passed, if it is a singularity, we draw up to it but not
across it, thereby avoiding spurious vertical lines.
In case (2): Evaluate the formula for the singularities at every step to
see if a singularity is being passed.
In remaining cases: just draw. If it comes out wrong, too bad.
Handling extrema: when graphs are wiggly as in sin(1/x) or sin(44 x) it's
important to plot a point at each extremum. Therefore the derivative is
passed along with the function. At each step check whether the derivative
has changed sign. If so, use Brent's method to find the critical point and be
sure to draw a point exactly there. */
/* This function also watches out not to draw across jump discontinuities.
It can handle both numerical and symbolic expressions for the jump
discontinuities, which are stored in appropriate fields of the graph
structure. */
/* Return values:
0 for success,
1 for out-of-space,
2 for function-undefined-on-entire-interval.
4 for function defined only on isolated points;
either 2 or 4 can include a case like y = 1/x^1,000,000,000 in which
the points all, or all but a few, evaluate to BADVAL.
*/
{ int err;
int justpassedflag = 0;
int nlines = 0;
svgDevice *device = get_device();
term f = g->function; /* the function to be graphed */
// get the value of the active parameter if any
double activeparameterValue = 0.0;
if(g->nparameters && g->activeparameter >= 0)
{ parameter p = g->parameters[g->activeparameter];
activeparameterValue = *(p.addr);
}
term fprime = g->fprime; /* its derivative */
term xvar = g->independent_variable;
double *xp =(double *) ARGPTR(xvar); /* pointer to value of the independent variable */
double xmin = g->xmin;
double xmax = g->xmax;
double ymin = g->ymin;
double ymax = g->ymax;
int m = g->numberofpoints; /* number of points to be plotted */
int msing = g->nsingularities; /* dimension of array of singularities */
int mjumps = g->njumps;
int lastsing = -1;
double *numjumps = g->jumps;
double *singularities = g->singularities; /* sorted array of numerical singularities */
term *sing = g->slist;
int dim = g->dimslist;
int flag,newflag; /* nonzero value of flag says last point was bad */
int initial_singularity=0;
double x,y,oldy,slope,yprime,oldyprime,nextsing,oldsing;
double gap = (xmax - xmin) / (m - 1);
double nominalgap = gap;
double epsilon = gap/1000.0;
double roundoff = VERYSMALL * (xmax-xmin);
double deltayprime;
double pix = device->xpixel;
double savex,ext;
double slopeone = (ymax-ymin)/(xmax-xmin);
double xjump,yjump,xplus,xminus,yplus,yminus;
double lastx, lasty;
int i=0;
int next=0; /* index of next singularity to watch out for */
int nextjump = 0; /* index of next jump to watch out for */
term left,right;
int singflag,backup,jj;
double *singstore; /* space to store singularities computations */
double t,t1,t2;
int k;
int hradius; /* pixel radius of circles to draw at jumps */
double wradius;
hradius =(int)(0.30 * device->fontheight); /* works for printer or screen */
wradius = hradius * device->xpixel; /* world coordinates of radius in x-direction */
x = *xp = xmin;
if(whichcase==2) /* usable symbolic singularities */
{ assert(dim > 0);
singstore = (double *) callocate(dim,sizeof(double));
if(!singstore)
nospace();
for(k=0;k<dim;k++)
{ if(FUNCTOR(sing[k])=='=')
{ deval(ARG(1,sing[k]),&t2);
deval(ARG(0,sing[k]),&t1);
singstore[k] = t2-t1;
}
else
{ deval(sing[k],&singstore[k]);
}
}
}
deval(f,&y);
flag = badpt(y); /* 1 if y is a bad point, 0 if not */
while(flag) /* we only enter this loop if first point was bad */
{ x += gap;
*xp = x;
if(x >= xmax)
{ if(whichcase==2)
free2(singstore);
return 2; /* function undefined */
}
if(fabs(x) < roundoff)
x = *xp = 0.0;
/* probably it's really zero, this is roundoff error */
/* For example graphing log x, we get a small positive x
which should be zero. Without this we get a spurious line
because we have crossed a singularity */
if(next < msing && fabs(x-singularities[next]) < roundoff)
x = *xp = singularities[next];
/* similarly in case the domain begins with a singularity
somewhere other than zero */
/* There can be 'singularities' that are not in the (closure of the)
domain of the function, e.g. when graphing sqrt(x tan^2x).
See sing_exp in file singular.c for more explanation.
*/
if(whichcase == 1 && next < msing && x > singularities[next])
{ ++next;
initial_singularity = 1;
}
else
initial_singularity = 0;
deval(f,&y); /* keep advancing till we get a good starting point */
++i; /* e.g. if the function is undefined for a while */
flag = badpt(y);
if(!flag)
{ /* try backing up a little bit so as not to skip the first little
piece of the graph */
solve_edge(x-gap,x,xp,f,&y);
x = *xp;
if(initial_singularity && next > 0 && x < singularities[next-1])
{ /* previous point had f undefined, but between two points there was first a
portion where the graph WAS defined, then a singularity. E.g. when
the range is 256 and the domain begins at x=0 and there's a singularity
at x=1, gap is 1.28 so we skip from 0 to 1.28 */
initial_singularity = 0;
--next;
}
break;
}
}
oldy = y;
deval(fprime,&yprime);
begin_path3(activeparameterValue,visibility);
if(initial_singularity)
{ /* then we've just passed a singularity at the edge of the domain, so we
need to draw a line to (x,y) from (x-gap, BADVAL) or (x-gap, - BADVAL)
*/
savex = *xp;
nextsing = singularities[next-1];
*xp = nextsing + epsilon;
deval(fprime,&slope);
if(slope == BADVAL)
{ *xp = nextsing + 10*epsilon;
deval(fprime,&slope);
if(slope == BADVAL)
{ *xp = nextsing + 100 *epsilon;
deval(fprime,&slope);
}
}
*xp = savex;
if(slope > slopeone )
/* then we are coming on from below */
{ move_to(nextsing,device->ymin-device->ycliplimit);
line_to(x,y);
lastx = x; lasty = y;
++nlines;
}
else if( slope < -slopeone ) /* coming on from above */
{ move_to(nextsing,device->ycliplimit);
line_to(x,y);
lastx = x; lasty = y;
++nlines;
}
/* else if the slope wasn't large, something was fishy...don't draw */
else
move_to(x,y);
}
else if(whichcase == 1 && next > 0 && next-1 < msing)
{ if (x-gap <= singularities[next-1] &&
singularities[next-1] < x + roundoff
)
/* a singularity AFTER x-gap but before or equal to x*/
{ if(singularities[next-1] >= x)
/* move x a little to the right so it's certainly past the
singularity. Without this, x can be within roundoff error
of the singularity and not register as crossing it yet. */
{ x += roundoff;
*xp = x;
deval(fprime,&yprime);
deval(f,&y);
}
if(yprime > 0.0 && yprime != BADVAL)
{ move_to(singularities[next-1],-device->ycliplimit);
line_to(x,y);
lastx = x; lasty = y;
++nlines;
}
else if(yprime < 0.0 && yprime != BADVAL)
{ move_to(singularities[next-1],device->ycliplimit);
line_to(x,y);
lastx = x; lasty = y;
++nlines;
}
}
else
move_to(x,y);
}
else
move_to(x,y); /* initialize graphics cursor (if nothing was drawn yet) */
/* Now here comes the main for-loop that draws segment after segment */
for (++i; x <= xmax ;i++) /* i is initialized to 1 + the number of initial bad points */
{ *xp = x = x+gap; /* x[i] */
oldy = y; /* y[i-1] */
justpassedflag = 0;
if( whichcase == 1 && next < msing &&
x <= singularities[next] &&
x + roundoff > singularities[next]
)
{ /* Within roundoff error of a singularity. Push x a
little to the right to cross the singularity now,
because otherwise the code below that is intended
to plot exact maxima can cause mistaken lines: if fprime
doesn't deval to BADVAL, ssolve gets called and can even
succeed, resulting in plotting a spurious "maximum". */
*xp = x = x + roundoff;
justpassedflag = 1;
}
if( whichcase == 1 && next < msing &&
x > singularities[next] &&
x - 1.1 * roundoff < singularities[next]
)
{ /* just past a singularity. The trouble here is that deval
tries to 'forgive' roundoff error and e.g. returns 0 for
the square root of a tiny negative number, so it can
return a value when the function is really not defined
to the right of the singularity */
justpassedflag = 1;
*xp = x + epsilon;
deval(f,&y);
newflag = badpt(y);
if(!newflag) // the function is apparently defined at x + epsilon, but maybe that's not really true, as remarked above, so let's check it.
{ *xp = singularities[next] + roundoff;
deval(f,&y);
newflag = badpt(y);
}
}
else
{ deval(f,&y); /* y[i] */
newflag = badpt(y); /*flag refers to point i-1 */
}
if(flag && newflag)
{ if(next < msing && x > singularities[next])
++next; /* crossed a 'singularity' outside domain of function */
if(nextjump < mjumps && x > numjumps[nextjump])
++nextjump; /* crossed a 'jump' outside domain of function */
flag = newflag;
continue; /* function was undefined at previous point
and also at this point */
}
if(!flag && !newflag) /* previous point and this one both defined */
{ oldyprime = yprime;
deval(fprime,&yprime);
if(yprime != BADVAL && !justpassedflag)
/* if the second derivative is large we should decrease the gap */
/* or what amounts to the same thing, if y' is changing too rapidly */
/* unless the gap is already pixel-sized */
{ deltayprime = fabs(yprime-oldyprime);
if(deltayprime > slopeone && gap > pix)
{ gap /= 2.0;
*xp = x = x-gap;
deval(f,&y);
newflag = badpt(y);
deval(fprime,&yprime);
}
else if(deltayprime < 0.1 * slopeone && gap < nominalgap)
/* increase the gap again after getting through the wiggly part */
gap *= 2.0;
}
/* now test for crossing the next critical point */
if (oldyprime != BADVAL && yprime != BADVAL && !justpassedflag &&
OPPOSITE_SIGN(oldyprime,yprime) &&
/* Even if they aren't BADVAL, it's conceivable we might be crossing
a pole of even order here; but nothing will be drawn in that case
anyway. A worse danger here is that roundoff error may result
in yprime having an incorrect sign. */
!(whichcase == 1 && next < msing && x > singularities[next]) &&
!( nextjump < mjumps && x > numjumps[nextjump]) // added 9.21.10
/* be sure we're not crossing a singularity or a jump here; if we are,
don't worry about the critical point we may also be passing. */
)
{ err = solve(fprime,xvar,x-gap,x,&ext); /* Using Brent's method, find
the exact critical point */
*xp = x; /* solve changes it */
if(!err) /* although we know the root is bracketed here, it can
still fail, e.g. when graphing x sin x^3 across x=0,
due to "too many iterations" */
{ savex = *xp;
*xp = x = ext;
deval(f,&t);
if (t != BADVAL)
{ line_to(ext,t); /* then draw! */
lastx = ext; lasty = t;
++nlines;
}
else
{ move_to(ext,t); /* without drawing */
if(nextjump < mjumps && x > numjumps[nextjump])
++nextjump;
}
*xp = x = savex;
if (t != BADVAL)
{ line_to(x,y); /* then draw! */
lastx = x; lasty = y;
++nlines;
}
else
{ move_to(x,y); /* without drawing */
// we've crossed a jump, so put in any closed or open circles that
// need to be drawn at that jump.
if(nextjump < mjumps && x > numjumps[nextjump])
++nextjump;
}
flag = newflag;
continue;
}
/* we get here (1) when the curve is too FLAT for Brent's
method, so we aren't in danger of too many wiggles;
or (2) when crossing an even-order singularity. But
we don't know which yet, so we can't draw. */
}
}
/* That takes care of getting extrema drawn correctly. Now let's
worry about getting singularities and jumps drawn correctly.
The code within the following switch draws singularities. The
code AFTER the switch draws circles on jumps. Thus we should
escape from the switch with 'continue' unless we have passed
a jump. */
switch(whichcase)
{ case 1: /* numerical singularities */
if ( (next >= msing) || (x < singularities[next]))
/* Not crossing a singularity. */
{ if (flag == 0 && newflag==0 && /* both points ok */
(nextjump >= mjumps || x <= numjumps[nextjump])
/* and not crossing a jump */
)
{ line_to(x,y); /* then draw! */
lastx = x; lasty = y;
++nlines;
flag =newflag;
continue;
}
else if(flag && !newflag && lastsing >= 0 && y < ymax && y > ymin &&
x-singularities[lastsing] < 5.0*gap
/* if it's more than that, chances are this is a
gap in the domain. */
)
{ /* have crossed a singularity and still got y = BADVAL
one or more times after the singularity. Is this
due to the function being undefined, or just due to
its being too large to deval? If the latter, now
it's time to draw. */
savex = *xp;
*xp = singularities[lastsing] + epsilon;
deval(fprime,&slope);
backup = 10;
for(jj = 0; jj < 4 && slope == BADVAL;jj++)
{ *xp = singularities[lastsing] + backup*epsilon;
deval(fprime,&slope);
if(slope != BADVAL)
break;
backup *= 10;
}
*xp = savex;
if(slope > slopeone )
/* then we are probably coming on from below */
{ move_to(singularities[lastsing],device->ymin-device->ycliplimit);
line_to(x,y);
lastx = x; lasty = y;
++nlines;
}
else if( slope < -slopeone ) /* coming on from above */
{ move_to(singularities[lastsing],device->ycliplimit);
line_to(x,y);
++nlines;
}
/* else probably spurious */
*xp += gap;
lastsing = -1;
}
else if(!flag || !newflag) // modified 10.21.10
{ if(nextjump < mjumps && x + epsilon > numjumps[nextjump])
break; /* crossing a jump */
}
/* else one or both of x and oldx are bad values!
Don't draw */
if(newflag == 0)
move_to(x,y);
}
else /* crossing singularity, don't just draw, but make sure we
draw if necessary to make the graph go off-screen,
and finally update next. */
{ nextsing = singularities[next];
crossing:
/* Check if the last point was on-screen: */
if( i > 0 && oldy < ymax && oldy > ymin)
{ /* then it was on-screen so we have to draw something*/
/* But which way? up or down? */
backup = 10;
savex = *xp;
*xp = nextsing-epsilon;
deval(fprime,&slope);
for(jj = 0; jj < 6 && slope == BADVAL;jj++)
{ *xp = nextsing - backup*epsilon;
deval(fprime,&slope);
if(slope != BADVAL)
break;
backup *= 10;
}
*xp = savex;
if(slope != BADVAL && nextjump < mjumps && fabs(numjumps[nextjump]-nextsing)< epsilon)
{ // this might be a one-sided singularity in a function defined by cases
if( (slope > slopeone && fabs(y-ymax) > 0.2*(ymax-ymin))
|| (slope < -slopeone && fabs(y-ymin) > 0.2*(ymax-ymin))
)
slope = 0; // prevents drawing in next few lines
}
if(slope > slopeone) /* going up */
{ line_to(nextsing,device->ycliplimit); /* go offscreen up */
lastx = nextsing; lasty = device->ycliplimit;
++nlines;
}
else if(slope < -slopeone) /* going down */
{ line_to(nextsing,device->ymin-device->ycliplimit); /*go offscreen down*/
lastx = nextsing; lasty = device->ycliplimit;
++nlines;
}
/* else it's probably a spurious singularity
as in 1/x -1/(x-a) when a=0--why else wouldn't it be
achieving a slope of one in device coordinates when
we're only epsilon from the singularity? It could
also be a one-sided singularity in a function defined
by cases.
*/
}
/* Now on the other side of the singularity */
if(y == BADVAL)
lastsing = next;
if (y < ymax && y > ymin) /* then must draw*/
{ savex = *xp;
*xp = nextsing + epsilon;
deval(fprime,&slope);
backup = 10;
for(jj = 0; jj < 6 && slope == BADVAL;jj++)
{ *xp = nextsing + backup*epsilon;
deval(fprime,&slope);
if(slope != BADVAL)
break;
backup *= 10;
}
*xp = savex;
if(slope != BADVAL && nextjump < mjumps && fabs(numjumps[nextjump]-nextsing)< epsilon)
{ // this might be a one-sided singularity in a function defined by cases
if( (slope < -slopeone && fabs(y-ymax) > 0.2*(ymax-ymin))
|| (slope > slopeone && fabs(y-ymin) > 0.2*(ymax-ymin))
)
slope = 0; // prevent drawing in next few lines
}
if(slope > slopeone)
/* then we are coming on from below */
{ move_to(nextsing,device->ymin-device->ycliplimit);
line_to(x,y);
lastx = x; lasty = y;
++nlines;
}
else if( slope < -slopeone ) /* coming on from above */
{ move_to(nextsing,device->ycliplimit);
line_to(x,y);
lastx = x; lasty = y;
++nlines;
}
/*else probably spurious, see above */
*xp += gap;
}
move_to(x,y); /* harmless if we're already there, necessary if not */
++next; /* prepare for the next singularity crossing */
if(mjumps && mjumps > nextjump && x > numjumps[nextjump])
{ /* in graphing a CASES term the jumps and
singularities may coincide */
/* do we need to draw a filled circle? */
double savexp = *xp;
double y3;
*xp = numjumps[nextjump];
deval(f,&y3);
*xp = savexp;
if(y3 != BADVAL)
{ end_path();
filled_circle2(*xp,y3, 1.4*wradius,visibility,activeparameterValue);
/* But if it comes out BADVAL, we don't know whether
or where to draw an open_filled_circle. */
begin_path3(activeparameterValue,visibility);
}
++nextjump;
}
}
flag = newflag; /* now flag refers to point i */
continue;
case 2: /* symbolic singularities with no existential parameters */
if(flag || newflag) /* one point or the other was bad */
{ move_to(x,y);
flag = newflag;
continue;
}
/* are we crossing a singularity? */
singflag = 0;
for(k=0;k<dim;k++)
{ if(FUNCTOR(sing[k])== '=')
{ left = ARG(0,sing[k]);
right = ARG(1,sing[k]);
deval(left,&t1);
deval(right,&t2);
t = t2-t1;
if ( !singflag && OPPOSITE_SIGN(t,singstore[k]))
/* we did cross a putative singularity--
but, maybe it doesn't satisfy one of the restrictions--
we have to check that before concluding that we
REALLY passed a singularity. */
{ int derr = check_restrictions(g->srestrictions,g->dimsrestrictions);
if(derr) /* failed to satisfy one or more restrictions */
singflag = 0; /* flunked one of the restrictions */
else
{ term temp;
temp = make_term('=',2);
ARGREP(temp,0,left);
ARGREP(temp,1,right);
if(k == 0)
oldsing = xmin;
savex = *xp;
err = solve(temp,xvar,oldsing,x,&nextsing);
*xp = savex;
singflag = err ? 0 : 1;
RELEASE(temp);
break;
}
}
}
}
if(k < dim)
{ singstore[k] = t;
oldsing = *xp;
}
if(singflag) /* we did cross a singularity */
goto crossing;
else /* are we crossing a jump? */
{ if(nextjump < mjumps && x >= numjumps[nextjump])
break;
else
{ line_to(x,y);
lastx = x; lasty = y;
++nlines;
}
}
flag = newflag;
continue;
default: /* draw if both points are good. If not, don't draw;
i.e., don't assume there was a singularity if you
couldn't calculate the singularities */
if(nextjump < mjumps && x+epsilon > numjumps[nextjump])
break; /* crossing a jump */
if(!flag && !newflag)
{ line_to(x,y); /* then draw! */
lastx = x; lasty = y;
++nlines;
}
else if(flag && !newflag)
{ /* try backing up a little bit so as not to skip the first little
piece of the graph, e.g. on x^x just past the origin;
since (-2)^(-2) is defined, we are here instead of
at the first place where solve_edge is called. */
solve_edge(x-gap,x,xp,f,&y);
x = *xp;
move_to(x,y);
}
else
move_to(x,y);
flag = newflag;
continue;
} /* close switch */
/* Now we have to cross a jump, or we have a bad point */
/* Put in the open or closed circles for this jump */
xjump = numjumps[nextjump];
++nextjump;
*xp = xjump;
deval(f,&yjump);
if(flag) /* probably oldx == the jump */
{ assert(oldy==BADVAL);
*xp = xjump-epsilon;
deval(f,&oldy);
flag = 0;
}
if(newflag) /* probably x == the jump */
{ assert(y == BADVAL);
*xp = x = xjump+epsilon;
deval(f,&y);
newflag = 0;
}
if(yjump == BADVAL)
{ /* Function not defined at the jump;
draw open circles at both ends
*/
end_path();
open_filled_circle(xjump,oldy,wradius,visibility,activeparameterValue);
open_filled_circle(xjump,y,wradius,visibility,activeparameterValue);
begin_path3(activeparameterValue,visibility);
}
else /* Function defined at the jump */
{ double hyp, costheta,y2;
xplus = xjump + epsilon;
xminus = xjump - epsilon;
*xp = xminus;
deval(f,&yminus);
*xp = xplus;
deval(f,&yplus);
*xp = (x-wradius)/10.0;
deval(f,&y2);
if(fabs(yminus-yjump) > 2*hradius*device->ypixel)
{ end_path();
open_filled_circle(xjump,yminus,wradius,visibility,activeparameterValue);
begin_path3(activeparameterValue,visibility);
move_to(lastx,lasty); // path must begin with a move, not a line
/* Be sure we draw right up to the circle */
/* This must be done BEFORE drawing the circle,
or the line will stick into the circle visibly. */
hyp = hypot(wradius/(10.0 *pix),(yminus-y2)/device->ypixel);
costheta = wradius /(10 * pix *hyp);
*xp = xjump - wradius *costheta;
deval(f,&y2);
if(y2 != BADVAL)
{ line_to(*xp,y2);
// lastx = *xp; lasty = y2; Don't set them here!
x = *xp;
}
end_path();
open_filled_circle(xjump,yminus,wradius,visibility,activeparameterValue);
begin_path3(activeparameterValue,visibility);
move_to(lastx,lasty); // path must begin with a move, not a line
}
if(fabs(yplus-yjump) > 2*hradius*device->ypixel)
{
end_path();
open_filled_circle(xjump,yplus,wradius,visibility,activeparameterValue);
begin_path3(activeparameterValue,visibility);
move_to(lastx,lasty); // path must begin with a move, not a line
}
if(fabs(yplus-yminus) <= 10*epsilon)
{ line_to(xjump,yjump);
lastx = xjump; lasty = yjump;
continue; /* this isn't really a jump, e.g. x = 0 in x floor(x) */
}
end_path();
filled_circle2(xjump,yjump,1.4*wradius,visibility,activeparameterValue);
begin_path3(activeparameterValue,visibility);
move_to(lastx,lasty); // path must begin with a move, not a line
}
/* Now, try not to write through any open circles you've just drawn */
if(
(nextjump >= mjumps || *xp < numjumps[nextjump]) &&
(next >= msing || *xp < singularities[next]) &&
fabs(yplus-yjump) > 2*hradius*device->ypixel /* open circle at yplus */
)
{ double y2;
*xp = xjump + wradius;
deval(f,&y2);
if(y2 != BADVAL)
{ double hyp = hypot(wradius/pix,(y2-yplus)/device->ypixel);
double costheta = wradius/(pix *hyp);
*xp = xjump + wradius *costheta;
deval(f,&y2);
if(y2 != BADVAL)
move_to(*xp,y2);
else
move_to(x,y);
}
else
move_to(x,y);
}
else if(newflag == 0)
{ if(fabs(yminus-yjump) > 2*hradius*device->ypixel)
{ x = xjump; /* drawing line from the center of a closed circle */
*xp = xjump;
deval(f,&y);
}
move_to(x,y);
}
} /*close for*/
end_path();
return nlines ? 0 : 4;
}
/*________________________________________________________________________*/
#if 0
static unsigned invert_color(unsigned background)
/* return white for a black background, etc. */
{ unsigned ans = ~background;
/* but this is correct only in the lower three bytes which are used for
the color. We need to correct byte 3, which should be 2 (to indicate
don't use a hatched color). */
ans = 0x00ffffffUL & ans; /* sets bit 3 to zero */
ans = 0x02000000UL | ans; /* sets bit 3 to 2 */
return ans;
}
#endif
/*________________________________________________________________________*/
void draw_riemann(graph *g)
/* draw Riemann sums, by drawing a lot of filled rectangles.
For Riemann sums, there are two graph structures; the
second one has graphtype ORDINARY_GRAPH and draws the curve.
This one draws the rectangles only. It computes the value of
the Riemann sum (the signed area 'under' the rectangles) and
stashes it in g->area. It uses g->riemannflag to determine
whether to use left, right, or midpoint Riemann sums. It takes
the number of intervals from g->nintervals, which is a term,
so it can contain a parameter. The left and right endpoints
are taken from g->left and g->right.
*/
{ int i;
double x1,x2,xmin,xmax,xeval,yeval,u;
double *xp = (double *) ARGPTR(g->independent_variable);
double dnintervals,gap,area;
int nintervals;
int type = g->riemannflag; /* 0 = left, 1 = right, 2 = midpoint */
term t = g->function;
deval(g->nintervals,&dnintervals);
if(dnintervals > 512.0)
dnintervals = 512.0; /* The maximum allowed */
if(dnintervals < 1.0)
dnintervals = 1.0; /* The minimum allowed */
nintervals = (int) dnintervals;
set_graphpencolor(g->graphcolor); /* for outlining the rectangles */
set_graphbackgroundcolor(g->fillcolor);
// invert_color(g->background)); /* white for a black background */
xmin = g->left;
xmax = g->right;
if (ARITY(g->function) == 5) // an integral_test problem
// then the gap is always 1
gap = 1.0;
else
gap = (xmax-xmin)/nintervals;
area = 0.0;
for(i=0;i<nintervals;i++)
{ x1 = xmin + i*gap;
x2 = x1 + gap;
xeval = (type == 2 ? x2 : type == 0 ? x1 : 0.5 *(x1+x2));
*xp = xeval;
deval(t,&yeval);
/* Now avoid clipping problems: Windows draws nonsense
if you pass too-large pixel coordinates */
if(yeval > g->ymax)
u = g->ymax + (g->ymax-g->ymin);
else if(yeval < g->ymin)
u = g->ymin - (g->ymax-g->ymin);
else
u = yeval;
filled_rect(x1,u,x2,0.0);
area += yeval * (x2-x1); /* it's the signed area actually */
}
set_graphbackgroundcolor(g->background);
g->area = area;
}
/*________________________________________________________________________*/
void draw_simpson(graph *g)
/* draw a Simpson's rule approximation to an integral. As
for Riemann sums, there are two graph structures; the
second one has graphtype ORDINARY_GRAPH and draws the curve.
This one draws the 'rectangles' (with curved tops) only.
It computes the value of the signed area of the approximation and
stashes it in g->area. It takes the number of intervals from
g->nintervals, which is a term, so it can contain a parameter.
The left and right endpoints are taken from g->left and g->right.
*/
{ int i,k,err,err2;
double x,x1,y1,x2,y2,x3,y3,xmin,xmax,h,xgap;
double dnintervals,gap,area,a,b,c;
int nboundarypts;
double *xp = (double *) ARGPTR(g->independent_variable);
double *xboundary;
double *yboundary;
int enough; /* dimension of the xboundary, yboundary arrays */
int nintervals;
term t = g->function;
deval(g->nintervals,&dnintervals);
if(dnintervals > 512.0)
dnintervals = 512.0; /* The maximum allowed */
if(dnintervals < 1.0)
dnintervals = 1.0; /* The minimum allowed */
nintervals = (int) dnintervals;
enough = g->numberofpoints/ nintervals + 10;
xboundary = (double *) callocate(enough, sizeof(double));
yboundary = (double *) callocate(enough, sizeof(double));
if(!xboundary || !yboundary)
nospace();
set_graphbackgroundcolor(g->fillcolor);
set_graphpencolor(g->graphcolor); /* this color is used for outlining the rectangles */
xmin = g->left;
xmax = g->right;
gap = (xmax-xmin)/nintervals;
h = 0.5 * gap;
area = 0.0;
*xp = xmin;
err = deval(t,&y3); /* to start the loop */
if(err)
{ *xp = xmin+gap; /* e.g. for integral(1/sqrt x,x,0,1) this happens. */
deval(t,&y3);
}
for(i=0;i<nintervals;i++)
{ x1 = xmin + i*gap;
x3 = x1 + gap;
x2 = 0.5*(x3+x1);
y1 = y3;
*xp = x2;
err = deval(t,&y2);
*xp = x3;
err2 = deval(t,&y3);
if(err)
y2 = y3;
if(err2)
y3 = y2;
// deval can fail only at the endpoints, so one of them must be good.
/* compute the coefficients of a parabola ax^2 + bx + c passing
through (-h,y1), (0,y2), and (h,y3). See Stewart page 459 */
a = (y1 - 2*y2 + y3)/(2*h*h);
b = (y3-y1)/gap;
c = y2;
/* Draw and fill the parabola-topped 'rectangle' */
xboundary[0] = x3; /* right side */
yboundary[0] = y3;
xboundary[1] = x3;
yboundary[1] = 0.0;
xboundary[2] = x1;
yboundary[2] = 0.0; /* bottom */
xboundary[3] = x1;
yboundary[3] = y1; /* left side */
nboundarypts = g->numberofpoints/nintervals +3;
xgap = gap/(nboundarypts-3);
for(k=4; k<nboundarypts;k++)
{ x = x1+ (k-3)*xgap - x2;
xboundary[k] = x2 + x;
yboundary[k] = (a*x + b)*x + c; /* only 2 multiplications */
}
polygon(xboundary,yboundary,nboundarypts);
area += (y1 + 4*y2 + y3) * h/3;
}
set_graphbackgroundcolor(g->background);
g->area = area;
free2(xboundary);
free2(yboundary);
}
/*________________________________________________________________________*/
void draw_trapezoid(graph *g)
/* draw a trapezoidal rule approximation to an integral.
Similar to draw_simpson, above.
*/
{ int i;
double x1,y1,x2,y2,xmin,xmax;
double dnintervals,gap,area;
int nboundarypts;
double *xp = (double *) ARGPTR(g->independent_variable);
double xboundary[4]; /* boundary of a trapezoid only needs 4 points */
double yboundary[4];
int nintervals;
term t = g->function;
deval(g->nintervals,&dnintervals);
if(dnintervals > 512.0)
dnintervals = 512.0; /* The maximum allowed */
if(dnintervals < 1.0)
dnintervals = 1.0; /* The minimum allowed */
nintervals = (int) dnintervals;
set_graphbackgroundcolor(g->fillcolor);
set_graphpencolor(g->graphcolor);
xmin = g->left;
xmax = g->right;
gap = (xmax-xmin)/nintervals;
area = 0.0;
*xp = xmin;
deval(t,&y2); /* to start the loop */
for(i=0;i<nintervals;i++)
{ x1 = xmin + i*gap;
x2 = x1 + gap;
y1 = y2;
*xp = x2;
deval(t,&y2);
/* Draw and fill the trapezoid */
xboundary[0] = x2; /* right side */
yboundary[0] = y2;
xboundary[1] = x2;
yboundary[1] = 0.0;
xboundary[2] = x1;
yboundary[2] = 0.0; /* bottom */
xboundary[3] = x1;
yboundary[3] = y1; /* left side */
nboundarypts = 4;
polygon(xboundary,yboundary,nboundarypts);
area += 0.5 * gap *(y1+y2);
}
set_graphbackgroundcolor(g->background);
g->area = area;
}
/*__________________________________________________________________________*/
static void solve_edge(double a, double b, double *xp, term f, double *y)
/* deval(f,&z) has failed when *xp = a, and succeeded when *xp = b.
It's assumed that a < b. Find the "smallest value" of *xp between a and b such
that deval(f,&z) succeeds. Return the corresponding value of f in *y.
*/
{ double z;
double lastgoodx = b;
double lastgoodz = *y;
int count = 0;
*xp = b;
deval(f,&z);
lastgoodz = z;
if(z == BADVAL)
assert(0); /* precondition of the function call */
start:
*xp = a + 0.5 *(b-a);
deval(f,&z);
if(z == BADVAL)
a = *xp;
else
{ b = *xp;
if(count >= 10)
{ *y = z;
return;
}
lastgoodx = *xp;
lastgoodz = z;
}
++count;
if(count >= 11)
{ *xp = lastgoodx;
*y = lastgoodz;
return;
}
goto start;
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists