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

Improve model and transform_func interaction with Axis limits #3864

Merged
merged 14 commits into from
May 16, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [Unreleased]

- `boundingbox` now relies on `apply_transform(transform, data_limits(plot))` rather than transforming the corner points of the bounding box [#3856](https://github.com/MakieOrg/Makie.jl/pull/3856)
- Adjusted `Axis` limits to consider transformations more consistently [#3864](https://github.com/MakieOrg/Makie.jl/pull/3864)

## [0.21.0] - 2024-05-08

Expand Down
6 changes: 3 additions & 3 deletions ReferenceTests/src/tests/examples2d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1151,10 +1151,10 @@ end

fig = Figure(size = (600, 600))
# Create a recipe plot
ax, plot_top = heatmap(fig[1, 1], randn(10, 10))
ax, plot_top = heatmap(fig[1, 1], randn(10, 10), colormap = [:transparent])
# Plot some recipes at the level below the contour
scatterlineplot_1 = scatterlines!(plot_top, 1:10, 1:10; linewidth = 20, markersize = 20, color = :red)
scatterlineplot_2 = scatterlines!(plot_top, 1:10, 1:10; linewidth = 20, markersize = 30, color = :blue)
scatterlineplot_1 = scatterlines!(ax, 1:10, 1:10; linewidth = 20, markersize = 20, color = :red)
scatterlineplot_2 = scatterlines!(ax, 1:10, 1:10; linewidth = 20, markersize = 30, color = :blue)
Comment on lines +1154 to +1157
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test failed because of what I'd say is undefined behavior - plotting to a primitive plot.
Specifically because data_limits has a specific overload for Heatmap, thus using heatmap limits, while boundingbox attempts to recognize primitives by their lack of child plots. Since heatmap has children here, boundingbox uses scatter + lines for limits.
Plotting to a primitive also results in other weirdness - the heatmap doesn't show and the axis uses tight limits.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, plotting to a primitive outside a recipe?! That's something we shouldn't do in any case ;) Didn't realize we actually do this in the tests!

# Translate the lowest level plots (scatters)
translate!(scatterlineplot_1.plots[2], 0, 0, 1)
translate!(scatterlineplot_2.plots[2], 0, 0, -1)
Expand Down
13 changes: 13 additions & 0 deletions ReferenceTests/src/tests/figures_and_makielayout.jl
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,16 @@ end
fig
end

@reference_test "Axis limits with translation, scaling and transform_func" begin
f = Figure()
a = Axis(f[1,1], xscale = log10, yscale = log10)
ps = Point2f.([0.1, 0.1, 1000, 1000], [1, 100, 1, 100])
hl = linesegments!(a, ps[[1, 3, 2, 4]], color = :red)
vl = linesegments!(a, ps, color = :blue)
# translation happens before scale! here because scale! acts on scene and
# translate! acts on the plot (these are combined by matmult)
scale!(a.scene, 0.5, 2, 1.0)
translate!(hl, 0, 1, 0)
translate!(vl, 1, 0, 0)
f
end
41 changes: 19 additions & 22 deletions src/makielayout/blocks/axis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ function update_axis_camera(scene::Scene, t, lims, xrev::Bool, yrev::Bool)
tlims = Makie.apply_transform(t, lims)
camera = scene.camera

# TODO: apply model
update_limits!(scene.float32convert, tlims) # update float32 scaling
lims32 = f32_convert(scene.float32convert, tlims) # get scaled limits
left, bottom = minimum(lims32)
Expand Down Expand Up @@ -853,33 +852,31 @@ function getlimits(la::Axis, dim)
return !to_value(get(plot, :visible, true))
end

# TODO:
# We used to include scale! and rotate! in data_limits. For compat we include
# them here again until we implement a full solution

# # get all data limits, minus the excluded plots
# boundingbox = Makie.data_limits(la.scene, exclude)
bb_ref = Base.RefValue(Rect3d())
for plot in la.scene
if !exclude(plot)
bb = data_limits(plot)
# Limits can be one dimensional (partially NaN) e.g. for hlines
# which results in every model * point to become NaN. For now we skip
# model application if the model matrix is identity to avoid this...
model = plot.model[][Vec(1,2,3), Vec(1,2,3)]
if !(model ≈ I)
bb = Rect3d(map(p -> model * to_ndim(Point3d, p, 0), coordinates(bb)))
end
update_boundingbox!(bb_ref, bb)
# get all data limits, minus the excluded plots
tf = la.scene.transformation.transform_func[]
itf = inverse_transform(tf)
if itf === nothing
@warn "Axis transformation $tf does not define an `inverse_transform()`. This may result in a bad choice of limits due to model transformations being ignored." maxlog = 1
bb = data_limits(la.scene, exclude)
else
# get limits with transform_func and model applied
bb = boundingbox(la.scene, exclude)
# then undo transform_func so that ticks can handle transform_func
# without ignoring translations, scaling or rotations from model
try
bb = apply_transform(itf, bb)
catch e
# TODO: Is this necessary?
@warn "Failed to apply inverse transform $itf to bounding box $bb. Falling back on data_limits()." exception = e
bb = data_limits(la.scene, exclude)
end
end
boundingbox = bb_ref[]

# if there are no bboxes remaining, `nothing` signals that no limits could be determined
isfinite_rect(boundingbox, dim) || return nothing
isfinite_rect(bb, dim) || return nothing

# otherwise start with the first box
mini, maxi = minimum(boundingbox), maximum(boundingbox)
mini, maxi = minimum(bb), maximum(bb)
return (mini[dim], maxi[dim])
end

Expand Down
Loading