Skip to content

Commit

Permalink
Experimental colorbar limit changes, sampling method
Browse files Browse the repository at this point in the history
  • Loading branch information
BioTurboNick committed Nov 26, 2022
1 parent 452bd9a commit f9fdf4a
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 137 deletions.
2 changes: 1 addition & 1 deletion src/args.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const _allTypes = vcat(
_3dTypes,
)

const _z_colored_series = [:contour, :contour3d, :heatmap, :histogram2d, :surface, :hexbin]
const _z_colored_series = Dict([:contour, :contour3d, :heatmap, :histogram2d, :surface, :hexbin] .=> true)

const _typeAliases = Dict{Symbol,Symbol}(
:n => :none,
Expand Down
96 changes: 62 additions & 34 deletions src/axes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -407,34 +407,60 @@ finitemin(x::Real, y::Real) = min(promote(x, y)...)
finitemax(x::Real, y::Real) = max(promote(x, y)...)

finitemin(x::T, y::T) where {T<:AbstractFloat} = ifelse((y < x) | (signbit(y) > signbit(x)),
ifelse(isinf(y), x, y),
ifelse(isinf(x), y, x))
ifelse(isfinite(y), y, x),
ifelse(isfinite(x), x, y))
finitemax(x::T, y::T) where {T<:AbstractFloat} = ifelse((y > x) | (signbit(y) < signbit(x)),
ifelse(isinf(y), x, y),
ifelse(isinf(x), y, x))
ifelse(isfinite(y), y, x),
ifelse(isfinite(x), x, y))

function finite_extrema(v::AbstractArray)
emin, emax = Inf, -Inf
for x in v
emin = finitemin(emin, x)
emax = finitemax(emax, x)
end
emin, emax
end

finite_extrema(v) = extrema(v)

finite_minimum(v::AbstractArray) = reduce(finitemin, v)
finite_maximum(v::AbstractArray) = reduce(finitemax, v)

function expand_extrema!(ex::Extrema, v::Number)
ex.emin = finitemin(v, ex.emin)
ex.emax = finitemax(v, ex.emax)
ex
end

function expand_extrema!(ex::Extrema, v::Extrema)
ex.emin = finitemin(v.emin, ex.emin)
ex.emax = finitemax(v.emax, ex.emax)
ex
end

expand_extrema!(axis::Axis, v::Number) = expand_extrema!(axis[:extrema], v)

# these shouldn't impact the extrema
expand_extrema!(axis::Axis, ::Nothing) = axis[:extrema]
expand_extrema!(axis::Axis, ::Bool) = axis[:extrema]

function expand_extrema!(axis::Axis, v::Tuple{MIN,MAX}) where {MIN<:Number,MAX<:Number}
function expand_extrema!(axis::Axis, v::Tuple{<:Number, <:Number})
ex = axis[:extrema]::Extrema
ex.emin = isfinite(v[1]) ? min(v[1], ex.emin) : ex.emin
ex.emax = isfinite(v[2]) ? max(v[2], ex.emax) : ex.emax
ex.emin = finitemin(v[1], ex.emin)
ex.emax = finitemax(v[2], ex.emax)
ex
end
function expand_extrema!(axis::Axis, v::AVec{N}) where {N<:Number}
ex = axis[:extrema]::Extrema
foreach(vi -> expand_extrema!(ex, vi), v)
ex
function expand_extrema!(axis::Axis, v::AbstractArray{<:Number})
vex = if length(v) > 1024
vex = finite_extrema(@view v[1:1000])
stride = length(v) ÷ 1024 + 1
vex2 = finite_extrema(@view v[1001:stride:end])
finitemin(vex[1], vex2[1]), finitemax(vex[2], vex2[2])
else
finite_extrema(v)
end
expand_extrema!(axis, vex)
end

function expand_extrema!(sp::Subplot, plotattributes::AKW)
Expand Down Expand Up @@ -478,6 +504,31 @@ function expand_extrema!(sp::Subplot, plotattributes::AKW)
expand_extrema!(axis, plotattributes[letter])
end
end

# expand for bar_width
if plotattributes[:seriestype] === :bar
dsym = vert ? :x : :y
data = plotattributes[dsym]

