Skip to content

Commit a1d4cb8

Browse files
committed
keep async plots in order per axis
1 parent 1c87a2b commit a1d4cb8

File tree

3 files changed

+82
-33
lines changed

3 files changed

+82
-33
lines changed

NetworkDynamicsInspector/src/appstate.jl

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ struct AppState
4040
active_tsplot::Observable{String}
4141
graphplot::GraphPlot
4242
tsplots::Observable{OrderedDict{String, TimeseriesPlot}}
43-
_plotqueue::Channel{Task}
43+
_tsplotscache::IdDict{TimeseriesPlot,@NamedTuple{
44+
card::Bonito.Hyperscript.Node{Bonito.Hyperscript.HTMLSVG},
45+
observerfunctions::Vector{Observables.ObserverFunction},
46+
plotqueue::Channel{Task}
47+
}}
4448
end
4549
function AppState(sol::SciMLBase.AbstractODESolution)
4650
t = sol.t[begin]
@@ -51,8 +55,12 @@ function AppState(sol::SciMLBase.AbstractODESolution)
5155
"ts-1" => TimeseriesPlot(),
5256
)
5357
active_tsplot = "ts-1"
54-
_plotqueue = Channel{Task}(Inf)
55-
AppState(sol, t, tmin, tmax, active_tsplot, graphplot, tsplots, _plotqueue)
58+
_tsplotscache = IdDict{TimeseriesPlot,@NamedTuple{
59+
card::Bonito.Hyperscript.Node{Bonito.Hyperscript.HTMLSVG},
60+
observerfunctions::Vector{Observables.ObserverFunction},
61+
plotqueue::Channel{Task}
62+
}}()
63+
AppState(sol, t, tmin, tmax, active_tsplot, graphplot, tsplots, _tsplotscache)
5664
end
5765

5866
function free_ts_key()

NetworkDynamicsInspector/src/timeseries.jl

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -44,37 +44,46 @@ function timeseries_col(app, session)
4444
end
4545

4646
function timeseries_cards(app, session)
47-
# we store the cards based on objid of TSPlot, otherwise overwrite with same key
48-
# leads to problems
49-
cards = OrderedDict{UInt, Bonito.Hyperscript.Node{Bonito.Hyperscript.HTMLSVG}}()
50-
observables = OrderedDict{UInt, Vector{Observables.ObserverFunction}}()
5147
container = Observable{Bonito.Hyperscript.Node{Bonito.Hyperscript.HTMLSVG}}()
5248

49+
# update the timeseries-sstacke based on the tsplots in appstate
5350
on(app.tsplots; update=true) do _tsplots
5451
@debug "TS: app.tsplots => update timeseries cards"
55-
id_to_key = Dict(Base.objectid(val) => key for (key, val) in _tsplots)
56-
newids = Base.objectid.(values(_tsplots)) # collect to preserv order on setdiff
57-
knownids = keys(cards)
58-
59-
for delid in setdiff(knownids, newids)
60-
Observables.off.(observables[delid]) # deactivate allobservables from card
61-
delete!(observables, delid)
62-
delete!(cards, delid)
52+
# this cache contains all the card, the observer functions and the plotqueue
53+
cache = app._tsplotscache
54+
55+
known_tsplots = collect(keys(cache))
56+
current_tsplots = collect(values(_tsplots))
57+
58+
# do nothing if the TSPlots objects themselve did not change
59+
Set(known_tsplots) == Set(current_tsplots) && return
60+
61+
# del_tsplots = setdiff(known_tsplots, current_tsplots)
62+
# new_tsplots = setdiff(current_tsplots, known_tsplots)
63+
# FIXME: it is not possible to redisplay the same tsplot, recreate all
64+
del_tsplots = known_tsplots
65+
new_tsplots = current_tsplots
66+
67+
for del in del_tsplots
68+
(; card, observerfunctions, plotqueue) = cache[del]
69+
close(plotqueue) # close plotqueue
70+
Observables.off.(observerfunctions) # disable all observer functions
71+
delete!(cache, del) # delete from cache
6372
end
64-
for newid in setdiff(newids, knownids)
65-
card, obsf = timeseries_card(app, id_to_key[newid], session)
66-
cards[newid] = card
67-
observables[newid] = obsf
68-
end
69-
if collect(keys(cards)) != newids
70-
@warn "The keys do not match: $(keys(cards)) vs $(newids)"
73+
for new in new_tsplots
74+
key = only([key for (key, value) in _tsplots if value == new])
75+
ntup = timeseries_card(app, key, session)
76+
cache[new] = ntup
7177
end
7278

