;-------------------------------------------------------------
; $Id: loglevels.pro 15739 2014-09-03 22:05:59Z nikos $
;+
; NAME:
; LOGLEVELS (function)
;
; PURPOSE:
; Compute default values for logarithmic axis labeling
; or contour levels. For a range from 1 to 100 these
; would be 1., 2., 5., 10., 20., 50., 100.
; If the range spans more than (usually) 3 decades, only
; decadal values will be returned unless the /FINE keyword
; is set.
;
; CATEGORY:
; Tools
;
; CALLING SEQUENCE:
; result = LOGLEVELS([range | MIN=min,MAX=max] [,/FINE]
;[,COARSE=dec])
;
; INPUTS:
; RANGE -> A 2-element vector with the minimum and maximum
; value to be returned. Only levels _within_ this range
; will be returned. If RANGE contains only one element,
; this is interpreted as MAX and MIN will be assumed as
; 3 decades smaller. RANGE superseeds the MIN and MAX
; keywords. Note that RANGE must be positive definite
; but can be given in descending order in which case
; the labels will be reversed.
;
; KEYWORD PARAMETERS:
; MIN, MAX -> alternative way of specifying a RANGE. If only
; one keyword is given, the other one is computed as
; 3 decades smaller/larger than the given parameter.
; RANGE superseeds MIN and MAX.
;
; /FINE -> always return finer levels (1,2,5,...)
;
; COARSE -> the maximum number of decades for which LOGLEVELS
; shall return fine labels. Default is 3. (non-integer
; values are possible).
;
; OUTPUTS:
; A vector with "round" logarithmic values within the given
; range. The original (or modified) RANGE will be returned
; unchanged if RANGE does not span at least one label interval.
; The result will always contain at least two elements.
;
;
; SUBROUTINES:
; none
;
; REQUIREMENTS:
; none
;
; NOTES:
; If COARSE is lt 0, the nearest decades will be returned
; instead. The result will always have at least two elements.
; If COARSE forces decades, the result values may be out-of-
; range if RANGE spans less than a decade.
;
; Caution with type conversion from FLOAT to DOUBLE !!
;
; EXAMPLE:
; range = [ min(data), max(data) ]
; c_level = LOGLEVELS(range)
; contour,...,c_level=c_level
;
;
; MODIFICATION HISTORY:
; mgs, 17 Mar 1999: VERSION 1.00
;
;-
; Copyright (C) 1999, Martin Schultz, Harvard University
; This software is provided as is without any warranty
; whatsoever. It may be freely used, copied or distributed
; for non-commercial purposes. This copyright notice must be
; kept with any copy of this software. If this software shall
; be used commercially or sold as part of a larger package,
; please contact the author to arrange payment.
; Bugs and comments should be directed to mgs@io.harvard.edu
; with subject "IDL routine loglevels"
;-------------------------------------------------------------
function loglevels,range,min=mind,max=maxd, $
coarse=coarse,fine=fine
if (n_elements(COARSE) eq 0) then COARSE = 3
; Make sure to have a valid range which is positive definite
; NOTE that range does not need to be sorted!
if (n_elements(mind) gt 0) then begin
mind = mind[0]
if (n_elements(maxd) eq 0) then maxd = mind*1000.
endif
if (n_elements(maxd) gt 0) then begin
maxd = maxd[0]
if (n_elements(mind) eq 0) then mind = maxd*0.001
endif
; still not defined, i.e. neither mind nor maxd given
if (n_elements(mind) eq 0) then begin
mind = 0.1
maxd = 100.
endif
; RANGE superseeds min and max
if (n_elements(range) eq 0) then range = [ mind,maxd ]
; one element for RANGE is interpreted as MAX
if (n_elements(range) eq 1) then range = [ range*0.001, range ]
thisrange = double(range) > 1.D-100
thisrange = thisrange[sort(thisrange)]
; set lower range to 3 decades below upper range if it is zero
if (thisrange[0] lt 1.D-36) then thisrange[0] = thisrange[1]/1000.
; get log of ranges and decadal log
lrange = alog10(thisrange)
if (lrange[0] lt 0.) then lrange[0] = lrange[0] - 1.0D-6
if (lrange[1] lt 0.) then lrange[1] = lrange[1] - 1.0D-6
; if (lrange[1] gt 0.) then lrange[1] = lrange[1] + 1.0D-6
drange = fix(lrange)
; create label arrays to choose from
; currently 1.D-15 to 5.D+16
; ranges outside these limits always return only decades
; set mode according to following rules:
; - range outside limits -> return decades
; - coarse exceeded -> return decades
; - /fine set -> return 1,2,5,... for any number of decades
; - [automatic] -> return decades if more than COARSE decades
; otherwise 1,2,5,..
mode = 0 ; return decades
if (keyword_set(fine)) then mode = 1
if ((lrange[1]-lrange[0]) le COARSE) then mode = 1
if (thisrange[0] lt 1.D-15 OR thisrange[1] gt 5.D16) then mode = 0
if (mode) then begin
; make overall array
labels = [ 1.D-15, 2.D-15, 5.D-15, 1.D-14, 2.D-14, 5.D-14, $
1.D-13, 2.D-13, 5.D-13, 1.D-12, 2.D-12, 5.D-12, $
1.D-11, 2.D-11, 5.D-11, 1.D-10, 2.D-10, 5.D-10, $
1.D-09, 2.D-09, 5.D-09, 1.D-08, 2.D-08, 5.D-08, $
1.D-07, 2.D-07, 5.D-07, 1.D-06, 2.D-06, 5.D-06, $
1.D-05, 2.D-05, 5.D-05, 1.D-04, 2.D-04, 5.D-04, $
1.D-03, 2.D-03, 5.D-03, 1.D-02, 2.D-02, 5.D-02, $
1.D-01, 2.D-01, 5.D-01, 1.D+00, 2.D+00, 5.D+00 ]
labels = [ labels, $
1.D+01, 2.D+01, 5.D+01, 1.D+02, 2.D+02, 5.D+02, $
1.D+03, 2.D+03, 5.D+03, 1.D+04, 2.D+04, 5.D+04, $
1.D+05, 2.D+05, 5.D+05, 1.D+06, 2.D+06, 5.D+06, $
1.D+07, 2.D+07, 5.D+07, 1.D+08, 2.D+08, 5.D+08, $
1.D+09, 2.D+09, 5.D+09, 1.D+10, 2.D+10, 5.D+10, $
1.D+11, 2.D+11, 5.D+11, 1.D+12, 2.D+12, 5.D+12, $
1.D+13, 2.D+13, 5.D+13, 1.D+14, 2.D+14, 5.D+14, $
1.D+15, 2.D+15, 5.D+15, 1.D+16, 2.D+16, 5.D+16 ]
llabels = alog10(labels)
ind = where(llabels ge lrange[0] AND llabels le lrange[1])
; TJK 8/9/2013 if range values are too close return original range
; if (ind[0] lt 0) then return,range
if (((range[1]-range[0] lt 1) and (labels[ind[0]] ne 0.1D)) or ind[0] lt 0) then begin
print, 'LOGLEVELS: data range too small for log labels, possible log label was ',labels[ind[0]],' returning original range.'
return,range
endif
; return reversed labels if range[0] gt range[1]
if (range[0] gt range[1]) then $
return,reverse(labels[min(ind):max(ind)]) $
else $
return,labels[min(ind):max(ind)]
endif else begin
if (lrange[1] lt 0.) then drange[1] = drange[1] - 1
exponent = indgen(drange[1]-drange[0]+1)+drange[0]
if (n_elements(exponent) eq 1) then $
exponent = [ exponent, exponent+1 ]
if (range[0] gt range[1]) then $
return,reverse(10.D0^exponent) $
else $
return,10.D0^exponent
endelse
end