15.1 graph-body-print 函数

经过上一节的准备,graph-body-print 函数的实现会很直观。该函数会逐列打印星号与空格,使用数字列表中的元素指定每列的星号数量。这是重复操作,因此可以使用递减 while 循环或递归函数实现。本节我们使用 while 循环编写定义。

column-of-graph 函数需要图表高度作为参数,因此我们应将其计算结果保存为局部变量。

由此我们得到该函数 while 循环版的模板:

(defun graph-body-print (numbers-list)
  "documentation..."
  (let ((height  ...
         ...))

    (while numbers-list
      insert-columns-and-reposition-point
      (setq numbers-list (cdr numbers-list)))))

我们需要填充模板中的空缺部分。

显然,我们可以使用 (apply 'max numbers-list) 表达式确定图表高度。

while 循环会逐个遍历 numbers-list。随着 (setq numbers-list (cdr numbers-list)) 不断缩短列表,每次的列表首个元素即为 column-of-graph 的参数值。

while 循环的每一轮中,insert-rectangle 会插入由 column-of-graph 返回的列表。由于 insert-rectangle 会将光标移动到插入矩形的右下角,我们需要在插入时保存光标位置,插入完成后返回该位置,再水平移动到下一次调用 insert-rectangle 的起始位置。

如果插入的列宽为一个字符(使用单个空格和星号时),重定位命令只需 (forward-char 1);但列宽也可能大于一个字符。因此重定位命令应写为 (forward-char symbol-width)symbol-width 本身即为 graph-blank 的长度,可通过表达式 (length graph-blank) 获取。将 symbol-width 变量绑定为图表列宽的最佳位置是 let 表达式的变量列表中。

基于以上考虑,得到如下函数定义:

(defun graph-body-print (numbers-list)
  "根据 NUMBERS-LIST 打印柱状图。
numbers-list 由纵轴数值构成。"

  (let ((height (apply 'max numbers-list))
        (symbol-width (length graph-blank))
        from-position)

    (while numbers-list
      (setq from-position (point))
      (insert-rectangle
       (column-of-graph height (car numbers-list)))
      (goto-char from-position)
      (forward-char symbol-width)
      ;; Draw graph column by column.
      (sit-for 0)
      (setq numbers-list (cdr numbers-list)))
    ;; Place point for X axis labels.
    (forward-line height)
    (insert "\n")
))

该函数中有一个不太直观的表达式:while 循环中的 (sit-for 0)。该表达式让图表打印过程的可视效果更好。它会让 Emacs 等待(sit) 零时长并刷新屏幕。放在此处可以让 Emacs 逐列刷新屏幕;如果没有它,Emacs 会等到函数退出后才刷新屏幕。

我们可以用一个简短的数字列表测试 graph-body-print

  1. 安装 graph-symbolgraph-blankcolumn-of-graph(位于 打印图表的列 )以及 graph-body-print
  2. 复制下面的表达式:
    (graph-body-print '(1 2 3 4 6 4 3 5 7 6 5 2 3))
    
  3. 切换到 *scratch* 缓冲区,将光标放在希望图表开始的位置。
  4. 输入 M-:eval-expression)。
  5. 使用 C-yyank)将 graph-body-print 表达式粘贴到小缓冲中。
  6. 按下 RET 执行 graph-body-print 表达式。

Emacs 会打印出类似如下的图表:

                    *
                *   **
                *  ****
               *** ****
              ********* *
             ************
            *************