Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Bonito slider emit event only if mousedrag stops #256

Open
baxmittens opened this issue Sep 30, 2024 · 4 comments
Open

Make Bonito slider emit event only if mousedrag stops #256

baxmittens opened this issue Sep 30, 2024 · 4 comments

Comments

@baxmittens
Copy link

Hi there,

in the following example, the update should only take place after releasing the slider and not on any change. Is that possible?

using Bonito
using WGLMakie
import Bonito.TailwindDashboard as D

app = App() do session::Session
	sliders = [Bonito.Slider(-1:.1:1, value=0.0) for i = 1:6]
	vals = map!(Observable{Any}(), map(x->x.value, sliders)...) do x...
		return [x...]
	end
	f = Figure(size=(1000,200))
	ax = Axis(f[1,1])
	scatter!(ax, 1:6, vals) 
        sliderdiv(slider) = DOM.div(slider, "value: ", slider.value)
	flexrow = D.FlexCol(map(sliderdiv,sliders))
	return DOM.div(flexrow, f)
end

I appreciate any help you can give me.
Greetz max

@SimonDanisch
Copy link
Owner

You could do something like this:

slider_val = Observable(0.0; ignore_equal_values=true)
onany(ax.scene.events.mousebutton, slider.value) do mb, val  
    if mb.action == Mouse.release 
        slicer_val[] = val
    end
end

@baxmittens
Copy link
Author

I altered my example

using Bonito
using WGLMakie
import Bonito.TailwindDashboard as D

app = App() do session::Session
	sliders = [Bonito.Slider(-1:.1:1, value=0.0) for i = 1:6]

	f = Figure(size=(1000,200))
	ax = Axis(f[1,1])

	slider_vals1 = map!(Observable{Any}(), map(x->x.value, sliders)...) do x...
		return [x...]
	end
	slider_vals2 = map!(Observable{Any}(), map(x->x.value, sliders)...) do x...
		return [x...]
	end
	onany(ax.scene.events.mousebutton, slider_vals2) do mb, val  	
    	        if mb.action == Mouse.release 
        	      slider_vals1[] = val
    	        end
	end

	scatter!(ax, 1:6, slider_vals1) 
        sliderdiv(slider) = DOM.div(slider, "value: ", slider.value)
	flexrow = D.FlexCol(map(sliderdiv,sliders))
	return DOM.div(flexrow, f)
end

But this also doesn't work. It seems like since the sliders are not part of the scene the event
Makie.MouseButtonEvent(Makie.Mouse.left, Makie.Mouse.release)
is triggered each time I change a slider.

Another solution would be to use Makie.Slider but there I do not know how to register an interaction on a slider because it is no Axis. If I could do so, I could watch for the leftdragstop event.

@ConnectedSystems
Copy link

ConnectedSystems commented Feb 3, 2025

I've hit this issue and I think there's a small mismatch with the example @SimonDanisch gave.

The Slider controls come from Bonito but the example checks the Makie Axis scene/mouse events. I assume this requires the interaction to occur within/on the plotted object, which the slider is not part of? Although maybe it's a misunderstanding on my part.

Instead I've come up with this approach:

function update_display(cb, ax1)
    # Update plot.... 
   
    # See note below as to why we need this
    close(cb)
end

fig = Figure(size=(800, 600))
ax1 = Axis(fig[1, 1],
          title="Some figure",
          xlabel="Timestep",
          ylabel="Something"
)

slider_val = Bonito.Slider(0:1:100, value=5, label="example")
redraw_limit = nothing
onany(slider.value) do val
    if @isdefined(redraw_limit) && !isnothing(redraw_limit)
        close(redraw_limit)
    end
    
    # interval=0 is supposed to trigger the function call once 
    # but it doesn't seem to be case in this context.
    # Instead, we have to explictly close the timer within the callback
    redraw_limit = Timer(cb -> update_display(cb, ax1), 0.5; interval=0)
end

The logic here is that as the user moves the slider, it triggers the onany (or lift) which resets the timer. Only when the user stops interacting with the controls does the timer actually get a chance to trigger. Once triggered, the callback closes the timer.

@baxmittens
Copy link
Author

My solution was comparable, albeit somewhat less elegant. But for me it did the trick.

using Bonito
using WGLMakie
import Bonito.TailwindDashboard as D

frequence = 0.5

if @isdefined(taskisrunning) && taskisrunning[]
	taskisrunning[] = false
	sleep(2*frequence)
end

taskisrunning = Observable(false)
time_increment = Observable(0)

app = App() do session::Session
	global time_increment
	do_plot = Observable(0)
	sliders = [Bonito.Slider(-1:.1:1, value=0.0) for i = 1:6]
	old_x = zeros(Float64,6)
	on(time_increment) do next_timestep
		new_x = map(x->x.value[], sliders)
		if any(map((x,y)->!isapprox(x,y,atol=0.1), new_x, old_x))
			old_x = new_x
			do_plot[] += 1
		end	
	end
	vals = map!(Observable{Any}(), do_plot) do _
		return map(x->x.value[], sliders)
	end
	f = Figure(size=(1000,200))
	ax = Axis(f[1,1])
	scatter!(ax, 1:6, vals) 
        sliderdiv(slider) = DOM.div(slider, "value: ", slider.value)
	flexrow = D.FlexCol(map(sliderdiv,sliders))
	return DOM.div(flexrow, f)
end

task = @async try
	global taskisrunning, frequence
	taskisrunning[] = true
	while taskisrunning[]
		time_increment[] += 1
		sleep(frequence)
	end
	println("Task stopped")
catch err
	bt = catch_backtrace()
	showerror(stderr, err, bt)
end

app

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants