All Manuals > CLIM 2.0 User Guide > 17 Formatted Output

17.1 Formatting Tables in CLIM

17.1.1 Conceptual Overview of Formatting Tables

CLIM makes it easy to construct tabular output. The usual way of making tables is by indicating what you want to put in the table and letting CLIM choose the placement of the row and column cells. CLIM also allows you to specify constraints on the placement of the table elements with some flexibility.

In the CLIM model of formatting tables, each cell of the table is handled separately:

You are responsible only for specifying the contents of the cell. CLIM's table formatter will figure out how to lay out the table so that all the cells fit together properly. It derives the width of each column from the the widest cell within the column, and the height of each row from the the tallest cell within the row.

All the cells in a row have the same height. All the cells in a column have the same width. The contents of the cells can be of irregular shapes and sizes. You can impose both vertical and horizontal constraints on the objects within the cell, aligning them vertically at the top, bottom, or center of the cell, and horizontally at the left, right, or center of the cell.

Some tables are "multiple column" tables, in which two or more rows of the table are placed side by side (usually with intervening spacing) rather than all rows being aligned vertically. Multiple column tables are generally used to produce a table that is more esthetically pleasing, or to make more efficient use of space on the output device. When a table is a multiple column table, one additional step takes place in the formatting of the table: the rows of the table are rearranged into multiple columns in which some rows are placed side by side.

The programmer can give CLIM the following advice about assembling the table:

You can specify other constraints that affect the appearance of the table, such as the width or length of the table.

Note that table formatting is inherently two-dimensional from the point of view of the application. Item list formatting is inherently one-dimensional output that is presented two-dimensionally. The canonical example is a menu, where the programmer specifies a list of items to be presented. If the list is small enough, a single column or row of menu entries suffices. In this case, formatting is done when viewport requirements make it desirable.

These constraints affect the appearance of item lists:

See 17.5 Advanced Topics for the table and item list formatting protocols.

17.1.2 CLIM Operators for Formatting Tables

This subsection covers the general-purpose table formatting operators.

formatting-table Macro

formatting-table (&optional stream &key x-spacing y-spacing multiple-columns multiple-columns-x-spacing equalize-column-widths (move-cursor t) record-type) &body body

Summary: Binds the local environment in such a way the output of body will be done in a tabular format. This must be used in conjunction with formatting-row or formatting-column, and formatting-cell. The table is placed so that its upper left corner is at the current text cursor position of stream. If the boolean move-cursor is t (the default), then the text cursor will be moved so that it immediately follows the last cell of the table.

The returned value is the output record corresponding to the table.

stream is an output recording stream to which output will be done. The stream argument is not evaluated, and must be a symbol that is bound to a stream. If stream is t (the default), *standard-output* is used. body may have zero or more declarations as its first forms.

x-spacing specifies the number of units of spacing to be inserted between columns of the table; the default is the width of a space character in the current text style. y-spacing specifies the number of units of spacing to be inserted between rows in the table; the default is the default vertical spacing of the stream. Possible values for these two options option are:

multiple-columns is either nil, t, or an integer. If it is t or an integer, the table rows will be broken up into a multiple columns. If it is t, CLIM will determine the optimal number of columns. If it is an integer, it will be interpreted as the desired number of columns. multiple-columns-x-spacing has the same format as x-spacing. It controls the spacing between the multiple columns. It defaults to the value of the x-spacing option.

When the boolean equalize-column-widths is t, all the columns will have the same width (the width of the widest cell in any column in the entire table).

record-type specifies the class of output record to create. The default is standard-table-output-record. This argument should only be supplied by a programmer if there is a new class of output record that supports the table formatting protocol.

formatting-row Macro

formatting-row (&optional stream &key record-type) &body body

Summary: Binds the local environment in such a way the output of body will be grouped into a table row. All of the output performed by body becomes the contents of one row. This must be used inside of formatting-table, and in conjunction with formatting-cell.

stream is an output recording stream to which output will be done. The stream argument is not evaluated, and must be a symbol that is bound to a stream. If stream is t (the default), *standard-output* is used. body may have zero or more declarations as its first forms.

Once a table has had a row added to it via formatting-row, no columns may be added to it.

record-type specifies the class of output record to create. The default is standard-row-output-record. This argument should only be supplied by a programmer if there is a new class of output record that supports the row formatting protocol.

formatting-column Macro

formatting-column (&optional stream &key record-type) &body body

Summary: Binds the local environment in such a way the output of body will be grouped into a table column. All of the output performed by body becomes the contents of one column. This must be used inside of formatting-table, and in conjunction with formatting-cell.

