# Updating Makie plot based on button action

**URL:** <https://discourse.julialang.org/t/updating-makie-plot-based-on-button-action/111706>\
**Category:** Visualization\
**Tags:** question, makie, animations\
**Created:** [March 16, 2024, 7:12pm UTC](https://discourse.julialang.org/t/updating-makie-plot-based-on-button-action/111706 "2024-03-16T19:12:30Z")\
**Posts on this page:** 7\
**Page:** 1

<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:** [March 16, 2024, 7:12pm UTC](https://discourse.julialang.org/t/updating-makie-plot-based-on-button-action/111706/1 "2024-03-16T19:12:30Z")

</div>

I’m currently discovering interactions with `on` in `GLMakie`.

I’ve tried to make an example where

- I can start an animation by clicking on a button
- The animation performed as a loop  
however, it seems intermediate frames are not displayed and only the last frame shows directly.

Here is the example.

```julia
function plot_random_with_button()

    x = Observable(rand(10))
    y = Observable(rand(10))

    f = Figure()
    ax = Axis(f[1,1])
    p = plot!(x, y)

    but = Button(f[2, :], label="Animate", tellwidth=false)

    on(but.clicks, priority=1) do _
        but.label[] = "Animating"
        for _ in 1:10
            x[] = rand(10)
            y[] = rand(10)
            sleep(0.1)
        end
        but.label[] = "Animate"
        Consume(false)
    end
    return f
end

```

Is it due to some `Consume` or priority between events ?

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [March 16, 2024, 9:01pm UTC](https://discourse.julialang.org/t/updating-makie-plot-based-on-button-action/111706/2 "2024-03-16T21:01:09Z")

</div>

Try `@async` ing the for loop so that it can be interleaved with the rendering

---

<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:** [March 16, 2024, 11:03pm UTC](https://discourse.julialang.org/t/updating-makie-plot-based-on-button-action/111706/3 "2024-03-16T23:03:00Z")

</div>

Ah yes, that seems to do the trick. That works well for a limited `for`-loop. Would you know how to properly kill it assuming the `for`-loop is replaced by a `while flag`-loop ? I found [Stop/terminate a (sub)task started with @async - #16 by attdona](https://discourse.julialang.org/t/stop-terminate-a-sub-task-started-with-async/32193/16) but I could not scope things properly.

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [March 17, 2024, 6:36am UTC](https://discourse.julialang.org/t/updating-makie-plot-based-on-button-action/111706/4 "2024-03-17T06:36:06Z")

</div>

Make the flag something you can change from the outside so that you can break the loop whenever you want. It’s also good to use `Base.errormonitor` on the async task because otherwise it’ll swallow error messages

---

<div class="post-metadata">

**Author:** ![attdona](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/attdona/32/15859_2.png) [@attdona](https://discourse.julialang.org/u/attdona)\
**Post date:** [March 17, 2024, 8:28am UTC](https://discourse.julialang.org/t/updating-makie-plot-based-on-button-action/111706/5 "2024-03-17T08:28:32Z")

</div>

A easy way to control the lifecycle of a long running task may be to use [Visor.jl](https://github.com/cardo-org/Visor.jl).

If you don’t mind to give a try let me know if the Visor docs (should suffice the README in your case) is clear enough to get your case done.

---

<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:** [March 17, 2024, 9:00am UTC](https://discourse.julialang.org/t/updating-makie-plot-based-on-button-action/111706/6 "2024-03-17T09:00:46Z")

</div>

So my latest attempt is the following. I use two flags, one to trigger start an the other to trigger stop. Is that the kind of logic you had in mind ?  
I also limited the number of runnable tasks by watching the animate button click count.

```julia
function plot_random_with_button()

    x = Observable(rand(10))
    y = Observable(rand(10))

    f = Figure()
    ax = Axis(f[1, 1])
    p = plot!(x, y)

    buta = Button(f[2, :], label="Animate", tellwidth=false)
    buts = Button(f[3, :], label="Stop", tellwidth=false)

    onflag = Observable(false)
    offflag = Observable(true)

    on(buta.clicks, priority=1) do c
        if c == 1
            onflag.val = true
            @async while onflag.val
                x[] = rand(10)
                y[] = rand(10)
                sleep(0.1)
                offflag.val == true && break
            end
            offflag.val = false
        end
        Consume(true)
    end

    on(buts.clicks, priority=2) do _
        offflag.val = true
        buta.clicks = 0
    end
    return f
end

```

@attdona Thanks for the tip about `Visor`! I think it’s a bit much for what I’m working on right now, but I have another project that could really benefit from this.

**EDIT** I’ve tried coupling the former solution with a slider and `set_close_to!` but there is something weird. Two clicks on the `Stop` button are required while from the code, I would have expected only one to suffice. Do you have a recommendation about this behavior ?

```julia
function plot_random_with_button()
    f = Figure()
    ax = Axis(f[1, 1])
    
    buta = Button(f[2, :], label="Animate", tellwidth=false)
    buts = Button(f[3, :], label="Stop", tellwidth=false)
    sl = Slider(f[4, :], range=range(0, 2 * pi, 50))
    
    
    x = Observable(range(0, 2 * pi, 100))
    y = lift(x, sl.value) do x, p
        cos.(x .+ p)
    end
    p = plot!(x, y)

    onflag = Observable(false)
    offflag = Observable(true)

    on(buta.clicks, priority=1) do c
        if c == 1
            onflag.val = true
            @async for val in Iterators.cycle(sl.range[])
                sleep(1 / 30)
                set_close_to!(sl, val)
                offflag.val == true && break
            end
            offflag.val = false
        end
        Consume(true)
    end

    on(buts.clicks, priority=2) do _
        offflag.val = true
        set_close_to!(sl, 0)
        buta.clicks = 0
        Consume(true)
    end
    f
end

```

After one click

![image](https://global.discourse-cdn.com/julialang/original/3X/f/6/f6c33317d9418ea5a264a210bfaa73a42558cc37.png)

After two clicks

![image](https://global.discourse-cdn.com/julialang/original/3X/5/f/5f1bf76778cff9e61c3f01627a3d82c760ce6fe1.png)

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [March 17, 2024, 6:11pm UTC](https://discourse.julialang.org/t/updating-makie-plot-based-on-button-action/111706/7 "2024-03-17T18:11:23Z")

</div>

You have a race condition because you set your `offflag` to true and immediately readjust the slider, but the async task only gets to the flag after setting the slider again itself. This logic here should be a bit more robust, async code is always more complicated to reason about than sync code:

```julia
function plot_random_with_button()
    f = Figure()
    ax = Axis(f[1, 1])
    
    buta = Button(f[2, :], label="Animate", tellwidth=false)
    buts = Button(f[3, :], label="Stop", tellwidth=false)
    sl = Slider(f[4, :], range=range(0, 2 * pi, 50))
    
    
    x = Observable(range(0, 2 * pi, 100))
    y = lift(x, sl.value) do x, p
        cos.(x .+ p)
    end
    p = plot!(x, y)

    
    taskref = Ref{Union{Nothing,Task}}(nothing)
    should_close = Ref(false)

    on(buta.clicks) do _
        if taskref[] === nothing
            taskref[] = @async begin
                for val in Iterators.cycle(sl.range[])
                    sleep(1 / 30)
                    set_close_to!(sl, val)
                    should_close[] && break
                end
                should_close[] = false
            end
        end
        Consume(true)
    end

    on(buts.clicks) do _
        if taskref[] !== nothing && !should_close[]
            should_close[] = true
            wait(taskref[])
            taskref[] = nothing
            set_close_to!(sl, 0)
        end
        Consume(true)
    end
    f
end

plot_random_with_button()

```
