# Makie cascading menus

**URL:** <https://discourse.julialang.org/t/makie-cascading-menus/136488>\
**Category:** Visualization\
**Tags:** makie\
**Created:** [April 1, 2026, 12:12am UTC](https://discourse.julialang.org/t/makie-cascading-menus/136488 "2026-04-01T00:12:27Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![Jake](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jake/32/46007_2.png) [@Jake](https://discourse.julialang.org/u/Jake)\
**Post date:** [April 1, 2026, 12:12am UTC](https://discourse.julialang.org/t/makie-cascading-menus/136488/1 "2026-04-01T00:12:27Z")

</div>

I am trying to build some cascading menus in Makie.jl. If we call them menu1, menu2, menu3, then the `options` in menu2 will depend upon the result of menu1, and likewise the `options` in menu3 will depend up the selection in menu2. A Minimum, non-working example is given below. When I uncomment line 53 (it has the comment to uncomment) then the example will not error.

I think what i need is a way to prevent Observables from menu1 to automatically affect menu2, until menu2 is called and similarly for menu3. Is this possible? And if so how?

```julia-auto
using GLMakie

kpitables = ["kpi_table1", "kpi_table2", "kpi_table3"] 
localidsmatrix = [
    ["id1", "id2", "id3"],
    ["id2", "id3", "id4"],
    ["id3", "id4", "id5"]
]

ids = ["id1", "id2", "id3", "id4", "id5"]
id = "default"
kpitableswithid = Observable{Any}(["default"])
kpis = Observable{Any}(["default"])
kpi = Observable{Any}(["default"])

fig = Figure(size = (800, 600))

menu1 = Menu(fig, options = ids) #, default = ids[1])

on(menu1.selection) do s
    id = s
    # println("Selected ID: $id")
    # find the tables with id
    tmp = String[]
    for (i, kpitable) in enumerate(kpitables)
        # @show (i, kpitable)
        localids = localidsmatrix[i]
        # println("Working on table: $kpitable with ID: $id and localids: $localids")
        if !isempty(localids) && in(id, localids)
            # println(kpitable)
            push!(tmp, kpitable)
        end
    end
    # @show tmp
    kpitableswithid[] = length(tmp) == 0 ? ["No tables with this ID"] : tmp
end

menu2 = Menu(fig, options = kpitableswithid)

# kpitable = Observable{Any}("default")
kpitable = "default"
kpismatrix = [
    ["kpi1", "kpi2", "kpi3", "kpi4", "kpi5", "kpi6", "kpi7", "kpi8", "kpi9", "kpi10"],
    ["kpi11", "kpi12", "kpi13", "kpi14", "kpi15", "kpi16", "kpi17", "kpi18", "kpi19", "kpi20"],
    ["kpi21", "kpi22", "kpi23", "kpi24", "kpi25", "kpi26", "kpi27", "kpi28", "kpi29", "kpi30"]
]

on(menu2.selection) do s
    kpitable = s
    # println("menu2 selection is: $kpitable")
    element = findfirst(x -> x == kpitable, kpitableswithid[])
    @show(element)
    # element = rand(1:3, 1) |> only # when I uncomment this line there is no error
    kpis[] = kpismatrix[element] # actual selection will be deterministic
end

menu3 = Menu(fig, options = kpis)

on(menu3.selection) do s
    kpi[] = s
    # @show(id, kpitableswithid[], kpi)
end

fig[1,1] = vgrid!(
    Label(fig, "Select Point ID", width = nothing), menu1,
    Label(fig, "Select KPI Table", width = nothing), menu2,
    Label(fig, "Select KPI", width = nothing), menu3;
    tellheight = false, width = 150
)

ax = Axis(fig[1, 2])

on(kpi) do kpi
    println("Items selected:", kpitableswithid, " ", kpitable, " ", kpi)
end

```

---

<div class="post-metadata">

**Author:** ![BambOoxX](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bambooxx/32/22179_2.png) [@BambOoxX](https://discourse.julialang.org/u/BambOoxX)\
**Post date:** [April 1, 2026, 7:47am UTC](https://discourse.julialang.org/t/makie-cascading-menus/136488/2 "2026-04-01T07:47:26Z")

</div>

In your `on` calls you should be able update the `options` for the next menus

```julia-auto
on(menu2.selection) do s
    #...
    menu3.options[] = new_vector_of_options
end

```

---

<div class="post-metadata">

**Author:** ![Jake](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jake/32/46007_2.png) [@Jake](https://discourse.julialang.org/u/Jake)\
**Post date:** [May 28, 2026, 11:28am UTC](https://discourse.julialang.org/t/makie-cascading-menus/136488/3 "2026-05-28T11:28:45Z")

</div>

I now have a working example, in case others want to try this.

```julia-auto
using GLMakie

# kpitables is the top level menu
# id? are items in the second level menu which are dependent on the selection of kpitables
# kpi? are items in the third level menu which are dependent on the selection of id

kpitables = ["kpi_table1", "kpi_table2", "kpi_table3"] 
localidsmatrix = [
    ["id1", "id2", "id3"],
    ["id2", "id3", "id4"],
    ["id3", "id4", "id5"]
]

ids = ["id1", "id2", "id3", "id4", "id5"]
id = "default"
kpitableswithid = Observable{Any}(["default"])
kpis = Observable{Any}(["default"])
kpi = Observable{Any}(["default"])

fig = Figure(size = (800, 600))

menu1 = Menu(fig, options = ids) #, default = ids[1])

on(menu1.selection) do s
    id = s
    println("Selected ID: $id with type ")
    # find the tables with id
    tmp = String[]
    for (i, kpitable) in enumerate(kpitables)
        # @show (i, kpitable)
        localids = localidsmatrix[i]
        # println("Working on table: $kpitable with ID: $id and localids: $localids")
        if !isempty(localids) && in(id, localids)
            # println(kpitable)
            push!(tmp, kpitable)
        end
    end
    # @show tmp
    kpitableswithid[] = length(tmp) == 0 ? ["No tables with this ID"] : tmp
    menu2.options[] = length(tmp) == 0 ? ["No tables with this ID"] : tmp
end

menu2 = Menu(fig, options = ["Select"])

# kpitable = Observable{Any}("default")
kpitable = "default"
kpismatrix = [
    ["kpi1", "kpi2", "kpi3", "kpi4", "kpi5", "kpi6", "kpi7", "kpi8", "kpi9", "kpi10"],
    ["kpi11", "kpi12", "kpi13", "kpi14", "kpi15", "kpi16", "kpi17", "kpi18", "kpi19", "kpi20"],
    ["kpi21", "kpi22", "kpi23", "kpi24", "kpi25", "kpi26", "kpi27", "kpi28", "kpi29", "kpi30"]
]

on(menu2.selection) do s
    global kpitable = s
    println("menu2 selection is: $s and kpitable is: $kpitable of type $(typeof(kpitable))")
    element = findfirst(x -> x == kpitable, kpitableswithid[])
    @show(element)
    # element = rand(1:3, 1) |> only # when I uncomment this line there is no error
    kpis[] = isnothing(element) ? ["No KPIs available"] : kpismatrix[element] 
    menu3.options[] = isnothing(element) ? ["No KPIs available"] : kpismatrix[element] 
end

menu3 = Menu(fig, options = ["Select"])

on(menu3.selection) do s
    kpi[] = s
    # @show(id, kpitableswithid[], kpi)
end

fig[1,1] = vgrid!(
    Label(fig, "Select Point ID", width = nothing), menu1,
    Label(fig, "Select KPI Table", width = nothing), menu2,
    Label(fig, "Select KPI", width = nothing), menu3;
    tellheight = false, width = 150
)

ax = Axis(fig[1, 2])

on(kpi) do kpi
    println("Items selected:", " ", kpitable, " ", kpi)
    # Note that global kpitable is not updated in menu2 but kpi[] is in menu3
end

```