stream is an output recording stream to which output will be done. The stream argument is not evaluated, and must be a symbol that is bound to a stream. If stream is t (the default), *standard-output* is used. body may have zero or more declarations as its first forms.

Once a table has had a column added to it via formatting-column, no rows may be added to it.

record-type specifies the class of output record to create. The default is standard-column-output-record. This argument should only be supplied if there is a new class of output record that supports the column formatting protocol.

formatting-cell Macro

formatting-cell (&optional stream &key (align-x :left) (align-y :baseline) min-width min-height record-type) &body body

Summary: Controls the output of a single cell inside a table row or column, or of a single item inside formatting-item-list. All of the output performed by body becomes the contents of the cell.

stream is an output recording stream to which output will be done. The stream argument is not evaluated, and must be a symbol that is bound to a stream. If stream is t (the default), *standard-output* is used. body may have zero or more declarations as its first forms.

align-x specifies how the output in a cell will be aligned relative to other cells in the same table column. The default, :left, causes the cells to be flush-left in the column. The other possible values are :right (meaning flush-right in the column) and :center (meaning centered in the column). Each cell within a column may have a different alignment; thus it is possible, for example, to have centered legends over flush-right numeric data.

align-y specifies how the output in a cell will be aligned vertically. The default, :baseline, causes textual cells to be aligned along their baselines and graphical cells to be aligned at the bottom. The other possible values are :bottom (align at the bottom of the output), :top (align at the top of the output), and :center (center the output in the cell).

min-width and min-height are used to specify minimum width or height of the cell. The default, nil, causes the cell to be only as wide or high as is necessary to contain the cell's contents. Otherwise, min-width and min-height are specified in the same way as the :x-spacing and :y-spacing arguments to formatting-table.

record-type specifies the class of output record to create. The default is standard-cell-output-record. This argument should only be supplied by a programmer if there is a new class of output record that supports the cell formatting protocol.

formatting-item-list Macro

formatting-item-list (&optional stream &key x-spacing y-spacing n-columns n-rows stream-width stream-height max-width max-height initial-spacing (row-wise t) (move-cursor t) record-type) &body body

Summary: Binds the local environment in such a way that the output of body will be done in an item list (that is, menu) format. This must be used in conjunction with formatting-cell, which delimits each item. The item list is placed so that its upper left corner is at the current text cursor position of stream. If the boolean move-cursor is t (the default), then the text cursor will be moved so that it immediately follows the last cell of the item list.

"Item list output" means that each row of the item list consists of a single cell. The first row is on top, and each succeeding row has its top aligned with the bottom of the previous row (plus the specified y-spacing). Multiple rows and columns are constructed after laying the item list out in a single column. Item list output takes place in a normalized +y-downward coordinate system.

The returned value is the output record corresponding to the table.

stream is an output recording stream to which output will be done. The stream argument is not evaluated, and must be a symbol that is bound to a stream. If stream is t (the default), *standard-output* is used. body may have zero or more declarations as its first forms.

x-spacing specifies the number of units of spacing to be inserted between columns of the item list; the default is the width of a #\Space character in the current text style. y-spacing specifies the number of units of spacing to be inserted between rows in the item list; the default is default vertical spacing of the stream. The format of these arguments is as for formatting-table.

When the boolean equalize-column-widths is t, all the columns will have the same width (the width of the widest cell in any column in the entire item list).

n-columns and n-rows specify the number of columns or rows in the item list. The default for both is nil, which causes CLIM to pick an aesthetically pleasing layout, possibly constrained by the other options. If both n-columns and n-rows are supplied and the item list contains more elements than will fit according to the specification, CLIM will format the item list as if n-rows were supplied as nil.

max-width and max-height constrain the layout of the item list. max-width can be overridden by n-rows. max-height can be overridden by n-columns.

formatting-item-list normally spaces items across the entire width of the stream. When initial-spacing is t, it inserts some whitespace (about half as much space as is between each item) before the first item on each line. When it is nil (the default), the initial whitespace is not inserted. If row-wise is t (the default) and the item list requires multiple columns, each successive element in the item list is laid out from left to right. If row-wise is nil and the item list requires multiple columns, each successive element in the item list is laid out below its predecessor, as in a telephone book.

record-type specifies the class of output record to create. The default is standard-item-list-output-record. Supply this argument s only if there is a new class of output record that supports the item list formatting protocol.

format-items Function

format-items items &key stream printer presentation-type x-spacing y-spacing n-columns n-rows max-width max-height cell-align-x cell-align-y initial-spacing (move-cursor t) record-type