if (bw = plotattributes[:bar_width]) === nothing
pos = filter(>(0), diff(sort(data)))
plotattributes[:bar_width] = bw = _bar_width * finite_minimum(pos)
end
axis = sp.attr[get_attr_symbol(dsym, :axis)]
ex = finite_extrema(data)
expand_extrema!(axis, ex[1] + 0.5maximum(bw))
expand_extrema!(axis, ex[2] - 0.5minimum(bw))
elseif plotattributes[:seriestype] === :heatmap
for letter in (:x, :y)
data = plotattributes[letter]
axis = sp[get_attr_symbol(letter, :axis)]
scale = get(plotattributes, get_attr_symbol(letter, :scale), :identity)
ex = scale === :identity ?
heatmap_extrema(data) :
heatmap_extrema(data, scale)
expand_extrema!(axis, ex)
end
end

# # expand for fillrange/bar_width
# fillaxis, baraxis = sp.attr[:yaxis], sp.attr[:xaxis]
Expand All @@ -499,29 +550,6 @@ function expand_extrema!(sp::Subplot, plotattributes::AKW)
end
end

# expand for bar_width
if plotattributes[:seriestype] === :bar
dsym = vert ? :x : :y
data = plotattributes[dsym]

if (bw = plotattributes[:bar_width]) === nothing
pos = filter(>(0), diff(sort(data)))
plotattributes[:bar_width] = bw = _bar_width * ignorenan_minimum(pos)
end
axis = sp.attr[get_attr_symbol(dsym, :axis)]
expand_extrema!(axis, ignorenan_maximum(data) + 0.5maximum(bw))
expand_extrema!(axis, ignorenan_minimum(data) - 0.5minimum(bw))
end

# expand for heatmaps
if plotattributes[:seriestype] === :heatmap
for letter in (:x, :y)
data = plotattributes[letter]
axis = sp[get_attr_symbol(letter, :axis)]
scale = get(plotattributes, get_attr_symbol(letter, :scale), :identity)
expand_extrema!(axis, heatmap_extrema(data, scale))
end
end
end

function expand_extrema!(sp::Subplot, xmin, xmax, ymin, ymax)
Expand Down
1 change: 1 addition & 0 deletions src/backends/gaston.jl
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ function gaston_seriesconf!(
extra_curves = String[]

clims = get_clims(sp, series)
clims = clims.emin, clims.emax
if st (:scatter, :scatter3d)
lc, dt, lw = gaston_lc_ls_lw(series, clims, i)
pt, ps, mc = gaston_mk_ms_mc(series, clims, i)
Expand Down
8 changes: 6 additions & 2 deletions src/backends/gr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -898,8 +898,12 @@ remap(x, lo, hi) = (x - lo) / (hi - lo)
get_z_normalized(z, clims...) = isnan(z) ? 256 / 255 : remap(clamp(z, clims...), clims...)

function gr_clims(sp, args...)
sp[:clims] === :auto || return get_clims(sp)
lo, hi = get_clims(sp, args...)
if sp[:clims] !== :auto
lims = get_clims(sp)
return lims.emin, lims.emax
end
lims = get_clims(sp, args...)
lo, hi = lims.emin, lims.emax
if lo == hi
if lo == 0
hi = one(hi)
Expand Down
1 change: 1 addition & 0 deletions src/backends/inspectdr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ function _series_added(plt::Plot{InspectDRBackend}, series::Series)
(plot = sp.o) === nothing && return

clims = get_clims(sp, series)
clims = clims.emin, clims.emax

_vectorize(v) = isa(v, Vector) ? v : collect(v) #InspectDR only supports vectors
x, y = if st === :straightline
Expand Down
4 changes: 2 additions & 2 deletions src/backends/pgfplotsx.jl
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend})
bgc_inside = plot_color(sp[:background_color_inside])
update_clims(sp)
axis_opt = Options(
"point meta max" => get_clims(sp)[2],
"point meta min" => get_clims(sp)[1],
"point meta max" => get_clims(sp).emax,
"point meta min" => get_clims(sp).emin,
"legend cell align" => "left",
"legend columns" => pgfx_legend_col(sp[:legend_column]),
"title" => sp[:title],
Expand Down
4 changes: 3 additions & 1 deletion src/backends/plotly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@ as_gradient(grad, α) = cgrad(alpha = α)
function plotly_series(plt::Plot, series::Series)
sp = series[:subplot]
clims = get_clims(sp, series)
clims = clims.emin, clims.emax

(st = series[:seriestype]) === :shape && return plotly_series_shapes(plt, series, clims)

Expand Down Expand Up @@ -953,7 +954,8 @@ end

function plotly_colorbar_hack(series::Series, plotattributes_base::KW, sym::Symbol)
plotattributes_out = deepcopy(plotattributes_base)
cmin, cmax = get_clims(series[:subplot])
clims = get_clims(series[:subplot])
cmin, cmax = clims.emin, clims.emax
plotattributes_out[:showlegend] = false
plotattributes_out[:type] = RecipesPipeline.is3d(series) ? :scatter3d : :scatter
plotattributes_out[:hoverinfo] = :none
Expand Down
7 changes: 5 additions & 2 deletions src/backends/pyplot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,8 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)

