Skip to content

Commit

Permalink
Violin scaling continued (#3715)
Browse files Browse the repository at this point in the history
* scale violin area by amount

* add `scale` to `violin`

* doc: add `scale` of `violin`

* add reference test

* add change log

* use stable RNG

---------

Co-authored-by: Tarn Yeong Ching <i@ctarn.io>
  • Loading branch information
jkrumbiegel and ctarn authored Mar 19, 2024
1 parent d06140d commit fce5b2e
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Fixed bug in CairoMakie line drawing when multiple successive points had the same color [#3712](https://github.com/MakieOrg/Makie.jl/pull/3712).
- Remove StableHashTraits in favor of calculating hashes directly with CRC32c [#3667](https://github.com/MakieOrg/Makie.jl/pull/3667).
- Fixed `contourf` bug where n levels would sometimes miss the uppermost value, causing gaps [#3713](https://github.com/MakieOrg/Makie.jl/pull/3713).
- Added `scale` attribute to `violin` [#3352](https://github.com/MakieOrg/Makie.jl/pull/3352).

## [0.20.8] - 2024-02-22

Expand Down
13 changes: 13 additions & 0 deletions ReferenceTests/src/tests/examples2d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1404,3 +1404,16 @@ end
Colorbar(fig[1, 2], cof)
fig
end

@reference_test "Violin plots differently scaled" begin
fig = Figure()
xs = vcat([fill(i, i * 1000) for i in 1:4]...)
ys = vcat(RNG.randn(6000), RNG.randn(4000) * 2)
for (i, scale) in enumerate([:area, :count, :width])
ax = Axis(fig[i, 1])
violin!(ax, xs, ys; scale, show_median=true)
Makie.xlims!(0.2, 4.8)
ax.title = "scale=:$(scale)"
end
fig
end
19 changes: 19 additions & 0 deletions docs/reference/plots/violin.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,25 @@ violin(categories, values)
```
\end{examplefigure}

\begin{examplefigure}{}
```julia
using Makie, CairoMakie
CairoMakie.activate!() # hide


fig = Figure()
xs = vcat([fill(i, i * 1000) for i in 1:4]...)
ys = vcat(randn(6000), randn(4000) * 2)
for (i, scale) in enumerate([:area, :count, :width])
ax = Axis(fig[i, 1])
violin!(ax, xs, ys; scale, show_median=true)
Makie.xlims!(0.2, 4.8)
ax.title = "scale=:$(scale)"
end
fig
```
\end{examplefigure}

\begin{examplefigure}{}
```julia
using CairoMakie
Expand Down
28 changes: 22 additions & 6 deletions src/stats/violin.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Draw a violin plot.
- `gap=0.2`: shrinking factor, `width -> width * (1 - gap)`
- `show_median=false`: show median as midline
- `side=:both`: specify `:left` or `:right` to only plot the violin on one side
- `scale=:width`: scale density by area (`:area`), count (`:count`), or width (`:width`).
- `datalimits`: specify values to trim the `violin`. Can be a `Tuple` or a `Function` (e.g. `datalimits=extrema`)
"""
@recipe(Violin, x, y) do scene
Expand All @@ -21,6 +22,7 @@ Draw a violin plot.
bandwidth = automatic,
weights = automatic,
side = :both,
scale = :area,
orientation = :vertical,
width = automatic,
dodge = automatic,
Expand Down Expand Up @@ -49,10 +51,10 @@ end

function plot!(plot::Violin)
x, y = plot[1], plot[2]
args = @extract plot (width, side, color, show_median, npoints, boundary, bandwidth, weights,
args = @extract plot (width, side, scale, color, show_median, npoints, boundary, bandwidth, weights,
datalimits, max_density, dodge, n_dodge, gap, dodge_gap, orientation)
signals = lift(plot, x, y,
args...) do x, y, width, vside, color, show_median, n, bound, bw, w, limits, max_density,
args...) do x, y, width, vside, scale_type, color, show_median, n, bound, bw, w, limits, max_density,
dodge, n_dodge, gap, dodge_gap, orientation
x̂, violinwidth = compute_x_and_width(x, width, gap, dodge, n_dodge, dodge_gap)

Expand Down Expand Up @@ -81,13 +83,20 @@ function plot!(plot::Violin)
i1, i2 = searchsortedfirst(k.x, l1), searchsortedlast(k.x, l2)
kde = (x = view(k.x, i1:i2), density = view(k.density, i1:i2))
c = getuniquevalue(color, idxs)
return (x = key.x, side = key.side, color = to_color(c), kde = kde, median = median(v))
return (x = key.x, side = key.side, color = to_color(c), kde = kde, median = median(v), amount = length(idxs))
end

(scale_type [:area, :count, :width]) || error("Invalid scale type: $(scale_type)")

max = if max_density === automatic
maximum(specs) do spec
_, max = extrema_nan(spec.kde.density)
return max
if scale_type === :area
return extrema_nan(spec.kde.density) |> last
elseif scale_type === :count
return extrema_nan(spec.kde.density .* spec.amount) |> last
elseif scale_type === :width
return NaN
end
end
else
max_density
Expand All @@ -98,7 +107,14 @@ function plot!(plot::Violin)
colors = RGBA{Float32}[]

for spec in specs
scale = 0.5*violinwidth/max
scale = 0.5 * violinwidth
if scale_type === :area
scale = scale / max
elseif scale_type === :count
scale = scale / max * spec.amount
elseif scale_type === :width
scale = scale / (extrema_nan(spec.kde.density) |> last)
end
xl = reverse(spec.x .- spec.kde.density .* scale)
xr = spec.x .+ spec.kde.density .* scale
yl = reverse(spec.kde.x)
Expand Down

0 comments on commit fce5b2e

Please sign in to comment.