initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.nicegui
|
||||
venv
|
||||
193
app.py
Normal file
193
app.py
Normal file
@@ -0,0 +1,193 @@
|
||||
"""
|
||||
|
||||
"""
|
||||
import os
|
||||
import numpy as np
|
||||
import datetime
|
||||
try:
|
||||
from nicegui import app, ui, events, Client
|
||||
except ModuleNotFoundError as e:
|
||||
print('Module `nicegui` not found.')
|
||||
exit(1)
|
||||
try:
|
||||
import plotly.graph_objects as pgo
|
||||
import plotly.io as pio
|
||||
import plotly as ply
|
||||
import plotly.subplots
|
||||
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)}'
|
||||
|
||||
def main_cli():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(
|
||||
description=__doc__,
|
||||
formatter_class=argparse.RawTextHelpFormatter,
|
||||
prefix_chars='-',
|
||||
add_help=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
'--host', type=str, default='', help='default 127.0.0.1')
|
||||
parser.add_argument(
|
||||
'--port', type=str, default='8085', help='default 8080')
|
||||
parser.add_argument(
|
||||
'-v', '--verbose', action='store_true', help='Verbose output')
|
||||
parser.add_argument(
|
||||
'-D', '--debug', action='store_true', help=argparse.SUPPRESS)
|
||||
parser.add_argument(
|
||||
'-h', '--help', action='store_true', help='Show this help message and exit')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.debug:
|
||||
DEBUG = True
|
||||
print(f'[DEBUG] args: {args}')
|
||||
print(f'[DEBUG] __file__: {__file__}')
|
||||
print(f'[DEBUG] cwd: {os.getcwd()}')
|
||||
print(f'[DEBUG] host: {args.host}')
|
||||
print(f'[DEBUG] port: {args.port}')
|
||||
|
||||
if args.help:
|
||||
parser.print_help()
|
||||
exit(0)
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def np2pgo(x, y, label=None):
|
||||
res = {'x': x, 'y': y}
|
||||
if label is not 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)))
|
||||
|
||||
def get_data(filename=FILENAME):
|
||||
print(f"filename: {filename}")
|
||||
data = np.genfromtxt(fname=filename, skip_header=1)
|
||||
|
||||
if len(data) > 0 and len(data[0]) > 6:
|
||||
names = ['time', 'ts', 'ta', 'tb', 'tc', 'td', 'hp', 'hl']
|
||||
else:
|
||||
names = ['time', 'ts', 'ta', 'tb', 'hp', 'hl']
|
||||
|
||||
data = np.genfromtxt(
|
||||
fname=filename, dtype=None, skip_header=1, converters={
|
||||
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)
|
||||
|
||||
fig.data = []
|
||||
plot.visible = False
|
||||
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')))
|
||||
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')))
|
||||
|
||||
fig.add_trace(pgo.Scatter(**np2pgo(data['time'], data['hp'], 'Heat')),
|
||||
row=2, col=1)
|
||||
|
||||
plot.visible = True
|
||||
plot.update()
|
||||
|
||||
@ui.page('/')
|
||||
def index():
|
||||
global fig, plot, label_file
|
||||
|
||||
# data = get_data()
|
||||
|
||||
#
|
||||
fig = ply.subplots.make_subplots(rows=2, cols=1)
|
||||
|
||||
#
|
||||
ui.context.client.content.classes('h-[100vh]')
|
||||
ui.add_head_html('''<meta name="viewport", content="width=device-width, initial-scale=1">
|
||||
<meta name="mobile-web-app-capable" content="yes">''')
|
||||
|
||||
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)))
|
||||
|
||||
# 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')
|
||||
|
||||
pio.templates.default = 'plotly_dark'
|
||||
# fig = pgo.Figure()
|
||||
fig.update_layout(
|
||||
height=600,
|
||||
#template='plotly_white'
|
||||
template='plotly_dark' if dark_mode.value else 'plotly_white',
|
||||
)
|
||||
plot = ui.plotly(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]))
|
||||
|
||||
def main_gui(args):
|
||||
ui.run(
|
||||
host=args.host, port=int(args.port), title=NAME,
|
||||
#dark=True,
|
||||
native=False,
|
||||
show=False, storage_secret='tempview')
|
||||
|
||||
def main(args):
|
||||
return 0
|
||||
|
||||
if __name__ in {'__main__', '__mp_main__'}:
|
||||
args = main_cli()
|
||||
main_gui(args)
|
||||
|
||||
|
||||
|
||||
23519
data/20260420213656.txt
Normal file
23519
data/20260420213656.txt
Normal file
File diff suppressed because it is too large
Load Diff
35285
data/20260423200731.txt
Normal file
35285
data/20260423200731.txt
Normal file
File diff suppressed because it is too large
Load Diff
83
local_file_picker.py
Normal file
83
local_file_picker.py
Normal file
@@ -0,0 +1,83 @@
|
||||
# https://github.com/zauberzeug/nicegui/blob/main/examples/local_file_picker/local_file_picker.py
|
||||
import platform
|
||||
from pathlib import Path
|
||||
|
||||
from nicegui import events, ui
|
||||
|
||||
|
||||
class local_file_picker(ui.dialog):
|
||||
|
||||
def __init__(self, directory: str, *,
|
||||
upper_limit: str | None = ..., multiple: bool = False, show_hidden_files: bool = False) -> None:
|
||||
"""Local File Picker
|
||||
|
||||
This is a simple file picker that allows you to select a file from the local filesystem where NiceGUI is running.
|
||||
|
||||
:param directory: The directory to start in.
|
||||
:param upper_limit: The directory to stop at (None: no limit, default: same as the starting directory).
|
||||
:param multiple: Whether to allow multiple files to be selected.
|
||||
:param show_hidden_files: Whether to show hidden files.
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.path = Path(directory).expanduser()
|
||||
if upper_limit is None:
|
||||
self.upper_limit = None
|
||||
else:
|
||||
self.upper_limit = Path(directory if upper_limit == ... else upper_limit).expanduser()
|
||||
self.show_hidden_files = show_hidden_files
|
||||
|
||||
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)
|
||||
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)
|
||||
self.update_grid()
|
||||
|
||||
def add_drives_toggle(self):
|
||||
if platform.system() == 'Windows':
|
||||
import win32api
|
||||
drives = win32api.GetLogicalDriveStrings().split('\000')[:-1]
|
||||
self.drives_toggle = ui.toggle(drives, value=drives[0], on_change=self.update_drive)
|
||||
|
||||
def update_drive(self):
|
||||
self.path = Path(self.drives_toggle.value).expanduser()
|
||||
self.update_grid()
|
||||
|
||||
def update_grid(self) -> None:
|
||||
paths = list(self.path.glob('*'))
|
||||
if not self.show_hidden_files:
|
||||
paths = [p for p in paths if not p.name.startswith('.')]
|
||||
paths.sort(key=lambda p: p.name.lower())
|
||||
paths.sort(key=lambda p: not p.is_dir())
|
||||
|
||||
self.grid.options['rowData'] = [
|
||||
{
|
||||
'name': f'📁 <strong>{p.name}</strong>' if p.is_dir() else p.name,
|
||||
'path': str(p),
|
||||
}
|
||||
for p in paths
|
||||
]
|
||||
if (self.upper_limit is None and self.path != self.path.parent) or \
|
||||
(self.upper_limit is not None and self.path != self.upper_limit):
|
||||
self.grid.options['rowData'].insert(0, {
|
||||
'name': '📁 <strong>..</strong>',
|
||||
'path': str(self.path.parent),
|
||||
})
|
||||
self.grid.update()
|
||||
|
||||
def handle_double_click(self, e: events.GenericEventArguments) -> None:
|
||||
self.path = Path(e.args['data']['path'])
|
||||
if self.path.is_dir():
|
||||
self.update_grid()
|
||||
else:
|
||||
self.submit([str(self.path)])
|
||||
|
||||
async def _handle_ok(self):
|
||||
rows = await self.grid.get_selected_rows()
|
||||
self.submit([r['path'] for r in rows])
|
||||
|
||||
Reference in New Issue
Block a user