73-
container[] = DOM.div(values(cards)...; class="timeseries-stack")
79+
cards = [cache[ts].card for ts in values(_tsplots)]
80+
# update the display by notifying the content
81+
container[] = DOM.div(cards; class="timeseries-stack")
7482

7583
nothing
7684
end
7785

86+
# update the selected components in the graphplot when the active tsplot changes
7887
on(app.active_tsplot; update=true) do active
7988
activesel = app.tsplots[][active].selcomp[]
8089
app.graphplot._selcomp[] = activesel
@@ -303,6 +312,7 @@ function timeseries_card(app, key, session)
303312
ts = Observable(collect(range(app.sol[].t[begin], app.sol[].t[end], length=1000)))
304313
refined_xlims = Ref((NaN, NaN))
305314
onany_delayed(ax.finallimits; delay=0.5) do axlims
315+
@debug "$key: Adapt ts vector"
306316
sollims = (app.sol[].t[begin], app.sol[].t[end])
307317
xlims = (axlims.origin[1], axlims.origin[1] + axlims.widths[1])
308318
if xlims != refined_xlims[]
@@ -354,34 +364,37 @@ function timeseries_card(app, key, session)
354364

355365
replot = Observable{Nothing}(nothing)
356366

367+
# create queue for async plot updates
368+
plotqueue = _plot_queue(key)
369+
357370
# store the idxs for which the autolmits where last set
358371
last_autolimits = Ref((eltype(valid_idxs)(), tsplot.rel[]))
359372
# plot the thing
360373
onany(data, replot; update=true) do _dat, _
361-
task = @async begin
374+
task = @task begin
375+
@debug "$key: Launch plot for valid_idxs[]"
362376
try
363377
empty!(ax)
364378
vlines!(ax.scene, app.t; color=:black)
365379
for (idx, y) in zip(valid_idxs[], data[])
366380
color = begin
367-
key = idx isa VIndex ? VIndex(idx.compidx) : EIndex(idx.compidx)
368-
getcycled(COLORS, color_cache[key])
381+
idxkey = idx isa VIndex ? VIndex(idx.compidx) : EIndex(idx.compidx)
382+
getcycled(COLORS, color_cache[idxkey])
369383
end
370384
linestyle = getcycled(LINESTYLES, linestyle_cache[idx.subidx])
371385
lines!(ax.scene, ts[], y; label=string(idx), color, linestyle)
372386
# scatterlines!(ax, ts[], y; label=string(idx), color, linestyle)
373387
end
374-
# if last_autolimits[][1] != valid_idxs[] || last_autolimits[][2] != tsplot.rel[]
375388
if last_autolimits[] != (valid_idxs[], tsplot.rel[])
376389
autolimits!(ax)
377390
xlims!(ax, (app.tmin[], app.tmax[]))
378391
last_autolimits[] = (copy(valid_idxs[]), tsplot.rel[])
379392
end
380393
catch e
381-
@error "Plotting failed" e
394+
@error "$key: Plotting failed for idx $(valid_idxs[])" e
382395
end
383396
end
384-
push!(app._plotqueue, task)
397+
put!(plotqueue, task)
385398
nothing
386399
end |> track_obsf
387400

@@ -432,8 +445,31 @@ function timeseries_card(app, key, session)
432445
id=key
433446
)
434447

435-
return card, obsf
448+
return (card, observerfunctions=obsf, plotqueue)
449+
end
450+
function _plot_queue(key)
451+
ch = Channel{Task}(Inf; spawn=true) do ch
452+
while isopen(ch)
453+
t = try
454+
fetch(ch)
455+
catch e
456+
if !e isa InvalidStateException && e.msg == "Channel is closed."
457+
@error "$key: Error while waiting for Task: $e"
458+
end
459+
break
460+
end
461+
try
462+
wait(schedule(t))
463+
catch e
464+
@warn "$key: Error in task: $e"
465+
end
466+
rmt = take!(ch) # remove the executed task
467+
@assert istaskdone(rmt)
468+
end
469+
@info "Plot queue for $key closed"
470+
end
436471
end
472+
437473
function closebutton(app, key)
438474
button = Bonito.Button("×", class="close-button")
439475
on(button.value) do _

NetworkDynamicsInspector/src/utils.jl

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,14 @@ end
144144

145145
function wait_for()
146146
isnothing(APPSTATE[]) && return
147-
queue = APPSTATE[]._plotqueue
148-
while !isempty(queue)
149-
wait(take!(queue))
147+
for cache in values(APPSTATE[]._tsplotscache)
148+
wait_for(cache.plotqueue)
150149
end
151150
nothing
152151
end
152+
153+
function wait_for(queue)
154+
while isopen(queue) && !isempty(queue)
155+
sleep(0.01)
156+
end
157+
end

0 commit comments

Comments
 (0)