Skip to content

Try fixing log-barplot with explicit transformation and boundingbox #4911

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

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [Unreleased]

- Use the `bar_default_fillto` method from [#3004](https://github.com/MakieOrg/Makie.jl/pull/3004) in stacked barplots as well, to stop the first member of the stack from disappearing in log scale [#4670](https://github.com/MakieOrg/Makie.jl/pull/4849).
- Added `alpha` attribute to `tricontourf.jl` to control the transparency of filled contours [#4800](https://github.com/MakieOrg/Makie.jl/pull/4800)
- Fixed hexbin using log-scales [#4898](https://github.com/MakieOrg/Makie.jl/pull/4898)
- Updated scope of `space` attribute, restricting it to camera related projections in the conversion-transformation-projection pipeline. (See docs on `space` or the pipeline) [#4792](https://github.com/MakieOrg/Makie.jl/pull/4792)
Expand Down Expand Up @@ -67,7 +68,7 @@
- Changed the order of `Rect2` coordinates to be counter-clockwise.
- Updated `Cylinder` to avoid visually rounding off the top and bottom.
- Added `MetaMesh` to store non-vertex metadata in a GeometryBasics Mesh object. These are now produced by MeshIO for `.obj` files, containing information from `.mtl` files.
- Renamed `Tesselation/tesselation` to `Tessellation/tessellation` [GeometryBasics#227](https://github.com/JuliaGeometry/GeometryBasics.jl/pull/227) [#4564](https://github.com/MakieOrg/Makie.jl/pull/4564)

Check failure on line 71 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / check

tesselation ==> tessellation

Check failure on line 71 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / check

Tesselation ==> Tessellation
- Added `Makie.mesh` option for `MetaMesh` which applies some of the bundled information [#4368](https://github.com/MakieOrg/Makie.jl/pull/4368), [#4496](https://github.com/MakieOrg/Makie.jl/pull/4496)
- `Voronoiplot`s automatic colors are now defined based on the underlying point set instead of only those generators appearing in the tessellation. This makes the selected colors consistent between tessellations when generators might have been deleted or added. [#4357](https://github.com/MakieOrg/Makie.jl/pull/4357)
- `contour` now supports _curvilinear_ grids, where `x` and `y` are matrices [#4670](https://github.com/MakieOrg/Makie.jl/pull/4670).
Expand Down
8 changes: 7 additions & 1 deletion ReferenceTests/src/tests/examples2d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,12 @@ end
f
end

@reference_test "Log scale stacked barplot" begin
f = Figure()
barplot(f[1,1], [1,2,3,1,2,3], [1,2,3,1,2,3], stack=[1,1,1,2,2,2], color=[1,1,1,2,2,2], gap=0; axis=(; yscale=log))
f
end

@reference_test "Log scale histogram (barplot)" begin
f = Figure()
hist(
Expand Down Expand Up @@ -1953,4 +1959,4 @@ end
translate!(a.scene, 0.1, 0.05) # test that pattern are anchored to the plot
Makie.step!(st)
st
end
end
87 changes: 50 additions & 37 deletions src/basic_recipes/barplot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,6 @@ bar_label_formatter(value::Number) = string(round(value; digits=3))
bar_label_formatter(label::String) = label
bar_label_formatter(label::LaTeXString) = label

"""
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, in_y_direction)
return ys, offset
end

# `fillto` is related to `y-axis` transformation only, thus we expect `tf::Tuple`
function bar_default_fillto(tf::Tuple, ys, offset, in_y_direction)
_logT = Union{typeof(log), typeof(log2), typeof(log10), Base.Fix1{typeof(log), <: Real}}
if in_y_direction && tf[2] isa _logT || (!in_y_direction && tf[1] isa _logT)
# x-scale log and !(in_y_direction) is equiavlent to y-scale log in_y_direction
# 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
return clamp.(ys, smart_fillto, Inf), smart_fillto
else
return ys, offset
end
end

"""
barplot(positions, heights; kwargs...)

Expand Down Expand Up @@ -88,13 +56,18 @@ end

conversion_trait(::Type{<: BarPlot}) = PointBased()

function bar_rectangle(x, y, width, fillto, in_y_direction)
function bar_rectangle(x, y, width, fillto, in_y_direction, transform_func)
# y could be smaller than fillto...
ymin = min(fillto, y)
ymax = max(fillto, y)
w = abs(width)
rect = Rectd(x - (w / 2f0), ymin, w, ymax - ymin)
return in_y_direction ? rect : flip(rect)
rect = in_y_direction ? rect : flip(rect)
# Transform coordinates of bar rectangle and clamp result to a workable value range.
# Do not repack as Rect because the representation with widths can cause float
# precision issues for vertices.
ps = apply_transform(transform_func, coordinates(rect))
return map(p -> clamp.(p, -1e32, 1e32), ps)
end

flip(r::Rect2) = Rect2(reverse(origin(r)), reverse(widths(r)))
Expand Down Expand Up @@ -162,7 +135,7 @@ function stack_grouped_from_to(i_stack, y, grp)
to[inds] .= fromto.to
end

(from = from, to = to)
return (from = from, to = to)
end

function calculate_bar_label_align(label_align, label_rotation::Real, in_y_direction::Bool, flip::Bool)
Expand Down Expand Up @@ -293,7 +266,7 @@ function Makie.plot!(p::BarPlot)

if stack === automatic
if fillto === automatic
y, fillto = bar_default_fillto(transformation, y, offset, in_y_direction)
fillto = offset
end
elseif eltype(stack) <: Integer
fillto === automatic || @warn "Ignore keyword fillto when keyword stack is provided"
Expand Down Expand Up @@ -322,7 +295,7 @@ function Makie.plot!(p::BarPlot)
labels[], label_aligns[], label_offsets[], label_colors[] = label_args
end

return bar_rectangle.(x̂, y .+ offset, barwidth, fillto, in_y_direction)
return bar_rectangle.(x̂, y .+ offset, barwidth, fillto, in_y_direction, Ref(transformation))
end

bars = lift(calculate_bars, p, p[1], p.fillto, p.offset, p.transformation.transform_func, p.width, p.dodge, p.n_dodge, p.gap,
Expand All @@ -333,9 +306,49 @@ function Makie.plot!(p::BarPlot)
strokewidth = p.strokewidth, strokecolor = p.strokecolor, visible = p.visible,
inspectable = p.inspectable, transparency = p.transparency, space = p.space,
highclip = p.highclip, lowclip = p.lowclip, nan_color = p.nan_color, alpha = p.alpha,
transformation = :inherit_model
)

if !isnothing(p.bar_labels[])
text!(p, labels; align=label_aligns, offset=label_offsets, color=label_colors, font=p.label_font, fontsize=p.label_size, rotation=p.label_rotation)
end
end

data_limits(p::BarPlot) = update_boundingbox(Rect3d(p[1][]), Vec3d(NaN, 0, NaN))
function boundingbox(p::BarPlot, space::Symbol = :data)
# plot construction will error check this
in_y_direction = p.direction[] == :y
transformation = transform_func(p)
_logT = Union{typeof(log), typeof(log2), typeof(log10), Base.Fix1{typeof(log), <: Real}}
is_log = transformation isa Tuple && in_y_direction && transformation[2] isa _logT || (!in_y_direction && transformation[1] isa _logT)

if !is_log
bb_transformed = boundingbox(p.plots[1])
return bb_transformed
else
# use the minimal non-zero y divided by 2 as lower bound for log scale
ps = p[1][]
dim = ifelse(in_y_direction, 2, 1)
smart_min = minimum(p -> p[dim] <= 0 ? oftype(p[dim], Inf) : p[dim], ps) / 2

# get transformed fillto
mini = to_ndim(Point3d, minimum(ps), 0)
mini = ntuple(i -> ifelse(i == dim, smart_min, mini[i]), 3)
smart_min_transformed = apply_transform_and_model(p, mini)[dim]

# Since Rect represents maximum as rect.origin + rect.widths it will
# have float precision issues if maximum ≲ eps(maximum) * widths. To
# avoid this we need to explicitly calculate the bounds:
rect_verts = p.plots[1][1][]
mini_transformed = Point3d(Inf)
maxi_transformed = Point3d(-Inf)
for verts in rect_verts
low, high = extrema(verts)
mini_transformed = min.(mini_transformed, to_ndim(Point3d, low, 0))
maxi_transformed = max.(maxi_transformed, to_ndim(Point3d, high, 0))
end
# With smart_min_transformed it should be resolvable
mini_transformed = ntuple(i -> ifelse(i == dim, smart_min_transformed, mini_transformed[i]), 3)
return apply_model(p.model[], Rect3d(mini_transformed, maxi_transformed .- mini_transformed))
end
end
Loading