Skip to content

Commit

Permalink
Give beeswarm plots fixed limits given input data (#26)
Browse files Browse the repository at this point in the history
Co-authored-by: Anshul Singhvi <[email protected]>
  • Loading branch information
jkrumbiegel and asinghvi17 authored Oct 21, 2024
1 parent b442d8d commit 18543c0
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 21 deletions.
1 change: 0 additions & 1 deletion docs/src/algorithms.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ ax_plots = [beeswarm(fig[Tuple(idx)...], xs, ys; color = xs, algorithm = algorit
jitter_plots = getproperty.(ax_plots[2, :], :plot)
setproperty!.(jitter_plots, :markersize, 7)
setproperty!.(jitter_plots, :alpha, 0.3)
Makie.update_state_before_display!(fig)
fig
```

Expand Down
7 changes: 0 additions & 7 deletions docs/src/examples/examples.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ using PalmerPenguins, DataFrames
penguins = dropmissing(DataFrame(PalmerPenguins.load()))

f = data(penguins) * mapping(:species, :bill_depth_mm, color=:sex) * visual(Beeswarm) |> draw
Makie.update_state_before_display!(f.figure)
Makie.update_state_before_display!(f.figure)
Makie.update_state_before_display!(f.figure)
f

# ## SwarmMakie logo
Expand All @@ -43,10 +40,6 @@ f.scene.backgroundcolor[] = RGBAf(1,1,1,0)
a.scene.backgroundcolor[] = RGBAf(1,1,1,0)
hidedecorations!(a)
hidespines!(a)
Makie.update_state_before_display!(f)
Makie.update_state_before_display!(f)
Makie.update_state_before_display!(f)
Makie.update_state_before_display!(f)
f

# ## Wilkinson's dot histogram
Expand Down
3 changes: 0 additions & 3 deletions docs/src/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,10 @@ iris = dataset("datasets", "iris")
f = data(iris) *
mapping(:Species, :SepalLength; color = :Species) *
visual(Beeswarm) |> draw
Makie.update_state_before_display!(f.figure)
Makie.update_state_before_display!(f.figure)
f
```

## Tips and tricks

If your beeswarms are overlapping, or extending outside the axis area, try decreasing `markersize`. You can do this by setting `plot.markersize = 6` for example, and then re-displaying the figure.

Generally, the algorithm takes a few iterations of calling `Makie.update_state_before_display!(figure)` to settle in a good configuration. We are working to fix this.
41 changes: 40 additions & 1 deletion src/recipe.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,46 @@ function calculate!(buffer::AbstractVector{<: Point2}, alg::NoBeeswarm, position
return
end

Makie.data_limits(bs::Beeswarm) = Makie.data_limits(bs.plots[1])
# Beeswarm plots inherently have an extent that is dependent on the placement
# algorithm and the available space. However, you cannot use the actual placement
# of scatter dots to infer limits, because adjusting the axis given these limits
# invalidates the limits again, and so on, potentially ad infinitum.
#
# Instead, it makes more sense to pick fixed limits given the input data. If
# that doesn't leave enough place for all beeswarms, probably the axis size
# has to be increased, or the marker sized decreased, anyway.
#
# The dimension that's not controlled by the beeswarm placement algorithm we
# can take directly from the input data. For the "categories" or group placement
# values, we simply determine the differences between the unique sorted values and
# increase the width at the sides by half the minimum distance. That means, we create
# equal space for all categories. If the beeswarm doesn't fit that, again, other
# parameters have to be adjusted anway.
function Makie.data_limits(bs::Beeswarm)
points = bs.converted[1][]
categories = sort(unique(p[1] for p in points))
range_1 = if length(categories) == 1
(only(categories) - 0.5, only(categories) + 0.5)
else
mindiff = if isnothing(bs.gutter[])
minimum(diff(categories))
else
bs.gutter[]
end
(first(categories) - mindiff/2, last(categories) + mindiff/2)
end
range_2 = extrema(p[2] for p in points)
bb = if bs.direction[] === :y
BBox(range_1..., range_2...)
elseif bs.direction[] === :x
BBox(range_2..., range_1...)
else
error("Invalid direction $(repr(bs.direction[])), expected :x or :y")
end
return Rect3f(bb)
end

Makie.boundingbox(s::Beeswarm, space::Symbol = :data) = Makie.apply_transform_and_model(s, Makie.data_limits(s))

function Makie.plot!(plot::Beeswarm)
positions = plot.converted[1] # being PointBased, it should always receive a vector of Point2
Expand Down
13 changes: 4 additions & 9 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ buffer = deepcopy(pixel_points)
hidedecorations!(a)
hidespines!(a)
Makie.update_state_before_display!(f)
Makie.update_state_before_display!(f)
Makie.update_state_before_display!(f)
img = Makie.colorbuffer(f.scene; px_per_unit = 1, pt_per_unit = 1, antialias = :none, visible = true, start_renderloop = false)
# We have a matrix of all colors in the image. Now, what we do is the following:
# The color white in RGBf is (1, 1, 1). For a color to be red, the blue and green components
Expand All @@ -53,16 +51,13 @@ buffer = deepcopy(pixel_points)
# First, we test the regular gutter with multiple categories.
f, a, p = beeswarm(rand(1:3, 300), randn(300); color = rand(RGBAf, 300), markersize = 20, algorithm = SimpleBeeswarm())
Makie.update_state_before_display!(f)
Makie.update_state_before_display!(f)
@test_warn "Gutter threshold exceeded" p.gutter = 0.5
@test_warn "Gutter threshold exceeded" p.gutter = 0.2
# Next, we test it in direction y
f, a, p = beeswarm(rand(1:3, 300), randn(300); direction = :x, color = rand(RGBAf, 300), markersize = 20, algorithm = SimpleBeeswarm())
Makie.update_state_before_display!(f)
f, a, p = beeswarm(rand(1:3, 300), randn(300); direction = :x, color = rand(RGBAf, 300), markersize = 20, algorithm = SimpleBeeswarm())
Makie.update_state_before_display!(f)
@test_warn "Gutter threshold exceeded" p.gutter = 0.5
@test_warn "Gutter threshold exceeded" p.gutter = 0.2
# and it shouldn't warn if, when using a lower markersize, the gutter is not reached.
f, a, p = beeswarm(rand(1:3, 300), randn(300); direction = :y, color = rand(RGBAf, 300), markersize = 9, algorithm = SimpleBeeswarm())
Makie.update_state_before_display!(f)
f, a, p = beeswarm(rand(1:3, 300), randn(300); direction = :y, color = rand(RGBAf, 300), markersize = 9, algorithm = SimpleBeeswarm())
Makie.update_state_before_display!(f)
@test_nowarn p.gutter = 0.5
end
Expand Down

0 comments on commit 18543c0

Please sign in to comment.