From a4fbbc64cfe1adaa3a7e406e0e4fd00d7c657b24 Mon Sep 17 00:00:00 2001 From: Zh samsung laptop Date: Mon, 8 Mar 2021 13:28:52 +0900 Subject: [PATCH] colorbar redesign --- src/Plots.jl | 1 + src/args.jl | 382 ++++++++++++++++++++++++----------------------- src/backends.jl | 4 + src/colorbars.jl | 209 ++++++++++++++++++++++++++ src/utils.jl | 73 --------- 5 files changed, 411 insertions(+), 258 deletions(-) create mode 100644 src/colorbars.jl diff --git a/src/Plots.jl b/src/Plots.jl index 5378fb73..e7a71878 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -193,6 +193,7 @@ const _plotly_min_js_filename = "plotly-1.57.1.min.js" include("types.jl") include("utils.jl") +include("colorbars.jl") include("axes.jl") include("args.jl") include("components.jl") diff --git a/src/args.jl b/src/args.jl index 3a3c963c..262a2a26 100644 --- a/src/args.jl +++ b/src/args.jl @@ -151,54 +151,54 @@ const _shape_keys = Symbol[ const _allMarkers = vcat(:none, :auto, _shape_keys) #sort(collect(keys(_shapes)))) const _markerAliases = Dict{Symbol,Symbol}( - :n => :none, - :no => :none, - :a => :auto, - :ellipse => :circle, - :c => :circle, - :circ => :circle, - :square => :rect, - :sq => :rect, - :r => :rect, - :d => :diamond, - :^ => :utriangle, - :ut => :utriangle, - :utri => :utriangle, - :uptri => :utriangle, - :uptriangle => :utriangle, - :v => :dtriangle, - :V => :dtriangle, - :dt => :dtriangle, - :dtri => :dtriangle, - :downtri => :dtriangle, - :downtriangle => :dtriangle, - :> => :rtriangle, - :rt => :rtriangle, - :rtri => :rtriangle, + :n => :none, + :no => :none, + :a => :auto, + :ellipse => :circle, + :c => :circle, + :circ => :circle, + :square => :rect, + :sq => :rect, + :r => :rect, + :d => :diamond, + :^ => :utriangle, + :ut => :utriangle, + :utri => :utriangle, + :uptri => :utriangle, + :uptriangle => :utriangle, + :v => :dtriangle, + :V => :dtriangle, + :dt => :dtriangle, + :dtri => :dtriangle, + :downtri => :dtriangle, + :downtriangle => :dtriangle, + :> => :rtriangle, + :rt => :rtriangle, + :rtri => :rtriangle, :righttri => :rtriangle, :righttriangle => :rtriangle, - :< => :ltriangle, - :lt => :ltriangle, - :ltri => :ltriangle, + :< => :ltriangle, + :lt => :ltriangle, + :ltri => :ltriangle, :lighttri => :ltriangle, :lighttriangle => :ltriangle, - # :+ => :cross, - :plus => :cross, - # :x => :xcross, - :X => :xcross, - :star => :star5, - :s => :star5, - :star1 => :star5, - :s2 => :star8, - :star2 => :star8, - :p => :pentagon, - :pent => :pentagon, - :h => :hexagon, - :hex => :hexagon, - :hep => :heptagon, - :o => :octagon, - :oct => :octagon, - :spike => :vline, + # :+ => :cross, + :plus => :cross, + # :x => :xcross, + :X => :xcross, + :star => :star5, + :s => :star5, + :star1 => :star5, + :s2 => :star8, + :star2 => :star8, + :p => :pentagon, + :pent => :pentagon, + :h => :hexagon, + :hex => :hexagon, + :hep => :heptagon, + :o => :octagon, + :oct => :octagon, + :spike => :vline, ) const _positionAliases = Dict{Symbol,Symbol}( @@ -273,58 +273,58 @@ const _bar_width = 0.8 # ----------------------------------------------------------------------------- const _series_defaults = KW( - :label => :auto, - :colorbar_entry => true, - :seriescolor => :auto, - :seriesalpha => nothing, - :seriestype => :path, - :linestyle => :solid, - :linewidth => :auto, - :linecolor => :auto, - :linealpha => nothing, - :fillrange => nothing, # ribbons, areas, etc - :fillcolor => :match, - :fillalpha => nothing, - :markershape => :none, - :markercolor => :match, - :markeralpha => nothing, - :markersize => 4, - :markerstrokestyle => :solid, - :markerstrokewidth => 1, - :markerstrokecolor => :match, - :markerstrokealpha => nothing, - :bins => :auto, # number of bins for hists - :smooth => false, # regression line? - :group => nothing, # groupby vector - :x => nothing, - :y => nothing, - :z => nothing, # depth for contour, surface, etc - :marker_z => nothing, # value for color scale - :line_z => nothing, - :fill_z => nothing, - :levels => 15, - :orientation => :vertical, - :bar_position => :overlay, # for bar plots and histograms: could also be stack (stack up) or dodge (side by side) - :bar_width => nothing, - :bar_edges => false, - :xerror => nothing, - :yerror => nothing, - :zerror => nothing, - :ribbon => nothing, - :quiver => nothing, - :arrow => nothing, # allows for adding arrows to line/path... call `arrow(args...)` - :normalize => false, # do we want a normalized histogram? - :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) - :contours => false, # add contours to 3d surface and wireframe plots - :contour_labels => false, - :subplot => :auto, # which subplot(s) does this series belong to? + :label => :auto, + :colorbar_entry => true, + :seriescolor => :auto, + :seriesalpha => nothing, + :seriestype => :path, + :linestyle => :solid, + :linewidth => :auto, + :linecolor => :auto, + :linealpha => nothing, + :fillrange => nothing, # ribbons, areas, etc + :fillcolor => :match, + :fillalpha => nothing, + :markershape => :none, + :markercolor => :match, + :markeralpha => nothing, + :markersize => 4, + :markerstrokestyle => :solid, + :markerstrokewidth => 1, + :markerstrokecolor => :match, + :markerstrokealpha => nothing, + :bins => :auto, # number of bins for hists + :smooth => false, # regression line? + :group => nothing, # groupby vector + :x => nothing, + :y => nothing, + :z => nothing, # depth for contour, surface, etc + :marker_z => nothing, # value for color scale + :line_z => nothing, + :fill_z => nothing, + :levels => 15, + :orientation => :vertical, + :bar_position => :overlay, # for bar plots and histograms: could also be stack (stack up) or dodge (side by side) + :bar_width => nothing, + :bar_edges => false, + :xerror => nothing, + :yerror => nothing, + :zerror => nothing, + :ribbon => nothing, + :quiver => nothing, + :arrow => nothing, # allows for adding arrows to line/path... call `arrow(args...)` + :normalize => false, # do we want a normalized histogram? + :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) + :contours => false, # add contours to 3d surface and wireframe plots + :contour_labels => false, + :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 :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) :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. - :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() ) @@ -363,104 +363,115 @@ const _plot_defaults = KW( const _subplot_defaults = KW( - :title => "", - :titlelocation => :center, # also :left or :right - :fontfamily_subplot => :match, - :titlefontfamily => :match, - :titlefontsize => 14, - :titlefonthalign => :hcenter, - :titlefontvalign => :vcenter, - :titlefontrotation => 0.0, - :titlefontcolor => :match, - :background_color_subplot => :match, # default for other bg colors... match takes plot default - :background_color_legend => :match, # background of legend - :background_color_inside => :match, # background inside grid - :foreground_color_subplot => :match, # default for other fg colors... match takes plot default - :foreground_color_legend => :match, # foreground of legend - :foreground_color_title => :match, # title color - :color_palette => :auto, - :legend => :best, - :legendtitle => nothing, - :colorbar => :legend, - :clims => :auto, - :legendfontfamily => :match, - :legendfontsize => 8, - :legendfonthalign => :hcenter, - :legendfontvalign => :vcenter, - :legendfontrotation => 0.0, - :legendfontcolor => :match, - :legendtitlefontfamily => :match, - :legendtitlefontsize => 11, - :legendtitlefonthalign => :hcenter, - :legendtitlefontvalign => :vcenter, - :legendtitlefontrotation => 0.0, - :legendtitlefontcolor => :match, - :annotations => [], # annotation tuples... list of (x,y,annotation) - :projection => :none, # can also be :polar or :3d - :aspect_ratio => :auto, # choose from :none or :equal - :margin => 1mm, - :left_margin => :match, - :top_margin => :match, - :right_margin => :match, - :bottom_margin => :match, - :subplot_index => -1, - :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() + :title => "", + :titlelocation => :center, # also :left or :right + :fontfamily_subplot => :match, + :titlefontfamily => :match, + :titlefontsize => 14, + :titlefonthalign => :hcenter, + :titlefontvalign => :vcenter, + :titlefontrotation => 0.0, + :titlefontcolor => :match, + :background_color_subplot => :match, # default for other bg colors... match takes plot default + :background_color_legend => :match, # background of legend + :background_color_inside => :match, # background inside grid + :foreground_color_subplot => :match, # default for other fg colors... match takes plot default + :foreground_color_legend => :match, # foreground of legend + :foreground_color_title => :match, # title color + :color_palette => :auto, + :legend => :best, + :legendtitle => nothing, + :colorbar => :legend, + :clims => :auto, + :colorbar_ticks => :auto, + :colorbar_tickfontfamily => :match, + :colorbar_tickfontsize => 8, + :colorbar_tickfonthalign => :hcenter, + :colorbar_tickfontvalign => :vcenter, + :colorbar_tickfontrotation => 0.0, + :colorbar_tickfontcolor => :match, + :colorbar_scale => :identity, + :colorbar_formatter => :auto, + :colorbar_discrete_values => [], + :colorbar_continuous_values => zeros(0), + :legendfontfamily => :match, + :legendfontsize => 8, + :legendfonthalign => :hcenter, + :legendfontvalign => :vcenter, + :legendfontrotation => 0.0, + :legendfontcolor => :match, + :legendtitlefontfamily => :match, + :legendtitlefontsize => 11, + :legendtitlefonthalign => :hcenter, + :legendtitlefontvalign => :vcenter, + :legendtitlefontrotation => 0.0, + :legendtitlefontcolor => :match, + :annotations => [], # annotation tuples... list of (x,y,annotation) + :projection => :none, # can also be :polar or :3d + :aspect_ratio => :auto, # choose from :none or :equal + :margin => 1mm, + :left_margin => :match, + :top_margin => :match, + :right_margin => :match, + :bottom_margin => :match, + :subplot_index => -1, + :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( - :guide => "", - :guide_position => :auto, - :lims => :auto, - :ticks => :auto, - :scale => :identity, - :rotation => 0, - :flip => false, - :link => [], - :tickfontfamily => :match, - :tickfontsize => 8, - :tickfonthalign => :hcenter, - :tickfontvalign => :vcenter, - :tickfontrotation => 0.0, - :tickfontcolor => :match, - :guidefontfamily => :match, - :guidefontsize => 11, - :guidefonthalign => :hcenter, - :guidefontvalign => :vcenter, - :guidefontrotation => 0.0, - :guidefontcolor => :match, - :foreground_color_axis => :match, # axis border/tick colors, - :foreground_color_border => :match, # plot area border/spines, - :foreground_color_text => :match, # tick text color, - :foreground_color_guide => :match, # guide text color, - :discrete_values => [], - :formatter => :auto, - :mirror => false, - :grid => true, - :foreground_color_grid => :match, # grid color - :gridalpha => 0.1, - :gridstyle => :solid, - :gridlinewidth => 0.5, + :guide => "", + :guide_position => :auto, + :lims => :auto, + :ticks => :auto, + :scale => :identity, + :rotation => 0, + :flip => false, + :link => [], + :tickfontfamily => :match, + :tickfontsize => 8, + :tickfonthalign => :hcenter, + :tickfontvalign => :vcenter, + :tickfontrotation => 0.0, + :tickfontcolor => :match, + :guidefontfamily => :match, + :guidefontsize => 11, + :guidefonthalign => :hcenter, + :guidefontvalign => :vcenter, + :guidefontrotation => 0.0, + :guidefontcolor => :match, + :foreground_color_axis => :match, # axis border/tick colors, + :foreground_color_border => :match, # plot area border/spines, + :foreground_color_text => :match, # tick text color, + :foreground_color_guide => :match, # guide text color, + :discrete_values => [], + :formatter => :auto, + :mirror => false, + :grid => true, + :foreground_color_grid => :match, # grid color + :gridalpha => 0.1, + :gridstyle => :solid, + :gridlinewidth => 0.5, :foreground_color_minor_grid => :match, # grid color - :minorgridalpha => 0.05, - :minorgridstyle => :solid, - :minorgridlinewidth => 0.5, - :tick_direction => :in, - :minorticks => false, - :minorgrid => false, - :showaxis => true, - :widen => true, - :draw_arrow => false, + :minorgridalpha => 0.05, + :minorgridstyle => :solid, + :minorgridlinewidth => 0.5, + :tick_direction => :in, + :minorticks => false, + :minorgrid => false, + :showaxis => true, + :widen => true, + :draw_arrow => false, ) const _suppress_warnings = Set{Symbol}([ @@ -1580,6 +1591,7 @@ function _update_subplot_args(plt::Plot, sp::Subplot, plotattributes_in, subplot lims_warned = true end end + _update_subplot_colorbars(sp) end # ----------------------------------------------------------------------------- diff --git a/src/backends.jl b/src/backends.jl index 6164441d..c6c54f22 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -531,6 +531,10 @@ const _pyplot_attr = merge_with_base_supported([ :guidefontfamily, :guidefontsize, :guidefontcolor, :grid, :gridalpha, :gridstyle, :gridlinewidth, :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, :levels, :ribbon, :quiver, :arrow, diff --git a/src/colorbars.jl b/src/colorbars.jl new file mode 100644 index 00000000..14e839ed --- /dev/null +++ b/src/colorbars.jl @@ -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 diff --git a/src/utils.jl b/src/utils.jl index a23a47b3..36527159 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -428,79 +428,6 @@ xlims(sp_idx::Int = 1) = xlims(current(), sp_idx) ylims(sp_idx::Int = 1) = ylims(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) isfilledcontour(series::Series) = iscontour(series) && series[:fillrange] !== nothing