Context:
I’m trying to recreate this demo Collide force | D3 by Observable in Makie.jl with GraphPlayground.jl (and do other demos from there too…)
Issue:
What’s are different ways of driving the update loop in GLMakie.jl ?
Things I’ve tried:
- Create an async task to update the positions
- Buttons to start/stop the async task based on Updating Makie plot based on button action - #7 by jules
- Create a new screen and use render_tick to get notified when there is a new frame.
- Figure out the current screen for the (scene.current_screens[1].render_tick) event.
The downsides to 1 and 2 are that I have to manage tasks.
The downside to 3 is that I have to start a new window.
The downside to 4 is that it seems really slow. Also, I have to call display on the scene first to get current_screens to instantiate.
Are there other ways I should think about handling this?
Thanks!
David
My solution right now for 3 is below. (4 is basically the same, but we don’t have to create a new screen and use scene.current_screens[1] after calling display…)
using GLMakie
using StableRNGs, GeometryBasics, GraphPlayground, GLMakie
# ## Setup the simulation
rng = StableRNG(1)
n = 200
width = 564
k = width/n
radiusdist = k:4k
radius = rand(rng, radiusdist, n)
#radius[1] = 10 # make the first point a fixed point
pos = [Point2f0(rand(rng, 0:width), rand(rng, 0:width)) for _ in 1:n]
pos = pos .- sum(pos) / length(pos)
sim = ForceSimulation(pos, eachindex(pos);
position=PositionForce(;strength=0.01),
collide=CollisionForce(;radius=radius.+1,iterations=3),
charge=ManyBodyForce(strength=(i) -> i==1 ? -width*2/3 : 0.0, theta2=0.82),
alpha=GraphPlayground.CoolingStepper(alpha_target=0.3),
velocity_decay=0.9,)
# ## Setup the screen and scene
screen2 = GLMakie.Screen(framerate=60.0, vsync=true, render_on_demand=false, title="GLMakie Playground")
scene = Scene(camera = campixel!, size = (width, width))
GLMakie.display_scene!(screen2, scene)
# ## setup links between all the display elements and simulation
pos2 = sim.positions[2:end]
posobs = Observable(pos2 .+ Point2f0(width/2, width/2))
f = scatter!(scene, posobs, markersize=pi*radius[2:end]/1.11, markerspace=:pixel, strokewidth=0, strokecolor=:white,
)
velobs = Observable(sim.velocities[2:end])
vscale = Observable(1.0)
# g = arrows!(scene, posobs, velobs; lengthscale = vscale)
mppos = Observable(Point2f0(0,0))
scatter!(scene, mppos, markersize=pi*100/1.11, color=:grey, strokewidth=0, markerspace=:pixel)
on(events(scene).mouseposition) do mp
if mp[1] < 0 || mp[1] > width || mp[2] < 0 || mp[2] > width
sim.alpha.alpha_target = 0.0
else
sim.alpha.alpha_target = 0.3
end
fixnode!(sim, 1, mp .- Point2f0(width/2, width/2))
notify(posobs)
end
# ## Add the simulation update loop on render_tick.
on(screen2.render_tick) do _
#println("Frame")
step!(sim)
posobs[] = sim.positions[2:end] .+ Point2f0(width/2, width/2)
velobs[] = sim.velocities[2:end]
vscale[] = 10*sim.alpha.alpha
mppos[] = sim.positions[1] .+ Point2f0(width/2, width/2)
end
display(scene)
## Old code using my own task to set updates...
# This was an older approach where I just had a loop that tried to update at 60fps and changed the positions
# with render-on-demand.
# But then I have to manage my own update task, which is annoying...
#=
task, close = GraphPlayground.updateloop(scene) do dt
step!(sim)
posobs[] = sim.positions[2:end] .+ Point2f0(width/2, width/2)
velobs[] = sim.velocities[2:end]
vscale[] = 10*sim.alpha.alpha
mppos[] = sim.positions[1] .+ Point2f0(width/2, width/2)
return true
end
=#