diff --git a/app.py b/app.py index caa2cdc..3c25f8f 100644 --- a/app.py +++ b/app.py @@ -3,6 +3,7 @@ """ import os import numpy as np +import time import datetime try: from nicegui import app, ui, events, Client @@ -30,11 +31,15 @@ pio.templates["plotly_light"].layout['yaxis']['zerolinecolor'] = 'lightgray' ELM = { 'name': 'tempplot', 'filename': './data/20260420213656.txt', - 'label_file_fmt': 'file: {filename}', - #'label_data_len_fmt': 'data points (rendered): {num}', - 'label_data_len_fmt': 'data points: {num} (rendered), {numa} (total)', + 'label_file_fmt': 'File: {filename}', + 'label_data_len_fmt': 'Data points: {num:,} (rendered) / {numa:,} (total)', + 'label_data_temp_fmt': 'Temperature = [{min:.1f}, {max:.1f}] K', + 'label_data_heat_fmt': 'Heater = [{min:.1f}, {max:.1f}] %', 'plotly_light': 'plotly_light', 'plotly_dark': 'plotly_dark', + 'update_last': time.time(), + 'update_timeout': 1, + 'label_update_last_fmt': 'Last refresh: {last}', } def main_cli(): @@ -84,10 +89,11 @@ async def pick_file() -> None: if result: filename = result[-1] ELM['filename'] = filename - # update_data() # see timer below + # see timer below, but still here active b/c the time might be inactive + ui.timer(0, lambda: update_data(), once=True) ELM['label_file'].set_text(ELM['label_file_fmt'].format(filename=os.path.basename(filename))) -async def get_data(filename=ELM['filename']): +def get_data(filename=ELM['filename']): data = np.genfromtxt(fname=filename, skip_header=1) if len(data) > 0 and len(data[0]) > 6: @@ -102,42 +108,82 @@ async def get_data(filename=ELM['filename']): ) return data -async def update_data(): - filename=ELM['filename'] - data_all = await get_data(filename) - data = data_all - # time_range = ELM['number_time_range'].value - time_range = app.storage.user['time_range'] - if time_range is not None and time_range > 0: - # delta = datetime.datetime.now() - data['time'] - delta = data['time'][-1] - data['time'] - data = data[np.where(delta <= datetime.timedelta(hours=time_range))] - print(filename, data_all.shape, data.shape) - ELM['label_data_len'].set_text(ELM['label_data_len_fmt'].format(num=len(data), numa=len(data_all))) - - ELM['fig'].data = [] - ELM['plot'].visible = False - ELM['plot'].update() +def update_data(): + if not app.storage.user['timer'] or time.time() - ELM['update_last'] > ELM['update_timeout']: + ELM['update_busy'] = True + filename=ELM['filename'] + data_all = get_data(filename) + data = data_all - ELM['fig'].add_trace(pgo.Scatter(**np2pgo(data['time'], data['ts'], 'Setp'))) - ELM['fig'].add_trace(pgo.Scatter(**np2pgo(data['time'], data['ta'], 'Temp A'))) - ELM['fig'].add_trace(pgo.Scatter(**np2pgo(data['time'], data['tb'], 'Temp B'))) - if len(data) > 0 and len(data[0]) > 6: - ELM['fig'].add_trace(pgo.Scatter(**np2pgo(data['time'], data['tc'], 'Temp C'))) - ELM['fig'].add_trace(pgo.Scatter(**np2pgo(data['time'], data['td'], 'Temp D'))) + # time_range = ELM['number_time_range'].value + time_range = app.storage.user['time_range'] + if time_range is not None and time_range > 0: + # delta = datetime.datetime.now() - data['time'] + delta = data['time'][-1] - data['time'] + data = data[np.where(delta <= datetime.timedelta(hours=time_range))] + print(datetime.datetime.now(), filename, data_all.shape, data.shape) + ELM['label_data_len'].set_text(ELM['label_data_len_fmt'].format( + num=len(data), numa=len(data_all))) + + if len(data) > 0: + if len(data[0]) > 6: + ELM['label_data_temp'].set_text(ELM['label_data_temp_fmt'].format( + min=np.min([data['ts'], data['ta'], data['tb'], data['tc'], data['td']]), + max=np.max([data['ts'], data['ta'], data['tb'], data['tc'], data['td']]))) + else: + ELM['label_data_temp'].set_text(ELM['label_data_temp_fmt'].format( + min=np.min([data['ts'], data['ta'], data['tb']]), + max=np.max([data['ts'], data['ta'], data['tb']]))) + ELM['label_data_heat'].set_text(ELM['label_data_heat_fmt'].format( + min=np.min(data['hp']), max=np.max(data['hp']))) - ELM['fig'].add_trace(pgo.Scatter(**np2pgo(data['time'], data['hp'], 'Heat')), - row=2, col=1) + ELM['fig'].data = [] + ELM['plot'].visible = False + ELM['plot'].update() - ELM['plot'].visible = True - ELM['plot'].update() + # mode='lines+markers' too heavy + ELM['fig'].add_trace(pgo.Scatter( + **np2pgo(data['time'], data['ts'], 'Setp'), mode='lines', legendgroup=None)) + ELM['fig'].add_trace(pgo.Scatter( + **np2pgo(data['time'], data['ta'], 'Temp A'), mode='lines', legendgroup=None)) + ELM['fig'].add_trace(pgo.Scatter( + **np2pgo(data['time'], data['tb'], 'Temp B'), mode='lines', legendgroup=None)) + 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)) + ELM['fig'].add_trace(pgo.Scatter( + **np2pgo(data['time'], data['td'], 'Temp D'), mode='lines', legendgroup=None)) + + ELM['fig'].add_trace(pgo.Scatter( + **np2pgo(data['time'], data['hp'], 'Heat'), mode='lines', + legendgroup=None), row=2, col=1) + + ELM['plot'].visible = True + ELM['plot'].update() + + ELM['update_last'] = time.time() + + ELM['label_update_last'].set_text( + ELM['label_update_last_fmt'].format( + last=datetime.datetime.fromtimestamp( + ELM['update_last']).strftime("%Y-%m-%dT%H:%M:%S.%f"))) + +def update_fig(): + ELM['fig'].update_yaxes( + row=1, col=1, range=[ELM['number_temp_min'].value, ELM['number_temp_max'].value], + rangemode="nonnegative") + ELM['fig'].update_yaxes( + row=2, col=1, range=[ELM['number_heat_min'].value, ELM['number_heat_max'].value], + rangemode="nonnegative") @ui.page('/') -async def index(): - await ui.context.client.connected() - +def index(): # initialize objects - ELM['fig'] = ply.subplots.make_subplots(rows=2, cols=1) + pio.templates.default = 'plotly_dark' + # ELM['fig'] = pgo.Figure() + ELM['fig'] = ply.subplots.make_subplots( + rows=2, cols=1, specs = [[{'t': 0.0}], [{}]], shared_xaxes=True, + vertical_spacing=0.02) # render header ui.context.client.content.classes('h-[100vh]') @@ -152,12 +198,18 @@ async def index(): ui.button('Choose file', on_click=pick_file, icon='folder') ELM['number_time_range'] = ui.number( - label='time range', value=6, format='%.2f', min=0, step=0.5, + label='Time range', value=6, format='%.2f', min=0, step=0.5, suffix='h', - # on_change=lambda e: update_data(), # see timer below - ).props('clearable').bind_value(app.storage.user, 'time_range').classes('w-32') + # see timer below, but still here active b/c the time might be inactive + ).on('update:model-value', lambda e: update_data(), throttle=1.0) \ + .props('clearable').bind_value(app.storage.user, 'time_range').classes('w-32') - ELM['switch_timer'] = ui.switch('auto-refresh').bind_value(app.storage.user, 'timer') + ELM['switch_timer'] = ui.switch('Auto-refresh').bind_value(app.storage.user, 'timer') + + ELM['label_update_last'] = ui.label( + ELM['label_update_last_fmt'].format( + last=datetime.datetime.fromtimestamp( + ELM['update_last']).strftime("%Y-%m-%dT%H:%M:%S.%f"))) ui.space() @@ -172,19 +224,55 @@ async def index(): ELM['label_file'] = ui.label( ELM['label_file_fmt'].format(filename=os.path.basename(ELM['filename']))) ELM['label_data_len'] = ui.label(ELM['label_data_len_fmt'].format(num=0, numa=0)) + ELM['label_data_temp'] = ui.label(ELM['label_data_temp_fmt'].format(min=0, max=0)) + ELM['label_data_heat'] = ui.label(ELM['label_data_heat_fmt'].format(min=0, max=0)) + + with ui.row(align_items="baseline").classes('w-full'): + ui.label('Temperature') + ELM['number_temp_min'] = \ + ui.number( + label='min', value=None, format='%.2f', min=0, step=1.0, suffix='K') \ + .props('clearable').classes('w-32').on( + 'update:model-value', lambda e: update_fig(), throttle=1.0) + ELM['number_temp_max'] = \ + ui.number( + label='max', value=None, format='%.2f', min=0, step=1.0, suffix='K') \ + .props('clearable').classes('w-32').on( + 'update:model-value', lambda e: update_fig(), throttle=1.0) + ui.label().classes('w-22') + ui.label('Heater') + ELM['number_heat_min'] = \ + ui.number( + label='min', value=None, format='%.2f', min=0, step=1.0, suffix='%') \ + .props('clearable').classes('w-32').on( + 'update:model-value', lambda e: update_fig(), throttle=1.0) + ELM['number_heat_max'] = \ + ui.number( + label='max', value=None, format='%.2f', min=0, step=1.0, suffix='%') \ + .props('clearable').classes('w-32').on( + 'update:model-value', lambda e: update_fig(), throttle=1.0) - pio.templates.default = 'plotly_dark' - # ELM['fig'] = pgo.Figure() ELM['fig'].update_layout( - height=600, - #template='plotly_white' + height=640, + margin={'t': 25}, + #legend_tracegroupgap=140, template=ELM['plotly_dark'] if dark_mode.value else ELM['plotly_light'], + yaxis1_title='Temperature, K', + yaxis2_title='Power, %', + yaxis2_range=[0, 100], + xaxis2_title='Time', ) + ELM['fig'].update_yaxes( + row=1, col=1, range=[ELM['number_temp_min'].value, ELM['number_temp_max'].value], + rangemode="nonnegative") + ELM['fig'].update_yaxes( + row=2, col=1, range=[ELM['number_heat_min'].value, ELM['number_heat_max'].value], + rangemode="nonnegative") ELM['plot'] = ui.plotly(ELM['fig']).classes('w-full') # update_data() ui.timer(0, lambda: update_data(), once=True) - ELM['timer'] = ui.timer(2.0, lambda: update_data(), immediate=False) + ELM['timer'] = ui.timer(0.5, lambda: update_data(), immediate=False) ELM['switch_timer'].bind_value_to(ELM['timer'], 'active') def main_gui(args):