racket / plot

Other
40 stars 37 forks source link

Tick labels at the wrong place #117

Closed Metaxal closed 1 year ago

Metaxal commented 1 year ago

The following code:

#lang racket
(require plot)

;; Ok plot
(list (parameterize ([plot-y-transform log-transform]
                     [plot-y-ticks (log-ticks #:number 10)])
  (plot (function exp)
        #:x-min 0. #:x-max 20
        #:height 600))

;; Wrong labels on the y axis
(parameterize ([plot-y-transform log-transform]
               [plot-y-ticks (ticks-add
                              (log-ticks #:number 10)
                              '(500))])
  (plot (function exp)
        #:x-min 0. #:x-max 20
        #:height 600)))

produces: Screenshot from 2023-05-10 10-55-14

Observe that on the r.h.s. plot the y-tick labelled 10² is at the wrong place.

I'm not sure what's causing this. I tried changing the #:number value but the problem remains.

soegaard commented 1 year ago

I think, the culprit is log-ticks-format. It turns out the number 500 in your example matches this case:

                  (cond [((abs (- x (expt base log-x))) . < . epsilon)
                         (displayln (list 'base base 'x x 'base-to-log-x (expt base log-x) 'eps epsilon))
                         (displayln (list 'AAA t x))                         
                         (major-str)]

If x ~ (expt base log-x) then the label is of the form "10..." which is produced by major-string. But ... the epsilon used is 10000 ?!

(base 10 x 500 base-to-log-x 100 eps 100000)
(AAA #(struct:pre-tick 500 #t) 500)

The epsilon value is computed by

         (define epsilon (expt 10 (- (digits-for-range x-min x-max))))

and (digits-for-range x-min x-max) returns -5 in your example.

I am unsure whether to blame the above expression or digits-for-range.

(:: log-ticks-format (->* [] [#:base Positive-Integer] Ticks-Format))
(define (log-ticks-format #:base [base 10])
  (cond
    [(< base 2)  (error 'log-ticks-format "expected base >= 2; given ~e" base)]
    [else
     (define base-str (number->string base))
     (λ (x-min x-max ts)
       (with-exact-bounds x-min x-max
         (define epsilon (expt 10 (- (digits-for-range x-min x-max))))
         (displayln (list 'digits-range (digits-for-range x-min x-max)))
         (define base-digits (digits-for-range 0 base))
         (for/list ([t  (in-list ts)])
           (define x (pre-tick-value t))
           (cond [(<= x 0)  (raise-argument-error 'log-ticks-format
                                                  "(Listof pre-tick) with positive positions"
                                                  2 x-min x-max ts)]
                 [else
                  (define log-x (floor-log/base base x))
                  (define (major-str)
                    (if (zero? log-x) "1" (format "~a~a" base-str (integer->superscript log-x))))
                  (cond [((abs (- x (expt base log-x))) . < . epsilon)
                         (displayln (list 'base base 'x x 'base-to-log-x (expt base log-x) 'eps epsilon))
                         (displayln (list 'AAA t x))                         
                         (major-str)]
                        [(zero? log-x)
                         (displayln (list 'BBB t x))
                         (real->plot-label x base-digits)]
                        [else  
                         (format "~a×~a"
                                       (real->plot-label (/ x (expt base log-x)) base-digits)
                                       (major-str))])]))))]))

https://github.com/racket/plot/blob/728d2c3bb1451d4c296e6caf5b22743321998f6f/plot-lib/plot/private/common/ticks.rkt#L230

alex-hhh commented 1 year ago

Hi @soegaard , I think you are on the right track.

First, what is happening in the plot is that the tick at position 500 is getting the label "10²", the same label that the tick at position 100 would get. The plot package will remove ticks with the same label, so the tick at position 100 is removed and the tick at position 500 is shown at the correct position but with the incorrect label. This creates the impression that the tick is incorrectly placed, when the problem is that the tick has the incorrect label.

The epsilon value does indeed looks incorrect. I suspect the problem is that digits-for-range should not be used here, since it is supposed to return the number of fractional digits required to distinguish values; it should also only return positive values, but it returns -5.

In particular digits-for-range adds a number of extra digits (by default 3) to the result, so it calculates the digit value as -8 (since the range is from 0 to 10⁸), adds 3 to it and returns -5. I think the correct fix would be for digits-for-range to return 0 in such cases and for log-ticks-format to use a different method to calculate the epsilon value, but I am not sure what are the implications for the rest of the code base for these changes.

I will need to investigate this further, but thanks @soegaard for the initial investigation.

alex-hhh commented 1 year ago

I changed the epsilon for log ticks so that the tick at 500 shows up. The issue is however that, depending on the magnitude of the numbers, sometimes ticks won't show up. In the example below, additional ticks are added at 500, (+ (expt 10 3) 500), (+ (expt 10 4) 500), (+ (expt 10 5) 500), (+ (expt 10 6) 500), (+ (expt 10 7) 500), but later ticks are not visible, since they are too close to existing major ticks.

A more accurate way of saying this is that the tick at (+ (expt 10 6) 500) will be drawn, but the label will be 10⁷, but the precision of the drawing makes the difference irrelevant.

pr117

At first, I wanted to change digits-for-range, which returns negative numbers for values that are too large, but that breaks to many things in the plot code -- it seems the rest of the code base relies on this behavior, and I don't know enough maths to be able to make a call on how to fix it.

Metaxal commented 1 year ago

Sounds like you solved the problem, thanks @alex-hhh !