home *** CD-ROM | disk | FTP | other *** search
- /* 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)));
- }
-