Pluto.jl
Pluto.jl copied to clipboard
Does this physics interactive buttons can be done in Pluto.jl [1]
I have been looking for examples in Julia and Pluto to reproduce these simulations done in Java a long time ago.
The "Start" button starts the simulation. Immediately after pressing "Start", the button changes its label to "Stop", and the two buttons on the right "Step" and "New" are greyed out (disabled). The button "Step" would allow the user to advance the simulation one $\Delta t$ at a time. The "New" button just enable the input panel for a new entry and offers the user the options of "Initialize" (stores the changes) and "Reset" (clear all values preset to default).
After the simulation is stopped, the buttons "Step" and "New" are enabled again, allowing the user to resume the simulation by clicking step by step, or start over with a new set of values with New.
The "New" button will bring a new front end, with the buttons "Initialize" and "Reset".
I have been playing a bit with the buttons and the JS custom made button but I am not versed enough in JS to make the label change (from Initialize) to a new one (Start), and then to another one (Stop), when the state of the simulation is changed by the user.
Any thoughts?
I think I've got something:
using HypertextLiteral, PlutoUI
@htl("""
<!-- the wrapper span -->
<span>
<button id="left">Start</button>
<button id="center">Step</button>
<button id="right">New</button>
<script>
const wrapper_span = currentScript.parentElement
const leftButton = wrapper_span.querySelector("button#left")
const centerButton = wrapper_span.querySelector("button#center")
const rightButton = wrapper_span.querySelector("button#right")
// console.log(leftButton)
leftButton.addEventListener("click", (e) => {
console.log(leftButton.innerText + " button clicked")
wrapper_span.dispatchEvent(new CustomEvent("input"))
leftButton.innerText = "Stop"
centerButton.disabled = true
rightButton.disabled = true
setTimeout(function(){
//do what you need here
leftButton.innerText = "Start"
centerButton.disabled = false
rightButton.disabled = false
}, 4000);
e.preventDefault()
})
centerButton.addEventListener("click", (e) => {
console.log(centerButton.innerText + " button clicked")
wrapper_span.dispatchEvent(new CustomEvent("input"))
e.preventDefault()
})
rightButton.addEventListener("click", (e) => {
console.log(rightButton.innerText + " button clicked")
wrapper_span.dispatchEvent(new CustomEvent("input"))
e.preventDefault()
})
</script>
</span>
""")
This script will capture the click event for any of the three buttons. If the user clicks the leftButton, it will disable the buttons on the right for 4000 ms, then re-enable them right after. The leftButton also changes its inner text from Start to Stop for 4000 ms. After that, it is restored to Start.
Hey @f0nzie !
That looks super fun! I love the picture on the top left :) Consider sending the notebook file when you're done, I would love to see!
The solution you came up with is exactly what I would suggest, great! I would say that this JS widget should also fire tick events, just like PlutoUI.Clock(). So when you click Step, wrapper_span.dispatchEvent(new CustomEvent("input")) gets called once, but when you click Start, an interval starts that fires wrapper_span.dispatchEvent(new CustomEvent("input")) every 100ms.
You can also set wrapper_span.value = 20 or something, and then if you do @bind x @htl(...), then x will get the value 20 when the input event is fired.
In your Julia code, you can use x as the current timestamp. The easiest way to use it is:
@bind x MyTimeWidget()
begin
x # refernce for trigger
update_simulation!(state)
end
Stateless
Something I prefer is to make it stateless:
states = Dict(0 => initial_state)
get_state_at(time::Integer) =
get!(states, time) do
prev_state = get_state_at(time - 1)
step_simulation(prev_state)
end
@bind x MyTimeWidget()
get_state_at(x)
-fonzie
Hi @fonsp ,
Consider sending the notebook file when you're done
This is the notebook: https://gist.github.com/f0nzie/5e6b69f7e561751093632c637c247b4a
and a newer one, passing an argument: https://gist.github.com/f0nzie/acd51ebda291abb1c97c49dd64f1a142
You can also set
wrapper_span.value = 20or something, and then if you do@bind x @htl(...), thenxwill get the value 20 when the input event is fired.
What is the purpose of making the wrapper_span.value = 20?
If you are interested in seeing how the simulation buttons work in the original Java application, I am sharing a JAR file with a whole bunch of simulation demos. The one I showed in the post is in Chapter 12. See screenshot. To run the app, simply unzip the file. On the location where you have osp_csm.jar open a terminal and run java -jar osp_csm.jar. A launcher will open. At the bottom, select the tab "Programs". Select the chapter on the sidebar. Finally, run the app by double-clicking on ClustersApp or PercolationApp.