Skip to content

Commit 55ed0f3

Browse files
committed
Merge branch 'main' into shiny-theme
* main: Rename shiny/examples to shiny/api-examples (and X/examples to X/api-examples) (#627) Make --app-path work with app file argument (#598) Don't eval example code block Fix example code blocks (#626) Annotation export example (#584)
2 parents e635506 + 3d455d5 commit 55ed0f3

File tree

130 files changed

+562
-73
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

130 files changed

+562
-73
lines changed

MANIFEST.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif
88

99
recursive-include shiny/www *
1010
recursive-include shiny/experimental/www *
11-
recursive-include shiny/examples *
11+
recursive-include shiny/api-examples *
1212
recursive-include shiny/ui/dataframe/js/dist *

e2e/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ tests against their apps.)
1010
The actual tests are in subdirectories. Each subdirectory contains one or more Pytest
1111
files (`test_*.py`) containing [Playwright](https://playwright.dev/python/) assertions,
1212
and optionally, a single app (`app.py`) that the assertions test against. (The app is
13-
optional, because the tests may also be for apps in the `../examples` or `../shiny/examples` directory.)
13+
optional, because the tests may also be for apps in the `../examples` or `../shiny/api-examples` directory.)
1414

1515
## Running tests
1616

e2e/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,9 @@ def create_example_fixture(example_name: str, scope: str = "module"):
194194

195195

196196
def create_doc_example_fixture(example_name: str, scope: str = "module"):
197-
"""Used to create app fixtures from apps in py-shiny/shiny/examples"""
197+
"""Used to create app fixtures from apps in py-shiny/shiny/api-examples"""
198198
return create_app_fixture(
199-
here / "../shiny/examples" / example_name / "app.py", scope
199+
here / "../shiny/api-examples" / example_name / "app.py", scope
200200
)
201201

202202

e2e/cpuinfo/test_app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# pyright: reportUnknownMemberType=false
22

3-
# TODO-barret; Convert test into loop that tests all examples to make sure they load
3+
# TODO-karan; Convert test into loop that tests all examples to make sure they load
44

55
import re
66

e2e/examples/test_examples.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def get_apps(path: str) -> typing.List[str]:
2424

2525
example_apps: typing.List[str] = [
2626
*get_apps("../../examples"),
27-
*get_apps("../../shiny/examples"),
27+
*get_apps("../../shiny/api-examples"),
2828
]
2929

3030
app_idle_wait = {"duration": 300, "timeout": 5 * 1000}
@@ -36,6 +36,8 @@ def get_apps(path: str) -> typing.List[str]:
3636
"SafeException": True,
3737
"global_pyplot": True,
3838
"static_plots": ["PlotnineWarning", "RuntimeWarning"],
39+
# https://github.com/rstudio/py-shiny/issues/611#issuecomment-1632866419
40+
"penguins": ["UserWarning", "plt.tight_layout"],
3941
}
4042
app_allow_js_errors: typing.Dict[str, typing.List[str]] = {
4143
"brownian": ["Failed to acquire camera feed:"],

examples/annotation-export/app.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
from pathlib import Path
2+
3+
import matplotlib.dates as mdates
4+
import matplotlib.pyplot as plt
5+
import pandas as pd
6+
import seaborn as sns
7+
8+
from shiny import App, Inputs, Outputs, Session, reactive, render, ui
9+
from shiny.plotutils import brushed_points
10+
11+
path = Path(__file__).parent / "boulder_temp.csv"
12+
weather_df = pd.read_csv(path)
13+
weather_df["date"] = pd.to_datetime(weather_df["date"])
14+
weather_df["annotation"] = ""
15+
16+
app_ui = ui.page_fluid(
17+
ui.panel_title("Plot annotation example"),
18+
ui.p(
19+
"""
20+
Select points to annotate them.
21+
The plot is rendered with seaborn and all interaction is handled by Shiny.
22+
""",
23+
{"style": "font-size: larger"},
24+
),
25+
ui.row(
26+
ui.column(
27+
6,
28+
ui.output_plot("time_series", brush=ui.brush_opts(direction="x")),
29+
ui.output_ui("annotator"),
30+
),
31+
ui.column(
32+
4,
33+
ui.h3("Annotated points"),
34+
ui.output_data_frame("annotations"),
35+
),
36+
ui.column(2, ui.download_button("download", "Download CSV")),
37+
),
38+
)
39+
40+
41+
def server(input: Inputs, output: Outputs, session: Session):
42+
annotated_data = reactive.Value(weather_df)
43+
44+
@reactive.Calc
45+
def selected_data():
46+
out = brushed_points(annotated_data(), input.time_series_brush(), xvar="date")
47+
return out
48+
49+
@reactive.Effect
50+
@reactive.event(input.annotate_button)
51+
def _():
52+
selected = selected_data()
53+
selected["annotation_new"] = input.annotation()
54+
selected = selected.loc[:, ["date", "annotation_new"]]
55+
56+
df = annotated_data().copy()
57+
58+
df = df.merge(selected, on="date", how="left")
59+
df["annotation_new"] = df["annotation_new"].fillna("")
60+
updated_rows = df["annotation_new"] != ""
61+
df.loc[updated_rows, "annotation"] = df.loc[updated_rows, "annotation_new"]
62+
df = df.loc[:, ["date", "temp_c", "annotation"]]
63+
annotated_data.set(df)
64+
65+
@output
66+
@render.plot
67+
def time_series():
68+
fig, ax = plt.subplots()
69+
ax.xaxis.set_major_locator(mdates.MonthLocator())
70+
ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m"))
71+
ax.set_title("Temperature readings, Boulder Colorado")
72+
out = sns.scatterplot(
73+
data=annotated_data(), x="date", y="temp_c", hue="annotation", ax=ax
74+
)
75+
76+
out.tick_params(axis="x", rotation=30)
77+
return out.get_figure()
78+
79+
@output
80+
@render.ui
81+
def annotator():
82+
if input.time_series_brush() is not None:
83+
selected = selected_data()
84+
85+
min = str(selected["date"].min())
86+
max = str(selected["date"].max())
87+
88+
min = min.replace(" 00:00:00+00:00", "")
89+
max = max.replace(" 00:00:00+00:00", "")
90+
91+
out = ui.TagList(
92+
ui.row(
93+
{"style": "padding-top: 20px;"},
94+
ui.column(
95+
4,
96+
ui.p(f"{min} to", ui.br(), f"{max}"),
97+
),
98+
ui.column(
99+
4,
100+
ui.input_text("annotation", "", placeholder="Enter annotation"),
101+
),
102+
ui.column(4, ui.input_action_button("annotate_button", "Submit")),
103+
)
104+
)
105+
return out
106+
107+
@output
108+
@render.data_frame
109+
def annotations():
110+
df = annotated_data().copy()
111+
df["date"] = df["date"].dt.strftime("%Y-%m-%d")
112+
df = df.loc[df["annotation"] != ""]
113+
return df
114+
115+
@session.download(filename="data.csv")
116+
def download():
117+
yield annotated_data().to_csv()
118+
119+
120+
app = App(app_ui, server)

0 commit comments

Comments
 (0)