Skip to content
Snippets Groups Projects

Viz devl 28 01.2021

Merged Jürgen Dammers requested to merge viz-devl-28-01.2021 into master
3 files
+ 443
304
Compare changes
  • Side-by-side
  • Inline
Files
3
+ 261
219
@@ -26,7 +26,6 @@ from dash.exceptions import PreventUpdate
import dash_bootstrap_components as dbc
import dash_daq as daq
import plotly.graph_objects as go
from plotly.subplots import make_subplots
@@ -48,17 +47,20 @@ logger = get_logger()
__version__= "2021-01-21-001"
class _BASE(object):
def __init__( self,groups=None,title="Test",logo=None,color='primary'):
self.groups = groups
class _FIGURE_DATA(_SLOTS):
__slots__=("id","data","times","label","labels","type","info","ics","topo_img","chop","figure","page_spacing","plot_spacing","downsampling","cols","rows","_cls_name")
@property
def n_channels( self ): return self.data.shape[0]
self.title = title
if logo:
self.logo=logo
else:
self.logo = "https://images.plot.ly/logo/new-branding/plotly-logomark.png"
class _BASE(_SLOTS):
__slots__ = ('app','title','logo','href','color','colors','theme','groups',
'_times','_figure_data','_files','_file','_chop','_chops','_chop',
'_id_toggle_button_group','_id_selection_box','_navbar_href')
def __init__( self,**kwargs):
super().__init__(**kwargs)
self._cls_name="APP BASE"
self.color = color
self.colors = {
'dark-blue-grey' : 'rgb(62, 64, 76)',
'medium-blue-grey' : 'rgb(77, 79, 91)',
@@ -70,8 +72,6 @@ class _BASE(object):
'dark-pink-red' : 'rgb(247, 80, 99)',
'white' : 'rgb(251, 251, 252)',
'light-grey' : 'rgb(208, 206, 206)'}
self.theme = {
'dark': True,
'detail': self.colors['dark-pink-red'],
@@ -79,11 +79,11 @@ class _BASE(object):
'secondary': self.colors['light-grey'],
}
self.id_toggle_button_group = "toggle-button-group"
self.id_selection_box = "selection-box"
self._id_toggle_button_group = "toggle-button-group"
self._id_selection_box = "selection-box"
def init_group_toggle_buttons(self,groups=None,color="info"):
id = self.id_toggle_button_group
id = self._id_toggle_button_group
tgls= [ dbc.Col( html.H3( dbc.Badge("Groups",color=color,className="mr-2")),width="auto")]
for g in groups:
tgls.append( html.H3( g.upper(),style={'color':self.colors['white'],'font-weight':'bold'})),
@@ -94,11 +94,13 @@ class _BASE(object):
if placeholder is None:
placeholder = f'select a {label}'
if options is None:
options=["A","B","C"]
if not isinstance( options,(dict)):
options=[] #"A","B","C"]
if isinstance( options,(list)):
options= [ {'label': options[i],'value':i+1} for i in range(len(options)) ]
elif isinstance(options,(int)):
options= [ {'label': options[i],'value':i} for i in arange(options) ]
id = f"{ label.lower() }-{self.id_selection_box}"
id = f"{ label.lower() }-{self._id_selection_box}"
return dbc.Row([
dbc.Col(dbc.Button(label,id=f"{id}-bt",color=color,className="ml-2"),width="auto"),
@@ -113,61 +115,81 @@ class _BASE(object):
dbc.Col(html.B(),width=1)
],className="ml-auto flex-nowrap mt-3 mt-md-0 ",justify="start",form=True)
'''
#type=f'{ label.lower() }-{self.id_selection_box}'
tp=self.id_selection_box
return dbc.Row([
dbc.Col(dbc.Button(label,id={'type':f"{tp}-bt",'index':index},color=color,className="ml-2"),width="auto"),
#ToDo dbc.Col(dbc.Spinner(color="info",id=id+"SP"),width="auto"),
dbc.Col(dbc.Select(id={'type':f"{tp}-sl",'index':index},options=options,
#value=options[0]['value'],
placeholder=placeholder,
style={"min-width":wmin,"display":'flex'}),width='auto' ),
dbc.Col(html.H2( dbc.Button("<",id={'type':f"{tp}-bt-left",'index':index},n_clicks=0,className="mr-2")),width="auto"),
dbc.Col(html.H2([dbc.Badge(children=[html.H4("000")],id={'type':f"{tp}-badge",'index':index},color='info',className="mr-1") ]),width="auto"),
dbc.Col(html.H2(dbc.Button(">",id={'type':f"{tp}-bt-right",'index':index},n_clicks=0,className="mr-2")),width="auto"),
dbc.Col(html.B(),width=1)
],className="ml-auto flex-nowrap mt-3 mt-md-0 ",justify="start",form=True)
'''
class PLOT_DATA(_SLOTS):
__slots__=("id","data","label","labels","info","ics","topo_img","chop","figure","page_spacing","plot_spacing")
@property
def n_channels( self ): return self.data.shape[0]
class DCNN_APP(_BASE):
def __init__(self,groups=None,data_ics=None,data_aux=None,times=None,files=None,chops=None,title="gDCNN JuMEG",logo=None,color='primary',href=None,app=app,update_function=None):
super().__init__(groups=groups,title=title,logo=logo,color=color)
self.files = files
self.chops = chops
self.file = None
self.chop = None
self.app = app
self.update_function = update_function
def __init__(self,**kwargs):
super().__init__(**kwargs)
#self.get_info()
if not self.title:
self.title ="JuMEG gDCNN"
if not self.color:
self.color='primary'
if not self.app:
self.app = app
if not self.logo:
self.logo = "https://images.plot.ly/logo/new-branding/plotly-logomark.png"
self._navbar_href = kwargs.get("href","https://github.com/jdammers/jumeg")
# -- plot
#self.ics = {'label':'ICs','id':'ics-figure','data':data_ics,'figure':None,'page_spacing':70,'plot_spacing':100}
#self.aux = {'label':'AUX','id':'aux-figure','data':data_aux,'figure':None,'page_spacing':30,'plot_spacing':30}
self.ics = PLOT_DATA(label='ICs',id='ics-figure',data=data_ics,page_spacing=70,plot_spacing=100)
self.aux = PLOT_DATA(label='AUX',id='aux-figure',data=data_aux,page_spacing=30,plot_spacing=30)
self.times = times
#self.ics = _FIGURE(label='ICs',id='ics-figure',data=data_ics,page_spacing=70,plot_spacing=100)
#self.aux = _FIGURE(label='AUX',id='aux-figure',data=data_aux,page_spacing=30,plot_spacing=30)
if href:
self._navbar_href = href
else:
self._navbar_href = "https://github.com/jdammers/jumeg"
@property
def files( self ): return self._files
@files.setter
def files( self,v ):
if v is not None:
self._files = v
@property
def file( self ): return self._file
@file.setter
def file( self,v ):
if v :
self._file = v
@property
def chop( self ): return self._chop
@chop.setter
def chop( self,v ):
if v:
self._chop = v
@property
def n_chops( self ): return None
@property
def times( self ): return self._times
@times.setter
def times( self,v ):
if v is not None:
self._times = v
@property
def figure_data( self ): return self._figure_data
@figure_data.setter
def figure_data( self,v ):
if v:
if not isinstance(v,(list)):
self._figure_data = [v]
else:
self._figure_data = v
self.groups=[]
for fd in self._figure_data:
self.groups.append(fd.label)
#self._update_data()
def _init_navbar(self,color="primary"):
files = [ os.path.basename(f) for f in self.files]
self._FileSelectionBox = self.init_selection_box(label="File",options=self.files,index=1)
self._ChopSelectionBox = self.init_selection_box(label="Chop",options=self.chops,index=2)
if self.files:
files = [ os.path.basename(f) for f in self.files]
else:
files = []
self._FileSelectionBox = self.init_selection_box(label="File",options=files,index=1)
self._ChopSelectionBox = self.init_selection_box(label="Chop",options=self.n_chops,index=2)
self._ToggleButtonGroup = self.init_group_toggle_buttons(groups=self.groups,color="info")
self._Navbar = dbc.Navbar(
@@ -183,16 +205,15 @@ class DCNN_APP(_BASE):
return self._Navbar
def _init_ica_panel(self):
grps = []
divs = []
#for d,l in zip(data,labels,spacing):
for g in [self.ics,self.aux]:
pgs= g.page_spacing[i]
pls= self._plot_spacing[i]
# g.figure = self.init_fig(data=dg.data,times=self.times)
grps.append(
html.Div( dcc.Graph(figure=g.figure,id=g.id,config={'displayModeBar':False},style={'height':f"{g.plot_spacing}vh"}),
style={'overflowY':'scroll','height':f"{g.page_spacing}vh"}))
return dbc.Card( html.Div([dbc.Collapse(hdiv,id=f"collapse-{grp.label.lower()}",is_open=True) for hdiv,grp in zip(grps,[self.ics,self.aux]) ] ),body=True)
for fd in self.figure_data:
divs.append(
html.Div( dcc.Graph(figure=fd.figure,id=fd.id,config={'displayModeBar':False},style={'height':f"{fd.plot_spacing}vh"}),
id= f"{fd.id}-div",
style={'overflowY':'scroll','height':f"{fd.page_spacing}vh"}))
return dbc.Card( html.Div([dbc.Collapse(div,id=f"collapse-{fd.id}",is_open=True) for div,fd in zip(divs,self.figure_data) ] ),body=True)
def _init_performance_panel(self):
@@ -219,7 +240,7 @@ class DCNN_APP(_BASE):
])
def start(self,**kwargs):
def _init_layout(self,**kwargs):
self.app.layout = html.Div([self._init_navbar(),self._init_main_panel()])
self._init_callback_selectionbox()
@@ -227,99 +248,100 @@ class DCNN_APP(_BASE):
#self.app.config.suppress_callback_exceptions = True
self.app.run_server(debug=True, use_reloader=True)
def _ClickOnToggleButton(status):
def _clickOnToggleButton(status):
return status
def _update_figure(self,chop_idx):
ctx = dash.callback_context
id = ctx.triggered[0]['prop_id']
fig_ics,fig_aux = self.update_figure()# chop_index=int(chop_idx)-1 )
return fig_ics,fig_aux #,chop_idx
#def _init_callback_plots( self ):
#
# self.app.callback(
# [Output(f"{fg.id}","figure") for fg in self.figure_data],
# [Input("file-{self.id_selection_box}-sl","value"),Input("chop-{self.id_selection_box}-sl","value")],
# prevent_initial_call=True )( self._clickOnFileSelectionBox() )
# -- callbacks
def OnChangeFigure(self,chop_idx):
ctx=dash.callback_context
id = dash.callback_context.triggered[0]['prop_id']
logger.info(id)
logger.info(f"ctx output list: {ctx.outputs_list}" )
id = ctx.outputs_list[0]['id'].split('-')[0]
logger.info(id)
return [ self.update_figure(type=id) ] # chop_index=int(chop_idx)-1 )
def OnChangeSelectionBox(self,down,up,options,idx):
ctx = dash.callback_context
id = ctx.triggered[0]['prop_id']
if idx is None:
idx = 1
else:
idx = int(idx)
if id.endswith('down.n_clicks'):
idx -= 1
elif id.endswith('up.n_clicks'):
idx += 1
def _ClickOnSelectionSpinButtons(self,min,max):
if (idx < 1 ) or (idx > len(options)):
return dash.no_update
print(min)
print(max)
return max
return [idx]
def OnFileChange(self,idx):
self.update_data( file_index=int(idx) -1 )
return [ [{'label':f"{c}",'value':c} for c in np.arange(self.n_chops)+1] ]
#return [f"{int(idx):03d}",chops]
def OnChangeBadge(self,idx):
if isinstance(idx,(list)):
idx=idx[0]
return [f"{int(idx):03d}"]
def _update_data(self,file_idx,chop_idx):
ctx = dash.callback_context
id = ctx.triggered[0]['prop_id']
if id.startswith('file'):
# new file load data chop1 reset chop idx
chop_idx = 1
elif id.startswith('chop'):
# load chop x
pass
return fig_ica,fig_aux
def _init_callback_plots( self ):
self.app.callback(
[Output(f"{self.ics.id}","figure"),
Output(f"{self.aux.id}","figure")]
[Input("file-{self.id_selection_box}-sl","value"),Input("chop-{self.id_selection_box}-sl","value")],
)( self.ClickOnFileSelectionBox() )
# --- selectionbox
def _init_callback_selectionbox(self,labels=["File","Chop"]):
def clickOnSelectionBox(idx):
if isinstance(idx,(list)):
idx=idx[0]
return [f"{int(idx):03d}"]
def clickOnSelectionBoxUpDown(down,up,options,idx):
'''
my_input = request.state['my-input.value']
if my_input.has_changed:
result = f"Input {request.trigger.id} triggered callback; {my_input.id} changed value from {my_input.prev_value} to {my_input.value}"
else:
result = f"Input {request.trigger.id} triggered callback; {my_input.id} did not change value."
'''
ctx = dash.callback_context
id = ctx.triggered[0]['prop_id']
#logger.info(f"caller: {id}")
#logger.info(f"ctx trigger: {ctx.triggered}" )
#logger.info(f"ctx inputs: {ctx.inputs_list}" )
#logger.info(f"ctx input lists: {ctx.inputs_list}" )
#logger.info(f"ctx output list: {ctx.outputs_list}" )
#logger.info(options)
#logger.info(f"{l} {r} {idx}")
# id = [p['prop_id'] for p in dash.callback_context.triggered][0]
# logger.info( dash.callback_context.triggered )
if idx is None:
idx = 1
else:
idx = int(idx)
if id.endswith('left.n_clicks'):
idx -= 1
elif id.endswith('right.n_clicks'):
idx += 1
if idx < 1:
idx = 1
if idx > len(options):
idx = len(options)
# --- selectionbox
def _init_callback_selectionbox(self):
return [idx]
type1 = f"file-{self._id_selection_box}" # -- Files
type2 = f"chop-{self._id_selection_box}" # -- Chops
# -- File
app.callback([Output(f"{type1}-sl",'value')],
[Input(f"{type1}-bt-down",'n_clicks'),Input(f"{type1}-bt-up",'n_clicks')],
[State(f"{type1}-sl",'options'),State(f"{type1}-sl",'value')],
prevent_initial_call=True)(self.OnChangeSelectionBox)
for label in labels:
type = f"{ label.lower() }-{self.id_selection_box}"
app.callback([Output(f"{type2}-sl","options") ],
[Input(f"{type1}-sl", 'value')],
prevent_initial_call=True)(self.OnFileChange)
app.callback([Output(f"{type}-sl",'value')],
[Input(f"{type}-bt-down",'n_clicks'),Input(f"{type}-bt-up",'n_clicks')],
[State(f"{type}-sl",'options'),State(f"{type}-sl",'value')],
prevent_initial_call=True)(clickOnSelectionBoxUpDown)
app.callback([Output(f"{type1}-badge",'children')],
[Input(f"{type1}-sl", 'value')],
prevent_initial_call=True)(self.OnChangeBadge)
app.callback([Output(f"{type}-badge",'children')],
[Input(f"{type}-sl", 'value')],
prevent_initial_call=True)(clickOnSelectionBox)
# -- Chop
app.callback([Output(f"{type2}-sl",'value')],
[Input(f"{type2}-bt-down",'n_clicks'),Input(f"{type2}-bt-up",'n_clicks'),Input(f"{type2}-sl",'options')],
[State(f"{type2}-sl",'value')],
prevent_initial_call=True)(self.OnChangeSelectionBox)
app.callback([Output(f"{type2}-badge",'children')],
[Input(f"{type2}-sl", 'value')],
prevent_initial_call=True)(self.OnChangeBadge)
self.app.callback(
[Output(f"ica_plots-ics","figure"),Output(f"ica_plots-aux","figure")],
[Input(f"file-{self.id_selection_box}-sl",'value'),Input(f"chop-{self.id_selection_box}-sl",'value')],
prevent_initial_call=True)(self._update_data)
for fg in self.figure_data:
app.callback(
[Output(f"{fg.id}","figure")],
[Input(f"{type2}-sl",'value')],
prevent_initial_call=True)(self.OnChangeFigure)
#app.callback(
# [Output(f"{fg.id}","figure") for fg in self.figure_data],
# [Input(f"{type2}-sl",'value')],
# prevent_initial_call=True)(self._update_figure)
'''
app.callback(
@@ -337,10 +359,78 @@ class DCNN_APP(_BASE):
],prevent_initial_call=True)(clickOnSelectionBox)
'''
'''
my_input = request.state['my-input.value']
if my_input.has_changed:
result = f"Input {request.trigger.id} triggered callback; {my_input.id} changed value from {my_input.prev_value} to {my_input.value}"
else:
result = f"Input {request.trigger.id} triggered callback; {my_input.id} did not change value."
#logger.info(f"caller: {id}")
#logger.info(f"ctx trigger: {ctx.triggered}" )
#logger.info(f"ctx inputs: {ctx.inputs_list}" )
#logger.info(f"ctx input lists: {ctx.inputs_list}" )
#logger.info(f"ctx output list: {ctx.outputs_list}" )
#logger.info(options)
#logger.info(f"{l} {r} {idx}")
# id = [p['prop_id'] for p in dash.callback_context.triggered][0]
# logger.info( dash.callback_context.triggered )
'''
'''
def _update_figure_data(self,**kwargs):
for fd in self.fiure_data:
self._update_figure(fd,**kwargs)
def _update_figure(self,figure_data,n_chan=None,time_range=None):
#("id","data","label","labels","info","ics","topo_img","chop","figure","page_spacing","plot_spacing")
if not n_chan:
n_chan = figure_data.n_channels
figure_data.figure = make_subplots(n_chan,1)
# ToDo add img_plot
#shared_xaxes=False, shared_yaxes=False, start_cell='top-left', print_grid=False, horizontal_spacing=None,
#vertical_spacing=None, subplot_titles=None, column_widths=None,
#row_heights=None, specs=None, insets=None, column_titles=None,
#row_titles=None, x_title=None, y_title=None, figure=None,
#print(times[0:10])
#print(times[-10:])
if time_range:
#print(f"TR: {time_range}")
tp0 = np.where(times >= time_range[0])[0]
tp1 = np.where( time_range[1] <= times)[0]
#print(f" found tp0: {tp0} len: { len(tp0) } tp1: {tp1} len: { len(tp1) }")
if len(tp0):
t0=tp0[0]
else: t0 = 0
if len(tp1):
t1=tp1[0]
else: t1= -1
#print(f"t0: {t0} t1: {t1}")
#print("---")
for i in range(n_chan):
figure_data.figure.add_trace(go.Scatter(x=self.times[t0:t1], y=data[i,t0:t1]),i+1,1)
'''
else:
for i in range(n_chan):
figure_data.figure.add_trace(go.Scatter(x=self.times, y=figure_data.data[i]),i+1,1)
#fig.update_xaxes(matches='x')
figure_data.figure.update_layout(autosize=True)#, width=1200, height=1800,)
#facet_row_spacing=0.04, # default is 0.07 when facet_col_wrap is used
#facet_col_spacing=0.04, # default is 0.03
#height=600, width=800,
#fig.show(config=fig_config)
return figure_data.figure
'''
'''
ctx = dash.callback_context
if not ctx.triggered:
@@ -354,72 +444,8 @@ class DCNN_APP(_BASE):
'inputs': ctx.inputs
}, indent=2)
prevent_initial_call=True)
'''
def __clickOnSelectionSpinButton(options,l,r,idx):
'''
my_input = request.state['my-input.value']
if my_input.has_changed:
result = f"Input {request.trigger.id} triggered callback; {my_input.id} changed value from {my_input.prev_value} to {my_input.value}"
else:
result = f"Input {request.trigger.id} triggered callback; {my_input.id} did not change value."
'''
ctx = dash.callback_context
logger.info(ctx)
id = ctx.triggered[0]['prop_id']
logger.info(f"caller: {id}")
# id = [p['prop_id'] for p in dash.callback_context.triggered][0]
# logger.info( dash.callback_context.triggered )
if idx is None:
idx = 1
else:
idx = int(idx)
if id.endswith('left.n_clicks'):
idx -= 1
if idx < 1: idx = 1
elif id.endswith('right.n_clicks'):
idx += 1
if idx > len(options):
idx = len(options)
logger.info(idx)
return idx # ,f"{idx:03d}"
'''
def __clickOnSelectionBox(idx):
logger.info(idx)
return f"{idx:03d}"
#id=f"{ label.lower() }-{self.id_selection_box}"
id=self.id_selection_box
self.app.callback(
[
Output(f"{id}-sl","value"),
#Output(f"{id}-badge","children"),
],
[
Input(f"{id}-sl","options"),
Input(f"{id}-bt-left","n_clicks"),
Input(f"{id}-bt-right","n_clicks"),
],
[
State(f"{id}-sl","value"),
],
prevent_initial_call=True)(clickOnSelectionSpinButton)
self.app.callback(
[
Output(f"{id}-badge","children"),
],
[
Input(f"{id}-sl","value"),
],
prevent_initial_call=True)(clickOnSelectionBox)
'''
'''
def __init_callbacks(self):
@@ -696,6 +722,22 @@ def MainPanel():
],align='stretch')
])
'''
'''
\ No newline at end of file
'''
#type=f'{ label.lower() }-{self.id_selection_box}'
tp=self.id_selection_box
return dbc.Row([
dbc.Col(dbc.Button(label,id={'type':f"{tp}-bt",'index':index},color=color,className="ml-2"),width="auto"),
#ToDo dbc.Col(dbc.Spinner(color="info",id=id+"SP"),width="auto"),
dbc.Col(dbc.Select(id={'type':f"{tp}-sl",'index':index},options=options,
#value=options[0]['value'],
placeholder=placeholder,
style={"min-width":wmin,"display":'flex'}),width='auto' ),
dbc.Col(html.H2( dbc.Button("<",id={'type':f"{tp}-bt-left",'index':index},n_clicks=0,className="mr-2")),width="auto"),
dbc.Col(html.H2([dbc.Badge(children=[html.H4("000")],id={'type':f"{tp}-badge",'index':index},color='info',className="mr-1") ]),width="auto"),
dbc.Col(html.H2(dbc.Button(">",id={'type':f"{tp}-bt-right",'index':index},n_clicks=0,className="mr-2")),width="auto"),
dbc.Col(html.B(),width=1)
],className="ml-auto flex-nowrap mt-3 mt-md-0 ",justify="start",form=True)
'''
Loading