From 5543e2fe1dcee453d9c9beca2f04f9ba178d674f Mon Sep 17 00:00:00 2001 From: Daniel Weschke Date: Sat, 25 Apr 2026 17:41:11 +0200 Subject: [PATCH] add time-range and auto-refresh --- app.py | 160 +++++++++++++++++++++++-------------------- local_file_picker.py | 18 +++-- 2 files changed, 100 insertions(+), 78 deletions(-) diff --git a/app.py b/app.py index 9b06295..caa2cdc 100644 --- a/app.py +++ b/app.py @@ -14,17 +14,28 @@ try: import plotly.io as pio import plotly as ply import plotly.subplots + import local_file_picker except ModuleNotFoundError as e: print('Module `plotly` not found.') exit(1) -NAME = 'tempplot' -FILENAME = './data/20260420213656.txt' -fig = None -plot = None -label_file = None -label_file_fmt = 'tempplot: {filename}' -# label_file_fmt = 'tempplot: {os.path.basename(filename)}' +pio.templates["plotly_light"] = pgo.layout.Template( + layout = pio.templates["plotly_white"].layout) +#pio.templates["plotly_light"].layout['scene']['xaxis']['gridcolor'] = 'gray' +pio.templates["plotly_light"].layout['xaxis']['gridcolor'] = 'lightgray' +pio.templates["plotly_light"].layout['xaxis']['zerolinecolor'] = 'lightgray' +pio.templates["plotly_light"].layout['yaxis']['gridcolor'] = 'lightgray' +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)', + 'plotly_light': 'plotly_light', + 'plotly_dark': 'plotly_dark', +} def main_cli(): import argparse @@ -67,19 +78,16 @@ def np2pgo(x, y, label=None): res.update({'name': label}) return res -# def str2date(x): -# return datetime.datetime.strptime(x.decode('utf-8'), '%Y%m%d%H%M%S') - -import local_file_picker async def pick_file() -> None: result = await local_file_picker.local_file_picker('./data', multiple=False) # ui.notify(f'You chose {result}') - filename = result[-1] - update_data(filename) - label_file.set_text(label_file_fmt.format(filename=os.path.basename(filename))) + if result: + filename = result[-1] + ELM['filename'] = filename + # update_data() # see timer below + ELM['label_file'].set_text(ELM['label_file_fmt'].format(filename=os.path.basename(filename))) -def get_data(filename=FILENAME): - print(f"filename: {filename}") +async def get_data(filename=ELM['filename']): data = np.genfromtxt(fname=filename, skip_header=1) if len(data) > 0 and len(data[0]) > 6: @@ -92,93 +100,97 @@ def get_data(filename=FILENAME): 0: lambda x: datetime.datetime.strptime(x, '%Y%m%d%H%M%S')}, names=names, encoding='utf-8' ) - print(data.shape) - print(data['time']) - #data[:, 0] = np.array(data[:, 0], dtype='datetime64') - #print(data.time) return data -def update_data(filename=FILENAME): - data = get_data(filename) +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))) - fig.data = [] - plot.visible = False - plot.update() + ELM['fig'].data = [] + ELM['plot'].visible = False + ELM['plot'].update() - fig.add_trace(pgo.Scatter(**np2pgo(data['time'], data['ts'], 'Setp'))) - fig.add_trace(pgo.Scatter(**np2pgo(data['time'], data['ta'], 'Temp A'))) - fig.add_trace(pgo.Scatter(**np2pgo(data['time'], data['tb'], 'Temp B'))) + 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: - fig.add_trace(pgo.Scatter(**np2pgo(data['time'], data['tc'], 'Temp C'))) - fig.add_trace(pgo.Scatter(**np2pgo(data['time'], data['td'], 'Temp D'))) + 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'))) - fig.add_trace(pgo.Scatter(**np2pgo(data['time'], data['hp'], 'Heat')), - row=2, col=1) + ELM['fig'].add_trace(pgo.Scatter(**np2pgo(data['time'], data['hp'], 'Heat')), + row=2, col=1) - plot.visible = True - plot.update() + ELM['plot'].visible = True + ELM['plot'].update() @ui.page('/') -def index(): - global fig, plot, label_file +async def index(): + await ui.context.client.connected() - # data = get_data() + # initialize objects + ELM['fig'] = ply.subplots.make_subplots(rows=2, cols=1) - # - fig = ply.subplots.make_subplots(rows=2, cols=1) - - # + # render header ui.context.client.content.classes('h-[100vh]') ui.add_head_html(''' ''') dark_mode = ui.dark_mode().bind_value(app.storage.user, 'dark_mode') - #ui.button('Dark', on_click=dark.enable) - #ui.button('Light', on_click=dark.disable) - ui.switch( - 'Dark mode', on_change=lambda: fig.update_layout(template='plotly_dark' if dark_mode.value else 'plotly_white') - ).bind_value(app.storage.user, 'dark_mode').props('icon="dark_mode"') - print(dark_mode.value) ui.colors(primary='#4888c4') - - ui.button('Choose file', on_click=pick_file, icon='folder') - label_file = ui.label(label_file_fmt.format(filename=os.path.basename(FILENAME))) + with ui.row(align_items="center").classes('w-full'): + ui.button('Choose file', on_click=pick_file, icon='folder') -# range_t = ui.range( -# min=data['time'][0].timestamp(), max=data['time'][-1].timestamp(), -# # on_change=lambda e: fig.update_xaxes( -# # range=[str(datetime.datetime.fromtimestamp(e.value["min"])), -# # str(datetime.datetime.fromtimestamp(e.value["max"]))]), -# ) -# ui.label().bind_text_from( -# range_t, 'value', -# backward=lambda v: ( -# f'min: {datetime.datetime.fromtimestamp(v["min"])}\n\ -# max: {datetime.datetime.fromtimestamp(v["max"])}'), -# ).style('white-space: pre-wrap') + ELM['number_time_range'] = ui.number( + 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') + + ELM['switch_timer'] = ui.switch('auto-refresh').bind_value(app.storage.user, 'timer') + + ui.space() + + ui.switch( + 'Dark mode', on_change=lambda: ( + ELM['fig'].update_layout( + template=ELM['plotly_dark'] if dark_mode.value else ELM['plotly_light']), + 'plot' in ELM and ELM['plot'].update()) + ).bind_value(app.storage.user, 'dark_mode').props('icon="dark_mode"') + + with ui.row(): + 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)) pio.templates.default = 'plotly_dark' - # fig = pgo.Figure() - fig.update_layout( + # ELM['fig'] = pgo.Figure() + ELM['fig'].update_layout( height=600, #template='plotly_white' - template='plotly_dark' if dark_mode.value else 'plotly_white', + template=ELM['plotly_dark'] if dark_mode.value else ELM['plotly_light'], ) - plot = ui.plotly(fig).classes('w-full') + ELM['plot'] = ui.plotly(ELM['fig']).classes('w-full') - update_data() - - # print(fig.full_figure_for_development().layout.xaxis.range) - # ('2026-04-20 21:36:56', '2026-04-21 10:40:53') - # print(fig.full_figure_for_development().layout.xaxis.range[0]) - # print(type(fig.full_figure_for_development().layout.xaxis.range[0])) + # update_data() + ui.timer(0, lambda: update_data(), once=True) + ELM['timer'] = ui.timer(2.0, lambda: update_data(), immediate=False) + ELM['switch_timer'].bind_value_to(ELM['timer'], 'active') def main_gui(args): ui.run( - host=args.host, port=int(args.port), title=NAME, - #dark=True, + host=args.host, port=int(args.port), title=ELM['name'], + dark=False, native=False, show=False, storage_secret='tempview') diff --git a/local_file_picker.py b/local_file_picker.py index 00fb505..e5288e0 100644 --- a/local_file_picker.py +++ b/local_file_picker.py @@ -1,4 +1,4 @@ -# https://github.com/zauberzeug/nicegui/blob/main/examples/local_file_picker/local_file_picker.py +# based on https://github.com/zauberzeug/nicegui/blob/main/examples/local_file_picker/local_file_picker.py import platform from pathlib import Path @@ -30,9 +30,19 @@ class local_file_picker(ui.dialog): with self, ui.card(): self.add_drives_toggle() self.grid = ui.aggrid({ - 'columnDefs': [{'field': 'name', 'headerName': 'File'}], - 'rowSelection': {'mode': 'multiRow' if multiple else 'singleRow'}, - }, html_columns=[0]).classes('w-96').on('cellDoubleClicked', self.handle_double_click) + 'columnDefs': [{ + 'field': 'name', 'headerName': 'File', 'sort': 'desc', + 'filter': 'agTextColumnFilter', 'floatingFilter': True, + }], + 'rowSelection': { + 'mode': 'multiRow' if multiple else 'singleRow', + #'checkboxes': True if multiple else False, + 'enableClickSelection': True, + }, + 'selectionColumnDef': {'width': 28, 'cellStyle': {'textAlign': 'center'} }, # will be width+20 and min 48 + }, html_columns=[0], theme='balham').classes('w-96').on('cellDoubleClicked', self.handle_double_click) + #\ + #.on('cellClicked', self.handle_click) with ui.row().classes('w-full justify-end'): ui.button('Cancel', on_click=self.close).props('outline') ui.button('Ok', on_click=self._handle_ok)