C.3.1 X轴刻度标记

第一个函数应该打印X轴的刻度标记。我们需要指定刻度标记本身及其间距:

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

(注意,graph-blank的值由另一个defvar设置。boundp谓词检查它是否已经被设置;如果尚未设置graph-blank,并且我们没有使用此条件构造,将进入调试器,并显示错误消息,其中包含 ‘Debugger entered--Lisp error: (void-variable graph-blank)’。)

这是X-axis-tic-symboldefvar

(defvar X-axis-tic-symbol "|"
  "插入到X轴列的字符串。")

目标是生成如下一行:

       |   |    |    |

第一个刻度标记缩进,使其位于第一列下方,该列缩进以提供给Y轴标签留出空间。

刻度元素包括从一个刻度到下一个刻度的空格以及一个刻度符号。空格的数量由刻度符号的宽度和X-axis-label-spacing确定。

代码如下:

;;; X-axis-tic-element
…
(concat
 (make-string
  ;; 生成空格字符串。
  (-  (* symbol-width X-axis-label-spacing)
      (length X-axis-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))))

所有这些直接导致打印X轴刻度线的函数:

(defun print-X-axis-tic-line
  (number-of-X-tics X-axis-leading-spaces X-axis-tic-element)
  "打印X轴的刻度标记。"
    (insert X-axis-leading-spaces)
    (insert X-axis-tic-symbol)  ; 在第一列下方插入刻度符号。
    ;; 在正确的位置插入第二个刻度。
    (insert (concat
             (make-string
              (-  (* symbol-width X-axis-label-spacing)
                  ;; 插入到第二个刻度符号的空格。
                  (* 2 (length X-axis-tic-symbol)))
              ? )
             X-axis-tic-symbol))
    ;; 插入剩余的刻度标记。
    (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)
  "构造带编号的X轴元素。"
  (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)
  "打印X轴数字行"
  (let ((number X-axis-label-spacing))
    (insert X-axis-leading-spaces)
    (insert "1")
    (insert (concat
             (make-string
              ;; 插入到下一个数字的空格。
              (-  (* symbol-width X-axis-label-spacing) 2)
              ? )
             (number-to-string number)))
    ;; 插入剩余的数字。
    (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函数,该函数使用print-X-axis-tic-lineprint-X-axis-numbered-line

该函数必须确定两个函数都使用的变量的局部值,然后调用它们。此外,它必须打印分隔两行的换行符。

该函数包含一个varlist,指定了五个局部变量,并调用了两个打印行的函数:

(defun print-X-axis (numbers-list)
  "打印X轴标签到NUMBERS-LIST的长度。"
  (let* ((leading-spaces
          (make-string full-Y-label-width ? ))
       ;; symbol-width  graph-body-print 提供
       (tic-width (* symbol-width X-axis-label-spacing))
       (X-length (length numbers-list))
       (X-tic
        (concat
         (make-string
          ;; 生成空格字符串。
          (-  (* symbol-width X-axis-label-spacing)
              (length X-axis-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