# handle zcolor and get c/cmap
needs_colorbar = hascolorbar(sp)
vmin, vmax = clims = get_clims(sp, series)
clims = get_clims(sp, series)
vmin, vmax = clims = clims.emin, clims.emax

# Dict to store extra kwargs
extrakw = if st === :wireframe || st === :hexbin
Expand Down Expand Up @@ -1011,7 +1012,8 @@ function _before_layout_calcs(plt::Plot{PyPlotBackend})
elseif any(
colorbar_series[attr] !== nothing for attr in (:line_z, :fill_z, :marker_z)
)
cmin, cmax = get_clims(sp)
clims = get_clims(sp)
cmin, cmax = clims.emin, clims.emax
norm = pycolors."Normalize"(vmin = cmin, vmax = cmax)
f = if colorbar_series[:line_z] !== nothing
py_linecolormap
Expand Down Expand Up @@ -1467,6 +1469,7 @@ function py_add_legend(plt::Plot, sp::Subplot, ax)
should_add_to_legend(series) || continue
nseries += 1
clims = get_clims(sp, series)
clims = clims.emin, clims.emax
# add a line/marker and a label
if series[:seriestype] === :shape || series[:fillrange] !== nothing
lc = get_linecolor(series, clims)
Expand Down
139 changes: 65 additions & 74 deletions src/colorbars.jl
Original file line number Diff line number Diff line change
@@ -1,81 +1,14 @@
# 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
(zlims -> ifelse.(isfinite.(lims), lims, zlims)) finite_extrema
process_clims(::Union{Symbol,Nothing,Missing}) = finite_extrema
# don't specialize on ::Function otherwise python functions won't work
process_clims(f) = f

get_clims(sp::Subplot)::Tuple{Float64,Float64} =
haskey(sp.attr, :clims_calculated) ? sp[:clims_calculated] : update_clims(sp)
get_clims(series::Series)::Tuple{Float64,Float64} =
haskey(series.plotattributes, :clims_calculated) ?
series[:clims_calculated]::Tuple{Float64,Float64} : update_clims(series)
get_clims(sp::Subplot, series::Series)::Tuple{Float64,Float64} =
get_clims(sp::Subplot) = sp.color_extrema
get_clims(series::Series) = series.color_extrema
get_clims(sp::Subplot, series::Series) =
series[:colorbar_entry] ? get_clims(sp) : get_clims(series)

function update_clims(sp::Subplot, op = process_clims(sp[:clims]))::Tuple{Float64,Float64}
zmin, zmax = Inf, -Inf
for series in series_list(sp)
if series[:colorbar_entry]::Bool
zmin, zmax = _update_clims(zmin, zmax, update_clims(series, op)...)
else
update_clims(series, op)
end
end
return sp[:clims_calculated] = zmin <= zmax ? (zmin, zmax) : (NaN, NaN)
end

function update_clims(
sp::Subplot,
series::Series,
op = process_clims(sp[:clims]),
)::Tuple{Float64,Float64}
zmin, zmax = get_clims(sp)
old_zmin, old_zmax = zmin, zmax
if series[:colorbar_entry]::Bool
zmin, zmax = _update_clims(zmin, zmax, update_clims(series, op)...)
else
update_clims(series, op)
end
isnan(zmin) && isnan(old_zmin) && isnan(zmax) && isnan(old_zmax) ||
zmin == old_zmin && zmax == old_zmax ||
update_clims(sp)
return zmin <= zmax ? (zmin, zmax) : (NaN, NaN)
end

"""
update_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. The value is stored as a series property, which is retrieved by `get_clims`.
"""
function update_clims(series::Series, op = ignorenan_extrema)::Tuple{Float64,Float64}
zmin, zmax = Inf, -Inf

