Skip to content

Commit 1aaf3f4

Browse files
committed
show color and linestyle of components
1 parent ad341f9 commit 1aaf3f4

File tree

3 files changed

+127
-14
lines changed

3 files changed

+127
-14
lines changed

NetworkDynamicsInspector/src/NetworkDynamicsInspector.jl

Lines changed: 109 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -352,17 +352,29 @@ function timeseries_card(app, session)
352352
placeholder="Select components",
353353
multi=true,
354354
option_to_string=_sidx_to_str,
355-
T=SymbolicIndex)
355+
T=SymbolicIndex,
356+
id=gendomid("compsel"))
356357
# comp_sel_dom = Grid(DOM.span("Components"), comp_sel; columns = "70px 1fr", align_items = "center")
357358
state_sel = MultiSelect(state_options, app.tsplot.states;
358359
placeholder="Select states",
359360
multi=true,
360-
T=Symbol)
361+
T=Symbol,
362+
id=gendomid("statesel"))
363+
364+
reset_button = Bonito.Button("Reset Color", style=Styles("margin-left"=>"10px"))
365+
on(reset_button.value) do _
366+
empty!(color_cache)
367+
empty!(linestyle_cache)
368+
notify(app.tsplot.selcomp)
369+
notify(app.tsplot.states)
370+
end
371+
372+
rel_toggle = ToggleSwitch(value=app.tsplot.rel, label="Rel to u0")
361373

362374
comp_state_sel_dom = Grid(
363-
DOM.span("Components"), comp_sel,
364-
DOM.span("States"), state_sel;
365-
columns = "auto 1fr", align_items = "center"
375+
DOM.span("Components"), comp_sel, reset_button,
376+
DOM.span("States"), state_sel, rel_toggle;
377+
columns = "auto 1fr auto", align_items = "center"
366378
)
367379

368380
# hl choice of elements in graphplot
@@ -377,8 +389,15 @@ function timeseries_card(app, session)
377389
####
378390
#### actual plot
379391
####
392+
COLORS = Makie.wong_colors()
393+
LINESTYLES = [:solid, :dot, :dash, :dashdot, :dashdotdot]
394+
LINESTYLES = ["", "", "--", "-⋅-", "-⋅⋅"]
380395
color_cache = Dict{Union{EIndex{Int,Nothing},VIndex{Int,Nothing}}, Int}()
381396
linestyle_cache = Dict{Symbol,Int}()
397+
398+
colorpairs = Observable{Vector{@NamedTuple{title::String,color::String}}}()
399+
lstylepairs = Observable{Vector{@NamedTuple{title::String,linestyle::String}}}()
400+
382401
on(app.tsplot.selcomp; update=true) do _sel
383402
@debug "TS: comp selection => update color_cache"
384403
for unused in setdiff(keys(color_cache), _sel)
@@ -388,6 +407,9 @@ function timeseries_card(app, session)
388407
i = _smallest_free(color_cache)
389408
color_cache[new] = i
390409
end
410+
colorpairs[] = [(; title=_sidx_to_str(k),
411+
color="#"*Colors.hex(getcycled(COLORS, v)))
412+
for (k,v) in color_cache]
391413
nothing
392414
end
393415
on(app.tsplot.states; update=true) do _states
@@ -399,9 +421,91 @@ function timeseries_card(app, session)
399421
i = _smallest_free(linestyle_cache)
400422
linestyle_cache[new] = i
401423
end
424+
lstylepairs[] = [(; title=repr(s), linestyle=getcycled(LINESTYLES, i))
425+
for (s,i) in linestyle_cache]
402426
nothing
403427
end
404428

