home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Graphics 16,000
/
graphics-16000.iso
/
msdos
/
animutil
/
anim13
/
grafsupp.c
< prev
next >
Wrap
Text File
|
1990-02-07
|
14KB
|
401 lines
/* grafsupp.c
This file contains functions scale(), load_array(), draw_perimeter(),
label_plot(), and plot_curve() which supplement the graphics functions
in Borland's Turbo C.
In particular, they make possible device independent graphics programming,
i.e. you don't need to use pixels for graphics coordinates.
You are free to use whatever coordinates are natural for your problem.
The routines in this file are modeled after routines
in the graphics package written at the National Center for
Atmospheric Research (NCAR) in Boulder, Colorado;
I have not seen the source code for the NCAR (Fortran) subroutines,
but I have read their documentation.
Copyright 1990 by Jon Ahlquist, Department of Meteorology B-161,
Florida State University, Tallahassee, Florida 32306-3034, USA.
Telephone: (904) 644-1558.
Telnet address: ahlquist@metsat.met.fsu.edu (ahlquist@128.186.5.2)
This software may be freely copied without charge.
Copyright is made to prevent anyone from trying to impose restrictions
on this software.
All software and documentation is provided "as is" without warranty
of any kind.
Development of this material was sponsored by NSF grant ATM-8714674.
*/
/* Prototypes for: */
#include <graphics.h> /* Borland graphics */
#include <grafsupp.h> /* Functions here */
#include <stdlib.h> /* min() and max() */
/* Declare global variables set by scale(). */
float _xscale, _xoffset, _yscale, _yoffset,
_xscale_gen, _xoffset_gen, _yscale_gen, _yoffset_gen,
_X_left_gen, _X_right_gen, _Y_bottom_gen, _Y_top_gen,
pixel_aspect_ratio;
int XX_max, YY_max, XX_width;
/*********************************************************************/
void scale(float X_left_gen, float X_right_gen,
float Y_bottom_gen, float Y_top_gen,
float x_left_user, float x_right_user,
float y_bottom_user, float y_top_user)
/*
After initgraph() has been called to switch the screen display
to graphics mode, you can call scale() to compute constants needed for
coordinate transformations from "generic" and "user" coordinates
to the pixel coordinates needed by Borland graphics functions.
The actual transformations are carried out by the following macros
defined in grafsupp.h.
Macro Convert coordinate to pixel coordinate.
int XX(float x) user x horizontal
int YY(float y) user y vertical
int XG(float xg) generic xg horizontal
int YG(float yg) generic yg vertical
"Generic" and "user" coordinates and macros XX(), etc. are explained below.
An example program is contained in file grafdemo.c.
Also, see the other functions in this file, which complement scale().
Imagine the largest square that will fit on the screen
of your computer monitor, and center it on your monitor screen.
Let (0,0) denote the lower left corner and (1,1) the upper right corner
of this square. These reference points define what
we shall call "generic" coordinates. Since a computer monitor is actually
about 1/3 wider than it is tall, the lower left and upper right corners
of the monitor screen are approximately at generic coordinates
(-1/6, 0) and (7/6, 1).
The first four arguments to scale() define the x and y edges, in terms
of generic coordinates, of the window that you want to use for plotting.
For example, scale(-1./6., 7./6., 0.5, 1.0, ...)
would define the upper half of the screen.
The last four arguments to scale() define "user" coordinates
that will refer to the same locations on the screen
as were defined by the generic coordinates.
"User" coordinates are whatever coordinates are natural
for the plot that you are creating. For example, let's suppose
that you want to plot temperature as a function of time of day.
You have measured time in hours, starting with 0 at midnight,
passing through 12 at noon, and ending with 24 at the next midnight.
During that time, temperature varied between 50 and 70 degrees
Fahrenheit. Therefore, you want to create a graph whose x-axis
runs from 0 to 24 and whose y-axis runs from 50 to 70.
That would define the user coordinates for our problem.
Suppose your program says
scale(-1./6., 7./6., 0.5, 1.0, 0., 24., 50., 70.);
That would mean that you want time = 0 to appear at horizontal generic
coordinate -1/6, which is the left edge of the monitor screen.
Time = 24 would be associated with horizontal generic coordinate 7/6,
which is the right edge of the monitor screen.
Temperature = 50 would be associated with vertical generic coordinate 0.5,
which is halfway up the screen.
Temperature = 70 would be associated with vertical generic coordinate 1.0,
which is at the top of the screen.
Once you have called scale(), you can use macros XX(), YY(), XG(), and YG()
defined in grafsupp.h which are explained as follows.
Let (x,y) be an arbitrary user coordinate, and
let (xg,yg) be an arbitrary generic coordinate.
XX(x) and YY(y) will be integers denoting the horizontal and vertical
pixel coordinates of (x,y).
XG(xg) and YG(yg) will be integers denoting the horizontal and
vertical pixel coordinates of (xg,yg).
Thus, with scale() and the four macros XX(), etc.,
you can think in terms of user coordinates (x,y) and/or generic
cooridnates (xg,yg), which are totally independent of the graphics mode
that you are using. You can rely on XX(), YY(), XG(), and YG()
to compute the pixel addresses that you need for any of the Borland
graphics functions.
Dr. Jon Ahlquist
Dept. of Meteorology
Florida State University
Tallahassee, FL 32306-3034
Telephone (904) 644-1558
26 June 1988, 25 Jul 1989.
*/
{
int x_asp, y_asp;
/* Make global variable copies of the user's "generic" coordinates. */
_Y_top_gen = Y_top_gen;
_X_left_gen = X_left_gen; _X_right_gen = X_right_gen;
_Y_bottom_gen = Y_bottom_gen;
/*
First we cite the equations for converting between
the user's coordinates and "generic" coordinates.
"Generic" coordinates lie in a square extending
from (0,0) in the lower left corner of a square to (1,1) in the
upper right corner.
Let (x,y) refer to a coordinate in the user's system,
and (X,Y) be the corresponding point in "generic"
coordinates.
Using floating point arithmetic, the user's coordinates
are related to generic coordinates through the formulae:
X = (x - x_left_user) * (X_right_gen - X_left_gen)
/ (x_right_user - x_left_user)
+ X_left_gen
and
Y = (y - y_bottom_user) * (Y_top_gen - Y_bottom_gen)
/ (y_top_user - y_bottom_user)
+ Y_bottom_gen
We may rewrite these equations as
X = _xscale * x + _xoffset, and
Y = _yscale * y + _yoffset,
where the constants are given by:
*/
_xscale = (X_right_gen - X_left_gen ) / (x_right_user - x_left_user);
_yscale = (Y_top_gen - Y_bottom_gen) / (y_top_user - y_bottom_user);
_xoffset = (x_right_user * X_left_gen - x_left_user * X_right_gen)
/ (x_right_user - x_left_user);
_yoffset = (y_top_user * Y_bottom_gen - y_bottom_user * Y_top_gen)
/ (y_top_user - y_bottom_user);
/*
Next, we turn to converting from "generic" coordinates to
graphics mode coordinates.
Let (XX,YY) denote a Borland graphics mode coordinate where (0,0) is
in the upper left corner of the screen and (XX_max, YY_max) is
in the lower right corner. XX_max and YY_max are given by:
*/
XX_max = getmaxx(); /* No. of columns of pixels, less 1. */
YY_max = getmaxy(); /* No. of rows of pixels, less 1. */
/*
Let pixel_aspect_ratio denote the ratio of width to height of a
pixel. Specifically:
*/
getaspectratio(&x_asp, &y_asp);
pixel_aspect_ratio = (float) x_asp / (float) y_asp;
/*
We note that a TV monitor screen is always wider than it is tall.
Thus, we will place Y=0 at YY=YY_max and Y=1 at YY=0.
X=0 and X=1 will lie equidistant to the left and right of the
center of the screen. The distance from X=0 to X=1 on the monitor
screen should be the same as the distance from Y=0 to Y=1, i.e.
XX_width * x_asp = YY_max * y_asp,
where XX_width is the width in pixels of the X=0 to X=1 region, so */
XX_width = (int) ( ((float)YY_max) / pixel_aspect_ratio + 0.5 );
/* Thus, the transformation betweeen "generic" coordinates
and graphics mode coordinates is:
XX = X * XX_width + 0.5 * (XX_max - XX_width) + 0.5
and
YY = (1. - Y) * YY_max + 0.5
The additive factors of 0.5 are to guarantee that
the remainder of the right hand side of each equation will be
rounded to the nearest integer when computing XX and YY,
which are always integer values.
*/
/* Now write the conversion from (X,Y) to (XX,YY) in the form
XX = _xscale_gen * X + _xoffset_gen and
YY = _yscale_gen * Y + _yoffset_gen
where
*/
_xscale_gen = XX_width;
_xoffset_gen = 0.5 * (XX_max - XX_width) + 0.5;
_yscale_gen = -1.0 * YY_max;
_yoffset_gen = YY_max + 0.5;
/*
Combining the formulae to convert from (x,y) to (X,Y)
and thence to (XX,YY), we may write
XX = _xscale * x + _xoffset, and
YY = _yscale * y + _yoffset,
where
*/
_xscale = XX_width * _xscale;
_xoffset = XX_width * _xoffset + 0.5 * (XX_max - XX_width) + 0.5;
_yscale =-YY_max * _yscale;
_yoffset = YY_max * (1.0 - _yoffset) + 0.5;
} /*End of function scale(); */
/***********************************************************/
void load_array(float *x, int n, float xmin, float xmax)
/* This function loads floating point array x with uniformly
spaced values ranging from x[0]=xmin to x[n-1]=xmax. */
{
int i;
float dx;
dx = (xmax-xmin)/(n-1);
for (i=0; i<(n-1); i++) *(x+i) = xmin + i*dx;
*(x+n-1) = xmax;
/* We assign the last element of x separately to guarantee that it is
exactly equal to xmax without round-off errror. */
}
/***********************************************************/
void draw_perimeter(int n_major_x, int n_minor_x,
int n_major_y, int n_minor_y)
{
/* Perimeter draws a box around the plotting page region specified
in the call to scale();
It also draws tick marks on the inside of the box.
A "major" tick mark is a little longer than a "minor" tick mark.
n_major_x is the number of pieces into which the x axis will
be cut by major tick marks. For example, if n_major_x=2,
then one major tick mark will be drawn on the x-axis.
n_minor_x is the number of pieces into which one of the pieces
created by n_major_x is cut.
n_major_y and n_minor_y have analogous meanings for the y axis.
Jon Ahlquist, 13 January 1990.
*/
int i, j;
float x, x1, x2, Dx, dx,
y, y1, y2, Dy, dy;
/* Draw a box around the window. */
moveto (XG(_X_left_gen), YG(_Y_bottom_gen));
lineto (XG(_X_right_gen), YG(_Y_bottom_gen));
lineto (XG(_X_right_gen), YG(_Y_top_gen));
lineto (XG(_X_left_gen), YG(_Y_top_gen));
lineto (XG(_X_left_gen), YG(_Y_bottom_gen));
/* Make sure that values are proper. */
n_major_x = (int) max(n_major_x, 1);
n_minor_x = (int) max(n_minor_x, 1);
n_major_y = (int) max(n_major_y, 1);
n_minor_y = (int) max(n_minor_y, 1);
/* Draw tick marks on the x axis. */
Dx = (_X_right_gen - _X_left_gen)/n_major_x;
dx = Dx/n_minor_x;
x2 = _X_left_gen;
for (i=1; i<=n_major_x; i++)
{
/* Major tick marks. */
x1 = x2;
x2 += Dx;
moveto(XG(x2), YG(_Y_top_gen)),
lineto(XG(x2), YG(_Y_top_gen-0.02));
moveto(XG(x2), YG(_Y_bottom_gen));
lineto(XG(x2), YG(_Y_bottom_gen+0.02));
/* Minor tick marks. */
x = x1;
for (j=1; j<n_minor_x; j++)
{
x += dx;
moveto(XG(x), YG(_Y_top_gen)),
lineto(XG(x), YG(_Y_top_gen-0.01));
moveto(XG(x), YG(_Y_bottom_gen));
lineto(XG(x), YG(_Y_bottom_gen+0.01));
}
}
/* Draw major tick marks on the y axis. */
Dy = (_Y_top_gen - _Y_bottom_gen)/n_major_y;
dy = Dy/n_minor_y;
y2 = _Y_bottom_gen;
for (i=1; i<=n_major_y; i++)
{
/* Major tick marks. */
y1 = y2;
y2 += Dy;
moveto(XG(_X_left_gen), YG(y1));
lineto(XG(_X_left_gen +0.02), YG(y1));
moveto(XG(_X_right_gen), YG(y1));
lineto(XG(_X_right_gen-0.02), YG(y1));
/* Minor tick marks. */
y = y1;
for (j=1; j<n_minor_y; j++)
{
y += dy;
moveto(XG(_X_left_gen), YG(y));
lineto(XG(_X_left_gen +0.01), YG(y));
moveto(XG(_X_right_gen), YG(y));
lineto(XG(_X_right_gen-0.01), YG(y));
}
}
}
/***********************************************************/
void label_plot(char *title, char *xlabel, char *ylabel)
/* This function will write a title just above a graph
created using scale() and will label the x and y axes along
the bottom and left sides of the graph.
Jon Ahlquist, 13 Jan 1990. */
{
settextjustify(CENTER_TEXT, BOTTOM_TEXT);
settextstyle(DEFAULT_FONT, HORIZ_DIR, 1);
outtextxy(XG(0.5*(_X_left_gen+_X_right_gen)),
YG(_Y_top_gen + 0.05), title);
settextstyle(DEFAULT_FONT,VERT_DIR, 1);
outtextxy(XG(_X_left_gen-0.05), YG(_Y_bottom_gen), ylabel);
/* Reset settings to default values. */
settextjustify(LEFT_TEXT, TOP_TEXT);
settextstyle(DEFAULT_FONT, HORIZ_DIR, 1);
outtextxy(XG(_X_left_gen), YG(_Y_bottom_gen - 0.05), xlabel);
}
/***********************************************************/
void plot_curve(float *x, float *y, int n)
/* Let x and y be floating point arrays holding "n" values
of "user" coordinates.
"User" coordinates are explained in the documentation for scale().
This routine plots the "n" values of (x,y) by connecting
the (x,y) values with straight line segments.
Before calling plot_curve(), you MUST call initgraph() and scale().
The prototype for plot_curve() is declared in header file grafsupp.h.
Jon Ahlquist, 12 Jan 1990. */
{
int i;
moveto (XX(*x), YY(*y));
for (i=1; i<n; i++) lineto (XX(*(x+i)), YY(*(y+i)));
}