diff --git a/_quarto.yml b/_quarto.yml index 5a84587e..ae91166f 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -19,6 +19,8 @@ project: - api/core - api/testing - templates + - get-started + - tutorials resources: - /pypi/** - /robots.txt @@ -73,7 +75,9 @@ website: logo-alt: The logo for Shiny for Python search: true left: - - text: "Learn Shiny" + - text: "Get Started" + file: get-started/welcome.qmd + - text: "Concepts" file: docs/overview.qmd - text: "Components" menu: @@ -86,13 +90,6 @@ website: - text: "Templates" file: templates/index.qmd icon: code-square - - text: "Deploy" - menu: - - text: "Overview" - href: docs/deploy.qmd - - docs/deploy-cloud.qmd - - docs/deploy-on-prem.qmd - - docs/shinylive.qmd - text: "Gallery" file: gallery/index.qmd - text: "Playground" @@ -227,7 +224,32 @@ website: - text: "Controlling for Page Size" href: "/layouts/arrange/index.html#controlling-for-page-width-and-height" - - id: docs + - id: get-started + style: "floating" + collapse-level: 1 + align: left + contents: + - text: "πŸ‘‹ Welcome" + href: "/get-started/welcome.qmd" + - text: "πŸ“¦ Installation" + href: "/get-started/install.qmd" + - text: "πŸƒβ€β™‚οΈ Create and Run" + href: "/get-started/create-run.qmd" + - text: "πŸ› Debug" + href: "/get-started/debug.qmd" + - section: "πŸš€ Deploy" + contents: + - text: "Overview" + href: get-started/deploy.qmd + - get-started/deploy-cloud.qmd + - get-started/deploy-on-prem.qmd + - get-started/shinylive.qmd + - section: "Tutorials" + contents: + - text: "Get Started with Shiny Express" + href: tutorials/intro-express/1-welcome.qmd + + - id: concepts style: "floating" collapse-level: 2 align: left @@ -236,10 +258,6 @@ website: contents: - docs/overview.qmd - docs/user-interfaces.qmd - - section: "πŸ’» __Workflow__" - contents: - - docs/install-create-run.qmd - - docs/debug.qmd - section: "🎨 __User interfaces__" contents: - docs/ui-overview.qmd @@ -278,6 +296,21 @@ website: contents: - docs/nonblocking.qmd - docs/routing.qmd + + - id: tutorial-express-intro + style: "docked" + align: left + contents: + - section: "Learn Shiny Express" + contents: + - tutorials/intro-express/1-welcome.qmd + - tutorials/intro-express/2-ui.qmd + - tutorials/intro-express/3-inputs.qmd + - tutorials/intro-express/4-external.qmd + - tutorials/intro-express/5-outputs.qmd + - tutorials/intro-express/6-reactivity.qmd + - tutorials/intro-express/7-publish.qmd + - tutorials/intro-express/8-next.qmd # TODO: if the sidebar only has 1 entry, then it displays for the entire site... # added entry below to prevent this. - id: deploy diff --git a/docs/_metadata.yml b/docs/_metadata.yml index 8eae132d..16b1a700 100644 --- a/docs/_metadata.yml +++ b/docs/_metadata.yml @@ -1 +1 @@ -sidebar: get-started +sidebar: concepts diff --git a/docs/install-create-run.qmd b/docs/install-create-run.qmd deleted file mode 100644 index a5471181..00000000 --- a/docs/install-create-run.qmd +++ /dev/null @@ -1,183 +0,0 @@ ---- -title: Install, create, & run -aliases: - - install.html ---- - -## Install - -`shiny` can be installed via `pip` or `conda`. - -::: {.panel-tabset .panel-pills} - -#### pip - -Before installing you may want to upgrade `pip` and install `wheel`: - -```bash -pip install --upgrade pip wheel -``` - -Next, install `shiny` from PyPI. - -```bash -pip install shiny -``` - -You may on occasion need to force installation of updated versions of our packages, since they are in development. This can be done with: - -```bash -pip install --upgrade shiny htmltools -``` - -::: {.callout-note collapse="true"} -##### Virtual environments - -For production apps, we recommend using a virtual environment to manage your dependencies. In this case, you should install `shiny` in your virtual environment scoped to your app, rather than globally. For example, if you are creating an app in a directory called `myapp`, you would create a virtual environment in that directory and install `shiny` there: - -```bash -mkdir myapp -cd myapp -# Create a virtual environment in the .venv subdirectory -python3 -m venv .venv -# Activate the virtual environment -source .venv/bin/activate -``` -::: - -::: {.callout-note collapse="true"} -##### Development versions - -If you want to install the development versions, you can do so with: - -```bash -pip install https://github.com/posit-dev/py-htmltools/tarball/main -pip install https://github.com/posit-dev/py-shiny/tarball/main -``` -::: - - -#### conda - -If you want to use a conda environment, feel free to create/activate one now: - -```bash -# Create a conda environment named 'myenv' -conda create --name myenv - -# Activate the virtual environment -conda activate myenv -``` - -Next, install `shiny` from conda-forge. - -```bash -conda install -c conda-forge shiny -``` - -You may on occasion need to force installation of updated versions of our packages, since they are in development. This can be done with: - -```bash -conda update -c conda-forge shiny -``` - -::: - -### VS Code - -We recommend installing the [Python][vscode-python] and [Shiny][vscode-shiny] extensions for [Visual Studio Code][vscode]. This provides, among other things, a play button in the top right corner of your editor that will run your Shiny app. - -If [type checking is important](https://john-tucker.medium.com/type-checking-python-306ad8339da1) to you, in addition to installing the [Python VSCode extension][vscode-python], you may want to do some additional configuration for a smooth experience with types in Shiny. See the tip below for more details. - -::: {.callout-tip collapse="true"} -##### Type checking - -We recommend the following settings in your project's `.vscode/settings.json` file: - -```default -{ - "python.analysis.typeCheckingMode": "basic", - "python.analysis.diagnosticSeverityOverrides": { - "reportUnusedFunction": "none" - } -} -``` - -or alternatively, if your project keeps these settings in `pyrightconfig.json`: - -```default -{ - "typeCheckingMode": "basic", - "reportUnusedFunction": "none", -} -``` - -The `basic` type checking mode will flag many potential problems in your code, but it does require an understanding of type hints in Python. This is the mode that is used by the [Shinylive](https://shinylive.io) examples editor. If you want to make even greater use of type checking, you can use `strict` mode: - -```default - "python.analysis.typeCheckingMode": "strict" -``` - -If you still find that too obtrusive and aren't used to working with type hints, you can remove that line entirely. - -In the above configuration, we also disable the `reportUnusedFunction` diagnostic, as it's idiomatic Shiny to create named functions that are never explicitly called by any code (i.e., `@reactive.effect`). - -You can also modify these settings on a per-file basis with comments at the top of the file. For example, you might have something like this at the top of your `app.py`: - -```default -# pyright: strict -# pyright: reportUnusedFunction=false -``` - -A full list of configuration settings for Pyright/Pylance is available [here](https://github.com/microsoft/pyright/blob/main/docs/configuration.md). - -[vscode]: https://code.visualstudio.com/ -[vscode-shiny]: https://marketplace.visualstudio.com/items?itemName=posit.shiny -[vscode-python]: https://marketplace.visualstudio.com/items?itemName=ms-python.python -::: - - -## Create - -The best way to create a new Shiny app is with the `shiny create` command line interface (CLI). This command asks you a series of questions about what kind of app you want to create, and then provides all the boilerplate code you need to get started with a working app. - -```bash -shiny create -``` - -![Running the shiny create command from a terminal](assets/shiny-create.mp4){class="img-shadow"} - -::: callout-tip -### Copy/paste examples - -If you find an example on this site that you want to run/edit locally, you can use `shiny create --template basic-app -m express` to get a basic app template, and then copy/paste the code from the example into the template. -::: - - -## Run - -Shiny apps can be launched from VSCode or the command line (via `shiny run`). - -### VS Code - -The best way to run (and develop) Shiny apps is in [Visual Studio Code][vscode] with the [Shiny extension][vscode-shiny]. When a Shiny `app.py` file is being edited, the default behavior of the Run button (circled in red in the screenshot below) becomes "Run Shiny App". - -![Visual Studio Code running with the Shiny extension](assets/vscode.png) - -This launches a Python process in a dedicated terminal instance, and a captive web browser. This lets you test your app without leaving your editor, and whenever you make changes to your app's source, the preview will update. To preview your app in a full browser, click the icon to the right of the URL bar to launch the app in an external browser. - -Next to the Run button is a dropdown menu that lets you "Debug Shiny App". This launches the app in debug mode, which lets you set breakpoints and step through your code. See the [debugging](debug.qmd) section for more information. - -### Command line - -To run a Shiny app from the command line, use the `shiny run` command. This command takes a single argument, the path to the app's entry point. For example, if your app's entry point is `app.py` in the directory `./app_dir`, you can run it like this: - -```bash -shiny run --reload --launch-browser app_dir/app.py -``` - -This should start your app and also automatically launch a web browser. - -The `--reload` flag means that file changes in the current directory tree will cause the Python process to restart and the browser to reload. Update and save changes to `app.py` and then wait a moment for the changes to appear in the browser. - -With these two `shiny` commands, you now have a project that you can run in your terminal. You can use any text editor or Python IDE to write Shiny apps, but we've taken special care to ensure a smooth workflow for [Visual Studio Code][vscode]. The next section will help you set up VS Code for Shiny for Python. diff --git a/get-started/_metadata.yml b/get-started/_metadata.yml new file mode 100644 index 00000000..8eae132d --- /dev/null +++ b/get-started/_metadata.yml @@ -0,0 +1 @@ +sidebar: get-started diff --git a/get-started/assets/dashboard-template.png b/get-started/assets/dashboard-template.png new file mode 100644 index 00000000..e71e79c7 Binary files /dev/null and b/get-started/assets/dashboard-template.png differ diff --git a/get-started/assets/positron-run.png b/get-started/assets/positron-run.png new file mode 100644 index 00000000..51adbdbc Binary files /dev/null and b/get-started/assets/positron-run.png differ diff --git a/docs/shinylive-share-button.png b/get-started/assets/shinylive-share-button.png similarity index 100% rename from docs/shinylive-share-button.png rename to get-started/assets/shinylive-share-button.png diff --git a/docs/shinylive-share-modal.png b/get-started/assets/shinylive-share-modal.png similarity index 100% rename from docs/shinylive-share-modal.png rename to get-started/assets/shinylive-share-modal.png diff --git a/docs/shinylive-shiny-deployment-model.png b/get-started/assets/shinylive-shiny-deployment-model.png similarity index 100% rename from docs/shinylive-shiny-deployment-model.png rename to get-started/assets/shinylive-shiny-deployment-model.png diff --git a/docs/shinylive-shinylive-deployment-model.png b/get-started/assets/shinylive-shinylive-deployment-model.png similarity index 100% rename from docs/shinylive-shinylive-deployment-model.png rename to get-started/assets/shinylive-shinylive-deployment-model.png diff --git a/get-started/assets/tipping-dashboard.png b/get-started/assets/tipping-dashboard.png new file mode 100644 index 00000000..6981ae25 Binary files /dev/null and b/get-started/assets/tipping-dashboard.png differ diff --git a/get-started/assets/vscode.png b/get-started/assets/vscode.png new file mode 100644 index 00000000..e07eaa97 Binary files /dev/null and b/get-started/assets/vscode.png differ diff --git a/get-started/create-run.qmd b/get-started/create-run.qmd new file mode 100644 index 00000000..c1b9b62a --- /dev/null +++ b/get-started/create-run.qmd @@ -0,0 +1,80 @@ +--- +title: "Create and Run" +--- + +When you [install shiny](get-started/install.qmd), you will have access to the `shiny` command line interface (CLI). +You can use this interface to help you create and run your Shiny applications. + +## Create a Shiny application + +The best way to create a new Shiny app is with the `shiny create` command line interface. +This command asks you a series of questions about what kind of app you want to create, +and then provides all the boilerplate code you need to get started with a working app. + +```bash +shiny create +``` + +![Running the shiny create command from a terminal](/docs/assets/shiny-create.mp4){class="img-shadow"} + +::: callout-tip +### Copy/paste examples + +If you find an example on this site that you want to run or edit locally, +you can use the following command to get a basic app template, +then copy and paste the code from the example into the template. + +```bash +shiny create --template basic-app -m express +``` +::: + + +## Run your Shiny application + +Shiny apps can be launched from Positron, VS Code, or the command line via `shiny run`. + +::: {.callout-tip} +#### Name your app `app.py` + +We recommend naming your shiny application `app.py`. +This is the default file that `shiny run` will look for, so you can run the application in the terminal without any additional parameters. + +If you need a more unique name, we recommend beginning the file with `app`, so the shiny extension can still run with a [play button click](install.qmd#positron). +::: + +### Positron and VS Code + +The best way to run (and develop) Shiny apps is in [Positron][positron] or [Visual Studio Code][vscode] with the [Shiny extension][vscode-shiny]. +When a Shiny `app.py` file is being edited, the default behavior of the Run button (circled in red in the screenshot below) becomes "Run Shiny App". + +![Visual Studio Code running with the Shiny extension](assets/positron-run.png) + +This launches a Python process in a dedicated terminal instance, and a captive web browser. +This lets you test your app without leaving your editor, and whenever you make changes to your app's source, the preview will update. To preview your app in a full browser, click the icon to the right of the URL bar to launch the app in an external browser. + +Next to the Run button is a dropdown menu that lets you "Debug Shiny App". This launches the app in debug mode, which lets you set breakpoints and step through your code. +See the [debugging](debug.qmd) section for more information. + +### Command line + +To run a Shiny app from the command line, use the `shiny run` command. +This command takes a single argument, the path to the app's entry point. +For example, if your app's entry point is `app.py` in the directory `./app_dir`, you can run it like this: + +```bash +shiny run --reload --launch-browser app_dir/app.py +``` + +This should start your app and also automatically launch a web browser. + +The `--reload` flag means that file changes in the current directory tree will cause the Python process to restart and the browser to reload. +Update and save changes to `app.py` and then wait a moment for the changes to appear in the browser. + +With these two `shiny` commands, you now have a project that you can run in your terminal. +You can use any text editor or Python IDE to write Shiny apps, but we've taken special care to ensure a smooth workflow for [Positron][positron] and [Visual Studio Code][vscode]. + +[positron]: https://positron.posit.co/ +[vscode]: https://code.visualstudio.com/ +[vscode-shiny]: https://marketplace.visualstudio.com/items?itemName=posit.shiny +[vscode-python]: https://marketplace.visualstudio.com/items?itemName=ms-python.python diff --git a/docs/debug.qmd b/get-started/debug.qmd similarity index 99% rename from docs/debug.qmd rename to get-started/debug.qmd index f78276ef..1e412638 100644 --- a/docs/debug.qmd +++ b/get-started/debug.qmd @@ -57,11 +57,9 @@ The error displayed in the app is only the final part of the stack trace, but th When Shiny apps are deployed, error messages are sanitized to the eliminate the possibility of leaking sensitive information. To unsanitize error messages, you'll need to set `sanitize_errors=True` in the `App` constructor (of a [Shiny core app](express-vs-core.qmd)). ::: - - ## Debugging -### VS Code debugger +### Positron and VS Code debugger The [VS Code debugger](https://code.visualstudio.com/docs/editor/debugging) is a powerful tool for debugging Python code. diff --git a/docs/deploy-cloud.qmd b/get-started/deploy-cloud.qmd similarity index 100% rename from docs/deploy-cloud.qmd rename to get-started/deploy-cloud.qmd diff --git a/docs/deploy-on-prem.qmd b/get-started/deploy-on-prem.qmd similarity index 100% rename from docs/deploy-on-prem.qmd rename to get-started/deploy-on-prem.qmd diff --git a/docs/deploy.qmd b/get-started/deploy.qmd similarity index 100% rename from docs/deploy.qmd rename to get-started/deploy.qmd diff --git a/get-started/install.qmd b/get-started/install.qmd new file mode 100644 index 00000000..5580a32f --- /dev/null +++ b/get-started/install.qmd @@ -0,0 +1,165 @@ +--- +title: Installation +--- + +Shiny for Python can be installed can be installed via `pip` or `conda`. + +::: {.panel-tabset .panel-pills} + +#### pip + +Before installing you may want to upgrade `pip` and install `wheel`: + +```bash +pip install --upgrade pip wheel +``` + + +Next, install `shiny` from PyPI. + +```bash +pip install shiny +``` + + +You may on occasion need to force installation of updated versions of our packages, since they are in development. +This can be done with: + +```bash +pip install --upgrade shiny htmltools +``` + +::: {.callout-note collapse="true"} +##### Virtual environments + +For production apps, we recommend using a virtual environment to manage your dependencies. +In this case, you should install `shiny` in your virtual environment scoped to your app, rather than globally. +For example, if you are creating an app in a directory called `myapp`, you would create a virtual environment in that directory and install `shiny` there: + +```bash +mkdir myapp +cd myapp + +# Create a virtual environment in the .venv subdirectory +python3 -m venv .venv + +# Activate the virtual environment +source .venv/bin/activate +``` +::: + +::: {.callout-note collapse="true"} +##### Development versions + +If you want to install the development versions, you can do so with: + + +```bash +pip install https://github.com/posit-dev/py-htmltools/tarball/main +pip install https://github.com/posit-dev/py-shiny/tarball/main +``` +::: + + +#### conda + +You can install `shiny` from conda-forge channel. + +```bash +conda install -c conda-forge shiny +``` + +You may on occasion need to force installation of updated versions of our packages, since they are in development. +This can be done with: + +```bash +conda update -c conda-forge shiny +``` + +::: {.callout-note collapse="true"} +##### Conda Virtual environments + +For production apps, we recommend using a virtual environment to manage your dependencies. +Create a conda environment with the code below before installing the `shiny` package. + +```bash +# Create a conda environment named 'shiny' +conda create --name shiny + +# Activate the virtual environment +conda activate shiny +``` +::: + +::: + +## Positron {#positron} + +We recommend using +[Positron](https://positron.posit.co/), +a next-generation data science focused fork of Visual Studio Code. + +If you already have VS Code installed, we still recommend giving Positron a try, +your extensions from VS Code will not conflict with your Positron extensions. +Positron already ships with many Python-focused VS Code extensions from Open VSX, +including Quarto, Jupyter Notebooks, and Pyright. + +To get started with Shiny for Python, you will need to install the +[Shiny Extension](https://open-vsx.org/extension/posit/shiny). +This provides, among other things, a play button in the top right corner of your editor that will run your Shiny app. + +![](assets/positron-run.png) + +## Visual Studio Code + +For for [Visual Studio Code][vscode], +you will need the [Python][vscode-python] and [Shiny][vscode-shiny] extensions. + +If [type checking is important](https://john-tucker.medium.com/type-checking-python-306ad8339da1) to you, in addition to installing the [Python VSCode extension][vscode-python], you may want to do some additional configuration for a smooth experience with types in Shiny. See the tip below for more details. + +::: {.callout-tip collapse="true"} +##### Type checking + +We recommend the following settings in your project's `.vscode/settings.json` file: + +```default +{ + "python.analysis.typeCheckingMode": "basic", + "python.analysis.diagnosticSeverityOverrides": { + "reportUnusedFunction": "none" + } +} +``` + +or alternatively, if your project keeps these settings in `pyrightconfig.json`: + +```json +{ + "typeCheckingMode": "basic", + "reportUnusedFunction": "none", +} +``` + +The `basic` type checking mode will flag many potential problems in your code, but it does require an understanding of type hints in Python. This is the mode that is used by the [Shinylive](https://shinylive.io) examples editor. If you want to make even greater use of type checking, you can use `strict` mode: + +```json + "python.analysis.typeCheckingMode": "strict" +``` + +If you still find that too obtrusive and aren't used to working with type hints, you can remove that line entirely. + +In the above configuration, we also disable the `reportUnusedFunction` diagnostic, as it's idiomatic Shiny to create named functions that are never explicitly called by any code (i.e., `@reactive.effect`). + +You can also modify these settings on a per-file basis with comments at the top of the file. For example, you might have something like this at the top of your `app.py`: + +```python +# pyright: strict +# pyright: reportUnusedFunction=false +``` + +A full list of configuration settings for Pyright/Pylance is available [here](https://github.com/microsoft/pyright/blob/main/docs/configuration.md). +::: + +[vscode]: https://code.visualstudio.com/ +[vscode-shiny]: https://marketplace.visualstudio.com/items?itemName=posit.shiny +[vscode-python]: https://marketplace.visualstudio.com/items?itemName=ms-python.python diff --git a/docs/shinylive.qmd b/get-started/shinylive.qmd similarity index 98% rename from docs/shinylive.qmd rename to get-started/shinylive.qmd index 2a291e6c..34cccbf7 100644 --- a/docs/shinylive.qmd +++ b/get-started/shinylive.qmd @@ -8,12 +8,12 @@ Shinylive allows you to run Shiny applications entirely in a web browser, withou The traditional way of deploying Shiny involves in a separate server and client: the server runs Python and Shiny, and clients connect via the web browser. Each client keeps an open websocket connection as long as they are using the application. -![](shinylive-shiny-deployment-model.png){fig-alt="Traditional Shiny deployment" width=500px} +![](assets/shinylive-shiny-deployment-model.png){fig-alt="Traditional Shiny deployment" width=500px} When an application is deployed with Shinylive, Python and Shiny _run in the web browser_: the browser is effectively both the client and server for the application. There is a web server that serves files, but it does not run Python or Shiny---it can be a "dumb" static web server. -![](shinylive-shinylive-deployment-model.png){fig-alt="Shinylive deployment" width=500px} +![](assets/shinylive-shinylive-deployment-model.png){fig-alt="Shinylive deployment" width=500px} If you've looked at any of the documentation on this web site, or have played with any of the [examples at shinylive.io](https://shinylive.io/py/examples/), you have already used Shinylive. The examples on this site (with a handful of exceptions) and the shinylive.io examples all run using Shinylive, meaning that they run in your web browser. @@ -57,11 +57,11 @@ When we talk about _deploying_ applications, we mean creating a set of files whi The easiest way to share an application is to create it on the [Shinylive editor](https://shinylive.io/py/examples/), and then click on the "Create share link" button. This will encode the application in a URL, which you can then share with others. -![](shinylive-share-button.png){fig-alt="Share button" width=250px} +![](assets/shinylive-share-button.png){fig-alt="Share button" width=250px} The dialog box that appears will provide two links: one for the application in the Shinylive editor, and one with for the application running standalone. -![](shinylive-share-modal.png){fig-alt="Share URLs" width=700px} +![](assets/shinylive-share-modal.png){fig-alt="Share URLs" width=700px} Here is an example of a Shiny application that is encoded in a share URL. This will lead to the application with an editor and Python console: diff --git a/get-started/welcome.qmd b/get-started/welcome.qmd new file mode 100644 index 00000000..f3298c0c --- /dev/null +++ b/get-started/welcome.qmd @@ -0,0 +1,108 @@ +--- +title: "Welcome to Shiny" +editor: + markdown: + wrap: sentence +--- + +Shiny for Python is a powerful and beginner-friendly framework for building interactive web applications and dashboards, all in pure Python. +Whether you’re a data scientist, analyst, or developer, Shiny makes it easy to create rich, interactive experiences without needing to learn JavaScript or front-end frameworks. + +## Batteries included: everything you need to build a dashboard {#batteries-included} + +Shiny for Python comes fully equipped with everything you need to build a dashboard right out of the box. +It includes a rich set of [input and output components](/components) so you can quickly build interactive applications without worrying about external dependencies. +Layout options let you organize your UI efficiently, while built-in theming (including dark mode) ensures your app looks great with minimal effort. +You can even use a [brand.yml](https://posit-dev.github.io/brand-yml/) file to apply consistent branding, colors, and logos across your application. + +## Reactivity: the secret to seamless interactivity {#reactivity} + +At the heart of Shiny is [reactivity](../docs/reactive-foundations.qmd), a system that automatically updates outputs when inputs change. +Unlike traditional web apps, where you need to manually handle state and data updates with callbacks, Shiny’s reactive engine keeps everything in sync effortlessly. +For example, when a user interacts with a slider or selects a filter, reactivity figures out the **minimum** amount of calculations to update the outputs without requiring you to write complex event-handling code. +This makes Shiny ideal for fast data-driven applications, enabling live updates for charts, tables, and reports with minimal effort, +and gives you the confidence that results are accurately rendered. + +```{shinylive-python} +#| standalone: true +#| components: [editor, viewer] +#| layout: vertical +#| viewerHeight: 250 + +from shiny import reactive +from shiny.express import ui, render, input + +# input slider, n +ui.input_slider("n", "Number", min=1, max=50, value=25) + +# output text that uses the input, n +@render.text +def result(): + return f"You selected: {input.n()}. Its value squared is {input.n()**2}." + +``` + +## Templates: get started quickly {#templates} + +To help you hit the ground running, Shiny provides starter [templates](/templates/) for common use cases, such as data dashboards, applications, streaming updates, data entry. + +The `shiny create` command walks you through a series of prompts to help you get started quickly with a helpful example. +You can learn more about the `shiny create` command and how to run your Shiny applications in the +[create and run](create-run.qmd) section. + +You can use this terminal command to load up a basic dashboard: + +```bash +shiny create --template dashboard +``` + +::::: {.column-screen-inset} +::: {.hello-output} + + + +::: +::::: + + +## Extensibility: customize and expand as needed {#extensible} + +While Shiny includes everything you need to build an app, is built on a foundation of web standards, making it highly extensible. +If you need a custom component or user interface, you can incrementally integrate JavaScript, HTML, or even WebAssembly without having to learn complicated build tooling and frameworks. + +The UI components themselves are built on a Python representation of HTML/CSS/JavaScript, which you can see by printing them in a Python REPL: + +```python +>>> from shiny import ui +>>> ui.input_action_button("btn", "Button") + +``` + +If you're versed in web programming, you can also use Shiny to create [custom components](/docs/custom-component-one-off.qmd) that leverage your favorite JavaScript framework from Python. + +## Why choose Shiny for Python? + +Why Choose Shiny for Python? + +- 🐍 No JavaScript required – Build full-featured interactive apps in pure Python. +- ⚑ Fast iteration – The reactive model allows quick and dynamic updates. +- 🎨 Built-in theming and layout – Make beautiful dashboards effortlessly. +- πŸš€ Scalability and extensibility – Start simple, grow as needed. + +Shiny for Python empowers you to bring your data to life with interactive applications that are easy to build, customize, and share. + +Ready to give it a try? diff --git a/index.qmd b/index.qmd index c5552f59..b4e9dffc 100644 --- a/index.qmd +++ b/index.qmd @@ -26,7 +26,7 @@ repo-actions: false ::: {.pt-4 .pb-4 style="z-index:1;"} [Install](#install){.btn .btn-dark .btn-lg .m-2 .px-5 role="button"} -[Learn Shiny](docs/overview.qmd){.btn .btn-dark .btn-lg .m-2 .px-5 role="button"} +[Get Started](get-started/welcome.qmd){.btn .btn-dark .btn-lg .m-2 .px-5 role="button"} ::: ::: diff --git a/tutorials/index.qmd b/tutorials/index.qmd new file mode 100644 index 00000000..b2fa1e9c --- /dev/null +++ b/tutorials/index.qmd @@ -0,0 +1,124 @@ +--- +title: "Tutorials" +--- + +```{=html} + +``` + +## Express-Only Tutorials + +Shiny Express is the easiest and quickest way to start with Shiny. +Create applications at the speed of thought. + +```{=html} +
+ +

Getting Started with Shiny

+

Install and build your first Shiny Application.

+

Updated: Feb 14, 2024

+ Start Tutorial +
+
+``` + +## Express and Core Tutorials + +Here are the more general tutorials to get you started with all things Shiny. + +```{=html} +
+ +

Shiny Express to Shiny Core

+

Learn how to transition from Shiny Express to Shiny Core.

+

Updated: Feb 5, 2024

+ Start Tutorial +
+ +

Shiny Modules

+

Create Shiny Modules to make more maintanable applications.

+

Updated: Mar 15, 2024

+ Start Tutorial +
+ +

Make your own component

+

Create your own custom javasscript components.

+

Updated: Apr 20, 2024

+ Start Tutorial +
+ +

Testing your application

+

Write unit tests and end-to-end tests with pytest and playwright.

+

Updated: Apr 20, 2024

+ Start Tutorial +
+
+``` + +## Core-Only Tutorials + +Shiny Core provides you all the flexibility and complexity you need. +The syntax is a bit more verbose, +but you can create more complex applications using the core syntax. + +```{=html} +
+ +

Getting Started with Shiny

+

Install and build your first Shiny Application with the Core syntax.

+

Updated: Feb 14, 2024

+ Start Tutorial +
+
+``` diff --git a/tutorials/intro-express/1-welcome.qmd b/tutorials/intro-express/1-welcome.qmd new file mode 100644 index 00000000..1e8f9fa5 --- /dev/null +++ b/tutorials/intro-express/1-welcome.qmd @@ -0,0 +1,152 @@ +--- +title: Getting Started +--- + +Shiny for Python is a web application framework that helps tell your data story. +If you've landed on this page, you probably have a bit of Python experience, worked with data, and now need a way to publish an interactive web application to help tell your data story. + +## Installation and Setup + +This is a 1 to 2 Hour tutorial to get you started and familiar with all the basic parts of creating and deploying a Shiny for Python application. +Before starting this tutorial, check to make sure you have your packages and environment setup. +You can see the [Installation section of the Get Started Guides](/get-started/install-create-run.qmd). + +## Parts of a Shiny Application + +Shiny express allows us to write shiny apps with a minimal amount of code. +This lets us rapidly link interactive components with our data in our web application. + +There are 3 main parts of a shiny express application + +1. [input components](/components/#inputs): provide user interactions that can be used as inputs in other parts of the web application. +2. [output components](/components/#outputs): results that are displayed on the web application. +3. [layout and ui components](/layouts): how and where the inputs and output of the web application are displayed. + +The example below demonstrates the basic mechanics behind Shiny apps. +As you move the slider (an input component), the text (output component) will react and update to the corresponding input value. + +```{shinylive-python} +#| standalone: true +#| components: [editor, viewer] +#| layout: vertical +#| viewerHeight: 150 +from shiny.express import input, render, ui + +ui.input_slider(id="val", label="Slider label", min=0, max=100, value=50) + +@render.text +def slider_val(): + return f"Slider value: {input.val()}" +``` + +Let's briefly break down the components of the application above: + +- Inputs + - Create a slider with a `ui.input_*` function. + - Here we are using the `ui.input_slider()` function to create the slider. + - This slider has an id of `"val"` which is used to get the slider value later +- Outputs + - Created by decorating a function with the corresponding `@render.*` decorator. + - Here we are displaying text, so we are using the `@render.text` decorator. + - Inside a `render` function, `input` values can be read [reactively](#reactivity). + - We read the value from the slider by calling `input.val()`. + - When those `input` values change, Shiny knows how to minimally re-render output. +- Layouts + - Inferred automatically based on what items you place in your application. + - We will learn more about layouts and user interfaces in the next lesson of this tutorial. + +:::: callout-note +## Exercise + +Let's make and run our first shiny for python application. + +1. Take the above code and save it to a file. Here we named it `app.py` +2. Click on the play button (red circle in the image below)j + +You will see the terminal run the `shiny run` command for you automatically. +The output will look something like this + +``` bash +$ python -m shiny run --port 55901 --reload --autoreload-port 55902 app-010-simple.py +INFO: Will watch for changes in these directories: ['~/Desktop/py-shiny-example'] +INFO: Uvicorn running on http://127.0.0.1:55901 (Press CTRL+C to quit) +INFO: Started reloader process [24969] using WatchFiles +INFO: Started server process [24986] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: 127.0.0.1:56426 - "GET /?vscodeBrowserReqId=1737097751843 HTTP/1.1" 200 OK +``` + +This will run the application on port `55901` and automatically reload and update as you make changes to the `app.py` file. + +1. You will see the app build in the bottom terminal and open in the viewer on the side +2. Move the slider and see how the output reacts +3. Congratulations, you made your first shiny for python application! + +::: column-margin +![](img/010-run_app-cropped_editor.png.png) +::: +:::: + +::: callout-tip +## Naming your files + +If you start your file with the word `app`, the shiny for python extension will recognize it as an application and you will be able to see the "play" button to run your application. +You can also name your file `app-get_started.py` and you will still get the shiny extension play button. + +To have Shiny for Python work well with the VS Code extensions and for you to go through the next series of lessons. +We recommend either one of the following file naming conventions: + +1. Create separate folders for each app example you will create and save separate `app.py` files in each folder +2. Create separate `app*.py` files in the same directory (e.g., `app-01.py`, `app-02.py`) + +If you named your application `app.py` you can omit it form the command and only use `shiny run --reload`. +The `app.py` is the default file Shiny looks for to run in the current directory. +Otherwise, you can pass in the name of the file that you wish to run. +The `app` prefix used in the example above is used to signal to the Shiny VS Code extension to display the run app button. +::: + +## Run your shiny application + +In addition to the play button in Positron, you can manually run your application from the command line. +This is useful if you wish to specify your own port or want to rename your application without the `app` prefix. + +``` bash +shiny run my_app.py --reload +``` + +::: callout-tip +## Helpful run options + +Some useful options you can pass the `shiny run` command are: + +- `--reload`: Enables auto-reload, the application will reload to reflect your changes as you save your work. +- `--port`: pass in a custom port, e.g., `--port 8000`. This will run the app on the specified port, instead of a random port. This makes it easier to have the same browser window open as you stop and start your application. + +You can learn more about these run options on the [`run_app` documentation page](https://shiny.posit.co/py/api/core/run_app.html). +::: + +## Shiny Express: Your first application + +The rest of this tutorial will work on creating this [Restaurant Tipping Dashboard](https://gallery.shinyapps.io/template-dashboard-tips1/). + +:::: {.column-screen .hero-image .pt-4 .pb-5 style="margin-top:0px;max-width:1600px;"} +::: {.hello-output .g-col-12 .g-col-xl-12} + + +```{=html} + +``` +::: +:::: diff --git a/tutorials/intro-express/2-ui.qmd b/tutorials/intro-express/2-ui.qmd new file mode 100644 index 00000000..e0230819 --- /dev/null +++ b/tutorials/intro-express/2-ui.qmd @@ -0,0 +1,232 @@ +--- +title: User Interfaces and Layouts +--- + + + +In the previous lesson, +we saw how to create and run a basic shiny for python application. + +Now let's see how we can layout different user interfaces. + +If you are trying to whip up a quick application that needs input controls +and reactive outputs, shiny express tries to make this as simple as possible +by inferring the user interface and page layout for you. +However, +you have the option (and ability) to override these default layouts. + +We will assume that you have the pre-existing knowledge on how to +load a dataframe from a csv file, filter the data, calculate summaries from the data, +and plot results. +Here we will set the foundation on how to set up the UI that our code will +insert into to be displayed in a web application or dashboard. + +:::{.callout-tip} +When building your Shiny applications, +a general good practice is break up the application into two (2) separate steps + +1. Write the code for whatever interactive components you want + and use variables as place holders for the code. + It isn't interactive without code modifications yet, + but it'll help to make sure you have all the code working as you add interactive + components to it. +2. Outline the general user interface either literally on paper and/or put in + placeholder UI elements to get a sense of the look and feel of your application. + +You can do these steps in whatever asynchronously and in whatever order you'd like. +By keeping these steps separate, +especially as you are learning the framework, +you'll reduce the risks of creating errors and bugs that may be hard to point down. +::: + +## Layout User Interfaces + +We can lay out the input and output components on our web application using different +[shiny layouts](/layouts/). +We can have different navigation bars, sidebars, tabs, panels, and cards to control where each +component is displayed on the page. + +:::{.callout-note} +We will talk about the different input and output components separately in +later lessons of this tutorial. + +All the individual input components begin with a `input_*()` function. +You can find a list of all the inputs in the +[input components gallery](/components/#inputs). + +Outputs are created by decorating a function with the `@render.*()` decorator. +You can find a list of all the outputs in the +[outputs components gallery](/components/#outputs). +::: + + +Layouts in Shiny Express begin with the `with ui.*():` python context manager. +Here is an example of an Shiny Express application with a sidebar on the left. +One use case for this kind of layout is to provide the user the ability to interact +with components on the page +but also hide away the components to declutter +the application when they are not needed. + +```{shinylive-python} +#| standalone: true +#| components: [editor, viewer] +#| layout: vertical +#| viewerHeight: 150 +from shiny.express import ui + +with ui.sidebar(bg="#f8f8f8"): + "Sidebar" + +"Main content" +``` + +You can use navigation bars (navbars) to add different pages to your application. +Let's build on our current sidebar layout, +and add a navigation bar to the top of the application. +We can nest layouts by nesting the context managers. + +:::{.callout-note} +Context managers are not specific to Shiny for Python. +They are features and tools used thought the Python ecosystem. +Typically you will not have to write your own context manager in Python +and use the `with` statement for an existing context manager. + +For Shiny, all you need to remember that Shiny Express uses context managers +to layout each part of the user interface. +You can also nest shiny layout context managers, +but be mindful where the `with` statement is and where the indentations are. +::: + +## Page Layouts + +If you need to embed different page layouts, you will need to look for the `ui.layout_*()` functions. + +:::{.column-body-outset-right} +```{shinylive-python} +#| standalone: true +#| components: [editor, viewer] +#| layout: vertical +#| viewerHeight: 200 +from shiny.express import ui + +with ui.nav_panel("A"): + with ui.layout_sidebar(): + with ui.sidebar(id="sidebar_left", open="desktop"): + "Left sidebar content" + "Main content" + +with ui.nav_panel("B"): + "Page 2 content" + +with ui.nav_panel("C"): + "Page C content" +``` +::: + +## Restaurant Tips UI + +For a given page, there are a few ways you can +[layout specific elements](layouts/panels-cards/#content-divided-by-cards). + +Let's sketch out the basic outline of the shiny application. + +![](../../docs/assets/tipping-dashboard.png) + +:::{.callout-note} +## Exercise + +Let's replicate the restaurant tipping dashboard, +but only put in the UI elements. + +The Restaurant Tipping dashboard has the following parts: + +1. Title: "Restaurant tipping" + - You can use the [`ui.page_opts()`](https://shiny.posit.co/py/api/express/express.ui.page_opts.html) + and pass in a `title=''` parameter to add an application title. +1. Sidebar for a few input components (we'll add those later) + - You can put some text here as a place holder, e.g., `"sidebar inputs"` +2. A full width column with 3 value boxes + - Each value box will take up the same width of space + - The value boxes will have labels for "Total tippers", "Average tip", and "Average bill" + - The value boxes will need a placeholder value (we will populate them with a reactive value later) +3. A full width column with 2 cards, one for a dataframe and another for a scatter plot + - Each card will share the same width of space + - The care headers will have values of "Tips data" and "Total bill vs tip" +4. a full width column with 1 card + - The card has a header of "Tip percentages" + + +::::::{.callout-tip} +Here are the documentation pages for functions that may be useful for this exercise: + +- `ui.page_opts()`: +- `ui.sidebar()`: +- `ui.layout_columns()`: +- `ui.card()`: +- `ui.card_header()`: +:::::: +::: + +::: {.callout-caution collapse="true"} +## Solution + +```{shinylive-python} +#| standalone: true +#| components: [editor, viewer] +#| layout: vertical +#| viewerHeight: 150 +from shiny.express import input, ui + +# title +ui.page_opts(title="Restaurant tipping", fillable=True) + +# sidebar (empty for now) +with ui.sidebar(open="desktop"): + "sidebar inputs" + +# body of application + +# first row of value boxes +with ui.layout_columns(fill=False): + with ui.value_box(): + "Total tippers" + 42 + + with ui.value_box(): + "Average tip" + 42 + + with ui.value_box(): + "Average bill" + 42 + +# second row of cards +with ui.layout_columns(col_widths=[6, 6]): + with ui.card(full_screen=True): + ui.card_header("Tips data") + with ui.card(full_screen=True): + ui.card_header("Total bill vs tip") + +with ui.layout_columns(): + with ui.card(full_screen=True): + ui.card_header("Tip percentages") + +``` +::: + + +## Context managers + +You will typically not need to write your own context managers using the Python `with` statement. +If you would like to learn more about what context managers are, +and how to potentially write your own, you can check out +this +[context managers tutorial from Real Python](https://realpython.com/python-with-statement/) + +## Summary + +We have now created a skeleton for our dashboard by laying out the main UI components. +We will now be able to add input components. diff --git a/tutorials/intro-express/3-inputs.qmd b/tutorials/intro-express/3-inputs.qmd new file mode 100644 index 00000000..0b422a19 --- /dev/null +++ b/tutorials/intro-express/3-inputs.qmd @@ -0,0 +1,209 @@ +--- +title: Input Components +--- + + + +So far we've seen how to customize our user interface, +and saw how we can use layouts, cards, and the 12-Grid CSS Bootstrap layout +to help place different elements on our web application. +Now let's get a sense of all the different kinds of input components we can work with. +You can see a list of all the possible input components in the +[components gallery](/components/). + +In general, all the input components are imported with `from shiny.express import ui`, +and we can access each of the input components from the corresponding input +component function, `ui.input_*()`. +We typically pass in the input `id` as the first parameter, +and the `label` as the second parameter. +The `id` is a unique name for **each** input component that we can use +to look up its (reactive) value. +The `label` is the text that is displayed along with the input component, +it is usually the name or really short description for what the input component controls. +The rest of the arguments will differ for each component, +such as what values to be displayed for button choices, +or starting and ending range for a slider. +Each input component also has their own parameters for customizations specific for that particular input. + +:::{.callout-tip} +The [components gallery](/components/) is a great way to quickly see all the possible components +that come with Shiny for Python. +Each component page has their own mini example tutorial how to use the corresponding component. +It's useful to have the components page open to the side as you are +planning and building your application. +::: + +Let's combine a few of our layout knowledge from the previous lesson, +and add some input components to a shiny application. + +Here we have a fillable page with 2 columns, each containing a card +with a different UI component in it. + +```{shinylive-python} +#| standalone: true +#| components: [editor, viewer] +#| layout: vertical +#| viewerHeight: 300 +from shiny.express import ui + +ui.page_opts(fillable=True) + +with ui.layout_columns(): + with ui.card(): + ui.card_header("Card 1 header") + ui.p("Card 1 body") + ui.input_slider("slider", "Slider", 0, 10, 5) + + with ui.card(): + ui.card_header("Card 2 header") + ui.p("Card 2 body") + ui.input_text("text", "Add text", "") +``` + + + +:::{.callout-note .column-page-right} +## Exercise + +Now that you have a bit more practice with UIs and Input components, +Let's build add a few inputs to our existing tips dashboard. + +![](../../docs/assets/tipping-dashboard.png) + +Our application only has inputs in the left sidebar. +Let's add them to the application (we will work on connecting them with data later) + +1. [`input_slider()`](https://shiny.posit.co/py/components/inputs/slider-range/): + We'll use `0` and `100` as the lower and upper bounds for now. + When we load our data we can calculate actual data range. +2. [`input_checkbox_group()`](https://shiny.posit.co/py/components/inputs/checkbox-group/): + With a label of `Food service` and options for `Lunch` and `Dinner`. +3. [`input_action_button()`](https://shiny.posit.co/py/components/inputs/action-button/): + Labeled `Reset filter`. + +We will connect these inputs to outputs in the next lesson. + +For reference, here's our current code and application: + +```{shinylive-python} +#| standalone: true +#| components: [editor, viewer] +#| layout: vertical +#| viewerHeight: 500 +from shiny.express import input, ui + +# title +ui.page_opts(title="Restaurant tipping", fillable=True) + +# sidebar (empty for now) +with ui.sidebar(open="desktop"): + "sidebar inputs" + +# body of application + +# first row of value boxes +with ui.layout_columns(fill=False): + with ui.value_box(): + "Total tippers" + "Value 1" + + with ui.value_box(): + "Average tip" + "Value 2" + + with ui.value_box(): + "Average bill" + "Value 3" + +# second row of cards +with ui.layout_columns(col_widths=[6, 6]): + with ui.card(full_screen=True): + ui.card_header("Tips data") + "Tips DataFrame" + + with ui.card(full_screen=True): + ui.card_header("Total bill vs tip") + "Scatterplot" + +with ui.layout_columns(): + with ui.card(full_screen=True): + ui.card_header("Tip percentages") + "ridgeplot" + +``` + +::: + +::: {.callout-caution collapse="true" .column-page-right} + +## Solution + +```{shinylive-python} +#| standalone: true +#| components: [editor, viewer] +#| layout: vertical +#| viewerHeight: 500 +from shiny.express import input, ui + +# title +ui.page_opts(title="Restaurant tipping", fillable=True) + +# sidebar +with ui.sidebar(open="desktop"): + ui.input_slider( + id="slider", + label="Bill amount", + min=0, + max=100, + value=[0, 100], + ) + ui.input_checkbox_group( + id="checkbox_group", + label="Food service", + choices={ + "Lunch": "Lunch", + "Dinner": "Dinner", + }, + selected=[ + "Lunch", + "Dinner", + ], + ) + ui.input_action_button("action_button", "Reset filter") + + +# body of application + +# first row of value boxes +with ui.layout_columns(fill=False): + with ui.value_box(): + "Total tippers" + "Value 1" + + with ui.value_box(): + "Average tip" + "Value 2" + + with ui.value_box(): + "Average bill" + "Value 3" + +# second row of cards +with ui.layout_columns(col_widths=[6, 6]): + with ui.card(full_screen=True): + ui.card_header("Tips data") + "Tips DataFrame" + + with ui.card(full_screen=True): + ui.card_header("Total bill vs tip") + "Scatterplot" + +with ui.layout_columns(): + with ui.card(full_screen=True): + ui.card_header("Tip percentages") + "ridgeplot" + +``` +::: diff --git a/tutorials/intro-express/4-external.qmd b/tutorials/intro-express/4-external.qmd new file mode 100644 index 00000000..05b902ff --- /dev/null +++ b/tutorials/intro-express/4-external.qmd @@ -0,0 +1,196 @@ +--- +title: External Resources +--- + +It's good to keep the UI and code logic separate. +Since we have an outline of the application UI, +let's make sure we can get all the logic working for the code. + +Let's take a look at the application we're planning to make and make sure we +can code up all the individual parts first. + +![](../../docs/assets/tipping-dashboard.png) + +From the dashboard sketch we need to make the following outputs + +1. Load the tips data that can be filtered byt the bill amount and food service time +2. Display the tips data after filtering +3. Calculate the total number of tippers (i.e., number of rows after filtering) +4. Calculate average tip percentage after filtering +5. Calculate average bill after filtering +6. [Plotly scatterplot](https://plotly.com/python/line-and-scatter/) + comparing `tip` vs `total_bill` of the filtered data +7. [ridgeplot](https://ridgeplot.readthedocs.io/en/stable/) + comparing days of the week vs tip percentages of the filtered data + +Now that we can lay out components and have the output components react to the input components, +let's see how we can incorporate modules, packages, and external data into our application. + +Before we start, make sure you have pandas, plotly, and ridgeplot installed. +If you are following along this tutorial from the beginning, +make sure you are in the proper virtual environment + +::: {.panel-tabset} + +## pip + +```bash +pip install pandas plotly ridgeplot +``` + +## conda + +```bash +conda install -c conda-forge pandas plotly +pip install ridgeplot +``` + +## mamba + +```bash +mamba install install -c conda-forge pandas plotly +pip install ridgeplot +``` + +::: + + +## External Data + +External data can be read into a Shiny for Python just like any other +python data science project, e.g., pandas, polars, ibis, eager, duckdb, etc. + +:::{.callout-note} +You can use the Python `narwhals` library to convert between +different dataframe backends. + + +::: + +For example, if we wanted to read in data from the `tips.csv` file in pandas, +we can use the same code in our shiny for python application. + +```{python} +#| include: false + +import pandas as pd + +try: + tips = pd.read_csv("tips.csv") +except FileNotFoundError: + tips = pd.read_csv("tutorials/intro-express/tips.csv") +``` + +```python +import pandas as pd + +tips = pd.read_csv("tips.csv") +``` + +Next, let's create a few variables to serve as placeholders for the input components: + +```{python} +total_lower = tips.total_bill.min() +total_upper = tips.total_bill.max() +time_selected = tips.time.unique().tolist() +``` + +And a placeholder for the filtered tips dataframe: + +```{python} +idx1 = tips.total_bill.between( + left=total_lower, + right=total_upper, + inclusive="both", +) + +idx2 = tips.time.isin(time_selected) + +tips_filtered = tips[idx1 & idx2] +``` + +Now that we have a placeholder for the filtered dataframe, +we can write the code for the other components of the application. + +```{python} +tips_filtered.head() +``` + +## Individual values + +Now, let's calculate the individual numbers that are showed in the value boxes. + +```{python} +# total tippers +total_tippers = tips_filtered.shape[0] +total_tippers +``` + +```{python} +# average tip +perc = tips_filtered.tip / tips_filtered.total_bill +average_tip = f"{perc.mean():.1%}" +average_tip +``` + +```{python} +# average bill +bill = tips_filtered.total_bill.mean() +average_bill = f"${bill:.2f}" +average_bill +``` + +## Plots + +We now need to create 2 figures, a scatterplot and a ridgeplot. + +The scatterplot will use the `plotly` library + +```{python} +import plotly.express as px + +px.scatter( + tips_filtered, + x="total_bill", + y="tip", + trendline="lowess" +) +``` + +The ridgeplot will use the `ridgeplot` library + +```{python} +from ridgeplot import ridgeplot + +tips_filtered["percent"] = tips_filtered.tip / tips_filtered.total_bill + +uvals = tips_filtered.day.unique() +samples = [[tips_filtered.percent[tips_filtered.day == val]] for val in uvals] + +plt = ridgeplot( + samples=samples, + labels=uvals, + bandwidth=0.01, + colorscale="viridis", + colormode="row-index" +) + +plt.update_layout( + legend=dict( + orientation="h", + yanchor="bottom", + y=1.02, + xanchor="center", + x=0.5 + ) +) + +plt +``` + +## Next steps + +Now we have the working code for all the parts of our application. +Next we will add these outputs to the application +and then link the input components to our placeholder variables +to filtered the data. diff --git a/tutorials/intro-express/4-external.quarto_ipynb b/tutorials/intro-express/4-external.quarto_ipynb new file mode 100644 index 00000000..96ef7a3f --- /dev/null +++ b/tutorials/intro-express/4-external.quarto_ipynb @@ -0,0 +1,314 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "title: External Resources\n", + "---\n", + "\n", + "\n", + "It's good to keep the UI and code logic separate.\n", + "Since we have an outline of the application UI,\n", + "let's make sure we can get all the logic working for the code.\n", + "\n", + "Let's take a look at the application we're planning to make and make sure we\n", + "can code up all the individual parts first.\n", + "\n", + "![](../../docs/assets/tipping-dashboard.png)\n", + "\n", + "From the dashboard sketch we need to make the following outputs\n", + "\n", + "1. Load the tips data that can be filtered byt the bill amount and food service time\n", + "2. Display the tips data after filtering\n", + "3. Calculate the total number of tippers (i.e., number of rows after filtering)\n", + "4. Calculate average tip percentage after filtering\n", + "5. Calculate average bill after filtering\n", + "6. [Plotly scatterplot](https://plotly.com/python/line-and-scatter/)\n", + " comparing `tip` vs `total_bill` of the filtered data\n", + "7. [ridgeplot](https://ridgeplot.readthedocs.io/en/stable/)\n", + " comparing days of the week vs tip percentages of the filtered data\n", + "\n", + "Now that we can lay out components and have the output components react to the input components,\n", + "let's see how we can incorporate modules, packages, and external data into our application.\n", + "\n", + "Before we start, make sure you have pandas, plotly, and ridgeplot installed.\n", + "If you are following along this tutorial from the beginning,\n", + "make sure you are in the proper virtual environment\n", + "\n", + "::: {.panel-tabset}\n", + "\n", + "## pip\n", + "\n", + "```bash\n", + "pip install pandas plotly ridgeplot\n", + "```\n", + "\n", + "## conda\n", + "\n", + "```bash\n", + "conda install -c conda-forge pandas plotly\n", + "pip install ridgeplot\n", + "```\n", + "\n", + "## mamba\n", + "\n", + "```bash\n", + "mamba install install -c conda-forge pandas plotly\n", + "pip install ridgeplot\n", + "```\n", + "\n", + ":::\n", + "\n", + "\n", + "## External Data\n", + "\n", + "External data can be read into a Shiny for Python just like any other\n", + "python data science project, e.g., pandas, polars, ibis, eager, duckdb, etc.\n", + "\n", + ":::{.callout-note}\n", + "You can use the Python `narwhals` library to convert between\n", + "different dataframe backends.\n", + "\n", + "\n", + ":::\n", + "\n", + "For example, if we wanted to read in data from the `tips.csv` file in pandas,\n", + "we can use the same code in our shiny for python application.\n" + ], + "id": "7f9cb307" + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "#| include: false\n", + "\n", + "import pandas as pd\n", + "\n", + "try:\n", + " tips = pd.read_csv(\"tips.csv\")\n", + "except FileNotFoundError:\n", + " tips = pd.read_csv(\"tutorials/intro-express/tips.csv\")" + ], + "id": "4e4d9bd6", + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "import pandas as pd\n", + "\n", + "tips = pd.read_csv(\"tips.csv\")\n", + "```\n", + "\n", + "Next, let's create a few variables to serve as placeholders for the input components:\n" + ], + "id": "3c84c3b8" + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "total_lower = tips.total_bill.min()\n", + "total_upper = tips.total_bill.max()\n", + "time_selected = tips.time.unique().tolist()" + ], + "id": "69757743", + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And a placeholder for the filtered tips dataframe:\n" + ], + "id": "ad67bf6d" + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "idx1 = tips.total_bill.between(\n", + " left=total_lower,\n", + " right=total_upper,\n", + " inclusive=\"both\",\n", + ")\n", + "\n", + "idx2 = tips.time.isin(time_selected)\n", + "\n", + "tips_filtered = tips[idx1 & idx2]" + ], + "id": "97d5101d", + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that we have a placeholder for the filtered dataframe,\n", + "we can write the code for the other components of the application.\n" + ], + "id": "725aac84" + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "tips_filtered.head()" + ], + "id": "76e30ca9", + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Individual values\n", + "\n", + "Now, let's calculate the individual numbers that are showed in the value boxes.\n" + ], + "id": "7b502085" + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# total tippers\n", + "total_tippers = tips_filtered.shape[0]\n", + "total_tippers" + ], + "id": "a25ff9fd", + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# average tip\n", + "perc = tips_filtered.tip / tips_filtered.total_bill\n", + "average_tip = f\"{perc.mean():.1%}\"\n", + "average_tip" + ], + "id": "74b10c7b", + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# average bill\n", + "bill = tips_filtered.total_bill.mean()\n", + "average_bill = f\"${bill:.2f}\"\n", + "average_bill" + ], + "id": "da8bdf28", + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plots\n", + "\n", + "We now need to create 2 figures, a scatterplot and a ridgeplot.\n", + "\n", + "The scatterplot will use the `plotly` library\n" + ], + "id": "05e2fc36" + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "import plotly.express as px\n", + "\n", + "px.scatter(\n", + " tips_filtered,\n", + " x=\"total_bill\",\n", + " y=\"tip\",\n", + " trendline=\"lowess\"\n", + ")" + ], + "id": "5f52cf0d", + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The ridgeplot will use the `ridgeplot` library\n" + ], + "id": "39a291a9" + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from ridgeplot import ridgeplot\n", + "\n", + "tips_filtered[\"percent\"] = tips_filtered.tip / tips_filtered.total_bill\n", + "\n", + "uvals = tips_filtered.day.unique()\n", + "samples = [[tips_filtered.percent[tips_filtered.day == val]] for val in uvals]\n", + "\n", + "plt = ridgeplot(\n", + " samples=samples,\n", + " labels=uvals,\n", + " bandwidth=0.01,\n", + " colorscale=\"viridis\",\n", + " colormode=\"row-index\"\n", + ")\n", + "\n", + "plt.update_layout(\n", + " legend=dict(\n", + " orientation=\"h\",\n", + " yanchor=\"bottom\",\n", + " y=1.02,\n", + " xanchor=\"center\",\n", + " x=0.5\n", + " )\n", + ")\n", + "\n", + "plt" + ], + "id": "7c1e3637", + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "Now we have the working code for all the parts of our application.\n", + "Next we will add these outputs to the application\n", + "and then link the input components to our placeholder variables\n", + "to filtered the data." + ], + "id": "0b68a24b" + } + ], + "metadata": { + "kernelspec": { + "name": "pfe_book", + "language": "python", + "display_name": "pfe_book", + "path": "/Users/danielchen/Library/Jupyter/kernels/pfe_book" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/tutorials/intro-express/5-outputs.qmd b/tutorials/intro-express/5-outputs.qmd new file mode 100644 index 00000000..d368ec3f --- /dev/null +++ b/tutorials/intro-express/5-outputs.qmd @@ -0,0 +1,793 @@ +--- +title: Output Components +--- + + + +Now that we know how to lay our the application and insert input components for the user to interact with, +let's create some output components that **react** to the input components. + +Output components all begin with a function with a `ui.render_*` decorator above a function definition. +The decorator is one of the ways that you signal to Shiny that the code will react to some change +in the application. +The name of the function does not matter to shiny, but you should pick a name +that hints at what value is going to be returned. +Finally, the body of the function should return the corresponding object for the output component. +Again, the decorator function signals to Shiny what kind of output component is displayed in the application. + +In Shiny Express, wherever the output function is defined +(i.e., where we are using the `ui.render_*` decorator), +is where the output will be displayed. +So it's perfectly normal to see function definitions throughout the application. + +:::{.callout-important} +In Shiny Express, +the name of the function is used as the output ID. +Each function that is decorated with a `@rander.*` decorator should have a unique name. +::: + + +The `@render.text` output is one way you can help debug your application visually. +Similar to `print()` statement debugging, +except the print statement will be rendered in your application. + +```{shinylive-python} +#| standalone: true +#| components: [editor, viewer] +#| layout: vertical +#| viewerHeight: 150 +from shiny.express import input, render, ui + +ui.input_slider("val", "Slider label", min=0, max=100, value=50) + +@render.text +def slider_val(): + return f"Slider value: {input.val()}" +``` + +:::{.callout-note} +Since we are now using output components, you will need to also +import the `render` module from `shiny.express` + + +```python +from shiny.express import input, render, ui +``` +::: + + +## Use input values + +In the application above, we had this particular line in our function body, `input.var()`. +This line shows one of the main features in Shiny, reactive values. + +- The `input` variable automatically holds all the values from the input components as a Python dictionary +- We can access the input component value with dot notation and + use the same `id` we defined in the `ui.input_*()` function +- The `input.var` represents the actual reactive value object, + if we want to actually calculate the current reactive value, + we need to call it as a function with `input.var()` + + + +:::{.callout-tip .column-page-right} +## Exercise + + +Let's add `@render.text` outputs to the sidebar so we can confirm what the code will see from the input components. +We will work with the slider and checkbox components. + +1. Our current application should already have `input_slider()` and `input_checkbox_group()` components +2. Define separate output functions in the sidebar under the reset button, + one for the `input_sider()` values, and another for the `input_checkbox_group()` values. + - Use the `input.()` pattern to have shiny reactively get the input component values + - Return the value you want to use in the application (returning the input values directly is fine) +3. Decorate the functions with `@render.text` to signal that we want the returned value rendered as text in the application +4. Add 2 additional text outputs, one for the lower bound of the input slider, + and another for the upper bound of the input slider + +:::{.callout-tip} +The `ui.input_slider()` component returns a list of values +where the first (`0` index) is the lower slider value, +and the second (`1` index) is the upper slider value. +::: + +Here is our current application for reference: + +```{shinylive-python} +#| standalone: true +#| components: [editor, viewer] +#| layout: vertical +#| viewerHeight: 500 +from shiny.express import input, ui + +# title +ui.page_opts(title="Restaurant tipping", fillable=True) + +# sidebar +with ui.sidebar(open="desktop"): + ui.input_slider( + id="slider", + label="Bill amount", + min=0, + max=100, + value=[0, 100], + ) + ui.input_checkbox_group( + id="checkbox_group", + label="Food service", + choices={ + "Lunch": "Lunch", + "Dinner": "Dinner", + }, + selected=[ + "Lunch", + "Dinner", + ], + ) + ui.input_action_button("action_button", "Reset filter") + + + +# body of application + +# first row of value boxes +with ui.layout_columns(fill=False): + with ui.value_box(): + "Total tippers" + "Value 1" + + with ui.value_box(): + "Average tip" + "Value 2" + + with ui.value_box(): + "Average bill" + "Value 3" + +# second row of cards +with ui.layout_columns(col_widths=[6, 6]): + with ui.card(full_screen=True): + ui.card_header("Tips data") + "Tips DataFrame" + + with ui.card(full_screen=True): + ui.card_header("Total bill vs tip") + "Scatterplot" + +with ui.layout_columns(): + with ui.card(full_screen=True): + ui.card_header("Tip percentages") + "ridgeplot" + +``` +::: + + +::: {.callout-caution collapse="true" .column-page-right} + +## Solution + +```{shinylive-python} +#| standalone: true +#| components: [editor, viewer] +#| layout: vertical +#| viewerHeight: 500 +from shiny.express import input, ui, render + +# title +ui.page_opts(title="Restaurant tipping", fillable=True) + +# sidebar +with ui.sidebar(open="desktop"): + ui.input_slider( + id="slider", + label="Bill amount", + min=0, + max=100, + value=[0, 100], + ) + ui.input_checkbox_group( + id="checkbox_group", + label="Food service", + choices={ + "Lunch": "Lunch", + "Dinner": "Dinner", + }, + selected=[ + "Lunch", + "Dinner", + ], + ) + ui.input_action_button("action_button", "Reset filter") + + + @render.text + def slider_val(): + return f"Slider values: {input.slider()}" + + @render.text + def checkbox_group_val(): + return f"Checkbox values: {input.checkbox_group()}" + + @render.text + def slider_val_0(): + return f"Slider value (lower): {input.slider()[0]}" + + @render.text + def slider_val_1(): + return f"Slider value (upper): {input.slider()[1]}" + + +# body of application + +# first row of value boxes +with ui.layout_columns(fill=False): + with ui.value_box(): + "Total tippers" + "Value 1" + + with ui.value_box(): + "Average tip" + "Value 2" + + with ui.value_box(): + "Average bill" + "Value 3" + +# second row of cards +with ui.layout_columns(col_widths=[6, 6]): + with ui.card(full_screen=True): + ui.card_header("Tips data") + "Tips DataFrame" + + with ui.card(full_screen=True): + ui.card_header("Total bill vs tip") + "Scatterplot" + +with ui.layout_columns(): + with ui.card(full_screen=True): + ui.card_header("Tip percentages") + "ridgeplot" + +``` +::: + +In the previous lesson, +we created variables that served as place holders we can use to filter our `tips` data + +```python +total_lower = tips.total_bill.min() +total_upper = tips.total_bill.max() +time_selected = tips.time.unique().tolist() +``` + +Instead of these hardcoded values, +we can use the values from the `input_*()` components instead. + +:::{.callout-tip} +## Exercise + +We will learn more about reactivity and reactive calculations in the next lesson. +But in this exercise, +we will use the input component values to filter our tips dataframe for each +output component. + +For reference, below we have the code for each of the outputs we created earlier. +Wrap each output into a function and decorate it with the corresponding +output component decorator. + +- dataframe: [`render.data_frame`](https://shiny.posit.co/py/api/express/express.render.data_frame.html) +- value box text: [`render.express`](https://shiny.posit.co/py/api/express/express.render.express.html) +- scatterplot (plotly): [`render_plotly`](https://shiny.posit.co/py/docs/jupyter-widgets.html) +- ridgeplot (plotly): [`render_plotly`](https://shiny.posit.co/py/docs/jupyter-widgets.html) + +:::::: {.panel-tabset} + +## dataframe + +```python +tips = pd.read_csv("tips.csv") + +total_lower = tips.total_bill.min() +total_upper = tips.total_bill.max() +time_selected = tips.time.unique().tolist() + +idx1 = tips.total_bill.between( + left=total_lower, + right=total_upper, + inclusive="both", +) + +idx2 = tips.time.isin(time_selected) + +tips_filtered = tips[idx1 & idx2] +``` + +## valuebox + +```python +# total tippers +total_tippers = tips_filtered.shape[0] + +# average tip +perc = tips_filtered.tip / tips_filtered.total_bill +average_tip = f"{perc.mean():.1%}" + +# average bill +bill = tips_filtered.total_bill.mean() +average_bill = f"${bill:.2f}" +``` + +## scatterplot + +```python +# scatterplot +import plotly.express as px + +px.scatter( + tips_filtered, + x="total_bill", + y="tip", + trendline="lowess" +) +``` + +## ridgeplot + +```python +# ridgeplot +from ridgeplot import ridgeplot + +tips_filtered["percent"] = tips_filtered.tip / tips_filtered.total_bill + +uvals = tips_filtered.day.unique() +samples = [[tips_filtered.percent[tips_filtered.day == val]] for val in uvals] + +plt = ridgeplot( + samples=samples, + labels=uvals, + bandwidth=0.01, + colorscale="viridis", + colormode="row-index" +) + +plt.update_layout( + legend=dict( + orientation="h", + yanchor="bottom", + y=1.02, + xanchor="center", + x=0.5 + ) +) +) +``` +:::::: + +::: + +::: {.callout-caution collapse="true" .column-page-right} +## Solution + +```{shinylive-python} +#| standalone: true +#| components: [editor, viewer] +#| layout: vertical +#| viewerHeight: 600 + +from pathlib import Path # only used for shinylive render + +import pandas as pd +import plotly.express as px +from ridgeplot import ridgeplot +from shiny.express import input, render, ui +from shinywidgets import render_plotly + +# only used for shinylive render +file = Path(__file__).parent / "tips.csv" + +# you may need to change the path to the tips.csv file +tips = pd.read_csv(file) + +# title +ui.page_opts(title="Restaurant tipping", fillable=True) + +# sidebar +with ui.sidebar(open="desktop"): + ui.input_slider( + id="slider", + label="Bill amount", + min=tips.total_bill.min(), + max=tips.total_bill.max(), + value=[tips.total_bill.min(), tips.total_bill.max()]) + ui.input_checkbox_group( + id="checkbox_group", + label="Food service", + choices={ + "Lunch": "Lunch", + "Dinner": "Dinner", + }, + selected=[ + "Lunch", + "Dinner", + ], + ) + ui.input_action_button("action_button", "Reset filter") + + +# body of application + +# first row of value boxes +with ui.layout_columns(fill=False): + with ui.value_box(): + "Total tippers" + @render.express + def total_tippers(): + idx1 = tips.total_bill.between( + left=input.slider()[0], + right=input.slider()[1], + inclusive="both", + ) + idx2 = tips.time.isin(input.checkbox_group()) + tips_filtered = tips[idx1 & idx2] + + tips_filtered.shape[0] + + with ui.value_box(): + "Average tip" + @render.express + def average_tip(): + idx1 = tips.total_bill.between( + left=input.slider()[0], + right=input.slider()[1], + inclusive="both", + ) + idx2 = tips.time.isin(input.checkbox_group()) + tips_filtered = tips[idx1 & idx2] + + perc = tips_filtered.tip / tips_filtered.total_bill + f"{perc.mean():.1%}" + + with ui.value_box(): + "Average bill" + @render.express + def average_bill(): + idx1 = tips.total_bill.between( + left=input.slider()[0], + right=input.slider()[1], + inclusive="both", + ) + idx2 = tips.time.isin(input.checkbox_group()) + tips_filtered = tips[idx1 & idx2] + + bill = tips_filtered.total_bill.mean() + f"${bill:.2f}" + +# second row of cards +with ui.layout_columns(col_widths=[6, 6]): + with ui.card(full_screen=True): + ui.card_header("Tips data") + @render.data_frame + def table(): + idx1 = tips.total_bill.between( + left=input.slider()[0], + right=input.slider()[1], + inclusive="both", + ) + idx2 = tips.time.isin(input.checkbox_group()) + tips_filtered = tips[idx1 & idx2] + + return render.DataGrid(tips_filtered) + + with ui.card(full_screen=True): + ui.card_header("Total bill vs tip") + @render_plotly + def scatterplot(): + idx1 = tips.total_bill.between( + left=input.slider()[0], + right=input.slider()[1], + inclusive="both", + ) + idx2 = tips.time.isin(input.checkbox_group()) + tips_filtered = tips[idx1 & idx2] + + return px.scatter( + tips_filtered, + x="total_bill", + y="tip", + trendline="lowess", + ) + +with ui.layout_columns(): + with ui.card(full_screen=True): + ui.card_header("Tip percentages") + @render_plotly + def tip_perc(): + idx1 = tips.total_bill.between( + left=input.slider()[0], + right=input.slider()[1], + inclusive="both", + ) + idx2 = tips.time.isin(input.checkbox_group()) + tips_filtered = tips[idx1 & idx2] + + tips_filtered["percent"] = tips_filtered.tip / tips_filtered.total_bill + + uvals = tips_filtered.day.unique() + samples = [[tips_filtered.percent[tips_filtered.day == val]] for val in uvals] + + plt = ridgeplot( + samples=samples, + labels=uvals, + bandwidth=0.01, + colorscale="viridis", + colormode="row-index" + ) + + plt.update_layout( + legend=dict( + orientation="h", + yanchor="bottom", + y=1.02, + xanchor="center", + x=0.5 + ) + ) + return plt + +## file: requirements.txt +ridgeplot==0.1.25 + +## file: tips.csv +total_bill,tip,sex,smoker,day,time,size +16.99,1.01,Female,No,Sun,Dinner,2 +10.34,1.66,Male,No,Sun,Dinner,3 +21.01,3.5,Male,No,Sun,Dinner,3 +23.68,3.31,Male,No,Sun,Dinner,2 +24.59,3.61,Female,No,Sun,Dinner,4 +25.29,4.71,Male,No,Sun,Dinner,4 +8.77,2.0,Male,No,Sun,Dinner,2 +26.88,3.12,Male,No,Sun,Dinner,4 +15.04,1.96,Male,No,Sun,Dinner,2 +14.78,3.23,Male,No,Sun,Dinner,2 +10.27,1.71,Male,No,Sun,Dinner,2 +35.26,5.0,Female,No,Sun,Dinner,4 +15.42,1.57,Male,No,Sun,Dinner,2 +18.43,3.0,Male,No,Sun,Dinner,4 +14.83,3.02,Female,No,Sun,Dinner,2 +21.58,3.92,Male,No,Sun,Dinner,2 +10.33,1.67,Female,No,Sun,Dinner,3 +16.29,3.71,Male,No,Sun,Dinner,3 +16.97,3.5,Female,No,Sun,Dinner,3 +20.65,3.35,Male,No,Sat,Dinner,3 +17.92,4.08,Male,No,Sat,Dinner,2 +20.29,2.75,Female,No,Sat,Dinner,2 +15.77,2.23,Female,No,Sat,Dinner,2 +39.42,7.58,Male,No,Sat,Dinner,4 +19.82,3.18,Male,No,Sat,Dinner,2 +17.81,2.34,Male,No,Sat,Dinner,4 +13.37,2.0,Male,No,Sat,Dinner,2 +12.69,2.0,Male,No,Sat,Dinner,2 +21.7,4.3,Male,No,Sat,Dinner,2 +19.65,3.0,Female,No,Sat,Dinner,2 +9.55,1.45,Male,No,Sat,Dinner,2 +18.35,2.5,Male,No,Sat,Dinner,4 +15.06,3.0,Female,No,Sat,Dinner,2 +20.69,2.45,Female,No,Sat,Dinner,4 +17.78,3.27,Male,No,Sat,Dinner,2 +24.06,3.6,Male,No,Sat,Dinner,3 +16.31,2.0,Male,No,Sat,Dinner,3 +16.93,3.07,Female,No,Sat,Dinner,3 +18.69,2.31,Male,No,Sat,Dinner,3 +31.27,5.0,Male,No,Sat,Dinner,3 +16.04,2.24,Male,No,Sat,Dinner,3 +17.46,2.54,Male,No,Sun,Dinner,2 +13.94,3.06,Male,No,Sun,Dinner,2 +9.68,1.32,Male,No,Sun,Dinner,2 +30.4,5.6,Male,No,Sun,Dinner,4 +18.29,3.0,Male,No,Sun,Dinner,2 +22.23,5.0,Male,No,Sun,Dinner,2 +32.4,6.0,Male,No,Sun,Dinner,4 +28.55,2.05,Male,No,Sun,Dinner,3 +18.04,3.0,Male,No,Sun,Dinner,2 +12.54,2.5,Male,No,Sun,Dinner,2 +10.29,2.6,Female,No,Sun,Dinner,2 +34.81,5.2,Female,No,Sun,Dinner,4 +9.94,1.56,Male,No,Sun,Dinner,2 +25.56,4.34,Male,No,Sun,Dinner,4 +19.49,3.51,Male,No,Sun,Dinner,2 +38.01,3.0,Male,Yes,Sat,Dinner,4 +26.41,1.5,Female,No,Sat,Dinner,2 +11.24,1.76,Male,Yes,Sat,Dinner,2 +48.27,6.73,Male,No,Sat,Dinner,4 +20.29,3.21,Male,Yes,Sat,Dinner,2 +13.81,2.0,Male,Yes,Sat,Dinner,2 +11.02,1.98,Male,Yes,Sat,Dinner,2 +18.29,3.76,Male,Yes,Sat,Dinner,4 +17.59,2.64,Male,No,Sat,Dinner,3 +20.08,3.15,Male,No,Sat,Dinner,3 +16.45,2.47,Female,No,Sat,Dinner,2 +3.07,1.0,Female,Yes,Sat,Dinner,1 +20.23,2.01,Male,No,Sat,Dinner,2 +15.01,2.09,Male,Yes,Sat,Dinner,2 +12.02,1.97,Male,No,Sat,Dinner,2 +17.07,3.0,Female,No,Sat,Dinner,3 +26.86,3.14,Female,Yes,Sat,Dinner,2 +25.28,5.0,Female,Yes,Sat,Dinner,2 +14.73,2.2,Female,No,Sat,Dinner,2 +10.51,1.25,Male,No,Sat,Dinner,2 +17.92,3.08,Male,Yes,Sat,Dinner,2 +27.2,4.0,Male,No,Thur,Lunch,4 +22.76,3.0,Male,No,Thur,Lunch,2 +17.29,2.71,Male,No,Thur,Lunch,2 +19.44,3.0,Male,Yes,Thur,Lunch,2 +16.66,3.4,Male,No,Thur,Lunch,2 +10.07,1.83,Female,No,Thur,Lunch,1 +32.68,5.0,Male,Yes,Thur,Lunch,2 +15.98,2.03,Male,No,Thur,Lunch,2 +34.83,5.17,Female,No,Thur,Lunch,4 +13.03,2.0,Male,No,Thur,Lunch,2 +18.28,4.0,Male,No,Thur,Lunch,2 +24.71,5.85,Male,No,Thur,Lunch,2 +21.16,3.0,Male,No,Thur,Lunch,2 +28.97,3.0,Male,Yes,Fri,Dinner,2 +22.49,3.5,Male,No,Fri,Dinner,2 +5.75,1.0,Female,Yes,Fri,Dinner,2 +16.32,4.3,Female,Yes,Fri,Dinner,2 +22.75,3.25,Female,No,Fri,Dinner,2 +40.17,4.73,Male,Yes,Fri,Dinner,4 +27.28,4.0,Male,Yes,Fri,Dinner,2 +12.03,1.5,Male,Yes,Fri,Dinner,2 +21.01,3.0,Male,Yes,Fri,Dinner,2 +12.46,1.5,Male,No,Fri,Dinner,2 +11.35,2.5,Female,Yes,Fri,Dinner,2 +15.38,3.0,Female,Yes,Fri,Dinner,2 +44.3,2.5,Female,Yes,Sat,Dinner,3 +22.42,3.48,Female,Yes,Sat,Dinner,2 +20.92,4.08,Female,No,Sat,Dinner,2 +15.36,1.64,Male,Yes,Sat,Dinner,2 +20.49,4.06,Male,Yes,Sat,Dinner,2 +25.21,4.29,Male,Yes,Sat,Dinner,2 +18.24,3.76,Male,No,Sat,Dinner,2 +14.31,4.0,Female,Yes,Sat,Dinner,2 +14.0,3.0,Male,No,Sat,Dinner,2 +7.25,1.0,Female,No,Sat,Dinner,1 +38.07,4.0,Male,No,Sun,Dinner,3 +23.95,2.55,Male,No,Sun,Dinner,2 +25.71,4.0,Female,No,Sun,Dinner,3 +17.31,3.5,Female,No,Sun,Dinner,2 +29.93,5.07,Male,No,Sun,Dinner,4 +10.65,1.5,Female,No,Thur,Lunch,2 +12.43,1.8,Female,No,Thur,Lunch,2 +24.08,2.92,Female,No,Thur,Lunch,4 +11.69,2.31,Male,No,Thur,Lunch,2 +13.42,1.68,Female,No,Thur,Lunch,2 +14.26,2.5,Male,No,Thur,Lunch,2 +15.95,2.0,Male,No,Thur,Lunch,2 +12.48,2.52,Female,No,Thur,Lunch,2 +29.8,4.2,Female,No,Thur,Lunch,6 +8.52,1.48,Male,No,Thur,Lunch,2 +14.52,2.0,Female,No,Thur,Lunch,2 +11.38,2.0,Female,No,Thur,Lunch,2 +22.82,2.18,Male,No,Thur,Lunch,3 +19.08,1.5,Male,No,Thur,Lunch,2 +20.27,2.83,Female,No,Thur,Lunch,2 +11.17,1.5,Female,No,Thur,Lunch,2 +12.26,2.0,Female,No,Thur,Lunch,2 +18.26,3.25,Female,No,Thur,Lunch,2 +8.51,1.25,Female,No,Thur,Lunch,2 +10.33,2.0,Female,No,Thur,Lunch,2 +14.15,2.0,Female,No,Thur,Lunch,2 +16.0,2.0,Male,Yes,Thur,Lunch,2 +13.16,2.75,Female,No,Thur,Lunch,2 +17.47,3.5,Female,No,Thur,Lunch,2 +34.3,6.7,Male,No,Thur,Lunch,6 +41.19,5.0,Male,No,Thur,Lunch,5 +27.05,5.0,Female,No,Thur,Lunch,6 +16.43,2.3,Female,No,Thur,Lunch,2 +8.35,1.5,Female,No,Thur,Lunch,2 +18.64,1.36,Female,No,Thur,Lunch,3 +11.87,1.63,Female,No,Thur,Lunch,2 +9.78,1.73,Male,No,Thur,Lunch,2 +7.51,2.0,Male,No,Thur,Lunch,2 +14.07,2.5,Male,No,Sun,Dinner,2 +13.13,2.0,Male,No,Sun,Dinner,2 +17.26,2.74,Male,No,Sun,Dinner,3 +24.55,2.0,Male,No,Sun,Dinner,4 +19.77,2.0,Male,No,Sun,Dinner,4 +29.85,5.14,Female,No,Sun,Dinner,5 +48.17,5.0,Male,No,Sun,Dinner,6 +25.0,3.75,Female,No,Sun,Dinner,4 +13.39,2.61,Female,No,Sun,Dinner,2 +16.49,2.0,Male,No,Sun,Dinner,4 +21.5,3.5,Male,No,Sun,Dinner,4 +12.66,2.5,Male,No,Sun,Dinner,2 +16.21,2.0,Female,No,Sun,Dinner,3 +13.81,2.0,Male,No,Sun,Dinner,2 +17.51,3.0,Female,Yes,Sun,Dinner,2 +24.52,3.48,Male,No,Sun,Dinner,3 +20.76,2.24,Male,No,Sun,Dinner,2 +31.71,4.5,Male,No,Sun,Dinner,4 +10.59,1.61,Female,Yes,Sat,Dinner,2 +10.63,2.0,Female,Yes,Sat,Dinner,2 +50.81,10.0,Male,Yes,Sat,Dinner,3 +15.81,3.16,Male,Yes,Sat,Dinner,2 +7.25,5.15,Male,Yes,Sun,Dinner,2 +31.85,3.18,Male,Yes,Sun,Dinner,2 +16.82,4.0,Male,Yes,Sun,Dinner,2 +32.9,3.11,Male,Yes,Sun,Dinner,2 +17.89,2.0,Male,Yes,Sun,Dinner,2 +14.48,2.0,Male,Yes,Sun,Dinner,2 +9.6,4.0,Female,Yes,Sun,Dinner,2 +34.63,3.55,Male,Yes,Sun,Dinner,2 +34.65,3.68,Male,Yes,Sun,Dinner,4 +23.33,5.65,Male,Yes,Sun,Dinner,2 +45.35,3.5,Male,Yes,Sun,Dinner,3 +23.17,6.5,Male,Yes,Sun,Dinner,4 +40.55,3.0,Male,Yes,Sun,Dinner,2 +20.69,5.0,Male,No,Sun,Dinner,5 +20.9,3.5,Female,Yes,Sun,Dinner,3 +30.46,2.0,Male,Yes,Sun,Dinner,5 +18.15,3.5,Female,Yes,Sun,Dinner,3 +23.1,4.0,Male,Yes,Sun,Dinner,3 +15.69,1.5,Male,Yes,Sun,Dinner,2 +19.81,4.19,Female,Yes,Thur,Lunch,2 +28.44,2.56,Male,Yes,Thur,Lunch,2 +15.48,2.02,Male,Yes,Thur,Lunch,2 +16.58,4.0,Male,Yes,Thur,Lunch,2 +7.56,1.44,Male,No,Thur,Lunch,2 +10.34,2.0,Male,Yes,Thur,Lunch,2 +43.11,5.0,Female,Yes,Thur,Lunch,4 +13.0,2.0,Female,Yes,Thur,Lunch,2 +13.51,2.0,Male,Yes,Thur,Lunch,2 +18.71,4.0,Male,Yes,Thur,Lunch,3 +12.74,2.01,Female,Yes,Thur,Lunch,2 +13.0,2.0,Female,Yes,Thur,Lunch,2 +16.4,2.5,Female,Yes,Thur,Lunch,2 +20.53,4.0,Male,Yes,Thur,Lunch,4 +16.47,3.23,Female,Yes,Thur,Lunch,3 +26.59,3.41,Male,Yes,Sat,Dinner,3 +38.73,3.0,Male,Yes,Sat,Dinner,4 +24.27,2.03,Male,Yes,Sat,Dinner,2 +12.76,2.23,Female,Yes,Sat,Dinner,2 +30.06,2.0,Male,Yes,Sat,Dinner,3 +25.89,5.16,Male,Yes,Sat,Dinner,4 +48.33,9.0,Male,No,Sat,Dinner,4 +13.27,2.5,Female,Yes,Sat,Dinner,2 +28.17,6.5,Female,Yes,Sat,Dinner,3 +12.9,1.1,Female,Yes,Sat,Dinner,2 +28.15,3.0,Male,Yes,Sat,Dinner,5 +11.59,1.5,Male,Yes,Sat,Dinner,2 +7.74,1.44,Male,Yes,Sat,Dinner,2 +30.14,3.09,Female,Yes,Sat,Dinner,4 +12.16,2.2,Male,Yes,Fri,Lunch,2 +13.42,3.48,Female,Yes,Fri,Lunch,2 +8.58,1.92,Male,Yes,Fri,Lunch,1 +15.98,3.0,Female,No,Fri,Lunch,3 +13.42,1.58,Male,Yes,Fri,Lunch,2 +16.27,2.5,Female,Yes,Fri,Lunch,2 +10.09,2.0,Female,Yes,Fri,Lunch,2 +20.45,3.0,Male,No,Sat,Dinner,4 +13.28,2.72,Male,No,Sat,Dinner,2 +22.12,2.88,Female,Yes,Sat,Dinner,2 +24.01,2.0,Male,Yes,Sat,Dinner,4 +15.69,3.0,Male,Yes,Sat,Dinner,3 +11.61,3.39,Male,No,Sat,Dinner,2 +10.77,1.47,Male,No,Sat,Dinner,2 +15.53,3.0,Male,Yes,Sat,Dinner,2 +10.07,1.25,Male,No,Sat,Dinner,2 +12.6,1.0,Male,Yes,Sat,Dinner,2 +32.83,1.17,Male,Yes,Sat,Dinner,2 +35.83,4.67,Female,No,Sat,Dinner,3 +29.03,5.92,Male,No,Sat,Dinner,3 +27.18,2.0,Female,Yes,Sat,Dinner,2 +22.67,2.0,Male,Yes,Sat,Dinner,2 +17.82,1.75,Male,No,Sat,Dinner,2 +18.78,3.0,Female,No,Thur,Dinner,2 + +``` +::: diff --git a/tutorials/intro-express/6-reactivity.qmd b/tutorials/intro-express/6-reactivity.qmd new file mode 100644 index 00000000..0d8654de --- /dev/null +++ b/tutorials/intro-express/6-reactivity.qmd @@ -0,0 +1,143 @@ +--- +title: Reactivity +--- + +We've been using the term "reactive" a lot during these tutorials. +But what does it mean? +It's actually more than "user interacts with input and new value gets calculated". +Reactivity is actually what makes Shiny special: +when an input changes, only the minimum amount of calculations are made to update the outputs. +This makes shiny very efficient. + +Shiny knows to re-execute **reactive functions** (e.g., `render` functions) when their **reactive dependencies** (e.g., `input`) change. +There are other main forms of reactive functions and dependencies: + +* Calculations with `@reactive.calc` + * Write your reactive calculation once, then call it as needed. +* Side effects with `@reactive.effect` + * Effects are similar to `@render.*` functions, but they don't return anything. They're used for their side-effects (e.g., writing to a database, sending an email, etc.) +* Reactive values with `reactive.value` + * Create `input`-like values that aren't tied to input controls and can be updated. They're often used to maintain state in an app. + +In this lesson we'll focus on the `@reactive.calc`, let's see why we may want reactive calculations. + +```{mermaid} +flowchart LR + A[Input] --> B(Calculated from Input) + B --> C{Value calculated from the input calc} + B --> D[Another value calculated from the input calc] +``` + +Let's say we have an input (A), +this input creates a value in the application (B). +But what if another part of the application needs to use this calculated value (C) +or another part (D)? + +Similar to why we create variables in python to capture intermediate values, +we can save these intermediate "reactive" calculations in shiny. + + +```{shinylive-python} +#| standalone: true +#| components: [editor, viewer] +#| layout: vertical +#| viewerHeight: 200 + +from shiny import reactive +from shiny.express import input, render, ui + +ui.input_slider("x", "Slider value", min=0, max=100, value=10) + +# we need to make a calculation from an input value +@render.text +def x_squared_text(): + return f"Squared value: {input.x() ** 2}" + +# we can save this calculation to be used later +@reactive.calc +def x_squared(): + return input.x() ** 2 + +# we can use that saved calculation +@render.text +def x_squared_calc_text(): + return f"Saved squared: {x_squared()}" + +# we can build on top of that saved calculation +@render.text +def x_squared_half_calc_text(): + return f"Build on squared value: {x_squared() / 2}" + +# we don't need to re-calculate everything from the input again +@render.text +def x_squared_half_text(): + return f"Recalculate from input: {input.x() ** 2 / 2}" +``` + +The app above shows that we only need to make the `input.x() ** 2` calculation **once**, +so we do not need to repeat that calculation, `input.x() ** 2 / 2`. + +This idea is really similar to the following Python code, +where we save an intermediate value, +instead of repeating a calculation. + +```{python} +initial_input_value = 3 + +initial_squared = initial_input_value ** 2 + +initial_squared_half = initial_squared / 2 +``` + +If we did not save the `initial_squared` intermediate value, +we would have to re-make that calculation when doing a square and half. + +```{python} +initial_squared_half = initial_input_value ** 2 / 2 +``` + + + +Currently our application is re-filtering the data from the input components +for each output component displayed in the application. + +```{mermaid} +flowchart LR + A[Input Slider] --> C(Filtered Data) + B[Input Checkboxes] --> C + C --> D{value box 1} + + E[Input Slider] --> G(Filtered Data) + F[Input Checkboxes] --> G + G --> H{value box 2} + + I[Input Slider] --> K(Filtered Data) + J[Input Checkboxes] --> K + K --> L{value box 3} + + M[Input Slider] --> O(Filtered Data) + N[Input Checkboxes] --> O + O --> P{dataframe display} + + Q[Input Slider] --> S(Filtered Data) + R[Input Checkboxes] --> S + S --> T{scatter plot} + + U[Input Slider] --> W(Filtered Data) + V[Input Checkboxes] --> W + W --> X{ridgeplot} +``` + +It would be great if we calculated the filtered data **once** +and re-used it across all our output components. +```{mermaid} +flowchart LR + A[Input Slider] --> C(Filtered Data) + B[Input Checkboxes] --> C + C --> D{value box 1} + C --> E{value box 2} + C --> F{value box 3} + C --> G{dataframe display} + C --> H{scatter plot} + C --> I{ridgeplot} +``` diff --git a/tutorials/intro-express/6-reactivity.quarto_ipynb b/tutorials/intro-express/6-reactivity.quarto_ipynb new file mode 100644 index 00000000..d7fc1b83 --- /dev/null +++ b/tutorials/intro-express/6-reactivity.quarto_ipynb @@ -0,0 +1,192 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "title: Reactivity\n", + "---\n", + "\n", + "\n", + "We've been using the term \"reactive\" a lot during these tutorials.\n", + "But what does it mean?\n", + "It's actually more than \"user interacts with input and new value gets calculated\".\n", + "Reactivity is actually what makes Shiny special:\n", + "when an input changes, only the minimum amount of calculations are made to update the outputs.\n", + "This makes shiny very efficient.\n", + "\n", + "Shiny knows to re-execute **reactive functions** (e.g., `render` functions) when their **reactive dependencies** (e.g., `input`) change.\n", + "There are other main forms of reactive functions and dependencies:\n", + "\n", + "* Calculations with `@reactive.calc`\n", + " * Write your reactive calculation once, then call it as needed.\n", + "* Side effects with `@reactive.effect`\n", + " * Effects are similar to `@render.*` functions, but they don't return anything. They're used for their side-effects (e.g., writing to a database, sending an email, etc.)\n", + "* Reactive values with `reactive.value`\n", + " * Create `input`-like values that aren't tied to input controls and can be updated. They're often used to maintain state in an app.\n", + "\n", + "In this lesson we'll focus on the `@reactive.calc`, let's see why we may want reactive calculations.\n", + "\n", + "\n", + "```{mermaid}\n", + "flowchart LR\n", + " A[Input] --> B(Calculated from Input)\n", + " B --> C{Value calculated from the input calc}\n", + " B --> D[Another value calculated from the input calc]\n", + "```\n", + "\n", + "\n", + "Let's say we have an input (A),\n", + "this input creates a value in the application (B).\n", + "But what if another part of the application needs to use this calculated value (C)\n", + "or another part (D)?\n", + "\n", + "Similar to why we create variables in python to capture intermediate values,\n", + "we can save these intermediate \"reactive\" calculations in shiny.\n", + "\n", + "\n", + "```{shinylive-python}\n", + "#| standalone: true\n", + "#| components: [editor, viewer]\n", + "#| layout: vertical\n", + "#| viewerHeight: 200\n", + "\n", + "from shiny import reactive\n", + "from shiny.express import input, render, ui\n", + "\n", + "ui.input_slider(\"x\", \"Slider value\", min=0, max=100, value=10)\n", + "\n", + "# we need to make a calculation from an input value\n", + "@render.text\n", + "def x_squared_text():\n", + " return f\"Squared value: {input.x() ** 2}\"\n", + "\n", + "# we can save this calculation to be used later\n", + "@reactive.calc\n", + "def x_squared():\n", + " return input.x() ** 2\n", + "\n", + "# we can use that saved calculation\n", + "@render.text\n", + "def x_squared_calc_text():\n", + " return f\"Saved squared: {x_squared()}\"\n", + "\n", + "# we can build on top of that saved calculation\n", + "@render.text\n", + "def x_squared_half_calc_text():\n", + " return f\"Build on squared value: {x_squared() / 2}\"\n", + "\n", + "# we don't need to re-calculate everything from the input again\n", + "@render.text\n", + "def x_squared_half_text():\n", + " return f\"Recalculate from input: {input.x() ** 2 / 2}\"\n", + "```\n", + "\n", + "The app above shows that we only need to make the `input.x() ** 2` calculation **once**,\n", + "so we do not need to repeat that calculation, `input.x() ** 2 / 2`.\n", + "\n", + "This idea is really similar to the following Python code,\n", + "where we save an intermediate value,\n", + "instead of repeating a calculation.\n" + ], + "id": "d17d93f0" + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "initial_input_value = 3\n", + "\n", + "initial_squared = initial_input_value ** 2\n", + "\n", + "initial_squared_half = initial_squared / 2" + ], + "id": "c5c2e4fb", + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we did not save the `initial_squared` intermediate value,\n", + "we would have to re-make that calculation when doing a square and half.\n" + ], + "id": "dfad6d5f" + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "initial_squared_half = initial_input_value ** 2 / 2" + ], + "id": "a7eedb91", + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Currently our application is re-filtering the data from the input components\n", + "for each output component displayed in the application.\n", + "\n", + "\n", + "```{mermaid}\n", + "flowchart LR\n", + " A[Input Slider] --> C(Filtered Data)\n", + " B[Input Checkboxes] --> C\n", + " C --> D{value box 1}\n", + "\n", + " E[Input Slider] --> G(Filtered Data)\n", + " F[Input Checkboxes] --> G\n", + " G --> H{value box 2}\n", + "\n", + " I[Input Slider] --> K(Filtered Data)\n", + " J[Input Checkboxes] --> K\n", + " K --> L{value box 3}\n", + "\n", + " M[Input Slider] --> O(Filtered Data)\n", + " N[Input Checkboxes] --> O\n", + " O --> P{dataframe display}\n", + "\n", + " Q[Input Slider] --> S(Filtered Data)\n", + " R[Input Checkboxes] --> S\n", + " S --> T{scatter plot}\n", + "\n", + " U[Input Slider] --> W(Filtered Data)\n", + " V[Input Checkboxes] --> W\n", + " W --> X{ridgeplot}\n", + "```\n", + "\n", + "\n", + "It would be great if we calculated the filtered data **once**\n", + "and re-used it across all our output components.\n", + "\n", + "```{mermaid}\n", + "flowchart LR\n", + " A[Input Slider] --> C(Filtered Data)\n", + " B[Input Checkboxes] --> C\n", + " C --> D{value box 1}\n", + " C --> E{value box 2}\n", + " C --> F{value box 3}\n", + " C --> G{dataframe display}\n", + " C --> H{scatter plot}\n", + " C --> I{ridgeplot}\n", + "```" + ], + "id": "27068e29" + } + ], + "metadata": { + "kernelspec": { + "name": "pfe_book", + "language": "python", + "display_name": "pfe_book", + "path": "/Users/danielchen/Library/Jupyter/kernels/pfe_book" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/tutorials/intro-express/7-publish.qmd b/tutorials/intro-express/7-publish.qmd new file mode 100644 index 00000000..719dc1a9 --- /dev/null +++ b/tutorials/intro-express/7-publish.qmd @@ -0,0 +1,77 @@ +--- +title: Publish and Share Your Application +--- + +## `app.py` + +At the start of these tutorials, +we've shown how to create an `app.py` file and how to run it +from either Positron, VS Code, or in the command line. + +If your file begins with the `app` prefix, +the shiny extension for VS Code will give you a play button to run +the current file as a shiny application. + +![](img/010-run_app.png) + +The play button executes the `shiny run` command in the terminal +for you, but you can manually run an application file +on your own. + +```bash +shiny run app.py +``` +This `app.py` file can be shared with others + +## Shiny Live + +Throughout these tutorials, +we provided you the code and working example in line with the text. +These applications were run using Shinylive. +It uses Shiny and WebAssembly to run Shiny applications +completely in the browser without having to set anything up. + +If you have all your code in an `app.py` file, +you can go to the shiny live editor and paste in your code. +The shiny live editor is at this location: + +:::{.callout-note} +There is no empty editor, the site will take you to the shinylive page and +default to one of the example applications. +You can copy and paste your `app.py` file into the editor and run it in the browser +::: + +You cannot save the application file in the browser, +instead you can click on the "Share" button on the corner of the Shinylive page, +and use this URL to share your application with others. +There is an option to use the link that shows the code and rendered application. + +:::{.callout-note} +Shinylive URLs are extremely long. That is because +all the code is embeded into the URL. +::: + +You can read more about Shinylive here: + + +## Connect Cloud + +[Connect Cloud](https://connect.posit.cloud/) is a free service that +allows you to publish your web applications. +You can actually use it to publish more than a shiny for python application! + +The code you want published needs to first exist in a +[GitHub](https://github.com/) +repository. +Once your code is in a github repository, +you can use the Posit Connect Cloud interface to link to the repository, +and the service will automatically look for your `app.py` file to publish. +You can follow these instructions from the connect cloud shiny for python +publishing page: + + +If you want to update your application, +you will make your changes, commit, and push them to the same github repository. +Connect Cloud has a +[republish](https://docs.posit.co/connect-cloud/user/manage/content_page.html#republish) +feature on the main page that will re-deploy your application. diff --git a/tutorials/intro-express/8-next.qmd b/tutorials/intro-express/8-next.qmd new file mode 100644 index 00000000..48f92100 --- /dev/null +++ b/tutorials/intro-express/8-next.qmd @@ -0,0 +1,46 @@ +--- +title: "What's Next?" +--- + +## Templates + +This dashboard is one of many example dashboards available on the +[Shiny for Python Templates Page](https://shiny.posit.co/py/templates/). + +If you want to download and run this application, +you can visit the +[Restaurant tips dashboard template page](https://shiny.posit.co/py/templates/dashboard-tips/) +and follow the command to downlaod the application + +```bash +shiny create --template dashboard-tips --mode express --github posit-dev/py-shiny-templates +``` + +This will create a `dashboard-tips` folder in your current directory. +The output of the command will then prompt you to install the dependencies for the application +using `pip`. + +```bash +$ shiny create --template dashboard-tips --mode express --github posit-dev/py-shiny-templates +β„Ή Using GitHub repository posit-dev/py-shiny-templates. +… Creating Restaurant tips dashboard Shiny app... +? Enter destination directory: ./dashboard-tips +βœ“ Created Shiny app at dashboard-tips + +β†’ Next steps: +- Install required dependencies: + cd dashboard-tips + pip install -r requirements.txt +- Open and edit the app file: dashboard-tips/app.py +``` + +To run the example tips dashboard, +you can use the same `shiny run` command we did in the previous exercise. +The application code is in the `app.py` file. + +```bash +shiny run +``` + +There are a few other files and modules that are in this example application. +We will spend the next few lessons of this tutorial going though each of the components. diff --git a/tutorials/intro-express/_metadata.yml b/tutorials/intro-express/_metadata.yml new file mode 100644 index 00000000..62df4970 --- /dev/null +++ b/tutorials/intro-express/_metadata.yml @@ -0,0 +1 @@ +sidebar: tutorial-express-intro diff --git a/tutorials/intro-express/img/010-run_app-cropped_editor.png b/tutorials/intro-express/img/010-run_app-cropped_editor.png new file mode 100644 index 00000000..9ad7ef1d Binary files /dev/null and b/tutorials/intro-express/img/010-run_app-cropped_editor.png differ diff --git a/tutorials/intro-express/img/010-run_app.png b/tutorials/intro-express/img/010-run_app.png new file mode 100644 index 00000000..51adbdbc Binary files /dev/null and b/tutorials/intro-express/img/010-run_app.png differ diff --git a/tutorials/intro-express/tips.csv b/tutorials/intro-express/tips.csv new file mode 100644 index 00000000..856a65a6 --- /dev/null +++ b/tutorials/intro-express/tips.csv @@ -0,0 +1,245 @@ +total_bill,tip,sex,smoker,day,time,size +16.99,1.01,Female,No,Sun,Dinner,2 +10.34,1.66,Male,No,Sun,Dinner,3 +21.01,3.5,Male,No,Sun,Dinner,3 +23.68,3.31,Male,No,Sun,Dinner,2 +24.59,3.61,Female,No,Sun,Dinner,4 +25.29,4.71,Male,No,Sun,Dinner,4 +8.77,2.0,Male,No,Sun,Dinner,2 +26.88,3.12,Male,No,Sun,Dinner,4 +15.04,1.96,Male,No,Sun,Dinner,2 +14.78,3.23,Male,No,Sun,Dinner,2 +10.27,1.71,Male,No,Sun,Dinner,2 +35.26,5.0,Female,No,Sun,Dinner,4 +15.42,1.57,Male,No,Sun,Dinner,2 +18.43,3.0,Male,No,Sun,Dinner,4 +14.83,3.02,Female,No,Sun,Dinner,2 +21.58,3.92,Male,No,Sun,Dinner,2 +10.33,1.67,Female,No,Sun,Dinner,3 +16.29,3.71,Male,No,Sun,Dinner,3 +16.97,3.5,Female,No,Sun,Dinner,3 +20.65,3.35,Male,No,Sat,Dinner,3 +17.92,4.08,Male,No,Sat,Dinner,2 +20.29,2.75,Female,No,Sat,Dinner,2 +15.77,2.23,Female,No,Sat,Dinner,2 +39.42,7.58,Male,No,Sat,Dinner,4 +19.82,3.18,Male,No,Sat,Dinner,2 +17.81,2.34,Male,No,Sat,Dinner,4 +13.37,2.0,Male,No,Sat,Dinner,2 +12.69,2.0,Male,No,Sat,Dinner,2 +21.7,4.3,Male,No,Sat,Dinner,2 +19.65,3.0,Female,No,Sat,Dinner,2 +9.55,1.45,Male,No,Sat,Dinner,2 +18.35,2.5,Male,No,Sat,Dinner,4 +15.06,3.0,Female,No,Sat,Dinner,2 +20.69,2.45,Female,No,Sat,Dinner,4 +17.78,3.27,Male,No,Sat,Dinner,2 +24.06,3.6,Male,No,Sat,Dinner,3 +16.31,2.0,Male,No,Sat,Dinner,3 +16.93,3.07,Female,No,Sat,Dinner,3 +18.69,2.31,Male,No,Sat,Dinner,3 +31.27,5.0,Male,No,Sat,Dinner,3 +16.04,2.24,Male,No,Sat,Dinner,3 +17.46,2.54,Male,No,Sun,Dinner,2 +13.94,3.06,Male,No,Sun,Dinner,2 +9.68,1.32,Male,No,Sun,Dinner,2 +30.4,5.6,Male,No,Sun,Dinner,4 +18.29,3.0,Male,No,Sun,Dinner,2 +22.23,5.0,Male,No,Sun,Dinner,2 +32.4,6.0,Male,No,Sun,Dinner,4 +28.55,2.05,Male,No,Sun,Dinner,3 +18.04,3.0,Male,No,Sun,Dinner,2 +12.54,2.5,Male,No,Sun,Dinner,2 +10.29,2.6,Female,No,Sun,Dinner,2 +34.81,5.2,Female,No,Sun,Dinner,4 +9.94,1.56,Male,No,Sun,Dinner,2 +25.56,4.34,Male,No,Sun,Dinner,4 +19.49,3.51,Male,No,Sun,Dinner,2 +38.01,3.0,Male,Yes,Sat,Dinner,4 +26.41,1.5,Female,No,Sat,Dinner,2 +11.24,1.76,Male,Yes,Sat,Dinner,2 +48.27,6.73,Male,No,Sat,Dinner,4 +20.29,3.21,Male,Yes,Sat,Dinner,2 +13.81,2.0,Male,Yes,Sat,Dinner,2 +11.02,1.98,Male,Yes,Sat,Dinner,2 +18.29,3.76,Male,Yes,Sat,Dinner,4 +17.59,2.64,Male,No,Sat,Dinner,3 +20.08,3.15,Male,No,Sat,Dinner,3 +16.45,2.47,Female,No,Sat,Dinner,2 +3.07,1.0,Female,Yes,Sat,Dinner,1 +20.23,2.01,Male,No,Sat,Dinner,2 +15.01,2.09,Male,Yes,Sat,Dinner,2 +12.02,1.97,Male,No,Sat,Dinner,2 +17.07,3.0,Female,No,Sat,Dinner,3 +26.86,3.14,Female,Yes,Sat,Dinner,2 +25.28,5.0,Female,Yes,Sat,Dinner,2 +14.73,2.2,Female,No,Sat,Dinner,2 +10.51,1.25,Male,No,Sat,Dinner,2 +17.92,3.08,Male,Yes,Sat,Dinner,2 +27.2,4.0,Male,No,Thur,Lunch,4 +22.76,3.0,Male,No,Thur,Lunch,2 +17.29,2.71,Male,No,Thur,Lunch,2 +19.44,3.0,Male,Yes,Thur,Lunch,2 +16.66,3.4,Male,No,Thur,Lunch,2 +10.07,1.83,Female,No,Thur,Lunch,1 +32.68,5.0,Male,Yes,Thur,Lunch,2 +15.98,2.03,Male,No,Thur,Lunch,2 +34.83,5.17,Female,No,Thur,Lunch,4 +13.03,2.0,Male,No,Thur,Lunch,2 +18.28,4.0,Male,No,Thur,Lunch,2 +24.71,5.85,Male,No,Thur,Lunch,2 +21.16,3.0,Male,No,Thur,Lunch,2 +28.97,3.0,Male,Yes,Fri,Dinner,2 +22.49,3.5,Male,No,Fri,Dinner,2 +5.75,1.0,Female,Yes,Fri,Dinner,2 +16.32,4.3,Female,Yes,Fri,Dinner,2 +22.75,3.25,Female,No,Fri,Dinner,2 +40.17,4.73,Male,Yes,Fri,Dinner,4 +27.28,4.0,Male,Yes,Fri,Dinner,2 +12.03,1.5,Male,Yes,Fri,Dinner,2 +21.01,3.0,Male,Yes,Fri,Dinner,2 +12.46,1.5,Male,No,Fri,Dinner,2 +11.35,2.5,Female,Yes,Fri,Dinner,2 +15.38,3.0,Female,Yes,Fri,Dinner,2 +44.3,2.5,Female,Yes,Sat,Dinner,3 +22.42,3.48,Female,Yes,Sat,Dinner,2 +20.92,4.08,Female,No,Sat,Dinner,2 +15.36,1.64,Male,Yes,Sat,Dinner,2 +20.49,4.06,Male,Yes,Sat,Dinner,2 +25.21,4.29,Male,Yes,Sat,Dinner,2 +18.24,3.76,Male,No,Sat,Dinner,2 +14.31,4.0,Female,Yes,Sat,Dinner,2 +14.0,3.0,Male,No,Sat,Dinner,2 +7.25,1.0,Female,No,Sat,Dinner,1 +38.07,4.0,Male,No,Sun,Dinner,3 +23.95,2.55,Male,No,Sun,Dinner,2 +25.71,4.0,Female,No,Sun,Dinner,3 +17.31,3.5,Female,No,Sun,Dinner,2 +29.93,5.07,Male,No,Sun,Dinner,4 +10.65,1.5,Female,No,Thur,Lunch,2 +12.43,1.8,Female,No,Thur,Lunch,2 +24.08,2.92,Female,No,Thur,Lunch,4 +11.69,2.31,Male,No,Thur,Lunch,2 +13.42,1.68,Female,No,Thur,Lunch,2 +14.26,2.5,Male,No,Thur,Lunch,2 +15.95,2.0,Male,No,Thur,Lunch,2 +12.48,2.52,Female,No,Thur,Lunch,2 +29.8,4.2,Female,No,Thur,Lunch,6 +8.52,1.48,Male,No,Thur,Lunch,2 +14.52,2.0,Female,No,Thur,Lunch,2 +11.38,2.0,Female,No,Thur,Lunch,2 +22.82,2.18,Male,No,Thur,Lunch,3 +19.08,1.5,Male,No,Thur,Lunch,2 +20.27,2.83,Female,No,Thur,Lunch,2 +11.17,1.5,Female,No,Thur,Lunch,2 +12.26,2.0,Female,No,Thur,Lunch,2 +18.26,3.25,Female,No,Thur,Lunch,2 +8.51,1.25,Female,No,Thur,Lunch,2 +10.33,2.0,Female,No,Thur,Lunch,2 +14.15,2.0,Female,No,Thur,Lunch,2 +16.0,2.0,Male,Yes,Thur,Lunch,2 +13.16,2.75,Female,No,Thur,Lunch,2 +17.47,3.5,Female,No,Thur,Lunch,2 +34.3,6.7,Male,No,Thur,Lunch,6 +41.19,5.0,Male,No,Thur,Lunch,5 +27.05,5.0,Female,No,Thur,Lunch,6 +16.43,2.3,Female,No,Thur,Lunch,2 +8.35,1.5,Female,No,Thur,Lunch,2 +18.64,1.36,Female,No,Thur,Lunch,3 +11.87,1.63,Female,No,Thur,Lunch,2 +9.78,1.73,Male,No,Thur,Lunch,2 +7.51,2.0,Male,No,Thur,Lunch,2 +14.07,2.5,Male,No,Sun,Dinner,2 +13.13,2.0,Male,No,Sun,Dinner,2 +17.26,2.74,Male,No,Sun,Dinner,3 +24.55,2.0,Male,No,Sun,Dinner,4 +19.77,2.0,Male,No,Sun,Dinner,4 +29.85,5.14,Female,No,Sun,Dinner,5 +48.17,5.0,Male,No,Sun,Dinner,6 +25.0,3.75,Female,No,Sun,Dinner,4 +13.39,2.61,Female,No,Sun,Dinner,2 +16.49,2.0,Male,No,Sun,Dinner,4 +21.5,3.5,Male,No,Sun,Dinner,4 +12.66,2.5,Male,No,Sun,Dinner,2 +16.21,2.0,Female,No,Sun,Dinner,3 +13.81,2.0,Male,No,Sun,Dinner,2 +17.51,3.0,Female,Yes,Sun,Dinner,2 +24.52,3.48,Male,No,Sun,Dinner,3 +20.76,2.24,Male,No,Sun,Dinner,2 +31.71,4.5,Male,No,Sun,Dinner,4 +10.59,1.61,Female,Yes,Sat,Dinner,2 +10.63,2.0,Female,Yes,Sat,Dinner,2 +50.81,10.0,Male,Yes,Sat,Dinner,3 +15.81,3.16,Male,Yes,Sat,Dinner,2 +7.25,5.15,Male,Yes,Sun,Dinner,2 +31.85,3.18,Male,Yes,Sun,Dinner,2 +16.82,4.0,Male,Yes,Sun,Dinner,2 +32.9,3.11,Male,Yes,Sun,Dinner,2 +17.89,2.0,Male,Yes,Sun,Dinner,2 +14.48,2.0,Male,Yes,Sun,Dinner,2 +9.6,4.0,Female,Yes,Sun,Dinner,2 +34.63,3.55,Male,Yes,Sun,Dinner,2 +34.65,3.68,Male,Yes,Sun,Dinner,4 +23.33,5.65,Male,Yes,Sun,Dinner,2 +45.35,3.5,Male,Yes,Sun,Dinner,3 +23.17,6.5,Male,Yes,Sun,Dinner,4 +40.55,3.0,Male,Yes,Sun,Dinner,2 +20.69,5.0,Male,No,Sun,Dinner,5 +20.9,3.5,Female,Yes,Sun,Dinner,3 +30.46,2.0,Male,Yes,Sun,Dinner,5 +18.15,3.5,Female,Yes,Sun,Dinner,3 +23.1,4.0,Male,Yes,Sun,Dinner,3 +15.69,1.5,Male,Yes,Sun,Dinner,2 +19.81,4.19,Female,Yes,Thur,Lunch,2 +28.44,2.56,Male,Yes,Thur,Lunch,2 +15.48,2.02,Male,Yes,Thur,Lunch,2 +16.58,4.0,Male,Yes,Thur,Lunch,2 +7.56,1.44,Male,No,Thur,Lunch,2 +10.34,2.0,Male,Yes,Thur,Lunch,2 +43.11,5.0,Female,Yes,Thur,Lunch,4 +13.0,2.0,Female,Yes,Thur,Lunch,2 +13.51,2.0,Male,Yes,Thur,Lunch,2 +18.71,4.0,Male,Yes,Thur,Lunch,3 +12.74,2.01,Female,Yes,Thur,Lunch,2 +13.0,2.0,Female,Yes,Thur,Lunch,2 +16.4,2.5,Female,Yes,Thur,Lunch,2 +20.53,4.0,Male,Yes,Thur,Lunch,4 +16.47,3.23,Female,Yes,Thur,Lunch,3 +26.59,3.41,Male,Yes,Sat,Dinner,3 +38.73,3.0,Male,Yes,Sat,Dinner,4 +24.27,2.03,Male,Yes,Sat,Dinner,2 +12.76,2.23,Female,Yes,Sat,Dinner,2 +30.06,2.0,Male,Yes,Sat,Dinner,3 +25.89,5.16,Male,Yes,Sat,Dinner,4 +48.33,9.0,Male,No,Sat,Dinner,4 +13.27,2.5,Female,Yes,Sat,Dinner,2 +28.17,6.5,Female,Yes,Sat,Dinner,3 +12.9,1.1,Female,Yes,Sat,Dinner,2 +28.15,3.0,Male,Yes,Sat,Dinner,5 +11.59,1.5,Male,Yes,Sat,Dinner,2 +7.74,1.44,Male,Yes,Sat,Dinner,2 +30.14,3.09,Female,Yes,Sat,Dinner,4 +12.16,2.2,Male,Yes,Fri,Lunch,2 +13.42,3.48,Female,Yes,Fri,Lunch,2 +8.58,1.92,Male,Yes,Fri,Lunch,1 +15.98,3.0,Female,No,Fri,Lunch,3 +13.42,1.58,Male,Yes,Fri,Lunch,2 +16.27,2.5,Female,Yes,Fri,Lunch,2 +10.09,2.0,Female,Yes,Fri,Lunch,2 +20.45,3.0,Male,No,Sat,Dinner,4 +13.28,2.72,Male,No,Sat,Dinner,2 +22.12,2.88,Female,Yes,Sat,Dinner,2 +24.01,2.0,Male,Yes,Sat,Dinner,4 +15.69,3.0,Male,Yes,Sat,Dinner,3 +11.61,3.39,Male,No,Sat,Dinner,2 +10.77,1.47,Male,No,Sat,Dinner,2 +15.53,3.0,Male,Yes,Sat,Dinner,2 +10.07,1.25,Male,No,Sat,Dinner,2 +12.6,1.0,Male,Yes,Sat,Dinner,2 +32.83,1.17,Male,Yes,Sat,Dinner,2 +35.83,4.67,Female,No,Sat,Dinner,3 +29.03,5.92,Male,No,Sat,Dinner,3 +27.18,2.0,Female,Yes,Sat,Dinner,2 +22.67,2.0,Male,Yes,Sat,Dinner,2 +17.82,1.75,Male,No,Sat,Dinner,2 +18.78,3.0,Female,No,Thur,Dinner,2