# keeping this unrolled has higher performance
if series[:seriestype] _z_colored_series && series[:z] !== nothing
zmin, zmax = update_clims(zmin, zmax, series[:z], op)
end
if series[:line_z] !== nothing
zmin, zmax = update_clims(zmin, zmax, series[:line_z], op)
end
if series[:marker_z] !== nothing
zmin, zmax = update_clims(zmin, zmax, series[:marker_z], op)
end
if series[:fill_z] !== nothing
zmin, zmax = update_clims(zmin, zmax, series[:fill_z], op)
end
return series[:clims_calculated] = zmin <= zmax ? (zmin, zmax) : (NaN, NaN)
end

update_clims(zmin, zmax, vals::AbstractSurface, op)::Tuple{Float64,Float64} =
update_clims(zmin, zmax, vals.surf, op)
update_clims(zmin, zmax, vals::Any, op)::Tuple{Float64,Float64} =
_update_clims(zmin, zmax, op(vals)...)
update_clims(zmin, zmax, ::Nothing, ::Any)::Tuple{Float64,Float64} = zmin, zmax

_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)
Expand Down Expand Up @@ -109,6 +42,7 @@ function get_colorbar_ticks(sp::Subplot; update = true, formatter = sp[:colorbar
cvals = sp[:colorbar_continuous_values]
dvals = sp[:colorbar_discrete_values]
clims = get_clims(sp)
clims = clims.emin, clims.emax
scale = sp[:colorbar_scale]
sp.attr[:colorbar_optimized_ticks] =
get_ticks(ticks, cvals, dvals, clims, scale, formatter)
Expand All @@ -117,5 +51,62 @@ function get_colorbar_ticks(sp::Subplot; update = true, formatter = sp[:colorbar
end

# Dynamic callback from the pipeline if needed
_update_subplot_colorbars(sp::Subplot) = update_clims(sp)
_update_subplot_colorbars(sp::Subplot, series::Series) = update_clims(sp, series)
function _update_subplot_colorbar_extrema(sp::Subplot, series::Series, op = process_clims(sp[:clims]))
ex = sp.color_extrema
old_emin = ex.emin
old_emax = ex.emax
seriesex = expand_colorbar_extrema!(series, op)
if series[:colorbar_entry]::Bool
expand_colorbar_extrema!(sp, (seriesex.emin, seriesex.emax))
end
if ex.emin != old_emin || ex.emax != old_emax
# expanded, need to update other series
for s in series_list(sp)
s.color_extrema = ex
end
end
nothing
end

function expand_colorbar_extrema!(series::Series, op)
if haskey(_z_colored_series, series[:seriestype]) && series[:z] !== nothing
expand_colorbar_extrema!(series, series[:z], op)
end
expand_colorbar_extrema!(series, series[:line_z], op)
expand_colorbar_extrema!(series, series[:marker_z], op)
expand_colorbar_extrema!(series, series[:fill_z], op)
end

function expand_colorbar_extrema!(series::Series, v::AbstractArray{<:Number}, op)
vex = if length(v) > 1024
vex = op(@view v[1:1000])
stride = length(v) ÷ 1024 + 1
vex2 = op(@view v[1001:stride:end])
finitemin(vex[1], vex2[1]), finitemax(vex[2], vex2[2])
else
op(v)
end
expand_colorbar_extrema!(series, vex)
end

expand_colorbar_extrema!(series::Series, ::Nothing, ::Any) = series.color_extrema

function expand_colorbar_extrema!(series::Series, v::Tuple{<:Number, <:Number})
ex = series.color_extrema
ex.emin = finitemin(v[1], ex.emin)
ex.emax = finitemax(v[2], ex.emax)
ex
end

function expand_colorbar_extrema!(sp::Subplot, v::Tuple{<:Number, <:Number})
ex = sp.color_extrema
ex.emin = finitemin(v[1], ex.emin)
ex.emax = finitemax(v[2], ex.emax)
ex
end

expand_colorbar_extrema!(series::Series, v::Number, ::Any) = expand_extrema!(series.color_extrema, v)

expand_colorbar_extrema!(series::Series, surf::Surface, op) =
expand_colorbar_extrema!(series, surf.surf, op)

Loading

0 comments on commit f9fdf4a

Please sign in to comment.