GLMakie change SliderGrid on Menu

I’m trying to create a small interactive plot/ dashboard to showcase different network models during a presentation. My goal is to have a menu in the top left to select a model, a series of sliders below it to adjust the model parameters, and a plotting area on the right. In the MWE below, I select the model Watts-Strogatz from the dropdown and everything works fine, the SliderGrid updates as it should. However, once I switch back to Erdos–Renyi, the sliders no longer update visually (even though you can change their values).

Any ideas on how to fix this?

NOTE: I’m only using GLMakie & Observables because that’s what my google searches have led me to, but if anybody knows a better approach I’m all ears!

MWE

using Makie, GLMakie, Observables, GraphMakie, Graphs, Graphs.SimpleGraphs
import NetworkLayout
GLMakie.activate!()

function updateNetwork(ax, method, value)

    @show method, value

    if method == "Erdos–Renyi"
        n, p = value
        g = erdos_renyi(n, p)
    elseif method == "Watts-Strogatz"
        n, k, β = value
        k = min(n - 1, k)
        g = watts_strogatz(n, k, β)
    end

    empty!(ax)
    xlims!(ax, -1.1, 1.1)
    ylims!(ax, -1.1, 1.1)
    graphplot!(ax, g; layout = NetworkLayout.Shell())
    # in a presentation I'd probably hide the axes but for now I keep them fixed.
    # Makie.hidedecorations!(ax)
    # Makie.hidespines!(ax)
    # ax.aspect = Makie.DataAspect()

end

function set_sliders!(fig, m)
    @show m
    @show contents(fig[2, 1])
    !isempty(contents(fig[2, 1])) && delete!(contents(fig[2, 1])[1])
    empty!(contents(fig[2, 1]))
    if m == "Erdos–Renyi"
        sg = SliderGrid(
            fig[2, 1],
            (label = "n", range = 3:7,     format = "{:d}",   startvalue = 3),
            (label = "p", range = 0:.05:1, format = "{:.2f}", startvalue = .1),
            width = 250,
            tellheight = false
        )

    elseif m == "Watts-Strogatz"
        sg = SliderGrid(
            fig[2, 1],
            (label = "n", range = 3:7,      format = "{:d}",   startvalue = 3),
            (label = "k", range = 0:8,      format = "{:d}",   startvalue = 1),
            (label = "β", range = 0:.05:10, format = "{:.2f}", startvalue = .1),
            width = 250,
            tellheight = false
        )
    end
    return sg
end

fig = Figure(fontsize = 30, resolution=(1000, 1000))

menu = Menu(fig[1, 1],
    options = ["Erdos–Renyi", "Watts-Strogatz"],
    default = "Erdos–Renyi"
)

slidergrid = map(menu.selection) do m
    println("changing slidergrid")
    sg = set_sliders!(fig, m)
    return sg
end

ax = Axis(fig[1:2, 2])
map(slidergrid) do sg

    sliderobservables = [async_latest(s.value) for s in sg.sliders]

    map(sliderobservables...) do value...

        println("changing plot")
        @show value
        m = menu.selection[]

        updateNetwork(ax, m, value)
    end
    sliderobservables
end

fig

This is probably because of some insufficient cleanup when delete!ing the slider grid. Deleting objects is currently not well tested so it’s likely for some observable connections to still be intact after deletion, which then mess with the mouse interaction.

I also had a similar issue when building a GUI with several successive steps, with each step requiring showing a new slider and deleting the old one.

In the end I resorted to a hacky workaround:

function hide_slider(s)
    s.blockscene.visible[] = false
    s.width[] = s.height[] = Fixed(0) # If not, the hidden slider seems to grab the mouse interaction instead of the visible one
end
1 Like

Yeah currently such workarounds are effective even if annoying. If somebody has time to look into it, I think one could improve the deletion step by step by registering more and more observables for disconnection when delete! is called. This is basically a lot of housekeeping.

1 Like

@touste many thanks for that idea, I wasn’t aware it was that straightforward to show and hide elements. In case it helps anybody in the future, below is the solution to the MWE that I implemented with @touste’s idea.

