diff --git a/src/ticks.jl b/src/ticks.jl index 1e5fa81..7b5bc57 100644 --- a/src/ticks.jl +++ b/src/ticks.jl @@ -11,12 +11,6 @@ function bounding_order_of_magnitude(xspan::T) where {T} a = step = 1 while xspan < base^a * one_dt -function bounding_order_of_magnitude(xspan::DT) where DT - one_dt = oneunit(DT) - - a = 1 - step = 1 - while xspan < 10.0^a * one_dt a -= step end @@ -48,7 +42,7 @@ struct Ticks{T} <: AbstractRange{T} q::Int z::Int step::Float64 - Ticks{T}(u,q,z) where T = new(u, q, z, q * 10.0^z) + Ticks{T}(u, q, z) where {T} = new(u, q, z, q * 10.0^z) end fallback_ticks(x_min::T, x_max::T, k_min, k_max) where {T} = ( @@ -60,33 +54,32 @@ fallback_ticks(x_min::T, x_max::T, k_min, k_max) where {T} = ( ) Base.size(t::Ticks) = size(t.u) -Base.step(t::Ticks{T}) where T = t.step * oneunit(T) -Base.getindex(t::Ticks{T}, i::Integer) where T = round(t.u[i]*t.step; digits=-t.z) * oneunit(T) +Base.step(t::Ticks{T}) where {T} = t.step * oneunit(T) +Base.getindex(t::Ticks{T}, i::Integer) where {T} = + round(t.u[i] * t.step; digits = -t.z) * oneunit(T) _ticks_str(t::Ticks) = "($(t.q*t.u))*10^$(t.z)" Base.show(io::IO, t::Ticks{<:AbstractFloat}) = print(io, _ticks_str(t)) -Base.show(io::IO, t::Ticks{T}) where T = print(io, _ticks_str(t), " * ", oneunit(T)) - +Base.show(io::IO, t::Ticks{T}) where {T} = print(io, _ticks_str(t), " * ", oneunit(T)) -function restrict_ticks(t::Ticks{T}, from, to) where T +function restrict_ticks(t::Ticks{T}, from, to) where {T} tickspan = step(t) u_start = max(first(t.u), ceil(Int64, from / tickspan)) u_end = min(last(t.u), floor(Int64, to / tickspan)) - t = Ticks{T}(u_start:u_end,t.q,t.z) + t = Ticks{T}(u_start:u_end, t.q, t.z) # Fix possible floating-point errors (may occur in division above, or due # rounding in (::Ticks)[::Int] when endpoints are near a round number) while u_start <= u_end && t[1] < from u_start += 1 - t = Ticks{T}(u_start:u_end,t.q,t.z) + t = Ticks{T}(u_start:u_end, t.q, t.z) end while u_start <= u_end && t[end] > to u_end -= 1 - t = Ticks{T}(u_start:u_end,t.q,t.z) + t = Ticks{T}(u_start:u_end, t.q, t.z) end t end - # Empty catchall optimize_ticks() = Any[] @@ -179,27 +172,56 @@ and the variables here are: * `i`: index of `q` in `Q`. * `v`: 1 if label range includes 0, 0 otherwise. """ -function optimize_ticks(x_min::T, x_max::T; extend_ticks::Bool=false, - Q=[(10,1.0), (50, 0.9), (20, 0.7), (25, 0.5), (30, 0.2)], - 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 - +function optimize_ticks( + x_min::T, + x_max::T; + extend_ticks::Bool = false, + Q = [(10, 1.0), (50, 0.9), (20, 0.7), (25, 0.5), (30, 0.2)], + 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} Qv = [(Int(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) + 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, + ) end -function optimize_ticks_typed(x_min::T, x_max::T, extend_ticks, - Q::Vector{Tuple{Int,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 +function optimize_ticks_typed( + x_min::T, + x_max::T, + extend_ticks, + Q::Vector{Tuple{Int,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} one_t = oneunit(T) - if x_max - x_min < eps()*one_t + if x_max - x_min < eps() * one_t R = typeof(1.0 * one_t) return R[x_min], x_min - one_t, x_min + one_t end @@ -233,11 +255,11 @@ function optimize_ticks_typed( high_score = -Inf best_ticks = nothing - max_q_exponent = ceil(Int,log10(maximum(q[1] for q in Q))) - while 2k_max * 10.0^(z+max_q_exponent) * one_t > xspan - for (ik, k) in enumerate(k_min:2k_max) + max_q_exponent = ceil(Int, log10(maximum(q[1] for q in Q))) + while 2k_max * 10.0^(z + max_q_exponent) * one_t > xspan + for (ik, k) in enumerate(k_min:(2k_max)) for (q, qscore) in Q - stp = q*10.0^z + stp = q * 10.0^z if stp < eps() continue end @@ -250,16 +272,16 @@ function optimize_ticks_typed( r = ceil(Int64, (x_max - span) / tickspan) - while r*tickspan <= x_min - u = extend_ticks ? (r-k:r+2k-1) : (r:r+k-1) + while r * tickspan <= x_min + u = extend_ticks ? ((r - k):(r + 2k - 1)) : (r:(r + k - 1)) ticks = Ticks{T}(u, q, z) if strict_span - viewmin = max(r*tickspan, x_min) - viewmax = min((r+k-1)*tickspan, x_max) + viewmin = max(r * tickspan, x_min) + viewmax = min((r + k - 1) * tickspan, x_max) buf = something(span_buffer, 0) * (viewmax - viewmin) - ticks = restrict_ticks(ticks,viewmin-buf,viewmax+buf) + ticks = restrict_ticks(ticks, viewmin - buf, viewmax + buf) if length(ticks) < k_min r += 1 continue @@ -267,34 +289,35 @@ function optimize_ticks_typed( end nticks = length(ticks) - # evaluate quality of ticks - has_zero = r <= 0 && abs(r) < k + # evaluate quality of ticks + has_zero = r <= 0 && abs(r) < k - # simplicity - s = has_zero && nice_scale ? 1 : 0 + # simplicity + s = has_zero && nice_scale ? 1 : 0 - # granularity - g = 0 < len < 2k_ideal ? 1 - abs(len - k_ideal) / k_ideal : F(0) + # granularity + g = 0 < len < 2k_ideal ? 1 - abs(len - k_ideal) / k_ideal : F(0) # granularity g = 0 < nticks < 2k_ideal ? 1 - abs(nticks - k_ideal) / k_ideal : 0.0 # coverage - effective_span = (nticks-1) * tickspan - c = 1.5 * xspan/effective_span - - score = granularity_weight * g + - simplicity_weight * s + - coverage_weight * c + - niceness_weight * qscore - - # strict limits on coverage - if strict_span && span > xspan - score -= 10000 - end - if span >= 2xspan - score -= 1000 - end + effective_span = (nticks - 1) * tickspan + c = 1.5 * xspan / effective_span + + score = + granularity_weight * g + + simplicity_weight * s + + coverage_weight * c + + niceness_weight * qscore + + # strict limits on coverage + if strict_span && span > xspan + score -= 10000 + end + if span >= 2xspan + score -= 1000 + end if score > high_score && (k_min <= nticks <= k_max) best_ticks = ticks @@ -309,12 +332,21 @@ function optimize_ticks_typed( if best_ticks === nothing 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, - granularity_weight, simplicity_weight, - coverage_weight, niceness_weight, - false, span_buffer) + 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, + ) else R = typeof(1.0 * one_t) return R[x_min], x_min - one_t, x_min + one_t diff --git a/test/runtests.jl b/test/runtests.jl index 1254292..c4e4f3c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -114,7 +114,7 @@ end ticks, = optimize_ticks(x, y) @test issorted(ticks) @test allunique(ticks) - if (x,y) ∈ [(1.0,1.0+eps()), (1.0-eps(),1.0)] # known failures + if (x, y) ∈ [(1.0, 1.0 + eps()), (1.0 - eps(), 1.0)] # known failures @test_broken all(x .<= ticks .<= y) else @test all(x .<= ticks .<= y)