first prototype
This commit is contained in:
+141
@@ -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
@@ -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))
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user