diff --git a/src/ticks.jl b/src/ticks.jl index fb3dd7c..6cd32a9 100644 --- a/src/ticks.jl +++ b/src/ticks.jl @@ -1,27 +1,29 @@ +const _logScales = [:ln, :log2, :log10] +const _logScaleBases = Dict(:ln => ℯ, :log2 => 2.0, :log10 => 10.0) # NOTE: This file was moved from Gadfly.jl, and the original author is Daniel Jones (@dcjones) # Find the smallest order of magnitude that is larger than xspan This is a # little opaque because I want to avoid assuming the log function is defined # over typeof(xspan) -function bounding_order_of_magnitude(xspan::DT) where DT +function bounding_order_of_magnitude(xspan::DT, base) where DT one_dt = convert(DT, one(DT)) a = 1 step = 1 - while xspan < 10.0^a * one_dt + while xspan < base^a * one_dt a -= step end b = 1 step = 1 - while xspan > 10.0^b * one_dt + while xspan > base^b * one_dt b += step end while a + 1 < b c = div(a + b, 2) - if xspan < 10.0^c * one_dt + if xspan < base^c * one_dt b = c else a = c @@ -131,78 +133,88 @@ function optimize_ticks(x_min::T, x_max::T; extend_ticks::Bool=false, k_min::Int=2, k_max::Int=10, k_ideal::Int=5, granularity_weight::Float64=1/4, simplicity_weight::Float64=1/6, coverage_weight::Float64=1/3, niceness_weight::Float64=1/4, - strict_span=true, span_buffer = nothing) where T - + strict_span=true, span_buffer=nothing, scale=nothing) where T + Qv = [(Float64(q[1]), Float64(q[2])) for q in Q] optimize_ticks_typed(x_min, x_max, extend_ticks, Qv, k_min, k_max, k_ideal, granularity_weight, simplicity_weight, - coverage_weight, niceness_weight, strict_span, span_buffer) + coverage_weight, niceness_weight, strict_span, span_buffer, scale) end function optimize_ticks_typed(x_min::T, x_max::T, extend_ticks, - Q::Vector{Tuple{Float64,Float64}}, k_min, - k_max, k_ideal, + Q::Vector{Tuple{Float64,Float64}}, k_min, k_max, k_ideal, granularity_weight::Float64, simplicity_weight::Float64, coverage_weight::Float64, niceness_weight::Float64, - strict_span, span_buffer) where T + strict_span, span_buffer, scale) where T one_t = convert(T, one(T)) - if x_max - x_min < eps()*one_t - R = typeof(1.0 * one_t) + if x_max - x_min < eps() * one_t + R = typeof(1. * one_t) return R[x_min], x_min - one_t, x_min + one_t end n = length(Q) + is_log_scale = scale ∈ _logScales + base = get(_logScaleBases, scale, 10.) # generalizing "order of magnitude" xspan = x_max - x_min - z = bounding_order_of_magnitude(xspan) + z = bounding_order_of_magnitude(xspan, base) - # find required significant digits for ticks with q*10^z spacing, + # find required significant digits for ticks with q*base^z spacing, # for q values specified in Q - x_digits = bounding_order_of_magnitude(max(abs(x_min), abs(x_max))) + x_digits = bounding_order_of_magnitude(max(abs(x_min), abs(x_max)), base) q_extra_digits = maximum(postdecimal_digits(q[1]) for q in Q) sigdigits(z) = max(1, x_digits - z + q_extra_digits) + ib = Int(base) + round_base = ( + isinteger(base) ? + v -> round(v, sigdigits=sigdigits(z), base=ib) : + v -> round(v, sigdigits=sigdigits(z)) + ) + high_score = -Inf - S_best = Array{typeof(1.0 * one_t)}(undef, 1) + S_best = Array{typeof(1. * one_t)}(undef, 1) viewmin_best, viewmax_best = x_min, x_max - # we preallocate arrays that hold all required S arrays for every given # the k parameter, so we don't have to create them again and again, which # saves many allocations prealloc_Ss = if extend_ticks - [Array{typeof(1.0 * one_t)}(undef, Int(3 * k)) for k in k_min:2k_max] + [Array{typeof(1. * one_t)}(undef, Int(3k)) for k in k_min:2k_max] else - [Array{typeof(1.0 * one_t)}(undef, k) for k in k_min:2k_max] + [Array{typeof(1. * one_t)}(undef, k) for k in k_min:2k_max] end - while 2k_max * 10.0^(z+1) * one_t > xspan + while 2k_max * base^(z + 1) * one_t > xspan for (ik, k) in enumerate(k_min:2k_max) for (q, qscore) in Q - tickspan = q * 10.0^z * one_t + tickspan = q * base^z * one_t span = (k - 1) * tickspan if span < xspan continue end - stp = q*10.0^z + stp = q*base^z if stp < eps() continue end + if is_log_scale && !isinteger(stp) + continue # prefer integer exponents for log scales + end r = ceil(Int64, (x_max - span) / (stp * one_t)) while r*stp * one_t <= x_min # Filter or expand ticks if extend_ticks S = prealloc_Ss[ik] - for i in 0:(3*k - 1) + for i in 0:(3k - 1) S[i+1] = (r + i - k) * tickspan end # round only those values that end up as viewmin and viewmax # to save computation time - S[k + 1] = round(S[k + 1], sigdigits = sigdigits(z)) - S[2 * k] = round(S[2 * k], sigdigits = sigdigits(z)) + S[k + 1] = round_base(S[k + 1]) + S[2 * k] = round_base(S[2 * k]) viewmin, viewmax = S[k + 1], S[2 * k] else S = prealloc_Ss[ik] @@ -211,8 +223,8 @@ function optimize_ticks_typed(x_min::T, x_max::T, extend_ticks, end # round only those values that end up as viewmin and viewmax # to save computation time - S[1] = round(S[1], sigdigits = sigdigits(z)) - S[k] = round(S[k], sigdigits = sigdigits(z)) + S[1] = round_base(S[1]) + S[k] = round_base(S[k]) viewmin, viewmax = S[1], S[k] end if strict_span @@ -240,14 +252,14 @@ function optimize_ticks_typed(x_min::T, x_max::T, extend_ticks, has_zero = r <= 0 && abs(r) < k # simplicity - s = has_zero ? 1.0 : 0.0 + s = has_zero ? 1 : 0 # granularity - g = 0 < length(S) < 2k_ideal ? 1 - abs(length(S) - k_ideal) / k_ideal : 0.0 + g = 0 < length(S) < 2k_ideal ? 1 - abs(length(S) - k_ideal) / k_ideal : 0 # coverage - effective_span = (length(S)-1) * tickspan - c = 1.5 * xspan/effective_span + effective_span = (length(S) - 1) * tickspan + c = 1.5xspan / effective_span score = granularity_weight * g + simplicity_weight * s + @@ -258,7 +270,7 @@ function optimize_ticks_typed(x_min::T, x_max::T, extend_ticks, if strict_span && span > xspan score -= 10000 end - if span >= 2.0*xspan + if span >= 2xspan score -= 1000 end @@ -268,7 +280,7 @@ function optimize_ticks_typed(x_min::T, x_max::T, extend_ticks, # could otherwise be mutated in the next runs S = collect(S) end - (S_best, viewmin_best, viewmax_best) = (S, viewmin, viewmax) + S_best, viewmin_best, viewmax_best = S, viewmin, viewmax high_score = score end r += 1 @@ -281,14 +293,12 @@ function optimize_ticks_typed(x_min::T, x_max::T, extend_ticks, if isinf(high_score) if strict_span @warn("No strict ticks found") - return optimize_ticks_typed(x_min, x_max, extend_ticks, - Q, k_min, - k_max, k_ideal, + return optimize_ticks_typed(x_min, x_max, extend_ticks, Q, k_min, k_max, k_ideal, granularity_weight, simplicity_weight, coverage_weight, niceness_weight, - false, span_buffer) + false, span_buffer, scale) else - R = typeof(1.0 * one_t) + R = typeof(1. * one_t) return R[x_min], x_min - one_t, x_min + one_t end end