From 37ee8c2c221659cc0d99ad5ab06bf6ad0003b88d Mon Sep 17 00:00:00 2001 From: Daniel Schwabeneder Date: Sun, 14 Mar 2021 17:12:45 +0100 Subject: [PATCH] reduce code duplication for colorbar ticks --- src/axes.jl | 93 ++++++++++++++++---------------- src/colorbars.jl | 135 +++-------------------------------------------- 2 files changed, 54 insertions(+), 174 deletions(-) diff --git a/src/axes.jl b/src/axes.jl index 64bee8c6..7173999e 100644 --- a/src/axes.jl +++ b/src/axes.jl @@ -137,11 +137,10 @@ const _label_func_tex = Dict{Symbol,Function}( labelfunc_tex(scale::Symbol) = get(_label_func_tex, scale, convert_sci_unicode) -function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing) - amin, amax = axis_limits(sp, axis[:letter]) +function optimal_ticks_and_labels(ticks, alims, scale, formatter) + amin, amax = alims # scale the limits - scale = axis[:scale] sf = RecipesPipeline.scale_func(scale) # If the axis input was a Date or DateTime use a special logic to find @@ -152,7 +151,7 @@ function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing) # rather than on the input format # TODO: maybe: non-trivial scale (:ln, :log2, :log10) for date/datetime if ticks === nothing && scale == :identity - if axis[:formatter] == RecipesPipeline.dateformatter + if formatter == RecipesPipeline.dateformatter # optimize_datetime_ticks returns ticks and labels(!) based on # integers/floats corresponding to the DateTime type. Thus, the axes # limits, which resulted from converting the Date type to integers, @@ -163,7 +162,7 @@ function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing) k_min = 2, k_max = 4) # Now the ticks are converted back to floats corresponding to Dates. return ticks / 864e5, labels - elseif axis[:formatter] == RecipesPipeline.datetimeformatter + elseif formatter == RecipesPipeline.datetimeformatter return optimize_datetime_ticks(amin, amax; k_min = 2, k_max = 4) end end @@ -187,14 +186,13 @@ function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing) # chosen ticks is not too much bigger than amin - amax: strict_span = false, ) - axis[:lims] = map(RecipesPipeline.inverse_scale_func(scale), (viewmin, viewmax)) + # axis[:lims] = map(RecipesPipeline.inverse_scale_func(scale), (viewmin, viewmax)) else scaled_ticks = map(sf, (filter(t -> amin <= t <= amax, ticks))) end unscaled_ticks = map(RecipesPipeline.inverse_scale_func(scale), scaled_ticks) labels = if any(isfinite, unscaled_ticks) - formatter = axis[:formatter] if formatter in (:auto, :plain, :scientific, :engineering) map(labelfunc(scale, backend()), Showoff.showoff(scaled_ticks, formatter)) elseif formatter == :latex @@ -221,52 +219,53 @@ end # return (continuous_values, discrete_values) for the ticks on this axis function get_ticks(sp::Subplot, axis::Axis; update = true) if update || !haskey(axis.plotattributes, :optimized_ticks) + dvals = axis[:discrete_values] ticks = _transform_ticks(axis[:ticks]) - if ticks in (:none, nothing, false) - axis.plotattributes[:optimized_ticks] = nothing + axis.plotattributes[:optimized_ticks] = if ticks isa Symbol && ticks !== :none && + ispolar(sp) && axis[:letter] === :x && !isempty(dvals) + collect(0:pi/4:7pi/4), string.(0:45:315) else - # treat :native ticks as :auto - ticks = ticks == :native ? :auto : ticks - - dvals = axis[:discrete_values] - cv, dv = if typeof(ticks) <: Symbol - if !isempty(dvals) - # discrete ticks... - n = length(dvals) - rng = if ticks == :auto && n > 15 - Δ = ceil(Int, n / 10) - Δ:Δ:n - else # if ticks == :all - 1:n - end - axis[:continuous_values][rng], dvals[rng] - elseif ispolar(axis.sps[1]) && axis[:letter] == :x - #force theta axis to be full circle - (collect(0:pi/4:7pi/4), string.(0:45:315)) - else - # compute optimal ticks and labels - optimal_ticks_and_labels(sp, axis) - end - elseif typeof(ticks) <: Union{AVec, Int} - if !isempty(dvals) && typeof(ticks) <: Int - rng = Int[round(Int,i) for i in range(1, stop=length(dvals), length=ticks)] - axis[:continuous_values][rng], dvals[rng] - else - # override ticks, but get the labels - optimal_ticks_and_labels(sp, axis, ticks) - end - elseif typeof(ticks) <: NTuple{2, Any} - # assuming we're passed (ticks, labels) - ticks - else - error("Unknown ticks type in get_ticks: $(typeof(ticks))") - end - axis.plotattributes[:optimized_ticks] = (cv, dv) + cvals = axis[:continuous_values] + alims = axis_limits(sp, axis[:letter]) + scale = axis[:scale] + formatter = axis[:formatter] + get_ticks(ticks, cvals, dvals, alims, scale, formatter) end end - axis.plotattributes[:optimized_ticks] + return axis.plotattributes[:optimized_ticks] end +function get_ticks(ticks::Symbol, cvals::T, dvals, args...) where T + if ticks === :none + return T[], String[] + elseif !isempty(dvals) + n = length(dvals) + if ticks === :all || n < 16 + return cvals, string.(dvals) + else + Δ = ceil(Int, n / 10) + rng = Δ:Δ:n + return cvals[rng], string.(dvals[rng]) + end + else + return optimal_ticks_and_labels(nothing, args...) + end +end +get_ticks(ticks::AVec, cvals, dvals, args...) = optimal_ticks_and_labels(ticks, args...) +function get_ticks(ticks::Int, dvals, cvals, args...) + if !isempty(dvals) + rng = round.(Int, range(1, stop=length(dvals), length=ticks)) + cvals[rng], string.(dvals[rng]) + else + optimal_ticks_and_labels(ticks, args...) + end +end +get_ticks(ticks::NTuple{2, Any}, args...) = ticks +get_ticks(::Nothing, cvals::T, args...) where T = T[], String[] +get_ticks(ticks::Bool, args...) = + ticks ? get_ticks(:auto, args...) : get_ticks(nothing, args...) +get_ticks(::T, args...) where T = error("Unknown ticks type in get_ticks: $T") + _transform_ticks(ticks) = ticks _transform_ticks(ticks::AbstractArray{T}) where T <: Dates.TimeType = Dates.value.(ticks) _transform_ticks(ticks::NTuple{2, Any}) = (_transform_ticks(ticks[1]), ticks[2]) diff --git a/src/colorbars.jl b/src/colorbars.jl index a16153b5..b0f9996a 100644 --- a/src/colorbars.jl +++ b/src/colorbars.jl @@ -71,139 +71,20 @@ end hascolorbar(series::Series) = colorbar_style(series) !== nothing hascolorbar(sp::Subplot) = sp[:colorbar] != :none && any(hascolorbar(s) for s in series_list(sp)) -function optimal_colorbar_ticks_and_labels(sp::Subplot, ticks = nothing) - amin, amax = get_clims(sp) - - # scale the limits - scale = sp[:colorbar_scale] - sf = RecipesPipeline.scale_func(scale) - - # Taken from optimal_ticks_and_labels, but needs a different method as there can only be 1 colorbar per subplot - # - # If the axis input was a Date or DateTime use a special logic to find - # "round" Date(Time)s as ticks - # This bypasses the rest of optimal_ticks_and_labels, because - # optimize_datetime_ticks returns ticks AND labels: the label format (Date - # or DateTime) is chosen based on the time span between amin and amax - # rather than on the input format - # TODO: maybe: non-trivial scale (:ln, :log2, :log10) for date/datetime - if ticks === nothing && scale == :identity - if sp[:colorbar_formatter] == RecipesPipeline.dateformatter - # optimize_datetime_ticks returns ticks and labels(!) based on - # integers/floats corresponding to the DateTime type. Thus, the axes - # limits, which resulted from converting the Date type to integers, - # are converted to 'DateTime integers' (actually floats) before - # being passed to optimize_datetime_ticks. - # (convert(Int, convert(DateTime, convert(Date, i))) == 87600000*i) - ticks, labels = optimize_datetime_ticks(864e5 * amin, 864e5 * amax; - k_min = 2, k_max = 4) - # Now the ticks are converted back to floats corresponding to Dates. - return ticks / 864e5, labels - elseif sp[:colorbar_formatter] == RecipesPipeline.datetimeformatter - return optimize_datetime_ticks(amin, amax; k_min = 2, k_max = 4) - end - end - - # get a list of well-laid-out ticks - if ticks === nothing - scaled_ticks = optimize_ticks( - sf(amin), - sf(amax); - k_min = 4, # minimum number of ticks - k_max = 8, # maximum number of ticks - )[1] - elseif typeof(ticks) <: Int - scaled_ticks, viewmin, viewmax = optimize_ticks( - sf(amin), - sf(amax); - k_min = ticks, # minimum number of ticks - k_max = ticks, # maximum number of ticks - k_ideal = ticks, - # `strict_span = false` rewards cases where the span of the - # chosen ticks is not too much bigger than amin - amax: - strict_span = false, - ) - sp[:clims] = map(RecipesPipeline.inverse_scale_func(scale), (viewmin, viewmax)) - else - scaled_ticks = map(sf, (filter(t -> amin <= t <= amax, ticks))) - end - unscaled_ticks = map(RecipesPipeline.inverse_scale_func(scale), scaled_ticks) - - labels = if any(isfinite, unscaled_ticks) - formatter = sp[:colorbar_formatter] - if formatter in (:auto, :plain, :scientific, :engineering) - map(labelfunc(scale, backend()), Showoff.showoff(scaled_ticks, formatter)) - elseif formatter == :latex - map(x -> string("\$", replace(convert_sci_unicode(x), '×' => "\\times"), "\$"), Showoff.showoff(unscaled_ticks, :auto)) - else - # there was an override for the formatter... use that on the unscaled ticks - map(formatter, unscaled_ticks) - # if the formatter left us with numbers, still apply the default formatter - # However it leave us with the problem of unicode number decoding by the backend - # if eltype(unscaled_ticks) <: Number - # Showoff.showoff(unscaled_ticks, :auto) - # end - end - else - # no finite ticks to show... - String[] - end - - # @show unscaled_ticks labels - # labels = Showoff.showoff(unscaled_ticks, scale == :log10 ? :scientific : :auto) - unscaled_ticks, labels -end - -# return (continuous_values, discrete_values) for the ticks on this axis function get_colorbar_ticks(sp::Subplot; update = true) if update || !haskey(sp.attr, :colorbar_optimized_ticks) ticks = _transform_ticks(sp[:colorbar_ticks]) - if ticks in (:none, nothing, false) - sp.attr[:colorbar_optimized_ticks] = nothing - else - # treat :native ticks as :auto - ticks = ticks == :native ? :auto : ticks - - dvals = sp[:colorbar_discrete_values] - cv, dv = if typeof(ticks) <: Symbol - if !isempty(dvals) - # discrete ticks... - n = length(dvals) - rng = if ticks == :auto && n > 15 - Δ = ceil(Int, n / 10) - Δ:Δ:n - else # if ticks == :all - 1:n - end - sp[:colorbar_continuous_values][rng], dvals[rng] - else - # compute optimal ticks and labels - optimal_colorbar_ticks_and_labels(sp) - end - elseif typeof(ticks) <: Union{AVec, Int} - if !isempty(dvals) && typeof(ticks) <: Int - rng = Int[round(Int,i) for i in range(1, stop=length(dvals), length=ticks)] - sp[:colorbar_continuous_values][rng], dvals[rng] - else - # override ticks, but get the labels - optimal_colorbar_ticks_and_labels(sp, ticks) - end - elseif typeof(ticks) <: NTuple{2, Any} - # assuming we're passed (ticks, labels) - ticks - else - error("Unknown ticks type in get_ticks: $(typeof(ticks))") - end - sp.attr[:colorbar_optimized_ticks] = (cv, dv) - end + cvals = sp[:colorbar_continuous_values] + dvals = sp[:colorbar_discrete_values] + clims = get_clims(sp) + scale = sp[:colorbar_scale] + formatter = sp[:colorbar_formatter] + sp.attr[:colorbar_optimized_ticks] = + get_ticks(ticks, cvals, dvals, clims, scale, formatter) end - sp.attr[:colorbar_optimized_ticks] + return sp.attr[:colorbar_optimized_ticks] end -_transform_ticks(ticks) = ticks -_transform_ticks(ticks::AbstractArray{T}) where T <: Dates.TimeType = Dates.value.(ticks) -_transform_ticks(ticks::NTuple{2, Any}) = (_transform_ticks(ticks[1]), ticks[2]) - function _update_subplot_colorbars(sp::Subplot) # Dynamic callback from the pipeline if needed end