Summary: This is a function interface to the item list formatter. The elements of the sequence items are formatted as separate cells within the item list.

stream is an output recording stream to which output will be done. It defaults to *standard-output*.

printer (default is prin1)is a function that takes two arguments, an item and a stream, and outputs the item on the stream. printer has dynamic extent.

presentation-type is a presentation-type. When printer is not supplied, the items will be printed as if printer were:

#'(lambda (item stream)
    (present item presentation-type :stream stream)) 

When printer is supplied, each item will be enclosed in a presentation whose type is presentation-type.

x-spacing, y-spacing, n-columns, n-rows, max-width, max-height, initial-spacing, and move-cursor are as for formatting-item-list.

cell-align-x and cell-align-y are used to supply :align-x and :align-y to an implicitly used formatting-cell.

record-type is as for formatting-item-list.

format-textual-list Function

format-textual-list sequence printer &key (stream *standard-output*) (separator ", ") conjunction

Summary: Outputs a sequence of items as a textual list.

Note that format-items is similar to formatting-item-list. Both operators do the same thing, except they accept their input differently:

Note that menus use the one-dimensional table formatting model.

17.1.3 Examples of Formatting Tables

17.1.3.1 Formatting a Table From a List

The example1 function formats a simple table whose contents come from a list.

(defvar *alphabet* '(a b c d e f g h i j k l m n o p q r s t u v w x y z))
 
(defun example1 (&optional (items *alphabet*)    
                 &key (stream *standard-output*) (n-columns 6)
                      y-spacing x-spacing) 
  (clim:formatting-table 
       (stream :y-spacing y-spacing                                  
            :x-spacing x-spacing) 
 
(do () 
    ((null items)) 
  (clim:formatting-row (stream)  
    (do ((i 0 (1+ i))) 
        ((or (null items) (= i n-columns)))  
      (clim:formatting-cell (stream) 
        (format stream "~A" (pop items))))))))

Evaluating (example1 *alphabet* :stream *my-window*) shows this table:

A B C D E F
G H I J K L
M N O P Q R
S T U V W X
Y Z

which demonstrates the result of evaluating the example1 function call without providing the :y-spacing and :x-spacing keywords. The defaults for these keywords makes tables whose elements are characters look reasonable.

You can easily vary the number of columns, and the spacing between rows or between columns. In the following example, we provide keyword arguments that change the appearance of the table.

Evaluating this form:

(example1 *alphabet* :stream *my-window* 
          :n-columns 10 :x-spacing 10         
          :y-spacing 10) 

shows this table with the :y-spacing keyword:

A  B  C  D  E  F  G  H  I  J
K  L  M  N  O  P  Q  R  S  T
U  V  W  X  Y  Z

(Note that this example can be done with formatting-item-list as shown in example4.)

17.1.3.2 Formatting a Table Representing a Calendar Month

The calendar-month function shows how you can format a table that represents a calendar month. The first row in the table acts as column headings representing the days of the week. The following rows are numbers representing the days of the month.

This example shows how you can align the contents of a cell. The column headings (Sun, Mon, Tue, etc.) are centered within the cells. However, the dates themselves (1, 2, 3, ... 31) are aligned to the right edge of the cells. The resulting calendar looks good because the dates are aligned in the natural way.

(in-package :clim-user)
 
(defvar *day-of-the-week-string* '((0 . "Mon")(1 . "Tue")
                                   (2 . "Wed")(3 . "Thu")
                                   (4 . "Fri")(5 . "Sat")
                                   (6 . "Sun")))
 
(defun day-of-the-week-string (day-of-week)
  (cdr (assoc day-of-week  *day-of-the-week-string*)))
 
(defvar *days-in-month* '((1 . 31)(2 . 28) ( 3 . 31)( 4 . 30)
                          (5 . 31)(6 . 30) ( 7 . 31)( 8 . 31)
                          (9 . 30)(10 . 31)(11 . 30)(12 . 31))
  "alist whose first element is numeric value returned by
decode-universal-time and second is the number of days in that month")
 
;; In a leap year, the month-length function increments the number of
;; days in February as required 
(defun leap-year-p (year)
  (cond ((and (integerp (/ year 100))
              (integerp (/ year 400)))
         t)
        ((and (not (integerp (/ year 100)))
              (integerp (/ year 4)))
         t)
        (t nil)))
 
(defun month-length (month year)
  (let ((days (cdr (assoc month *days-in-month*))))
    (when (and (eql month 2)
               (leap-year-p year))
      (incf days))
    days))
 
