C.3.1 X Axis Tic Marks

The first function should print the X axis tic marks. We must specify the tic marks themselves and their spacing:

(defvar X-axis-label-spacing
  (if (boundp 'graph-blank)
      (* 5 (length graph-blank)) 5)
  "Number of units from one X axis label to next.")

(Note that the value of graph-blank is set by another defvar. The boundp predicate checks whether it has already been set; boundp returns nil if it has not. If graph-blank were unbound and we did not use this conditional construction, we would enter the debugger and see an error message saying ‘Debugger entered--Lisp error: (void-variable graph-blank)’.)

Here is the defvar for X-axis-tic-symbol:

(defvar X-axis-tic-symbol "|"
  "String to insert to point to a column in X axis.")

The goal is to make a line that looks like this:

       |   |    |    |

The first tic is indented so that it is under the first column, which is indented to provide space for the Y axis labels.

A tic element consists of the blank spaces that stretch from one tic to the next plus a tic symbol. The number of blanks is determined by the width of the tic symbol and the X-axis-label-spacing.

The code looks like this:

;;; X-axis-tic-element
  ;; Make a string of blanks.
  (-  (* symbol-width X-axis-label-spacing)
      (length X-axis-tic-symbol))
  ? )
 ;; Concatenate blanks with tic symbol.

Next, we determine how many blanks are needed to indent the first tic mark to the first column of the graph. This uses the value of full-Y-label-width passed it by the print-graph function.

The code to make X-axis-leading-spaces looks like this:

;; X-axis-leading-spaces
(make-string full-Y-label-width ? )

We also need to determine the length of the horizontal axis, which is the length of the numbers list, and the number of ticks in the horizontal axis:

;; X-length
(length numbers-list)

;; tic-width
(* symbol-width X-axis-label-spacing)

;; number-of-X-ticks
(if (zerop (% (X-length tic-width)))
    (/ (X-length tic-width))
  (1+ (/ (X-length tic-width))))

All this leads us directly to the function for printing the X axis tic line:

(defun print-X-axis-tic-line
  (number-of-X-tics X-axis-leading-spaces X-axis-tic-element)
  "Print ticks for X axis."
    (insert X-axis-leading-spaces)
    (insert X-axis-tic-symbol)  ; Under first column.
    ;; Insert second tic in the right spot.
    (insert (concat
              (-  (* symbol-width X-axis-label-spacing)
                  ;; Insert white space up to second tic symbol.
                  (* 2 (length X-axis-tic-symbol)))
              ? )
    ;; Insert remaining ticks.
    (while (> number-of-X-tics 1)
      (insert X-axis-tic-element)
      (setq number-of-X-tics (1- number-of-X-tics))))

The line of numbers is equally straightforward:

First, we create a numbered element with blank spaces before each number:

(defun X-axis-element (number)
  "Construct a numbered X axis element."
  (let ((leading-spaces
         (-  (* symbol-width X-axis-label-spacing)
             (length (number-to-string number)))))
    (concat (make-string leading-spaces ? )
            (number-to-string number))))

Next, we create the function to print the numbered line, starting with the number 1 under the first column:

(defun print-X-axis-numbered-line
  (number-of-X-tics X-axis-leading-spaces)
  "Print line of X-axis numbers"
  (let ((number X-axis-label-spacing))
    (insert X-axis-leading-spaces)
    (insert "1")
    (insert (concat
              ;; Insert white space up to next number.
              (-  (* symbol-width X-axis-label-spacing) 2)
              ? )
             (number-to-string number)))
    ;; Insert remaining numbers.
    (setq number (+ number X-axis-label-spacing))
    (while (> number-of-X-tics 1)
      (insert (X-axis-element number))
      (setq number (+ number X-axis-label-spacing))
      (setq number-of-X-tics (1- number-of-X-tics)))))

Finally, we need to write the print-X-axis that uses print-X-axis-tic-line and print-X-axis-numbered-line.

The function must determine the local values of the variables used by both print-X-axis-tic-line and print-X-axis-numbered-line, and then it must call them. Also, it must print the carriage return that separates the two lines.

The function consists of a varlist that specifies five local variables, and calls to each of the two line printing functions:

(defun print-X-axis (numbers-list)
  "Print X axis labels to length of NUMBERS-LIST."
  (let* ((leading-spaces
          (make-string full-Y-label-width ? ))
       ;; symbol-width is provided by graph-body-print
       (tic-width (* symbol-width X-axis-label-spacing))
       (X-length (length numbers-list))
          ;; Make a string of blanks.
          (-  (* symbol-width X-axis-label-spacing)
              (length X-axis-tic-symbol))
          ? )
         ;; Concatenate blanks with tic symbol.
        (if (zerop (% X-length tic-width))
            (/ X-length tic-width)
          (1+ (/ X-length tic-width)))))
    (print-X-axis-tic-line tic-number leading-spaces X-tic)
    (insert "\n")
    (print-X-axis-numbered-line tic-number leading-spaces)))

You can test print-X-axis:

  1. Install X-axis-tic-symbol, X-axis-label-spacing, print-X-axis-tic-line, as well as X-axis-element, print-X-axis-numbered-line, and print-X-axis.
  2. Copy the following expression:
     (let ((full-Y-label-width 5)
           (symbol-width 1))
        '(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16))))
  3. Switch to the *scratch* buffer and place the cursor where you want the axis labels to start.
  4. Type M-: (eval-expression).
  5. Yank the test expression into the minibuffer with C-y (yank).
  6. Press RET to evaluate the expression.

Emacs will print the horizontal axis like this:

     |   |    |    |    |
     1   5   10   15   20