Sindbad~EG File Manager
/* M Beeson, for Windows Mathpert.
6.23.00 Graph paper for graph backgrounds
7.12.00 last modified
7.27.00 modified AdjustGraphRangesAux
3.25.00 modified GetRoundNumber and AdjustRangesAux
8.20.04 corrected DrawPaper
10.7.04 modified GetRoundNumber
10.7.08 modified GetRoundNumber
2.24.24 changed include cgraph to svgGraph and dstate to svgDevice
and set_graphpencolor to set_graphpencolor
and set_backgroundcolor to set_graphbackgroundcolor
removed unused variables
2.26.24 added begin_path() and end_path() in DrawPaper
3.1.24 --and in DrawPolarGrid, and tinkered with them in DrawPaper
3.9.24 adjusted calls to set_device and relinquish_device
3.21.24 added code in AdjustGraphRanges for polar graphs involving aspect.
3.31.24 added fabs in second line of AdjustGraphRangesAux
6.30.24 modified GetRoundNumber
7.11.24 and again
7.16.24 and again
8.9.24 and again, don't use 1/7 or 1/9
2.16.25 removed unused xgap2, ygap2 in DrawPaper
2.16.25 made AdjustGraphRanges return without doing anything if g->adjustToPaper == 0
*/
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <math.h> /* frexp */
#include <stdio.h> /* for printf debugging output */
#include "globals.h"
//#include "mp2.h"
#include "mathpert.h"
#include "graphstr.h" /* graph */
#include "svgGraph.h"
#include "grpaper.h"
#include "grafinit.h" /* needs_circular_aspect */
#include "deval.h" /* nearint */
#include "tdefn.h"
#define GRPAPER_NMAX 30
static void DrawPolarGrid(graph *g, double gap, double radius,int n);
/*___________________________________________________________________*/
void DrawPaper(graph *g)
/* draw the specified GraphPaper as the background
for the graph. This will also print the graph paper
if the device has been set appropriately to the print
device.
*/
{ double a,b,c,d;
double radius, xgap1, xgap2;
// int piflag = (g->labels == 3); /* use multiples of pi */
int nn;
// int polarflag = 0;
svgDevice *dev = get_device();
GraphPaper *G = &g->grpaper;
/* G->spacingPoints is the distance between major lines in CSS pixels,
which is the same as printer's points, 72 to an inch.
Now we convert it to world coordinates
*/
xgap1 = dev->xpixel * G->spacingPoints; // distance between major lines
// double ygap1 = dev->ypixel * G->spacingPoints; // not used. I hope that's correct.
xgap2 = xgap1/10;
// assuming 72 pixels per inch and dev->xpixel = dev->ypixel == 1
/* Since drawing will be done in world coordinates
we must determine what world coordinates correspond
to the spacings specified in the GraphPaper. Those
spacings are specified in printer's points.
*/
a = g->xmin;
b = g->xmax;
c = g->ymin;
d = g->ymax;
set_graphpencolor(g->border);
set_graphbackgroundcolor(G->background);
filled_rect(a,c,b,d);
if(g->graphtype == POLAR_CIRCULAR ||
g->graphtype == POLAR
)
{ /* radius should be in world coordinates what GRPAPER_RADIUS is in inches */
dev = get_device();
radius = dev->xpixel * dev->xPixelsPerInch * GRPAPER_RADIUS;
relinquish_device();
nn = Round360(2*PI_DECIMAL * radius / xgap1);
if(G->ncolors > 1)
{ G->thickness2 = 0.4;
set_linewidth(G->thickness2);
set_graphpencolor(G->color2);
DrawPolarGrid(g,xgap2,radius,nn * G->nlines);
}
set_linewidth(G->thickness1);
set_graphpencolor(G->color1);
DrawPolarGrid(g,xgap1,radius,nn);
set_linewidth(g->linewidth);
return;
}
drawGraphPaper(G,a,b,c,d);
}
/*________________________________________________________________*/
void drawGraphPaper(GraphPaper *G, double a, double b, double c, double d)
{ double x,y,xgap1,ygap1,xgap2,ygap2,delta,test;
svgDevice *dev = get_device();
xgap1 = dev->xpixel * G->spacingPoints; // distance between major lines
ygap1 = dev->ypixel * G->spacingPoints;
if(G->ncolors == 0)
return; // nothing to draw
if(G->nlines) // Need the spacing of vellum or minor lines
{ xgap2 = xgap1 / G->nlines;
ygap2 = ygap1 / G->nlines;
}
else
{ xgap2 = xgap1; // be sure we never divide by 0
ygap2 = ygap1;
}
int vellum = G->color3 ? 1 : 0;
if(G->nlines) // there will be minor lines, so we need their spacing
{ xgap2 = xgap1 / G->nlines;
ygap2 = ygap1 / G->nlines;
}
double a1,b1; // nearest multiples of xgap2;
double c1,d1; // nearest multiples of ygap2;
a1 = (int)(fabs(a)/xgap2) * xgap2;
a1 = a > 0 ? a1 + xgap2 : -a1;
b1 = (int)(fabs(b)/xgap2) * xgap2;
b1 = b > 0 ? b1 : -b1+xgap2;
c1 = (int)(fabs(c)/ygap2) * ygap2;
c1 = c> 0 ? c1 + ygap2+1 : -c1;
d1 = (int)(fabs(d)/ygap2) * ygap2;
d1 = d > 0 ? d1 : -d1+ygap2;
a = a1;
b = b1;
c = c1;
d = d1;
begin_path();
if(vellum)
{ /* First draw all the vellum lines; if you draw them later
then they overwrite the other lines. These lines
are drawn at multiples of xgap2 and ygap2, using color3.
*/
set_graphpencolor(G->color3);
set_linewidth(1); // vellum lines as thin as possible
for(y = c; y<= d;y += ygap2)
{ move_to(a,y);
line_to(b,y);
}
for(x=a; x<= b; x+= xgap2)
{ move_to(x,c);
line_to(x,d);
}
}
// That completes drawing the vellum lines
if(G->ncolors > 1)
/* draw the minor lines first */
{ set_linewidth(G->thickness2);
set_graphpencolor(G->color2);
if(G->color3)
{ // there are vellum lines, so the minor lines are
// spaced xgap1/2 apart
xgap2 = xgap1/2;
ygap2 = ygap1/2;
}
/* Draw the vertical minor lines first. */
/* a and b are already multiples of the original xgap2 and ygap2 */
delta = xgap2;
for(x = a; x <= b; x += xgap2)
{ test = fabs(x/delta);
// is test close to an integer? if so draw a minor vertical line.
// Can't use function nearint as it is too discriminating.
int q = (int)(test+0.0000001);
if(fabs(test-q) > 0.000001)
continue;
/* so x is a multiple of delta, draw a minor vertical line */
move_to(x,c);
line_to(x,d);
}
/* Now the horizontal minor lines */
delta = ygap2;
for(y=c; y<= d; y += ygap2)
{ double test = fabs(y/delta);
int q = (int)(test+0.0000001);
if(fabs(test-q) > 0.000001)
continue;
/* so y is a multiple of delta, draw a minor horizontal line */
move_to(a,y);
line_to(b,y);
}
}
/* that completes drawing the minor lines */
/* Prepare to draw the major lines */
set_graphpencolor(G->color1);
set_linewidth(G->thickness1);
delta = xgap1;
// first the vertical major lines
for(x = a; x <= b + 0.1; x += xgap2)
{ test = fabs(x/delta);
int q = (int)(test+0.0000001);
if(fabs(test-q) > 0.000001)
continue;
/* so x is an multiple of delta, draw a major vertical line */
move_to(x,c);
line_to(x,d);
}
/* Now the horizontal major lines */
delta = ygap1;
for(y=c; y<= d + 0.1; y += ygap2)
{ double test = fabs(y/delta);
int q = (int)(test+0.0000001);
if(fabs(test-q) > 0.000001)
continue;
/* so y is a multiple of delta, draw a major horizontal line */
move_to(a,y);
line_to(b,y);
}
end_path();
}
/*__________________________________________________________________________*/
static void DrawPolarGrid(graph *g, double gap, double radius, int n)
/* draw a polar grid using the currently selected color and thickness,
spacing the circular lines so that they are separated by approximately 'gap' at the specified radius;
gap and radius are in world coordinates. The number n is the number of radial lines to draw
in a full circle of 2 pi radians. If it's more than GRPAPER_NMAX, don't draw them all the way to
the center. Vellum lines are not used in polar graphs.
*/
{ double r, rmax, rmin, theta, alpha, c,s;
int i;
r = gap;
if(g->xmin <= 0 && g->xmax > 0)
{ rmin = 0.0;
rmax = fabs(g->xmin) > fabs(g->xmax) ? fabs(g->xmin) : fabs(g->xmax);
}
else
{ rmin = g->xmin;
rmax = g->xmax;
}
if(g->ymin > rmin)
rmin = g->ymin;
if(g->ymin < 0)
rmax = fabs(g->ymin) + rmax;
else
rmax = g->ymax + rmax;
r = rmin + gap;
while(r <= rmax)
{ draw_circle(0,0,r);
r += gap;
}
begin_path();
alpha = 2.0 *PI_DECIMAL/n;
for(i=0;i<n;i++)
{ theta = i*alpha;
c = cos(theta);
s = sin(theta);
if(n <= GRPAPER_NMAX)
move_to(0,0);
else
move_to(gap*c, gap*s);
line_to(rmax * c, rmax * s);
}
end_path();
}
/*_______________________________________________________________________*/
int Round360(double n)
/* return an integer close to n which divides 360, but no more than 120. */
/* The divisors of 360 are 180, 120, 90, 72, 60, 45, 40, 36, 30, 24, 20, 18, 15, 12, 10, 8, 6, 5,4,3,2 */
{ if(n <= 6)
return (int) (n+0.5);
if(n <= 10)
return 8;
if(n <= 14)
return 12;
/* don't use 10 or 15 */
if(n <= 20)
return 18;
if(n <= 28)
return 24;
if(n <= 38)
return 36;
if(n <= 44)
return 40;
if(n <= 66)
return 60;
if(n <= 80)
return 72;
if(n <= 110)
return 90;
return 120;
}
/*______________________________________________________________________*/
static double GetRoundNumber(double x, int k)
/* return a "nice round number" near x, assuming x > 0.
Try to get a multiple of k if x > 10.
This function should ideally be idempotent, i.e. GetRoundNumber(GetRoundNumber(x,k),k)=
GetRoundNumber(x,k), so that when you push Adjust to Graph Paper twice, nothing further happens.
I think it is idempotent. But just to be sure, the static table 'answers'.
*/
{
int i;
double ans, u, v;
long m;
static double answers[24];
static int nextanswer = 0;
double epsilon = 0.0000001;
// Check if x is already in answers
for (i = 0; i < nextanswer; i++) {
if (fabs(x - answers[i]) < epsilon) {
return answers[i];
}
}
if (x <= 0.0)
assert(0);
if (k == 0)
k = 10;
if (0.8 < x && x <= 1.2) {
ans = 1.0;
goto store_and_return;
}
if (0.1 <= x && x <= 0.5) {
double recips[] = { 1.0 / 8, 1.0 / 6, 1.0 / 5, 1.0 / 4, 1.0 / 3, 0.5};
for (i = 0; i < sizeof(recips) / sizeof(double); i++) {
if (recips[i] >= x - epsilon) {
ans = recips[i];
goto store_and_return;
}
}
}
if (0.5 < x && x <= 0.8) {
ans = 2 * GetRoundNumber(x / 2, k);
goto store_and_return;
}
if (x < 0.1) {
ans = 0.1 * GetRoundNumber(10 * x, k);
goto store_and_return;
}
if (x <= 5) {
ans = (int)((10 * x + 1) / 10.0);
goto store_and_return;
}
if (5 < x && x <= 10) {
if (nearint(x, &m) && (m & 1) == 0) {
ans = (double)m;
goto store_and_return;
}
ans = floor(x + 1);
if (((int)(ans + 0.1)) & 1)
++ans;
goto store_and_return;
}
if (x > 10 && x <= 100) {
if (nearint(x, &m) && (m % k == 0))
return (double)m;
ans = ((int)x / k + 1) * k;
goto store_and_return;
}
if (x > 100) {
u = x / 100;
v = GetRoundNumber(u, k);
ans = 100 * v;
goto store_and_return;
} else {
ans = x;
}
store_and_return:
// Store ans in answers array if there's space
for (i = 0; i < nextanswer; i++) {
if (fabs(ans - answers[i]) < epsilon) {
return answers[i];
}
}
if (nextanswer < sizeof(answers) / sizeof(double)) {
answers[nextanswer] = ans;
++nextanswer;
}
return ans;
}
/*_______________________________________________________________________*/
static void AdjustRangesAux(int pmin, int pmax, double xmin, double xmax, double *left, double *right, int k, int piflag, double *factor)
/* pmin and pmax are the pixel coordinates of the left and right ends of the graph (or top and bottom).
xmin and xmax are world coordinates of the left and right
(or top and bottom) of the graph. k is the number of minor lines per major line in a graph paper.
Increase the value of xmax and decrease the value of xmin so that nice values close to the original
values will lie on half-inch boundaries, and return the answers in *left and *right. Return in *factor
the factor by which the world-coordinate difference xmax-xmin is multiplied.
If piflag is nonzero, then try to make the graph lines fall on multiples of pi instead of
on integers.
*/
{ int PixelsPerInch = 72; // in Web MathXpert it never changes.
if(pmin == pmax)
assert(0);
double halfinches = 2*(pmax-pmin)/(double) PixelsPerInch; /* total width (or height) in half-inches */
double x = fabs(xmax - xmin); /* width in world coordinates */
assert(x != 0);
assert(PixelsPerInch != 0);
double UnitsPerHalfInch = piflag ? x/(PI_DECIMAL * halfinches) : x/halfinches;
double t,delta;
/* we want to increase UnitsPerHalfInch to a round number, but not increasing by
more than a factor of b or decreasing more than by a factor of a. */
double NewUnitsPerHalfInch = GetRoundNumber(UnitsPerHalfInch,k);
// printf("NewUnits = %lf, OldUnits = %lf\n",NewUnitsPerHalfInch,UnitsPerHalfInch);
if(fabs(NewUnitsPerHalfInch) < 0.1 ) // oops, it came out zero
return; // don't do anything
t = NewUnitsPerHalfInch/UnitsPerHalfInch;
*factor = t;
if( xmin <= 0.0 && xmax >= 0.0 )
{ *left = xmin *t;
*right = xmax *t;
return;
}
else if(xmin > 0.0) // and hence also xmax > 0.0
{ delta = xmin - (xmin/NewUnitsPerHalfInch) * NewUnitsPerHalfInch;
*left = xmin - delta;
*right = *left + t*(xmax-xmin);
return;
}
else
{ delta = -xmax -( (-xmax)/NewUnitsPerHalfInch * NewUnitsPerHalfInch);
*right = xmax + delta;
*left = *right - t *(xmax-xmin);
return;
}
}
/*________________________________________________________________________*/
void AdjustGraphRanges(graph *g, int topic)
/* increase, or slightly decrease,
the (absolute value of) fields g->xmin, g->xmax, g->ymin, g->ymax so that
the original g->xmin, rounded to a nice round number,
lies at a multiple of (1/2) inches from origin.
*/
{ int k = g->grpaper.nlines;
int piflag = g->ticks == 2 ? 1 : 0;
double t;
if(k==0)
return; /* should not be called unless there is a valid graph paper */
if(g->adjustToPaper == 0) // set to zero disables AdjustGraphRanges, e.g. for scrolling
return;
// adjust the horizontal axis to fit graph paper
AdjustRangesAux(g->pxmin, g->pxmax, g->xmin, g->xmax, &g->xmin, &g->xmax,k, piflag,&t);
if(topic == _graph_circle || // user pressed "circle" button
g->circular
)
{ double aspect = (g->pymax-g->pymin)/ (double)(g->pxmax-g->pxmin);
g->ymax = aspect * g->xmax;
g->ymin = aspect * g->xmin;
/* This ensures that the graph paper is circular on polar graphs */
}
else // adjust the vertical axis to fit graph paper
AdjustRangesAux(g->pymin, g->pymax, g->ymin, g->ymax, &g->ymin, &g->ymax,k, 0, &t);
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists