(define (real-point point)
; Type-checking for points
(and (pair? point)
(number? (car point))
(number? (cdr point))))
(set-point?-! real-point)
; Use the real-coordinates system
Now imagine you want to draw a bar chart using an exponential scale. Instead of adding a call to the LOG function before each call to BGI routine, you can define your own coordinates-conversion function. Call SET-COORDINATES! with three functions as parameters, one extracting the device's X-coordinate from a point, one extracting the Y-coordinate, and the reverse procedure which returns the point corresponding to given device coordinates:
(set-coordinates! my-x my-y reverse-xy)
(define (my-x point)
; X-coord scaled from 0 to 1
(round
; Return a whole number
(* (car point)
; Multiply the given X-coordinate...
(car (get-max-xy)))))
; ...by the maximum X-coordinate
(define (my-y point)
; Y-coord log: 1 (bottom) to 1000
(round
; Return a whole number
(* (- 1 (log (cdr point) 1000))
; Log of reversed y-coordinate...
(cdr (get-max-xy)))))
; ...times the maximum y-coordinate
(define (reverse-xy xy)
; Reverse procedure, used when BGI
(let ((max-xy (get-max-xy)))
; wants to return a coordinate
(cons
; Return a point (a pair)
(/ (car xy) (car max-xy))
(expt 1000 (- 1 (/ (cdr xy) (cdr max-xy)))))))
(set-coordinates! my-x my-y reverse-xy)
; Use the vertical-logarithmic system
Don't forget to call SET-POINT?-!, because SET-COORDINATES! won't call it for you (the example above assumes you've already typed in the SET-POINT?-! example).
Note that using this coordinate system, the same call to a function such as LINE-REL can have different results, depending on where the pen is:
(move-to) (move-to
)
(line-rel) ; length = 1/3 of total height
(move-to) ; goes up into log scale...
(line-rel) ; length = 1/72 of total height
Of course, you don't need to assume that a point is a pair; why not a list of numbers, in a three- or four-dimensional space? Just write the functions that extract a projection out of a vector, and you're done!
Do you like Euclid? If you don't, you can also define how to calculate distances, almost the same way you defined the coordinates. SET-DISTANCES! accepts three parameters: the X distance extractor, the Y distance extractor and the unary distance extractor. These three functions receive the point from which the distance is to be calculated and the user-desired distance. X & Y distances are used for the following functions: MOVE-REL, LINE-REL, ELLIPSE, FILL-ELLIPSE and SECTOR, while the unary distance is used by ARC, CIRCLE and PIE-SLICE (``unary'' means that the function receives distances as a number and returns a number; while X and Y distances receive a fictive point which is the displacement between two points).
All these coordinate-manipulation primitives return the previous version of all the procedures they change, so you can restore them later. In particular, SET-WORLD! returns all of seven procedures, in the order POINT?, X, Y, REVERSE-XY, X-DIST, Y-DIST, and UNARY-DIST; and all can be restored in one single call to RESTORE-WORLD!:
(let ((old-sys (set-world!![]()
))) ; change and remember
...
(restore-world! old-sys))
If you want to restore procedures by yourself, note the following example:
(let* ((old-sys (set-world!![]()
)) ; change and remember
(old-point? (car old-sys))
(old-coord (cdr old-sys))
(old-dist (cdddr old-coord)))
...
(set-point?-! old-point?)
(set-coordinates! (car old-coord) (cadr old-coord) (caddr old-coord))
(set-distances! (car old-dist) (cadr old-dist) (caddr old-dist))
Always restore procedures in this order, since SET-COORDINATES! modifies the distance-functions to adapt them to the new given system.