TEACH RC_GRAPHPLOT David Young, Nov 1990 Revised: Adrian Howard, Jul 1993 LIB * RC_GRAPHPLOT is a Pop-11 library for drawing graphs of functions and data, with axes and annotations. This file provides an introduction to it. See HELP * RC_GRAPHPLOT for a summary. It uses LIB * RC_GRAPHIC, a more general graphics interface which at the time of writing is interfaced to the X Windows system. You may occasionally want to refer to TEACH * RC_GRAPHIC for more information about this, but the graph plotting library can be used with no knowledge of the underlying system. There is a simpler routine available as LIB * RC_DRAWGRAPH (see TEACH * RC_GRAPHIC) which is smaller and faster to compile. However, it lacks most of the functionality of this package - for instance, it will not do automatic scaling or axis annotations. CONTENTS - (Use g to access required sections) 1 Introduction 2 Getting started and finishing off 3 Plotting a function on the y-axis 4 Plotting data on the y-axis 5 Plotting a function or data on the x-axis 6 Plotting x and y together - separate data sets or functions 7 Plotting x and y together - combined data set or function 8 Plotting points instead of lines 9 Drawing several graphs on the same axes 10 Setting axis limits explicitly 11 The window region 12 Setting the window region 13 Changing the axis style 14 Drawing on graph paper 15 Finding data limits without plotting 16 Note regarding functions with side effects 17 Drawing graphs in several windows at once 18 Other changes to graph style 19 Drawing graphs in XpwGraphic widgets ----------------------------------------------------------------------- 1 Introduction ----------------------------------------------------------------------- There is one main procedure, which is called once to produce each graph. The function or the data for the graph can be supplied in a variety of formats. (A slightly different version of the main procedure has to be used for some kinds of x-y data or functions.) There are many global variables, to which you can assign new values in order to change the kind of graph produced, but which for many purposes can be ignored. You will need to know how to open, close, raise and lower windows in the windowing system you are using, using the mouse or keyboard. ----------------------------------------------------------------------- 2 Getting started and finishing off ----------------------------------------------------------------------- The examples in this file can be tried out by marking and loading them - see TEACH * MARK and TEACH * LMR. The first thing to do is to make sure the X facilities are available by loading this line: uses popxlib; See the "Getting started" section in TEACH * RC_GRAPHIC for more information about this step. Then load the graph plotting library with uses rc_graphplot; This may take some time, if X facilities need to be loaded. Start up a window with: rc_start(); or if you prefer a different shape use rc_new_window - see TEACH * RC_GRAPHIC. You can also change the shape and position using the mouse. If, when you have finished drawing graphs or doing other graphics, you want to get rid of the window, you can assign false to the variable rc_window. The window will disappear at the next garbage collection, and in the meantime you can close (iconify) the window with the mouse. Alternatively, you can do XptDestroyWindow(rc_window); to destroy it immediately (see * XptDestroyWindow.) ----------------------------------------------------------------------- 3 Plotting a function on the y-axis ----------------------------------------------------------------------- Suppose, for the sake of example, you want a graph of the logarithm function, for values of the independent variable from 1 to 10. This does it: vars region; rc_graphplot( 1, 1/10, 10, 'X', log, 'log(X)') -> region; The first three arguments say that you want the function to plotted with the independent variable going from 1 to 10 in steps of 0.1. The order of these arguments is like a Pop-11 for loop, that goes for x from xmin by xinc to xmax do i.e. the first is where to start, the second is how much to jump for each new point, and the third is where to stop. The third argument, 'X', is a string that is to printed as a label on the horizontal axis (usually called the x-axis). The fourth argument is just the function to be plotted. In this case it's available as part of the Pop-11 system. The final argument is the label to be printed on the vertical axis (the y-axis). You can ignore the result, stored here in region, for now. Here is another example, using a function defined specially. define myfunc(x) -> y; ;;; A function to be plotted - actually a polynomial lvars x y; 2 * x**4 + x**3 - 16 * x**2 + 50 -> y enddefine; rc_graphplot( -3, 1/10, 3, 'X', myfunc, 'Y') -> region; ----------------------------------------------------------------------- 4 Plotting data on the y-axis ----------------------------------------------------------------------- Suppose you have done an experiment and recorded some data you would like to plot, and that you have stored them in a Pop-11 data structure such as a list, a vector, an array, or a string. To simulate this, load these two lines of code: vars mydata; [13 12 7 3 1 1 19 29 25 20 undef 15 10 3 -3 2 1] -> mydata; The "undef" value means that something went wrong and there is no value for this time. Let's say the data were collected at 5-second intervals, starting at 20 seconds into the experiment. This would then be an appropriate call to the routine: rc_graphplot( 20, 5, 100, 'time (s)', mydata, 'Data') -> region; The arguments are just the same as for plotting the function, except that the list (or vector etc.) of data goes in place of the function name. See below for how to change from plotting a line to plotting data points as individual symbols. ----------------------------------------------------------------------- 5 Plotting a function or data on the x-axis ----------------------------------------------------------------------- So far, the data have been plotted as y-values, with x as the independent variable. To have y independent, you give the arguments to graphplot with the function or data first, e.g.: rc_graphplot( log, 'log(Y)', 1, 1/10, 10, 'Y') -> region; or rc_graphplot( mydata, 'Data', 20, 5, 100, 'time (s)'); ----------------------------------------------------------------------- 6 Plotting x and y together - separate data sets or functions ----------------------------------------------------------------------- If you have two sets of data, and you want to plot one set against the other, you simply pass these to graphplot with the x data first, each set followed by its label. The data can be lists, vectors, arrays, or any other subscriptable structure. In this example, the two vectors might represent the x and y coordinates of moving object, for example. vars xvals, yvals; { 2 3 2 3 5 7 8 7 8 6 4} -> xvals; {20 24 28 30 31 28 33 26 19 23 24} -> yvals; rc_graphplot(xvals, 'X', yvals, 'Y'); Often such data need to be plotted as individual points rather than joined with lines. For how to do that, see below. If either x or y is a procedure rather than a data structure, or both x and y are procedures, then some extra arguments are necessary to provide their input values. These must be given before the x and y specifications, in exactly the same way as x and y would be. Here is an example, defining a couple of demonstration functions first (their details don't matter, but note that each takes one argument and returns one result): define x_func(t) -> x; lvars x t; cos(11 * t) -> x enddefine; define y_func(t) -> y; lvars y t; sin(7 * t) -> y enddefine; Now plot one against the other, varying their input parameter t from 0 to 360 degrees in 1-degree steps (* popradians should be false for this): rc_graphplot(0, 1, 360, x_func, 'X', y_func, 'Y') -> region; (The first three arguments here could be replaced by a data structure giving the parameter values, or by another function of no arguments and one result, which must return termin when finished.) You can plot a function on one axis against data on the other. ----------------------------------------------------------------------- 7 Plotting x and y together - combined data set or function ----------------------------------------------------------------------- Sometimes, it may be more convenient to store x-y data as a single data structure containing pairs of values, or to plot the results of a single function which returns x and y values together. To do this, you will need to load a different version of graphplot - LIB * GRAPHPLOT2, thus: uses rc_graphplot2 This does not stop you using graphplot as before - it just makes an extra procedure available, which is largely just the same as graphplot, and is controlled by the same globals. If you just want graphplot2, you can load it without graphplot. If you have already loaded graphplot, graphplot2 will be quick to load. Suppose the x-y data has been stored as a list of vectors (but it could have been a list of lists, a vector of vectors, an array of pairs, or what you will). Here's some simulated data - actually the same values as we had in the previous example - followed by the call to graphplot2: vars xyvals; [{2 20} {3 24} {2 28} {3 30} {5 31} {7 28} {8 33} {7 26} {8 19} {6 23} {4 24}] -> xyvals; rc_graphplot2( xyvals, 'X', 'Y') -> region; Now the two labels follow the single data set in the calling sequence. Alternatively, the values could have been stored just as numbers in the list (or whatever) with x and y values alternating. [2 20 3 24 2 28 3 30 5 31 7 28 8 33 7 26 8 19 6 23 4 24] -> xyvals; rc_graphplot2( xyvals, 'X', 'Y') -> region; Finally, a function that takes one argument (a parameter such as time) and returns two results, x and y, can be plotted. Here is a definition of such a function, and the call to graphplot2 to plot it: define xy_func(t) -> (x,y); lvars t x y; t * cos(t) -> x; t * sin(t) -> y enddefine; rc_graphplot2(0,2,3200, xy_func, 'X', 'Y') -> region; If you find that this causes a mishap with the message "ROM: MEMORY LIMIT (popmemlim) EXCEEDED", don't worry - it's just that the procedure has to store a lot of values between finding the scales and plotting the curve. Increase the value of the variable * popmemlim (say add 100000 to it) and do the example again. ----------------------------------------------------------------------- 8 Plotting points instead of lines ----------------------------------------------------------------------- You might prefer to plot points on the graph instead of drawing lines between the data points or function values. This can be done by changing the value of one of the many controlling global variables, like this: "plus" -> rcg_pt_type; rc_graphplot(xvals, 'X', yvals, 'Y') -> region; You will find that the individual points are plotted with "plus" signs. Other words that you can assign to rcg_pt_type are "square", "cross", "plus" and "circle". More might be added in due course. You can assign a plotting procedure of your own to rcg_pt_type - it must take two arguments, x and y. To get back to drawing lines, do this: "line" -> rcg_pt_type; You can assign a number to rcg_pt_cs to change the size of the symbol. ----------------------------------------------------------------------- 9 Drawing several graphs on the same axes ----------------------------------------------------------------------- So far, the program has started setting the scales from scratch for each graph, and has cleared the window between graphs. Often, however, you will want to put several curves or sets of data on the same axes. Let's plot some data, and a curve (the square root function) that happens to fit them approximately, on the same axes. We start by setting up some more synthetic data and plotting them. vars moredata; [0.1 0.9 1.3 1.8 1.95 2.2 2.5 2.9 3.1 3.2 3.4 3.45 3.5] -> moredata; "cross" -> rcg_pt_type; rc_graphplot(0, 1, 12, 'X', moredata, 'Y') -> region; (You may have noticed that the crosses at the ends of the graph get cut in half by clipping. You can avoid this either by lengthening the x-axis (see below for how to do it), or by assigning false to rc_clipping.) We'll now change the plotting style so that the theoretical function comes out as a line: "line" -> rcg_pt_type; ;;; make it look different Now we change some more global variables and plot the curve: false -> rcg_newgraph; ;;; don't clear the window region -> rcg_usr_reg; ;;; use same axes as before undef -> rcg_win_reg; ;;; use same bit of window as before rc_graphplot(0, 1/10, 12, false, sqrt, false) -> region; These three assignments do slightly different things, to allow for the wide variety of different situations in which the package might be used. This may seem over-complex, but it allows very accurate control by the user. For more details of what these variables do, see below and HELP * RC_GRAPHPLOT; for straightforward use the recipe above will suffice. Setting the labels in the call to graphplot to false means that the axes are not redrawn. You can plot as many graphs on the same axes as you like. Clipping is normally used to stop new curves or data points going into the border, but you can allow this to happen by assigning false to rc_clipping. To start a new set of axes with automatic scaling, reset the global variables like this: true -> rcg_newgraph; ;;; clear the window for each graph undef -> rcg_usr_reg; ;;; find region from the data 0.1 -> rcg_win_reg; ;;; map onto window Do this before running the following examples. ----------------------------------------------------------------------- 10 Setting axis limits explicitly ----------------------------------------------------------------------- So far, graphplot has set the positions and lengths of the axes for us automatically. Skip this section unless you have layout needs which are not met by the behaviour you've encountered so far. You can override the default behaviour like this: [-10 20 -5 5] -> rcg_usr_reg; ;;; [xmin xmax ymin ymax] rc_graphplot(0, 1/10, 12, 'X', sqrt, 'sqrt(X)') -> region; This says that you want the x-axis to go from -10 to +20, and the y-axis to go from -5 to +5, even though you have actually asked for the curve to be plotted over a smaller range of values than this. You can put some undef values in the list for rcg_usr_reg, and the corresponding limits will be determined from the data. For example, this plots the first data set we used, with a longer x-axis, from 0 to 120, but still with automatic setting of the y-axis length. [0 120 undef undef] -> rcg_usr_reg; rc_graphplot( 20, 5, 100, 'time (s)', mydata, 'Data') -> region; To go back to fully automatic setting, assign undef to the variable: undef -> rcg_usr_reg; ;;; find region from the data ----------------------------------------------------------------------- 11 The window region ----------------------------------------------------------------------- When a graph is displayed on the screen it alters the coordinate frame of the window to match that of the graph. This allows you to overlay graphics using LIB * RC_GRAPHIC using the same coordinate system as the graph. When a graph is displayed the following global variables will be affected: rc_xmax rc_xmin rc_xorigin rc_xscale rc_ymax rc_ymin rc_yorigin rc_yscale For details see HELP * RC_GRAPHIC. If you wish to store the old coordinate frame, you can use LIB * RC_CONTEXT, see HELP * RC_GRAPHIC. ----------------------------------------------------------------------- 12 Setting the window region ----------------------------------------------------------------------- So far we have allowed graphplot to fit the graphs neatly into the window. Again, skip this section if that suffices for you. You can change the size of the border left round the graph area, by assigning to rcg_win_reg a number giving the fraction of the window to leave as a border, like this: 0.3 -> rcg_win_reg; rc_graphplot(0, 1/10, 12, 'X', sqrt, 'sqrt(X)') -> region; You can be more explicit, if you remember that window coordinates have their origin at the top left of the window, and that y runs from top to bottom and x from left to right. Given this, you can tell graphplot to use a region specified in these coordinates, like this: [inches 3 5 4 2] -> rcg_win_reg; rc_graphplot(0, 1/10, 12, 'X', sqrt, 'sqrt(X)') -> region; The first element of the list gives the units - see HELP * RC_GRAPHIC/RC_SET_SCALE for how this works. If you omit the first element, the values are taken to be in pixels. If rc_win_reg is set to undef, then the current user coordinates are used for the graph - the program doesn't worry about where these place the graph physically on the screen. Back to normal now: 0.1 -> rcg_win_reg; ----------------------------------------------------------------------- 13 Changing the axis style ----------------------------------------------------------------------- By default, a pair of axes is drawn. You can switch to a box instead: "box" -> rcg_ax_type; rc_graphplot(0, 1, 360, x_func, 'X', y_func, 'Y') -> region; To go back to axes assign the word "axes" to rcg_ax_type; to switch off axis drawing altogether, assign false to the variable. You can switch off individual axes in the call to graphplot, by giving false as the label argument. If you want an axis but without a label, pass nullstring as the label argument. You can also change the number and length of the tick marks, and the axis numbering - see below and HELP * RC_GRAPHPLOT. ----------------------------------------------------------------------- 14 Drawing on graph paper ----------------------------------------------------------------------- As an example of changing the appearance using the global variables, this section shows how you can provide a grid under the graph by a few assignments before calling rc_graphplot. An example (ensure * popradians is false): "box" -> rcg_ax_type; ;;; Box is needed 1 -> rcg_tk_len; ;;; Make the tick marks the full size 1 -> rcg_mk_len; ;;; Make the main marks full size 2 -> rcg_mk_lw; ;;; emphasise the main marks 2 -> rcg_pt_lw; ;;; thicken up the curve rc_graphplot(-360, 1, 360, 'X', cos, 'cos(X)') -> region; Back to normal: "axes" -> rcg_ax_type; 0.01 -> rcg_tk_len; 0.02 -> rcg_mk_len; 0 -> rcg_mk_lw; 0 -> rcg_pt_lw; rc_graphplot(-360, 1, 360, 'X', cos, 'cos(X)') -> region; ----------------------------------------------------------------------- 15 Finding data limits without plotting ----------------------------------------------------------------------- You may wish to simply find out what the limits of your data are, without drawing a graph or changing the window scales. To do this, switch off all of graphplot's side effects, and just look at the result it returns, which gives the limits of the axes it would have used. false -> rcg_newgraph; ;;; don't clear window false -> rcg_pt_type; ;;; don't plot data false -> rcg_ax_type; ;;; don't draw axes undef -> rcg_win_reg; ;;; don't change scale rc_graphplot(0, 1, 12, 'X', moredata, 'Y') -> region; region => ** [0 12 0 4] The list gives the minimum and maximum x values, then the minimum and maximum y values for the graph. Note, however, that by default the dependent variable's limits are rounded out so that the axes can begin and end on roundish numbers. To switch this behaviour off, do this: false -> rcg_ax_space; Now we obtain the exact data limits - note the change in the y values: rc_graphplot(0, 1, 12, 'X', moredata, 'Y') -> region; region => ** [0 12 0.1 3.5] If you try plotting graphs with rcg_ax_space set false, you will find that the curve or data points will go right up to the axis limits - usually this is ugly. The variable in question says roughly what fraction to add to the axes when their lengths are rounded up. Back to normal: 0.2 -> rcg_ax_space; true -> rcg_newgraph; "line" -> rcg_pt_type; "axes" -> rcg_ax_type; 0.1 -> rcg_win_reg; ----------------------------------------------------------------------- 16 Note regarding functions with side effects ----------------------------------------------------------------------- Some users will want to pass functions to graphplot that have side effects - e.g. they may remember previous results using global variables, or monitor the state of some other system such as a neural network or an operating system parameter. Such functions will normally cause no problems for graphplot - it will call them once only for each point to be plotted, even when automatic scaling is in use. Here's an example, where a global variable is used to generate a random walk in one dimension - the graph goes up or down at random on each step. vars r = 0; ;;; random variable - start at zero define ranwalk(t) -> y; ;;; t is ignored. lvars t y; r + 2 * random0(2) - 1 -> r; ;;; update r: 1 up or 1 down r -> y ;;; return it enddefine; rc_graphplot(1,1,200, 't', ranwalk, 'random'); If automatic scaling is in use, the values are stored until the graph is ready to be drawn; if the scales have been specified in advance (see above), each value will be plotted as soon as it is available. If two functions are being plotted, and automatic scaling is in use for x, then graphplot will find all the x-values before any of the y-values. If y is automatically scaled but x is not, then all the y-values will be found first. If there is no automatic scaling, graphplot will call the x function and the y function alternately. If these things matter, use graphplot2, which always takes the values in pairs. ----------------------------------------------------------------------- 17 Drawing graphs in several windows at once ----------------------------------------------------------------------- Before doing this, remember that you can display several graphs in one window, either on the same axes or on different axes in different regions of the window, using the facilities described above. If you want to use several windows, see TEACH * RC_GRAPHIC and HELP * RC_GRAPHIC, if the example below is not clear. Note that a window must be available before you call rc_graphplot, because it saves the current line settings for the existing window as soon as it is called. This example plots two graphs in two windows, assuming that the first window is already open (if you have worked through this file, you did that in the "getting started" section). In this example, the second window is put to the left of the first one on the screen - this is easily changed. The window you are reading is likely to be obscured - make sure you know how to make it reappear using the mouse. ;;; needed to hold the window identifier vars window1; rc_graphplot(1, 1/10, 10, 'X', log, 'log(X)') -> region; rc_window -> window1; ;;; save the current window as "window1" false -> rc_window; ;;; make a new one on the next call rc_window_x - rc_window_xsize -> rc_window_x; ;;; go left rc_start(); ;;; create the new window rc_graphplot(1, 1/10, 10, 'X', exp, 'exp(X)') -> region; To go back to the first window, you could do this: vars window2; rc_window -> window2; ;;; save the current window window1 -> rc_window; ;;; draw in first window rc_graphplot(1, 1/10, 10, 'X', sqrt, 'sqrt(X)') -> region; To restore the environment associated with a window (scales, positions and so on) see HELP * RC_GRAPHIC/RC_CONTEXT. To use LIB * RC_CONTEXT with LIB * RC_GRAPHPLOT, you may want to extend the list rc_context_vars with the graphplot variables - see HELP * RC_GRAPHPLOT for a list of these. You can use * XptDestroyWindow to remove the window (see "Getting started and finishing off".) ----------------------------------------------------------------------- 18 Other changes to graph style ----------------------------------------------------------------------- The numerical axis annotations are done using * pr, so you can redefine that or change the global variables associated with it to alter the way the numbers are printed. You can change the line-drawing behaviour of all the components (the curve itself and the bits of the axes) by assignments to global variables. Possibly most useful are those that change the number of axis marks and ticks. See HELP * RC_GRAPHPLOT for more details. For changing colour, and the like, see TEACH * RC_GRAPHIC - at present you can only have one colour for each call of RC_GRAPHPLOT. ----------------------------------------------------------------------- 19 Drawing graphs in XpwGraphic widgets ----------------------------------------------------------------------- This section can be skipped if you are not interested in using LIB * RC_GRAPHPLOT with widgets you have created in your own X applications. LIB * RC_GRAPHPLOT expects rc_window to hold an XpwGraphic widget for it to draw in. You can set this to a widget in your own application, rather than using the one created for you by rc_start. First we create a procedure to draw a graph as normal. define draw_graph(); ;;; NEED TO DLOCAL ALL OF THE RC_GRAPHIC VARIABLES THAT WE ;;; MODIFY SO THEY DON'T AFFECT OTHER CALLS TO rc_graphplot dlocal rcg_ax_type, rcg_tk_len, rcg_mk_len, rcg_mk_lw, rcg_pt_lw, rcg_win_reg; ;;; SET THE REGION OF THE WINDOW THAT WE ARE PLOTTING IN [pixels 60 390 10 290] -> rcg_win_reg; ;;; SET SOME OTHER RC_GRAPHPLOT VARIABLES "box" -> rcg_ax_type; ;;; Box is needed 1 -> rcg_tk_len; ;;; Make the tick marks full size 1 -> rcg_mk_len; ;;; Make the main marks full size 2 -> rcg_mk_lw; ;;; emphasise the main marks 2 -> rcg_pt_lw; ;;; thicken up the curve ;;; CALL RC_GRAPHPLOT WITH THE GRAPH YOU WISH TO DRAW rc_graphplot(-360, 1, 360, 'X', cos, 'cos(X)') -> ; enddefine; Then we define a procedure which will locally redefine rc_window, and draw a graph with a procedure we supply. ;;; DRAW A GRAPH USING PROCEDURE p IN THE WIDGET w define plot_with_widget(p, w); lvars p, w; ;;; MAKE SURE rc_window HOLDS OUR dlocal rc_window = w; p(); enddefine; We can then make out own widget ;;; INITIALIZE THE TOOLKIT XptDefaultSetup(); uses xt_widget; uses xtApplicationShellWidget; uses xpwGraphicWidget; ;;; CREATE A SHELL WIDGET vars shell = XtAppCreateShell( 'test', 'Test', xtApplicationShellWidget, XptDefaultDisplay, [] ); ;;; CREATE A GRAPHICS WIDGET IN THE SHELL vars graphic = XtCreateManagedWidget( 'graphic', xpwGraphicWidget, shell, [{width 400} {height 300}] ); ;;; PUT THE WIDGET ON SCREEN XtRealizeWidget(shell); and draw a graph in it. ;;; DRAW THE GRAPH plot_with_widget(draw_graph, graphic); See also TEACH * Xpw. --- C.x/x/pop/teach/rc_graphplot --- Copyright University of Sussex 1990. All rights reserved.