Plotter

April 11, 2014

Our program is simple, but tedious. It first reads the specification (all the rules are required, but they may appear in any order) and the data, checks that all the specifications have been given, then plots the data by writing points to a matrix of characters, and finally writes the graph to the output. We present the complete program without further comment:

(define height 24) ; number of printable rows
(define width 80) ; number of printable columns
(define ox 10) ; x-origin, leave room for y-ticks
(define oy 1) ; y-origin, leave room for x-ticks

(define label #f) ; graph label on top row
(define y-lo #f) ; lo end of y-axis
(define y-hi #f) ; hi end of y-axis
(define x-lo #f) ; lo end of x-axis
(define x-hi #f) ; hi end of x-axis
(define y-ticks (list)) ; list of y-axis tick marks
(define x-ticks (list)) ; list of x-axis tick marks
(define data (list)) ; list of x/y points to plot
(define grid (make-matrix width height #\space))

(define (graph file-name)
  (set! label #f) (set! y-lo #f) (set! y-hi #f) (set! x-lo #f) (set! x-hi #f)
  (set! y-ticks (list)) (set! x-ticks (list)) (set! data (list))
  (set! grid (make-matrix width height #\space))
  (with-input-from-file file-name (lambda ()
    (let loop ((line (read-line)))
      (if (eof-object? line) (plot)
        (let ((fields (string-split #\space line)))
          (cond ((null? fields) (verify-parameters))
                ((null? (cddr fields))
                  (set! data (cons (cons (string->number (car fields))
                                         (string->number (cadr fields))) data)))
                ((string=? (car fields) "label")
                  (set! label (substring line
                    (+ (string-length "label") 1)
                    (string-length line))))
                ((and (string=? (car fields) "left")
                      (string=? (cadr fields) "range"))
                  (set! y-lo (string->number (caddr fields)))
                  (set! y-hi (string->number (cadddr fields))))
                ((and (string=? (car fields) "bottom")
                      (string=? (cadr fields) "range"))
                  (set! x-lo (string->number (caddr fields)))
                  (set! x-hi (string->number (cadddr fields))))
                ((and (string=? (car fields) "left")
                      (string=? (cadr fields) "ticks"))
                  (set! y-ticks (map string->number (cddr fields))))
                ((and (string=? (car fields) "bottom")
                      (string=? (cadr fields) "ticks"))
                  (set! x-ticks (map string->number (cddr fields))))
                (else (error 'graph "unrecognized input")))
        (loop (read-line))))))))

(define (verify-parameters)
  (when (not (and label y-lo y-hi x-lo x-hi y-ticks x-ticks))
    (error 'verify-parameters "missing parameters"))
  (when (not (< y-lo y-hi))
    (error 'verify-parameters "invalid left range"))
  (when (not (< x-lo x-hi))
    (error 'verify-parameters "invalid bottom range"))
  (when (not (apply < y-ticks))
    (error 'verify-parameters "invalid left ticks"))
  (when (not (apply < x-ticks))
    (error 'verify-parameters "invalid bottom ticks")

(define (plot)
  (frame) (ticks) (labels) (points) (draw))

(define (frame)
  (for (i ox width)
    (matrix-set! grid i oy #\-)
    (matrix-set! grid i (- height 2) #\-))
  (for (i oy (- height 1))
    (matrix-set! grid ox i #\|)
    (matrix-set! grid (- width 1) i #\|)))

(define (ticks)
  (do ((ts y-ticks (cdr ts))) ((null? ts))
    (matrix-set! grid ox (y-scale (car ts)) #\-)
    (display-at (y-scale (car ts)) (- ox 1 (string-length (number->string (car ts))))
      (number->string (car ts))))
  (do ((ts x-ticks (cdr ts))) ((null? ts))
    (matrix-set! grid (x-scale (car ts)) oy #\|)
    (display-at (- oy 1) (- (x-scale (car ts))
      (quotient (string-length (number->string (car ts))) 2))
      (number->string (car ts)))))

(define (labels)
  (display-at (- height oy) (quotient (- width (string-length label)) 2) label))

(define (points)
  (do ((ds data (cdr ds))) ((null? ds))
    (matrix-set! grid (x-scale (caar ds)) (y-scale (cdar ds)) #\*)))

(define (x-scale x)
  (round (+ (* (/ (- x x-lo) (- x-hi x-lo)) (- width 1 ox)) ox)))

(define (y-scale y)
  (round (+ (* (/ (- y y-lo) (- y-hi y-lo)) (- height 3 oy)) oy)))

(define (display-at r c str)
  (for (i 0 (string-length str))
    (matrix-set! grid (+ i c) r (string-ref str i))))

(define (draw)
  (for (r (- height 1) -1 -1)
    (for (c 0 width)
      (display (matrix-ref grid c r)))
    (newline)))

Assuming the data file from the previous page is in a file called AnnualTrafficDeaths, produce output like this:

> (graph "AnnualTrafficDeaths")
                     Annual Traffic Deaths, USA, 1899-2012                      
          |--------------------------------------------------------------------|
          |                                        * *                         |
          |                                      ****   **                     |
          |                                            ***                     |
    45000 -                                     **    **    ***                |
          |                                     *         ***  * *******       |
          |                                    *                **      *      |
          |                      * *     ******                         *      |
          |                     *  *    ** * *                           * *   |
    30000 -                  ***  *   ***                                 *    |
          |                 * *     * *                                        |
          |                 *                                                  |
          |                *        **                                         |
          |              **                                                    |
    15000 -              *                                                     |
          |             *                                                      |
          |           **                                                       |
          |          **                                                        |
          |        **                                                          |
        0 - ********                                                           |
          |                                                                    |
          |-----|----------|----------|----------|----------|----------|-------|
              1905       1925       1945       1965       1985       2005       

We used matrices, read-line, and string-split from the Standard Prelude. You can run the program at http://programmingpraxis.codepad.org/SvXJRh3u.

About these ads

Pages: 1 2 3

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 634 other followers

%d bloggers like this: