.RIGHT MARGIN 76 .TITLE Bar Graphs in DATATRIEVE-11 .SUBTITLE B.#Z.#Lederman##I.T.T. World Communications .PARAGRAPH By now, most DATATRIEVE users are aware that VAX-DATATRIEVE will produce graphics output on the VT125, including bar charts, and there are many who would like to produce charts who do not have VT125's, or VAXes: I have neither, but bar charts are easy to produce in any version of DATATRIEVE, and on any terminal, CRT or printer. They may not look quite as classy as the graphics versions, but they are easy to reproduce, are easily modified to suit the individual user, and are free. .PARAGRAPH The most basic graph to produce is as follows: .BLANK.TEST PAGE 34 .NO JUSTIFY.NO FILL BAR GRAPH 1-Mar-83 Page 1 SCALE A B C D 50 **** 48 **** 46 **** 44 **** **** 42 **** **** 40 **** **** 38 **** **** 36 **** **** 34 **** **** 32 **** **** 30 **** **** **** 28 **** **** **** 26 **** **** **** 24 **** **** **** 22 **** **** **** 20 **** **** **** 18 **** **** **** 16 **** **** **** 14 **** **** **** 12 **** **** **** **** 10 **** **** **** **** 8 **** **** **** **** 6 **** **** **** **** 4 **** **** **** **** 2 **** **** **** **** 0 **** **** **** **** .JUSTIFY.FILL.BLANK .PARAGRAPH If one looks at this as a bar graph, then it's method of production may be obscure; but if one looks at it as the normal output of the report writer, with the vertical columns corrisponding to fields in a record, and each horizontal line one record in a domain, then the problem simplifies greatly. The record definition used is: .NO JUSTIFY.NO FILL.BLANK.TEST PAGE 11 DEFINE RECORD BASIC-BAR-REC 01 BAR-REC. 03 SCALE PIC 999 EDIT-STRING ZZ9. 03 A PIC X(4). 03 B PIC X(4). 03 C PIC X(4). 03 D PIC X(4). ; .JUSTIFY.FILL.BLANK One can have as many columns as desired of whatever width, and a scale of any size, but for these examples I will use four columns, each four spaces wide, and a scale of 0 to a maximum of 999. The domain definition is very simple: .BLANK DEFINE#DOMAIN#BASIC-BARGRAPH#USING#BASIC-BAR-REC#ON#BASIC.SEQ; .BLANK A sequential file is used, as the data will always be read sequentially from beginning to end, and there is no need for the extra overhead of keyed access. .PARAGRAPH To load the data correctly into the domain the following procedure is used: (note that comments have been added for clarity, but may not appear when the procedure is used.) .BLANK.NO JUSTIFY.NO FILL DEFINE PROCEDURE MAKE-BASIC-BARGRAPH .BLANK ! The following line creates a blank file for the new data faster than ! deleting old records. .BLANK DEFINE FILE FOR BASIC-BARGRAPH READY BASIC-BARGRAPH WRITE .BLANK ! The next two variables are a blank space or a filled bar respectivly. ! The 'bar' may be any desired printing character string. .BLANK DECLARE BLANK PIC X(4). DECLARE BAR PIC X(4). BLANK=" " BAR="****" .BLANK ! The next variable is used to determine the increment or "step" between lines .BLANK DECLARE INC PIC 999 USAGE IS INTEGER. .BLANK ! A temporary variable is needed .BLANK DECLARE TMP PIC 999 USAGE IS INTEGER. .BLANK ! The next four variables hold the values to be graphed. .BLANK DECLARE A1 PIC 999 USAGE IS INTEGER. DECLARE B1 PIC 999 USAGE IS INTEGER. DECLARE C1 PIC 999 USAGE IS INTEGER. DECLARE D1 PIC 999 USAGE IS INTEGER. .BLANK ! While the data to be graphed could be obtained from another domain, ! for this example it will be entered from the terminal by prompting. ! Set the temporary variable to the maximum value for this chart. .BLANK TMP=*.SCALE .BLANK ! Get the four values to be graphed. A1=*.A B1=*.B C1=*.C D1=*.D .BLANK ! The increment is the scale maximum divided by the number of lines per graph ! Normally 50 lines fit an 8 1/2 by 11" page, but ! in this article, only 25 are printed to save space. .BLANK INC=TMP/25 .BLANK ! Main loop to load the data. .BLANK WHILE TMP GE 0 BEGIN STORE BASIC-BARGRAPH USING BEGIN SCALE=TMP ! This is the present value. IF TMP GT A1 A=BLANK ELSE A=BAR IF TMP GT B1 B=BLANK ELSE B=BAR IF TMP GT C1 C=BLANK ELSE C=BAR IF TMP GT D1 D=BLANK ELSE D=BAR END TMP=TMP - INC ! Get the value for the next line END END-PROCEDURE .JUSTIFY.FILL.BLANK The first thing one must do is declare the variables which will be required, and obtain the data to be graphed: then the data may be loaded into the domain. The loop may be followed by holding a ruler horizontally over the bar graph and moving it from top to bottom: the SCALE variable is set equal to the current value of TMP, and if this value is greater than the value to be plotted in a given column, that column is set to BLANK (spaces); if the value to be plotted is greater than or equal to the current value of TMP, then a BAR (astrisks) is printed. When the value of TMP passes zero, the WHILE statement terminates. Once the data is in the domain, it may be printed. .BLANK.NO JUSTIFY.NO FILL.TEST PAGE 12 DEFINE PROCEDURE REPORT-BASIC-BARGRAPH READY BASIC-BARGRAPH REPORT BASIC-BARGRAPH ON BAR.RPT SET COLUMNS-PAGE=80 SET LINES-PAGE=60 ! If you want 50 line graphs SET REPORT-NAME="BAR GRAPH" PRINT BASIC-BAR-REC END-REPORT FINISH BASIC-BARGRAPH END-PROCEDURE .JUSTIFY.FILL.BLANK There are no tricks or special commands here, though one may make the report more elaborate with AT#TOP and AT#BOTTOM commands, etc. .PARAGRAPH Once the basic technique is mastered, the graphs may be made more sophisticated with a few simple additions. One may easily dress up the graph as follows: .NO JUSTIFY.NO FILL.BLANK.TEST PAGE 35 BAR GRAPH 2-Dec-82 Page 1 SCALE A B C D 25 | AAAA 24 | AAAA 23 | AAAA CCCC 22 | AAAA CCCC 21 | AAAA CCCC 20 | AAAA CCCC 19 | AAAA CCCC 18 | AAAA CCCC 17 | AAAA CCCC 16 | AAAA CCCC 15 | AAAA CCCC 14 | AAAA BBBB CCCC 13 | AAAA BBBB CCCC 12 | AAAA BBBB CCCC 11 | AAAA BBBB CCCC 10 | AAAA BBBB CCCC 9 | AAAA BBBB CCCC 8 | AAAA BBBB CCCC DDDD 7 | AAAA BBBB CCCC DDDD 6 | AAAA BBBB CCCC DDDD 5 | AAAA BBBB CCCC DDDD 4 | AAAA BBBB CCCC DDDD 3 | AAAA BBBB CCCC DDDD 2 | AAAA BBBB CCCC DDDD 1 | AAAA BBBB CCCC DDDD 0 | AAAA BBBB CCCC DDDD -------------------------------------------- SCALE A B C D .JUSTIFY.FILL.BLANK This graph is easily produced from the same basic procedure: however, within the STORE loop the commands are now: .BLANK.NO JUSTIFY.NO FILL.TEST PAGE 9 STORE BASIC-BARGRAPH USING BEGIN SCALE=TMP IF TMP GT A1 A=BLANK ELSE A="AAAA" IF TMP GT B1 B=BLANK ELSE B="BBBB" IF TMP GT C1 C=BLANK ELSE C="CCCC" IF TMP GT D1 D=BLANK ELSE D="DDDD" END .JUSTIFY.FILL.BLANK and the variable BAR is no longer required. The report writer statements are only slightly more elaborate: .TEST PAGE 10.NO JUSTIFY.NO FILL.BLANK DEFINE PROCEDURE REPORT-BARGRAPH READY BARGRAPH REPORT BARGRAPH ON BAR.RPT SET COLUMNS-PAGE=80 SET LINES-PAGE=60 SET REPORT-NAME="BAR GRAPH" PRINT COL 10, SCALE, COL 15, "|", COL 20, A, COL 30, B, COL 40, C, COL 50, D AT BOTTOM OF REPORT PRINT COL 10, "--------------------------------------------", SKIP, COL 10, "SCALE A B C D " END-REPORT FINISH BARGRAPH END-PROCEDURE .JUSTIFY.FILL.BLANK Here I have chosen to force the columns to print at specific locations rather than the default columns the report writer would assign, and I have placed a vertical bar (#|#) between the scale and the columns and a row of dashes at the bottom of the report; all simple steps which make the graph look better. .PARAGRAPH At this point I decided to get fancy and make the scale print out on every 'n'th line rather than every line, to make the graph look even better. .BLANK.TEST PAGE 42 .NO JUSTIFY.NO FILL BAR GRAPH 1-Mar-83 Page 1 SCALE A B C D 30 | AAAA | AAAA | AAAA | AAAA | AAAA | AAAA | AAAA | AAAA CCCC | AAAA CCCC | AAAA CCCC 20 | AAAA CCCC | AAAA CCCC | AAAA CCCC | AAAA CCCC | AAAA CCCC | AAAA CCCC | AAAA CCCC | AAAA CCCC | AAAA BBBB CCCC | AAAA BBBB CCCC 10 | AAAA BBBB CCCC | AAAA BBBB CCCC | AAAA BBBB CCCC | AAAA BBBB CCCC DDDD | AAAA BBBB CCCC DDDD | AAAA BBBB CCCC DDDD | AAAA BBBB CCCC DDDD | AAAA BBBB CCCC DDDD | AAAA BBBB CCCC DDDD | AAAA BBBB CCCC DDDD | AAAA BBBB CCCC DDDD -------------------------------------------- SCALE A B C D .JUSTIFY.FILL .BLANK The procedure to make this domain is only slightly different than before. .BLANK.NO JUSTIFY.NO FILL DEFINE PROCEDURE NEW-MAKE-BAR DEFINE FILE FOR NEW-BARGRAPH ALLOCATION=7 READY NEW-BARGRAPH WRITE DECLARE BLANK PIC X(4). DECLARE INC PIC 999 USAGE IS INTEGER. DECLARE TMP PIC 999 USAGE IS INTEGER. DECLARE ONE PIC 9. ONE=1 ! This is needed as a constant BLANK=" " DECLARE A1 PIC 999 USAGE IS INTEGER. DECLARE B1 PIC 999 USAGE IS INTEGER. DECLARE C1 PIC 999 USAGE IS INTEGER. DECLARE D1 PIC 999 USAGE IS INTEGER. TMP=*.SCALE A1=*.A B1=*.B C1=*.C D1=*.D INC=TMP/30 ! 30 lines for this example WHILE TMP GE 0 BEGIN STORE NEW-BARGRAPH USING BEGIN SCALE = 0 IF ONE = (TMP/10 - (TMP - 1)/10) SCALE=TMP IF TMP GT A1 A=BLANK ELSE A="AAAA" IF TMP GT B1 B=BLANK ELSE B="BBBB" IF TMP GT C1 C=BLANK ELSE C="CCCC" IF TMP GT D1 D=BLANK ELSE D="DDDD" END TMP=TMP - INC END END-PROCEDURE .BLANK.JUSTIFY.FILL In the STORE loop, SCALE is set equal to zero: then the IF statement will conditionally set the SCALE equal to TMP as was done before. That mess in the parenthesis is something I got from an old fortran program: if TMP is an exact multiple of 10, then the expression will be equal to 1, otherwise it will be zero (TMP must be an integer variable). This makes SCALE take on it's correct value on every 10th line, and zero elsewhere. It is important to note that the record definition was also changed so that the edit string for SCALE is now: .BLANK 03#SCALE#PIC#999#EDIT-STRING#ZZZ. .BLANK so that zero values will be totally suppressed. Of course, one could easily make the values print on every 5th line by changing the 10s to 5s, or every other line by changing the 10s to 2s, etc. The report writer statements are unchanged. The ALLOCATION= clause is added to the DEFINE FILE statement as it is more efficient to allocate space when the file is created once a few graphs have been produced and the size of the report file is known. .PARAGRAPH Now, just to show off, I will create a bargraph where the exact value of the variable prints at the top of the bar. This is something I don't believe the VAX graphics can do, but it does depend on a quirk in the record definition, so I cannot guarantee that it will be portable. The record definition now looks like this: .BLANK.TEST PAGE 10.NO JUSTIFY.NO FILL DEFINE RECORD BAR-REC 01 BAR-REC. 02 NORMAL-PART. 03 SCALE PIC 999 EDIT-STRING ZZ9. 03 A PIC 9(4) EDIT-STRING ZZZZ. 03 B PIC 9(4) EDIT-STRING ZZZZ. 03 C PIC 9(4) EDIT-STRING ZZZZ. 03 D PIC 9(4) EDIT-STRING ZZZZ. 02 TRICKS REDEFINES NORMAL-PART. 03 SBLANK PIC XXX. 03 AB PIC X(4). 03 BB PIC X(4). 03 CB PIC X(4). 03 DB PIC X(4). ; .JUSTIFY.FILL.BLANK The various columns must be defined as numbers so that the numeric value can be inserted and printed, and then re-defined as character strings so that they can contain blanks or the printing characters. The procedure is now just a bit more complicated. .BLANK.NO JUSTIFY.NO FILL.TEST PAGE 10 DEFINE PROCEDURE MAKE-BARGRAPH DEFINE FILE FOR BARGRAPH READY BARGRAPH WRITE DECLARE BLANK PIC X(4). DECLARE INC PIC 999 USAGE IS INTEGER. DECLARE TMP PIC 999 USAGE IS INTEGER. DECLARE PLUS PIC 999 USAGE IS INTEGER. DECLARE INC2 PIC 999 USAGE IS INTEGER. DECLARE MINUS PIC 999 USAGE IS INTEGER. DECLARE ONE PIC 9. ONE=1 BLANK=" " TMP=*.SCALE DECLARE A1 PIC 999 USAGE IS INTEGER. DECLARE B1 PIC 999 USAGE IS INTEGER. DECLARE C1 PIC 999 USAGE IS INTEGER. DECLARE D1 PIC 999 USAGE IS INTEGER. A1=*.A B1=*.B C1=*.C D1=*.D INC=TMP/30 ! amount of each step INC2=INC/2 ! half a step WHILE TMP GE 0 BEGIN STORE BARGRAPH USING BEGIN SBLANK=" " IF ONE = (TMP/10 - (TMP - 1)/10) SCALE=TMP PLUS=TMP+INC2 ! calculate range MINUS=TMP - INC2 IF PLUS GT A1 AB=BLANK ELSE AB="AAAA" ! insert bars IF PLUS GT B1 BB=BLANK ELSE BB="BBBB" IF PLUS GT C1 CB=BLANK ELSE CB="CCCC" IF PLUS GT D1 DB=BLANK ELSE DB="DDDD" IF A1 BT PLUS AND MINUS A=A1 ! insert values IF B1 BT PLUS AND MINUS B=B1 IF C1 BT PLUS AND MINUS C=C1 IF D1 BT PLUS AND MINUS D=D1 END TMP=TMP - INC END END-PROCEDURE .JUSTIFY.FILL.BLANK Rather than just determining if the value to be printed is greater than the value at that level in the chart, it is now also necessary to find the top step, and this requires looking at a range of values which is the present value plus or minus one-half step: thus the need for the new values PLUS which is the upper limit, MINUS, the lower limit, and INC2 which is one half of a step. If the present value is above the upper limit, then blank spaces are printed, otherwise the character string is printed. In addition, if the present value is within one-half step of the value for that column, then the number itself is inserted into the column. When this is reported (using the same procedure as before) the result looks like this: .BLANK.TEST PAGE 42 .NO JUSTIFY.NO FILL BAR GRAPH 1-Mar-83 Page 1 SCALE A B C D 30 | 30 | AAAA | AAAA | AAAA | AAAA | AAAA | AAAA | AAAA | AAAA | AAAA 20 | AAAA | AAAA | AAAA 18 | AAAA DDDD | AAAA DDDD | AAAA 15 DDDD | AAAA BBBB DDDD | AAAA BBBB DDDD | AAAA BBBB DDDD | AAAA BBBB DDDD 10 | AAAA BBBB DDDD | AAAA BBBB DDDD | AAAA BBBB DDDD | AAAA BBBB DDDD | AAAA BBBB DDDD | AAAA BBBB DDDD | AAAA BBBB 4 DDDD | AAAA BBBB CCCC DDDD | AAAA BBBB CCCC DDDD | AAAA BBBB CCCC DDDD | AAAA BBBB CCCC DDDD -------------------------------------------- SCALE A B C D .JUSTIFY.FILL.PARAGRAPH One could go on from here to remove the scale on the left if the values on the top of the bars is sufficient, or replace the printing strings with escape sequences which would cause a particular CRT or printer to print a more interesting character (such as a solid bar) for the column display, etc. .PARAGRAPH When I gave a copy of the above article to one of our system users to proof-read, he immediatly put some of the graphs to use, and then presented me with a problem where several variables had to plot out side by side; so#.#.#. .BLANK.TEST PAGE 45.NO JUSTIFY.NO FILL THREE BAR GRAPH 3-Mar-83 Page 1 SCALE A B C D 30 | O | O | O C | O S C | O S C | O S C C | O S C C | O S C C | O S C C | O S C C 20 | OS S C C | OS S C C | OS S C C | OS S C C | OS S C C | OS OS C C | OS OSC C C | OS OSC C C | OS OSC C C | OS OSC C C 10 | OS OSC C C | OS OSC C SC | OS OSC C OSC | OS OSC O C OSC | OS OSC O C OSC | OS OSC O C OSC | OS OSC O C OSC | OS OSC O C OSC | OS OSC OSC OSC | OSC OSC OSC OSC -------------------------------------------- SCALE A B C D .BLANK.JUSTIFY.FILL The record definition requires defining the scale and four bars, and then re-defining the bars to allow inserting the second and third sub-divisions. .PAGE .NO JUSTIFY.NO FILL DEFINE RECORD THREE-BAR-REC 01 BAR-REC. 03 SCALE PIC 999 EDIT-STRING ZZZ. 02 FIRST. 03 A PIC X(3). 03 B PIC X(3). 03 C PIC X(3). 03 D PIC X(3). 02 SECOND REDEFINES FIRST. 03 FILLER PIC X. 03 A2 PIC X. 03 FILLER PIC XX. 03 B2 PIC X. 03 FILLER PIC XX. 03 C2 PIC X. 03 FILLER PIC XX. 03 D2 PIC X. 03 FILLER PIC X. 02 THIRD REDEFINES FIRST. 03 FILLER PIC XX. 03 A3 PIC X. 03 FILLER PIC XX. 03 B3 PIC X. 03 FILLER PIC XX. 03 C3 PIC X. 03 FILLER PIC XX. 03 D3 PIC X. ; .BLANK.JUSTIFY.FILL The procedure to make this bar graph requires four passes: one to create the file and put in the first sub-division, and two more for the two additional sub-divisions. The final pass is to blank out the unwanted scale digits: this must be done last as the SCALE data is needed to load in the sub-divisions. .BLANK.NO JUSTIFY.NO FILL DEFINE PROCEDURE MAKE-THREE-BAR DEFINE FILE FOR THREE-BAR ALLOCATION=2 READY THREE-BAR WRITE DECLARE BLANK PIC X(3). ! reduced to 3 spaces now DECLARE OVE PIC X(3). DECLARE INC PIC 999 USAGE IS INTEGER. DECLARE TMP PIC 999 USAGE IS INTEGER. DECLARE ONE PIC 9. ONE=1 BLANK=" " OVE = "O " ! First sub-division, blanks second and third. TMP=*.SCALE INC=TMP/30 DECLARE A1 PIC 999 USAGE IS INTEGER. DECLARE B1 PIC 999 USAGE IS INTEGER. DECLARE C1 PIC 999 USAGE IS INTEGER. DECLARE D1 PIC 999 USAGE IS INTEGER. PRINT "Enter first set of variables." A1=*.A B1=*.B C1=*.C D1=*.D WHILE TMP GT 0 BEGIN STORE THREE-BAR USING BEGIN SCALE=TMP IF TMP GT A1 A=BLANK ELSE A=OVE ! load in first sub-division IF TMP GT B1 B=BLANK ELSE B=OVE ! blanking out second and IF TMP GT C1 C=BLANK ELSE C=OVE ! third sub-divisions. IF TMP GT D1 D=BLANK ELSE D=OVE END TMP=TMP - INC END PRINT "Enter second set of variables" A1=*.A B1=*.B C1=*.C D1=*.D FOR THREE-BAR MODIFY USING BEGIN IF SCALE LE A1 A2="S" ! insert the second sub-division IF SCALE LE B1 B2="S" IF SCALE LE C1 C2="S" IF SCALE LE D1 D2="S" END PRINT "Enter third set of variables" A1=*.A B1=*.B C1=*.C D1=*.D FOR THREE-BAR MODIFY USING BEGIN IF SCALE LE A1 A3="C" ! insert the third sub-division IF SCALE LE B1 B3="C" IF SCALE LE C1 C3="C" IF SCALE LE D1 D3="C" END ! Blank out the unwanted scale digits FOR THREE-BAR MODIFY USING BEGIN IF ONE NE (SCALE/10 - (SCALE - 1)/10) SCALE=0 END FINISH END-PROCEDURE .BLANK.JUSTIFY.FILL In this procedure I also changed the conditional in the WHILE statement from GE to GT so that the bottom line in this graph is not zero. Note also that the sense of the IF statement which blanks out the unwanted SCALE entries must be reversed from the previous examples. The report writer commands are unchanged. .PARAGRAPH Hopefully, the reader will now have some understanding of the types of graphs which may be produced with DATATRIEVE. All are easy if taken one step at a time, so I recomend you try experimenting with some of them, and have some fun.