reduce code duplication for colorbar ticks
This commit is contained in:
parent
820858576a
commit
37ee8c2c22
93
src/axes.jl
93
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)
|
labelfunc_tex(scale::Symbol) = get(_label_func_tex, scale, convert_sci_unicode)
|
||||||
|
|
||||||
|
|
||||||
function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing)
|
function optimal_ticks_and_labels(ticks, alims, scale, formatter)
|
||||||
amin, amax = axis_limits(sp, axis[:letter])
|
amin, amax = alims
|
||||||
|
|
||||||
# scale the limits
|
# scale the limits
|
||||||
scale = axis[:scale]
|
|
||||||
sf = RecipesPipeline.scale_func(scale)
|
sf = RecipesPipeline.scale_func(scale)
|
||||||
|
|
||||||
# If the axis input was a Date or DateTime use a special logic to find
|
# 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
|
# rather than on the input format
|
||||||
# TODO: maybe: non-trivial scale (:ln, :log2, :log10) for date/datetime
|
# TODO: maybe: non-trivial scale (:ln, :log2, :log10) for date/datetime
|
||||||
if ticks === nothing && scale == :identity
|
if ticks === nothing && scale == :identity
|
||||||
if axis[:formatter] == RecipesPipeline.dateformatter
|
if formatter == RecipesPipeline.dateformatter
|
||||||
# optimize_datetime_ticks returns ticks and labels(!) based on
|
# optimize_datetime_ticks returns ticks and labels(!) based on
|
||||||
# integers/floats corresponding to the DateTime type. Thus, the axes
|
# integers/floats corresponding to the DateTime type. Thus, the axes
|
||||||
# limits, which resulted from converting the Date type to integers,
|
# 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)
|
k_min = 2, k_max = 4)
|
||||||
# Now the ticks are converted back to floats corresponding to Dates.
|
# Now the ticks are converted back to floats corresponding to Dates.
|
||||||
return ticks / 864e5, labels
|
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)
|
return optimize_datetime_ticks(amin, amax; k_min = 2, k_max = 4)
|
||||||
end
|
end
|
||||||
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:
|
# chosen ticks is not too much bigger than amin - amax:
|
||||||
strict_span = false,
|
strict_span = false,
|
||||||
)
|
)
|
||||||
axis[:lims] = map(RecipesPipeline.inverse_scale_func(scale), (viewmin, viewmax))
|
# axis[:lims] = map(RecipesPipeline.inverse_scale_func(scale), (viewmin, viewmax))
|
||||||
else
|
else
|
||||||
scaled_ticks = map(sf, (filter(t -> amin <= t <= amax, ticks)))
|
scaled_ticks = map(sf, (filter(t -> amin <= t <= amax, ticks)))
|
||||||
end
|
end
|
||||||
unscaled_ticks = map(RecipesPipeline.inverse_scale_func(scale), scaled_ticks)
|
unscaled_ticks = map(RecipesPipeline.inverse_scale_func(scale), scaled_ticks)
|
||||||
|
|
||||||
labels = if any(isfinite, unscaled_ticks)
|
labels = if any(isfinite, unscaled_ticks)
|
||||||
formatter = axis[:formatter]
|
|
||||||
if formatter in (:auto, :plain, :scientific, :engineering)
|
if formatter in (:auto, :plain, :scientific, :engineering)
|
||||||
map(labelfunc(scale, backend()), Showoff.showoff(scaled_ticks, formatter))
|
map(labelfunc(scale, backend()), Showoff.showoff(scaled_ticks, formatter))
|
||||||
elseif formatter == :latex
|
elseif formatter == :latex
|
||||||
@ -221,52 +219,53 @@ end
|
|||||||
# return (continuous_values, discrete_values) for the ticks on this axis
|
# return (continuous_values, discrete_values) for the ticks on this axis
|
||||||
function get_ticks(sp::Subplot, axis::Axis; update = true)
|
function get_ticks(sp::Subplot, axis::Axis; update = true)
|
||||||
if update || !haskey(axis.plotattributes, :optimized_ticks)
|
if update || !haskey(axis.plotattributes, :optimized_ticks)
|
||||||
|
dvals = axis[:discrete_values]
|
||||||
ticks = _transform_ticks(axis[:ticks])
|
ticks = _transform_ticks(axis[:ticks])
|
||||||
if ticks in (:none, nothing, false)
|
axis.plotattributes[:optimized_ticks] = if ticks isa Symbol && ticks !== :none &&
|
||||||
axis.plotattributes[:optimized_ticks] = nothing
|
ispolar(sp) && axis[:letter] === :x && !isempty(dvals)
|
||||||
|
collect(0:pi/4:7pi/4), string.(0:45:315)
|
||||||
else
|
else
|
||||||
# treat :native ticks as :auto
|
cvals = axis[:continuous_values]
|
||||||
ticks = ticks == :native ? :auto : ticks
|
alims = axis_limits(sp, axis[:letter])
|
||||||
|
scale = axis[:scale]
|
||||||
dvals = axis[:discrete_values]
|
formatter = axis[:formatter]
|
||||||
cv, dv = if typeof(ticks) <: Symbol
|
get_ticks(ticks, cvals, dvals, alims, scale, formatter)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
axis.plotattributes[:optimized_ticks]
|
return axis.plotattributes[:optimized_ticks]
|
||||||
end
|
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) = ticks
|
||||||
_transform_ticks(ticks::AbstractArray{T}) where T <: Dates.TimeType = Dates.value.(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])
|
_transform_ticks(ticks::NTuple{2, Any}) = (_transform_ticks(ticks[1]), ticks[2])
|
||||||
|
|||||||
135
src/colorbars.jl
135
src/colorbars.jl
@ -71,139 +71,20 @@ end
|
|||||||
hascolorbar(series::Series) = colorbar_style(series) !== nothing
|
hascolorbar(series::Series) = colorbar_style(series) !== nothing
|
||||||
hascolorbar(sp::Subplot) = sp[:colorbar] != :none && any(hascolorbar(s) for s in series_list(sp))
|
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)
|
function get_colorbar_ticks(sp::Subplot; update = true)
|
||||||
if update || !haskey(sp.attr, :colorbar_optimized_ticks)
|
if update || !haskey(sp.attr, :colorbar_optimized_ticks)
|
||||||
ticks = _transform_ticks(sp[:colorbar_ticks])
|
ticks = _transform_ticks(sp[:colorbar_ticks])
|
||||||
if ticks in (:none, nothing, false)
|
cvals = sp[:colorbar_continuous_values]
|
||||||
sp.attr[:colorbar_optimized_ticks] = nothing
|
dvals = sp[:colorbar_discrete_values]
|
||||||
else
|
clims = get_clims(sp)
|
||||||
# treat :native ticks as :auto
|
scale = sp[:colorbar_scale]
|
||||||
ticks = ticks == :native ? :auto : ticks
|
formatter = sp[:colorbar_formatter]
|
||||||
|
sp.attr[:colorbar_optimized_ticks] =
|
||||||
dvals = sp[:colorbar_discrete_values]
|
get_ticks(ticks, cvals, dvals, clims, scale, formatter)
|
||||||
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
|
|
||||||
end
|
end
|
||||||
sp.attr[:colorbar_optimized_ticks]
|
return sp.attr[:colorbar_optimized_ticks]
|
||||||
end
|
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)
|
function _update_subplot_colorbars(sp::Subplot)
|
||||||
# Dynamic callback from the pipeline if needed
|
# Dynamic callback from the pipeline if needed
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user