/*-------------------------------------------------------------------+ | COPYRIGHT (C) 1995, SAS INSTITUTE INC. | | UNPUBLISHED - ALL RIGHTS RESERVED | | S A S / C S A M P L E | | | | NAME: BARCHART | | LANGUAGE: C | | PURPOSE: This program demonstrates the use of GDDM with | | SAS/C. It displays an alphanumeric menu panel | | to obtain titles and values and draws a simple | | bar chart using those values. It consists of | | BARCHART and GDDM.H. The JCL is in | | prefix.SAMPLE.AUX(BARCHART). | | MVS - | | COMPILE: LC370C with AT option | | Supplied header file "gddm.h" must be available. | | LINK: Linkedit with GDDM library | | EXECUTE: CALL .load(gddm) | | TSO - | | COMPILE: LC370C with AT option | | Supplied header file "gddm.h" must be available. | | LINK: Linkedit with GDDM library | | EXECUTE: CALL .load(gddm) | | CMS - | | COMPILE: LC370C with AT option | | Supplied header file "gddm.h" must be available. | | LINK: Linkedit with GDDM library | | EXECUTE: CALL GDDM | | MISC NOTES: | | INPUT: Full Screen entry | | OUTPUT: Full Screen output | | TSO ONLY: The GDDM symbol set PDS must be allocated to | | the ddname ADMSYMBL. | | The GDDM transient library must be allocated to | | STEPLIB. | | CMS ONLY: The GDDM symbol set files and transient library | | must be on a currently accessed minidisk. | | | +-------------------------------------------------------------------*/ #include #include #include #include #include "gddm.h" char *months[12] = /* Table of month names */ {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; float values[12] = /* Data values used in the chart */ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; char chtitle[81]; /* Chart title */ char xtitle[81]; /* X axis title */ char ytitle[81]; /* Y axis title */ float xpage; /* X page dimension */ float ypage; /* Y page dimension */ float xorigin; /* X offset of lower left axis corner */ float yorigin; /* Y offset of lower left axis corner */ float xaxis; /* X axis length */ float yaxis; /* Y axis length */ int nxticks; /* Number of spaces in X axis */ int nyticks; /* Number of spaces in Y axis */ float xwidth; /* Width of one X space */ float barwidth; /* Width of one bar */ float yinc; /* Y axis value increment */ float xcb; /* Default X character box size */ float ycb; /* Default Y character box size */ int keycode = 0; /* Key user pressed last */ #define ENTER 0 #define PF3 3 #define PF15 15 #define TRUE 1 #define FALSE 0 void bldmenu (void); /* Build the menu screen */ void readmenu (void); /* Read the data values */ void dispwait (int); /* Wait for an action key press */ void dispchrt (void); /* Display the chart */ void drinit (void); /* Initialize for drawing the chart */ void draxes (void); /* Draw the X and Y axes */ void drbars (void); /* Draw the bars */ void drstr ( /* Draw a string */ double x, /* X position of string */ double y, /* Y position of string */ char * str, /* String to draw */ int just ); /* Justification of string */ #define CENTER 1 /* Center the string */ #define RIGHT 2 /* Right justify the string */ #define LEFT 3 /* Left justify the string */ #eject /*-------------------------------------------------------------------*/ /* */ /* Main program */ /* */ /*-------------------------------------------------------------------*/ main () { /* Initialize GDDM and some other things */ fsinit (); chtitle[0] = '\0'; xtitle[0] = '\0'; ytitle[0] = '\0'; /* Suppress C runtime library error messages */ quiet (1); /* Loop until the user presses PF3 or PF15. The menu panel is displayed first. Each time the user presses ENTER, the display is switched between the menu panel and the chart. */ for (;;) { /* Build the menu panel */ bldmenu (); /* Read the values from the menu */ readmenu (); /* If the user pressed PF3 or PF15, exit from the loop */ if (keycode == PF3 || keycode == PF15) break; /* Display the chart */ dispchrt (); /* Again test for pressing PF3 or PF15 */ if (keycode == PF3 || keycode == PF15) break; } /* Terminate GDDM */ fsterm (); } #eject /*-------------------------------------------------------------------*/ /* */ /* Build the menu panel */ /* */ /* Define all of the fields on the menu panel and fill them with */ /* their default values. */ /* */ /*-------------------------------------------------------------------*/ void bldmenu () { static int dflts[8] = { /* Default field attributes */ 0, /* Type: unprotected alphanumeric */ 1, /* Intensity: normal */ NEUTRAL, /* Color */ 0, /* Symbol set: default */ 0, /* Highlight: normal */ 0, /* End: autoskip */ 1, /* Nulls: convert trailing blanks */ 1 }; /* Blanks: convert nulls to blanks */ static char *titles[3] = /* Title field titles */ { "Chart Title:", "X Axis Title:", "Y Axis Title:" }; static char *help = /* PF key help line */ "Enter = Display Chart, PF3/15 = Exit"; static char *mtitle = " Sample SAS/C Program Using GDDM "; int i; /* Loop counter */ int len; /* String length */ char temp[85]; /* Temporary string */ /* Set the default field attributes */ asdflt (8, dflts); /* Define and fill in the numeric value fields */ for (i=0; i < 12; i++) { /* Define the field */ asdfld (i+1, i+10, 15, 1, 15, 0); /* Fill in the default value */ if (values[i] != 0) { sprintf (temp, "%g", values[i]); ascput (i+1, strlen(temp), temp); } else ascput (i+1, 0, temp); } /* Define and fill in the title value fields */ asdfld (13, 3, 15, 1, 50, 0); ascput (13, strlen(chtitle), chtitle); asdfld (14, 4, 15, 1, 50, 0); ascput (14, strlen(xtitle), xtitle); asdfld (15, 5, 15, 1, 50, 0); ascput (15, strlen(ytitle), ytitle); /* Label the values with the month names */ for (i=0; i < 12; i++) { asdfld (i+16, i+10, 1, 1, 3, 2); asfcol (i+16, BLUE); ascput (i+16, 3, months[i]); } /* Label the other fields */ for (i=0; i < 3; i++) { asdfld (i+28, i+3, 1, 1, 14, 2); asfcol (i+28, BLUE); ascput (i+28, strlen(titles[i]), titles[i]); } /* Define the PF key help line */ asdfld (31, 23, 1, 1, 75, 2); asfcol (31, BLUE); ascput (31, strlen(help), help); /* Put a title above the values column */ asdfld (32, 8, 15, 1, 15, 2); ascput (32, 15, " Values "); asfcol (32, BLUE); asdfld (33, 9, 15, 1, 15, 2); ascput (33, 15, "---------------"); asfcol (33, BLUE); /* Add a title to the screen */ memset (temp, '-', 80); len = strlen (mtitle); memcpy (&temp[40-len/2], mtitle, len); asdfld (34, 1, 1, 1, 80, 2); ascput (34, 80, temp); /* Define a field for error messages */ asdfld (99, 2, 1, 1, 80, 2); asfcol (99, MAGENTA); ascput (99, 0, ""); } #eject /*-------------------------------------------------------------------*/ /* */ /* Read the values from the panel */ /* */ /* Note that we use a little trick to get GDDM to provide us with */ /* null-terminated strings on input. If the variable provided to */ /* GDDM via a call to ascget is longer than the field width, */ /* GDDM will fill the remaining characters in the variable with */ /* nulls. */ /* */ /*-------------------------------------------------------------------*/ void readmenu () { char input[81]; /* Input string */ int i; /* Loop index */ int error; /* TRUE if bad input found */ double tempval; /* Temporary for converting values */ char *end; /* Pointer to end of converted string */ static char *msg1 = "Invalid numeric character ' ' in this field"; static char *msg2 = "Numeric values must be >= 0"; /* Display the panel and wait for an action key */ dispwait (13); /* Just return if it's PF3/15 */ if (keycode == PF3 || keycode == PF15) return; /* Read and check the numeric fields. Repeat the whole process each time an error is found, since the user can modify any field -- not just the one with the error. */ do { error = FALSE; for (i=0; i < 12; i++) { /* Get the field contents */ ascget (i+1, 81, input); /* Check and convert to float */ if (input[0] == '\0') values[i] = 0.; else { tempval = strtod (input, &end); while (*end == ' ') end++; if (*end != '\0') { /* Invalid character in the number */ *(msg1+27) = *end; ascput (99, strlen(msg1), msg1); error = TRUE; break; } if (tempval < 0) { /* No negative values are allowed */ ascput (99, strlen(msg2), msg2); error = TRUE; break; } values[i] = (float)tempval; } } /* If we have an error, wait for the user to fix it */ if (error) { fsalrm (); dispwait (i+1); if (keycode == PF3 || keycode == PF15) return; } } while (error); /* Read the title fields */ ascget (13, 81, chtitle); ascget (14, 81, xtitle); ascget (15, 81, ytitle); } #eject /*-------------------------------------------------------------------*/ /* */ /* Display the panel and wait for a valid action key */ /* */ /*-------------------------------------------------------------------*/ void dispwait ( int field ) /* Field for initial cursor position */ { int attype; /* Attention type */ int attval; /* Attention value */ int count; /* Count of changed fields */ static char *msg1 = "That key has no defined action"; /* Position the cursor to the requested field */ if (field != 0) asfcur (field, 1, 1); /* Loop until the user presses a valid action key */ keycode = -1; do { asread (attype, attval, count); if (attype == 0) keycode = ENTER; else if (attype == 1 && (attval == PF3 || attval == PF15)) keycode = attval; else { if (field != 0) ascput (99, strlen(msg1), msg1); } } while (keycode < 0); /* Clear the error message field */ if (field != 0) ascput (99, 0, ""); } #eject /*-------------------------------------------------------------------*/ /* */ /* Display the chart */ /* */ /*-------------------------------------------------------------------*/ void dispchrt () { static int temp[5] = /* Dummy array of field attributes */ { 0, 0, 0, 0, 0 }; /* Initialize and compute scale factors */ drinit (); /* Draw the X and Y axes */ draxes (); /* Draw the chart title */ if (chtitle[0] != '\0') { gscb (xcb*2.f, ycb*2.f); drstr ((double)(xorigin+xaxis/2), (double)(yorigin+yaxis+ycb*2), chtitle, CENTER); } /* Draw the bars */ drbars (); /* Draw a heavy frame around the chart */ gslw (2); gscol (NEUTRAL); gsmove (xorigin, yorigin); gsline (xorigin+xaxis, yorigin); gsline (xorigin+xaxis, yorigin+yaxis); gsline (xorigin, yorigin+yaxis); gsline (xorigin, yorigin); /* Delete all of the alphanumeric fields */ asdfmt (0, 5, temp); /* Force the chart onto the screen and wait for an action key */ dispwait (0); /* Delete the graphics segment */ gssdel (1); } #eject /*-------------------------------------------------------------------*/ /* */ /* Initialize for graphics and compute various scale factors */ /* */ /*-------------------------------------------------------------------*/ void drinit () { static int ssloaded = /* TRUE if symbol set loaded already */ FALSE; float ymax; /* Maximum of all of the Y values */ float ymax2; /* Adjusted maximum of Y values */ double ylog; /* Log base 10 of ymax */ int i; /* Loop counter */ /* Set the page size to be 10 x 7.5 */ xpage = 10; ypage = 7.5; /* Set the location of the lower left corner of the chart */ xorigin = 1; yorigin = 1.5; /* Set the size of the chart area enclosed by the axes. The size is set so that there is the same amount of space on both all sides of the chart. */ xaxis = xpage - (xorigin * 2); yaxis = ypage - (yorigin * 2); /* Set the number of spaces on each axis */ nxticks = 12; nyticks = 5; /* Compute the width of the bars and spaces between them */ xwidth = xaxis / nxticks; barwidth = .75 * xwidth; /* Find the maximum data value supplied */ ymax = 0; for (i=0; i < 12; i++) { if (ymax < values[i]) ymax = values[i]; } /* Compute the Y axis maximum and increment based on the data values. Adjust the values so that the numbers on the Y axis are "nice" numbers. In this case, nice numbers are defined as numbers of the form: 1 or 5 * 10**n where "n" depends on the magnitude of the data values. The Y axis minimum is assumed to be 0. The maximum will always be a nice number. Having nice numbers on the other tick marks depends on having a reasonable number of ticks. */ if (ymax == 0) yinc = 1; else { ylog = log10 ((double)ymax); ymax2 = pow (10., ceil(ylog)); if ((ymax2 / 2) >= ymax) ymax2 /= 2; yinc = ymax2 / nyticks; } /* Define our window and start a graphics segment. The chart will be drawn in an area suitable for drawing on an 11 x 8.5 sheet of paper. Specifically, our window will be the largest uniform window that will fit in an area 10 x 7.5. */ gsuwin (0.f, xpage, 0.f, ypage); gsseg (1); /* Load the proportionally-spaced duplex vector symbol set if it has not been loaded before. Make it the current symbol set and set the character mode to 3. */ if (!ssloaded) { gslss (2, "ADMUWDRP", 65); ssloaded = TRUE; } gscs (65); gscm (3); /* Get the default character box size. These values are used to scale characters to other sizes without distortion. */ gsqcb (xcb, ycb); } #eject /*-------------------------------------------------------------------*/ /* */ /* Draw the X and Y axes */ /* */ /*-------------------------------------------------------------------*/ void draxes () { int i; /* Loop counter */ float ytemp; /* Temporary Y coordinate */ char tempstr[80]; /* Temporary string */ /* Make sure we have the right character size and color */ gscb (xcb, ycb); gscol (NEUTRAL); /* Draw the X axis line */ gsmove (xorigin, yorigin); gsline (xorigin + xaxis, yorigin); /* Put the month names on the axis */ for (i=0; i < 12; i++) drstr ((double)(xorigin+xwidth*i+xwidth/2), (double)(yorigin-ycb*1.5), months[i], CENTER); /* Draw the X axis title */ if (xtitle[0] != '\0') drstr ((double)(xorigin+xaxis/2), (double)(yorigin-ycb*4), xtitle, CENTER); /* Draw the Y axis line */ gsmove (xorigin, yorigin); gsline (xorigin, yorigin + yaxis); /* Draw the tick marks and numbers on the Y axis */ for (i=0, ytemp=yorigin; i <= nyticks; i++, ytemp+=yaxis/nyticks) { gsmove (xorigin, ytemp); gsline (xorigin - xcb * .75f, ytemp); sprintf (tempstr, "%g", (double)(i*yinc)); drstr ((double)(xorigin-xcb), (double)(ytemp-ycb/2), tempstr, RIGHT); } /* Draw the Y axis title */ if (ytitle[0] != '\0') drstr ((double)xorigin, (double)(yorigin+yaxis+ycb/2), ytitle, LEFT); } #eject /*-------------------------------------------------------------------*/ /* */ /* Draw the set of bars on the chart */ /* */ /*-------------------------------------------------------------------*/ void drbars () { float xbar[5]; /* X coordinates of bar */ float ybar[5]; /* Y coordinates of bar */ int i; /* Loop counter */ /* Make the bars green */ gscol (GREEN); /* Draw each of the bars */ for (i=0; i < 12; i++) { /* Compute corners of bar */ xbar[0] = xorigin + (xwidth * i) + ((xwidth - barwidth) / 2); ybar[0] = yorigin; xbar[1] = xbar[0] + barwidth; ybar[1] = yorigin; xbar[2] = xbar[1]; ybar[2] = yorigin + values[i] / ((yinc * nyticks) / yaxis); xbar[3] = xbar[0]; ybar[3] = ybar[2]; xbar[4] = xbar[0]; ybar[4] = ybar[0]; /* Draw the bar */ gsmove (xbar[0], ybar[0]); gsarea (1); gsplne (5, xbar, ybar); gsenda (); } } #eject /*-------------------------------------------------------------------*/ /* */ /* Draw a character string */ /* */ /*-------------------------------------------------------------------*/ void drstr ( /* Draw a string */ double x, /* X position of string */ double y, /* Y position of string */ char * str, /* String to draw */ int just ) /* Justification of string */ { float xpos; /* X position to start string */ float ypos; /* Y position to start string */ float xary[5]; /* X array returned by gsqtb */ float yary[5]; /* Y array returned by gsqtb */ /* Switch based on justification */ switch (just) { /* Left justification requires no adjustment */ case LEFT: xpos = x; ypos = y; break; /* Right justification adjusts x by width of string */ case RIGHT: gsqtb (strlen(str), str, 5, xary, yary); xpos = x - xary[3]; ypos = y; break; /* Center justification adjusts x by width of string / 2 */ case CENTER: gsqtb (strlen(str), str, 5, xary, yary); xpos = x - xary[3] / 2; ypos = y; break; } /* Draw the string at the adjusted position */ gschar (xpos, ypos, strlen(str), str); }