first prototype

This commit is contained in:
2026-05-02 13:46:36 +02:00
parent 0864326f5d
commit 4c4aaa4243
3 changed files with 269 additions and 0 deletions
+141
View File
@@ -0,0 +1,141 @@
from nicegui import ui
class aggridx(ui.aggrid):
# #TODO: this should be done in the constructor via a keyword argument switch
# #TODO: this would need the from_pandas/from_polars classmethods to pass through that switch
# def setup_sync_edits(self):
# """
# automatically sync data to the server after edits
# this operation maintains the current view on the data
# """
# self.on("cellValueChanged", self.set_cell_server)
def set_cell_server(self, evt):
row_index = evt.args["rowIndex"]
col_id = evt.args["colId"]
new_val = evt.args["newValue"]
with self.props.suspend_updates():
self.options["rowData"][row_index][col_id] = new_val
def set_cell_client(self, evt):
row_id = evt.args["rowId"]
col_id = evt.args["colId"]
new_val = evt.args["newValue"]
self.run_row_method(row_id, "setDataValue", col_id, new_val)
# def set_row_server_from_event(self, evt):
# index = evt.args["rowIndex"]
# data = evt.args["data"]
# self.set_row_server(index, data)
# def set_row_server(self, index, data):
# """
# overwrite row at index with data
# this operation maintains the current view on the data
# """
# with self.props.suspend_updates():
# self.options["rowData"][index] = data
def append(self, row):
"""
append row to the bottom of the table and
append any missing columns on the right side of the table
this operation maintains the current view on the data
"""
self.ensure_column_defs(row)
self.add_row_data([row])
# def insert(self, index, row):
# """
# insert row at the provided index
# append any missing columns on the right side of the table
# this operation maintains the current view on the data
# """
# self.ensure_column_defs(row)
# self.add_row_data([row], index=index)
# def extend(self, rows):
# """
# append rows to the bottom of the table and
# append any missing columns on the right side of the table
# this operation maintains the current view on the data
# """
# columns = dict.fromkeys(k for d in rows for k in d) # dict.fromkeys acts as ordered set
# self.ensure_column_defs(columns)
# self.add_row_data(rows)
def ensure_column_defs(self, columns):
"""
append any missing columns on the right side of the table
this operation maintains the current view on the data
"""
current_column_defs = self.options["columnDefs"]
current_fields = [i["field"] for i in current_column_defs]
added_fields = [i for i in columns if i not in current_fields]
if not added_fields:
return
added_column_defs = [{"field": n} for n in added_fields]
new_column_defs = current_column_defs + added_column_defs
# update server without re-draw
with self.props.suspend_updates():
self.options["columnDefs"] = new_column_defs
# update client
self.run_grid_method("setGridOption", "columnDefs", new_column_defs)
def add_row_data(self, rows, index=-1):
"""
insert rows at the provided index,
with the default being appending at the bottom of the table
this operation maintains the current view on the data
"""
# update server without re-draw
with self.props.suspend_updates():
self.options["rowData"].extend(rows)
# update client
self.run_grid_method("applyTransaction", {"add": rows, "addIndex": index})
# def scroll_to_row_index(self, index):
# """
# scroll the view on the data such that the row with the given index is visible
# negative indices count from the back
# """
# nrows = len(self.options["rowData"])
# index %= nrows # move index into [0, nrows)
# self.run_grid_method("ensureIndexVisible", index)
# def scroll_to_row_key(self, key):
# """
# scroll the view on the data such that the row with the given key is visible
# consistent with tables generated from dataframes, the column called index is used to match the key
# if the key is not found, the view is not scrolled
# """
# indices = [i.get("index") for i in self.options["rowData"]]
# try:
# index = indices.index(key)
# except ValueError:
# return
# self.scroll_to_row_index(index)
# def scroll_to_column_index(self, index):
# """
# scroll the view on the data such that the column with the given index is visible
# negative indices count from the back
# """
# key = self.options["columnDefs"][index]["field"]
# self.scroll_to_column_key(key)
# def scroll_to_column_key(self, key):
# """
# scroll the view on the data such that the column with the given key is visible
# """
# self.run_grid_method("ensureColumnVisible", key)
+18
View File
@@ -0,0 +1,18 @@
from nicegui import ui
class Registry:
def __init__(self):
self.data = {}
def __iter__(self):
return iter(self.data.values())
def add(self, widget):
client = ui.context.client
self.data[client.id] = widget
client.on_disconnect(lambda: self.data.pop(client.id, None))
+110
View File
@@ -0,0 +1,110 @@
from datetime import datetime
from typing import Any
import arcticdb as adb
import pandas as pd
from fastapi import APIRouter
from nicegui import app, ui
from aggridx import aggridx
from registry import Registry
uri = "lmdb://adb"
ac = adb.Arctic(uri)
lib = ac.get_library(
"stand",
create_if_missing=True,
library_options=adb.LibraryOptions(dynamic_schema=True)
)
router = APIRouter()
grids = Registry()
options = {
"defaultColDef": {
"filter": True,
"editable": True,
"sortable": True,
"resizable": True
},
"pagination": True,
"paginationAutoPageSize": True,
"theme": "balham"
}
def update_adb(evt):
if evt.args.get("source") != "edit":
# ignore event if it was no direct edit
return
index = evt.args["data"]["index"]
index = datetime.fromisoformat(index) # nicegui converts datetime to str
df = lib.read("p12345", date_range=[index]).data
col_id = evt.args["colId"]
new_val = evt.args["newValue"]
df.at[index, col_id] = new_val
lib.update("p12345", df)
def update_grids(evt):
sender = evt.sender
for grid in grids:
grid.set_cell_server(evt)
if grid == sender:
# the sender is already up-to-date
continue
grid.set_cell_client(evt)
@ui.page("/")
def page():
# with ui.left_drawer() as ld:
# ld.hide()
# dark = ui.dark_mode()
# ui.switch("dark mode").bind_value(dark)
try:
df = lib.read("p12345").data
except adb.exceptions.NoSuchVersionException:
df = pd.DataFrame()
df = df.reset_index()
grid = aggridx.from_pandas(df, options=options)
grid.classes("h-[calc(100vh-2rem)]") # full height minus padding
grid.on("cellValueChanged", update_adb)
grid.on("cellValueChanged", update_grids)
grids.add(grid)
@router.post("/append/")
def append(row: dict[str, Any]):
now = datetime.now()
df = pd.DataFrame(row, index=[now])
lib.append("p12345", df)
now = str(now) # nicegui converts datetime to str
row = {"index": now, **row} # setdefault would not force index to be the first column
res = []
for grid in grids:
grid.append(row)
res.append(grid.options)
return res
app.include_router(router)
ui.run(title="stand", favicon="icon.png", fastapi_docs=True, dark=True)