SCJ_Projekt/src/visualization.jl

264 lines
7.7 KiB
Julia

module Visualization
include("utils/constants.jl")
include("utils/templates.jl")
include("gray_scott_solver.jl")
include("fhn_solver.jl")
using Observables, Makie, GLMakie
using .Constants
using .Templates
using .GrayScottSolver: step_gray_scott!
using .FHNSolver: step_fhn!
const STEP_FUNCTIONS = Dict(
:gray_scott => GrayScottSolver.step_gray_scott!,
:fhn => FHNSolver.step_fhn!
)
function coord_to_index(x, y, N)
ix = clamp(round(Int, x), 1, N)
iy = clamp(round(Int, y), 1, N)
return ix, iy
end
"""
reset(U, V, heat_obs)
Resets heatmap to original state by replacing each cell.
Currently only places a square in the center
# Arguments
- `U`: Matrix filled with ones
- `V`: Empty matrix filled with zeros
- `heat_obs`: Heatmap observable
# Returns
- ``: resetted heatmap observable
"""
function reset!(U, V, heat_obs)
U .= 1.0
V .= 0.0
center = size(U, 1) ÷ 2
radius = 10
U[center-radius:center+radius, center-radius:center+radius] .= 0.50
V[center-radius:center+radius, center-radius:center+radius] .= 0.25
heat_obs[] = copy(U)
end
function param_box!(grid, row, labeltxt, observable::Observable; col=1)
label_cell = 2 * col - 1
textbox_cell = 2 * col
Label(grid[row, label_cell], labeltxt)
box = Textbox(grid[row, textbox_cell], validator=Float64, width=50, placeholder=labeltxt, stored_string="$(observable[])")
on(box.stored_string) do s
try
observable[] = parse(Float64, s)
println("changed $labeltxt to $s")
catch
@warn "Invalid input for $labeltxt: $s"
end
end
on(observable) do val
box.displayed_string[] = string(val)
end
end
function multi_step!(state, n_steps, heat_obs::Observable, params_obs::Observable; step_method=step_gray_scott!, dx=1)
for _ in 1:n_steps
heat_obs[] = step_method(state[1], state[2], params_obs; dx=1)
end
end
function build_ui(U, V, param_obs_map::NamedTuple, params_obs, heat_obs)
reset!(U, V, heat_obs)
fig = Figure(size=(1300, 950))
gh = GridLayout(fig[1, 1])
dropdown = Menu(fig, options=collect(zip(["Gray-Scott", "FHN"], [:gray_scott, :fhn])))
gh[1, 1] = dropdown
ax = Axis(gh[2, 1])
hm = Makie.heatmap!(ax, heat_obs, colormap=:viridis)
deactivate_interaction!(ax, :rectanglezoom)
ax.aspect = DataAspect()
run_label = Observable{String}("Run")
stepsize = Observable(30)
spoint = select_point(ax.scene)
step_method = Observable{Function}(step_gray_scott!)
# Controls
fig[2, 1] = buttongrid = GridLayout(ax.scene, tellwidth=false)
btn_step = Button(buttongrid[1, 1], width=50, label="Step")
btn_start = Button(buttongrid[1, 2], width=50, label=run_label)
btn_reset = Button(buttongrid[1, 3], width=50, label="Reset")
slidergrid = SliderGrid(fig[3, 1], (label="Speed", range=1:1:50, format="{}x", width=750, startvalue=stepsize[], tellwidth=false))
speed_slider = slidergrid.sliders[1].value
gh[1, 2] = templategrid = GridLayout(ax.scene, tellwidth=false)
templategrid[1, 1] = Label(fig, "Templates:")
btn_waves = Button(templategrid[1, 2], width=100, label="Wave Pattern")
btn_cow = Button(templategrid[1, 3], width=100, label="Cow Spots")
btn_cheetah = Button(templategrid[1, 4], width=100, label="Cheetah Spots")
# place all the parameter boxes
gh[2, 2] = textboxgrid = GridLayout(ax.scene, tellwidth=false)
# Create and assign column header labels
textboxgrid[1, 1] = Label(fig, "GrayScott:", halign=:center)
textboxgrid[1, 3] = Label(fig, "FHN:", halign=:center)
# GrayScott column (col 1)
param_box!(textboxgrid, 2, "Du", param_obs_map.Du, col=1)
param_box!(textboxgrid, 3, "Dv", param_obs_map.Dv, col=1)
param_box!(textboxgrid, 4, "Feed", param_obs_map.F, col=1)
param_box!(textboxgrid, 5, "Kill", param_obs_map.k, col=1)
# FHN column (col 2)
param_box!(textboxgrid, 2, "Du", param_obs_map.Du, col=2)
param_box!(textboxgrid, 3, "Dv", param_obs_map.Dv, col=2)
param_box!(textboxgrid, 4, "ϵ", param_obs_map.ϵ, col=2)
param_box!(textboxgrid, 5, "a", param_obs_map.a, col=2)
param_box!(textboxgrid, 6, "b", param_obs_map.b, col=2)
rowsize!(gh, 1, Relative(0.2)) # small row for the menu
rowsize!(gh, 2, Relative(0.8))
for c in 1:4
colsize!(textboxgrid, c, Relative(0.1))
end
# ======== Events ========
# Timer and state for animation
running = Observable(false)
on(running) do r
run_label[] = r ? "Pause" : "Run"
end
on(speed_slider) do s
try
stepsize[] = s
println("Changed stepsize to $s")
catch
@warn "Invalid input for $s"
end
end
# Control Listeners
on(btn_step.clicks) do _
multi_step!((U, V), stepsize[] * 5, heat_obs, params_obs; step_method=step_method[])
end
on(btn_start.clicks) do _
running[] = !running[]
end
on(btn_start.clicks) do _
@async while running[]
multi_step!((U, V), stepsize[], heat_obs, params_obs; step_method=step_method[])
sleep(0.0015) # ~20 FPS
end
end
on(btn_reset.clicks) do _
running[] = false
hm.colormap[] = :viridis
reset!(U, V, heat_obs)
end
# Template Control
on(btn_waves.clicks) do _
# add column to center of matrix
U, V = Templates.blocks_ic(params_obs[].N)
hm.colormap[] = :viridis
# change params
param_obs_map.Du[] = 0.0008
param_obs_map.Dv[] = 0.1
param_obs_map.ϵ[] = 0.01
param_obs_map.a[] = 0.5
param_obs_map.b[] = 0.15
heat_obs[] = copy(U)
end
# Template Control
on(btn_cow.clicks) do _
# fill matrix with random noise
U = 0.05 .* (2 .* rand(params_obs[].N, params_obs[].N) .- 1) # noise in [-0.05, 0.05]
V = 0.05 .* (2 .* rand(params_obs[].N, params_obs[].N) .- 1)
hm.colormap[] = :grayC
# change params
param_obs_map.Du[] = 1.0
param_obs_map.Dv[] = 10.0
param_obs_map.ϵ[] = 0.01
param_obs_map.a[] = -0.1
param_obs_map.b[] = 1.2
heat_obs[] = copy(U)
end
on(btn_cheetah.clicks) do _
# fill matrix with random noise
U = 0.05 .* (2 .* rand(params_obs[].N, params_obs[].N) .- 1) # noise in [-0.05, 0.05]
V = 0.05 .* (2 .* rand(params_obs[].N, params_obs[].N) .- 1)
hm.colormap[] = cgrad(:lajolla, rev=true)
# change params
param_obs_map.Du[] = 0.182
param_obs_map.Dv[] = 0.5
param_obs_map.ϵ[] = 0.05
param_obs_map.a[] = 0.2
param_obs_map.b[] = 2.0
heat_obs[] = copy(U)
end
on(spoint) do pt
r = 5
if pt === nothing
return
end
x, y = pt
i, j = coord_to_index(x, y, params_obs[].N)
# get corners of square that will get filled with concentration
imin = max(i - r, 1)
imax = min(i + r, params_obs[].N)
jmin = max(j - r, 1)
jmax = min(j + r, params_obs[].N)
# set disbalanced concentration of U and V
U[imin:imax, jmin:jmax] .= 0.5
V[imin:imax, jmin:jmax] .= 0.25
heat_obs[] = copy(U)
end
on(dropdown.selection) do sel
if sel isa Tuple
label, selected_model = sel
else
selected_model = sel
label = string(sel)
end
@info "Selected model: $label$selected_model"
if haskey(STEP_FUNCTIONS, selected_model)
step_method[] = STEP_FUNCTIONS[selected_model]
else
@warn "Unknown model selected: $selected_model"
end
end
return fig
end
export build_ui
end