plot-histogram.rkt
#lang racket
;;; Science Collection
;;; plot-histogram.ss
;;; Copyright (c) 2004-2011 M. Douglas Williams
;;;
;;; This file is part of the Science Collection.
;;;
;;; The Science Collection is free software: you can redistribute it and/or
;;; modify it under the terms of the GNU Lesser General Public License as
;;; published by the Free Software Foundation, either version 3 of the License
;;; or (at your option) any later version.
;;;
;;; The Science Collection is distributed in the hope that it will be useful,
;;; but WITHOUT WARRANTY; without even the implied warranty of MERCHANTABILITY
;;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
;;; License for more details.
;;;
;;; You should have received a copy of the GNU Lesser General Public License
;;; along with the Science Collection.  If not, see
;;; <http://www.gnu.org/licenses/>.
;;;
;;; -------------------------------------------------------------------
;;;
;;; This code adds histogram plotting to the PLoT collection in PLT
;;; Scheme (lib "plot.ss" "plot").
;;;
;;; Version Date      Description
;;; 0.1.0   08/27/04  This is the initial release of the histogram
;;;                   plotting extension to the PLoT collection in PLT
;;;                   Scheme (Doug Williams)
;;; 1.0.0   09/28/04  Marked as ready for Release 1.0.  (Doug Williams)
;;; 2.0.0   06/07/08  More V4.0 changes.  (Doug Williams)
;;; 4.0.0   08/16/11  Changed the header and restructured the code. (MDW)

(require plot/plot-extend)

;;; Draw a bar given the width
(define (draw-bar-given-width x width width-factor y view)
  (let* ((half-whitespace (/ (* width (- 1.0 width-factor)) 2.0))
         (x1 (+ x half-whitespace))
         (x2 (- (+ x width) half-whitespace)))
    (send view fill
          `(,x1 ,x1 ,x2 ,x2)
          `(0 ,y ,y 0))))

;;; Draw a bar
(define (draw-bar x1 x2 width-factor y view)
  (let* ((half-whitespace (/ (* (- x2 x1) (- 1.0 width-factor)) 2.0))
         (xx1 (+ x1 half-whitespace))
         (xx2 (- x2 half-whitespace)))
    (send view fill
          `(,xx1 ,xx1 ,xx2 ,xx2)
          `(0 ,y ,y 0))))

;;; Draw a histogram with equal width bins
(define (draw-histogram-with-equal-width-bins 
         bins x-min x-max width-factor view)
  (let* ((n (vector-length bins))
         (bin-width (/ (- x-max x-min) n)))
    (do ((i 0 (+ i 1)))
        ((= i n) (void))
      (draw-bar-given-width
       (+ x-min (* i bin-width)) bin-width width-factor
       (vector-ref bins i) view))))

;;; Draw a histogram (with specified bin ranges)
(define (draw-histogram bins x-ranges width-factor view)
  (let ((n (vector-length bins)))
    (do ((i 0 (+ i 1)))
        ((= i n) (void))
      (draw-bar (vector-ref x-ranges i)
                (vector-ref x-ranges (+ i 1))
                width-factor
                (vector-ref bins i)
                view))))

;;; Define histogram plot type
;;;
;;; The histogram data to be plotted may be provided in either of two
;;; formats:
;;; 1) A vector of binned data - if a single vector of binned data is
;;;    given, it is interpreted as n fixed width bins, where n is the
;;;    the length of the vector, with x ranging from x-min to x-max.
;;; 2) A list of two vectors - the first contains the binned data and
;;;    the second contains the x ranges; it is interpreted as n
;;;    (possibly) variable width bins, where n is the length of the
;;;    first vector; the x range for the i-th bin are the i-th
;;;    (lower) and (i+1)-th elements of the second vector; the length
;;;    of the second vector must be one greater than the length of
;;;    the first vector.
(define-plot-type histogram
  data 2dview (x-min x-max) ((color 'black) (width 1.0))
  (begin
    (send 2dview set-line-color color)
    (set! width (max width 0.0))
    (set! width (min width 1.0))
    (cond ((vector? data)
           ;; bin data with equal width bins
           (draw-histogram-with-equal-width-bins
            data x-min x-max width 2dview))
          ((and (list? data)
                (= (length data) 2)
                (vector? (car data))
                (vector? (cadr data))
                (= (vector-length (cadr data))
                   (+ (vector-length (car data)) 1)))
           ;; bin data and limits data
           (draw-histogram (car data) (cadr data) width 2dview))
          (else
           (error 'plot
                  "histogram plot data error")))))

;;; Module Contracts

(provide
 histogram)