[From Bill Powers (941106.0715 MST)]
Progress on the screen plotting Unit. It can still use some work but it's
at the point now where I'm more interesting in using it than developing
it further, so I'll pass it on.
The plots are organized around a "frame" which is basically a record
showing how a rectangular area of the screen is to be used for plotting
up to three variables against time or a fourth variable. Here is the
record for a frame:
type frametype = record
mx,my: integer;
numyvars: integer;
xbase,ybase: integer;
xsize,ysize: integer;
xgrid,ygrid: integer;
xzero,yzero: integer;
xmax: real;
ymax: array[1..3] of real;
xscale: real;
yscale: array[1..3] of real;
xvar: ^real;
yvar: array[1..3] of ^real;
color: array[1..3] of integer;
ylegend: array[1..3] of string[20];
xlegend: string[20];
end;
mx,my: Load with maxx and maxy, the maximum screen dimensions obtained
from getmaxx and getmaxy.
numyvars: The number of y-axis variables to be plotted (there is only
one x-axis variable).
xbase,ybase: the location of the lower left corner of the plotting axes,
measured from the LOWER LEFT CORNER of the screen. In placing
this point, be aware that the legends for the y axis, which are
written vertically, take up 10 pixels of space for each one to the
left of the y axis, plus a space of 10 pixels. So if there are 3 y
variables, xbase must be located 40 pixels or more from the left
edge of the screen or the right edge of another plot. There is no
error checking to see if the whole frame with legends will lie on
the screen. If the plot extends off the screen in any direction,
it may show up in a strange position. The program uses setviewport
WITHOUT clipping.
There is a single x-axis legend which requires 10 pixels below the
x-axis. The same considerations hold.
xsize,ysize: The x and y dimensions of the plotting area IN PIXELS.
numxgrid,numygrid: the number of dark-gray grid lines. Every 10th grid
line is marked with a short tick, every 5th with a long tick. To
make the grid lines come out even, the number of grid lines should
be an exact divisor of the corresponding size.
xgrid,ygrid: calculated by the program; size of grid spacing in pixels.
xzero, yzero: the location IN PIXELS of the x and y zero lines. This
should be an even multiple of xgrid and ygrid. The corresponding
grid line is shown in white. The variables are plotted relative to
this zero line. As usual, x and y measurements are from the lower
left corner, the conventional Cartesian origin.
xmax,ymax: This is a real-type entry specifying the value of the variable
that correponds to the rightmost or topmost pixel of the plot.
From this a scaling factor is computed which automatically fills
in xscale and yscale. You do not need to initialize xscale and
yscale. ymax is an array of 3 reals, but you need initialize only
the number of them corresponding to numyvars. xmax is a single
real, since there is only one x variable.
xvar,yvar: These are pointer to reals, yvar being an array of 3 pointers.
Here you load the addresses of the variables you want to plot. If
you're plotting the variable "dist" in your program as the first
variable, then you say frame.yvar[1] := @dist. To make a time
plot, define a timing variable as a real and increment it by 1.0
to advance the time by 1 unit. If the variable is called "clock",
then say frame.xvar := @clock.
NOTE THAT ALL PLOTTED VARIABLES MUST BE REALS. If you want to plot
an integer variable, you must assign it to a real variable and
plot the real variable. This applies to both x and y.
color: this array of three integers specifies the colors for the three y-
variable plots. To make the second y variable light red, say
frame.color[2] := lightred.
xlegend: an array of three strings of maximum length 20. These legends
are written vertically to the left of the y-axis, justified to the
origin. Each is written in the color of the corresponding variable
trace. Also, justified to the top of the y axis on the same
vertical line and also in the correct color, ymax is written.
xlegend: a single string that labels the x axis, written centered. The
value of xmax is written on the same line, right-justified.
There are three procedures declared in the interface of the Unit, along
with the type definition of a frame, above:
procedure initframe(var frame: frametype);
procedure plotvar(var frame: frametype);
procedure clrplot(var frame: frametype);
Calling initframe draws the frame and its legends, with the grid, on the
screen. Calling plotvar plots all the points that have been designated,
counting them by using frame.numyvars. Calling clrplot clears just the
plotting area, leaving the legends alone, and redraws the grid and ticks.
Because a frame is loaded with pointers to the variables to be plotted,
it is not necesary to specify the variables when calling plotvar. The
loaded addresses are like probes that connect the plotting area to any
variable in your program. Once the probe is in place, plotting of that
variable takes place every time you call plotvar.
All of the procedures take a variable of type pointer-to-frame as an
argument. This means that you can specify an array of frames such as
frame: array[1..MAXFRAME] of frametype;
The definition of frametype will be picked up from Unit frameplt.tpu.
Each frame record must be loaded with its proper variables. You can then
call the frame procedures with the pointer to the frame to be plotted, as
in
for i := 1 to MAXFRAMES do
plotvar(frame[i]).
The argument is defined as a "var" or pointer type; the address of that
frame will be passed. So once the frames are loaded with variable values,
it is simple to initialize them all, plot the variables in all of them,
and clear all of them.
When using time as the x variable, in order to keep all the plots
synchronized you will want to make all the xmax values the same in
different frames:
for i := 0 to MAXFRAMES do frame[i].xmax := maxtime;
When ANY of the variables xvar^ reaches the screen limit, you will want
to do a clrplot on ALL frames and reset the clock variable. However, you
can manage this any way you like, since xvar is simply a variable. If it
doesn't represent time, it can represent any other variable, so you can
do x-y plots (with multiple variables for y).
Note that the plots automatically scale with changes in xsize and ysize.
If you multiply ysize by 1.5, the y-axis becomes 1.5 times as tall, the
grid lines move 1.5 times as far apart, and the y dimension of the plot
stretches by a factor of 1.5. In a later version of this Unit, which
perhaps someone else will kindly write, it will be possible to size and
move the frames using a mouse.
Here is the code for the Unit Frameplt.pas:
···
======================================================================
Unit frameplt;
{
Unit for plotting up to three variables against time or another variable
in a frame of arbitrary size and scaling with a grid background and
an adjustable zero axis in x and y.
}
interface
Uses crt,graph,grUtils;
type realarrayptr = array[0..3] of ^real;
type frametype = record
mx,my: integer;
numyvars: integer;
xbase,ybase: integer;
xsize,ysize: integer;
numxgrid,numygrid: integer;
xgrid,ygrid: integer;
xzero,yzero: integer;
xmax: real;
ymax: array[1..3] of real;
xscale: real;
yscale: array[1..3] of real;
xvar: ^real;
yvar: array[1..3] of ^real;
color: array[1..3] of integer;
xlegend: string[20];
ylegend: array[1..3] of string[20];
end;
procedure initframe(var frame: frametype);
procedure plotvar(var frame: frametype);
procedure clrplot(var frame: frametype);
implementation
procedure makegrid(var frame: frametype; delta: integer);
var j,xoffset: integer;
ch: char;
temp: real;
numstr: string[10];
legstr: string[50];
begin
with frame do
begin
xgrid := xsize div numxgrid;
ygrid := ysize div numygrid;
xoffset := numyvars*delta;
setviewport(xbase - xoffset, my - ybase - ysize,
xbase + xsize, my - ybase,false);
for j := 0 to numygrid do
begin
if j = yzero div ygrid then setcolor(white) else setcolor(darkgray);
line(xoffset,ysize - j*ygrid,
xsize + xoffset, ysize - j*ygrid);
end;
setcolor(white);
for j := 0 to numygrid do
if (j mod 10) = 0 then
line(xoffset,ysize - ygrid*j,xoffset + 8,ysize - ygrid*j)
else
if (j mod 5) = 0 then
line(xoffset,ysize - ygrid*j,xoffset + 4,ysize - ygrid*j);
for j := 0 to numxgrid do
begin
if j = xzero div xgrid then setcolor(white) else setcolor(darkgray);
line(xoffset + j*xgrid, ysize-1,xoffset + j*xgrid, 0);
end;
setcolor(white);
for j := 0 to numxgrid do
if (j mod 10) = 0 then
line(xoffset + xgrid*j,ysize,xoffset + xgrid*j,ysize - 8)
else
if (j mod 5) = 0 then
line(xoffset + xgrid*j,ysize,xoffset + xgrid*j,ysize - 4);
if(delta > 0) then
begin
settextstyle(DefaultFont,VertDir,1);
for j := 1 to numyvars do
begin
settextjustify(TopText,LeftText);
setcolor(color[j]);
outtextxy(delta*(j-1),ysize,ylegend[j]);
settextjustify(TopText,RightText);
str(ymax[j]:3:1,numstr);
outtextxy(delta*(j-1),0,numstr);
end;
settextstyle(DefaultFont,Horizdir,1);
settextjustify(CenterText,TopText);
setcolor(white);
outtextxy(xsize div 2,ysize + 2,xlegend);
settextjustify(RightText,TopText);
str(xmax:5:1,numstr);
outtextxy(xsize + xoffset,ysize + 2 ,numstr);
end;
settextjustify(LeftText,TopText);
setcolor(white);
setviewport(0,0,mx,my,true);
end;
end;
procedure initframe;
var delta,i: integer;
begin
with frame do
begin
delta := textheight('H') + 2;
for i := 1 to numyvars do
yscale[i] := ysize/ymax[i];
xscale := xsize/xmax;
makegrid(frame,delta);
end;
end;
procedure clrplot;
begin
with frame do
begin
setviewport(xbase, my - ybase - ysize,
xbase + xsize, my - ybase,false);
clearviewport;
makegrid(frame,0);
end;
end;
procedure plotvar;
var i: integer;
begin
with frame do
begin
for i := 1 to numyvars do
putpixel(xbase + xzero + round(xvar^*xscale),
my - ybase - yzero - round(yvar[i]^*yscale[i]),
color[i]);
end;
end;
end.
Best,
Bill P.