# pkg> st
# [e9467ef8] GLMakie v0.8.2
# [1ecd5474] GraphMakie v0.5.3
# [86223c79] Graphs v1.8.0
# [ee78f7c6] Makie v0.19.2
# [46757867] NetworkLayout v0.4.4
# [510215fc] Observables v0.5.4
using GLMakie, GraphMakie, Graphs, Graphs.SimpleGraphs, Makie, Observables
import NetworkLayout
GLMakie.activate!()

function modify_element!(x, visible, width, height)
    x.blockscene.visible[] = visible
    x.width[] = width
    x.height[] = height
end
hide_element!(x) = modify_element!(x, false, Fixed(0), Fixed(0))
hide_slider!(x) = hide_element!(x)
hide_label!(x)  = hide_element!(x)
show_slider!(x) = modify_element!(x, true, 300,     Auto(0)) # width matches initialize_sliders!
show_label!(x)  = modify_element!(x, true, Auto(0), Auto(0))

function show_slidergrid!(slidergrid)
    show_slider!( slidergrid)             # show the selected slidergrid
    show_slider!.(slidergrid.sliders)     # show the sliders within the selected grid
    show_label!.( slidergrid.labels)      # show the labels  within the selected grid
    show_label!.( slidergrid.valuelabels) # hides the right-labels of the non-selected grid
end

function hide_slidergrid!(slidergrid)
    hide_slider!( slidergrid)             # hides the non-selected slidergrid
    hide_slider!.(slidergrid.sliders)     # hides the sliders of the non-selected grid
    hide_label!.( slidergrid.labels)      # hides the left-labels  of the non-selected grid
    hide_label!.( slidergrid.valuelabels) # hides the right-labels of the non-selected grid
end

function toggle_slidergrid!(slidervec, i)

    show_slidergrid!(slidervec[i])
    for j in 1:i-1
        hide_slidergrid!(slidervec[j])
    end
    for j in i+1:length(slidervec)
        hide_slidergrid!(slidervec[j])
    end
end

function initialize_sliders!(fig)
    SliderGrid(
        fig[2, 1],
        (label = "n", range = 3:7,     format = "{:d}",   startvalue = 3),
        (label = "p", range = 0:.05:1, format = "{:.2f}", startvalue = .1),
        width = 250,
        halign = :left,
        tellheight = false
    )
    SliderGrid(
        fig[2, 1],
        (label = "n", range = 3:7,      format = "{:d}",   startvalue = 3),
        (label = "k", range = 0:8,      format = "{:d}",   startvalue = 1),
        (label = "β", range = 0:.05:10, format = "{:.2f}", startvalue = .1),
        width = 300,
        halign = :left,
        tellheight = false
    )

    slidervec = contents(fig[2, 1])
    toggle_slidergrid!(slidervec, 1)

    return slidervec

end

function updateNetwork!(ax, method, value)

    @show method, value

    if method == "Erdos–Renyi"
        n, p = value[1:2]
        g = erdos_renyi(n, p)
    elseif method == "Watts-Strogatz"
        n, k, β = value[3:end]
        k = min(n - 1, k)
        g = watts_strogatz(n, k, β)
    end

    println(Graphs.adjacency_matrix(g))
    empty!(ax)
    xlims!(ax, -1.1, 1.1)
    ylims!(ax, -1.1, 1.1)
    graphplot!(ax, g; layout = NetworkLayout.Shell())
    Makie.hidedecorations!(ax)
    Makie.hidespines!(ax)
    ax.aspect = Makie.DataAspect()

end

fig = Figure(fontsize = 30, resolution=(1000, 1000),
    figure_padding = (60, 5, 5, 5) # without padding the label left of the slider fall of the figure area and is hidden
)

menu = Menu(fig[1, 1],
    options = ["Erdos–Renyi", "Watts-Strogatz"],
    default = "Erdos–Renyi"
)

slidervec = initialize_sliders!(fig)

on(menu.i_selected) do i
    println("changing sliders")
    @show i
    toggle_slidergrid!(slidervec, i)
end

ax = Axis(fig[1:2, 2])
sliderobservables = [async_latest(s.value) for sg in slidervec for s in sg.sliders]

map(menu.selection, sliderobservables...) do m, value...

    println("changing plot")
    @show m, value
    updateNetwork!(ax, m, value)

end

fig
1 Like