(defun calendar-month (month year &key (stream *standard-output*))
  (let ((days-in-month (month-length month year)))
    (multiple-value-bind (sec min hour date month year start-day)
        (decode-universal-time (encode-universal-time 
                                       0 0 0 1 month year))
      (setq start-day (mod (+ start-day 1) 7))
      (clim:formatting-table (stream)
        (clim:formatting-row (stream)
          (dotimes (d 7)
            (clim:formatting-cell (stream :align-x :center)
              (write-string (day-of-the-week-string 
                            (mod (- d 1) 7)) stream))))
        (do ((date 1)
             (first-week t nil))
            ((> date days-in-month))
          (clim:formatting-row (stream)
            (dotimes (d 7)
              (clim:formatting-cell (stream :align-x :right)
                (when (and (<= date days-in-month)
                           (or (not first-week) (>= d start-day)))
                  (format stream "~D" date)
                  (incf date))))))))))
 
(define-application-frame calendar ()
  ()
  (:panes
    (main :application
          :width :compute :height :compute
          :display-function 'display-main)))
 
(define-calendar-command (com-exit-calendar :menu "Exit") ()
  (frame-exit *application-frame*))
 
(defmethod display-main ((frame calendar) stream &key)
  (multiple-value-bind (sec min hour date month year start-day)
      (decode-universal-time (get-universal-time))
    (calendar-month month year :stream stream)))
 
(defun run ()
  (find-application-frame 'calendar))

Evaluating (calendar-month 5 90 :stream *my-stream*) shows this table:

Sun  Mon  Tue  Wed  Thu  Fri  Sat
            1    2    3    4    5
  6    7    8    9   10   11   12
 13   14   15   16   17   18   19
 20   21   22   23   24   25   26
 27   28   29   30   31
17.1.3.3 Formatting a Table With Regular Graphic Elements

The example2 function shows how you can draw graphics within the cells of a table. Each cell contains a rectangle of the same dimensions.

(defun example2 (&key (stream *standard-output*) 
                      y-spacing 
                      x-spacing)
  (clim:formatting-table
   (stream :y-spacing y-spacing
           :x-spacing x-spacing)
   (dotimes (i 3)
     (clim:formatting-row
      (stream)
      (dotimes (j 3)
        (clim:formatting-cell
         (stream)
         (clim:draw-rectangle* stream 10 10 50 50)))))))

Evaluating (example2 :stream *my-stream* :y-spacing 5) shows this table:

Example2 Table

17.1.3.4 Formatting a Table With Irregular Graphics in the Cells

The example3 function shows how you can format a table in which each cell contains graphics of different sizes.

(defun example3 (&optional (items *alphabet*)    
                 &key (stream *standard-output*) (n-columns 6) 
                      y-spacing x-spacing) 
  (clim:formatting-table
      (stream :y-spacing y-spacing   
              :x-spacing x-spacing) 
   (do ()
       ((null items)) 
     (clim:formatting-row (stream) 
        (do ((i 0 (1+ i)))
            ((or (null items) (= i n-columns))) 
          (clim:formatting-cell (stream)    
             (clim:draw-polygon* stream  
                                (list 0 0 (* 10 (1+ (random 3))) 
                                      5 5 (* 10 (1+ (random 3)))) 
                                :filled nil) 
            (pop items))))))) 

Evaluating (example3 *alphabet* :stream *my-stream*) shows this table:

Example3 Table

17.1.3.5 Formatting a Table of a Sequence of Items

The example4 function shows how you can use formatting-item-list to format a table of a sequence of items when the exact arrangement of the items and the table is not important. Note that you use formatting-cell inside the body of formatting-item-list to output each item. You do not use formatting-column or formatting-row, because CLIM figures out the number of columns and rows automatically (or obeys a constraint given in a keyword argument).

(defun example4 (&optional (items *alphabet*) 
                           &key (stream *standard-output*) n-columns n-rows
                           y-spacing x-spacing 
                           max-width max-height) 
  (clim:formatting-item-list 
   (stream :y-spacing y-spacing 
           :x-spacing x-spacing 
           :n-columns n-columns :n-rows n-rows 
           :max-width max-width :max-height max-height) 
   (do () 
       ((null items)) 
     (clim:formatting-cell (stream)
                           (format stream "~A" (pop items)))))) 

Evaluating (example4 :stream *my-window*) shows this table:

A B C D
E F G H
I J K L
M N O P
Q R S T
U V W X
Y Z

You can easily add a constraint specifying the number of columns.

Evaluating (example4 :stream *my-stream* :n-columns 8) gives this:

A B C D E F G H
I J K L M N O P
Q R S T U V W X
Y Z

CLIM 2.0 User Guide - 01 Dec 2021 19:39:01