From b7c035c8ee8cc8e8437a1d0144f71161202a5b88 Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Mon, 26 Jun 2023 09:46:13 -0300 Subject: [PATCH 1/8] Annotation export example --- examples/annotation-export/app.py | 127 +++++++ examples/annotation-export/boulder_temp.csv | 366 ++++++++++++++++++++ examples/annotation-export/requirements.txt | 2 + 3 files changed, 495 insertions(+) create mode 100644 examples/annotation-export/app.py create mode 100644 examples/annotation-export/boulder_temp.csv create mode 100644 examples/annotation-export/requirements.txt diff --git a/examples/annotation-export/app.py b/examples/annotation-export/app.py new file mode 100644 index 000000000..4abfc3115 --- /dev/null +++ b/examples/annotation-export/app.py @@ -0,0 +1,127 @@ +from pathlib import Path + +import matplotlib.dates as mdates +import matplotlib.pyplot as plt +import pandas as pd +import seaborn as sns +from shiny import App, Inputs, Outputs, Session, reactive, render, ui +from shiny.plotutils import brushed_points + +app_ui = ui.page_fluid( + ui.panel_title("Plot annotation example"), + ui.p( + """ + Select points to annotate them, + the plot is drawn with seaborn and all interaction is handled by Shiny. + """, + {"style": "font-size: larger"}, + ), + ui.row( + ui.column( + 6, + ui.output_plot("time_series", brush=ui.brush_opts(direction="x")), + ui.output_ui("annotator"), + ), + ui.column( + 4, + ui.output_data_frame("annotations"), + ), + ui.column(2, ui.download_button("download", "Download CSV")), + ), +) + + +def server(input: Inputs, output: Outputs, session: Session): + annotation_values = reactive.Value(None) + + @reactive.Calc + def ts_data(): + path = Path(__file__).parent / "boulder_temp.csv" + dataframe = pd.read_csv(path) + dataframe["date"] = pd.to_datetime(dataframe["date"]) + dataframe["annotation"] = "" + + annotated = annotation_values.get() + if annotated is None: + out = dataframe + else: + out = annotated + return out + + @reactive.Calc + def selected_data(): + out = brushed_points(ts_data(), input.time_series_brush(), xvar="date") + return out + + @reactive.Effect + @reactive.event(input.annotate_button) + def _(): + selected = selected_data().copy() + selected["annotation_new"] = input.annotation() + selected = selected.loc[:, ["date", "annotation_new"]] + + df = ts_data().copy() + + df = df.merge(selected, on="date", how="left") + df["annotation_new"] = df["annotation_new"].fillna("") + updated_rows = df["annotation_new"] != "" + df.loc[updated_rows, "annotation"] = df.loc[updated_rows, "annotation_new"] + df = df.loc[:, ["date", "temp_c", "annotation"]] + annotation_values.set(df) + + @output + @render.plot + def time_series(): + fig, ax = plt.subplots() + ax.xaxis.set_major_locator(mdates.MonthLocator()) + ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m")) + ax.set_title("Temperature readings, Boulder Colorado") + out = sns.scatterplot( + data=ts_data(), x="date", y="temp_c", hue="annotation", ax=ax + ) + + out.tick_params(axis="x", rotation=30) + return out.get_figure() + + @output + @render.ui + def annotator(): + if input.time_series_brush() is not None: + selected = selected_data().copy() + + min = str(selected["date"].min()) + max = str(selected["date"].max()) + + min = min.replace(" 00:00:00+00:00", "") + max = max.replace(" 00:00:00+00:00", "") + + out = ui.TagList( + ui.row( + {"style": "padding-top: 20px;"}, + ui.column( + 4, + ui.p(f"{min} to {max}"), + ), + ui.column( + 4, + ui.input_text("annotation", ""), + ), + ui.column(4, ui.input_action_button("annotate_button", "Submit")), + ) + ) + return out + + @output + @render.data_frame + def annotations(): + df = ts_data().copy() + df["date"] = df["date"].dt.strftime("%Y-%m-%d") + df = df.loc[df["annotation"] != ""] + return df + + @session.download(filename="data.csv") + def download(): + yield ts_data().to_csv() + + +app = App(app_ui, server) diff --git a/examples/annotation-export/boulder_temp.csv b/examples/annotation-export/boulder_temp.csv new file mode 100644 index 000000000..b09620afb --- /dev/null +++ b/examples/annotation-export/boulder_temp.csv @@ -0,0 +1,366 @@ +date,temp_c +2021-01-01T00:00:00Z,6.1 +2021-01-02T00:00:00Z,11.1 +2021-01-03T00:00:00Z,11.7 +2021-01-04T00:00:00Z,15 +2021-01-05T00:00:00Z,12.8 +2021-01-06T00:00:00Z,7.2 +2021-01-07T00:00:00Z,7.8 +2021-01-08T00:00:00Z,7.2 +2021-01-09T00:00:00Z,2.2 +2021-01-10T00:00:00Z,1.7 +2021-01-11T00:00:00Z,6.7 +2021-01-12T00:00:00Z,12.2 +2021-01-13T00:00:00Z,17.8 +2021-01-14T00:00:00Z,13.3 +2021-01-15T00:00:00Z,7.8 +2021-01-16T00:00:00Z,11.1 +2021-01-17T00:00:00Z,11.1 +2021-01-18T00:00:00Z,7.8 +2021-01-19T00:00:00Z,3.9 +2021-01-20T00:00:00Z,15 +2021-01-21T00:00:00Z,8.3 +2021-01-22T00:00:00Z,7.2 +2021-01-23T00:00:00Z,6.7 +2021-01-24T00:00:00Z,3.3 +2021-01-25T00:00:00Z,3.3 +2021-01-26T00:00:00Z,-2.2 +2021-01-27T00:00:00Z,-2.2 +2021-01-28T00:00:00Z,7.2 +2021-01-29T00:00:00Z,14.4 +2021-01-30T00:00:00Z,12.2 +2021-01-31T00:00:00Z,11.1 +2021-02-01T00:00:00Z,10 +2021-02-02T00:00:00Z,18.3 +2021-02-03T00:00:00Z,18.3 +2021-02-04T00:00:00Z,7.8 +2021-02-05T00:00:00Z,7.2 +2021-02-06T00:00:00Z,7.2 +2021-02-07T00:00:00Z,12.2 +2021-02-08T00:00:00Z,8.9 +2021-02-09T00:00:00Z,-0.6 +2021-02-10T00:00:00Z,-0.6 +2021-02-11T00:00:00Z,-2.8 +2021-02-12T00:00:00Z,-9.4 +2021-02-13T00:00:00Z,-13.9 +2021-02-14T00:00:00Z,-15.6 +2021-02-15T00:00:00Z,-6.1 +2021-02-16T00:00:00Z,5.6 +2021-02-17T00:00:00Z,1.7 +2021-02-18T00:00:00Z,1.1 +2021-02-19T00:00:00Z,7.2 +2021-02-20T00:00:00Z,10 +2021-02-21T00:00:00Z,6.1 +2021-02-22T00:00:00Z,13.3 +2021-02-23T00:00:00Z,15.6 +2021-02-24T00:00:00Z,12.2 +2021-02-25T00:00:00Z,1.7 +2021-02-26T00:00:00Z,7.2 +2021-02-27T00:00:00Z,5 +2021-02-28T00:00:00Z,3.9 +2021-03-01T00:00:00Z,9.4 +2021-03-02T00:00:00Z,16.7 +2021-03-03T00:00:00Z,16.7 +2021-03-04T00:00:00Z,8.9 +2021-03-05T00:00:00Z,15.6 +2021-03-06T00:00:00Z,19.4 +2021-03-07T00:00:00Z,20.6 +2021-03-08T00:00:00Z,20 +2021-03-09T00:00:00Z,20.6 +2021-03-10T00:00:00Z,15.6 +2021-03-11T00:00:00Z,6.7 +2021-03-12T00:00:00Z,5.6 +2021-03-13T00:00:00Z,4.4 +2021-03-14T00:00:00Z,0 +2021-03-15T00:00:00Z,4.4 +2021-03-16T00:00:00Z,4.4 +2021-03-17T00:00:00Z,7.8 +2021-03-18T00:00:00Z,7.8 +2021-03-19T00:00:00Z,14.4 +2021-03-20T00:00:00Z,17.8 +2021-03-21T00:00:00Z,11.1 +2021-03-22T00:00:00Z,4.4 +2021-03-23T00:00:00Z,5 +2021-03-24T00:00:00Z,4.4 +2021-03-25T00:00:00Z,10 +2021-03-26T00:00:00Z,9.4 +2021-03-27T00:00:00Z,12.2 +2021-03-28T00:00:00Z,17.2 +2021-03-29T00:00:00Z,21.7 +2021-03-30T00:00:00Z,16.7 +2021-03-31T00:00:00Z,12.2 +2021-04-01T00:00:00Z,21.7 +2021-04-02T00:00:00Z,23.3 +2021-04-03T00:00:00Z,25.6 +2021-04-04T00:00:00Z,26.7 +2021-04-05T00:00:00Z,25 +2021-04-06T00:00:00Z,24.4 +2021-04-07T00:00:00Z,17.2 +2021-04-08T00:00:00Z,21.1 +2021-04-09T00:00:00Z,19.4 +2021-04-10T00:00:00Z,21.1 +2021-04-11T00:00:00Z,18.3 +2021-04-12T00:00:00Z,11.7 +2021-04-13T00:00:00Z,3.9 +2021-04-14T00:00:00Z,3.9 +2021-04-15T00:00:00Z,3.9 +2021-04-16T00:00:00Z,3.3 +2021-04-17T00:00:00Z,6.7 +2021-04-18T00:00:00Z,14.4 +2021-04-19T00:00:00Z,12.2 +2021-04-20T00:00:00Z,2.8 +2021-04-21T00:00:00Z,1.7 +2021-04-22T00:00:00Z,10 +2021-04-23T00:00:00Z,13.9 +2021-04-24T00:00:00Z,17.8 +2021-04-25T00:00:00Z,26.1 +2021-04-26T00:00:00Z,24.4 +2021-04-27T00:00:00Z,20.6 +2021-04-28T00:00:00Z,15 +2021-04-29T00:00:00Z,20.6 +2021-04-30T00:00:00Z,26.1 +2021-05-01T00:00:00Z,28.9 +2021-05-02T00:00:00Z,26.1 +2021-05-03T00:00:00Z,12.2 +2021-05-04T00:00:00Z,15.6 +2021-05-05T00:00:00Z,15.6 +2021-05-06T00:00:00Z,20.6 +2021-05-07T00:00:00Z,27.8 +2021-05-08T00:00:00Z,24.4 +2021-05-09T00:00:00Z,16.1 +2021-05-10T00:00:00Z,7.2 +2021-05-11T00:00:00Z,5.6 +2021-05-12T00:00:00Z,16.7 +2021-05-13T00:00:00Z,23.9 +2021-05-14T00:00:00Z,22.2 +2021-05-15T00:00:00Z,19.4 +2021-05-16T00:00:00Z,15 +2021-05-17T00:00:00Z,17.2 +2021-05-18T00:00:00Z,17.8 +2021-05-19T00:00:00Z,21.7 +2021-05-20T00:00:00Z,25.6 +2021-05-21T00:00:00Z,24.4 +2021-05-22T00:00:00Z,22.8 +2021-05-23T00:00:00Z,23.9 +2021-05-24T00:00:00Z,22.8 +2021-05-25T00:00:00Z,24.4 +2021-05-26T00:00:00Z,25.6 +2021-05-27T00:00:00Z,25 +2021-05-28T00:00:00Z,26.1 +2021-05-29T00:00:00Z,21.7 +2021-05-30T00:00:00Z,17.8 +2021-05-31T00:00:00Z,13.9 +2021-06-01T00:00:00Z,20 +2021-06-02T00:00:00Z,23.3 +2021-06-03T00:00:00Z,27.8 +2021-06-04T00:00:00Z,30.6 +2021-06-05T00:00:00Z,32.8 +2021-06-06T00:00:00Z,31.1 +2021-06-07T00:00:00Z,29.4 +2021-06-08T00:00:00Z,31.1 +2021-06-09T00:00:00Z,31.7 +2021-06-10T00:00:00Z,32.8 +2021-06-11T00:00:00Z,32.2 +2021-06-12T00:00:00Z,30 +2021-06-13T00:00:00Z,33.3 +2021-06-14T00:00:00Z,34.4 +2021-06-15T00:00:00Z,35.6 +2021-06-16T00:00:00Z,37.2 +2021-06-17T00:00:00Z,37.2 +2021-06-18T00:00:00Z,32.8 +2021-06-19T00:00:00Z,31.7 +2021-06-20T00:00:00Z,27.2 +2021-06-21T00:00:00Z,23.9 +2021-06-22T00:00:00Z,33.9 +2021-06-23T00:00:00Z,35.6 +2021-06-24T00:00:00Z,29.4 +2021-06-25T00:00:00Z,25 +2021-06-26T00:00:00Z,20.6 +2021-06-27T00:00:00Z,22.2 +2021-06-28T00:00:00Z,22.2 +2021-06-29T00:00:00Z,24.4 +2021-06-30T00:00:00Z,27.2 +2021-07-01T00:00:00Z,25 +2021-07-02T00:00:00Z,28.3 +2021-07-03T00:00:00Z,30.6 +2021-07-04T00:00:00Z,29.4 +2021-07-05T00:00:00Z,30 +2021-07-06T00:00:00Z,28.3 +2021-07-07T00:00:00Z,30.6 +2021-07-08T00:00:00Z,36.7 +2021-07-09T00:00:00Z,34.4 +2021-07-10T00:00:00Z,32.8 +2021-07-11T00:00:00Z,28.9 +2021-07-12T00:00:00Z,31.1 +2021-07-13T00:00:00Z,30.6 +2021-07-14T00:00:00Z,25.6 +2021-07-15T00:00:00Z,27.8 +2021-07-16T00:00:00Z,32.2 +2021-07-17T00:00:00Z,32.8 +2021-07-18T00:00:00Z,32.8 +2021-07-19T00:00:00Z,33.3 +2021-07-20T00:00:00Z,33.9 +2021-07-21T00:00:00Z,31.7 +2021-07-22T00:00:00Z,34.4 +2021-07-23T00:00:00Z,33.9 +2021-07-24T00:00:00Z,30 +2021-07-25T00:00:00Z,32.2 +2021-07-26T00:00:00Z,32.8 +2021-07-27T00:00:00Z,33.9 +2021-07-28T00:00:00Z,35 +2021-07-29T00:00:00Z,31.7 +2021-07-30T00:00:00Z,32.8 +2021-07-31T00:00:00Z,24.4 +2021-08-01T00:00:00Z,26.7 +2021-08-02T00:00:00Z,29.4 +2021-08-03T00:00:00Z,27.8 +2021-08-04T00:00:00Z,27.2 +2021-08-05T00:00:00Z,32.8 +2021-08-06T00:00:00Z,32.2 +2021-08-07T00:00:00Z,27.2 +2021-08-08T00:00:00Z,32.8 +2021-08-09T00:00:00Z,34.4 +2021-08-10T00:00:00Z,34.4 +2021-08-11T00:00:00Z,33.9 +2021-08-12T00:00:00Z,30 +2021-08-13T00:00:00Z,28.9 +2021-08-14T00:00:00Z,33.9 +2021-08-15T00:00:00Z,31.7 +2021-08-16T00:00:00Z,32.8 +2021-08-17T00:00:00Z,35 +2021-08-18T00:00:00Z,33.3 +2021-08-19T00:00:00Z,27.8 +2021-08-20T00:00:00Z,26.1 +2021-08-21T00:00:00Z,27.8 +2021-08-22T00:00:00Z,32.2 +2021-08-23T00:00:00Z,32.2 +2021-08-24T00:00:00Z,33.9 +2021-08-25T00:00:00Z,31.1 +2021-08-26T00:00:00Z,28.3 +2021-08-27T00:00:00Z,32.8 +2021-08-28T00:00:00Z,31.7 +2021-08-29T00:00:00Z,28.3 +2021-08-30T00:00:00Z,32.8 +2021-08-31T00:00:00Z,34.4 +2021-09-01T00:00:00Z,30.6 +2021-09-02T00:00:00Z,27.8 +2021-09-03T00:00:00Z,27.2 +2021-09-04T00:00:00Z,28.9 +2021-09-05T00:00:00Z,30.6 +2021-09-06T00:00:00Z,35 +2021-09-07T00:00:00Z,30 +2021-09-08T00:00:00Z,30.6 +2021-09-09T00:00:00Z,35 +2021-09-10T00:00:00Z,37.2 +2021-09-11T00:00:00Z,35 +2021-09-12T00:00:00Z,29.4 +2021-09-13T00:00:00Z,28.3 +2021-09-14T00:00:00Z,25.6 +2021-09-15T00:00:00Z,32.2 +2021-09-16T00:00:00Z,32.8 +2021-09-17T00:00:00Z,23.3 +2021-09-18T00:00:00Z,32.8 +2021-09-19T00:00:00Z,31.1 +2021-09-20T00:00:00Z,26.1 +2021-09-21T00:00:00Z,18.9 +2021-09-22T00:00:00Z,26.1 +2021-09-23T00:00:00Z,28.3 +2021-09-24T00:00:00Z,22.8 +2021-09-25T00:00:00Z,30.6 +2021-09-26T00:00:00Z,32.2 +2021-09-27T00:00:00Z,31.1 +2021-09-28T00:00:00Z,27.8 +2021-09-29T00:00:00Z,21.7 +2021-09-30T00:00:00Z,12.2 +2021-10-01T00:00:00Z,19.4 +2021-10-02T00:00:00Z,22.2 +2021-10-03T00:00:00Z,26.1 +2021-10-04T00:00:00Z,27.8 +2021-10-05T00:00:00Z,28.3 +2021-10-06T00:00:00Z,25 +2021-10-07T00:00:00Z,26.1 +2021-10-08T00:00:00Z,24.4 +2021-10-09T00:00:00Z,20 +2021-10-10T00:00:00Z,21.1 +2021-10-11T00:00:00Z,21.1 +2021-10-12T00:00:00Z,12.2 +2021-10-13T00:00:00Z,16.1 +2021-10-14T00:00:00Z,10 +2021-10-15T00:00:00Z,13.3 +2021-10-16T00:00:00Z,21.7 +2021-10-17T00:00:00Z,23.9 +2021-10-18T00:00:00Z,24.4 +2021-10-19T00:00:00Z,18.3 +2021-10-20T00:00:00Z,18.3 +2021-10-21T00:00:00Z,17.2 +2021-10-22T00:00:00Z,23.9 +2021-10-23T00:00:00Z,22.8 +2021-10-24T00:00:00Z,21.1 +2021-10-25T00:00:00Z,22.8 +2021-10-26T00:00:00Z,21.7 +2021-10-27T00:00:00Z,15 +2021-10-28T00:00:00Z,15 +2021-10-29T00:00:00Z,24.4 +2021-10-30T00:00:00Z,24.4 +2021-10-31T00:00:00Z,12.8 +2021-11-01T00:00:00Z,3.9 +2021-11-02T00:00:00Z,6.7 +2021-11-03T00:00:00Z,16.1 +2021-11-04T00:00:00Z,21.1 +2021-11-05T00:00:00Z,23.9 +2021-11-06T00:00:00Z,23.9 +2021-11-07T00:00:00Z,25.6 +2021-11-08T00:00:00Z,12.8 +2021-11-09T00:00:00Z,12.8 +2021-11-10T00:00:00Z,13.9 +2021-11-11T00:00:00Z,12.8 +2021-11-12T00:00:00Z,13.3 +2021-11-13T00:00:00Z,19.4 +2021-11-14T00:00:00Z,18.9 +2021-11-15T00:00:00Z,22.8 +2021-11-16T00:00:00Z,22.2 +2021-11-17T00:00:00Z,15 +2021-11-18T00:00:00Z,10.6 +2021-11-19T00:00:00Z,18.9 +2021-11-20T00:00:00Z,14.4 +2021-11-21T00:00:00Z,14.4 +2021-11-22T00:00:00Z,21.1 +2021-11-23T00:00:00Z,21.1 +2021-11-24T00:00:00Z,13.9 +2021-11-25T00:00:00Z,16.1 +2021-11-26T00:00:00Z,22.2 +2021-11-27T00:00:00Z,14.4 +2021-11-28T00:00:00Z,19.4 +2021-11-29T00:00:00Z,23.3 +2021-11-30T00:00:00Z,15 +2021-12-01T00:00:00Z,22.2 +2021-12-02T00:00:00Z,22.2 +2021-12-03T00:00:00Z,16.1 +2021-12-04T00:00:00Z,20.6 +2021-12-05T00:00:00Z,17.8 +2021-12-06T00:00:00Z,-0.6 +2021-12-07T00:00:00Z,13.3 +2021-12-08T00:00:00Z,13.3 +2021-12-09T00:00:00Z,10.6 +2021-12-10T00:00:00Z,5.6 +2021-12-11T00:00:00Z,9.4 +2021-12-12T00:00:00Z,18.3 +2021-12-13T00:00:00Z,13.3 +2021-12-14T00:00:00Z,16.7 +2021-12-15T00:00:00Z,13.3 +2021-12-16T00:00:00Z,7.8 +2021-12-17T00:00:00Z,7.2 +2021-12-18T00:00:00Z,3.9 +2021-12-19T00:00:00Z,16.7 +2021-12-20T00:00:00Z,16.7 +2021-12-21T00:00:00Z,17.2 +2021-12-22T00:00:00Z,17.2 +2021-12-23T00:00:00Z,17.2 +2021-12-24T00:00:00Z,12.8 +2021-12-25T00:00:00Z,12.2 +2021-12-26T00:00:00Z,11.1 +2021-12-27T00:00:00Z,6.7 +2021-12-28T00:00:00Z,2.8 +2021-12-29T00:00:00Z,0.6 +2021-12-30T00:00:00Z,6.7 +2021-12-31T00:00:00Z,6.7 diff --git a/examples/annotation-export/requirements.txt b/examples/annotation-export/requirements.txt new file mode 100644 index 000000000..4695c7070 --- /dev/null +++ b/examples/annotation-export/requirements.txt @@ -0,0 +1,2 @@ +seaborn +pandas From 6a28ab6a6ca07a70dcdaa9deeaca5611397566b0 Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Mon, 26 Jun 2023 10:00:09 -0300 Subject: [PATCH 2/8] Linting --- examples/annotation-export/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/annotation-export/app.py b/examples/annotation-export/app.py index 4abfc3115..ddc220b2c 100644 --- a/examples/annotation-export/app.py +++ b/examples/annotation-export/app.py @@ -4,6 +4,7 @@ import matplotlib.pyplot as plt import pandas as pd import seaborn as sns + from shiny import App, Inputs, Outputs, Session, reactive, render, ui from shiny.plotutils import brushed_points From 22eb1b89ab0c9b81efbe59d919aa80ebc48fd2a8 Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Mon, 26 Jun 2023 16:07:04 -0300 Subject: [PATCH 3/8] Initial application --- examples/duckdb/app.py | 82 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 examples/duckdb/app.py diff --git a/examples/duckdb/app.py b/examples/duckdb/app.py new file mode 100644 index 000000000..8b8a0682b --- /dev/null +++ b/examples/duckdb/app.py @@ -0,0 +1,82 @@ +import os +import urllib.request +from pathlib import Path + +import duckdb +import shinyswatch.theme as theme +from query import query_output_server, query_output_ui + +from shiny import App, reactive, ui + +folder = Path(__file__).parent +db_file = folder / "weather.db" + +if not Path.exists(db_file): + csv_url = "https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2022/2022-12-20/weather_forecasts.csv" + local_file_path = folder / "weather.csv" + urllib.request.urlretrieve(csv_url, local_file_path) + con = duckdb.connect(str(db_file), read_only=False) + con.sql(f"CREATE TABLE weather AS SELECT * FROM read_csv_auto('{local_file_path}')") + con.close() + +con = duckdb.connect(str(db_file), read_only=True) + +button_style = {"style": "margin: 15px"} + +app_ui = ui.page_fluid( + theme.flatly(), + ui.panel_title("DuckDB query explorer"), + ui.row( + ui.column( + 2, + ui.row( + button_style, + ui.input_action_button("add_query", "Add Query"), + ), + ui.row( + button_style, + ui.input_action_button("remove_query", "Remove Query"), + ), + ui.br(), + ui.row( + ui.p( + """ + This app lets you explore a dataset using SQL and duckdb. + The data is stored in an on-disk duckdb database, and as a result + the queries fast enough that you don't need a separate button + to execute the queries. + """ + ), + ), + ), + ui.column( + 10, + ui.tags.div(query_output_ui("initial_query"), id="module_container"), + ), + ), +) + + +def server(input, output, session): + mod_counter = reactive.Value(0) + + query_output_server("initial_query", con=con) + + @reactive.Effect + @reactive.event(input.add_query) + def _(): + counter = mod_counter.get() + 1 + mod_counter.set(counter) + id = "query_" + str(counter) + ui.insert_ui( + selector="#module_container", where="afterBegin", ui=query_output_ui(id) + ) + query_output_server(id, con=con) + + @reactive.Effect + @reactive.event(input.remove_query) + def _(): + ui.remove_ui(selector=f"#module_container .row:first-child") + + +app = App(app_ui, server) From b7d1705d9e87db33f84b147aa0babf3af5753074 Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Wed, 12 Jul 2023 09:30:04 -0300 Subject: [PATCH 4/8] Move data reading outside of server function --- examples/annotation-export/app.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/annotation-export/app.py b/examples/annotation-export/app.py index ddc220b2c..5845b7970 100644 --- a/examples/annotation-export/app.py +++ b/examples/annotation-export/app.py @@ -8,6 +8,11 @@ from shiny import App, Inputs, Outputs, Session, reactive, render, ui from shiny.plotutils import brushed_points +path = Path(__file__).parent / "boulder_temp.csv" +weather_df = pd.read_csv(path) +weather_df["date"] = pd.to_datetime(weather_df["date"]) +weather_df["annotation"] = "" + app_ui = ui.page_fluid( ui.panel_title("Plot annotation example"), ui.p( @@ -37,17 +42,11 @@ def server(input: Inputs, output: Outputs, session: Session): @reactive.Calc def ts_data(): - path = Path(__file__).parent / "boulder_temp.csv" - dataframe = pd.read_csv(path) - dataframe["date"] = pd.to_datetime(dataframe["date"]) - dataframe["annotation"] = "" - annotated = annotation_values.get() if annotated is None: - out = dataframe + return weather_df else: - out = annotated - return out + return annotated @reactive.Calc def selected_data(): From 6d2f4b81b3226d5dd716c750d7bbf17bb4ce83f1 Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Wed, 12 Jul 2023 09:33:37 -0300 Subject: [PATCH 5/8] Remove unnecessary copies --- examples/annotation-export/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/annotation-export/app.py b/examples/annotation-export/app.py index 5845b7970..c63cae727 100644 --- a/examples/annotation-export/app.py +++ b/examples/annotation-export/app.py @@ -56,11 +56,11 @@ def selected_data(): @reactive.Effect @reactive.event(input.annotate_button) def _(): - selected = selected_data().copy() + selected = selected_data() selected["annotation_new"] = input.annotation() selected = selected.loc[:, ["date", "annotation_new"]] - df = ts_data().copy() + df = ts_data() df = df.merge(selected, on="date", how="left") df["annotation_new"] = df["annotation_new"].fillna("") @@ -87,7 +87,7 @@ def time_series(): @render.ui def annotator(): if input.time_series_brush() is not None: - selected = selected_data().copy() + selected = selected_data() min = str(selected["date"].min()) max = str(selected["date"].max()) From 028909eddfec7103ec07b46e9c4d494aa49917ae Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Wed, 12 Jul 2023 10:03:53 -0300 Subject: [PATCH 6/8] Add helper text to reduce user confusion. --- examples/annotation-export/app.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/annotation-export/app.py b/examples/annotation-export/app.py index c63cae727..af25b8e4f 100644 --- a/examples/annotation-export/app.py +++ b/examples/annotation-export/app.py @@ -17,7 +17,7 @@ ui.panel_title("Plot annotation example"), ui.p( """ - Select points to annotate them, + Select points to annotate, the plot is drawn with seaborn and all interaction is handled by Shiny. """, {"style": "font-size: larger"}, @@ -30,6 +30,7 @@ ), ui.column( 4, + ui.h3("Annotated points"), ui.output_data_frame("annotations"), ), ui.column(2, ui.download_button("download", "Download CSV")), @@ -104,7 +105,7 @@ def annotator(): ), ui.column( 4, - ui.input_text("annotation", ""), + ui.input_text("annotation", "", placeholder="Enter Annotation"), ), ui.column(4, ui.input_action_button("annotate_button", "Submit")), ) From 282ef7e7e13456d91d9559bbdc32d3fd57ab764e Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Mon, 17 Jul 2023 11:46:29 -0500 Subject: [PATCH 7/8] Formatting fixes --- examples/annotation-export/app.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/annotation-export/app.py b/examples/annotation-export/app.py index af25b8e4f..884da3ba4 100644 --- a/examples/annotation-export/app.py +++ b/examples/annotation-export/app.py @@ -17,8 +17,8 @@ ui.panel_title("Plot annotation example"), ui.p( """ - Select points to annotate, - the plot is drawn with seaborn and all interaction is handled by Shiny. + Select points to annotate them. + The plot is rendered with seaborn and all interaction is handled by Shiny. """, {"style": "font-size: larger"}, ), @@ -101,11 +101,11 @@ def annotator(): {"style": "padding-top: 20px;"}, ui.column( 4, - ui.p(f"{min} to {max}"), + ui.p(f"{min} to", ui.br(), f"{max}"), ), ui.column( 4, - ui.input_text("annotation", "", placeholder="Enter Annotation"), + ui.input_text("annotation", "", placeholder="Enter annotation"), ), ui.column(4, ui.input_action_button("annotate_button", "Submit")), ) From 88c268336970d5917cea265bee1a142413bc2b76 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Mon, 17 Jul 2023 11:59:05 -0500 Subject: [PATCH 8/8] Avoid modifying reactive.Value in place --- examples/annotation-export/app.py | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/examples/annotation-export/app.py b/examples/annotation-export/app.py index 884da3ba4..6f06f4d89 100644 --- a/examples/annotation-export/app.py +++ b/examples/annotation-export/app.py @@ -39,19 +39,11 @@ def server(input: Inputs, output: Outputs, session: Session): - annotation_values = reactive.Value(None) - - @reactive.Calc - def ts_data(): - annotated = annotation_values.get() - if annotated is None: - return weather_df - else: - return annotated + annotated_data = reactive.Value(weather_df) @reactive.Calc def selected_data(): - out = brushed_points(ts_data(), input.time_series_brush(), xvar="date") + out = brushed_points(annotated_data(), input.time_series_brush(), xvar="date") return out @reactive.Effect @@ -61,14 +53,14 @@ def _(): selected["annotation_new"] = input.annotation() selected = selected.loc[:, ["date", "annotation_new"]] - df = ts_data() + df = annotated_data().copy() df = df.merge(selected, on="date", how="left") df["annotation_new"] = df["annotation_new"].fillna("") updated_rows = df["annotation_new"] != "" df.loc[updated_rows, "annotation"] = df.loc[updated_rows, "annotation_new"] df = df.loc[:, ["date", "temp_c", "annotation"]] - annotation_values.set(df) + annotated_data.set(df) @output @render.plot @@ -78,7 +70,7 @@ def time_series(): ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m")) ax.set_title("Temperature readings, Boulder Colorado") out = sns.scatterplot( - data=ts_data(), x="date", y="temp_c", hue="annotation", ax=ax + data=annotated_data(), x="date", y="temp_c", hue="annotation", ax=ax ) out.tick_params(axis="x", rotation=30) @@ -115,14 +107,14 @@ def annotator(): @output @render.data_frame def annotations(): - df = ts_data().copy() + df = annotated_data().copy() df["date"] = df["date"].dt.strftime("%Y-%m-%d") df = df.loc[df["annotation"] != ""] return df @session.download(filename="data.csv") def download(): - yield ts_data().to_csv() + yield annotated_data().to_csv() app = App(app_ui, server)