diff --git a/docs/apps/000-penguins/app.py b/docs/apps/000-penguins/app.py
new file mode 100644
index 0000000..8fb5317
--- /dev/null
+++ b/docs/apps/000-penguins/app.py
@@ -0,0 +1,57 @@
+from pathlib import Path
+
+import pandas as pd
+import seaborn as sns
+
+from shiny.express import input, render, ui
+from shiny import reactive, req
+
+sns.set_theme()
+
+df = pd.read_csv(Path(__file__).parent / "penguins.csv", na_values="NA")
+numeric_cols = df.select_dtypes(include=["float64"]).columns.tolist()
+species = df["Species"].unique().tolist()
+species.sort()
+
+
+with ui.layout_sidebar():
+ with ui.sidebar():
+ ui.input_selectize(
+ "xvar", "X variable", numeric_cols, selected="Bill Length (mm)"
+ )
+ ui.input_selectize(
+ "yvar", "Y variable", numeric_cols, selected="Bill Depth (mm)"
+ )
+ ui.input_checkbox_group(
+ "species", "Filter by species", species, selected=species
+ )
+ ui.hr()
+ ui.input_switch("by_species", "Show species", value=True)
+ ui.input_switch("show_margins", "Show marginal plots", value=True)
+
+ @render.plot
+ def scatter():
+ """Generates a plot for Shiny to display to the user"""
+
+ # The plotting function to use depends on whether margins are desired
+ plotfunc = sns.jointplot if input.show_margins() else sns.scatterplot
+
+ plotfunc(
+ data=filtered_df(),
+ x=input.xvar(),
+ y=input.yvar(),
+ hue="Species" if input.by_species() else None,
+ hue_order=species,
+ legend=False,
+ )
+
+@reactive.calc
+def filtered_df() -> pd.DataFrame:
+ """Returns a Pandas data frame that includes only the desired rows"""
+
+ # This calculation "requires" that at least one species is selected
+ req(len(input.species()) > 0)
+
+ # Filter the rows so we only include the desired species
+ return df[df["Species"].isin(input.species())]
+
diff --git a/apps/examples/0.0-penguins/app.py b/docs/apps/000-penguins/app_core.py
similarity index 98%
rename from apps/examples/0.0-penguins/app.py
rename to docs/apps/000-penguins/app_core.py
index 2d0459b..e08d9ae 100644
--- a/apps/examples/0.0-penguins/app.py
+++ b/docs/apps/000-penguins/app_core.py
@@ -3,7 +3,6 @@
import pandas as pd
import seaborn as sns
-import shiny.experimental as x
from shiny import App, Inputs, Outputs, Session, reactive, render, req, ui
sns.set_theme()
diff --git a/apps/examples/0.0-penguins/penguins.csv b/docs/apps/000-penguins/penguins.csv
similarity index 100%
rename from apps/examples/0.0-penguins/penguins.csv
rename to docs/apps/000-penguins/penguins.csv
diff --git a/apps/examples/0.0-penguins/requirements.txt b/docs/apps/000-penguins/requirements.txt
similarity index 100%
rename from apps/examples/0.0-penguins/requirements.txt
rename to docs/apps/000-penguins/requirements.txt
diff --git a/docs/shiny-express-slides.qmd b/docs/shiny-express-slides.qmd
index 9073723..9584132 100644
--- a/docs/shiny-express-slides.qmd
+++ b/docs/shiny-express-slides.qmd
@@ -75,7 +75,7 @@ We need to tell the compute what to do when the user clicks.
# | output: asis
include_shiny_folder(
- "../apps/examples/0.0-penguins", components="viewer", viewer_height=700
+ "apps/000-penguins", components="viewer", viewer_height=700
)
```
diff --git a/docs/shiny-express-slides.quarto_ipynb b/docs/shiny-express-slides.quarto_ipynb
new file mode 100644
index 0000000..fe649ba
--- /dev/null
+++ b/docs/shiny-express-slides.quarto_ipynb
@@ -0,0 +1,431 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "title: \"Getting started with Shiny express\"\n",
+ "title-slide-attributes: \n",
+ " data-background-image: images/shiny-for-python-newcastle.jpg\n",
+ " data-background-position: bottom left\n",
+ " data-background-size: cover\n",
+ "format:\n",
+ " positconfslides-revealjs:\n",
+ " incremental: false\n",
+ " chalkboard: true\n",
+ " slide-number: c/t\n",
+ " code-copy: true\n",
+ " center-title-slide: false\n",
+ " code-link: true\n",
+ " highlight-style: a11y\n",
+ " width: \"1600\"\n",
+ " height: \"900\"\n",
+ " css: \"styles.css\"\n",
+ " filters:\n",
+ " - positconfslides\n",
+ " - reveal-auto-agenda\n",
+ " auto-agenda:\n",
+ " heading: Agenda\n",
+ "---"
+ ],
+ "id": "5edf717e"
+ },
+ {
+ "cell_type": "code",
+ "metadata": {},
+ "source": [
+ "# | echo: false\n",
+ "import os\n",
+ "import sys\n",
+ "exercises_path = \"./exercises\"\n",
+ "if exercises_path not in sys.path:\n",
+ " sys.path.append(exercises_path)\n",
+ "\n",
+ "from helpers import include_shiny_folder"
+ ],
+ "id": "4646c70c",
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Getting started\n",
+ "\n",
+ "\n",
+ "## Goals of Session 1\n",
+ "\n",
+ "1. Understand the mechanics of a Shiny app (what to do, not why you do it)\n",
+ "\n",
+ "2. Develop comfort with failure and debugging\n",
+ "\n",
+ "3. You know where to go for help\n",
+ "\n",
+ "## Why Shiny?\n",
+ "\n",
+ "Introduce the end state / whole game / bigger picture: web page with a server. \n",
+ "\n",
+ "The server watches the web page and responds to user events on that page.\n",
+ "\n",
+ "We need to tell the compute what to do when the user clicks.\n",
+ "\n",
+ "\n",
+ "## How this session works\n",
+ "\n",
+ "- This session is going to be exercise based\n",
+ "- All of the exercises and slides are running live on the [website](https://posit-conf-2024.github.io/intro-to-shiny-for-python/)\n",
+ "- You can do them on the website or open the apps in the `apps/core` folder\n",
+ "- If you need help, put a red sticker on your laptop; when you're done, put up a green sticker\n",
+ "\n",
+ "## Your turn\n",
+ "\n",
+ "\n",
+ "{{< yourturn 'express-101-hello-world' >}}\n",
+ "\n",
+ "\n",
+ "\n",
+ "# Hello Shiny\n",
+ "\n",
+ "## What is a Shiny app?\n"
+ ],
+ "id": "81f5c547"
+ },
+ {
+ "cell_type": "code",
+ "metadata": {},
+ "source": [
+ "# | echo: false\n",
+ "# | output: asis\n",
+ "\n",
+ "include_shiny_folder(\n",
+ " \"apps/000-penguins\", components=\"viewer\", viewer_height=700\n",
+ ")"
+ ],
+ "id": "0b96296f",
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Key features of Shiny\n",
+ "\n",
+ "- Easy to develop\n",
+ "- Extensible\n",
+ "- Efficient\n",
+ "\n",
+ "## Why Shiny for Python?\n",
+ "\n",
+ "Shiny is great because it lets you quickly create web apps without worrying about maintaining cache, managing state, and callbacks — or even HTML, CSS, and JavaScript.\n",
+ "\n",
+ "- Shiny is designed to take you from prototype to production\n",
+ "- Easy enough to use for prototypes\n",
+ "- Efficient enough to handle complexity\n",
+ "- Everything you need to build production quality apps\n",
+ "- You don't need to throw away your Shiny apps\n",
+ "\n",
+ "# Anatomy of a Shiny App\n",
+ "\n",
+ "## You need three things\n",
+ "\n",
+ "To make a Shiny app work you need to do three things:\n",
+ "\n",
+ "1. Create a **UI section** that describes inputs and outputs\n",
+ "\n",
+ " - Can contain text, plots, buttons, sliders, etc.\n",
+ "\n",
+ "2. Create a **server section**\n",
+ "\n",
+ " - Contains your application logic\n",
+ " - In particular, it contains **rendering functions** that turn inputs into outputs\n",
+ "\n",
+ "3. Link the server section and UI section\n",
+ "\n",
+ " - by referring to the inputs in the server section\n",
+ "\n",
+ "\n",
+ "## Inputs using shiny express\n",
+ "\n",
+ "```{.python filename=\"app.py\" code-line-numbers=\"3-4\"}\n",
+ "from shiny.express import input, render, ui\n",
+ "\n",
+ "# UI section\n",
+ "ui.input_slider(id = \"n\", label = \"N\", min = 0, max = 100, value = 20)\n",
+ "\n",
+ "## Server section\n",
+ "@render.text\n",
+ "def txt():\n",
+ " return f\"2 * n is {input.n() * 2}\"\n",
+ "```\n",
+ "\n",
+ "
\n",
+ "\n",
+ "\n",
+ "```{shinylive-python}\n",
+ "#| standalone: true\n",
+ "#| viewerHeight: 150\n",
+ "\n",
+ "from shiny.express import input, render, ui\n",
+ "\n",
+ "# UI section\n",
+ "ui.input_slider(\"n\", \"N\", 0, 100, 20)\n",
+ "\n",
+ "## Server section\n",
+ "@render.text\n",
+ "def txt():\n",
+ " return f\"2 * n is {input.n() * 2}\"\n",
+ "```\n",
+ "\n",
+ "## Server section using shiny express\n",
+ "\n",
+ "``` {.python filename=\"app.py\" code-line-numbers=\"6-9\"}\n",
+ "from shiny.express import input, render, ui\n",
+ "\n",
+ "# UI section\n",
+ "ui.input_slider(id = \"n\", label = \"N\", min = 0, max = 100, value = 20)\n",
+ "\n",
+ "## Server section\n",
+ "@render.text\n",
+ "def txt():\n",
+ " return f\"2 * n is {input.n() * 2}\"\n",
+ "```\n",
+ "\n",
+ "
\n",
+ "\n",
+ "```{shinylive-python}\n",
+ "#| standalone: true\n",
+ "#| viewerHeight: 150\n",
+ "\n",
+ "from shiny.express import input, render, ui\n",
+ "\n",
+ "# UI section\n",
+ "ui.input_slider(\"n\", \"N\", 0, 100, 20)\n",
+ "\n",
+ "## Server section\n",
+ "@render.text\n",
+ "def txt():\n",
+ " return f\"2 * n is {input.n() * 2}\"\n",
+ "```\n",
+ "\n",
+ "## Refer to inputs\n",
+ "\n",
+ "Your render function can refer to the input by calling it like a function.\n",
+ "\n",
+ "In this case, the `input_slider` has the name `\"n\"`, so you refer to it as `input.n()`.\n",
+ "\n",
+ "\n",
+ "```{.python filename=\"app.py\" code-line-numbers=\"9\"}\n",
+ "from shiny.express import input, render, ui\n",
+ "\n",
+ "# UI section\n",
+ "ui.input_slider(id = \"n\", label = \"N\", min = 0, max = 100, value = 20)\n",
+ "\n",
+ "## Server section\n",
+ "@render.text\n",
+ "def txt():\n",
+ " return f\"2 * n is {input.n() * 2}\"\n",
+ "```\n",
+ "\n",
+ "
\n",
+ "\n",
+ "```{shinylive-python}\n",
+ "#| standalone: true\n",
+ "#| viewerHeight: 150\n",
+ "\n",
+ "from shiny.express import input, render, ui\n",
+ "\n",
+ "# UI section\n",
+ "ui.input_slider(\"n\", \"N\", 0, 100, 20)\n",
+ "\n",
+ "## Server section\n",
+ "@render.text\n",
+ "def txt():\n",
+ " return f\"2 * n is {input.n() * 2}\"\n",
+ "```\n",
+ "\n",
+ "## UI functions\n",
+ "\n",
+ "- The `ui` submodule contains functions to create UI elements\n",
+ "- Inputs start with `ui.input_*()` and take an `id` and options\n",
+ "- Other functions like `ui.h1()` or `ui.p()` add static HTML to the app\n",
+ "- We'll get to layout functions in Session 3\n",
+ "\n",
+ "\n",
+ "\n",
+ "## Inputs component gallery\n",
+ "\n",
+ "![](images/input-component-gallery.png)\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "## Render functions for outputs\n",
+ "\n",
+ "The render functions define the outputs of the app.\n",
+ "\n",
+ "- Outputs start with `render_*()` and usually just take an id\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "## Outputs component gallery\n",
+ "\n",
+ "![](images/output-component-gallery.png)\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "## Your turn\n",
+ "\n",
+ "\n",
+ "{{< yourturn 'express-102-data-frame' >}}\n",
+ "\n",
+ "\n",
+ "\n",
+ "# Rendering functions on the server side\n",
+ "\n",
+ "## Server function\n",
+ "\n",
+ "- Every Shiny app needs a `server` function\n",
+ "- The server function executes for each user session\n",
+ "- This function contains **rendering functions** which define how to turn inputs into outputs\n",
+ "\n",
+ "## Rendering functions\n",
+ "\n",
+ "``` python\n",
+ "def server(input, output, session):\n",
+ " @output\n",
+ " @render.text\n",
+ " def txt():\n",
+ " return f\"n*2 is {input.n() * 2}\"\n",
+ "```\n",
+ "\n",
+ "- Functions are defined inside the main `server` function\n",
+ "- They need to be **decorated** with `@output` and `@render.*` in that order\n",
+ "- The function **name** should match the output id\n",
+ "- The return value is sent to the Shiny UI\n",
+ "\n",
+ "## Decorators are just functions\n",
+ "\n",
+ "``` python\n",
+ " @output\n",
+ " @render.plot(alt=\"A body mass plot of penguins\")\n",
+ " def mass_plot():\n",
+ " df = sample_data(penguins.copy(), input.sample())\n",
+ " df = df.loc[df[\"body_mass\"] < input.mass()]\n",
+ " return dist_plot(df)\n",
+ "```\n",
+ "\n",
+ "- Decorators are functions which take other functions\n",
+ "- Shiny uses them to identify reactive functions\n",
+ "- They can take arguments\n",
+ "\n",
+ "## Simple app example\n",
+ "\n",
+ "``` {.python code-line-numbers=\"5,10-13\"}\n",
+ "from shiny import Inputs, Outputs, Session, App, render, ui\n",
+ "\n",
+ "app_ui = ui.page_fluid(\n",
+ " ui.input_slider(\"n\", \"N\", 0, 100, 20),\n",
+ " ui.output_text_verbatim(\"txt\"),\n",
+ ")\n",
+ "\n",
+ "def server(input, output, session):\n",
+ " @output\n",
+ " @render.text\n",
+ " def txt():\n",
+ " return f\"n*2 is {input.n() * 2}\"\n",
+ "\n",
+ "app = App(app_ui, server)\n",
+ "```\n",
+ "\n",
+ "## Common problems\n",
+ "\n",
+ "- An output doesn't render\n",
+ "- An output doesn't update when an input changes\n",
+ "- There's some weird error\n",
+ "\n",
+ "## Your turn\n",
+ "\n",
+ "\n",
+ "{{< yourturn 'express-103-debug' >}}\n",
+ "\n",
+ "\n",
+ "\n",
+ "# Making the link\n",
+ "\n",
+ "## Connecting inputs to outputs\n",
+ "\n",
+ "``` {.python code-line-numbers=\"5\"}\n",
+ "def server(input: Inputs, output: Outputs, session: Session):\n",
+ " @output\n",
+ " @render.text\n",
+ " def txt():\n",
+ " return f\"n*2 is {input.n() * 2}\"\n",
+ "```\n",
+ "\n",
+ "- Inputs are read by calling them like a function: `input.n()`, not `input.n`\n",
+ "- Referring to an input creates a reactive link between that input and the rendering function\n",
+ "- When the input changes, the rendering function will re-execute\n",
+ "- You can use multiple inputs in the same rendering function\n",
+ "\n",
+ "## Your turn\n",
+ "\n",
+ "\n",
+ "{{< yourturn 'express-104-filter' >}}\n",
+ "\n",
+ "\n",
+ "\n",
+ "## Your turn\n",
+ "\n",
+ "\n",
+ "{{< yourturn 'express-105-connect-filter' >}}\n",
+ "\n",
+ "\n",
+ "\n",
+ "## Your turn\n",
+ "\n",
+ "\n",
+ "{{< yourturn 'express-106-debug' >}}\n",
+ "\n",
+ "\n",
+ "\n",
+ "## Your turn\n",
+ "\n",
+ "\n",
+ "{{< yourturn 'express-107-debug' >}}\n",
+ "\n",
+ "\n",
+ "\n",
+ "# Recap\n",
+ "\n",
+ "## Summary\n",
+ "\n",
+ "Most Shiny app development consists of variations of these three things:\n",
+ "\n",
+ "1. Add UI elements\n",
+ "2. Add rendering functions\n",
+ "3. Connect inputs and outputs\n",
+ "\n",
+ "## Your turn\n",
+ "\n",
+ "\n",
+ "{{< yourturn 'express-108-plot' >}}"
+ ],
+ "id": "cea64679"
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "name": "python3",
+ "language": "python",
+ "display_name": "Python 3 (ipykernel)",
+ "path": "/home/andrie/github/posit-conf-2024/intro-to-shiny-for-python/.venv/share/jupyter/kernels/python3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
\ No newline at end of file