Reworked number formatting for plot labels

This commit is contained in:
Neil Toronto 2011-10-07 17:28:56 -06:00
parent 9aa93ab5ae
commit 644abe30fe
11 changed files with 182 additions and 75 deletions

View File

@ -22,9 +22,9 @@
'left 'center 'right
'bottom-left 'bottom 'bottom-right))
(defcontract rgb/c (list/c real? real? real?))
(defcontract color/c (or/c rgb/c string? symbol? (is-a?/c color%)))
(defcontract color/c (or/c (list/c real? real? real?)
string? symbol?
(is-a?/c color%)))
(defcontract plot-color/c (or/c exact-integer? color/c))
@ -61,14 +61,17 @@
(defcontract point-sym/c (or/c char? string? integer? (apply one-of/c known-point-symbols)))
(defcontract plot-color-function/c ((listof real?) . -> . (listof plot-color/c)))
(defcontract pen-width-function/c ((listof real?) . -> . (listof (real>=/c 0))))
(defcontract plot-pen-style-function/c ((listof real?) . -> . (listof plot-pen-style/c)))
(defcontract plot-brush-style-function/c ((listof real?) . -> . (listof plot-brush-style/c)))
(defcontract alpha-function/c ((listof real?) . -> . (listof (real-in 0 1))))
(defcontract plot-colors/c (or/c (listof plot-color/c)
((listof real?) . -> . (listof plot-color/c))))
(defcontract plot-colors/c (or/c (listof plot-color/c) plot-color-function/c))
(defcontract pen-widths/c (or/c (listof (real>=/c 0)) pen-width-function/c))
(defcontract plot-pen-styles/c (or/c (listof plot-pen-style/c) plot-pen-style-function/c))
(defcontract plot-brush-styles/c (or/c (listof plot-brush-style/c) plot-brush-style-function/c))
(defcontract alphas/c (or/c (listof (real-in 0 1)) alpha-function/c))
(defcontract pen-widths/c (or/c (listof (real>=/c 0))
((listof real?) . -> . (listof (real>=/c 0)))))
(defcontract plot-pen-styles/c (or/c (listof plot-pen-style/c)
((listof real?) . -> . (listof plot-pen-style/c))))
(defcontract plot-brush-styles/c (or/c (listof plot-brush-style/c)
((listof real?) . -> . (listof plot-brush-style/c))))
(defcontract alphas/c (or/c (listof (real-in 0 1))
((listof real?) . -> . (listof (real-in 0 1)))))

View File

