210 lines
8.5 KiB
Julia
210 lines
8.5 KiB
Julia
# These functions return an operator for use in `get_clims(::Seres, op)`
|
||
process_clims(lims::Tuple{<:Number,<:Number}) = (zlims -> ifelse.(isfinite.(lims), lims, zlims)) ∘ ignorenan_extrema
|
||
process_clims(s::Union{Symbol,Nothing,Missing}) = ignorenan_extrema
|
||
# don't specialize on ::Function otherwise python functions won't work
|
||
process_clims(f) = f
|
||
|
||
function get_clims(sp::Subplot, op=process_clims(sp[:clims]))
|
||
zmin, zmax = Inf, -Inf
|
||
for series in series_list(sp)
|
||
if series[:colorbar_entry]
|
||
zmin, zmax = _update_clims(zmin, zmax, get_clims(series, op)...)
|
||
end
|
||
end
|
||
return zmin <= zmax ? (zmin, zmax) : (NaN, NaN)
|
||
end
|
||
|
||
function get_clims(sp::Subplot, series::Series, op=process_clims(sp[:clims]))
|
||
zmin, zmax = if series[:colorbar_entry]
|
||
get_clims(sp, op)
|
||
else
|
||
get_clims(series, op)
|
||
end
|
||
return zmin <= zmax ? (zmin, zmax) : (NaN, NaN)
|
||
end
|
||
|
||
"""
|
||
get_clims(::Series, op=Plots.ignorenan_extrema)
|
||
|
||
Finds the limits for the colorbar by taking the "z-values" for the series and passing them into `op`,
|
||
which must return the tuple `(zmin, zmax)`. The default op is the extrema of the finite
|
||
values of the input.
|
||
"""
|
||
function get_clims(series::Series, op=ignorenan_extrema)
|
||
zmin, zmax = Inf, -Inf
|
||
z_colored_series = (:contour, :contour3d, :heatmap, :histogram2d, :surface, :hexbin)
|
||
for vals in (series[:seriestype] in z_colored_series ? series[:z] : nothing, series[:line_z], series[:marker_z], series[:fill_z])
|
||
if (typeof(vals) <: AbstractSurface) && (eltype(vals.surf) <: Union{Missing, Real})
|
||
zmin, zmax = _update_clims(zmin, zmax, op(vals.surf)...)
|
||
elseif (vals !== nothing) && (eltype(vals) <: Union{Missing, Real})
|
||
zmin, zmax = _update_clims(zmin, zmax, op(vals)...)
|
||
end
|
||
end
|
||
return zmin <= zmax ? (zmin, zmax) : (NaN, NaN)
|
||
end
|
||
|
||
_update_clims(zmin, zmax, emin, emax) = NaNMath.min(zmin, emin), NaNMath.max(zmax, emax)
|
||
|
||
@enum ColorbarStyle cbar_gradient cbar_fill cbar_lines
|
||
|
||
function colorbar_style(series::Series)
|
||
colorbar_entry = series[:colorbar_entry]
|
||
if !(colorbar_entry isa Bool)
|
||
@warn "Non-boolean colorbar_entry ignored."
|
||
colorbar_entry = true
|
||
end
|
||
|
||
if !colorbar_entry
|
||
nothing
|
||
elseif isfilledcontour(series)
|
||
cbar_fill
|
||
elseif iscontour(series)
|
||
cbar_lines
|
||
elseif series[:seriestype] ∈ (:heatmap,:surface) ||
|
||
any(series[z] !== nothing for z ∈ [:marker_z,:line_z,:fill_z])
|
||
cbar_gradient
|
||
else
|
||
nothing
|
||
end
|
||
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 = ap[: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
|
||
end
|
||
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)
|
||
|
||
end
|