Added "Axis Transforms and Ticks" doc page

This commit is contained in:
Neil Toronto 2011-11-11 18:11:52 -07:00
parent 5a3c78a998
commit 723a54a4d9
20 changed files with 752 additions and 317 deletions

View File

@ -15,23 +15,24 @@
[(invertible-function f2 g2) f2])
(invertible-function (compose f1 f2) (compose g2 g1))))
(defproc (invertible-inverse [h invertible-function?]) invertible-function?
(defproc (invertible-inverse [h invertible-function?]) invertible-function? #:document-body
(match-define (invertible-function f g) h)
(invertible-function g f))
(defcontract axis-transform/c (real? real? invertible-function? . -> . invertible-function?))
(defproc (id-transform [x-min real?] [x-max real?] [old-function invertible-function?]
) invertible-function?
old-function)
(defthing id-transform axis-transform/c
(λ (x-min x-max old-function) old-function))
(defthing id-function invertible-function? (invertible-function (λ (x) x) (λ (x) x)))
(defproc (apply-axis-transform [t axis-transform/c] [x-min real?] [x-max real?]) invertible-function?
(defproc (apply-axis-transform [t axis-transform/c] [x-min real?] [x-max real?]
) invertible-function? #:document-body
(t x-min x-max id-function))
;; Turns any total, surjective, monotone real function and its inverse into an axis transform
(defproc (make-axis-transform [f (real? . -> . real?)] [g (real? . -> . real?)]) axis-transform/c
(defproc (make-axis-transform [fun invertible-function?]) axis-transform/c
(match-define (invertible-function f g) fun)
(λ (x-min x-max old-function)
(define fx-min (f x-min))
(define fx-scale (/ (- x-max x-min) (- (f x-max) fx-min)))
@ -46,24 +47,28 @@
(λ (x-min x-max old-function)
(t1 x-min x-max (t2 x-min x-max old-function))))
(defproc (axis-transform-append [t1 axis-transform/c] [t2 axis-transform/c] [x-mid real?]
(defproc (axis-transform-append [t1 axis-transform/c] [t2 axis-transform/c] [mid real?]
) axis-transform/c
(λ (x-min x-max old-function)
(match-define (invertible-function old-f old-g) old-function)
(let ([x-mid (old-f x-mid)])
(cond [(x-mid . >= . x-max) (t1 x-min x-max old-function)]
[(x-mid . <= . x-min) (t2 x-min x-max old-function)]
(let ([mid (old-f mid)])
(cond [(mid . >= . x-max) (t1 x-min x-max old-function)]
[(mid . <= . x-min) (t2 x-min x-max old-function)]
[else
(match-define (invertible-function f1 g1) (t1 x-min x-mid old-function))
(match-define (invertible-function f2 g2) (t2 x-mid x-max old-function))
((make-axis-transform (λ (x) (cond [((old-f x) . < . x-mid) (f1 x)]
[else (f2 x)]))
(λ (x) (cond [(x . < . x-mid) (g1 x)]
[else (g2 x)])))
(match-define (invertible-function f1 g1) (t1 x-min mid old-function))
(match-define (invertible-function f2 g2) (t2 mid x-max old-function))
((make-axis-transform
(invertible-function
(λ (x) (cond [((old-f x) . < . mid) (f1 x)]
[else (f2 x)]))
(λ (x) (cond [(x . < . mid) (g1 x)]
[else (g2 x)]))))
x-min x-max id-function)]))))
(defproc (axis-transform-bound [t axis-transform/c] [x-min real?] [x-max real?]) axis-transform/c
(axis-transform-append (axis-transform-append id-transform t x-min) id-transform x-max))
(defproc (axis-transform-bound [t axis-transform/c] [a real?] [b real?]
) axis-transform/c #:document-body
(axis-transform-append
(axis-transform-append id-transform t a) id-transform b))
;; ===================================================================================================
;; Specific axis transforms
@ -107,20 +112,21 @@
(define (real-exp x)
(flexp (exact->inexact x)))
(defproc (log-transform [x-min real?] [x-max real?] [old-function invertible-function?]
) invertible-function?
(when ((exact->inexact x-min) . <= . 0)
(raise-type-error 'log-transform "positive real" 0 x-min x-max))
((make-axis-transform real-log real-exp) x-min x-max old-function))
(defthing log-transform axis-transform/c
(λ (x-min x-max old-function)
(when ((exact->inexact x-min) . <= . 0)
(raise-type-error 'log-transform "positive real" 0 x-min x-max))
((make-axis-transform (invertible-function real-log real-exp)) x-min x-max old-function)))
(defproc (cbrt-transform [x-min real?] [x-max real?] [old-function invertible-function?]
) invertible-function?
((make-axis-transform cbrt cube) x-min x-max old-function))
(defthing cbrt-transform axis-transform/c
(λ (x-min x-max old-function)
((make-axis-transform (invertible-function cbrt cube)) x-min x-max old-function)))
(defproc (hand-drawn-transform [freq (>/c 0)]) axis-transform/c
(λ (x-min x-max old-function)
(define d (/ freq (- x-max x-min)))
((make-axis-transform (sine-diag d) (sine-diag-inv d)) x-min x-max old-function)))
((make-axis-transform (invertible-function (sine-diag d) (sine-diag-inv d)))
x-min x-max old-function)))
;; ===================================================================================================
@ -132,8 +138,7 @@
[(x . > . b) (+ (- x d) ds)]
[else (+ a (* (- x a) s))])))
(defproc (stretch-transform [a real?] [b real?] [scale (and/c real? (not/c (=/c 0)))]
) axis-transform/c
(defproc (stretch-transform [a real?] [b real?] [scale (>/c 0)]) axis-transform/c
(when (a . > . b) (error 'stretch-transform "expected a <= b; given ~e and ~e" a b))
(λ (x-min x-max old-function)
(match-define (invertible-function old-f old-g) old-function)
@ -141,7 +146,7 @@
[b (old-f b)])
(define f (stretch a b scale))
(define g (stretch (f a) (f b) (/ 1 scale)))
((make-axis-transform f g) x-min x-max old-function))))
((make-axis-transform (invertible-function f g)) x-min x-max old-function))))
(defproc (collapse-transform [a real?] [b real?]) axis-transform/c
(when (a . > . b) (error 'stretch-transform "expected a <= b; given ~e and ~e" a b))
@ -157,4 +162,4 @@
(define (g x) (cond [(x . < . center) (- x 1/2size)]
[(x . > . center) (+ x 1/2size)]
[else center]))
((make-axis-transform f g) x-min x-max old-function))))
((make-axis-transform (invertible-function f g)) x-min x-max old-function))))

View File

@ -41,16 +41,6 @@
(defparam plot-y-far-axis? boolean? #t)
(defparam plot-z-far-axis? boolean? #t)
(defparam plot-x-max-ticks exact-positive-integer? 5)
(defparam plot-y-max-ticks exact-positive-integer? 5)
(defparam plot-z-max-ticks exact-positive-integer? 8)
(defparam plot-d-max-ticks exact-positive-integer? 6)
(defparam plot-r-max-ticks exact-positive-integer? 8)
(defparam plot-x-far-max-ticks exact-positive-integer? 5)
(defparam plot-y-far-max-ticks exact-positive-integer? 5)
(defparam plot-z-far-max-ticks exact-positive-integer? 8)
(defparam plot-decorations? boolean? #t)
(define-parameter-group plot-axes?
@ -59,14 +49,6 @@
plot-z-axis? plot-z-far-axis?)
#:struct list)
(define-parameter-group plot-max-ticks
(plot-x-max-ticks plot-x-far-max-ticks
plot-y-max-ticks plot-y-far-max-ticks
plot-z-max-ticks plot-z-far-max-ticks
plot-d-max-ticks
plot-r-max-ticks)
#:struct list)
(define-parameter-group plot-appearance
(plot-width
plot-height
@ -75,7 +57,7 @@
plot-line-width plot-tick-size
plot-font-size plot-font-family
plot-legend-anchor plot-legend-box-alpha
plot-axes? plot-max-ticks plot-decorations?
plot-axes? plot-decorations?
plot-animating?))
(defproc (pen-gap) real? #:document-body
@ -135,9 +117,9 @@
(defparam plot-x-ticks ticks? (linear-ticks))
(defparam plot-y-ticks ticks? (linear-ticks))
(defparam plot-z-ticks ticks? (linear-ticks))
(defparam plot-d-ticks ticks? (linear-ticks #:divisors '(1 2 4 5)))
(defparam plot-r-ticks ticks? (linear-ticks))
(defparam plot-z-ticks ticks? (linear-ticks #:number 8))
(defparam plot-d-ticks ticks? (linear-ticks #:number 6 #:divisors '(1 2 4 5)))
(defparam plot-r-ticks ticks? (linear-ticks #:number 8))
(defparam plot-x-far-ticks ticks? (ticks-mimic plot-x-ticks))
(defparam plot-y-far-ticks ticks? (ticks-mimic plot-y-ticks))
@ -151,30 +133,6 @@
(define-parameter-group plot-axes (plot-x-axis plot-y-axis plot-z-axis plot-d-ticks plot-r-ticks)
#:struct list)
(defproc (default-x-ticks [x-min real?] [x-max real?]) (listof tick?) #:document-body
((plot-x-ticks) x-min x-max (plot-x-max-ticks)))
(defproc (default-y-ticks [y-min real?] [y-max real?]) (listof tick?) #:document-body
((plot-y-ticks) y-min y-max (plot-y-max-ticks)))
(defproc (default-z-ticks [z-min real?] [z-max real?]) (listof tick?) #:document-body
((plot-z-ticks) z-min z-max (plot-z-max-ticks)))
(defproc (default-d-ticks [d-min real?] [d-max real?]) (listof tick?) #:document-body
((plot-d-ticks) d-min d-max (plot-d-max-ticks)))
(defproc (default-r-ticks [r-min real?] [r-max real?]) (listof tick?) #:document-body
((plot-r-ticks) r-min r-max (plot-r-max-ticks)))
(defproc (default-x-far-ticks [x-min real?] [x-max real?]) (listof tick?) #:document-body
((plot-x-far-ticks) x-min x-max (plot-x-far-max-ticks)))
(defproc (default-y-far-ticks [y-min real?] [y-max real?]) (listof tick?) #:document-body
((plot-y-far-ticks) y-min y-max (plot-y-far-max-ticks)))
(defproc (default-z-far-ticks [z-min real?] [z-max real?]) (listof tick?) #:document-body
((plot-z-far-ticks) z-min z-max (plot-z-far-max-ticks)))
;; ===================================================================================================
(define-parameter-group plot-parameters

View File

@ -25,12 +25,12 @@
(λ (r)
(match r
[(vector (ivl xa xb) (ivl ya yb))
(values (default-x-ticks xa xb) (default-x-far-ticks xa xb)
(default-y-ticks ya yb) (default-y-far-ticks ya yb))]
(values ((plot-x-ticks) xa xb) ((plot-x-far-ticks) xa xb)
((plot-y-ticks) ya yb) ((plot-y-far-ticks) ya yb))]
[(vector (ivl xa xb) (ivl ya yb) (ivl za zb))
(values (default-x-ticks xa xb) (default-x-far-ticks xa xb)
(default-y-ticks ya yb) (default-y-far-ticks ya yb)
(default-z-ticks za zb) (default-z-far-ticks za zb))]
(values ((plot-x-ticks) xa xb) ((plot-x-far-ticks) xa xb)
((plot-y-ticks) ya yb) ((plot-y-far-ticks) ya yb)
((plot-z-ticks) za zb) ((plot-z-far-ticks) za zb))]
[_ (raise-type-error 'default-ticks-fun "2- or 3-vector of ivl" r)])))
(defproc (function-bounds-fun [f sampler/c] [samples exact-nonnegative-integer?]) bounds-fun/c

View File

@ -29,7 +29,7 @@
(define epsilon (expt 10 (- (digits-for-range z-min z-max))))
(match-define (ticks layout format) (plot-z-ticks))
(define ts
(cond [(eq? levels 'auto) (filter pre-tick-major? (layout z-min z-max (plot-z-max-ticks)))]
(cond [(eq? levels 'auto) (filter pre-tick-major? (layout z-min z-max))]
[else (define zs (cond [(list? levels) (filter (λ (z) (<= z-min z z-max)) levels)]
[else (linear-seq z-min z-max levels #:start? #f #:end? #f)]))
(map (λ (z) (pre-tick z #t)) zs)]))
@ -56,7 +56,7 @@
(define epsilon (expt 10 (- (digits-for-range d-min d-max))))
(match-define (ticks layout format) (plot-d-ticks))
(define ts
(cond [(eq? levels 'auto) (filter pre-tick-major? (layout d-min d-max (plot-d-max-ticks)))]
(cond [(eq? levels 'auto) (filter pre-tick-major? (layout d-min d-max))]
[else (define ds (cond [(list? levels) (filter (λ (d) (<= d-min d d-max)) levels)]
[else (linear-seq d-min d-max levels #:start? #f #:end? #f)]))
(map (λ (d) (pre-tick d #t)) ds)]))

View File

@ -20,17 +20,16 @@
(struct ticks (layout format) #:transparent
#:property prop:procedure
(λ (t x-min x-max max-ticks)
(λ (t x-min x-max)
(match-define (ticks layout format) t)
(define ts (layout x-min x-max max-ticks))
(define ts (layout x-min x-max))
(match-define (list (pre-tick xs majors) ...) ts)
(map tick xs majors (format x-min x-max ts))))
(defcontract ticks-layout/c
(real? real? exact-positive-integer? . -> . (listof pre-tick?)))
(defcontract ticks-layout/c (real? real? . -> . (listof pre-tick?)))
(defcontract ticks-format/c (real? real? (listof pre-tick?) . -> . (listof string?)))
(defcontract ticks-format/c
(real? real? (listof pre-tick?) . -> . (listof string?)))
(defparam ticks-default-number exact-positive-integer? 5)
;; ===================================================================================================
;; Helpers
@ -108,11 +107,12 @@
(define minor-xs (linear-minor-values/step major-xs step (- n 1)))
(values major-xs (filter (λ (x) (<= x-min x x-max)) minor-xs))))
(defproc (linear-ticks-layout [#:base base (and/c exact-integer? (>=/c 2)) 10]
(defproc (linear-ticks-layout [#:number number exact-positive-integer? (ticks-default-number)]
[#:base base (and/c exact-integer? (>=/c 2)) 10]
[#:divisors divisors (listof exact-positive-integer?) '(1 2 5)]
) ticks-layout/c
(λ (x-min x-max max-ticks)
(define-values (major-xs minor-xs) (linear-tick-values x-min x-max max-ticks base divisors))
(λ (x-min x-max)
(define-values (major-xs minor-xs) (linear-tick-values x-min x-max number base divisors))
(tick-values->pre-ticks major-xs minor-xs)))
(defproc (linear-ticks-format) ticks-format/c
@ -123,32 +123,41 @@
(for/list ([t (in-list ts)])
(real->plot-label (pre-tick-value t) digits)))))
(defproc (linear-ticks [#:base base (and/c exact-integer? (>=/c 2)) 10]
[#:divisors divisors (listof exact-positive-integer?) '(1 2 5)]) ticks?
(ticks (linear-ticks-layout #:base base #:divisors divisors)
(defproc (linear-ticks [#:number number exact-positive-integer? (ticks-default-number)]
[#:base base (and/c exact-integer? (>=/c 2)) 10]
[#:divisors divisors (listof exact-positive-integer?) '(1 2 5)]
) ticks? #:document-body
(ticks (linear-ticks-layout #:number number #:base base
#:divisors divisors)
(linear-ticks-format)))
;; ===================================================================================================
;; No ticks
(defproc (no-ticks-layout) ticks-layout/c
(λ (x-min x-max max-ticks) empty))
(defthing no-ticks-layout ticks-layout/c #:document-value
(λ (x-min x-max) empty))
(defproc (no-ticks) ticks?
(ticks (no-ticks-layout) (linear-ticks-format)))
(defthing no-ticks-format ticks-format/c #:document-value
(λ (x-min x-max pre-ticks)
(map (λ (_) "") pre-ticks)))
(defthing no-ticks ticks? #:document-value
(ticks no-ticks-layout no-ticks-format))
;; ===================================================================================================
;; Exponential ticks (for log scale)
(defproc (log-ticks-layout [#:base base (and/c exact-integer? (>=/c 2)) 10]) ticks-layout/c
(λ (x-min x-max max-ticks)
(defproc (log-ticks-layout [#:number number exact-positive-integer? (ticks-default-number)]
[#:base base (and/c exact-integer? (>=/c 2)) 10]
) ticks-layout/c
(λ (x-min x-max)
(with-exact-bounds
x-min x-max
(when ((exact->inexact x-min) . <= . 0)
(raise-type-error 'log-ticks-layout "positive real" 0 x-min x-max))
(define log-start (ceiling-log/base base x-min))
(define log-end (floor-log/base base x-max))
(define skip (max 1 (ceiling (/ (+ 1 (- log-end log-start)) max-ticks))))
(define skip (max 1 (ceiling (/ (+ 1 (- log-end log-start)) number))))
(filter (λ (t) (<= x-min (pre-tick-value t) x-max))
(append*
(for/list ([log-x (in-range (- log-start 1) (+ log-end 2))]
@ -177,8 +186,10 @@
(real->plot-label (/ x (expt base log-x)) base-digits)
(major-str))])))))
(defproc (log-ticks [#:base base (and/c exact-integer? (>=/c 2)) 10]) ticks?
(ticks (log-ticks-layout #:base base)
(defproc (log-ticks [#:number number exact-positive-integer? (ticks-default-number)]
[#:base base (and/c exact-integer? (>=/c 2)) 10]
) ticks? #:document-body
(ticks (log-ticks-layout #:number number #:base base)
(log-ticks-format #:base base)))
;; ===================================================================================================
@ -213,48 +224,40 @@
;; ===================================================================================================
;; Date ticks
(define 12h-descending-date-ticks-formats
'("~Y-~m-~d ~I:~M:~f ~p"
"~Y-~m-~d ~I:~M ~p"
"~Y-~m-~d ~I ~p"
"~Y-~m-~d"
"~Y-~m"
"~Y"
"~m-~d ~I:~M:~f ~p"
"~m-~d ~I:~M ~p"
"~m-~d ~I ~p"
"~m-~d"
"~I:~M:~f ~p"
"~I:~M ~p"
"~I ~p"
"~M:~fs"
"~Mm"
"~fs"))
(define 24h-descending-date-ticks-formats
(defthing 24h-descending-date-ticks-formats (listof string?) #:document-value
'("~Y-~m-~d ~H:~M:~f"
"~Y-~m-~d ~H:~M"
"~Y-~m-~d ~Hh"
"~Y-~m-~d"
"~Y-~m"
"~Y"
"~m-~d ~H:~M:~f"
"~m-~d ~H:~M"
"~m-~d ~Hh"
"~m-~d"
"~H:~M:~f"
"~H:~M"
"~Hh"
"~M:~fs"
"~Mm"
"~fs"))
(defthing 12h-descending-date-ticks-formats (listof string?) #:document-value
'("~Y-~m-~d ~I:~M:~f ~p"
"~Y-~m-~d ~I:~M ~p"
"~Y-~m-~d ~I ~p"
"~Y-~m-~d"
"~Y-~m"
"~Y"
"~m-~d ~I:~M:~f ~p"
"~m-~d ~I:~M ~p"
"~m-~d ~I ~p"
"~m-~d"
"~I:~M:~f ~p"
"~I:~M ~p"
"~I ~p"
"~M:~fs"
"~Mm"
"~fs"))
(defparam date-ticks-formats (listof string?) 24h-descending-date-ticks-formats)
@ -316,9 +319,10 @@
(define major-xs (linear-major-values/step x-min x-max step))
(values (map date-round major-xs) empty)))
(defproc (date-ticks-layout) ticks-layout/c
(λ (x-min x-max max-ticks)
(define-values (major-xs minor-xs) (date-tick-values x-min x-max max-ticks))
(defproc (date-ticks-layout [#:number number exact-positive-integer? (ticks-default-number)]
) ticks-layout/c
(λ (x-min x-max)
(define-values (major-xs minor-xs) (date-tick-values x-min x-max number))
(tick-values->pre-ticks major-xs minor-xs)))
(defproc (date-ticks-format [#:formats formats (listof string?) (date-ticks-formats)]) ticks-format/c
@ -336,29 +340,40 @@
(define fmt-list (choose-format-list formatter fmt-lists (list last-x x)))
(string-append* (apply-formatter formatter fmt-list x))))]))))
(defproc (date-ticks [#:formats formats (listof string?) (date-ticks-formats)]) ticks?
(ticks (date-ticks-layout)
(defproc (date-ticks [#:number number exact-positive-integer? (ticks-default-number)]
[#:formats formats (listof string?) (date-ticks-formats)]
) ticks? #:document-body
(ticks (date-ticks-layout #:number number)
(date-ticks-format #:formats formats)))
;; ===================================================================================================
;; Time ticks
(define descending-time-ticks-formats
(defthing 24h-descending-time-ticks-formats (listof string?) #:document-value
'("~dd ~H:~M:~f"
"~dd ~H:~M"
"~dd ~Hh"
"~dd"
"~H:~M:~f"
"~H:~M"
"~Hh"
"~M:~fs"
"~Mm"
"~fs"))
(defparam time-ticks-formats (listof string?) descending-time-ticks-formats)
(defthing 12h-descending-time-ticks-formats (listof string?) #:document-value
'("~dd ~I:~M:~f ~p"
"~dd ~I:~M ~p"
"~dd ~I ~p"
"~dd"
"~I:~M:~f ~p"
"~I:~M ~p"
"~I ~p"
"~M:~fs"
"~Mm"
"~fs"))
(defparam time-ticks-formats (listof string?) 24h-descending-time-ticks-formats)
;; Tick steps to try, in seconds
(define time-steps
@ -408,9 +423,10 @@
(define major-xs (linear-major-values/step x-min x-max step))
(values major-xs empty)))
(defproc (time-ticks-layout) ticks-layout/c
(λ (x-min x-max max-ticks)
(define-values (major-xs minor-xs) (time-tick-values x-min x-max max-ticks))
(defproc (time-ticks-layout [#:number number exact-positive-integer? (ticks-default-number)]
) ticks-layout/c
(λ (x-min x-max)
(define-values (major-xs minor-xs) (time-tick-values x-min x-max number))
(tick-values->pre-ticks major-xs minor-xs)))
(defproc (time-ticks-format [#:formats formats (listof string?) (time-ticks-formats)]) ticks-format/c
@ -428,8 +444,10 @@
(define fmt-list (choose-format-list formatter fmt-lists (list last-x x)))
(string-append* (apply-formatter formatter fmt-list x))))]))))
(defproc (time-ticks [#:formats formats (listof string?) (time-ticks-formats)]) ticks?
(ticks (time-ticks-layout)
(defproc (time-ticks [#:number number exact-positive-integer? (ticks-default-number)]
[#:formats formats (listof string?) (time-ticks-formats)]
) ticks? #:document-body
(ticks (time-ticks-layout #:number number)
(time-ticks-format #:formats formats)))
;; ===================================================================================================
@ -462,33 +480,36 @@
(define unit-x (/ (pre-tick-value t) unit))
(format format-str (real->plot-label unit-x digits #f))))))
(defproc (bit/byte-ticks [#:size size (or/c 'byte 'bit) 'byte]
[#:kind kind (or/c 'CS 'SI) 'CS]) ticks?
(define layout
(case kind
[(SI) (linear-ticks-layout #:base 10 #:divisors '(1 2 5))]
[else (linear-ticks-layout #:base 2 #:divisors '(1 2))]))
(ticks layout (bit/byte-ticks-format #:size size #:kind kind)))
(defproc (bit/byte-ticks [#:number number exact-positive-integer? (ticks-default-number)]
[#:size size (or/c 'byte 'bit) 'byte]
[#:kind kind (or/c 'CS 'SI) 'CS]
) ticks? #:document-body
(define si? (eq? kind 'SI))
(ticks (linear-ticks-layout #:number number #:base (if si? 10 2)
#:divisors (if si? '(1 2 5) '(1 2)))
(bit/byte-ticks-format #:size size #:kind kind)))
;; ===================================================================================================
;; Currency
;; US "short scale" suffixes
(define us-currency-scales '("" "K" "M" "B" "T"))
;; The UK officially uses the short scale now
;; Million is abbreviated "m" instead of "mn" because "mn" stands for minutes; also, the Daily
;; Telegraph Style Guide totally says to use "m"
(define uk-currency-scales '("" "k" "m" "bn" "tr"))
(defthing us-currency-scales (listof string?) #:document-value '("" "K" "M" "B" "T"))
;; The UK officially uses the short scale since 1974
;; Million is abbreviated "m" instead of "mn" because "mn" stands for minutes
(defthing uk-currency-scales (listof string?) #:document-value '("" "k" "m" "bn" "tr"))
;; European countries use the long scale: million, milliard, billion
(define eu-currency-scales '("" "K" "M" "Md" "B"))
;; The larger the scale suffixes get, the less standardized they are; so we stop at trillion (short)
(defthing eu-currency-scales (listof string?) #:document-value '("" "K" "M" "Md" "B"))
;; The larger the scale suffixes get, the less standardized they are; so we stop at billion (long)
;; US negative amounts are in parenthesis:
(define us-currency-formats '("~$~w.~f~s" "(~$~w.~f~s)" "~$0"))
(defthing us-currency-formats (list/c string? string? string?) #:document-value
'("~$~w.~f~s" "(~$~w.~f~s)" "~$0"))
;; The UK is more reasonable, using a negative sign for negative amounts:
(define uk-currency-formats '("~$~w.~f ~s" "-~$~w.~f ~s" "~$0"))
(defthing uk-currency-formats (list/c string? string? string?) #:document-value
'("~$~w.~f~s" "-~$~w.~f~s" "~$0"))
;; The more common EU format (e.g. France, Germany, Italy, Spain):
(define eu-currency-formats '("~w,~f ~s~$" "-~w,~f ~s~$" "0 ~$"))
(defthing eu-currency-formats (list/c string? string? string?) #:document-value
'("~w,~f ~s~$" "-~w,~f ~s~$" "0 ~$"))
(defparam currency-ticks-scales (listof string?) us-currency-scales)
(defparam currency-ticks-formats (list/c string? string? string?) us-currency-formats)
@ -546,22 +567,19 @@
(apply-formatter formatter format-list
(amount-data sign whole frac unit suffix)))))))
(defproc (currency-ticks-layout) ticks-layout/c
(linear-ticks-layout #:base 10 #:divisors '(1 2 4 5)))
(defproc (currency-ticks [#:kind kind (or/c string? symbol?) 'USD]
(defproc (currency-ticks [#:number number exact-positive-integer? (ticks-default-number)]
[#:kind kind (or/c string? symbol?) 'USD]
[#:scales scales (listof string?) (currency-ticks-scales)]
[#:formats formats (list/c string? string? string?) (currency-ticks-formats)]
) ticks?
(ticks (currency-ticks-layout)
(currency-ticks-format #:kind kind #:scales scales #:formats formats)))
) ticks? #:document-body
(ticks (linear-ticks-layout #:number number #:base 10
#:divisors '(1 2 4 5))
(currency-ticks-format #:kind kind #:scales scales
#:formats formats)))
;; ===================================================================================================
;; Fractions
(defparam fraction-ticks-base (and/c exact-integer? (>=/c 2)) 10)
(defparam fraction-ticks-divisors (listof exact-positive-integer?) '(1 2 3 4 5))
(define (format-fraction x)
(cond [(inexact? x) (format-fraction (inexact->exact x))]
[(x . < . 0) (format "-~a" (format-fraction (- x)))]
@ -580,9 +598,9 @@
(for/list ([t (in-list ts)])
(format-fraction (pre-tick-value t)))))
(defproc (fraction-ticks [#:base base (and/c exact-integer? (>=/c 2)) (fraction-ticks-base)]
[#:divisors divisors (listof exact-positive-integer?)
(fraction-ticks-divisors)]) ticks?
(defproc (fraction-ticks [#:base base (and/c exact-integer? (>=/c 2)) 10]
[#:divisors divisors (listof exact-positive-integer?) '(1 2 3 4 5)]
) ticks? #:document-body
(ticks (linear-ticks #:base base #:divisors divisors)
(fraction-ticks-format)))
@ -590,16 +608,14 @@
;; Tick combinators
(defproc (ticks-mimic [thunk (-> ticks?)]) ticks?
(ticks (λ (x-min x-max max-ticks)
((ticks-layout (thunk)) x-min x-max max-ticks))
(λ (x-min x-max ts)
((ticks-format (thunk)) x-min x-max ts))))
(ticks (λ (x-min x-max) ((ticks-layout (thunk)) x-min x-max))
(λ (x-min x-max ts) ((ticks-format (thunk)) x-min x-max ts))))
(defproc (ticks-scale [t ticks?] [fun invertible-function?]) ticks?
(match-define (invertible-function f g) fun)
(match-define (ticks layout format) t)
(ticks (λ (x-min x-max max-ticks)
(define ts (layout (f x-min) (f x-max) max-ticks))
(ticks (λ (x-min x-max)
(define ts (layout (f x-min) (f x-max)))
(for/list ([t (in-list ts)])
(match-define (pre-tick x major?) t)
(pre-tick (g x) major?)))
@ -611,13 +627,13 @@
(defproc (ticks-add [t ticks?] [xs (listof real?)] [major? boolean? #t]) ticks?
(match-define (ticks layout format) t)
(ticks (λ (x-min x-max max-ticks)
(append (layout x-min x-max max-ticks)
(ticks (λ (x-min x-max)
(append (layout x-min x-max)
(for/list ([x (in-list xs)])
(pre-tick x major?))))
format))
(defproc (linear-scale [m real?] [b real? 0]) invertible-function?
(defproc (linear-scale [m real?] [b real? 0]) invertible-function? #:document-body
(invertible-function (λ (x) (+ (* m x) b))
(λ (y) (/ (- y b) m))))

View File

@ -13,7 +13,7 @@
"plot3d/plot-area.rkt"
(prefix-in new. (only-in "main.rkt"
x-axis y-axis
default-x-ticks default-y-ticks default-z-ticks
plot-x-ticks plot-y-ticks plot-z-ticks
points error-bars vector-field
plot-title plot-x-label plot-y-label plot-z-label
plot-foreground plot-background
@ -75,8 +75,8 @@
[#:lncolor lncolor (list/c byte? byte? byte?) '(255 0 0)]
[#:out-file out-file (or/c path-string? output-port? #f) #f]
) (is-a?/c image-snip%)
(define x-ticks (new.default-x-ticks x-min x-max))
(define y-ticks (new.default-y-ticks y-min y-max))
(define x-ticks ((new.plot-x-ticks) x-min x-max))
(define y-ticks ((new.plot-y-ticks) y-min y-max))
(define bounds-rect (vector (ivl x-min x-max) (ivl y-min y-max)))
(parameterize ([new.plot-title title]
@ -117,9 +117,9 @@
[#:lncolor lncolor (list/c byte? byte? byte?) '(255 0 0)]
[#:out-file out-file (or/c path-string? output-port? #f) #f]
) (is-a?/c image-snip%)
(define x-ticks (new.default-x-ticks x-min x-max))
(define y-ticks (new.default-y-ticks y-min y-max))
(define z-ticks (new.default-z-ticks z-min z-max))
(define x-ticks ((new.plot-x-ticks) x-min x-max))
(define y-ticks ((new.plot-y-ticks) y-min y-max))
(define z-ticks ((new.plot-z-ticks) z-min z-max))
(define bounds-rect (vector (ivl x-min x-max) (ivl y-min y-max) (ivl z-min z-max)))
(parameterize ([new.plot-title title]

View File

@ -9,8 +9,6 @@
;; General plot parameters
plot-x-axis? plot-y-axis? plot-z-axis?
plot-x-far-axis? plot-y-far-axis? plot-z-far-axis?
plot-x-max-ticks plot-y-max-ticks plot-z-max-ticks plot-d-max-ticks plot-r-max-ticks
plot-x-far-max-ticks plot-y-far-max-ticks plot-z-far-max-ticks
plot-width plot-height
plot-foreground plot-foreground-alpha
plot-background plot-background-alpha
@ -68,14 +66,11 @@
;; Functions
pen-gap
animated-samples
default-x-ticks default-y-ticks default-z-ticks default-d-ticks default-r-ticks
default-x-far-ticks default-y-far-ticks default-z-far-ticks
default-contour-colors default-contour-fill-colors
default-isosurface-colors default-isosurface-line-colors)
;; Parameter groups
plot-parameters
plot-axes?
plot-max-ticks
plot-appearance
plot3d-appearance
plot-output

View File

@ -8,18 +8,19 @@
(struct (tick pre-tick) ([value real?] [major? boolean?] [label string?]))
(struct ticks ([layout ticks-layout/c] [format ticks-format/c])))
24h-descending-date-ticks-formats 12h-descending-date-ticks-formats
descending-time-ticks-formats
24h-descending-time-ticks-formats 12h-descending-time-ticks-formats
us-currency-scales uk-currency-scales eu-currency-scales
us-currency-formats uk-currency-formats eu-currency-formats
ticks-layout/c ticks-format/c
(activate-contract-out ticks-mimic ticks-scale ticks-add linear-scale
no-ticks-layout no-ticks
no-ticks-layout no-ticks-format no-ticks
(activate-contract-out ticks-default-number
ticks-mimic ticks-scale ticks-add linear-scale
linear-ticks-layout linear-ticks-format linear-ticks
log-ticks-layout log-ticks-format log-ticks
date-ticks-formats date-ticks-layout date-ticks-format date-ticks
time-ticks-formats time-ticks-layout time-ticks-format time-ticks
bit/byte-ticks-format bit/byte-ticks
currency-ticks-scales currency-ticks-formats
currency-ticks-layout currency-ticks-format currency-ticks
currency-ticks-format currency-ticks
fraction-ticks-format fraction-ticks
collapse-ticks))

View File

@ -12,9 +12,7 @@
(provide (struct-out ivl))
(require "contracted/axis-transform.rkt")
(provide axis-transform-compose axis-transform-append axis-transform-bound
id-transform log-transform cbrt-transform hand-drawn-transform
stretch-transform collapse-transform)
(provide (all-from-out "contracted/axis-transform.rkt"))
(require "contracted/ticks.rkt")
(provide (all-from-out "contracted/ticks.rkt"))

View File

@ -120,7 +120,7 @@
(define r-min (if (and (<= x-min 0 x-max) (<= y-min 0 y-max)) 0 (apply min corner-rs)))
(define r-max (apply max corner-rs))
(define ts (filter (λ (t) (not (zero? (pre-tick-value t))))
(default-r-ticks r-min r-max)))
((plot-r-ticks) r-min r-max)))
;; Draw the tick lines
(for ([t (in-list ts)])
(match-define (tick r major? label) t)

View File

@ -96,14 +96,12 @@
(tick x #t (->plot-label cat)))])
(if far-ticks? (values empty ticks) (values ticks empty))))
(match-let*
([(vector default-x-ticks default-y-ticks)
(maybe-invert default-x-ticks default-y-ticks)]
[(vector default-x-far-ticks default-y-far-ticks)
(maybe-invert default-x-far-ticks default-y-far-ticks)]
[(vector x-ticks y-ticks)
(maybe-invert x-ticks (default-y-ticks y-min y-max))]
[(vector x-far-ticks y-far-ticks)
(maybe-invert x-far-ticks (default-y-far-ticks y-min y-max))])
([(vector plot-x-ticks plot-y-ticks) (maybe-invert (plot-x-ticks)
(plot-y-ticks))]
[(vector plot-x-far-ticks plot-y-far-ticks) (maybe-invert (plot-x-far-ticks)
(plot-y-far-ticks))]
[(vector x-ticks y-ticks) (maybe-invert x-ticks (plot-y-ticks y-min y-max))]
[(vector x-far-ticks y-far-ticks) (maybe-invert x-far-ticks (plot-y-far-ticks y-min y-max))])
(values x-ticks x-far-ticks y-ticks y-far-ticks)))
(defproc (discrete-histogram

View File

@ -69,7 +69,7 @@
(if y-far-ticks? (values empty ts) (values ts empty))))
(values x-ticks x-far-ticks
y-ticks y-far-ticks
(default-z-ticks z-min z-max) (default-z-far-ticks z-min z-max)))
((plot-z-ticks) z-min z-max) ((plot-z-far-ticks) z-min z-max)))
(define (adjust/gap i gap)
(match-define (ivl x1 x2) i)

View File

@ -8,6 +8,10 @@
@section{Convenience Contracts}
@doc-apply[contract/c]{
Identifies @racket[contract?]s and predicates that can be used as contracts.
}
@doc-apply[treeof]{
Identifies trees of values that meet the contract @(racket ct).
Used by @(racket plot) and @(racket plot3d) to construct the contract for a tree of @(racket renderer2d?) or @(racket renderer3d?).
@ -49,6 +53,9 @@ A list containing the symbols that are valid @(racket points) symbols.
@section{Appearance Argument Sequence Contracts}
@doc-apply[maybe-function/c]{
}
@doc-apply[plot-colors/c]{
The contract for @(racket #:colors) arguments, as in @(racket contours).
If the contracted value is a function, it is intended to take a list of values, such as contour values, as input, and return a list of colors.

View File

@ -4,8 +4,4 @@
@title[#:tag "custom"]{Making Custom Plot Renderers}
@defmodule[plot/custom]
Eventually, enough of the underlying PLoT API will be exposed that anyone can create new @tech{renderers}.
However, the underlying API still changes too often.
As soon as it settles, @racketmodname[plot/custom] will export it, and this page will document how to use it.
@declare-exporting[plot/utils]

View File

@ -31,42 +31,6 @@ The quality of JPEG images written by @(racket plot-file) and @(racket plot3d-fi
If @(racket #t), @(racket plot-file) and @(racket plot3d-file) open a dialog when writing PostScript or PDF files. See @(racket post-script-dc%) and @(racket pdf-dc%).
}
@section{Axis Transforms}
@doc-apply[plot-x-transform]
@doc-apply[plot-y-transform]
@doc-apply[plot-z-transform]{
Per-axis, nonlinear transforms. Set these, for example, to plot with log-scale axes. See @(racket log-transform).
}
@doc-apply[id-transform]{
The default transform for all axes.
}
@doc-apply[log-transform]{
A log transform. Use this to generate plots with log-scale axes. Any log-scaled axis must be on a positive interval.
@examples[#:eval plot-eval
(parameterize ([plot-y-transform log-transform])
(plot (function (λ (x) x) 0.01 1)))
(parameterize ([plot-x-transform log-transform])
(plot (function (λ (x) x) -1 1)))]
}
@doc-apply[cbrt-transform]{
A "cube-root" transform. Unlike the log transform, it is defined on the entire real line, making it better for testing the appearance of plots with nonlinearly transformed axes.
}
@doc-apply[hand-drawn-transform]{
An @italic{extremely important} test case, which makes sure that @(plot-name) can use any monotone, invertible function as an axis transform.
The @(racket freq) parameter controls the ``shakiness'' of the transform. At high values, it makes plots look like Peanuts cartoons. For example,
@interaction[#:eval plot-eval (parameterize ([plot-x-transform (hand-drawn-transform 200)]
[plot-y-transform (hand-drawn-transform 200)])
(plot (function sqr -1 1)))]
}
@section{General Appearance}
@doc-apply[plot-foreground]
@ -90,6 +54,9 @@ See @(racket ->pen-color) and @(racket ->brush-color) for details on how PLoT in
@doc-apply[plot-y-label]
@doc-apply[plot-z-label]{The title and axis labels. A @(racket #f) value means the label is not drawn and takes no space. A @(racket "") value effectively means the label is not drawn, but it takes space.
}
@doc-apply[plot-x-far-label]
@doc-apply[plot-y-far-label]
@doc-apply[plot-z-far-label]
@doc-apply[plot-animating?]{
When @(racket #t), certain renderers draw simplified plots to speed up drawing. PLoT sets it to @(racket #t), for example, when a user is clicking and dragging a 3D plot to rotate it.
@ -168,17 +135,33 @@ When @(racket #t), certain renderers draw simplified plots to speed up drawing.
@doc-apply[rectangle3d-line-width]
@doc-apply[discrete-histogram-gap]
@doc-apply[discrete-histogram-skip]
@doc-apply[discrete-histogram-invert?]
@section{Decorations}
These parameters do not control the @italic{typical} appearance of plots. Instead, they control the look of renderers that add specific decorations, such as labeled points.
@doc-apply[x-axis-alpha]
@doc-apply[y-axis-alpha]
@doc-apply[z-axis-alpha]
@doc-apply[x-axis-far?]
@doc-apply[y-axis-far?]
@doc-apply[z-axis-far?]
@doc-apply[x-axis-ticks?]
@doc-apply[y-axis-ticks?]
@doc-apply[z-axis-ticks?]
@doc-apply[x-axis-labels?]
@doc-apply[y-axis-labels?]
@doc-apply[z-axis-labels?]
@doc-apply[polar-axes-number]
@doc-apply[polar-axes-alpha]
@doc-apply[polar-axes-ticks?]
@doc-apply[polar-axes-labels?]
@doc-apply[label-anchor]
@doc-apply[label-angle]
@ -220,6 +203,7 @@ Single isosurfaces (@(racket isosurface3d)) use surface parameters. Nested isosu
@doc-apply[isosurface-levels]
@doc-apply[isosurface-colors]
@doc-apply[isosurface-styles]
@doc-apply[isosurface-line-colors]
@doc-apply[isosurface-line-widths]
@doc-apply[isosurface-line-styles]

View File

@ -31,6 +31,8 @@ If you have code written for PLoT 5.1.3 or earlier, please see @secref["porting"
@include-section["renderer3d.scrbl"]
@include-section["ticks.scrbl"]
@include-section["utils.scrbl"]
@include-section["params.scrbl"]

View File

@ -0,0 +1,468 @@
#lang scribble/manual
@(require "common.rkt" (for-label racket/date db))
@declare-exporting[plot]
@title[#:tag "ticks and transforms"]{Axis Transforms and Ticks}
@section[#:tag "transforms"]{Axis Transforms}
The @italic{x}, @italic{y} and @italic{z} axes for any plot can be independently transformed by parameterizing the plot on different @racket[plot-x-transform], @racket[plot-y-transform] and @racket[plot-z-transform] values.
For example, to plot the @italic{x} axis with a log transform:
@interaction[#:eval plot-eval
(parameterize ([plot-x-transform log-transform])
(plot (function sin 1 100)))]
Most @racket[log-transform]ed plots use different ticks than the default, uniformly spaced ticks, however.
To put log ticks on the @italic{x} axis, set the @racket[plot-x-ticks] parameter:
@interaction[#:eval plot-eval
(parameterize ([plot-x-transform log-transform]
[plot-x-ticks (log-ticks)])
(plot (function sin 1 100)))]
See @secref["ticks"] for more details on parameterizing a plot's axis ticks.
@doc-apply[plot-x-transform]
@doc-apply[plot-y-transform]
@doc-apply[plot-z-transform]{
Independent, per-axis, monotone, nonlinear transforms. PLoT comes with some typical (and some atypical) axis transforms, documented immediately below.
}
@doc-apply[id-transform]{
The identity axis transform, the default transform for all axes.
}
@doc-apply[log-transform]{
A log transform. Use this to generate plots with log-scale axes. Any such axis must have positive bounds.
The beginning of the @secref["ticks and transforms"] section has a working example. An example of exceeding the bounds is
@interaction[#:eval plot-eval
(parameterize ([plot-x-transform log-transform])
(plot (function (λ (x) x) -1 1)))]
See @racket[axis-transform-bound] and @racket[axis-transform-append] for ways to get around an axis transform's bounds limitations.
}
@doc-apply[stretch-transform]{
Returns an axis transform that stretches a finite interval.
The following example uses a @racket[stretch-transform] to draw attention to the interval [-1,1] in an illustration of the limit of @italic{sin(x)/x} as @italic{x} approaches zero (a critical part of proving the derivative of @italic{sin(x)}):
@interaction[#:eval plot-eval
(parameterize ([plot-x-transform (stretch-transform -1 1 20)]
[plot-x-ticks (ticks-add (plot-x-ticks) '(-1 1))])
(plot (list (y-axis -1 #:ticks? #f) (y-axis 1 #:ticks? #f)
(function (λ (x) (/ (sin x) x)) -14 14
#:width 2 #:color 4 #:label "y = sin(x)/x")
(point-label (vector 0 1) "y → 1 as x → 0"
#:anchor 'bottom-right))
#:y-max 1.2))]
}
@doc-apply[collapse-transform]{
Returns an axis transform that collapses a finite interval to its midpoint.
For example, to remove part of the long, boring asymptotic approach of @italic{atan(x)} toward π/2:
@interaction[#:eval plot-eval
(parameterize ([plot-x-transform (collapse-transform 50 150)])
(plot (function atan 10 200 #:label "y = atan(x)")
#:legend-anchor 'center))]
In this case, there were already ticks at the collapsed interval's endpoints.
If there had not been, it would have been necessary to use @racket[ticks-add] to let viewers know precisely the interval that was collapsed.
(See @racket[stretch-transform] for an example.)
}
@doc-apply[cbrt-transform]{
A ``cube-root'' transform, mostly used for testing.
Unlike the log transform, it is defined on the entire real line, making it better for testing the appearance of plots with nonlinearly transformed axes.
}
@doc-apply[hand-drawn-transform]{
An @italic{extremely important} test case, which makes sure that @(plot-name) can use any monotone, invertible function as an axis transform.
The @(racket freq) parameter controls the ``shakiness'' of the transform. At high values, it makes plots look like Peanuts cartoons.
@examples[#:eval plot-eval
(parameterize ([plot-x-transform (hand-drawn-transform 200)]
[plot-y-transform (hand-drawn-transform 200)])
(plot (function sqr -1 1)))
(parameterize ([plot-x-transform (hand-drawn-transform 50)]
[plot-y-transform (hand-drawn-transform 50)]
[plot-z-transform (hand-drawn-transform 50)])
(plot3d (contour-intervals3d (λ (x y) (- (sqr x) (sqr y)))
-1 1 -1 1 #:samples 9)))]
The last example shows that the transform is applied to the primitive shapes that comprise the plot (by recursive subdivision).
}
@doc-apply[axis-transform/c]{
The contract for axis transforms.
The easiest ways to construct novel axis transforms are to use the axis transform combinators @racket[axis-transform-append], @racket[axis-transform-bound] and @racket[axis-transform-compose], or to apply @racket[make-axis-transform] to an @racket[invertible-function].
}
@doc-apply[axis-transform-append]{
Returns an axis transform that transforms values less than @racket[mid] like @racket[t1], and transforms values greater than @racket[mid] like @racket[t2].
(Whether it transforms @racket[mid] like @racket[t1] or @racket[t2] is immaterial, as a transformed @racket[mid] is equal to @racket[mid] either way.)
@examples[#:eval plot-eval
(parameterize ([plot-x-transform (axis-transform-append
(stretch-transform -2 -1 10)
(stretch-transform 1 2 10)
0)])
(plot (function (λ (x) x) -3 3)))]
}
@doc-apply[axis-transform-bound]{
Returns an axis transform that transforms values like @racket[t] does in the interval [@racket[a],@racket[b]], but like the identity transform outside of it.
For example, to bound @racket[log-transform] to an interval in which it is well-defined,
@interaction[#:eval plot-eval
(parameterize ([plot-x-transform (axis-transform-bound
log-transform 0.01 +inf.0)])
(plot (function (λ (x) x) -4 8 #:label "y = x")))]
}
@doc-apply[axis-transform-compose]{
Composes two axis transforms.
For example, to collapse part of a @racket[log-transform]ed axis, try something like
@interaction[#:eval plot-eval
(parameterize ([plot-x-transform (axis-transform-compose
log-transform
(collapse-transform 2 4))])
(plot (function (λ (x) x) 1 5)))]
Argument order matters, but predicting the effects of exchanging arguments can be difficult.
Fortunately, the effects are usually slight.
}
@doc-apply[make-axis-transform]{
Given a monotone @racket[invertible-function], returns an axis transform.
Monotonicity is necessary, but cannot be enforced.
The inverse is used to take samples uniformly along transformed axes (see @racket[nonlinear-seq]).
@examples[#:eval plot-eval
(parameterize ([plot-y-transform (make-axis-transform
(invertible-function sqrt sqr))])
(plot (function (λ (x) x) 0 5)))]
An axis transform created by @racket[make-axis-transform] (or by any of the above combinators) does not transform the endpoints of an axis's bounds, to within floating-point error.
For example,
@interaction[#:eval plot-eval
(match-let ([(invertible-function f g)
(apply-axis-transform log-transform 1 3)])
(values (list (f 1) (f 2) (f 3))
(list (g 1) (g 2.2618595071429146) (g 3))))]
Technically, @racket[fun] does not need to be truly invertible.
Given @racket[fun] = @racket[(invertible-function f g)], it is enough for @racket[f] to be a @hyperlink["http://en.wikipedia.org/wiki/Inverse_function#Left_and_right_inverses"]{left inverse} of @racket[g].
If @racket[f] and @racket[g] had to be strict inverses of each other, there could be no @racket[collapse-transform].
}
@doc-apply[apply-axis-transform]{
Returns an invertible function that transforms axis points within the given axis bounds.
This convenience function is used internally to transform points before rendering, but is provided for completeness.
}
@section[#:tag "ticks"]{Axis Ticks}
Each plot axis has two indepedent sets of ticks: the @italic{near} ticks and the @italic{far} ticks.
@doc-apply[plot-x-ticks]
@doc-apply[plot-x-far-ticks]
@doc-apply[plot-y-ticks]
@doc-apply[plot-y-far-ticks]
@doc-apply[plot-z-ticks]
@doc-apply[plot-z-far-ticks]{
@examples[#:eval plot-eval
(parameterize ([plot-x-label "Near x axis"]
[plot-y-label "Near y axis"]
[plot-z-label "Near z axis"]
[plot-x-ticks (date-ticks)]
[plot-y-ticks (time-ticks)]
[plot-z-ticks (fraction-ticks)]
[plot-x-far-label "Far x axis"]
[plot-y-far-label "Far y axis"]
[plot-z-far-label "Far z axis"]
[plot-x-far-ticks (linear-ticks)]
[plot-y-far-ticks (currency-ticks)]
[plot-z-far-ticks (log-ticks #:base 2)])
(plot3d (lines3d '(#(1 1 1) #(40000000 4 4)) #:style 'transparent)
#:angle 45 #:altitude 50
#:title "Axis Names and Tick Locations"))]
At any @racket[#:angle], the far @italic{x} and @italic{y} ticks are behind the plot, and the far @italic{z} ticks are on the right.
Far ticks are drawn, but not labeled, if they are identical to their corresponding near ticks.
They are always identical by default.
@deftech{Major ticks} are longer than @deftech{minor ticks}. Major tick labels are always drawn unless collapsed with a nearby tick.
Minor tick labels are never drawn.
Renderers produced by @racket[contours] and @racket[contour-intervals] use the value of @racket[plot-z-ticks] to place and label contour lines.
For example, compare plots of the same function renderered using both @racket[contour-intervals] and @racket[contour-intervals3d]:
@interaction[#:eval plot-eval
(parameterize ([plot-z-ticks (currency-ticks)])
(define (saddle x y) (- (sqr x) (sqr y)))
(values
(plot (contour-intervals saddle -1 1 -1 1 #:label "z")
#:legend-anchor 'center)
(plot3d (contour-intervals3d saddle -1 1 -1 1 #:label "z")
#:legend-anchor 'center)))]
}
@defstruct[ticks ([layout ticks-layout/c] [format ticks-format/c])]{
A @racket[ticks] for a near or far axis consists of a @racket[layout] function, which determines the number of ticks and where they will be placed, and a @racket[format] function, which determines the ticks' labels.
}
@doc-apply[ticks-default-number]{
Most tick layout functions (and thus their corresponding @racket[ticks]-constructing functions) have a @racket[#:number] keyword argument with default @racket[(ticks-default-number)]. What the number means depends on the tick layout function. Most use it for the maximum number of major ticks.
It is unlikely to mean the exact number of major ticks.
Without adjusting the number of ticks, layout functions usually cannot find uniformly spaced ticks that will have simple labels after formatting.
For example, the following plot shows the actual number of major ticks for the interval [0,@italic{x}] when the requested number of ticks is 8, as generated by @racket[linear-ticks-layout]:
@interaction[#:eval plot-eval
(plot (function (λ (x)
(count pre-tick-major?
((linear-ticks-layout #:number 8) 0 x)))
0.1 10)
#:x-label "Interval [0,x]" #:y-label "Number of ticks")]
}
@subsection{Linear Ticks}
@doc-apply[linear-ticks-layout]
@doc-apply[linear-ticks-format]
@doc-apply[linear-ticks]{
The layout function, format function, and combined @racket[ticks] for uniformly spaced ticks.
To lay out ticks, @racket[linear-ticks-layout] finds the power of @racket[base] closest to the axis interval size, chooses a simple first tick, and then chooses a skip length using @racket[divisors] that maximizes the number of ticks without exceeding @racket[number].
@margin-note*{For strategic use of non-default arguments, see @racket[bit/byte-ticks], @racket[currency-ticks], and @racket[fraction-ticks].}
The default arguments correspond to the standard 1-2-5-in-base-10 rule used almost everywhere in plot tick layout.
To format ticks, @racket[linear-ticks-format] uses @racket[real->plot-label], and uses @racket[digits-for-range] to determine the maximum number of fractional digits in the decimal expansion.
}
@subsection{Log Ticks}
@doc-apply[log-ticks-layout]
@doc-apply[log-ticks-format]
@doc-apply[log-ticks]{
The layout function, format function, and combined @racket[ticks] for exponentially spaced major ticks.
(The minor ticks between are uniformly spaced.)
Use these ticks for @racket[log-transform]ed axes, because when exponentially spaced tick positions are @racket[log-transform]ed, they become uniformly spaced.
The @racket[#:base] keyword argument is the logarithm base.
See @racket[plot-z-far-ticks] for an example of use.
}
@subsection{Date Ticks}
@doc-apply[date-ticks-layout]
@doc-apply[date-ticks-format]
@doc-apply[date-ticks]{
The layout function, format function, and combined @racket[ticks] for uniformly spaced ticks with date labels.
These axis ticks regard values as being in seconds since @italic{a system-dependent Universal Coordinated Time (UTC) epoch}.
(For example, the Unix and Mac OS X epoch is January 1, 1970 UTC, and the Windows epoch is January 1, 1601 UTC.)
Use @racket[date->seconds] to convert local dates to seconds, or @racket[datetime->real] to convert dates to UTC seconds in a way that accounts for time zone offsets.
Actually, @racket[date-ticks-layout] does not always space ticks @italic{quite} uniformly.
For example, it rounds ticks that are spaced about one month apart or more to the nearest month.
Generally, @racket[date-ticks-layout] tries to place ticks at minute, hour, day, week, month and year boundaries, as well as common multiples such as 90 days or 6 months.
To avoid displaying overlapping labels, @racket[date-ticks-format] chooses date formats from @racket[formats] for which labels will contain no redundant information.
All the format specifiers given in @racketmodname[srfi/19] (which are derived from Unix's @tt{date} command), except those that represent time zones, are allowed in date format strings.
}
@doc-apply[date-ticks-formats]{
The default date formats.
}
@doc-apply[24h-descending-date-ticks-formats]
@doc-apply[12h-descending-date-ticks-formats]
@subsection{Time Ticks}
@doc-apply[time-ticks-layout]
@doc-apply[time-ticks-format]
@doc-apply[time-ticks]{
The layout function, format function, and combined @racket[ticks] for uniformly spaced ticks with time labels.
These axis ticks regard values as being in seconds.
Use @racket[datetime->real] to convert @racket[sql-time] or @racket[plot-time] values to seconds.
Generally, @racket[time-ticks-layout] tries to place ticks at minute, hour and day boundaries, as well as common multiples such as 12 hours or 30 days.
To avoid displaying overlapping labels, @racket[time-ticks-format] chooses a date format from @racket[formats] for which labels will contain no redundant information.
All the time-related format specifiers given in @racketmodname[srfi/19] (which are derived from Unix's @tt{date} command) are allowed in time format strings.
}
@doc-apply[time-ticks-formats]{
The default time formats.
}
@doc-apply[24h-descending-time-ticks-formats]
@doc-apply[12h-descending-time-ticks-formats]
@subsection{Currency Ticks}
@doc-apply[currency-ticks-format]
@doc-apply[currency-ticks]{
The format function and combined @racket[ticks] for uniformly spaced ticks with currency labels.
The @racket[#:kind] keyword argument is either a string containing the currency symbol, or a currency code such as @racket['USD], @racket['GBP] or @racket['EUR].
The @racket[currency-ticks-format] function can map most ISO 4217 currency codes to their corresponding currency symbol.
The @racket[#:scales] keyword argument is a list of suffixes for each 10@superscript{3} scale, such as @racket["K"] (US thousand, or kilo), @racket["bn"] (UK short-scale billion) or @racket["Md"] (EU long-scale milliard). Off-scale amounts are given power-of-ten suffixes such as ``×10@superscript{21}.''
The @racket[#:formats] keyword argument is a list of three format strings, representing the formats of positive, negative, and zero amounts, respectively. The format specifiers are:
@itemlist[@item{@racket["~$"]: replaced by the currency symbol}
@item{@racket["~w"]: replaced by the whole part of the amount}
@item{@racket["~f"]: replaced by the fractional part, with 2 or more decimal digits}
@item{@racket["~s"]: replaced by the scale suffix}
@item{@racket["~~"]: replaced by ``~''}]
Note that the @racket[#:divisors] passed to @racket[linear-ticks-layout] are @racket['(1 2 4 5)]. This allows quarter divisions to be used for tick positions, corresponding to 25/100 denominations such as the US quarter dollar.
}
@doc-apply[currency-ticks-scales]
@doc-apply[currency-ticks-formats]{
The default currency scales and formats.
For example, a PLoT user in France would probably begin programs with
@racketblock[(require plot)
(currency-ticks-scales eu-currency-scales)
(currency-ticks-formats eu-currency-formats)]
and use @racket[(currency-ticks #:kind 'EUR)] for local currency or @racket[(currency-ticks #:kind 'JPY)] for Japanese Yen.
Cultural sensitivity notwithstanding, when writing for a local audience, it is generally considered proper to use local currency scales and formats for foreign currencies.
}
@doc-apply[us-currency-scales]{
Short-scale suffix abbreviations as commonly used in the United States, Canada, and some other English-speaking countries. These stand for ``kilo,'' ``million,'' ``billion,'' and ``trillion.''
}
@doc-apply[uk-currency-scales]{
Short-scale suffix abbreviations as commonly used in the United Kingdom since switching to the short scale in 1974, and as currently recommended by the Daily Telegraph and Times style guides.
}
@doc-apply[eu-currency-scales]{
European Union long-scale suffix abbreviations, which stand for ``kilo,'' ``million,'' ``milliard,'' and ``billion.''
The abbreviations actually used vary with geography, even within countries, but these seem to be common.
Further long-scale suffix abbreviations such as for ``billiard'' are ommitted due to lack of even weak consensus.
}
@doc-apply[us-currency-formats]{
Common currency formats used in the United States.
}
@doc-apply[uk-currency-formats]{
Common currency formats used in the United Kingdom.
Note that it sensibly uses a negative sign to denote negative amounts.
}
@doc-apply[eu-currency-formats]{
A guess at common currency formats for the European Union.
Like scale suffixes, actual formats vary with geography, but currency formats can even vary with audience or tone.
}
@subsection{Other Ticks}
@doc-apply[no-ticks-layout]
@doc-apply[no-ticks-format]
@doc-apply[no-ticks]{
The layout function, format function, and combined @racket[ticks] for no ticks whatsoever.
@examples[#:eval plot-eval
(parameterize ([plot-x-ticks no-ticks]
[plot-y-ticks no-ticks]
[plot-x-label #f]
[plot-y-label #f])
(plot (list (polar-axes) (polar (λ (θ) 1/3)))))]
}
@doc-apply[bit/byte-ticks-format]
@doc-apply[bit/byte-ticks]{
The format function and and combined @racket[ticks] for bit or byte values.
The @racket[#:kind] keyword argument indicates either International System of Units (@racket['SI]) suffixes, as used to communicate hard drive capacities, or Computer Science (@racket['CS]) suffixes, as used to communicate memory capacities.
}
@doc-apply[fraction-ticks-format]
@doc-apply[fraction-ticks]{
The format function and and combined @racket[ticks] for fraction-formatted values.
}
@subsection{Tick Combinators}
@doc-apply[ticks-mimic]{
Returns a @racket[ticks] that mimics the given @racket[ticks] returned by @racket[thunk].
Used in default values for @racket[plot-x-far-ticks], @racket[plot-y-far-ticks] and @racket[plot-z-far-ticks] to ensure that, unless one of these parameters is changed, the far tick labels are not drawn.
}
@doc-apply[ticks-add]{
Returns a new @racket[ticks] that acts like @racket[t], except that it puts additional ticks at positions @racket[xs]. If @racket[major?] is true, the ticks at positions @racket[xs] are all @tech{major ticks}; otherwise, they are minor ticks.
}
@doc-apply[ticks-scale]{
Returns a new @racket[ticks] that acts like @racket[t], but for an axis transformed by @racket[fun].
Unlike with typical @secref["transforms"], @racket[fun] is allowed to transform axis endpoints.
(See @racket[make-axis-transform] for an explanation about transforming endpoints.)
Use @racket[ticks-scale] to plot values at multiple scales simultaneously, with one scale on the near axis and one scale on the far axis.
The following example plots degrees Celsius on the left and degrees Farenheit on the right:
@interaction[#:eval plot-eval
(parameterize
([plot-x-ticks (time-ticks)]
[plot-y-far-ticks (ticks-scale (plot-y-ticks)
(linear-scale 9/5 32))]
[plot-y-label "Temperature (\u00b0C)"]
[plot-y-far-label "Temperature (\u00b0F)"])
(define data
(list #(0 0) #(15 0.6) #(30 9.5) #(45 10.0) #(60 16.6)
#(75 41.6) #(90 42.7) #(105 65.5) #(120 78.9)
#(135 78.9) #(150 131.1) #(165 151.1) #(180 176.2)))
(plot (list
(function (λ (x) (/ (sqr x) 180)) 0 180
#:style 'long-dash #:color 3 #:label "Trend")
(lines data #:color 2 #:width 2)
(points data #:color 1 #:line-width 2 #:label "Measured"))
#:y-min -25 #:x-label "Time"))]
}
@subsection{Tick Data Types and Contracts}
@defstruct[pre-tick ([value real?] [major? boolean?])]{
Represents a tick that has not yet been labeled.
}
@defstruct[(tick pre-tick) ([label string?])]{
Represents a tick with a label.
}
@doc-apply[ticks-layout/c]{
The contract for tick layout functions. Note that a layout function returns @racket[pre-tick]s, or unlabeled ticks.
}
@doc-apply[ticks-format/c]{
The contract for tick format functions. A format function receives axis bounds so it can determine how many decimal digits to display (usually by applying @racket[digits-for-range] to the bounds).
}
@section[#:tag "invertible"]{Invertible Functions}
@defstruct[invertible-function ([f (real? . -> . real?)] [g (real? . -> . real?)])]{
Represents an invertible function. Used for @secref["transforms"] and by @racket[ticks-scale].
The function itself is @racket[f], and its inverse is @racket[g].
Because @racket[real?]s can be inexact, this invariant must be approximate and therefore cannot be enforced.
(For example, @racket[(exp (log 10))] = @racket[10.000000000000002].)
The obligation to maintain it rests on whomever constructs one.
}
@doc-apply[id-function]{
The identity function as an @racket[invertible-function].
}
@doc-apply[invertible-compose]{
Returns the composition of two invertible functions.
}
@doc-apply[invertible-inverse]{
Returns the inverse of an invertible function.
}
@doc-apply[linear-scale]{
Returns a one-dimensional linear scaling function, as an @racket[invertible-function].
This function constructs the most common arguments to @racket[ticks-scale].
}

View File

@ -6,13 +6,7 @@
@defmodule[plot/utils]
@doc-apply[degrees->radians]{
Converts degrees to radians.
}
@doc-apply[radians->degrees]{
Converts radians to degrees.
}
@section{Formatting}
@doc-apply[digits-for-range]{
Given a range, returns the number of decimal places necessary to distinguish numbers in the range. This may return negative numbers for large ranges.
@ -41,37 +35,7 @@ Converts a Racket value to a label. Used by @(racket discrete-histogram) and @(r
Like @(racket real->decimal-string), but removes trailing zeros and a trailing decimal point.
}
@doc-apply[linear-seq]{
Returns a list of evenly spaced real numbers between @(racket start) and @(racket end).
If @(racket start?) is @(racket #t), the list includes @(racket start).
If @(racket end?) is @(racket #t), the list includes @(racket end).
This function is used internally to generate sample points.
@examples[#:eval plot-eval
(linear-seq 0 1 5)
(linear-seq 0 1 5 #:start? #f)
(linear-seq 0 1 5 #:end? #f)
(linear-seq 0 1 5 #:start? #f #:end? #f)]
}
@doc-apply[linear-seq*]{
Like @(racket linear-seq), but accepts a list of reals instead of a start and end.
The @(racket #:start?) and @(racket #:end?) keyword arguments work as in @(racket linear-seq).
This function does not guarantee that each inner value will be in the returned list.
@examples[#:eval plot-eval
(linear-seq* '(0 1 2) 5)
(linear-seq* '(0 1 2) 6)
(linear-seq* '(0 1 0) 5)]
}
@doc-apply[bounds->intervals]{
Given a list of points, returns intervals between each pair.
Use this to construct inputs for @(racket rectangles) and @(racket rectangles3d).
@examples[#:eval plot-eval (bounds->intervals (linear-seq 0 1 5))]
}
@section{Plot Colors and Styles}
@doc-apply[color-seq]{
Interpolates between colors---red, green and blue components separately---using @(racket linear-seq).
@ -169,18 +133,44 @@ Integer brush styles repeat starting at @(racket 7).
(map ->brush-style '(4 5 6))]
}
@defstruct[invertible-function ([f (real? . -> . real?)] [finv (real? . -> . real?)])]{
Represents an invertible function.
@section{Plot-Specific Math}
The function itself is @(racket f), and its inverse is @(racket finv).
Because @(racket real?)s can be inexact, this invariant must be approximate and therefore cannot be enforced.
(For example, @(racket (exp (log 10))) = @(racket 10.000000000000002).)
The obligation to maintain it rests on whomever constructs one.
@subsection{Real Numbers}
An axis transform such as @(racket plot-x-transform) is a function from bounds @(racket start end) to an @(racket invertible-function) for which @(racket (f start)) = @(racket start) and @(racket (f end)) = @(racket end) (approximately), and the same is true of @(racket finv).
The function @(racket f) is used to transform points before drawing; its inverse @(racket finv) is used to generate samples that will be evenly spaced after being transformed by @(racket f).
@doc-apply[regular-real?]{
}
(Technically, because of the way PLoT uses @(racket invertible-function), @(racket f) must only be a left inverse of @(racket finv); there is no requirement that @(racket f) also be a right inverse of @(racket finv).)
@doc-apply[degrees->radians]{
Converts degrees to radians.
}
@doc-apply[radians->degrees]{
Converts radians to degrees.
}
@doc-apply[linear-seq]{
Returns a list of evenly spaced real numbers between @(racket start) and @(racket end).
If @(racket start?) is @(racket #t), the list includes @(racket start).
If @(racket end?) is @(racket #t), the list includes @(racket end).
This function is used internally to generate sample points.
@examples[#:eval plot-eval
(linear-seq 0 1 5)
(linear-seq 0 1 5 #:start? #f)
(linear-seq 0 1 5 #:end? #f)
(linear-seq 0 1 5 #:start? #f #:end? #f)]
}
@doc-apply[linear-seq*]{
Like @(racket linear-seq), but accepts a list of reals instead of a start and end.
The @(racket #:start?) and @(racket #:end?) keyword arguments work as in @(racket linear-seq).
This function does not guarantee that each inner value will be in the returned list.
@examples[#:eval plot-eval
(linear-seq* '(0 1 2) 5)
(linear-seq* '(0 1 2) 6)
(linear-seq* '(0 1 0) 5)]
}
@doc-apply[nonlinear-seq]{
@ -193,6 +183,23 @@ This is used to generate samples for transformed axes.
(plot (area-histogram sqr (nonlinear-seq 1 10 4 log-transform))))]
}
@subsection[#:tag "math.vectors"]{Vectors}
@subsection[#:tag "math.intervals"]{Intervals}
@doc-apply[bounds->intervals]{
Given a list of points, returns intervals between each pair.
Use this to construct inputs for @(racket rectangles) and @(racket rectangles3d).
@examples[#:eval plot-eval (bounds->intervals (linear-seq 0 1 5))]
}
@subsection[#:tag "math.rectangles"]{Rectangles}
@section{Dates and Times}
@section{Sampling}
@defstruct[mapped-function ([f (any/c . -> . any/c)] [fmap ((listof any/c) . -> . (listof any/c))])]{
Represents a function that maps over lists differently than @(racket (map f xs)).
@ -201,6 +208,8 @@ With some functions, mapping over a list can be done much more quickly if done s
Renderer-producing functions that accept a @(racket (real? . -> . real?)) also accept a @(racket mapped-function), and use its @(racket fmap) to sample more efficiently.
}
@section{Denity Estimation}
@doc-apply[kde]{
Given samples and a kernel bandwidth, returns a @(racket mapped-function) representing a kernel density estimate, and bounds, outside of which the density estimate is zero. Used by @(racket density).
}

View File

@ -18,11 +18,10 @@
(time (plot (function values 0 1000)))
(parameterize ([plot-x-ticks (log-ticks #:base 4)]
[plot-x-transform log-transform]
[plot-y-max-ticks 10]
[plot-y-ticks (linear-ticks)]
[plot-y-transform log-transform])
(parameterize ([plot-x-transform log-transform]
[plot-x-ticks (log-ticks #:base 4)]
[plot-y-transform log-transform]
[plot-y-ticks (linear-ticks #:number 10)])
(plot (function values 1 243)))
(parameterize ([plot-background "black"]

View File

@ -4,10 +4,10 @@
(plot-font-family 'swiss)
(plot (function (λ (x) (count pre-tick-major? ((linear-ticks) 0 x 8)))
(plot (function (λ (x) (count pre-tick-major? ((linear-ticks #:number 8) 0 x)))
0.1 10))
(plot (function (λ (x) (count pre-tick-major? ((linear-ticks) 0 x 40)))
(plot (function (λ (x) (count pre-tick-major? ((linear-ticks #:number 40) 0 x)))
1 100))
(parameterize ([plot-x-ticks (linear-ticks #:base 2 #:divisors '(1 2))]
@ -20,8 +20,7 @@
[plot-y-ticks (fraction-ticks)])
(plot (function (λ (x) (+ 1 (cos x))) 0.0001 12)))
(parameterize ([plot-x-ticks (date-ticks)]
[plot-x-max-ticks 3]
(parameterize ([plot-x-ticks (date-ticks #:number 3)]
[plot-y-ticks (currency-ticks)])
(plot (function values -1 1)))
@ -40,7 +39,7 @@
#:x-label "Euros"
#:y-label "Dollars"))
(parameterize ([plot-x-ticks (no-ticks)])
(parameterize ([plot-x-ticks no-ticks])
(plot (function sin -1 4)))
(parameterize ([plot-x-transform log-transform]
@ -90,7 +89,8 @@
(parameterize ([plot-y-ticks (ticks-scale (log-ticks) exp-scale)])
(plot (function values -10 10)))
(parameterize ([plot-y-ticks (ticks-add (no-ticks) '(1/3 2/3))])
(parameterize ([plot-y-ticks (ticks-add (ticks no-ticks-layout (linear-ticks-format))
'(1/3 2/3))])
(plot (function sin -4 4)))
(plot (list (function sin -4 4)
@ -98,7 +98,7 @@
(x-ticks (list (tick 1.5 #t "3/2") (tick 3 #t "Three")))
(y-ticks (list (tick 1/4 #t "1/4") (tick -1/4 #f "")))))
(parameterize ([plot-z-max-ticks 5])
(parameterize ([plot-z-ticks (linear-ticks #:number 5)])
(plot3d (list (surface3d (λ (x y) (* 2 (+ (sin x) (cos y)))) -4 4 -4 4 #:alpha 1/2)
(x-ticks (list (tick 1.5 #t "3/2") (tick 3 #t "Three")))
(y-ticks (list (tick 1/3 #t "1/3") (tick -1/3 #f "1/3")))
@ -127,13 +127,12 @@
(plot (function sin (- pi) pi)))
(parameterize ([plot-x-far-label "x far axis"]
[plot-x-max-ticks 10]
[plot-x-ticks (linear-ticks #:number 10)]
[plot-y-far-label "y far axis"]
[plot-y-far-ticks (date-ticks)]
[plot-z-label "z axis"]
[plot-z-far-label "z far axis"]
[plot-z-far-ticks (currency-ticks)]
[plot-z-far-max-ticks 5])
[plot-z-far-ticks (currency-ticks #:number 5)])
(plot3d (surface3d (λ (x y) (+ (sin x) (cos y))) -2 2 -2 2 #:alpha 1/2)
#:angle 60 #:altitude 35))