colorbar redesign

This commit is contained in:
Zh samsung laptop 2021-03-08 13:28:52 +09:00 committed by Zhanibek
parent 9fc1d574cd
commit 297ab3ef7e
5 changed files with 411 additions and 258 deletions

View File

@ -193,6 +193,7 @@ const _plotly_min_js_filename = "plotly-1.57.1.min.js"
include("types.jl") include("types.jl")
include("utils.jl") include("utils.jl")
include("colorbars.jl")
include("axes.jl") include("axes.jl")
include("args.jl") include("args.jl")
include("components.jl") include("components.jl")

View File

@ -151,54 +151,54 @@ const _shape_keys = Symbol[
const _allMarkers = vcat(:none, :auto, _shape_keys) #sort(collect(keys(_shapes)))) const _allMarkers = vcat(:none, :auto, _shape_keys) #sort(collect(keys(_shapes))))
const _markerAliases = Dict{Symbol,Symbol}( const _markerAliases = Dict{Symbol,Symbol}(
:n => :none, :n => :none,
:no => :none, :no => :none,
:a => :auto, :a => :auto,
:ellipse => :circle, :ellipse => :circle,
:c => :circle, :c => :circle,
:circ => :circle, :circ => :circle,
:square => :rect, :square => :rect,
:sq => :rect, :sq => :rect,
:r => :rect, :r => :rect,
:d => :diamond, :d => :diamond,
:^ => :utriangle, :^ => :utriangle,
:ut => :utriangle, :ut => :utriangle,
:utri => :utriangle, :utri => :utriangle,
:uptri => :utriangle, :uptri => :utriangle,
:uptriangle => :utriangle, :uptriangle => :utriangle,
:v => :dtriangle, :v => :dtriangle,
:V => :dtriangle, :V => :dtriangle,
:dt => :dtriangle, :dt => :dtriangle,
:dtri => :dtriangle, :dtri => :dtriangle,
:downtri => :dtriangle, :downtri => :dtriangle,
:downtriangle => :dtriangle, :downtriangle => :dtriangle,
:> => :rtriangle, :> => :rtriangle,
:rt => :rtriangle, :rt => :rtriangle,
:rtri => :rtriangle, :rtri => :rtriangle,
:righttri => :rtriangle, :righttri => :rtriangle,
:righttriangle => :rtriangle, :righttriangle => :rtriangle,
:< => :ltriangle, :< => :ltriangle,
:lt => :ltriangle, :lt => :ltriangle,
:ltri => :ltriangle, :ltri => :ltriangle,
:lighttri => :ltriangle, :lighttri => :ltriangle,
:lighttriangle => :ltriangle, :lighttriangle => :ltriangle,
# :+ => :cross, # :+ => :cross,
:plus => :cross, :plus => :cross,
# :x => :xcross, # :x => :xcross,
:X => :xcross, :X => :xcross,
:star => :star5, :star => :star5,
:s => :star5, :s => :star5,
:star1 => :star5, :star1 => :star5,
:s2 => :star8, :s2 => :star8,
:star2 => :star8, :star2 => :star8,
:p => :pentagon, :p => :pentagon,
:pent => :pentagon, :pent => :pentagon,
:h => :hexagon, :h => :hexagon,
:hex => :hexagon, :hex => :hexagon,
:hep => :heptagon, :hep => :heptagon,
:o => :octagon, :o => :octagon,
:oct => :octagon, :oct => :octagon,
:spike => :vline, :spike => :vline,
) )
const _positionAliases = Dict{Symbol,Symbol}( const _positionAliases = Dict{Symbol,Symbol}(
@ -273,58 +273,58 @@ const _bar_width = 0.8
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
const _series_defaults = KW( const _series_defaults = KW(
:label => :auto, :label => :auto,
:colorbar_entry => true, :colorbar_entry => true,
:seriescolor => :auto, :seriescolor => :auto,
:seriesalpha => nothing, :seriesalpha => nothing,
:seriestype => :path, :seriestype => :path,
:linestyle => :solid, :linestyle => :solid,
:linewidth => :auto, :linewidth => :auto,
:linecolor => :auto, :linecolor => :auto,
:linealpha => nothing, :linealpha => nothing,
:fillrange => nothing, # ribbons, areas, etc :fillrange => nothing, # ribbons, areas, etc
:fillcolor => :match, :fillcolor => :match,
:fillalpha => nothing, :fillalpha => nothing,
:markershape => :none, :markershape => :none,
:markercolor => :match, :markercolor => :match,
:markeralpha => nothing, :markeralpha => nothing,
:markersize => 4, :markersize => 4,
:markerstrokestyle => :solid, :markerstrokestyle => :solid,
:markerstrokewidth => 1, :markerstrokewidth => 1,
:markerstrokecolor => :match, :markerstrokecolor => :match,
:markerstrokealpha => nothing, :markerstrokealpha => nothing,
:bins => :auto, # number of bins for hists :bins => :auto, # number of bins for hists
:smooth => false, # regression line? :smooth => false, # regression line?
:group => nothing, # groupby vector :group => nothing, # groupby vector
:x => nothing, :x => nothing,
:y => nothing, :y => nothing,
:z => nothing, # depth for contour, surface, etc :z => nothing, # depth for contour, surface, etc
:marker_z => nothing, # value for color scale :marker_z => nothing, # value for color scale
:line_z => nothing, :line_z => nothing,
:fill_z => nothing, :fill_z => nothing,
:levels => 15, :levels => 15,
:orientation => :vertical, :orientation => :vertical,
:bar_position => :overlay, # for bar plots and histograms: could also be stack (stack up) or dodge (side by side) :bar_position => :overlay, # for bar plots and histograms: could also be stack (stack up) or dodge (side by side)
:bar_width => nothing, :bar_width => nothing,
:bar_edges => false, :bar_edges => false,
:xerror => nothing, :xerror => nothing,
:yerror => nothing, :yerror => nothing,
:zerror => nothing, :zerror => nothing,
:ribbon => nothing, :ribbon => nothing,
:quiver => nothing, :quiver => nothing,
:arrow => nothing, # allows for adding arrows to line/path... call `arrow(args...)` :arrow => nothing, # allows for adding arrows to line/path... call `arrow(args...)`
:normalize => false, # do we want a normalized histogram? :normalize => false, # do we want a normalized histogram?
:weights => nothing, # optional weights for histograms (1D and 2D) :weights => nothing, # optional weights for histograms (1D and 2D)
:show_empty_bins => false, # should empty bins in 2D histogram be colored as zero (otherwise they are transparent) :show_empty_bins => false, # should empty bins in 2D histogram be colored as zero (otherwise they are transparent)
:contours => false, # add contours to 3d surface and wireframe plots :contours => false, # add contours to 3d surface and wireframe plots
:contour_labels => false, :contour_labels => false,
:subplot => :auto, # which subplot(s) does this series belong to? :subplot => :auto, # which subplot(s) does this series belong to?
:series_annotations => nothing, # a list of annotations which apply to the coordinates of this series :series_annotations => nothing, # a list of annotations which apply to the coordinates of this series
:primary => true, # when true, this "counts" as a series for color selection, etc. the main use is to allow :primary => true, # when true, this "counts" as a series for color selection, etc. the main use is to allow
# one logical series to be broken up (path and markers, for example) # one logical series to be broken up (path and markers, for example)
:hover => nothing, # text to display when hovering over the data points :hover => nothing, # text to display when hovering over the data points
:stride => (1,1), # array stride for wireframe/surface, the first element is the row stride and the second is the column stride. :stride => (1,1), # array stride for wireframe/surface, the first element is the row stride and the second is the column stride.
:connections => nothing, # tuple of arrays to specifiy connectivity of a 3d mesh :connections => nothing, # tuple of arrays to specifiy connectivity of a 3d mesh
:extra_kwargs => Dict() :extra_kwargs => Dict()
) )
@ -363,104 +363,115 @@ const _plot_defaults = KW(
const _subplot_defaults = KW( const _subplot_defaults = KW(
:title => "", :title => "",
:titlelocation => :center, # also :left or :right :titlelocation => :center, # also :left or :right
:fontfamily_subplot => :match, :fontfamily_subplot => :match,
:titlefontfamily => :match, :titlefontfamily => :match,
:titlefontsize => 14, :titlefontsize => 14,
:titlefonthalign => :hcenter, :titlefonthalign => :hcenter,
:titlefontvalign => :vcenter, :titlefontvalign => :vcenter,
:titlefontrotation => 0.0, :titlefontrotation => 0.0,
:titlefontcolor => :match, :titlefontcolor => :match,
:background_color_subplot => :match, # default for other bg colors... match takes plot default :background_color_subplot => :match, # default for other bg colors... match takes plot default
:background_color_legend => :match, # background of legend :background_color_legend => :match, # background of legend
:background_color_inside => :match, # background inside grid :background_color_inside => :match, # background inside grid
:foreground_color_subplot => :match, # default for other fg colors... match takes plot default :foreground_color_subplot => :match, # default for other fg colors... match takes plot default
:foreground_color_legend => :match, # foreground of legend :foreground_color_legend => :match, # foreground of legend
:foreground_color_title => :match, # title color :foreground_color_title => :match, # title color
:color_palette => :auto, :color_palette => :auto,
:legend => :best, :legend => :best,
:legendtitle => nothing, :legendtitle => nothing,
:colorbar => :legend, :colorbar => :legend,
:clims => :auto, :clims => :auto,
:legendfontfamily => :match, :colorbar_ticks => :auto,
:legendfontsize => 8, :colorbar_tickfontfamily => :match,
:legendfonthalign => :hcenter, :colorbar_tickfontsize => 8,
:legendfontvalign => :vcenter, :colorbar_tickfonthalign => :hcenter,
:legendfontrotation => 0.0, :colorbar_tickfontvalign => :vcenter,
:legendfontcolor => :match, :colorbar_tickfontrotation => 0.0,
:legendtitlefontfamily => :match, :colorbar_tickfontcolor => :match,
:legendtitlefontsize => 11, :colorbar_scale => :identity,
:legendtitlefonthalign => :hcenter, :colorbar_formatter => :auto,
:legendtitlefontvalign => :vcenter, :colorbar_discrete_values => [],
:legendtitlefontrotation => 0.0, :colorbar_continuous_values => zeros(0),
:legendtitlefontcolor => :match, :legendfontfamily => :match,
:annotations => [], # annotation tuples... list of (x,y,annotation) :legendfontsize => 8,
:projection => :none, # can also be :polar or :3d :legendfonthalign => :hcenter,
:aspect_ratio => :auto, # choose from :none or :equal :legendfontvalign => :vcenter,
:margin => 1mm, :legendfontrotation => 0.0,
:left_margin => :match, :legendfontcolor => :match,
:top_margin => :match, :legendtitlefontfamily => :match,
:right_margin => :match, :legendtitlefontsize => 11,
:bottom_margin => :match, :legendtitlefonthalign => :hcenter,
:subplot_index => -1, :legendtitlefontvalign => :vcenter,
:colorbar_title => "", :legendtitlefontrotation => 0.0,
:colorbar_titlefontsize => 10, :legendtitlefontcolor => :match,
:colorbar_title_location => :center, # also :left or :right :annotations => [], # annotation tuples... list of (x,y,annotation)
:colorbar_fontfamily => :match, :projection => :none, # can also be :polar or :3d
:colorbar_titlefontfamily => :match, :aspect_ratio => :auto, # choose from :none or :equal
:colorbar_titlefonthalign => :hcenter, :margin => 1mm,
:colorbar_titlefontvalign => :vcenter, :left_margin => :match,
:colorbar_titlefontrotation => 0.0, :top_margin => :match,
:colorbar_titlefontcolor => :match, :right_margin => :match,
:framestyle => :axes, :bottom_margin => :match,
:camera => (30,30), :subplot_index => -1,
:extra_kwargs => Dict() :colorbar_title => "",
:colorbar_titlefontsize => 10,
:colorbar_title_location => :center, # also :left or :right
:colorbar_fontfamily => :match,
:colorbar_titlefontfamily => :match,
:colorbar_titlefonthalign => :hcenter,
:colorbar_titlefontvalign => :vcenter,
:colorbar_titlefontrotation => 0.0,
:colorbar_titlefontcolor => :match,
:framestyle => :axes,
:camera => (30,30),
:extra_kwargs => Dict()
) )
const _axis_defaults = KW( const _axis_defaults = KW(
:guide => "", :guide => "",
:guide_position => :auto, :guide_position => :auto,
:lims => :auto, :lims => :auto,
:ticks => :auto, :ticks => :auto,
:scale => :identity, :scale => :identity,
:rotation => 0, :rotation => 0,
:flip => false, :flip => false,
:link => [], :link => [],
:tickfontfamily => :match, :tickfontfamily => :match,
:tickfontsize => 8, :tickfontsize => 8,
:tickfonthalign => :hcenter, :tickfonthalign => :hcenter,
:tickfontvalign => :vcenter, :tickfontvalign => :vcenter,
:tickfontrotation => 0.0, :tickfontrotation => 0.0,
:tickfontcolor => :match, :tickfontcolor => :match,
:guidefontfamily => :match, :guidefontfamily => :match,
:guidefontsize => 11, :guidefontsize => 11,
:guidefonthalign => :hcenter, :guidefonthalign => :hcenter,
:guidefontvalign => :vcenter, :guidefontvalign => :vcenter,
:guidefontrotation => 0.0, :guidefontrotation => 0.0,
:guidefontcolor => :match, :guidefontcolor => :match,
:foreground_color_axis => :match, # axis border/tick colors, :foreground_color_axis => :match, # axis border/tick colors,
:foreground_color_border => :match, # plot area border/spines, :foreground_color_border => :match, # plot area border/spines,
:foreground_color_text => :match, # tick text color, :foreground_color_text => :match, # tick text color,
:foreground_color_guide => :match, # guide text color, :foreground_color_guide => :match, # guide text color,
:discrete_values => [], :discrete_values => [],
:formatter => :auto, :formatter => :auto,
:mirror => false, :mirror => false,
:grid => true, :grid => true,
:foreground_color_grid => :match, # grid color :foreground_color_grid => :match, # grid color
:gridalpha => 0.1, :gridalpha => 0.1,
:gridstyle => :solid, :gridstyle => :solid,
:gridlinewidth => 0.5, :gridlinewidth => 0.5,
:foreground_color_minor_grid => :match, # grid color :foreground_color_minor_grid => :match, # grid color
:minorgridalpha => 0.05, :minorgridalpha => 0.05,
:minorgridstyle => :solid, :minorgridstyle => :solid,
:minorgridlinewidth => 0.5, :minorgridlinewidth => 0.5,
:tick_direction => :in, :tick_direction => :in,
:minorticks => false, :minorticks => false,
:minorgrid => false, :minorgrid => false,
:showaxis => true, :showaxis => true,
:widen => true, :widen => true,
:draw_arrow => false, :draw_arrow => false,
) )
const _suppress_warnings = Set{Symbol}([ const _suppress_warnings = Set{Symbol}([
@ -1580,6 +1591,7 @@ function _update_subplot_args(plt::Plot, sp::Subplot, plotattributes_in, subplot
lims_warned = true lims_warned = true
end end
end end
_update_subplot_colorbars(sp)
end end
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------

View File

@ -531,6 +531,10 @@ const _pyplot_attr = merge_with_base_supported([
:guidefontfamily, :guidefontsize, :guidefontcolor, :guidefontfamily, :guidefontsize, :guidefontcolor,
:grid, :gridalpha, :gridstyle, :gridlinewidth, :grid, :gridalpha, :gridstyle, :gridlinewidth,
:legend, :legendtitle, :colorbar, :colorbar_title, :colorbar_entry, :legend, :legendtitle, :colorbar, :colorbar_title, :colorbar_entry,
:colorbar_ticks, :colorbar_tickfontfamily, :colorbar_tickfontsize,
:colorbar_tickfonthalign, :colorbar_tickfontvalign,
:colorbar_tickfontrotation, :colorbar_tickfontcolor,
:colorbar_scale,
:marker_z, :line_z, :fill_z, :marker_z, :line_z, :fill_z,
:levels, :levels,
:ribbon, :quiver, :arrow, :ribbon, :quiver, :arrow,

209
src/colorbars.jl Normal file
View File

@ -0,0 +1,209 @@
# 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

View File

@ -428,79 +428,6 @@ xlims(sp_idx::Int = 1) = xlims(current(), sp_idx)
ylims(sp_idx::Int = 1) = ylims(current(), sp_idx) ylims(sp_idx::Int = 1) = ylims(current(), sp_idx)
zlims(sp_idx::Int = 1) = zlims(current(), sp_idx) zlims(sp_idx::Int = 1) = zlims(current(), sp_idx)
# 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))
iscontour(series::Series) = series[:seriestype] in (:contour, :contour3d) iscontour(series::Series) = series[:seriestype] in (:contour, :contour3d)
isfilledcontour(series::Series) = iscontour(series) && series[:fillrange] !== nothing isfilledcontour(series::Series) = iscontour(series) && series[:fillrange] !== nothing