Fix log-scale for 1D bar/bin/histogram series types

This commit is contained in:
Oliver Schulz 2017-04-29 17:53:37 +02:00
parent 1188230641
commit 56a9389023
3 changed files with 120 additions and 38 deletions

View File

@ -156,6 +156,8 @@ const _markerAliases = Dict{Symbol,Symbol}(
) )
const _allScales = [:identity, :ln, :log2, :log10, :asinh, :sqrt] const _allScales = [:identity, :ln, :log2, :log10, :asinh, :sqrt]
const _logScales = [:ln, :log2, :log10]
const _logScaleBases = Dict(:ln => e, :log2 => 2.0, :log10 => 10.0)
const _scaleAliases = Dict{Symbol,Symbol}( const _scaleAliases = Dict{Symbol,Symbol}(
:none => :identity, :none => :identity,
:log => :log10, :log => :log10,

View File

@ -277,6 +277,13 @@ function _subplot_setup(plt::Plot, d::KW, kw_list::Vector{KW})
attr[Symbol(letter,k)] = v attr[Symbol(letter,k)] = v
end end
end end
for k in (:scale,), letter in (:x,:y,:z)
# Series recipes may need access to this information
lk = Symbol(letter,k)
if haskey(attr, lk)
kw[lk] = attr[lk]
end
end
end end
sp_attrs[sp] = attr sp_attrs[sp] = attr
end end

View File

@ -323,10 +323,11 @@ end
# create a bar plot as a filled step function # create a bar plot as a filled step function
@recipe function f(::Type{Val{:bar}}, x, y, z) @recipe function f(::Type{Val{:bar}}, x, y, z)
nx, ny = length(x), length(y) procx, procy, xscale, yscale, baseline = _preprocess_binlike(d, x, y)
nx, ny = length(procx), length(procy)
axis = d[:subplot][isvertical(d) ? :xaxis : :yaxis] axis = d[:subplot][isvertical(d) ? :xaxis : :yaxis]
cv = [discrete_value!(axis, xi)[1] for xi=x] cv = [discrete_value!(axis, xi)[1] for xi=procx]
x = if nx == ny procx = if nx == ny
cv cv
elseif nx == ny + 1 elseif nx == ny + 1
0.5diff(cv) + cv[1:end-1] 0.5diff(cv) + cv[1:end-1]
@ -337,9 +338,9 @@ end
# compute half-width of bars # compute half-width of bars
bw = d[:bar_width] bw = d[:bar_width]
hw = if bw == nothing hw = if bw == nothing
0.5mean(diff(x)) 0.5mean(diff(procx))
else else
Float64[0.5cycle(bw,i) for i=1:length(x)] Float64[0.5cycle(bw,i) for i=1:length(procx)]
end end
# make fillto a vector... default fills to 0 # make fillto a vector... default fills to 0
@ -347,17 +348,22 @@ end
if fillto == nothing if fillto == nothing
fillto = 0 fillto = 0
end end
if (yscale in _logScales) && !all(_is_positive, fillto)
fillto = map(x -> _is_positive(x) ? typeof(baseline)(x) : baseline, fillto)
end
# create the bar shapes by adding x/y segments # create the bar shapes by adding x/y segments
xseg, yseg = Segments(), Segments() xseg, yseg = Segments(), Segments()
for i=1:ny for i=1:ny
center = x[i] yi = procy[i]
if !isnan(yi)
center = procx[i]
hwi = cycle(hw,i) hwi = cycle(hw,i)
yi = y[i]
fi = cycle(fillto,i) fi = cycle(fillto,i)
push!(xseg, center-hwi, center-hwi, center+hwi, center+hwi, center-hwi) push!(xseg, center-hwi, center-hwi, center+hwi, center+hwi, center-hwi)
push!(yseg, yi, fi, fi, yi, yi) push!(yseg, yi, fi, fi, yi, yi)
end end
end
# widen limits out a bit # widen limits out a bit
expand_extrema!(axis, widen(extrema(xseg.pts)...)) expand_extrema!(axis, widen(extrema(xseg.pts)...))
@ -384,9 +390,51 @@ end
_bin_centers(v::AVec) = (v[1:end-1] + v[2:end]) / 2 _bin_centers(v::AVec) = (v[1:end-1] + v[2:end]) / 2
_is_positive(x) = (x > 0) && !(x 0)
_positive_else_nan{T}(::Type{T}, x::Real) = _is_positive(x) ? T(x) : T(NaN)
function _scale_adjusted_values{T<:AbstractFloat}(::Type{T}, V::AbstractVector, scale::Symbol)
if scale in _logScales
[_positive_else_nan(T, x) for x in V]
else
[T(x) for x in V]
end
end
function _hist_ylim_lo{T<:Real}(ymin::T, yscale::Symbol)
if (yscale in _logScales)
ymin / T(_logScaleBases[yscale]^log10(2))
else
zero(T)
end
end
function _hist_ylim_hi{T<:Real}(ymax::T, yscale::Symbol)
if (yscale in _logScales)
ymax * T(_logScaleBases[yscale]^log10(2))
else
ymax * T(1.1)
end
end
function _preprocess_binlike(d, x, y)
xscale = get(d, :xscale, :identity)
yscale = get(d, :yscale, :identity)
T = float(promote_type(eltype(x), eltype(y)))
edge = map(T, x)
weights = _scale_adjusted_values(T, y, yscale)
w_min = minimum(weights)
baseline = _hist_ylim_lo(isnan(w_min) ? one(T) : w_min, yscale)
edge, weights, xscale, yscale, baseline
end
@recipe function f(::Type{Val{:barbins}}, x, y, z) @recipe function f(::Type{Val{:barbins}}, x, y, z)
edge, weights = x, y edge, weights, xscale, yscale, baseline = _preprocess_binlike(d, x, y)
if (d[:bar_width] == nothing) if (d[:bar_width] == nothing)
bar_width := diff(edge) bar_width := diff(edge)
end end
@ -395,11 +443,11 @@ _bin_centers(v::AVec) = (v[1:end-1] + v[2:end]) / 2
seriestype := :bar seriestype := :bar
() ()
end end
@deps barbins bins @deps barbins bar
@recipe function f(::Type{Val{:scatterbins}}, x, y, z) @recipe function f(::Type{Val{:scatterbins}}, x, y, z)
edge, weights = x, y edge, weights, xscale, yscale, baseline = _preprocess_binlike(d, x, y)
xerror := diff(edge)/2 xerror := diff(edge)/2
x := _bin_centers(edge) x := _bin_centers(edge)
y := weights y := weights
@ -409,43 +457,65 @@ end
@deps scatterbins scatter @deps scatterbins scatter
function _stepbins_path(edge, weights) function _stepbins_path(edge, weights, baseline::Real, xscale::Symbol, yscale::Symbol)
log_scale_x = xscale in _logScales
log_scale_y = yscale in _logScales
nbins = length(linearindices(weights)) nbins = length(linearindices(weights))
if length(linearindices(edge)) != nbins + 1 if length(linearindices(edge)) != nbins + 1
error("Edge vector must be 1 longer than weight vector") error("Edge vector must be 1 longer than weight vector")
end end
x = eltype(edge)[]
y = eltype(weights)[]
it_e, it_w = start(edge), start(weights) it_e, it_w = start(edge), start(weights)
px, it_e = next(edge, it_e) a, it_e = next(edge, it_e)
py = zero(eltype(weights)) last_w = eltype(weights)(NaN)
i = 1
while (!done(edge, it_e) && !done(edge, it_e))
b, it_e = next(edge, it_e)
w, it_w = next(weights, it_w)
npathpts = 2 * nbins + 2 if (log_scale_x && a 0)
x = Vector{eltype(px)}(npathpts) a = b/_logScaleBases[xscale]^3
y = Vector{eltype(py)}(npathpts) end
x[1], y[1] = px, py if isnan(w)
i = 2 if !isnan(last_w)
while (i < npathpts - 1) push!(x, a)
py, it_w = next(weights, it_w) push!(y, baseline)
x[i], y[i] = px, py end
i += 1 else
px, it_e = next(edge, it_e) if isnan(last_w)
x[i], y[i] = px, py push!(x, a)
i += 1 push!(y, baseline)
end
push!(x, a)
push!(y, w)
push!(x, b)
push!(y, w)
end
a = b
last_w = w
end
if (last_w != baseline)
push!(x, a)
push!(y, baseline)
end end
assert(i == npathpts)
x[end], y[end] = px, zero(py)
(x, y) (x, y)
end end
@recipe function f(::Type{Val{:stepbins}}, x, y, z)
edge, weights = x, y
@recipe function f(::Type{Val{:stepbins}}, x, y, z)
axis = d[:subplot][Plots.isvertical(d) ? :xaxis : :yaxis] axis = d[:subplot][Plots.isvertical(d) ? :xaxis : :yaxis]
xpts, ypts = _stepbins_path(edge, weights) edge, weights, xscale, yscale, baseline = _preprocess_binlike(d, x, y)
if !Plots.isvertical(d)
xpts, ypts = _stepbins_path(edge, weights, baseline, xscale, yscale)
if !isvertical(d)
xpts, ypts = ypts, xpts xpts, ypts = ypts, xpts
end end
@ -453,7 +523,7 @@ end
if d[:markershape] != :none if d[:markershape] != :none
@series begin @series begin
seriestype := :scatter seriestype := :scatter
x := Plots._bin_centers(edge) x := _bin_centers(edge)
y := weights y := weights
fillrange := nothing fillrange := nothing
label := "" label := ""
@ -468,7 +538,8 @@ end
x := xpts x := xpts
y := ypts y := ypts
seriestype := :path seriestype := :path
ylims --> [0, 1.1 * maximum(weights)]
ylims --> [baseline, _hist_ylim_hi(maximum(weights), yscale)]
() ()
end end
Plots.@deps stepbins path Plots.@deps stepbins path
@ -568,9 +639,11 @@ end
if d[:seriestype] == :scatterbins if d[:seriestype] == :scatterbins
# Workaround, error bars currently not set correctly by scatterbins # Workaround, error bars currently not set correctly by scatterbins
edge, weights, xscale, yscale, baseline = _preprocess_binlike(d, h.edges[1], h.weights)
info("xscale = $xscale, yscale = $yscale")
xerror --> diff(h.edges[1])/2 xerror --> diff(h.edges[1])/2
seriestype := :scatter seriestype := :scatter
(Plots._bin_centers(h.edges[1]), h.weights) (Plots._bin_centers(edge), weights)
else else
(h.edges[1], h.weights) (h.edges[1], h.weights)
end end