429+
comp_ms_id = Bonito.JSString(comp_sel.id)
430+
state_ms_id = Bonito.JSString(state_sel.id)
431+
432+
js = js"""
433+
function colorListItems(items) {
434+
let styleTag = document.getElementById("dynamic-style-$(comp_ms_id)");
435+
436+
// Create the <style> tag if it doesn't exist
437+
if (!styleTag) {
438+
styleTag = document.createElement("style");
439+
styleTag.id = "dynamic-style-$(comp_ms_id)";
440+
document.head.appendChild(styleTag);
441+
}
442+
443+
// Clear previous styles
444+
styleTag.innerHTML = "";
445+
446+
// Generate new styles
447+
let styleContent = "";
448+
items.forEach(({ title, color }) => {
449+
styleContent += `#$(comp_ms_id) +span li[title='${title}']::after {
450+
content: 'xx';
451+
display: inline-block;
452+
padding-left: 0px 4px;
453+
background-color: ${color} !important;
454+
color: ${color} !important;
455+
border-left: 1px solid #aaa;
456+
cursor: default;
457+
}\n`;
458+
});
459+
460+
// Insert new styles
461+
styleTag.innerHTML = styleContent;
462+
}
463+
464+
colorListItems($(colorpairs).value);
465+
$(colorpairs).on((c) => {
466+
colorListItems(c);
467+
});
468+
469+
function linestyleListItems(items) {
470+
let styleTag = document.getElementById("dynamic-style-$(state_ms_id)");
471+
472+
// Create the <style> tag if it doesn't exist
473+
if (!styleTag) {
474+
styleTag = document.createElement("style");
475+
styleTag.id = "dynamic-style-$(state_ms_id)";
476+
document.head.appendChild(styleTag);
477+
}
478+
479+
// Clear previous styles
480+
styleTag.innerHTML = "";
481+
482+
// Generate new styles
483+
let styleContent = "";
484+
485+
if(items.length > 1) {
486+
items.forEach(({ title, linestyle }) => {
487+
styleContent += `#$(state_ms_id) +span li[title='${title}']::after {
488+
content: '${linestyle}';
489+
display: inline-block;
490+
padding-left: 5px;
491+
padding-right: 5px;
492+
color: inherit;
493+
border-left: 1px solid #aaa;
494+
font-size: smaller;
495+
cursor: default;
496+
}\n`;
497+
});
498+
499+
// Insert new styles
500+
styleTag.innerHTML = styleContent;
501+
}
502+
}
503+
linestyleListItems($(lstylepairs).value);
504+
$(lstylepairs).on((c) => {
505+
linestyleListItems(c);
506+
});
507+
"""
508+
evaljs(session, js)
405509

406510
fig, ax = with_theme(apptheme()) do
407511
fig = Figure()

NetworkDynamicsInspector/src/utils.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,6 @@ function NetworkDynamics.extract_nw(o::NamedTuple)
7474
error("No sol in NamedTuple")
7575
end
7676
end
77+
78+
getcycled(v::AbstractVector, i) = v[mod1(i, length(v))]
79+
gendomid(s::String) = replace(string(gensym("selectbox")), "#"=>"")

NetworkDynamicsInspector/src/widgets.jl

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,9 @@ struct MultiSelect{T}
324324
placeholder::String
325325
multi::Bool
326326
option_to_string::Any
327-
function MultiSelect(_options, _selection=nothing; T=Any, placeholder="", multi=true, option_to_string=repr)
327+
class::String
328+
id::String
329+
function MultiSelect(_options, _selection=nothing; T=Any, placeholder="", multi=true, option_to_string=repr, class="", id="")
328330
options = _options isa Observable ? _options : Observable{Vector{T}}(_options)
329331
selection = if isnothing(_selection)
330332
Observable(T[])
@@ -334,7 +336,14 @@ struct MultiSelect{T}
334336
else
335337
Observable{Vector{T}}(_selection)
336338
end
337-
new{T}(options, selection, placeholder, multi, option_to_string)
339+
_class = multi ? "bonito-select2-multi" : "bonito-select2-single"
340+
if class != ""
341+
_class *= " " * class
342+
end
343+
if id == ""
344+
id = gendomid("selectbox")
345+
end
346+
new{T}(options, selection, placeholder, multi, option_to_string, _class, id)
338347
end
339348
end
340349

@@ -375,21 +384,17 @@ function Bonito.jsrender(session::Session, multiselect::MultiSelect)
375384
end
376385

377386
# Create a multi-select element
378-
id = replace(string(gensym("selectbox")), "#"=>"")
379-
380387
style = Styles(
381388
"width" => "100%",
382-
# "font-family" => "monospace",
383389
)
384390

385-
class = multiselect.multi ? "bonito-select2-multi" : "bonito-select2-single"
386391
select = DOM.select(
387392
DOM.option();
388393
multiple = multiselect.multi,
389-
class,
394+
class=multiselect.class,
390395
# name = "states[]",
391396
style,
392-
id
397+
id=multiselect.id,
393398
)
394399

395400
container = DOM.div(
@@ -400,7 +405,7 @@ function Bonito.jsrender(session::Session, multiselect::MultiSelect)
400405
)
401406

402407
jqdocument = Bonito.JSString(raw"$(document)")
403-
jqselect = Bonito.JSString(raw"$('#"* id * raw"')")
408+
jqselect = Bonito.JSString(raw"$('#"* multiselect.id * raw"')")
404409

405410
esc = Bonito.JSString(raw"$")
406411
js_onload = js"""
@@ -472,6 +477,7 @@ function Bonito.jsrender(session::Session, multiselect::MultiSelect)
472477
473478
const sortedElements = new_order.map(i => jq_tags.eq(i));
474479
jq_tags_ul.append(sortedElements);
480+
console.log("Rendered selection")
475481
}
476482
function selectionHandler(e){
477483
const jq_select2 = $esc(this);

0 commit comments

Comments
 (0)