C.3.1 横轴刻度标记

第一个函数用于打印横轴刻度标记。我们必须指定刻度标记本身及其间距:

(defvar X-axis-label-spacing
  (if (boundp 'graph-blank)
      (* 5 (length graph-blank)) 5)
  "横轴相邻两个标注之间的单位数。")

(注意 graph-blank 的值由另一个 defvar 设置。boundp 谓词用于检测它是否已被定义;若未定义则返回 nil。如果 graph-blank 未绑定且我们未使用这种条件构造,就会进入调试器并看到错误信息:‘Debugger entered--Lisp error: (void-variable graph-blank)’。)

下面是 X-axis-tic-symboldefvar 定义:

(defvar X-axis-tic-symbol "|"
  "用于指向横轴某一列的插入字符串。")

目标是生成如下所示的一行:

       |   |    |    |

第一个刻度缩进,使其位于第一列下方,而第一列本身已缩进以便为纵轴标注留出空间。

一个刻度元素由相邻刻度间的空白加上一个刻度符号组成。空白数量由刻度符号宽度和 X-axis-label-spacing 共同决定。

代码如下:

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

接下来,我们计算将第一个刻度缩进至图表第一列所需的空白数。这里使用由 print-graph 函数传入的 full-Y-label-width 值。

生成 X-axis-leading-spaces 的代码如下:

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

我们还需要确定横轴长度(即数字列表的长度)以及横轴上的刻度数量:

;; 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))))

所有这些直接导出了打印横轴刻度行的函数:

(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
             (make-string
              (-  (* symbol-width X-axis-label-spacing)
                  ;; Insert white space up to second tic symbol.
                  (* 2 (length X-axis-tic-symbol)))
              ? )
             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))))

数字行的实现同样简单:

首先,创建每个数字前带空白的编号元素:

(defun X-axis-element (number)
  "构造带编号的横轴元素。"
  (let ((leading-spaces
         (-  (* symbol-width X-axis-label-spacing)
             (length (number-to-string number)))))
    (concat (make-string leading-spaces ? )
            (number-to-string number))))

接下来,创建函数打印数字行,以第一列下方的数字 1 开始:

(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
             (make-string
              ;; 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)))))

最后,我们需要编写使用 print-X-axis-tic-lineprint-X-axis-numbered-lineprint-X-axis 函数。

该函数必须确定两个子函数所用变量的局部值,然后调用它们。同时,它还需要打印分隔两行的换行符。

该函数包含一个指定五个局部变量的变量列表,以及对两个行打印函数的调用:

(defun print-X-axis (numbers-list)
  "打印与 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))
       (X-tic
        (concat
         (make-string
          ;; Make a string of blanks.
          (-  (* symbol-width X-axis-label-spacing)
              (length X-axis-tic-symbol))
          ? )
         ;; Concatenate blanks with tic symbol.
         X-axis-tic-symbol))
       (tic-number
        (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)))

你可以测试 print-X-axis

  1. 安装 X-axis-tic-symbolX-axis-label-spacingprint-X-axis-tic-line,以及 X-axis-elementprint-X-axis-numbered-lineprint-X-axis
  2. 复制下面的表达式:
    (progn
     (let ((full-Y-label-width 5)
           (symbol-width 1))
       (print-X-axis
        '(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16))))
    
  3. 切换到 *scratch* 缓冲区,将光标放到希望轴标注开始的位置。
  4. 输入 M-:eval-expression)。
  5. C-yyank)将测试表达式粘贴到小缓冲。
  6. 按下 RET 求值。

Emacs 会如下打印横轴:


     |   |    |    |    |
     1   5   10   15   20