Interactive boxplot with GLMakie

Hi. I am trying to create an interactive boxplot where the use selects the variable and the boxplot is updated accordingly. The code is like below:

xs = @lift($dfbox[: , 1])
ys = @lift($dfbox[:, 2])
ax = Axis(fig[1,1])
boxplot!(ax, xs, ys)

Here, dfbox is an observable dataframe which is created as:

dfbox = @lift(data |>                 
                 df -> select(df, :country, $xvar) |>
                 dropmissing |>
                 df -> transform(df, :country => labelencode => :cntlabel))

When I change the variable, I get errors due to shape mismatch. I have checked the title “Problems With Synchronous Updates” in the Makie documentation but still I couldn’t solve the problem. Any idea about how can I workaround this?

A good Minimal Working Example would help a lot here (maybe some code generating mock data and the plot that we can copy and paste!).

using DataFrames, GLMakie
df = DataFrame(:a => rand(50), :b => rand(50))
l = Observable(10)
xs = Observable(df[1:10,1])
ys = Observable(df[1:10,2])
fig = Figure()
ax = Axis(fig[1,1])
bp = boxplot!(ax, xs,ys)
on(l) do val
    xs.val = df[1:val,1]
   ys.val = df[1:val,2]
notify(xs)
notify(ys)
end
l[] = 20

I think this should work, but it does not. It seems like one value inside the boxplot is not being updated (maybe it is not observable when it should be?). I think this is worth opening an issue at Makie’s repo

Edit: I opened the issue here

I actually get an error:

ulia> l[] = 20
ERROR: BoundsError: attempt to access 10-element Vector{Float64} at index [[13]]
Stacktrace:
  [1] throw_boundserror(A::Vector{Float64}, I::Tuple{Vector{Int64}})
    @ Base ./abstractarray.jl:744
  [2] checkbounds
    @ ./abstractarray.jl:709 [inlined]
  [3] view
    @ ./subarray.jl:177 [inlined]
  [4] (::Makie.var"#883#897")(x::Vector{Float64}, y::Vector{Float64}, color::ColorTypes.RGBA{Float32}, weights::MakieCore.Automatic, width::MakieCore.Automatic, range::Float64, show_outliers::Bool, whiskerwidth::Float64, show_notch::Bool, orientation::Symbol, gap::Float64, dodge::MakieCore.Automatic, n_dodge::MakieCore.Automatic, dodge_gap::Float64)
    @ Makie ~/.julia/packages/Makie/Iqcri/src/stats/boxplot.jl:108
  [5] invokelatest(::Any, ::Any, ::Vararg{Any}; kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ Base ./essentials.jl:816
  [6] invokelatest(::Any, ::Any, ::Vararg{Any})
    @ Base ./essentials.jl:813
  [7] (::Observables.MapCallback)(value::Any)
    @ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:431
  [8] #invokelatest#2
    @ ./essentials.jl:816 [inlined]
  [9] invokelatest
    @ ./essentials.jl:813 [inlined]
 [10] notify
    @ ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:169 [inlined]
 [11] setindex!(observable::Observable, val::Any)
    @ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:86
 [12] (::Observables.MapCallback)(value::Any)
    @ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:431
 [13] #invokelatest#2
    @ ./essentials.jl:816 [inlined]
 [14] invokelatest
    @ ./essentials.jl:813 [inlined]
 [15] notify
    @ ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:169 [inlined]
 [16] setindex!(observable::Observable, val::Any)
    @ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:86
 [17] (::Makie.var"#171#173"{Attributes, Observable{Tuple{Vector{Float64}, Vector{Float64}}}, DataType})(::Tuple{}, ::Vector{Float64}, ::Vararg{Vector{Float64}})
    @ Makie ~/.julia/packages/Makie/Iqcri/src/interfaces.jl:342
 [18] invokelatest(::Any, ::Any, ::Vararg{Any}; kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ Base ./essentials.jl:816
 [19] invokelatest(::Any, ::Any, ::Vararg{Any})
    @ Base ./essentials.jl:813
 [20] (::Observables.OnAny)(value::Any)
    @ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:415
 [21] #invokelatest#2
    @ ./essentials.jl:816 [inlined]
 [22] invokelatest
    @ ./essentials.jl:813 [inlined]
 [23] notify(observable::Observables.AbstractObservable)
    @ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:169
 [24] (::var"#8#9")(val::Int64)
    @ Main ./REPL[9]:4
 [25] #invokelatest#2
    @ ./essentials.jl:816 [inlined]
 [26] invokelatest
    @ ./essentials.jl:813 [inlined]
 [27] notify
    @ ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:169 [inlined]
 [28] setindex!(observable::Observable, val::Any)
    @ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:86
 [29] top-level scope
    @ REPL[10]:1

julia> versioninfo()
Julia Version 1.9.0-rc2
Commit 72aec423c2a (2023-04-01 10:41 UTC)
Platform Info:
  OS: macOS (arm64-apple-darwin21.3.0)
  CPU: 12 × Apple M2 Max
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-14.0.6 (ORCJIT, apple-m1)
  Threads: 8 on 8 virtual cores
Environment:
  JULIA_EDITOR = code

Yes, that is why I suggested opening an issue :). So this error does not suggest a mismatch between x and y lengths but between another vector of length 10 and the new boxes. This makes me suspect it is a bug instead of an error on the code.

I wrote a minimal example. The following is my version which doesn’t work.

using DataFrames, GLMakie

data = DataFrame(
            country = [fill(1, 20); fill(2, 20); fill(3, 20)],            
            population = [(rand() > 0.2) ? rand() : missing for i in 1:60],
            pop_growth = [(rand() > 0.2) ? rand() : missing for i in 1:60],
            gdp = [(rand() > 0.2) ? rand() : missing for i in 1:60],
            gdp_growth = [(rand() > 0.2) ? rand() : missing for i in 1:60]
)

macrovars = ["population", "pop_growth", "gdp", "gdp_growth"]
var = Observable("population")

df = @lift(data[:, Cols(:country, $var)] |> dropmissing)
xs = @lift($df[:, 1])
ys = @lift($df[:, 2])

fig = Figure();
ax = Axis(fig[1,1])

menu = Menu(fig, options=macrovars, default="gdp_growth");

fig[1:end, 0] = vgrid!(
                Label(fig, "Variable:", width=200, font=:bold),
                menuxvar;                
                tellheight=false, width=200)

boxplot!(ax, xs, ys)

on(menu.selection) do v  
    var[] = v 
    autolimits!(ax)
end
notify(menu. Selection)

fig

Right, just adding the traceback. And I think you’re right, it’s a bug, and maybe in boxplot (which I think is what you mean @aramirezreyes) , because the equivalent with scatter! works:

using DataFrames, GLMakie
df = DataFrame(:a => rand(50), :b => rand(50))
l = Observable(10)
xs = Observable(df[1:10,1])
ys = Observable(df[1:10,2])
fig = Figure()
ax = Axis(fig[1,1])
# bp = boxplot!(ax, xs,ys)
scatter!(ax, xs, ys)
on(l) do val
   xs.val = df[1:val,1]
   ys.val = df[1:val,2]
   notify(xs)
   notify(ys)
end
l[] = 20