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
*/
#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 */
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,x,y,xgap1,ygap1,xgap2,ygap2,radius;
// int piflag = (g->labels == 3); /* use multiples of pi */
int count, nn;
// int polarflag = 0;
int vellum;
svgDevice *dev;
GraphPaper *G = &g->grpaper;
vellum = G->color3 ? 1 : 0;
set_graphbackgroundcolor(G->background);
/* 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.
*/
dev = get_device();
xgap1 = dev->xpixel * G->spacingPoints * dev->xPixelsPerInch / 72.0;
ygap1 = dev->ypixel * G->spacingPoints * dev->yPixelsPerInch / 72.0;
relinquish_device();
xgap2 = xgap1 / G->nlines;
ygap2 = ygap1 / G->nlines;
a = g->xmin;
b = g->xmax;
c = g->ymin;
d = g->ymax;
set_graphpencolor(g->border);
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;
}
begin_path();
if(G->ncolors > 1)
/* draw the thin lines first */
{ set_linewidth(G->thickness2);
set_graphpencolor(G->color2);
/* Draw the vertical lines first. */
if(a < 0 && b > 0)
{ x = -xgap2;
count = 1;
while(x > a)
{ if(count % G->nlines)
{ if(vellum && (count % (G->nlines/2) == 0))
{ set_graphpencolor(G->color3);
move_to(x,c);
line_to(x,d);
set_graphpencolor(G->color2);
}
else
{ move_to(x,c);
line_to(x,d);
}
}
x -= xgap2;
++count;
}
x = xgap2;
count = 1;
while(x <b)
{ if(count % G->nlines)
{ if(vellum && (count % (G->nlines/2) == 0))
{ set_graphpencolor(G->color3);
move_to(x,c);
line_to(x,d);
set_graphpencolor(G->color2);
}
else
{ move_to(x,c);
line_to(x,d);
}
}
x += xgap2;
++count;
}
}
else /* start at an exact multiple of xgap1 */
{ x = (int) (a/xgap1) * xgap1 + xgap2;
count = 1;
while(x < a)
{ if(count % G->nlines)
{ if(vellum && (count % (G->nlines/2) == 0))
{ set_graphpencolor(G->color3);
move_to(x,c);
line_to(x,d);
set_graphpencolor(G->color2);
}
else
{ move_to(x,c);
line_to(x,d);
}
}
x += xgap2;
++count;
}
while(x < b)
{ if(count % G->nlines)
{ if(vellum && (count % (G->nlines/2) == 0))
{ set_graphpencolor(G->color3);
move_to(x,c);
line_to(x,d);
set_graphpencolor(G->color2);
}
else
{ move_to(x,c);
line_to(x,d);
}
}
x += xgap2;
++count;
}
}
/* Draw the horizontal lines */
if(c < 0 && d > 0)
{ y = -ygap2;
count = 1;
while(y > c)
{ if(count % G->nlines)
{ if(vellum && (count % (G->nlines/2) == 0))
{ set_graphpencolor(G->color3);
move_to(a,y);
line_to(b,y);
set_graphpencolor(G->color2);
}
else
{ move_to(a,y);
line_to(b,y);
}
}
y -= ygap2;
++count;
}
y = ygap2;
count = 1;
while(y < d)
{ if(count % G->nlines)
{ if(vellum && (count % (G->nlines/2) == 0))
{ set_graphpencolor(G->color3);
move_to(a,y);
line_to(b,y);
set_graphpencolor(G->color2);
}
else
{ move_to(a,y);
line_to(b,y);
}
}
y += ygap2;
++count;
}
}
else /* start at an exact multiple of ygap1 plus ygap2 */
{ y = (int) (c/ygap1) * ygap1 + ygap2;
count = 1;
while(y < c)
{ if(count % G->nlines)
{ if(vellum && (count % (G->nlines/2) == 0))
{ set_graphpencolor(G->color3);
move_to(a,y);
line_to(b,y);
set_graphpencolor(G->color2);
}
else
{ move_to(a,y);
line_to(b,y);
}
}
y += ygap2;
++count;
}
while(y < d)
{ if(count % G->nlines)
{ if(vellum && (count % (G->nlines/2) == 0))
{ set_graphpencolor(G->color3);
move_to(a,y);
line_to(b,y);
set_graphpencolor(G->color2);
}
else
{ move_to(a,y);
line_to(b,y);
}
}
y += ygap2;
++count;
}
}
}
/* Now draw the thick lines */
set_linewidth(G->thickness1);
set_graphpencolor(G->color1);
/* Draw the vertical lines first. */
if(a < 0 && b > 0)
{ x = -xgap1;
while(x > a)
{ move_to(x,c);
line_to(x,d);
x -= xgap1;
}
x = xgap1;
while(x <b)
{ move_to(x,c);
line_to(x,d);
x += xgap1;
}
}
else /* start at an exact multiple of xgap1 */
{ x = (int) (a/xgap1) * xgap1;
if(x < a)
x += xgap1;
while(x < b)
{ move_to(x,c);
line_to(x,d);
x += xgap1;
}
}
/* Draw the horizontal lines */
if(c < 0 && d > 0)
{ y = -ygap1;
while(y > c)
{ move_to(a,y);
line_to(b,y);
y -= ygap1;
}
y = ygap1;
while(y < d)
{ move_to(a,y);
line_to(b,y);
y += ygap1;
}
}
else /* start at an exact multiple of ygap */
{ y = (int) (c/ygap1) * ygap1;
if(y < c)
y += ygap1;
while(y < d)
{ move_to(a,y);
line_to(b,y);
y += ygap1;
}
}
set_linewidth(g->linewidth);
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 / 9, 1.0 / 8, 1.0 / 7, 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);
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 */
AdjustRangesAux(g->pxmin, g->pxmax, g->xmin, g->xmax, &g->xmin, &g->xmax,k, piflag,&t);
if(!needs_circular_aspect(g->graphtype,topic))
AdjustRangesAux(g->pymin, g->pymax, g->ymin, g->ymax, &g->ymin, &g->ymax,k, 0, &t);
else
{ 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 */
}
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists