eintr: Columns of a graph

 
 Printing the Columns of a Graph
 ===============================
 
 Since Emacs is designed to be flexible and work with all kinds of
 terminals, including character-only terminals, the graph will need to be
 made from one of the typewriter symbols.  An asterisk will do; as we
 enhance the graph-printing function, we can make the choice of symbol a
 user option.
 
    We can call this function ‘graph-body-print’; it will take a
 ‘numbers-list’ as its only argument.  At this stage, we will not label
 the graph, but only print its body.
 
    The ‘graph-body-print’ function inserts a vertical column of
 asterisks for each element in the ‘numbers-list’.  The height of each
 line is determined by the value of that element of the ‘numbers-list’.
 
    Inserting columns is a repetitive act; that means that this function
 can be written either with a ‘while’ loop or recursively.
 
    Our first challenge is to discover how to print a column of
 asterisks.  Usually, in Emacs, we print characters onto a screen
 horizontally, line by line, by typing.  We have two routes we can
 follow: write our own column-insertion function or discover whether one
 exists in Emacs.
 
    To see whether there is one in Emacs, we can use the ‘M-x apropos’
 command.  This command is like the ‘C-h a’ (‘command-apropos’) command,
 except that the latter finds only those functions that are commands.
 The ‘M-x apropos’ command lists all symbols that match a regular
 expression, including functions that are not interactive.
 
    What we want to look for is some command that prints or inserts
 columns.  Very likely, the name of the function will contain either the
 word “print” or the word “insert” or the word “column”.  Therefore, we
 can simply type ‘M-x apropos RET print\|insert\|column RET’ and look at
 the result.  On my system, this command once took quite some time, and
 then produced a list of 79 functions and variables.  Now it does not
 take much time at all and produces a list of 211 functions and
 variables.  Scanning down the list, the only function that looks as if
 it might do the job is ‘insert-rectangle’.
 
    Indeed, this is the function we want; its documentation says:
 
      insert-rectangle:
      Insert text of RECTANGLE with upper left corner at point.
      RECTANGLE's first line is inserted at point,
      its second line is inserted at a point vertically under point, etc.
      RECTANGLE should be a list of strings.
      After this command, the mark is at the upper left corner
      and point is at the lower right corner.
 
    We can run a quick test, to make sure it does what we expect of it.
 
    Here is the result of placing the cursor after the ‘insert-rectangle’
 expression and typing ‘C-u C-x C-e’ (‘eval-last-sexp’).  The function
 inserts the strings ‘"first"’, ‘"second"’, and ‘"third"’ at and below
 point.  Also the function returns ‘nil’.
 
      (insert-rectangle '("first" "second" "third"))first
                                                    second
                                                    thirdnil
 
 Of course, we won’t be inserting the text of the ‘insert-rectangle’
 expression itself into the buffer in which we are making the graph, but
 will call the function from our program.  We shall, however, have to
 make sure that point is in the buffer at the place where the
 ‘insert-rectangle’ function will insert its column of strings.
 
    If you are reading this in Info, you can see how this works by
 switching to another buffer, such as the ‘*scratch*’ buffer, placing
 point somewhere in the buffer, typing ‘M-:’, typing the
 ‘insert-rectangle’ expression into the minibuffer at the prompt, and
 then typing <RET>.  This causes Emacs to evaluate the expression in the
 minibuffer, but to use as the value of point the position of point in
 the ‘*scratch*’ buffer.  (‘M-:’ is the keybinding for ‘eval-expression’.
 Also, ‘nil’ does not appear in the ‘*scratch*’ buffer since the
 expression is evaluated in the minibuffer.)
 
    We find when we do this that point ends up at the end of the last
 inserted line—that is to say, this function moves point as a
 side-effect.  If we were to repeat the command, with point at this
 position, the next insertion would be below and to the right of the
 previous insertion.  We don’t want this!  If we are going to make a bar
 graph, the columns need to be beside each other.
 
    So we discover that each cycle of the column-inserting ‘while’ loop
 must reposition point to the place we want it, and that place will be at
 the top, not the bottom, of the column.  Moreover, we remember that when
 we print a graph, we do not expect all the columns to be the same
 height.  This means that the top of each column may be at a different
 height from the previous one.  We cannot simply reposition point to the
 same line each time, but moved over to the right—or perhaps we can...
 
    We are planning to make the columns of the bar graph out of
 asterisks.  The number of asterisks in the column is the number
 specified by the current element of the ‘numbers-list’.  We need to
 construct a list of asterisks of the right length for each call to
 ‘insert-rectangle’.  If this list consists solely of the requisite
 number of asterisks, then we will have to position point the right
 number of lines above the base for the graph to print correctly.  This
 could be difficult.
 
    Alternatively, if we can figure out some way to pass
 ‘insert-rectangle’ a list of the same length each time, then we can
 place point on the same line each time, but move it over one column to
 the right for each new column.  If we do this, however, some of the
 entries in the list passed to ‘insert-rectangle’ must be blanks rather
 than asterisks.  For example, if the maximum height of the graph is 5,
 but the height of the column is 3, then ‘insert-rectangle’ requires an
 argument that looks like this:
 
      (" " " " "*" "*" "*")
 
    This last proposal is not so difficult, so long as we can determine
 the column height.  There are two ways for us to specify the column
 height: we can arbitrarily state what it will be, which would work fine
 for graphs of that height; or we can search through the list of numbers
 and use the maximum height of the list as the maximum height of the
 graph.  If the latter operation were difficult, then the former
 procedure would be easiest, but there is a function built into Emacs
 that determines the maximum of its arguments.  We can use that function.
 The function is called ‘max’ and it returns the largest of all its
 arguments, which must be numbers.  Thus, for example,
 
      (max  3 4 6 5 7 3)
 
 returns 7.  (A corresponding function called ‘min’ returns the smallest
 of all its arguments.)
 
    However, we cannot simply call ‘max’ on the ‘numbers-list’; the ‘max’
 function expects numbers as its argument, not a list of numbers.  Thus,
 the following expression,
 
      (max  '(3 4 6 5 7 3))
 
 produces the following error message;
 
      Wrong type of argument:  number-or-marker-p, (3 4 6 5 7 3)
 
    We need a function that passes a list of arguments to a function.
 This function is ‘apply’.  This function applies its first argument (a
 function) to its remaining arguments, the last of which may be a list.
 
    For example,
 
      (apply 'max 3 4 7 3 '(4 8 5))
 
 returns 8.
 
    (Incidentally, I don’t know how you would learn of this function
 without a book such as this.  It is possible to discover other
 functions, like ‘search-forward’ or ‘insert-rectangle’, by guessing at a
 part of their names and then using ‘apropos’.  Even though its base in
 metaphor is clear—apply its first argument to the rest—I doubt a novice
 would come up with that particular word when using ‘apropos’ or other
 aid.  Of course, I could be wrong; after all, the function was first
 named by someone who had to invent it.)
 
    The second and subsequent arguments to ‘apply’ are optional, so we
 can use ‘apply’ to call a function and pass the elements of a list to
 it, like this, which also returns 8:
 
      (apply 'max '(4 8 5))
 
    This latter way is how we will use ‘apply’.  The
 ‘recursive-lengths-list-many-files’ function returns a numbers’ list to
 which we can apply ‘max’ (we could also apply ‘max’ to the sorted
 numbers’ list; it does not matter whether the list is sorted or not.)
 
    Hence, the operation for finding the maximum height of the graph is
 this:
 
      (setq max-graph-height (apply 'max numbers-list))
 
    Now we can return to the question of how to create a list of strings
 for a column of the graph.  Told the maximum height of the graph and the
 number of asterisks that should appear in the column, the function
 should return a list of strings for the ‘insert-rectangle’ command to
 insert.
 
    Each column is made up of asterisks or blanks.  Since the function is
 passed the value of the height of the column and the number of asterisks
 in the column, the number of blanks can be found by subtracting the
 number of asterisks from the height of the column.  Given the number of
 blanks and the number of asterisks, two ‘while’ loops can be used to
 construct the list:
 
      ;;; First version.
      (defun column-of-graph (max-graph-height actual-height)
        "Return list of strings that is one column of a graph."
        (let ((insert-list nil)
              (number-of-top-blanks
               (- max-graph-height actual-height)))
 
          ;; Fill in asterisks.
          (while (> actual-height 0)
            (setq insert-list (cons "*" insert-list))
            (setq actual-height (1- actual-height)))
 
          ;; Fill in blanks.
          (while (> number-of-top-blanks 0)
            (setq insert-list (cons " " insert-list))
            (setq number-of-top-blanks
                  (1- number-of-top-blanks)))
 
          ;; Return whole list.
          insert-list))
 
    If you install this function and then evaluate the following
 expression you will see that it returns the list as desired:
 
      (column-of-graph 5 3)
 
 returns
 
      (" " " " "*" "*" "*")
 
    As written, ‘column-of-graph’ contains a major flaw: the symbols used
 for the blank and for the marked entries in the column are hard-coded as
 a space and asterisk.  This is fine for a prototype, but you, or another
 user, may wish to use other symbols.  For example, in testing the graph
 function, you may want to use a period in place of the space, to make
 sure the point is being repositioned properly each time the
 ‘insert-rectangle’ function is called; or you might want to substitute a
 ‘+’ sign or other symbol for the asterisk.  You might even want to make
 a graph-column that is more than one display column wide.  The program
 should be more flexible.  The way to do that is to replace the blank and
 the asterisk with two variables that we can call ‘graph-blank’ and
 ‘graph-symbol’ and define those variables separately.
 
    Also, the documentation is not well written.  These considerations
 lead us to the second version of the function:
 
      (defvar graph-symbol "*"
        "String used as symbol in graph, usually an asterisk.")
 
      (defvar graph-blank " "
        "String used as blank in graph, usually a blank space.
      graph-blank must be the same number of columns wide
      as graph-symbol.")
 
 (For an explanation of ‘defvar’, see SeeInitializing a Variable with
 ‘defvar’ defvar.)
 
      ;;; Second version.
      (defun column-of-graph (max-graph-height actual-height)
        "Return MAX-GRAPH-HEIGHT strings; ACTUAL-HEIGHT are graph-symbols.
 
      The graph-symbols are contiguous entries at the end
      of the list.
      The list will be inserted as one column of a graph.
      The strings are either graph-blank or graph-symbol."
 
        (let ((insert-list nil)
              (number-of-top-blanks
               (- max-graph-height actual-height)))
 
          ;; Fill in ‘graph-symbols’.
          (while (> actual-height 0)
            (setq insert-list (cons graph-symbol insert-list))
            (setq actual-height (1- actual-height)))
 
          ;; Fill in ‘graph-blanks’.
          (while (> number-of-top-blanks 0)
            (setq insert-list (cons graph-blank insert-list))
            (setq number-of-top-blanks
                  (1- number-of-top-blanks)))
 
          ;; Return whole list.
          insert-list))
 
    If we wished, we could rewrite ‘column-of-graph’ a third time to
 provide optionally for a line graph as well as for a bar graph.  This
 would not be hard to do.  One way to think of a line graph is that it is
 no more than a bar graph in which the part of each bar that is below the
 top is blank.  To construct a column for a line graph, the function
 first constructs a list of blanks that is one shorter than the value,
 then it uses ‘cons’ to attach a graph symbol to the list; then it uses
 ‘cons’ again to attach the top blanks to the list.
 
    It is easy to see how to write such a function, but since we don’t
 need it, we will not do it.  But the job could be done, and if it were
 done, it would be done with ‘column-of-graph’.  Even more important, it
 is worth noting that few changes would have to be made anywhere else.
 The enhancement, if we ever wish to make it, is simple.
 
    Now, finally, we come to our first actual graph printing function.
 This prints the body of a graph, not the labels for the vertical and
 horizontal axes, so we can call this ‘graph-body-print’.