diff --git a/app.py b/app.py index 712cf42..fb49caf 100644 --- a/app.py +++ b/app.py @@ -100,6 +100,29 @@ def fnttt(): res = f'{fn[:4]}-{fn[4:6]}-{fn[6:8]} {fn[8:10]}:{fn[10:12]}:{fn[12:14]}' return res +def islandinfo(y, trigger_val, stopind_inclusive=True): + """ + >>> y + >>> array([1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]) + >>> islandinfo(y, trigger_val=1)[0] + >>> [(0, 2), (8, 9), (16, 19)] + >>> islandinfo(y, trigger_val=0)[0] + >>> [(3, 7), (10, 15)] + """ + # Setup "sentients" on either sides to make sure we have setup + # "ramps" to catch the start and stop for the edge islands + # (left-most and right-most islands) respectively + y_ext = np.r_[False,y==trigger_val, False] + + # Get indices of shifts, which represent the start and stop indices + idx = np.flatnonzero(y_ext[:-1] != y_ext[1:]) + + # Lengths of islands if needed + lens = idx[1::2] - idx[:-1:2] + + # Using a stepsize of 2 would get us start and stop indices for each island + return list(zip(idx[:-1:2], idx[1::2]-int(stopind_inclusive))), lens + async def pick_file() -> None: result = await local_file_picker.local_file_picker(ELM['filedir'], multiple=False) # ui.notify(f'You chose {result}') @@ -161,25 +184,63 @@ async def update_data(): min=np.min(data['hp']), max=np.max(data['hp']))) ELM['fig'].data = [] + ELM['fig'].layout.shapes = [] ELM['plot'].visible = False ELM['plot'].update() # mode='lines+markers' too heavy ELM['fig'].add_trace(pgo.Scatter( - **np2pgo(data['time'], data['ts'], 'Setp'), mode='lines', legendgroup=None)) + **np2pgo(data['time'], data['ts'], 'Setp'), mode='lines', yaxis="y", legendgroup=None), secondary_y=False) ELM['fig'].add_trace(pgo.Scatter( - **np2pgo(data['time'], data['ta'], 'Temp A'), mode='lines', legendgroup=None)) + **np2pgo(data['time'], data['ta'], 'Temp A'), mode='lines', yaxis="y", legendgroup=None), secondary_y=False) ELM['fig'].add_trace(pgo.Scatter( - **np2pgo(data['time'], data['tb'], 'Temp B'), mode='lines', legendgroup=None)) + **np2pgo(data['time'], data['tb'], 'Temp B'), mode='lines', yaxis="y", legendgroup=None), secondary_y=False) if len(data) > 0 and len(data[0]) > 6: ELM['fig'].add_trace(pgo.Scatter( - **np2pgo(data['time'], data['tc'], 'Temp C'), mode='lines', legendgroup=None)) + **np2pgo(data['time'], data['tc'], 'Temp C'), mode='lines', yaxis="y", legendgroup=None), secondary_y=False) ELM['fig'].add_trace(pgo.Scatter( - **np2pgo(data['time'], data['td'], 'Temp D'), mode='lines', legendgroup=None)) + **np2pgo(data['time'], data['td'], 'Temp D'), mode='lines', yaxis="y", legendgroup=None), secondary_y=False) ELM['fig'].add_trace(pgo.Scatter( - **np2pgo(data['time'], data['hp'], 'Heat'), mode='lines', - legendgroup=None), row=2, col=1) + **np2pgo(data['time'], data['hp'], 'Heater'), mode='lines', yaxis="y2", + legendgroup=None, legend="legend2"), row=2, col=1, secondary_y=False) + + # display the heater range as background + n = len(data['time']) + + # dummy plot to generate the secondary axis + ELM['fig'].add_trace( + pgo.Scatter( + x=[data['time'][0]], y=[0], mode='lines', line_width=0, showlegend=False), + # important to not interleave gaps additionally. the limit-to-view also + # ensures that the autoscale is not disturbed. + gap_handler=prs.aggregation.NoGapHandler(), + limit_to_view=True, + secondary_y=True, + row=2, col=1, + # yref="paper", + ) + + shapes = [] + hls = [0, 1, 2, 3, 4, 5] + for hli in hls: + for (i0, i1) in islandinfo(data['hl'], hli, True)[0]: + shapes.append( + dict( + fillcolor="rgba(63, 81, 181, 0.2)", + opacity=0.70, + line={"width": 0}, + type="rect", + x0=data['time'][i0], + x1=data['time'][i1], + xref="x", + y0=0, + y1=hli/5, + yref="y2 domain", #"paper" + layer="below", # "between" + # label=dict(text=f"{hli}", textposition="top center", font=dict(size=12)), + )) + ELM['fig'].update_layout(shapes=shapes) ELM['plot'].visible = True ELM['plot'].update() @@ -207,7 +268,7 @@ def index(): pio.templates.default = 'plotly_dark' ELM['fig'] = prs.FigureResampler(pgo.Figure()).set_subplots( rows=2, cols=1, row_heights=[0.7, 0.3], - specs = [[{'t': 0.0}], [{}]], shared_xaxes=True, + specs = [[{'t': 0.0}], [{"secondary_y": True}]], shared_xaxes=True, vertical_spacing=0.025) # render header @@ -284,7 +345,7 @@ def index(): ELM['fig'].update_layout( height=640, - margin={'t': 25}, + margin={'t': 25, 'r': 0}, #legend_tracegroupgap=140, template=ELM['plotly_dark'] if dark_mode.value else ELM['plotly_light'], yaxis1_title='Temperature, K', @@ -295,7 +356,13 @@ def index(): 'orientation': 'h', 'xanchor': 'left', 'yanchor': 'bottom', - 'y': 1.01, + 'y': 1.0, + }, + legend2={ + 'orientation': 'h', + 'xanchor': 'left', + 'yanchor': 'bottom', + 'y': -0.13, }, ) ELM['fig'].update_yaxes( @@ -303,7 +370,16 @@ def index(): rangemode="nonnegative") ELM['fig'].update_yaxes( row=2, col=1, range=[ELM['number_heat_min'].value, ELM['number_heat_max'].value], - rangemode="nonnegative") + rangemode="nonnegative", showgrid=False) + ELM['fig'].update_yaxes( + title_text='Heater level', + row=2, col=1, range=[0, 5], rangemode="nonnegative", secondary_y=True, + fixedrange=True, showgrid=True, ticks="inside", tickwidth=1, + # tickcolor='crimson', + # nticks=6, + # ticklen=5, + tickvals=[0, 1, 2, 3, 4, 5], + ) ELM['plot'] = ui.plotly(ELM['fig']).classes('w-full') # update_data()