@ -57,7 +57,7 @@
(define (color%? c) (is-a? c color%))
(defproc (->color [c color/c]) rgb/c
(defproc (->color [c color/c]) (list/c real? real? real?)
(match c
[(? color%?) (list (send c red) (send c green) (send c blue))]
[(? string?) (define color (send the-color-database find-color c))
@ -81,7 +81,7 @@
(160 32 240) ; magenta
(160 160 160))) ; gray
(defproc (->pen-color [c plot-color/c]) rgb/c
(defproc (->pen-color [c plot-color/c]) (list/c real? real? real?)
(cond [(integer? c) (vector-ref pen-colors (remainder (abs c) 8))]
[else (->color c)]))
@ -95,7 +95,7 @@
(240 224 255) ; magenta
(212 212 212))) ; gray
(defproc (->brush-color [c plot-color/c]) rgb/c
(defproc (->brush-color [c plot-color/c]) (list/c real? real? real?)
(cond [(integer? c) (vector-ref brush-colors (remainder (abs c) 8))]
[else (->color c)]))
@ -126,7 +126,7 @@
(defproc (color-seq [c1 color/c] [c2 color/c] [num (integer>=/c 0)]
[#:start? start? boolean? #t]
[#:end? end? boolean? #t]) (listof rgb/c)
[#:end? end? boolean? #t]) (listof (list/c real? real? real?))
(match-define (list r1 g1 b1) (->color c1))
(match-define (list r2 g2 b2) (->color c2))
(define rs (linear-seq r1 r2 num #:start? start? #:end? end?))
@ -136,7 +136,7 @@
(defproc (color-seq* [colors (listof color/c)] [num (integer>=/c 0)]
[#:start? start? boolean? #t]
[#:end? end? boolean? #t]) (listof rgb/c)
[#:end? end? boolean? #t]) (listof (list/c real? real? real?))
(when (empty? colors) (raise-type-error 'color-seq* "nonempty (listof plot-color/c)" colors))
(match-define (list (list rs gs bs) ...) (map ->color colors))
(let ([rs (linear-seq* rs num #:start? start? #:end? end?)]

View File

@ -2,34 +2,118 @@
;; Functions to format numbers, and data structures containing numbers.
(require racket/string racket/list racket/pretty racket/contract
(require racket/string racket/list racket/pretty racket/contract racket/match
"math.rkt"
"contract.rkt" "contract-doc.rkt")
(provide (all-defined-out))
(provide digits-for-range real->plot-label ->plot-label real->string/trunc)
;; Like real->decimal-string, but removes trailing zeros
(defproc (real->string/trunc [x real?] [e exact-nonnegative-integer?]) string?
(define str (real->decimal-string x e))
(let loop ([x (string-length str)])
(cond [(zero? x) "0"]
[(char=? #\0 (string-ref str (sub1 x))) (loop (sub1 x))]
[(char=? #\. (string-ref str (sub1 x))) (substring str 0 (sub1 x))]
[else (substring str 0 x)])))
(define (remove-trailing-zeros str)
(let loop ([i (string-length str)])
(cond [(zero? i) "0"]
[(char=? #\0 (string-ref str (sub1 i))) (loop (sub1 i))]
[(char=? #\. (string-ref str (sub1 i))) (substring str 0 (sub1 i))]
[else (substring str 0 i)])))
;; Returns the number of digits needed to distinguish numbers [x-min..x-max]
;; Returns the number of fractional digits needed to distinguish numbers [x-min..x-max]
(defproc (digits-for-range [x-min real?] [x-max real?]
[extra-digits exact-nonnegative-integer? 3]) exact-nonnegative-integer?
[extra-digits exact-integer? 3]) exact-integer?
(define range (abs (- x-max x-min)))
(+ extra-digits (if (zero? range) 0 (max 0 (- (floor-log10 range))))))
(+ extra-digits (if (zero? range) 0 (- (floor-log10 range)))))
(define (int-str->e-str str)
(define n (string-length str))
(cond [(or (= 0 n) (string=? str "0")) "0"]
[else
(define fst (substring str 0 1))
(define rst (substring str 1 n))
(format "~ae~a" (remove-trailing-zeros (format "~a.~a" fst rst)) (sub1 n))]))
(begin
(require rackunit)
(check-equal? (int-str->e-str "") "0")
(check-equal? (int-str->e-str "0") "0")
(check-equal? (int-str->e-str "10") "1e1"))
(define (frac-str->e-str str)
(define n (string-length str))
(let loop ([i 0])
(cond [(= i n) "0"]
[(char=? #\0 (string-ref str i)) (loop (add1 i))]
[else
(define fst (substring str i (add1 i)))
(define rst (substring str (add1 i) n))
(cond [(= 0 (string-length rst)) (format "~ae~a" fst (- (add1 i)))]
[else (format "~a.~ae~a" fst rst (- (add1 i)))])])))
(begin
(require rackunit)
(check-equal? (frac-str->e-str "") "0")
(check-equal? (frac-str->e-str "0") "0")
(check-equal? (frac-str->e-str "00") "0")
(check-equal? (frac-str->e-str "1") "1e-1")
(check-equal? (frac-str->e-str "01") "1e-2"))
(define (zero-string n)
(list->string (build-list n (λ _ #\0))))
(defproc (real->plot-label [x real?] [digits exact-integer?]) any
(cond
[(zero? x) "0"]
[else
(define front-sign (if (x . < . 0) "-" ""))
(define mid-sign (if (x . < . 0) "-" "+"))
(let* ([x (abs (inexact->exact x))])
;; Round away any extra digits
(define round-fac (expt 10 digits))
(define y (/ (round (* x round-fac)) round-fac))
;; Parse the output of real->decimal-string
(define-values (int-str frac-str)
(match-let ([(list _ int-str frac-str)
(regexp-match #rx"(.*)\\.(.*)" (real->decimal-string y (max 0 digits)))])
(values int-str (remove-trailing-zeros frac-str))))
;; Get scientific notation for the integer and fractional parts
(define int-e-str (int-str->e-str int-str))
(define frac-e-str (frac-str->e-str frac-str))
;(printf "int-str = ~v, frac-str = ~v~n" int-str frac-str)
;(printf "int-e-str = ~v, frac-e-str = ~v~n" int-e-str frac-e-str)
(define int-zero? (string=? int-str "0"))
(define frac-zero? (string=? frac-str "0"))
(define int-e-zero? (string=? int-e-str "0"))
(define frac-e-zero? (string=? frac-e-str "0"))
;; Build a list of possible output strings
(define strs
(list (cond [(and int-zero? frac-zero?) "0"]
[int-zero? (format "~a.~a" front-sign frac-str)]
[frac-zero? (format "~a~a" front-sign int-str)]
[else (format "~a~a.~a" front-sign int-str frac-str)])
(cond [(and int-e-zero? frac-zero?) "0"]
[int-e-zero? (format "~a.~a" front-sign frac-str)]
[frac-zero? (format "~a~a" front-sign int-e-str)]
[else (format "~a(~a)~a.~a" front-sign int-e-str mid-sign frac-str)])
(cond [(and int-zero? frac-e-zero?) "0"]
[int-zero? (format "~a~a" front-sign frac-e-str)]
[frac-e-zero? (format "~a~a" front-sign int-str)]
[else (format "~a~a~a(~a)" front-sign int-str mid-sign frac-e-str)])
(cond [(and int-e-zero? frac-e-zero?) "0"]
[int-e-zero? (format "~a~a" front-sign frac-e-str)]
[frac-e-zero? (format "~a~a" front-sign int-e-str)]
[else
(format "~a(~a)~a(~a)" front-sign int-e-str mid-sign frac-e-str)])))
;; Return the shortest possible output string
(argmin string-length strs))]))
(defproc (->plot-label [a any/c] [digits exact-nonnegative-integer? 7]) string?
(let loop ([a a])
(cond [(string? a) a]
[(symbol? a) (symbol->string a)]
[(real? a) (real->string/trunc a digits)]
[(list? a) (string-append "(" (string-join (map loop a) " ") ")")]
[(cons? a) (string-append "(" (loop (car a)) " . " (loop (cdr a)) ")")]
[(boolean? a) (if a "true" "false")]
[(char? a) (list->string (list a))]
(cond [(string? a) a]
[(symbol? a) (symbol->string a)]
[(real? a) (real->plot-label a digits)]
[(list? a) (string-append "(" (string-join (map loop a) " ") ")")]
[(cons? a) (string-append "(" (loop (car a)) " . " (loop (cdr a)) ")")]
[(boolean? a) (if a "true" "false")]
[(char? a) (list->string (list a))]
[else (pretty-format a)])))
;; Like real->decimal-string, but removes trailing zeros
(defproc (real->string/trunc [x real?] [e exact-integer?]) string?
(remove-trailing-zeros (real->decimal-string x (max 0 e))))

View File

@ -29,7 +29,7 @@
[color (in-cycle (maybe-apply/list colors zs))]
[width (in-cycle (maybe-apply/list widths zs))]
[style (in-cycle (maybe-apply/list styles zs))])
(define entry-label (real->string/trunc z digits))
(define entry-label (real->plot-label z digits))
(assoc-cons hash (list color width style) entry-label)))
(reverse
@ -62,7 +62,7 @@
[line-color (in-cycle (maybe-apply/list line-colors zs))]
[line-width (in-cycle (maybe-apply/list line-widths zs))]
[line-style (in-cycle (maybe-apply/list line-styles zs))])
(define entry-label (real->string/trunc z digits))
(define entry-label (real->plot-label z digits))
(assoc-cons hash (list fill-color fill-style line-color line-width line-style) entry-label)))
(reverse
@ -113,7 +113,7 @@
[line2-width (in-cycle (maybe-apply/list line2-widths zs))]
[line2-style (in-cycle (maybe-apply/list line2-styles zs))])
(define entry-label
(format "[~a,~a]" (real->string/trunc za digits) (real->string/trunc zb digits)))
(format "[~a,~a]" (real->plot-label za digits) (real->plot-label zb digits)))
(assoc-cons hash
(list fill-color fill-style line-color line-width line-style
line1-color line1-width line1-style

View File

@ -37,7 +37,7 @@
(define num (+ 1 (round (/ (- stop start) step))))
(define ps (linear-seq start stop num))
(define digits (digits-for-range x-min x-max))
(define labels (map (λ (p) (real->string/trunc p digits)) ps))
(define labels (map (λ (p) (real->plot-label p digits)) ps))
(define majors (tick-ps->majors ps major-skip))
(map tick ps labels majors)))

View File

@ -155,12 +155,12 @@
(define (format-x-coordinate x area)
(define x-min (send area get-x-min))
(define x-max (send area get-x-max))
(format "~a" (real->string/trunc x (digits-for-range x-min x-max))))
(format "~a" (real->plot-label x (digits-for-range x-min x-max))))
(define (format-y-coordinate y area)
(define y-min (send area get-y-min))
(define y-max (send area get-y-max))
(format "~a" (real->string/trunc y (digits-for-range y-min y-max))))
(format "~a" (real->plot-label y (digits-for-range y-min y-max))))
(define (format-coordinate v area)
(match-define (vector x y) v)

View File

@ -25,9 +25,10 @@
(define plot-eval
(let ([eval (make-base-eval)])
(eval #'(require racket/math racket/match racket/list racket/draw racket/class
(rename-in (except-in plot plot plot3d)
[plot-bitmap plot]
[plot3d-bitmap plot3d])
plot/utils))
(eval '(begin
(require racket/math racket/match racket/list racket/draw racket/class
(rename-in (except-in plot plot plot3d)
[plot-bitmap plot]
[plot3d-bitmap plot3d])
plot/utils)))
eval))

View File

@ -6,16 +6,15 @@
@title[#:tag "contracts"]{Plot Contracts}
@section[#:tag "contracts.convenience"]{Conveniences}
@section{Convenience Contracts}
@doc-apply[real>=/c]
@doc-apply[integer>=/c]
@doc-apply[treeof]
@section[#:tag "contracts.drawing"]{Drawing Parameters}
@section{Appearance Argument Contracts}
@doc-apply[anchor/c]
@doc-apply[rgb/c]
@doc-apply[color/c]
@doc-apply[plot-color/c]
@doc-apply[pen-style/c]
@ -28,21 +27,19 @@
@defthing[known-point-symbols (listof symbol?)]{
A list containing the symbols that are valid @(racket points) labels.
@interaction[#:eval plot-eval known-point-symbols]
@interaction[#:eval plot-eval
(require (only-in srfi/13 string-pad-right))
(for ([sym (in-list known-point-symbols)]
[n (in-cycle (in-range 3))])
(display (string-pad-right (format "~v" sym) 22))
(when (= n 2) (newline)))
(length known-point-symbols)]
}
@section[#:tag "contracts.sequence"]{Color, Width and Style Sequences}
@section{Appearance Argument Sequence Contracts}
@doc-apply[plot-colors/c]
@doc-apply[plot-pen-styles/c]
@doc-apply[pen-widths/c]
@doc-apply[plot-brush-styles/c]
@doc-apply[alphas/c]
@section[#:tag "contracts.function"]{Color, Width and Style Functions}
@doc-apply[plot-color-function/c]
@doc-apply[plot-pen-style-function/c]
@doc-apply[pen-width-function/c]
@doc-apply[plot-brush-style-function/c]
@doc-apply[alpha-function/c]

View File

@ -14,19 +14,33 @@ Converts degrees to radians.
Converts radians to degrees.
}
@doc-apply[real->string/trunc]{
Like @(racket real->decimal-string), but removes trailing zeros and a trailing decimal point.
Used to format numbers for plots.
@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.
@examples[#:eval plot-eval
(digits-for-range 0.01 0.02)
(digits-for-range 0 100000)]
}
@doc-apply[digits-for-range]{
Given a range, returns the number of decimal places necessary to distinguish numbers in the range.
@doc-apply[real->plot-label]{
Converts a real number to a plot label. Used to format axis tick labels, @(racket point-label)s, and numbers in legend entries.
@examples[#:eval plot-eval
(let ([d (digits-for-range 0.01 0.03)])
(real->plot-label 0.02555555 d))
(real->plot-label 2352343 -2)
(real->plot-label 1000000000. 4)
(real->plot-label 1000000000.1234 4)]
}
@doc-apply[->plot-label]{
Converts a Racket value to a label. Used by @(racket discrete-histogram) and @(racket discrete-histogram3d).
}
@doc-apply[real->string/trunc]{
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).
@ -151,7 +165,8 @@ Integer brush styles repeat starting at @(racket 7).
@examples[#:eval plot-eval
(eq? (->brush-style 0) (->brush-style 7))
(map ->brush-style '(0 1 2 3 4 5 6))]
(map ->brush-style '(0 1 2 3))
(map ->brush-style '(4 5 6))]
}
@defstruct[invertible-function ([f (real? . -> . real?)] [finv (real? . -> . real?)])]{

View File

@ -88,9 +88,13 @@
;; an exact rational function and a floating-point function
;; the plot of the exact rational function's graph should be smooth
(time
(plot (list (function (λ (x) (+ x 1)) #:label "Exact")
(function (λ (x) (+ x 1.0)) #:color 2 #:label "Inexact"))
(plot (list (function (λ (x) x) #:label "Exact")
(function (λ (x) (exact->inexact x)) #:color 2 #:label "Inexact"))
#:x-min #e100000000000000.0 #:x-max #e100000000000000.1
#:width 450))
(time
(plot (function cos 0 0.0000001)
#:width 500))
(time
@ -248,19 +252,21 @@
(time (plot (list (tick-grid)
(contour-intervals f1 -5 2 -5 2
#:levels 5
#:contour-styles '(transparent)
#:label "")
(contours f1 -2 5 -2 5 #:label ""))
(contours f1 -2 5 -2 5 #:levels 5 #:label ""))
#:x-min -5 #:x-max 5 #:y-min -5 #:y-max 5
#:legend-anchor 'center))
(time (plot (list (tick-grid)
(contour-intervals f1 -5 2 -5 2
#:levels '(0.25 0.5 0.75 1.0 1.25 1.5 1.75)
#:colors default-contour-colors
#:styles '(0 1 2 3 4 5 6)
#:contour-styles '(transparent)
#:label "z")
(contours f1 -2 5 -2 5))
(contours f1 -2 5 -2 5 #:levels '(0.25 0.5 0.75 1.0 1.25 1.5 1.75)))
#:x-min -5 #:x-max 5 #:y-min -5 #:y-max 5
#:legend-anchor 'top-left))

View File

@ -7,9 +7,10 @@
radians->degrees)
(require "common/format.rkt")
(provide real->string/trunc
digits-for-range
->plot-label)
(provide digits-for-range
real->plot-label
->plot-label
real->string/trunc)
(require "common/draw.rkt")
(provide color-seq color-seq*