Introduction This manual introduces a subset of the powerful graphics functions which are part of the BASIC programming language as implimented on Hewlett-Packard Series 200/300 Technical Computers. This manual introduces these functions and explains their usage. Also provided are a set of example program which illustrate some of the features of these functions. The functions do not in themselves constitute a complete program. Rather, they are used to suppliment a program where graphical data is to be presented. These functions are distinct from the functions provided in the Microsoft v. 5.1 C compiler in that they scale the screen into units defined by the user. The programmer need not know what the screen size is in pixels to use these routines, and, in fact, these routine are designed to make a program independent of the hardware and screen mode used. It should be pointed out that, like all graphical representations, these routines perform best with high resolution screens. They will function with any screen resolution available, but line resolution improves as screen resolution increases. Thus, for best results, one would normally use the highest resolution screen available. Why the Hewlett-Packard Workstation BASIC Graphics? There are three reasons why I chose to impliment this particular set of graphics primitives. First, I was porting some applications written in HP workstation BASIC to an IBM PC compatible computer. Since these programs had extensive graphical displays and would not be nearly as useful without them, it was manditory to develop these graphic primitives. Second, it is a well thought out and utilitarian set of 2-dimensional graphics primitives. It is well suited to presentation of technical information and quite flexible. The functions are intuitive and easily incorporated into the original programs, and much of that has carried over into this library. And lastly, the graphics primitives provided with Microsoft's verion 5.1 C compiler are difficult to use. They require the user to keep track of his scaling and units. Although they can do quite a bit, that nagging problem of converting from user units to pixels is left to the user. Add to that the inflexible labeling demands of the PC text interface (it is not possible to place labels arbitrarily because the text interface is imbedded in the graphics modes: you must label in text rows and columns) and the lack of any ability to rotate the text or change font size without loading completely new fonts made it obvious that ignoring most of Microsoft's and the PC's graphics functions would be the best way to proceed. Why Use Graphics? The use of graphics to display information is unsurpassed in intuitive clarity. Trends and patterns are much more readily recognized on a graph than in tabular form. For example, examine the following lexical data and try to visualize the shape of the data: X Y 0 0.09 20 0.42 40 0.71 60 0.91 80 0.99 100 0.97 120 0.82 140 0.57 160 0.26 180 -0.09 200 -0.42 220 -0.71 240 -0.91 260 -1.00 280 -0.97 300 -0.82 If this were displayed on a graph, it would be immediately obvious that this is a sinusoid. Line Drawing To draw lines, one can simple draw () or plot () the points. The following short program illustrates this. #include #include #include "graph.h" double x; main () { g_init (EGA_640x350); for (x = 0; x < 70; ++x) plot (x, 45 + sin (x/100) + x/10, 1); } This program illustrates the most basic program one can write. The call to g_init () initializes the graphics routines and sets the screen to graphics mode. This is always the first function called before attempting to plot anything. The plot () function does exactly what its name implies. It draw a line from the last pen position to the current pen position using the current pen color. In this case, a pen control parameter is included. The value 1 causes the plotting program to initially move the pen and then drop the pen. Once the pen is down, it remains down and the plot is drawn. The constant 45 is used to center the y-values. When the plotting program is initialized, the screen default scaling is in Graphics Display Units (GDU's). GDU's have an origin in the lower left corner of the CRT and have values from 0 to 100 for the shorter side of the CRT (usually the y- direction). The value for the longer side of the CRT depends on the hardware and drawing mode. This can range from 100 to 150 or higher. In this example, it was assumed that the screen was 100 GDU's tall and 140 GDU's wide. Thus, this program will draw a sinusoid 2 GDU's tall over 1/2 of the screen. Scaling A Plot If you were to execute this program as it is written, the results would be disappointing. There would be almost nothing to see. However, if the Y scale is changed, the variations would become much more visible and would better represent the data. For this problem, we have scaling functions. These functions define values which the computer considers to be the edges of the plotting area. By definition, the left edges is the minimum x- value and the right edges is the maximum x-value. Similarly, the bottom edges is the minimum y-value and the top edge represents the maximum y-value. Any point plotted within these ranges will be visible. There are three functions which can be used to define the edges of the plotting area: show (), scale (), and mscale (). The first function, show (), is an isotropic scaling function. This type of scaling is most useful when displaying geometric data. show () forces the physical representation of the x-units and y-units to be equal. If the plotting area is square and one uses show (0, 100, 10, 30) to scale that area, the minimum x-value would be zero, the maximum x-value would be 100, the minimum y-value would be -30, and the maximum y-value would be 70. The reason for this is that the x- and y- units are identical. The show () function centers the specified area in the plotting area and takes whatever extra room it requires to make the units equal. Depending on the shape of the plotting area and the scaling values, there will always be extra room in either the x- or y-direction, but never both. Once the show () function has been executed, the plotting area is defined in User-Defined Units (UDU's). If we re-write the example program, #include #include #include "graph.h" double x; main () { g_init (EGA_640x350); show (0, 70, -1, 8); for (x = 0; x < 70; ++x) plot (x, sin (x/100) + x/10, 1 ); } the resulting display would fill the width of the screen, but the vertical scaling would remain unacceptable. To improve the vertical resolution, one must resort to anisotropic scaling using the scale () function. Where the show () function forced the scaling to be equal in both directions, scale () will not. Thus, #include #include #include "graph.h" double x; main () { g_init (EGA_640x350); scale (0, 70, -1, 8); for (x = 0; x < 70; ++x) plot (x, sin (x/100) + x/10, 1 ); } will display the data over the entire screen. This is rather nice, but there is no room for labeling the drawing. We can see the relative variations the plot, but we have no indication as to what units are being used and what the magnitude is. To adequately annotate the plot, we would like to put labels outside the plotting area so as to leave a clear view of the data. Defining Viewports A viewport is a subarea of the plotting area. The borders of the viewport are called the soft clip limits, and the subarea is called the soft clip area. The word clip implies that any line which is draw to a point outside this border will be cut off at the border (clipped). They are called soft limits because they can be overridden. To better understand this concept, imagine a piece of paper with a rectangle drawn on it. We can draw within the rectangle at any time. We can only draw outside the rectangle when permission is granted. Permission can be granted by the clip_off () function, and permission can be denied with the clip_on () function. The edges of the paper represent the hard clip limits. These can not be overridden. We can also change the size of the soft clip limits using the clip () function. The clip function units are in UDU's. For a given hardclip limit on the CRT, the GDU's are fixed and always predictable. It is for this reason that they are used by the locate () function. One GDU is defined as "One percent of the length of the shorter side of the plotting area." GDU's are always isotropic: one GDU in the x-direction is the same size as one GDU in the y-direction. However, because of the rectangular pixels on most PC graphics screens, the number of pixels/GDU will not be the same in the x-directions as it is in the y-direction. At this point, we will define a viewport for our example program. At the same time, we will introduce the frame () function. The frame function is used to draw a border around the soft clip boundary. When g_init () is first called, the hard clip and soft clip borders are identical. Hence, calling frame () before a viewport is defined will result in a frame about the hard clip border. #include #include #include "graph.h" double x; main () { g_init (EGA_640x350); locate (10, 120, 10, 90); frame (); scale (0, 70, -1, 8); for (x = 0; x < 70; ++x) plot (x, sin (x/100) + x/10, 1 ); } For this example, we are using a 220 mm x 160 mm screen, so there are 138 GDU's in the x-direction, and 100 GDU's in the y-direction. Working backwards, we find that the soft clip limit is about 16 mm in from the screen edges on the top, and left edge. It is about 29 mm in from the right edge of the screen. If we had wanted a symmetric display, we could have set the right edge of the viewport at 128 GDU's. Labeling the Plot With the definition of a viewport, we now have a border in which to label the graph. Typically, the title is centered at the top of the page, the y-axis label is along the left edge, and the x-axis label is centered along the bottom. The labelf () function allows text labels to be place at random on the screen. This function is distinct from printf () or any other built-in text generation program. All of the MS-DOS text programs use raster scan fonts which must be placed in rows and columns as ordinary ascii text output. Furthermore, the text is all aligned one-way. It is not possible to rotate the text without using a rotated font. Labels are positioned by moving to the desired starting location with the move () function or plot () function. The lower left corner of the label will be at the point to which you moved. Thus, to label the graph, #include #include #include "graph.h" double x; main () { g_init (EGA_640x350); locate (10, 120, 10, 90); frame (); move (40, 95); labelf ("Demonstration Plot"); move (5, 65); labelf ("D\ne\nv\ni\na\nt\ni\no\nn\n"); /* Deviation */ move (45, 5); labelf ("Time (seconds)"); scale (0, 70, -1, 8); for (x = 0; x < 70; ++x) plot (x, sin (x/100) + x/10, 1 ); } This program now labels the plot with a title (Demonstration Plot), an x-axis label (Time (seconds) ), and the y-axis label (Deviation). The y-axis label is broken with '\n' characters so that the label will be placed vertically. This type of label is the only type of label possible without rotated fonts on a PC, but the labelf () function is more versitile than this as will be shown later. Creative Labeling In the previous example, we demonstrated one possible method of labeling a plot with a vertical label. This resulted in characters which were "stacked" over each other. We also placed the title only approximately in the center of the viewport, and the title was the same size as all of the other characters. These simplification were used because we were discussing elementary plotting concepts and additional detail would have detracted from these basic ideas. To provide improved labeling, these are three functions which modify the performance of the labelf () function. The first of these is csize ( size, a_r, tilt ), and its 3 arguments size, a_r, and tilt. The first argument, size, regulates the size of the characters. The initialization default is 5 GDU's. The size units are always in GDU's. This is the height of the character cell. The character cell has borders on all sides so that they may be drawn side-by-side and top-to-bottom without overlapping. Because all cells are the same size, the amount of blank space between characters varies with the characters. The size of a character must always be specified. The second parameter is the aspect ratio. This is the measure of the width to the height of a character cell. The characters are defined on a 9 x 15 grid: hence, the 0.6 default aspect ratio. At 0.6, the characters are well defined and quite pleasing to the eye. The aspect ratio may, of course, be increased or decreased to add emphasis to the labels. The last parameter is the tilt. This is an angular measure in the current angular units (radians or degrees) of how far from vertical the vertical axis of the character cells is tilted. Positive angles tilt the characters clockwise, and negative angles tilt the characters counter-clockwise. The effect of tilting the characters is to italicize them for additional emphasis. To use csize (), simply call the function with the desired parameters. These parameters will remain in effect until csize () is called again with different parameters. Note: the accuracy with which a character is represented and is visually appealing depends on the screen resolution. This means that there are pratical limits to how small a character may be made for a given screen resolution. It is therefore recommended that you use the highest practical screen resolution consistent with your other graphics requirements. The second function which is useful in labeling is lorg () which selects from 9 label origins. The illustration below shows the function of lorg (). 3 6 9 + + + Test Text Test Text Test Text 5 2 +Test Text Test+Text Test Text + 8 Test Text Test Text 1 + + Test Text + 7 4 The label is "Test Text" and the "+" marks the label origin for the various origin codes. Thus, if we wanted to center the title on our graph, we could use move (65, 95); lorg (5); labelf ("Demonstration Plot"); and the label would automatically be centered both vertically and horizontally about the point (65, 95). The last of the label enhancement functions is ldir () which controls the direction labels are drawn. At initialization, the label direction is at 0 degrees (text is drawn horizontally). This angle can be set to any angle. Thus, if we say ldir (90); the labels will all be drawn vertically upward. If we say ldir (-90); the labels will still be drawn vertically, but now the labels will be drawn vertically downward. There is complete freedom in choosing the angle at which you want a label to be drawn. Now, if we add all of the above to the example program, we have #include #include #include "graph.h" double x; main () { double x_gdu_max, y_gdu_max; g_init (EGA_640x350); /* Determine the screen width and height in GDU's */ x_gdu_max = 100 * max (1, ratio () ); y_gdu_max = 100 * max (1, 1 / ratio () ); locate (10, 120, 10, 90); frame (); csize (7,0,0); /* set the label origin for centered label, top justified */ lorg (6); move (x_gdu_max / 2, y_gdu_max); /* move to top, middle of crt */ labelf ("Demonstration Plot"); move (50, 65); lorg (4); ldir (90); csize (4,0,0); labelf ("Deviation"); move (45, 5); lorg (6); ldir (0); labelf ("Time (seconds)"); scale (0, 70, -1, 8); for (x = 0; x < 70; ++x) plot (x, sin (x/100) + x/10, 1 ); } we have a plot with an enlarged title and both axes labels well centered along their respective axes. We could have emboldened the title slightly be placing the labeling inside a loop which draws the title successively slightly to the right or left of the starting point. for (i = 1; i < 5; ++i) { move (65 + i/10, 95); labelf ("Demonstration Plot"); } Note that this only emboldens the vertical part of the text, but the effect is not lost. We have also introduced a new function: ratio (). ratio () returns the aspect ratio of the screen. The use of the max () function simply selects which direction is defined as 100 GDU's. If the aspect ratio is greater than one, the y-direction will be 100 GDU's, and the x-direction will be 100 * ratio (). If the aspect ratio is less than one, the x-direction will be 100 GDU's, and the y-direction will be 100 / ratio (). Thus, we can determine the shape of the screen without apriori knowledge thereof. If we add clip_off (); move (0, -1); lorg (8); /* vertically centered, right justified */ labelf ("-1"); /* y-min */ move (0, 8); labelf ("8"); /* y-max */ move (0, -1); lorg (6); /* horizontally centered, top justified */ labelf ("0"); /* x-min */ move (70, -1); labelf ("70"); /* x-max */ we can place labels at the ends of the coordinate axes. The clip_off () function call is necessary if we are to have visible labels. This is because the labels are simply a series of vectors (lines of particular length and direction), and any portion of a line which lies outside of the soft clip limit will be cut off when clipping is in effect. The main title and axes labels were drawn before the locate () function was called, and these were within the soft and hard clip limits at initialization (the screen boundaries). Clipping Clipping is a hidden function for most graphics applications. It is completely "under cover" because it functions without explicitly requesting it. There are two clipping limits: the soft clip border and the hard clip border. The hard clip limit is generally an absolute limit determined by the physical size of the plotting surface (although the hard clip limits can be changed with the limit () function ). The soft clip border is user defined. Calling the locate () function automatically turns on soft clipping at the borders defined by locate (). The soft clip limits can be set independently of the viewport by using the clip () function. Calling clip (20, x_max, 10, y_max); sets only the soft clip border. The hard clip border is unchanged. After executing this function, any portion of a line outside of the x-limits (20 and x_max) or the y-limits (10 and y_max) will be truncated at the offending edge. Note that the clip () function uses only UDU's (user defined units). Thus, it is necessary to call the locate () function first to establish a scale for the viewing area. Also, calling locate () after calling clip () will override the border set with clip () and set the soft clip limit to the new border defined by locate (). We can disable soft clipping with clip_off (); and turn it back on with clip_on (); and soft clipping will resume at the previous soft clip border. If we wish to change the soft clip border, we simply use the clip () function to specify an new boundary. The hard clip boundary is always on and cannot be disabled. Drawing Modes When drawing on the CRT or any erasable device, several drawing modes are available. You can select "pens" dependent on the graphics mode selected. In CGA_640x200, there are two colors: background and foreground. In plotter terminology, you select pens to determine the color you want to draw. Pen # 0 is always the background color. For a monochrome drawing, there is only one other pen: pen # 1. In EGA_640x350, the total number of pens is 16, or 15 colors plus background. Here, the maximum pen number is 15. You can also "undraw" lines. By selecting the negative of the pen color and drawing over a line, the line is erased. The IBM BIOS provides two pixel functions: write and XOR. Because of this limitation, it is not possible to erase only the color which was drawn. Using the XOR function would do this, but if a pixel were not illuminated, XOR would make it so. This would defeat the intent of erasing a line. Therefore, selecting a negative pen number for erasing a line simply turns off all pixels on the line, irrespective of color, by writing the background color to those pixels. Hewlett-Packard impliments pen # 0 in a slightly different manner on monochrome CRT's as opposed to color CRT's. On monochrome CRT's, pen # 0 XOR's pixels. Thus, pen # 0 will write white on a black pixel, and black on a white pixel. On a color CRT with a color mapped graphics interface, the default colors are Pen Color 0 Black 1 White 2 Red 3 Yellow 4 Green 5 Cyan 6 Blue 7 Magenta 8 Black 9 Olive Green 10 Aqua 11 Royal BLue 12 Maroon 13 Brick Red 14 Orange 15 Brown This is not the same color set used by the PC color map. On Hewlett-Packard computers with color mapped displays, it is possible to change the assigned pen colors. This is possible only on IBM PC's with EGA compatible video interfaces (which contain writable color palettes. Also, the Hewlett-Packard graphics interface allows pixels to be selectively erased. Thus, a negative pen value will erase only the color values for that pen. If you change pens before erasing a line, it is possible to not erase the line but to merely change its color. For this implimentation, any negative pen value will erase a line of any color. Line Types In order to distinguish between different sets of data drawn in a single graph, it helps to pattern the lines. To do that, the function line_type () is used to select from 10 line patterns. line_type () takes two parameters. The first parameter selects the pattern, and the second selects the repetition length. The patterns will be described here. To see the patterns, run linedemo.exe . line type description 1 solid 2 dot at endpoint of line 3 lightly dotted 4 headily dotted 5 broken (dashed) 6 broken (long dash) with dot 7 broken (long dash) with short dash 8 broken (long dash) with double dot 9 solid with short tick (1 GDU total length) 10 solid with long tick (2 GDU's total length) Line types 9 and 10 are implimented slightly differently than in Hewlett-Packard workstation BASIC. Hewlett-Packard places the ticks at the endpoint of the line segment. Here, the ticks are repeated along the length of the line at the repeat rate of the line. The ticks are drawn either vertically or horizontally. The orientation depends on the angle of the line. Lines closer to vertical will have horizontal ticks, and line closer to horizontal will have vertical ticks. When these lines are drawn on the screen, however, the apparent angle at which the change from hoizontal to vertical ticks occurs will not be at 45 degrees as one would expect. This is due to the rectangular pixels of the PC graphics screen. An extensive discussion of this is presented under the discription of the function line_type (). The second parameter to line_type () is the repetition length. At initialization, this is 5 GDU's. This can be changed at any time to any length. It is up to the user to ensure that the pattern is not so compressed that the detail of the pattern is lost, or so long that continuity is lost. Unlike the HP BASIC implimentation, the line repetition need not be integer multiples of 5 GDU's. For all line types, the computer keep a record of where it is in the pattern. Thus, the pattern continues from one line segment to another. The line can change directions and the pattern will still be continuous. To start over (as you may want to do if you are drawing disconnected lines of the same type, simply call the line_type () function again with the same parameters and the pattern will start over. Relative and Incremental Plotting Up to now, we have discussed only absolute plotting: That is, we draw and move to specific coordinates. Relative plotting allows drawing relative to some relocatable origin, and incremental plotting allows one to draw relative to the last location. For example, move (50,50); rdraw (10,10); rdraw (10,-10); rdraw (0,0); rmove (0,-10); draws a triangle with verticies at (50,50), (60,60), and (60,40). We moved to a point (50,50) and then draw relative to that point. Any absolute motion function ( i.e., move, draw, or plot) can be used to set this origin. In this example, we have used the rdraw () function. We could also have used the rplot () function if we had required pen control. The last command simply moves the pen position to (50,40). The current relative origin is the last location resulting from calling any of the following commands: axes () draw () frame () g_init () grid () idraw () imove () iplot () labelf () move () draw () plot () Note that the relative origin is unchanged by any relative motion functions. The incremental motion functions (imove, idraw, and iplot) also change the relative origin. These also move the relative origin to the specified point. To plot the same triangle using incremental functions, move (50,50); idraw (10,10); idraw (0,-20); idraw (-10,10); imove (0,-10); Here, the reference position changes to the last location. idraw (10,10) draws to (60,60), but the reference position now becomes (60,60). Thus, to get to the point (60,40), we must move down by 20 units: hence idraw (0,-20). We are now at (60,40). To get back to (50,50), we move up to and to the left 10, or idraw (-10,10). We used an imove () to move to (50,40). It looks exactly like the rmove () command from the previous example because the last drawing command moved us back to our starting point. If we were not at our starting point, the commands would not look the same. Again, we could have used iplot () if we had required control of the pen state (up or down). Rotation and Translation of Drawings In many types of drawings, a single feature or structure is replicated several times at different locations in the drawing. We have seen how relative and incremental motion commands can be used to plot the same figure at any location. We can impart rotation to lines drawn with these functions by using the pivot () and pdir () functions. Each of these functions take a single argument which is the angle of rotation in the current angular units (degrees or radians). The function of these two functions is greatly different, however. The pdir () function specifies a relative plotting angle. If we write move (10,10); pdir (0); iplot (1,0); pdir (-90); iplot (1,0); the first line is drawn horizontally, and the second line is drawn vertically down. This is because we have introduced a relative rotation of -90 degrees. We now introduced another element of confusion: logical pen position and physical pen position. If we assume initialization values for pivot () and pdir (), the first iplot () moves the logical and physical pen position to (11,10), and the second iplot () move the logical and physical pen position to (11,9). So far, logical and physical pen position move in lock-step. The following code fragment illustrates the use of the pivot () function: move (10,10); pivot (90); iplot (1,0); We moved to (10,10) and now, instead of changing the relative plotting direction, we will pivot everything about this point. Thus, the iplot () function draw a line to (10,11), but it draws a line vertically up as if we drew to (11,10). Now, the logical pen position is at (11,10), but the physical pen position is at (10,11). In effect, we have rotated the coordinate axes 90 degrees about the point (10,10). We have assumed isotropic scaling in this discussion. If we have anisotropic scaling (i.e., we used scale () instead of show () ), the apparent line length would stretch or shrink according to the scales of the x- and y-axes. Because pivot works in a logical coordinate system, if we move to a new location (such as (5,5) ), pivot () will have the effect of pivoting about those points; it will not pivot about the physical location of (5,5). Although the iplot () function was used in this example, pivot will affect the direction of all lines drawn except labels, grids, and axes. At initialization, the pivot point is (0,0) and the pivot angle is zero. Miscellaneous Utilities A graphics dumping utility is also available. It is called dgraph () and can be used to dump entire screens or partial screen. It presently is configured for the EPSON FX-80 and 100 type of printers. Unlike the MS-DOS program GRAPHICS.COM or GRAPHICS.EXE, this program dumps all graphics at the same orientation to the page. It is somewhat slower than the MS-DOS version principly because of the way Microsoft C executes calls to the BIOS.