diff --git a/examples/annotation-export/app.py b/examples/annotation-export/app.py new file mode 100644 index 000000000..6f06f4d89 --- /dev/null +++ b/examples/annotation-export/app.py @@ -0,0 +1,120 @@ +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 + +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( + """ + Select points to annotate them. + The plot is rendered 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.h3("Annotated points"), + ui.output_data_frame("annotations"), + ), + ui.column(2, ui.download_button("download", "Download CSV")), + ), +) + + +def server(input: Inputs, output: Outputs, session: Session): + annotated_data = reactive.Value(weather_df) + + @reactive.Calc + def selected_data(): + out = brushed_points(annotated_data(), input.time_series_brush(), xvar="date") + return out + + @reactive.Effect + @reactive.event(input.annotate_button) + def _(): + selected = selected_data() + selected["annotation_new"] = input.annotation() + selected = selected.loc[:, ["date", "annotation_new"]] + + 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"]] + annotated_data.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=annotated_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() + + 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", ui.br(), f"{max}"), + ), + ui.column( + 4, + ui.input_text("annotation", "", placeholder="Enter annotation"), + ), + ui.column(4, ui.input_action_button("annotate_button", "Submit")), + ) + ) + return out + + @output + @render.data_frame + def annotations(): + 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 annotated_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