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。
graph-symbol、graph-blank、column-of-graph(位于
打印图表的列
)以及 graph-body-print。
(graph-body-print '(1 2 3 4 6 4 3 5 7 6 5 2 3))
eval-expression)。
yank)将 graph-body-print 表达式粘贴到小缓冲中。
graph-body-print 表达式。
Emacs 会打印出类似如下的图表:
*
* **
* ****
*** ****
********* *
************
*************