Skip to content

Commit 18d8eca

Browse files
authored
Allow pictures to be used as title and axis labels in plots (#65)
Updated the 2D and 3D plot procedures to accept a picture (from the pict library) or a string as the label for the `#:title` and`#:{x,y,z}-label`` keywords, as well as the corresponing parameters, `plot-title` and `plot-{x,y,z}-label`. The original request was to allow LaTeX notation for the labels, this changeset will enable doing that (and more) by installing the additional `latex-pict` package and using the pictures produced by that package as labels. The folowing caveats apply for the implementation: * the label position for the Y axis **is not** rotated when it is a picture, only when it is a string. This was done to allow full flexibility to users who chose to use pictures -- they can easily rotate the picture themselves. If the picture would be rotated by the plot package, the user might have to supply a rotated picture to have a horizontal picture for the y label, and this would result in a double rotation -- depending on the type of graphics used for the picture, this might create undesirable artifacts. * the font used for axis labels and title determines the gap between the label and the plot area and axis themselves. A bug was fixed, where passing incorect parameters to plot procedure could cause Racket to crash, an explanation for the fix is put as a comment in the "plot-gui-lib/plot/private/gui/plot2d.rkt" file. See also: * #58 * racket/racket#3321 * #64
1 parent 05a3718 commit 18d8eca

File tree

20 files changed

+368
-201
lines changed

20 files changed

+368
-201
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ jobs:
55
name: "Build on Racket '${{ matrix.racket-version }}' (${{ matrix.racket-variant }})"
66
runs-on: ubuntu-latest
77
strategy:
8+
# Let all jobs run to completion -- this will allow us to determine if
9+
# the failure is specific to a Racket version.
10+
fail-fast: false
811
matrix:
912
racket-version: ["7.8", "current"]
1013
racket-variant: ["regular", "CS"]

plot-doc/plot/scribblings/plotting.scrbl

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ Each 3D plotting procedure behaves the same way as its corresponding 2D procedur
2525
[#:y-min y-min (or/c rational? #f) #f] [#:y-max y-max (or/c rational? #f) #f]
2626
[#:width width exact-positive-integer? (plot-width)]
2727
[#:height height exact-positive-integer? (plot-height)]
28-
[#:title title (or/c string? #f) (plot-title)]
29-
[#:x-label x-label (or/c string? #f) (plot-x-label)]
30-
[#:y-label y-label (or/c string? #f) (plot-y-label)]
28+
[#:title title (or/c string? pict? #f) (plot-title)]
29+
[#:x-label x-label (or/c string? pict? #f) (plot-x-label)]
30+
[#:y-label y-label (or/c string? pict? #f) (plot-y-label)]
3131
[#:legend-anchor legend-anchor anchor/c (plot-legend-anchor)]
3232
[#:out-file out-file (or/c path-string? output-port? #f) #f]
3333
[#:out-kind out-kind plot-file-format/c 'auto]
@@ -60,6 +60,8 @@ Here, the renderer draws in [-1,1] × [-1,1], but the plot area is [-1.5,1.5] ×
6060
@bold{Deprecated keywords.} The @(racket #:fgcolor) and @(racket #:bgcolor) keyword arguments are currently supported for backward compatibility, but may not be in the future.
6161
Please set the @(racket plot-foreground) and @(racket plot-background) parameters instead of using these keyword arguments.
6262
The @(racket #:lncolor) keyword argument is also accepted for backward compatibility but deprecated. It does nothing.
63+
64+
@history[#:changed "7.9" "Added support for pictures for #:title, #:x-label and #:y-label"]
6365
}
6466

6567
@defproc[(plot3d [renderer-tree (treeof (or/c renderer3d? nonrenderer?))]
@@ -70,10 +72,10 @@ The @(racket #:lncolor) keyword argument is also accepted for backward compatibi
7072
[#:height height exact-positive-integer? (plot-height)]
7173
[#:angle angle real? (plot3d-angle)]
7274
[#:altitude altitude real? (plot3d-altitude)]
73-
[#:title title (or/c string? #f) (plot-title)]
74-
[#:x-label x-label (or/c string? #f) (plot-x-label)]
75-
[#:y-label y-label (or/c string? #f) (plot-y-label)]
76-
[#:z-label z-label (or/c string? #f) (plot-z-label)]
75+
[#:title title (or/c string? pict? #f) (plot-title)]
76+
[#:x-label x-label (or/c string? pict? #f) (plot-x-label)]
77+
[#:y-label y-label (or/c string? pict? #f) (plot-y-label)]
78+
[#:z-label z-label (or/c string? pict? #f) (plot-z-label)]
7779
[#:legend-anchor legend-anchor anchor/c (plot-legend-anchor)]
7880
[#:out-file out-file (or/c path-string? output-port? #f) #f]
7981
[#:out-kind out-kind plot-file-format/c 'auto]
@@ -91,6 +93,8 @@ Please set the @(racket plot-foreground) and @(racket plot-background) parameter
9193
The @(racket #:lncolor) keyword argument is also accepted for backward compatibility but deprecated. It does nothing.
9294

9395
The @(racket #:az) and @(racket #:alt) keyword arguments are backward-compatible, deprecated aliases for @(racket #:angle) and @(racket #:altitude), respectively.
96+
97+
@history[#:changed "7.9" "Added support for pictures for #:title, #:x-label, #:y-label and #:z-label"]
9498
}
9599

96100
@defproc[(plot-snip [<plot-argument> <plot-argument-contract>] ...)
Lines changed: 135 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,38 @@
1-
#lang typed/racket/base
1+
#lang typed/racket/base #:no-optimize
2+
3+
;;;; About `#:no-optimize` and `unsafe-provide`
4+
5+
;; This file provides the toplevel plot functions for 2D plotting and there
6+
;; are two peculiarities in this file: the use of `#:no-optimize` and using
7+
;; `unsafe-provide` to export the symbols.
8+
;;
9+
;; The plot library is written in Typed Racket (TR), but needs to be usable
10+
;; from Racket. TR will attach contracts to all functions it exports (as well
11+
;; as values those functions return) to ensure the values for the parameters
12+
;; are correct when these functions are called from Racket. Unfortunately,
13+
;; when returning the snips for the plots, the attached contracts to the snip
14+
;; interface will slow down interactive programs significantly. To avoid this
15+
;; problem, the plot functions are exported using `unsafe-provide`, which will
16+
;; tell TR to not create contracts for the function parameters and return
17+
;; values.
18+
;;
19+
;; Using `unsafe-provide` leaves the functions vulnerable to bad parameter
20+
;; passing: TR will still assume the parameters have valid values, and will
21+
;; not perform any other checks, which can result in a crash (segmentation
22+
;; fault). To mitigate this problem, there is code inside each plot function
23+
;; to verify that the parameters are correct. Unfortunately, TR will also
24+
;; notice that those checks are redundant (since it knows the types for the
25+
;; parameters) and will remove this parameter verification code during the
26+
;; optimization phase. To prevent this removal, optimizations are disabled in
27+
;; this file using the #:no-optimize line above.
28+
;;
29+
;; The #:no-optimize only applies to this file, which is the "entry point" of
30+
;; the plot package, so the rest of the plot package remains optimized.
231

332
(require (only-in typed/mred/mred Snip% Frame%)
433
(only-in racket/gui/base get-display-backing-scale)
534
typed/racket/draw typed/racket/class racket/match
35+
(only-in typed/pict pict pict?)
636
plot/utils
737
plot/private/common/parameter-group
838
plot/private/common/draw
@@ -33,9 +63,9 @@
3363
#:y-min (U Real #f) #:y-max (U Real #f)
3464
#:width Positive-Integer
3565
#:height Positive-Integer
36-
#:title (U String #f)
37-
#:x-label (U String #f)
38-
#:y-label (U String #f)
66+
#:title (U String pict #f)
67+
#:x-label (U String pict #f)
68+
#:y-label (U String pict #f)
3969
#:legend-anchor Anchor]
4070
(Instance Snip%)))
4171
(define (plot-snip renderer-tree
@@ -49,43 +79,50 @@
4979
#:legend-anchor [legend-anchor (plot-legend-anchor)])
5080
(define fail/kw (make-raise-keyword-error 'plot-snip))
5181
(cond
82+
;; check arguments, see note at the top of this file
5283
[(and x-min (not (rational? x-min))) (fail/kw "#f or rational" '#:x-min x-min)]
5384
[(and x-max (not (rational? x-max))) (fail/kw "#f or rational" '#:x-max x-max)]
5485
[(and y-min (not (rational? y-min))) (fail/kw "#f or rational" '#:y-min y-min)]
5586
[(and y-max (not (rational? y-max))) (fail/kw "#f or rational" '#:y-max y-max)]
56-
[else
57-
(parameterize ([plot-title title]
58-
[plot-x-label x-label]
59-
[plot-y-label y-label]
60-
[plot-legend-anchor legend-anchor])
61-
(define saved-plot-parameters (plot-parameters))
62-
(define renderer-list (get-renderer-list renderer-tree))
63-
(define bounds-rect (get-bounds-rect renderer-list x-min x-max y-min y-max))
64-
65-
(: make-bm (-> Boolean Rect Positive-Integer Positive-Integer
66-
(Values (Instance Bitmap%) (U #f (Instance 2D-Plot-Area%)))))
67-
(define (make-bm anim? bounds-rect width height)
68-
(: area (U #f (Instance 2D-Plot-Area%)))
69-
(define area #f)
70-
(define bm (make-bitmap
71-
width height #t
72-
#:backing-scale (or (get-display-backing-scale) 1.0)))
73-
(parameterize/group ([plot-parameters saved-plot-parameters]
74-
[plot-animating? (if anim? #t (plot-animating?))])
75-
(define dc (make-object bitmap-dc% bm))
76-
(define-values (x-ticks x-far-ticks y-ticks y-far-ticks)
77-
(get-ticks renderer-list bounds-rect))
78-
(define new-area
79-
(make-object 2d-plot-area%
80-
bounds-rect x-ticks x-far-ticks y-ticks y-far-ticks
81-
dc 0 0 width height))
82-
(set! area new-area)
83-
(plot-area new-area renderer-list))
84-
(values bm area))
85-
86-
(define-values (bm area) (make-bm #f bounds-rect width height))
87-
88-
(make-2d-plot-snip bm saved-plot-parameters make-bm bounds-rect area width height))]))
87+
[(not (and (integer? width) (positive? width))) (fail/kw "positive integer" '#:width width)]
88+
[(not (and (integer? height) (positive? height))) (fail/kw "positive integer" '#:height height)]
89+
[(and title (not (or (string? title) (pict? title)))) (fail/kw "#f, string or pict" '#:title title)]
90+
[(and x-label (not (or (string? x-label) (pict? x-label)))) (fail/kw "#f, string or pict" '#:x-label x-label)]
91+
[(and y-label (not (or (string? y-label) (pict? y-label)))) (fail/kw "#f, string or pict" '#:y-label y-label)]
92+
[(not (anchor/c legend-anchor)) (fail/kw "anchor/c" '#:legend-anchor legend-anchor)])
93+
94+
(parameterize ([plot-title title]
95+
[plot-x-label x-label]
96+
[plot-y-label y-label]
97+
[plot-legend-anchor legend-anchor])
98+
(define saved-plot-parameters (plot-parameters))
99+
(define renderer-list (get-renderer-list renderer-tree))
100+
(define bounds-rect (get-bounds-rect renderer-list x-min x-max y-min y-max))
101+
102+
(: make-bm (-> Boolean Rect Positive-Integer Positive-Integer
103+
(Values (Instance Bitmap%) (U #f (Instance 2D-Plot-Area%)))))
104+
(define (make-bm anim? bounds-rect width height)
105+
(: area (U #f (Instance 2D-Plot-Area%)))
106+
(define area #f)
107+
(define bm (make-bitmap
108+
width height #t
109+
#:backing-scale (or (get-display-backing-scale) 1.0)))
110+
(parameterize/group ([plot-parameters saved-plot-parameters]
111+
[plot-animating? (if anim? #t (plot-animating?))])
112+
(define dc (make-object bitmap-dc% bm))
113+
(define-values (x-ticks x-far-ticks y-ticks y-far-ticks)
114+
(get-ticks renderer-list bounds-rect))
115+
(define new-area
116+
(make-object 2d-plot-area%
117+
bounds-rect x-ticks x-far-ticks y-ticks y-far-ticks
118+
dc 0 0 width height))
119+
(set! area new-area)
120+
(plot-area new-area renderer-list))
121+
(values bm area))
122+
123+
(define-values (bm area) (make-bm #f bounds-rect width height))
124+
125+
(make-2d-plot-snip bm saved-plot-parameters make-bm bounds-rect area width height)))
89126

90127
;; ===================================================================================================
91128
;; Plot to a frame
@@ -96,9 +133,9 @@
96133
#:y-min (U Real #f) #:y-max (U Real #f)
97134
#:width Positive-Integer
98135
#:height Positive-Integer
99-
#:title (U String #f)
100-
#:x-label (U String #f)
101-
#:y-label (U String #f)
136+
#:title (U String pict #f)
137+
#:x-label (U String pict #f)
138+
#:y-label (U String pict #f)
102139
#:legend-anchor Anchor]
103140
(Instance Frame%)))
104141
(define (plot-frame renderer-tree
@@ -112,29 +149,29 @@
112149
#:legend-anchor [legend-anchor (plot-legend-anchor)])
113150
(define fail/kw (make-raise-keyword-error 'plot-frame))
114151
(cond
152+
;; check arguments, see note at the top of this file
115153
[(and x-min (not (rational? x-min))) (fail/kw "#f or rational" '#:x-min x-min)]
116154
[(and x-max (not (rational? x-max))) (fail/kw "#f or rational" '#:x-max x-max)]
117155
[(and y-min (not (rational? y-min))) (fail/kw "#f or rational" '#:y-min y-min)]
118156
[(and y-max (not (rational? y-max))) (fail/kw "#f or rational" '#:y-max y-max)]
119-
[else
120-
;; make-snip will be called in a separate thread, make sure the
121-
;; parameters have the correct values in that thread as well.
122-
(define saved-plot-parameters (plot-parameters))
123-
(cond ;; check arguments because function is provided unsafely
124-
[(not (and (integer? width) (positive? width))) (fail/kw "positive integer" '#:width width)]
125-
[(not (and (integer? height) (positive? height))) (fail/kw "positive integer" '#:height height)]
126-
[(and title (not (string? title))) (fail/kw "#f or string" '#:title title)]
127-
[(and x-label (not (string? x-label))) (fail/kw "#f or string" '#:x-label x-label)]
128-
[(and y-label (not (string? y-label))) (fail/kw "#f or string" '#:y-label y-label)]
129-
[(not (anchor/c legend-anchor)) (fail/kw "anchor/c" '#:legend-anchor legend-anchor)])
130-
(: make-snip (-> Positive-Integer Positive-Integer (Instance Snip%)))
131-
(define (make-snip width height)
132-
(parameterize/group ([plot-parameters saved-plot-parameters])
133-
(plot-snip
134-
renderer-tree
135-
#:x-min x-min #:x-max x-max #:y-min y-min #:y-max y-max #:width width #:height height
136-
#:title title #:x-label x-label #:y-label y-label #:legend-anchor legend-anchor)))
137-
(make-snip-frame make-snip width height (if title (format "Plot: ~a" title) "Plot"))]))
157+
[(not (and (integer? width) (positive? width))) (fail/kw "positive integer" '#:width width)]
158+
[(not (and (integer? height) (positive? height))) (fail/kw "positive integer" '#:height height)]
159+
[(and title (not (or (string? title) (pict? title)))) (fail/kw "#f, string or pict" '#:title title)]
160+
[(and x-label (not (or (string? x-label) (pict? x-label)))) (fail/kw "#f, string or pict" '#:x-label x-label)]
161+
[(and y-label (not (or (string? y-label) (pict? y-label)))) (fail/kw "#f, string or pict" '#:y-label y-label)]
162+
[(not (anchor/c legend-anchor)) (fail/kw "anchor/c" '#:legend-anchor legend-anchor)])
163+
164+
;; make-snip will be called in a separate thread, make sure the
165+
;; parameters have the correct values in that thread as well.
166+
(define saved-plot-parameters (plot-parameters))
167+
(: make-snip (-> Positive-Integer Positive-Integer (Instance Snip%)))
168+
(define (make-snip width height)
169+
(parameterize/group ([plot-parameters saved-plot-parameters])
170+
(plot-snip
171+
renderer-tree
172+
#:x-min x-min #:x-max x-max #:y-min y-min #:y-max y-max #:width width #:height height
173+
#:title title #:x-label x-label #:y-label y-label #:legend-anchor legend-anchor)))
174+
(make-snip-frame make-snip width height (if title (format "Plot: ~a" title) "Plot")))
138175

139176
;; ===================================================================================================
140177
;; Plot to a frame or a snip, depending on (plot-new-window?)
@@ -145,9 +182,9 @@
145182
#:y-min (U Real #f) #:y-max (U Real #f)
146183
#:width Positive-Integer
147184
#:height Positive-Integer
148-
#:title (U String #f)
149-
#:x-label (U String #f)
150-
#:y-label (U String #f)
185+
#:title (U String pict #f)
186+
#:x-label (U String pict #f)
187+
#:y-label (U String pict #f)
151188
#:legend-anchor Anchor
152189
#:out-file (U Path-String Output-Port #f)
153190
#:out-kind (U 'auto Image-File-Format)
@@ -178,36 +215,43 @@
178215

179216
(define fail/kw (make-raise-keyword-error 'plot))
180217
(cond
218+
;; check arguments, see note at the top of this file
181219
[(and x-min (not (rational? x-min))) (fail/kw "#f or rational" '#:x-min x-min)]
182220
[(and x-max (not (rational? x-max))) (fail/kw "#f or rational" '#:x-max x-max)]
183221
[(and y-min (not (rational? y-min))) (fail/kw "#f or rational" '#:y-min y-min)]
184222
[(and y-max (not (rational? y-max))) (fail/kw "#f or rational" '#:y-max y-max)]
185-
[else
186-
(cond ;; check arguments because function is provided unsafely
187-
[(and out-kind (not (plot-file-format/c out-kind)))
188-
(fail/kw "plot-file-format/c" '#:out-kind out-kind)]
189-
[(and fgcolor (not (plot-color/c fgcolor)))
190-
(fail/kw "plot-color/c" '#:fgcolor fgcolor)]
191-
[(and bgcolor (not (plot-color/c bgcolor)))
192-
(fail/kw "plot-color/c" '#:bgcolor bgcolor)])
193-
(parameterize ([plot-foreground (if fgcolor fgcolor (plot-foreground))]
194-
[plot-background (if bgcolor bgcolor (plot-background))])
195-
(when out-file
196-
(plot-file
197-
renderer-tree out-file out-kind
198-
#:x-min x-min #:x-max x-max #:y-min y-min #:y-max y-max #:width width #:height height
199-
#:title title #:x-label x-label #:y-label y-label #:legend-anchor legend-anchor))
200-
201-
(cond [(plot-new-window?)
202-
(define frame
203-
(plot-frame
204-
renderer-tree
205-
#:x-min x-min #:x-max x-max #:y-min y-min #:y-max y-max #:width width #:height height
206-
#:title title #:x-label x-label #:y-label y-label #:legend-anchor legend-anchor))
207-
(send frame show #t)
208-
(void)]
209-
[else
210-
(plot-snip
211-
renderer-tree
212-
#:x-min x-min #:x-max x-max #:y-min y-min #:y-max y-max #:width width #:height height
213-
#:title title #:x-label x-label #:y-label y-label #:legend-anchor legend-anchor)]))]))
223+
[(not (and (integer? width) (positive? width))) (fail/kw "positive integer" '#:width width)]
224+
[(not (and (integer? height) (positive? height))) (fail/kw "positive integer" '#:height height)]
225+
[(and title (not (or (string? title) (pict? title)))) (fail/kw "#f, string or pict" '#:title title)]
226+
[(and x-label (not (or (string? x-label) (pict? x-label)))) (fail/kw "#f, string or pict" '#:x-label x-label)]
227+
[(and y-label (not (or (string? y-label) (pict? y-label)))) (fail/kw "#f, string or pict" '#:y-label y-label)]
228+
[(not (anchor/c legend-anchor)) (fail/kw "anchor/c" '#:legend-anchor legend-anchor)]
229+
[(and out-kind (not (plot-file-format/c out-kind))) (fail/kw "plot-file-format/c" '#:out-kind out-kind)]
230+
[(not (plot-file-format/c out-kind)) (fail/kw "plot-file-format/c" '#:out-kind out-kind)]
231+
[(and fgcolor (not (plot-color/c fgcolor))) (fail/kw "plot-color/c" '#:fgcolor fgcolor)]
232+
[(and bgcolor (not (plot-color/c bgcolor))) (fail/kw "plot-color/c" '#:bgcolor bgcolor)]
233+
;; NOTE: don't check this one, as it is not used anyway
234+
;; [(and lncolor (not (plot-color/c lncolor))) (fail/kw "plot-color/c" '#:lncolor lncolor)]
235+
[(and out-file (not (or (path-string? out-file) (output-port? out-file))))
236+
(fail/kw "#f, path-string or output port" '#:out-file out-file)])
237+
(parameterize ([plot-foreground (if fgcolor fgcolor (plot-foreground))]
238+
[plot-background (if bgcolor bgcolor (plot-background))])
239+
(when out-file
240+
(plot-file
241+
renderer-tree out-file out-kind
242+
#:x-min x-min #:x-max x-max #:y-min y-min #:y-max y-max #:width width #:height height
243+
#:title title #:x-label x-label #:y-label y-label #:legend-anchor legend-anchor))
244+
245+
(cond [(plot-new-window?)
246+
(define frame
247+
(plot-frame
248+
renderer-tree
249+
#:x-min x-min #:x-max x-max #:y-min y-min #:y-max y-max #:width width #:height height
250+
#:title title #:x-label x-label #:y-label y-label #:legend-anchor legend-anchor))
251+
(send frame show #t)
252+
(void)]
253+
[else
254+
(plot-snip
255+
renderer-tree
256+
#:x-min x-min #:x-max x-max #:y-min y-min #:y-max y-max #:width width #:height height
257+
#:title title #:x-label x-label #:y-label y-label #:legend-anchor legend-anchor)])))

0 commit comments

Comments
 (0)