From 8f73b66cacc77e8b1ce77bc3c95dfbdfced69cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Mon, 17 Jun 2024 15:44:42 +0200 Subject: [PATCH] feat: more progress on gradio ui --- src/quantifiedme/ui/__main__.py | 2 +- src/quantifiedme/ui/main.py | 230 +++++++++++++++++++++++--------- 2 files changed, 171 insertions(+), 61 deletions(-) diff --git a/src/quantifiedme/ui/__main__.py b/src/quantifiedme/ui/__main__.py index bcbfde6..3e3878c 100644 --- a/src/quantifiedme/ui/__main__.py +++ b/src/quantifiedme/ui/__main__.py @@ -1,4 +1,4 @@ -from .main import main +from quantifiedme.ui.main import main if __name__ == "__main__": diff --git a/src/quantifiedme/ui/main.py b/src/quantifiedme/ui/main.py index 7f44c6a..c246283 100644 --- a/src/quantifiedme/ui/main.py +++ b/src/quantifiedme/ui/main.py @@ -1,4 +1,4 @@ -import threading +from datetime import datetime, timedelta from functools import lru_cache import gradio as gr @@ -8,55 +8,185 @@ @lru_cache -def load_all(fast=True): +def load_all(fast=True) -> pd.DataFrame: + print("Loading all data") df = load_all_df(fast) + print("DONE! Loaded", len(df), "rows") return df -# Load all data in background -threading.Thread(target=load_all, args=(True,)).start() +def load_timeperiods(tps: list[tuple[datetime, datetime]]) -> pd.DataFrame: + df = load_all() + print("Slicing timeperiods") + df = pd.concat([df[(df.index >= start) & (df.index <= end)] for start, end in tps]) + print("DONE! Sliced", len(df), "rows") + print(df) + return df + + +def load_timeperiod(period: str) -> pd.DataFrame: + return load_timeperiods([period_to_timeperiod(period)]) -def load_all_cols(): +def period_to_timeperiod(period: str) -> tuple[datetime, datetime]: + now = datetime.now() + match period: + case "Day": + return (now.replace(hour=0, minute=0, second=0), now) + case "Week": + return ( + now.replace(hour=0, minute=0, second=0) - timedelta(days=now.weekday()), + now, + ) + case "Month": + return (now.replace(day=1, hour=0, minute=0, second=0), now) + case "Year": + return (now.replace(month=1, day=1, hour=0, minute=0, second=0), now) + case _: + raise ValueError(f"Unknown period: {period}") + + +def load_all_cols() -> list[str]: df = load_all() - return df.columns + return [str(c) for c in df.columns] -def df_loaded(df): +def dropdown_dfcols(df) -> gr.Dropdown: # if no data, return empty choices if df.empty or len(df) == 0: - return gr.update(choices=[], value=None) - return gr.update(choices=[str(c) for c in df.columns], value=df.columns[0]) - - -def col_changed(df, col: str): - assert len(col) < 30, f"Column name too long: {col}" - print(f"Col changed to {col}") - df = df.reset_index().rename(columns={"index": "date"}) - df = df[["date", col]] - print(df[col].dtype) - print(df[col].head()) - gr.update(data=df, y=col.replace(":", "\\:"), y_lim=[0, max(df[col])]) - return df + return gr.Dropdown(choices=[], value=None) + return gr.Dropdown(choices=[str(c) for c in df.columns], value=df.columns[0]) + + +def plot_cat(df: pd.DataFrame | None, col: str | None = None) -> gr.BarPlot: + y_lim = None + if col: + assert df is not None + print(f"Col changed to {col}") + df = df.reset_index().rename(columns={"index": "date"}) + df = df[["date", col]] + y_max = max(df[col]) + y_lim = [0, round(y_max) + 1 if isinstance(y_max, (int, float)) else 1] + col = col.replace(":", "\\:") + else: + col = "" + return gr.BarPlot( + df, + x="date", + y=col, + y_lim=y_lim, + width=350, + height=300, + ) + + +def filter_df_cols(df: pd.DataFrame, prefix: str) -> pd.DataFrame: + # filter columns by column prefix (like "time:") + return df[[c for c in df.columns if c.startswith(prefix)]] + + +def plot_top_cats(df: pd.DataFrame | None) -> gr.BarPlot: + if df is not None: + df = ( + filter_df_cols(df, "time:") + .drop(columns=["time:All_events", "time:All_cols"]) + .sum() + .sort_values(ascending=False) + .head(10) + .reset_index() + .rename(columns={"index": "category", 0: "hours"}) + ) + else: + df = pd.DataFrame({"category": [], "hours": []}) + return gr.BarPlot( + df, + x="category", + y="hours", + vertical=False, + width=350, + height=300, + ) + + +def main(): + with gr.Blocks(title="QuantifiedMe (gradio)") as app: + # Title + gr.Markdown( + """ +# QuantifiedMe +This is a dashboard to explore quantified self data. + """.strip() + ) + + # Summary (day/week/month/year) + with gr.Tab("Summary"): + view_summary() + + with gr.Tab("Explore"): + view_explore() + + with gr.Tab("Time"): + gr.Markdown("TODO") + with gr.Tab("Sleep"): + gr.Markdown("TODO") -# Function to display data sources summary -def data_sources_summary(): - # Options - fast = gr.Checkbox(value=True, label="Fast") - btn = gr.Button(value="Load") + with gr.Tab("Drugs"): + gr.Markdown("TODO") + + with gr.Tab("Correlations"): + gr.Markdown("Just an example of plot with inputs") + view_plot_correlations() + + with gr.Tab("Data sources"): + gr.Markdown("TODO") + + app.launch() + + +def view_summary(): + """View to show summary of data for the current day/week/month/year""" + with gr.Group(): + gr.Markdown("Query options") + range = gr.Dropdown( + label="Range", choices=["Day", "Week", "Month", "Year"], value="Month" + ) + btn = gr.Button(value="Load") + + with gr.Group(): + gr.Markdown("Top categories") + plot_top_cats_output = plot_top_cats(None) + + dataframe_el = gr.Dataframe(pd.DataFrame()) + btn.click(load_timeperiod, [range], dataframe_el) + + # when loaded + # update the top categories plot + dataframe_el.change( + fn=plot_top_cats, inputs=[dataframe_el], outputs=[plot_top_cats_output] + ) + + +def view_explore(): + """View to explore the raw data""" + with gr.Group(): + gr.Markdown("Query options") + fast = gr.Checkbox(value=True, label="Fast") + btn = gr.Button(value="Load") cols_dropdown = gr.Dropdown( label="Columns", choices=[], allow_custom_value=True, interactive=True ) - dataframe_el = gr.Dataframe() + dataframe_el = gr.Dataframe([]) btn.click(load_all, [fast], dataframe_el) # when loaded, update the dropdown - dataframe_el.change(fn=df_loaded, inputs=[dataframe_el], outputs=[cols_dropdown]) + dataframe_el.change( + fn=dropdown_dfcols, inputs=[dataframe_el], outputs=[cols_dropdown] + ) - output = gr.BarPlot( + plot_cat_output = gr.BarPlot( x="date", y="time\\:Work", # tooltip=["x", "y"], @@ -67,43 +197,24 @@ def data_sources_summary(): # when dropdown changes, update the plot cols_dropdown.change( - fn=col_changed, inputs=[dataframe_el, cols_dropdown], outputs=[output] + fn=plot_cat, inputs=[dataframe_el, cols_dropdown], outputs=[plot_cat_output] ) - return "Summary of data sources goes here..." +def view_plot_correlations(): + """View to plot correlations between columns""" -def plot(v, a): - g = 9.81 - theta = a / 180 * 3.14 - tmax = ((2 * v) * np.sin(theta)) / g - timemat = tmax * np.linspace(0, 1, 40) - - x = (v * timemat) * np.cos(theta) - y = ((v * timemat) * np.sin(theta)) - ((0.5 * g) * (timemat**2)) - df = pd.DataFrame({"x": x, "y": y}) - return df - - -def main(): - with gr.Blocks() as app: - # Summary - with gr.Tab("Summary"): - gr.Markdown(data_sources_summary()) - - # Trajectory - with gr.Tab("Trajectory"): - gr.Markdown("Just an example of plot with inputs") - plot_trajectory() - - # Data overview - with gr.Tab("Data Overview"): - gr.Markdown("Data Overview goes here...") - - app.launch() + def plot(v, a): + g = 9.81 + theta = a / 180 * 3.14 + tmax = ((2 * v) * np.sin(theta)) / g + timemat = tmax * np.linspace(0, 1, 40) + x = (v * timemat) * np.cos(theta) + y = ((v * timemat) * np.sin(theta)) - ((0.5 * g) * (timemat**2)) + df = pd.DataFrame({"x": x, "y": y}) + return df -def plot_trajectory(): with gr.Row(): speed = gr.Slider(1, 30, 25, label="Speed") angle = gr.Slider(0, 90, 45, label="Angle") @@ -115,7 +226,6 @@ def plot_trajectory(): tooltip=["x", "y"], x_lim=[0, 100], y_lim=[0, 60], - width=350, height=300, ) btn = gr.Button(value="Run")