Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add smart fillto to enable logscale y-axis histogram barplot #3004

Merged
merged 11 commits into from
Jul 14, 2023
17 changes: 17 additions & 0 deletions ReferenceTests/src/tests/examples2d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,23 @@ end
f
end

@reference_test "Log y-scale histogram (barplot)" begin
hist(
randn(10^6);
axis=(; yscale=log2)
)
current_figure()
end

@reference_test "Log y-scale histogram (barplot) with gap" begin
# make a gap in histogram as edge case
hist(
filter!(x-> x<0 || x > 1.5, randn(10^6));
axis=(; yscale=log10)
)
current_figure()
end

@reference_test "Stephist" begin
stephist(RNG.rand(10000))
current_figure()
Expand Down
36 changes: 33 additions & 3 deletions src/basic_recipes/barplot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,36 @@ function bar_label_formatter(value::Number)
return string(round(value; digits=3))
end

"""
bar_default_fillto(tf, ys, offset)::(ys, offset)

Returns the default y-positions and offset positions for the given transform `tf`.

In order to customize this for your own transformation type, you can dispatch on
`tf`.

Returns a Tuple of new y positions and offset arrays.

## Arguments
- `tf`: `plot.transformation.transform_func[]`.
- `ys`: The y-values passed to `barplot`.
- `offset`: The `offset` parameter passed to `barplot`.
"""
function bar_default_fillto(tf, ys, offset)
Moelf marked this conversation as resolved.
Show resolved Hide resolved
return ys, offset
end

# `fillto` is related to `y-axis` transofrmation only, thus we expect `tf::Tuple`
function bar_default_fillto(tf::Tuple, ys, offset)
if tf[2] isa Union{typeof(log), typeof(log2), typeof(log10), Base.Fix1{typeof(log), <: Real}}
Moelf marked this conversation as resolved.
Show resolved Hide resolved
# use the minimal non-zero y divided by 2 as lower bound for log scale
smart_fillto = minimum(y -> y<=0 ? oftype(y, Inf) : y, ys) / 2
Moelf marked this conversation as resolved.
Show resolved Hide resolved
return clamp.(ys, smart_fillto, Inf), smart_fillto
else
return ys, offset
end
end

"""
barplot(x, y; kwargs...)

Expand Down Expand Up @@ -190,7 +220,7 @@ function Makie.plot!(p::BarPlot)
label_aligns = Observable(Vec2f[])
label_offsets = Observable(Vec2f[])
label_colors = Observable(RGBAf[])
function calculate_bars(xy, fillto, offset, width, dodge, n_dodge, gap, dodge_gap, stack,
function calculate_bars(xy, fillto, offset, transformation, width, dodge, n_dodge, gap, dodge_gap, stack,
dir, bar_labels, flip_labels_at, label_color, color_over_background,
color_over_bar, label_formatter, label_offset)

Expand All @@ -217,7 +247,7 @@ function Makie.plot!(p::BarPlot)

if stack === automatic
if fillto === automatic
fillto = offset
y, fillto = bar_default_fillto(transformation, y, offset)
end
elseif eltype(stack) <: Integer
fillto === automatic || @warn "Ignore keyword fillto when keyword stack is provided"
Expand Down Expand Up @@ -249,7 +279,7 @@ function Makie.plot!(p::BarPlot)
return bar_rectangle.(x̂, y .+ offset, barwidth, fillto, in_y_direction)
end

bars = lift(calculate_bars, p, p[1], p.fillto, p.offset, p.width, p.dodge, p.n_dodge, p.gap,
bars = lift(calculate_bars, p, p[1], p.fillto, p.offset, p.transformation.transform_func, p.width, p.dodge, p.n_dodge, p.gap,
p.dodge_gap, p.stack, p.direction, p.bar_labels, p.flip_labels_at,
p.label_color, p.color_over_background, p.color_over_bar, p.label_formatter, p.label_offset)

Expand Down