diff --git a/tutorials/shiny/esquisse.r b/tutorials/shiny/esquisse.r new file mode 100644 index 0000000..aa5f679 --- /dev/null +++ b/tutorials/shiny/esquisse.r @@ -0,0 +1,164 @@ +# Load libraries ---- +library(shiny) +library(esquisse) +library(datamods) +library(tidyverse) + +# Define UI ---- +ui <- navbarPage( + title = "Navigation", + + # Import datamods + tabPanel( + title = "Load Data", + fluidRow( + htmlOutput("welcome"), + tags$head(tags$style(HTML(" + #welcome { + text-align: center; + } + ") + ) + ), + hr() + ), + fluidRow( + column( + width = 4, + checkboxGroupInput( + inputId = "from", + label = "Input Source", + choices = c("env", "file", "copypaste", "googlesheets", "url"), + selected = c("env", "file") + ), + actionButton("launch_modal", "Launch Data Modal Window") + ), + column( + width = 8, + htmlOutput("info") + ), + column( + width = 8, + hr(), + htmlOutput("loaded"), + verbatimTextOutput("summary"), + tags$head(tags$style("#summary{max-width: 500px;}")) # sets max box window for scrolling + ) + ) + ), + + # Output: View Data ---- + tabPanel( + title = "View Data", + htmlOutput("dtheader"), + hr(), + DT::dataTableOutput("contents") + # tableOutput("contents") + ), + + # Output: Esquisse ---- + tabPanel( + title = "Plot (Esquisse)", + htmlOutput("esqheader"), + checkboxGroupInput( + inputId = "aes", + label = "Aesthetic Options:", + choices = c( + "fill", "color", "size", "shape", + "weight", "group", "facet", "facet_row", "facet_col" + ), + selected = c("fill", "color", "size", "shape", "facet"), + inline = TRUE + ), + esquisse_ui( + id = "esquisse", + header = FALSE, # set to TRUE to see blue esquisse icon header + container = esquisseContainer( + fixed = c(150, 0, 0, 0) + ) + ) + ) +) + +server <- function(input, output, session) { + + output$welcome <- renderUI({ + str <- tags$h3("Welcome") + str0 <- print("This app adapts code from add-ins and modules created by the dreamRs team at ") + tag <- tags$a(href="https://github.com/dreamRs/", "github.com/dreamRs") + spacer <- print(".") + HTML(paste(str, str0, tag, spacer, sep = "")) + }) + + output$info <- renderUI({ + str1 <- print("Instructions") + str2 <- print("- Select input source options to upload dataset.") + str3 <- print("- Example datasets can be loaded through 'env' (global environment).") + str4 <- print("- Once desired dataset is selected, variable classes can be changed as needed.") + str5 <- print("  e.g., characters can be changed to factors if data is categorical.") + str_launch <- print("
Click on 'Launch Data Modal Window' to launch module window.") + HTML(paste(str1, str2, str3, str4, str5, str_launch, sep = '
')) + }) + + # Launch import data modal + observeEvent(input$launch_modal, { + req(input$from) + import_modal( + id = "myid", + from = input$from, + title = "Import data to be used in application" + ) + }) + data_imported_r <- import_server("myid", return_class = "tbl_df") + # data_imported_r <- datamods::import_server("import-data") + + data_rv <- reactiveValues(data = data.frame()) + observeEvent(data_imported_r$data(), { + data_rv$data <- data_imported_r$data() + data_rv$name <- data_imported_r$name() + }) + + # Output name as part of summary ---- + output$loaded <- renderUI({ + str_ld <- print("Loaded Dataset: ") + str_fn <- print(data_rv$name) # dataset/file name + HTML(paste(str_ld, str_fn, sep = "")) + }) + + + # Generate a summary of the dataset ---- + output$summary <- renderPrint({ + if(nrow(data_rv$data)==0){ + # do nothing + } + else{ + summary(data_rv$data) + } + # print(names(data_rv$data)) # variable names + }) + + # dataset contents header ---- + output$dtheader <- renderUI({ + HTML("Please note: activating a column filter cannot be reset to neutral at this time.") + }) + + # Show contents of dataset ---- + output$contents <- DT::renderDataTable({ + data_rv$data %>% + DT::datatable(options = list(pageLength=10), + rownames=FALSE) + }) + + # Header for Esquisse ---- + output$esqheader <- renderUI({ + HTML("Instructions: Drag and drop variables into aesthetic boxes below to generate plots.") + }) + + # Launch Esquisse ---- + esquisse_server(id = "esquisse", data_rv = data_rv, default_aes = reactive(input$aes)) + +} + +# Launch RShiny App +if (interactive()) + shinyApp(ui, server) \ No newline at end of file diff --git a/tutorials/solara/pokemon.py b/tutorials/solara/pokemon.py new file mode 100644 index 0000000..690a1fa --- /dev/null +++ b/tutorials/solara/pokemon.py @@ -0,0 +1,52 @@ +"""# Pokemon search +From: https://solara.dev/examples/general/pokemon_search + +This example shows to to dynamically fetch data and render the results in a table when done, and show an error on failure. + +It also shows an optional filter to narrow down the results. +""" + +import solara +from solara import use_fetch +from solara.alias import rv + +github_url = solara.util.github_url(__file__) + +json_url = "https://jherr-pokemon.s3.us-west-1.amazonaws.com/index.json" + + +@solara.component +def Page(): + data = use_fetch(json_url) + json = solara.use_json_load(data) + filter, set_filter = solara.use_state("") + + with solara.Div() as main: + if json.error: + solara.Error(f"Error {json.error}") + solara.Button("Retry", on_click=data.retry) + else: + if json.value: + solara.InputText(label="Filter pokemons by name", value=filter, on_value=set_filter, continuous_update=True) + pokemons = json.value + if filter: + pokemons = [k for k in pokemons if filter.lower() in k["name"].lower()] + if len(pokemons) == 0: + solara.Warning("No pokemons found, try a different filter") + else: + solara.Info(f"{len(pokemons)} pokemons found") + else: + solara.Info(f"{len(pokemons)} pokemons in total") + with solara.GridFixed(columns=4, align_items="end", justify_items="stretch"): + for pokemon in pokemons[:20]: + with solara.Div(): + name = pokemon["name"] + url = "https://jherr-pokemon.s3.us-west-1.amazonaws.com/" + pokemon["image"] + # TODO: how to do this with solara + rv.Img(src=url, contain=True, max_height="200px") + solara.Text(name) + else: + with solara.Div(): + solara.Text("Loading...") + rv.ProgressCircular(indeterminate=True, class_="solara-progress") + return main \ No newline at end of file diff --git a/tutorials/solara/solara_demo-simple-button.ipynb b/tutorials/solara/solara_demo-simple-button.ipynb new file mode 100644 index 0000000..7bab576 --- /dev/null +++ b/tutorials/solara/solara_demo-simple-button.ipynb @@ -0,0 +1,350 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Solara Tutorial Using Jupyter Notebooks" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Solara](https://solara.dev/) is an open-source library that makes it easy to build web apps from pure Python using ipywidgets and a React-likle API. Solara offers the ability to build large, complex apps while keeping code simple. See quickstart instructions [here](https://solara.dev/docs/quickstart). Solara apps work both inside Jupyter Notebooks and as a standalone web app.\n", + "\n", + "**This is an example tutorial on how to use Solara app inside Jupyter Notebooks.**\n", + "\n", + "**We recommend using VS Code when creating Solara apps.**" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Install Solara\n", + "If you are using a built-in module such as `python-data-science-0.1.8`, please install the `solara` package inside a Jupyter cell. If you are using your own Python environment created inside Notebooks Hub (see instructions [here](https://polusai.github.io/notebooks-hub/user/environments.html#creating-a-new-user-environment)), `solara` can be installed directly inside your environment using the terminal.\n", + "\n", + "For the purpose of this tutorial, please use the Jupyter cell below." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Defaulting to user installation because normal site-packages is not writeable\n", + "Collecting solara\n", + " Downloading solara-1.23.0-py2.py3-none-any.whl (14.8 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m14.8/14.8 MB\u001b[0m \u001b[31m63.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:01\u001b[0m\n", + "\u001b[?25hCollecting humanize\n", + " Downloading humanize-4.8.0-py3-none-any.whl (117 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m117.1/117.1 kB\u001b[0m \u001b[31m33.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hRequirement already satisfied: ipywidgets in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (7.7.0)\n", + "Collecting watchdog\n", + " Downloading watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl (82 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m82.1/82.1 kB\u001b[0m \u001b[31m23.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hCollecting reacton>=1.7.1\n", + " Downloading reacton-1.8.0-py2.py3-none-any.whl (107 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m107.1/107.1 kB\u001b[0m \u001b[31m29.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hCollecting rich-click\n", + " Downloading rich_click-1.7.1-py3-none-any.whl (32 kB)\n", + "Requirement already satisfied: click>=7.1.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (8.1.3)\n", + "Requirement already satisfied: requests in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (2.28.2)\n", + "Requirement already satisfied: jinja2 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (3.1.2)\n", + "Requirement already satisfied: cachetools in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (5.3.0)\n", + "Collecting ipyvue>=1.9.0\n", + " Downloading ipyvue-1.10.1-py2.py3-none-any.whl (2.7 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2.7/2.7 MB\u001b[0m \u001b[31m75.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m:00:01\u001b[0m\n", + "\u001b[?25hCollecting uvicorn\n", + " Downloading uvicorn-0.24.0.post1-py3-none-any.whl (59 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m59.7/59.7 kB\u001b[0m \u001b[31m17.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hRequirement already satisfied: ipykernel in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (6.16.0)\n", + "Requirement already satisfied: markupsafe in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (2.1.2)\n", + "Collecting ipyvuetify>=1.8.10\n", + " Downloading ipyvuetify-1.8.10-py2.py3-none-any.whl (11.7 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m11.7/11.7 MB\u001b[0m \u001b[31m75.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:01\u001b[0m\n", + "\u001b[?25hRequirement already satisfied: jupyter-client>=7.0.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (7.4.9)\n", + "Requirement already satisfied: websockets in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (10.4)\n", + "Requirement already satisfied: filelock in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (3.10.0)\n", + "Requirement already satisfied: markdown in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (3.4.1)\n", + "Collecting pymdown-extensions\n", + " Downloading pymdown_extensions-10.4-py3-none-any.whl (240 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m240.8/240.8 kB\u001b[0m \u001b[31m49.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hRequirement already satisfied: nbformat in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (5.8.0)\n", + "Requirement already satisfied: starlette in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (0.26.1)\n", + "Requirement already satisfied: ipython-genutils~=0.2.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipywidgets->solara) (0.2.0)\n", + "Requirement already satisfied: traitlets>=4.3.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipywidgets->solara) (5.9.0)\n", + "Requirement already satisfied: ipython>=4.0.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipywidgets->solara) (8.11.0)\n", + "Requirement already satisfied: jupyterlab-widgets>=1.0.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipywidgets->solara) (1.1.3)\n", + "Requirement already satisfied: widgetsnbextension~=3.6.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipywidgets->solara) (3.6.3)\n", + "Requirement already satisfied: matplotlib-inline>=0.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipykernel->solara) (0.1.6)\n", + "Requirement already satisfied: debugpy>=1.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipykernel->solara) (1.6.6)\n", + "Requirement already satisfied: packaging in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipykernel->solara) (23.0)\n", + "Requirement already satisfied: pyzmq>=17 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipykernel->solara) (25.0.2)\n", + "Requirement already satisfied: tornado>=6.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipykernel->solara) (6.2)\n", + "Requirement already satisfied: psutil in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipykernel->solara) (5.9.4)\n", + "Requirement already satisfied: nest-asyncio in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipykernel->solara) (1.5.6)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from jupyter-client>=7.0.0->solara) (2.8.2)\n", + "Requirement already satisfied: jupyter-core>=4.9.2 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from jupyter-client>=7.0.0->solara) (5.3.0)\n", + "Requirement already satisfied: entrypoints in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from jupyter-client>=7.0.0->solara) (0.4)\n", + "Requirement already satisfied: fastjsonschema in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbformat->solara) (2.16.3)\n", + "Requirement already satisfied: jsonschema>=2.6 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbformat->solara) (4.16.0)\n", + "Requirement already satisfied: typing-extensions>=4.1.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from reacton>=1.7.1->solara) (4.5.0)\n", + "Requirement already satisfied: importlib-metadata>=4.4 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from markdown->solara) (6.1.0)\n", + "Requirement already satisfied: pyyaml in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from pymdown-extensions->solara) (6.0)\n", + "Requirement already satisfied: idna<4,>=2.5 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from requests->solara) (3.4)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from requests->solara) (2022.12.7)\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from requests->solara) (2.1.1)\n", + "Requirement already satisfied: urllib3<1.27,>=1.21.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from requests->solara) (1.26.15)\n", + "Requirement already satisfied: rich>=10.7.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from rich-click->solara) (13.3.2)\n", + "Requirement already satisfied: anyio<5,>=3.4.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from starlette->solara) (3.6.2)\n", + "Collecting h11>=0.8\n", + " Downloading h11-0.14.0-py3-none-any.whl (58 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m58.3/58.3 kB\u001b[0m \u001b[31m17.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hRequirement already satisfied: sniffio>=1.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from anyio<5,>=3.4.0->starlette->solara) (1.3.0)\n", + "Requirement already satisfied: zipp>=0.5 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from importlib-metadata>=4.4->markdown->solara) (3.15.0)\n", + "Requirement already satisfied: stack-data in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipython>=4.0.0->ipywidgets->solara) (0.6.2)\n", + "Requirement already satisfied: pygments>=2.4.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipython>=4.0.0->ipywidgets->solara) (2.14.0)\n", + "Requirement already satisfied: backcall in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipython>=4.0.0->ipywidgets->solara) (0.2.0)\n", + "Requirement already satisfied: jedi>=0.16 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipython>=4.0.0->ipywidgets->solara) (0.18.2)\n", + "Requirement already satisfied: pickleshare in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipython>=4.0.0->ipywidgets->solara) (0.7.5)\n", + "Requirement already satisfied: decorator in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipython>=4.0.0->ipywidgets->solara) (5.1.1)\n", + "Requirement already satisfied: prompt-toolkit!=3.0.37,<3.1.0,>=3.0.30 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipython>=4.0.0->ipywidgets->solara) (3.0.38)\n", + "Requirement already satisfied: pexpect>4.3 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipython>=4.0.0->ipywidgets->solara) (4.8.0)\n", + "Requirement already satisfied: attrs>=17.4.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from jsonschema>=2.6->nbformat->solara) (22.2.0)\n", + "Requirement already satisfied: pyrsistent!=0.17.0,!=0.17.1,!=0.17.2,>=0.14.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from jsonschema>=2.6->nbformat->solara) (0.19.3)\n", + "Requirement already satisfied: platformdirs>=2.5 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from jupyter-core>=4.9.2->jupyter-client>=7.0.0->solara) (3.1.1)\n", + "Requirement already satisfied: six>=1.5 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from python-dateutil>=2.8.2->jupyter-client>=7.0.0->solara) (1.16.0)\n", + "Requirement already satisfied: markdown-it-py<3.0.0,>=2.2.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from rich>=10.7.0->rich-click->solara) (2.2.0)\n", + "Requirement already satisfied: notebook>=4.4.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from widgetsnbextension~=3.6.0->ipywidgets->solara) (6.5.2)\n", + "Requirement already satisfied: parso<0.9.0,>=0.8.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from jedi>=0.16->ipython>=4.0.0->ipywidgets->solara) (0.8.3)\n", + "Requirement already satisfied: mdurl~=0.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from markdown-it-py<3.0.0,>=2.2.0->rich>=10.7.0->rich-click->solara) (0.1.0)\n", + "Requirement already satisfied: terminado>=0.8.3 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (0.17.1)\n", + "Requirement already satisfied: prometheus-client in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (0.16.0)\n", + "Requirement already satisfied: nbclassic>=0.4.7 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (0.5.3)\n", + "Requirement already satisfied: Send2Trash>=1.8.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (1.8.0)\n", + "Requirement already satisfied: argon2-cffi in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (21.3.0)\n", + "Requirement already satisfied: nbconvert>=5 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (6.5.3)\n", + "Requirement already satisfied: ptyprocess>=0.5 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from pexpect>4.3->ipython>=4.0.0->ipywidgets->solara) (0.7.0)\n", + "Requirement already satisfied: wcwidth in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from prompt-toolkit!=3.0.37,<3.1.0,>=3.0.30->ipython>=4.0.0->ipywidgets->solara) (0.2.6)\n", + "Requirement already satisfied: pure-eval in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from stack-data->ipython>=4.0.0->ipywidgets->solara) (0.2.2)\n", + "Requirement already satisfied: asttokens>=2.1.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from stack-data->ipython>=4.0.0->ipywidgets->solara) (2.2.1)\n", + "Requirement already satisfied: executing>=1.2.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from stack-data->ipython>=4.0.0->ipywidgets->solara) (1.2.0)\n", + "Requirement already satisfied: jupyter-server>=1.8 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbclassic>=0.4.7->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (1.23.6)\n", + "Requirement already satisfied: notebook-shim>=0.1.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbclassic>=0.4.7->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (0.2.2)\n", + "Requirement already satisfied: lxml in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (4.9.2)\n", + "Requirement already satisfied: defusedxml in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (0.7.1)\n", + "Requirement already satisfied: beautifulsoup4 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (4.11.1)\n", + "Requirement already satisfied: jupyterlab-pygments in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (0.2.2)\n", + "Requirement already satisfied: tinycss2 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (1.2.1)\n", + "Requirement already satisfied: bleach in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (6.0.0)\n", + "Requirement already satisfied: nbclient>=0.5.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (0.5.13)\n", + "Requirement already satisfied: pandocfilters>=1.4.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (1.5.0)\n", + "Requirement already satisfied: mistune<2,>=0.8.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (0.8.4)\n", + "Requirement already satisfied: argon2-cffi-bindings in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from argon2-cffi->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (21.2.0)\n", + "Requirement already satisfied: websocket-client in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from jupyter-server>=1.8->nbclassic>=0.4.7->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (1.5.1)\n", + "Requirement already satisfied: cffi>=1.0.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from argon2-cffi-bindings->argon2-cffi->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (1.15.1)\n", + "Requirement already satisfied: soupsieve>1.2 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from beautifulsoup4->nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (2.3.2.post1)\n", + "Requirement already satisfied: webencodings in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from bleach->nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (0.5.1)\n", + "Requirement already satisfied: pycparser in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from cffi>=1.0.1->argon2-cffi-bindings->argon2-cffi->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (2.21)\n", + "Installing collected packages: watchdog, humanize, h11, uvicorn, rich-click, pymdown-extensions, reacton, ipyvue, ipyvuetify, solara\n", + "\u001b[33m WARNING: The script watchmedo is installed in '/home/jovyan/.local/bin' which is not on PATH.\n", + " Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.\u001b[0m\u001b[33m\n", + "\u001b[0m\u001b[33m WARNING: The script uvicorn is installed in '/home/jovyan/.local/bin' which is not on PATH.\n", + " Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.\u001b[0m\u001b[33m\n", + "\u001b[0m\u001b[33m WARNING: The script rich-click is installed in '/home/jovyan/.local/bin' which is not on PATH.\n", + " Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.\u001b[0m\u001b[33m\n", + "\u001b[0m\u001b[33m WARNING: The script solara is installed in '/home/jovyan/.local/bin' which is not on PATH.\n", + " Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.\u001b[0m\u001b[33m\n", + "\u001b[0mSuccessfully installed h11-0.14.0 humanize-4.8.0 ipyvue-1.10.1 ipyvuetify-1.8.10 pymdown-extensions-10.4 reacton-1.8.0 rich-click-1.7.1 solara-1.23.0 uvicorn-0.24.0.post1 watchdog-3.0.0\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip install solara" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After running the code cell above to install `solara`, restart the kernel. This can be completed by click the **↺ Restart** button located towards the top of VS Code. Next, run the following code cell to confirm that the `solara` package is accessible." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Name: solara\n", + "Version: 1.23.0\n", + "Summary: \n", + "Home-page: \n", + "Author: \n", + "Author-email: \"Maarten A. Breddels\" \n", + "License: The MIT License (MIT)\n", + " \n", + " Copyright (c) 2022 Maarten A. Breddels\n", + " \n", + " Permission is hereby granted, free of charge, to any person obtaining a copy\n", + " of this software and associated documentation files (the \"Software\"), to deal\n", + " in the Software without restriction, including without limitation the rights\n", + " to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n", + " copies of the Software, and to permit persons to whom the Software is\n", + " furnished to do so, subject to the following conditions:\n", + " \n", + " The above copyright notice and this permission notice shall be included in\n", + " all copies or substantial portions of the Software.\n", + " \n", + " THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", + " IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", + " FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n", + " AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", + " LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n", + " OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n", + " THE SOFTWARE.\n", + "Location: /home/jovyan/.local/lib/python3.9/site-packages\n", + "Requires: cachetools, click, filelock, humanize, ipykernel, ipyvue, ipyvuetify, ipywidgets, jinja2, jupyter-client, markdown, markupsafe, nbformat, pymdown-extensions, reacton, requests, rich-click, starlette, uvicorn, watchdog, websockets\n", + "Required-by: \n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip show solara" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create Solara App\n", + "You are now ready to create your app!\n", + "\n", + "The following cell is a simple example taken directly from Solara's [documentation](https://solara.dev/docs). The code below will generate a button that, when clicked, will increase in the count displayed." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, import any dependencies." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import solara" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, declare any reactive variables at the top level. Components using these variables will be re-executed when their values change." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "clicks = solara.reactive(0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, add components to your app. Users can create custom components without any special distinction from the built-in components provided by the framework. To create a component, define a Python function decorated with `@solara.component`. Single elements are taken as the component's main element. If multiple elements are created, they are automatically wrapped in a Column component. See more information [here](https://solara.dev/docs/fundamentals/components)." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "@solara.component\n", + "def Page():\n", + " def increase_clicks():\n", + " clicks.value += 1\n", + "\n", + " solara.Button(label=f\"Clicked {clicks} times\", on_click=increase_clicks)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following cell is necessary to render the Solara app inside Jupyter Notebooks. This can be taken out when launching `solara` as a web app, but it is necessary here.\n", + "\n", + "The next cell should generate a live output when the cell is run. The button is clickable and will display a click count that increases in real time." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "232fcaada9a84a3f8dc953ad9193c109", + "version_major": 2, + "version_minor": 0 + }, + "text/html": [ + "Cannot show widget. You probably want to rerun the code cell above (Click in the code cell, and press Shift+Enter +)." + ], + "text/plain": [ + "Cannot show ipywidgets in text" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# in the Jupyter notebook, uncomment the next line:\n", + "display(Page())" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "python-data-science-0.1.8", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorials/solara/solara_demo-word-limit.ipynb b/tutorials/solara/solara_demo-word-limit.ipynb new file mode 100644 index 0000000..42e727b --- /dev/null +++ b/tutorials/solara/solara_demo-word-limit.ipynb @@ -0,0 +1,352 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Solara Tutorial Using Jupyter Notebooks" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Solara](https://solara.dev/) is an open-source library that makes it easy to build web apps from pure Python using ipywidgets and a React-likle API. Solara offers the ability to build large, complex apps while keeping code simple. See quickstart instructions [here](https://solara.dev/docs/quickstart). Solara apps work both inside Jupyter Notebooks and as a standalone web app.\n", + "\n", + "**This is an example tutorial on how to use Solara app inside Jupyter Notebooks.**\n", + "\n", + "**We recommend using VS Code when creating Solara apps.**" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Install Solara\n", + "If you are using a built-in module such as `python-data-science-0.1.8`, please install the `solara` package inside a Jupyter cell. If you are using your own Python environment created inside Notebooks Hub (see instructions [here](https://polusai.github.io/notebooks-hub/user/environments.html#creating-a-new-user-environment)), `solara` can be installed directly inside your environment using the terminal.\n", + "\n", + "For the purpose of this tutorial, please use the Jupyter cell below." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Defaulting to user installation because normal site-packages is not writeable\n", + "Collecting solara\n", + " Using cached solara-1.23.0-py2.py3-none-any.whl (14.8 MB)\n", + "Requirement already satisfied: jinja2 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (3.1.2)\n", + "Collecting reacton>=1.7.1\n", + " Using cached reacton-1.8.0-py2.py3-none-any.whl (107 kB)\n", + "Requirement already satisfied: markdown in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (3.4.1)\n", + "Collecting pymdown-extensions\n", + " Using cached pymdown_extensions-10.4-py3-none-any.whl (240 kB)\n", + "Collecting rich-click\n", + " Using cached rich_click-1.7.1-py3-none-any.whl (32 kB)\n", + "Collecting humanize\n", + " Using cached humanize-4.8.0-py3-none-any.whl (117 kB)\n", + "Requirement already satisfied: cachetools in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (5.3.0)\n", + "Collecting ipyvue>=1.9.0\n", + " Using cached ipyvue-1.10.1-py2.py3-none-any.whl (2.7 MB)\n", + "Requirement already satisfied: nbformat in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (5.8.0)\n", + "Requirement already satisfied: jupyter-client>=7.0.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (7.4.9)\n", + "Requirement already satisfied: ipywidgets in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (7.7.0)\n", + "Collecting uvicorn\n", + " Using cached uvicorn-0.24.0.post1-py3-none-any.whl (59 kB)\n", + "Requirement already satisfied: markupsafe in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (2.1.2)\n", + "Requirement already satisfied: websockets in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (10.4)\n", + "Requirement already satisfied: requests in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (2.28.2)\n", + "Requirement already satisfied: starlette in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (0.26.1)\n", + "Collecting ipyvuetify>=1.8.10\n", + " Using cached ipyvuetify-1.8.10-py2.py3-none-any.whl (11.7 MB)\n", + "Collecting watchdog\n", + " Using cached watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl (82 kB)\n", + "Requirement already satisfied: click>=7.1.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (8.1.3)\n", + "Requirement already satisfied: ipykernel in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (6.16.0)\n", + "Requirement already satisfied: filelock in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from solara) (3.10.0)\n", + "Requirement already satisfied: widgetsnbextension~=3.6.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipywidgets->solara) (3.6.3)\n", + "Requirement already satisfied: jupyterlab-widgets>=1.0.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipywidgets->solara) (1.1.3)\n", + "Requirement already satisfied: traitlets>=4.3.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipywidgets->solara) (5.9.0)\n", + "Requirement already satisfied: ipython-genutils~=0.2.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipywidgets->solara) (0.2.0)\n", + "Requirement already satisfied: ipython>=4.0.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipywidgets->solara) (8.11.0)\n", + "Requirement already satisfied: debugpy>=1.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipykernel->solara) (1.6.6)\n", + "Requirement already satisfied: pyzmq>=17 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipykernel->solara) (25.0.2)\n", + "Requirement already satisfied: tornado>=6.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipykernel->solara) (6.2)\n", + "Requirement already satisfied: packaging in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipykernel->solara) (23.0)\n", + "Requirement already satisfied: matplotlib-inline>=0.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipykernel->solara) (0.1.6)\n", + "Requirement already satisfied: psutil in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipykernel->solara) (5.9.4)\n", + "Requirement already satisfied: nest-asyncio in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipykernel->solara) (1.5.6)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from jupyter-client>=7.0.0->solara) (2.8.2)\n", + "Requirement already satisfied: entrypoints in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from jupyter-client>=7.0.0->solara) (0.4)\n", + "Requirement already satisfied: jupyter-core>=4.9.2 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from jupyter-client>=7.0.0->solara) (5.3.0)\n", + "Requirement already satisfied: jsonschema>=2.6 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbformat->solara) (4.16.0)\n", + "Requirement already satisfied: fastjsonschema in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbformat->solara) (2.16.3)\n", + "Requirement already satisfied: typing-extensions>=4.1.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from reacton>=1.7.1->solara) (4.5.0)\n", + "Requirement already satisfied: importlib-metadata>=4.4 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from markdown->solara) (6.1.0)\n", + "Requirement already satisfied: pyyaml in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from pymdown-extensions->solara) (6.0)\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from requests->solara) (2.1.1)\n", + "Requirement already satisfied: idna<4,>=2.5 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from requests->solara) (3.4)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from requests->solara) (2022.12.7)\n", + "Requirement already satisfied: urllib3<1.27,>=1.21.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from requests->solara) (1.26.15)\n", + "Requirement already satisfied: rich>=10.7.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from rich-click->solara) (13.3.2)\n", + "Requirement already satisfied: anyio<5,>=3.4.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from starlette->solara) (3.6.2)\n", + "Collecting h11>=0.8\n", + " Using cached h11-0.14.0-py3-none-any.whl (58 kB)\n", + "Requirement already satisfied: sniffio>=1.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from anyio<5,>=3.4.0->starlette->solara) (1.3.0)\n", + "Requirement already satisfied: zipp>=0.5 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from importlib-metadata>=4.4->markdown->solara) (3.15.0)\n", + "Requirement already satisfied: jedi>=0.16 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipython>=4.0.0->ipywidgets->solara) (0.18.2)\n", + "Requirement already satisfied: pygments>=2.4.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipython>=4.0.0->ipywidgets->solara) (2.14.0)\n", + "Requirement already satisfied: pickleshare in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipython>=4.0.0->ipywidgets->solara) (0.7.5)\n", + "Requirement already satisfied: prompt-toolkit!=3.0.37,<3.1.0,>=3.0.30 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipython>=4.0.0->ipywidgets->solara) (3.0.38)\n", + "Requirement already satisfied: pexpect>4.3 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipython>=4.0.0->ipywidgets->solara) (4.8.0)\n", + "Requirement already satisfied: stack-data in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipython>=4.0.0->ipywidgets->solara) (0.6.2)\n", + "Requirement already satisfied: backcall in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipython>=4.0.0->ipywidgets->solara) (0.2.0)\n", + "Requirement already satisfied: decorator in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from ipython>=4.0.0->ipywidgets->solara) (5.1.1)\n", + "Requirement already satisfied: attrs>=17.4.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from jsonschema>=2.6->nbformat->solara) (22.2.0)\n", + "Requirement already satisfied: pyrsistent!=0.17.0,!=0.17.1,!=0.17.2,>=0.14.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from jsonschema>=2.6->nbformat->solara) (0.19.3)\n", + "Requirement already satisfied: platformdirs>=2.5 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from jupyter-core>=4.9.2->jupyter-client>=7.0.0->solara) (3.1.1)\n", + "Requirement already satisfied: six>=1.5 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from python-dateutil>=2.8.2->jupyter-client>=7.0.0->solara) (1.16.0)\n", + "Requirement already satisfied: markdown-it-py<3.0.0,>=2.2.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from rich>=10.7.0->rich-click->solara) (2.2.0)\n", + "Requirement already satisfied: notebook>=4.4.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from widgetsnbextension~=3.6.0->ipywidgets->solara) (6.5.2)\n", + "Requirement already satisfied: parso<0.9.0,>=0.8.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from jedi>=0.16->ipython>=4.0.0->ipywidgets->solara) (0.8.3)\n", + "Requirement already satisfied: mdurl~=0.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from markdown-it-py<3.0.0,>=2.2.0->rich>=10.7.0->rich-click->solara) (0.1.0)\n", + "Requirement already satisfied: prometheus-client in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (0.16.0)\n", + "Requirement already satisfied: Send2Trash>=1.8.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (1.8.0)\n", + "Requirement already satisfied: nbclassic>=0.4.7 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (0.5.3)\n", + "Requirement already satisfied: argon2-cffi in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (21.3.0)\n", + "Requirement already satisfied: nbconvert>=5 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (6.5.3)\n", + "Requirement already satisfied: terminado>=0.8.3 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (0.17.1)\n", + "Requirement already satisfied: ptyprocess>=0.5 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from pexpect>4.3->ipython>=4.0.0->ipywidgets->solara) (0.7.0)\n", + "Requirement already satisfied: wcwidth in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from prompt-toolkit!=3.0.37,<3.1.0,>=3.0.30->ipython>=4.0.0->ipywidgets->solara) (0.2.6)\n", + "Requirement already satisfied: executing>=1.2.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from stack-data->ipython>=4.0.0->ipywidgets->solara) (1.2.0)\n", + "Requirement already satisfied: pure-eval in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from stack-data->ipython>=4.0.0->ipywidgets->solara) (0.2.2)\n", + "Requirement already satisfied: asttokens>=2.1.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from stack-data->ipython>=4.0.0->ipywidgets->solara) (2.2.1)\n", + "Requirement already satisfied: notebook-shim>=0.1.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbclassic>=0.4.7->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (0.2.2)\n", + "Requirement already satisfied: jupyter-server>=1.8 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbclassic>=0.4.7->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (1.23.6)\n", + "Requirement already satisfied: pandocfilters>=1.4.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (1.5.0)\n", + "Requirement already satisfied: tinycss2 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (1.2.1)\n", + "Requirement already satisfied: nbclient>=0.5.0 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (0.5.13)\n", + "Requirement already satisfied: jupyterlab-pygments in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (0.2.2)\n", + "Requirement already satisfied: lxml in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (4.9.2)\n", + "Requirement already satisfied: beautifulsoup4 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (4.11.1)\n", + "Requirement already satisfied: bleach in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (6.0.0)\n", + "Requirement already satisfied: defusedxml in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (0.7.1)\n", + "Requirement already satisfied: mistune<2,>=0.8.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (0.8.4)\n", + "Requirement already satisfied: argon2-cffi-bindings in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from argon2-cffi->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (21.2.0)\n", + "Requirement already satisfied: websocket-client in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from jupyter-server>=1.8->nbclassic>=0.4.7->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (1.5.1)\n", + "Requirement already satisfied: cffi>=1.0.1 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from argon2-cffi-bindings->argon2-cffi->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (1.15.1)\n", + "Requirement already satisfied: soupsieve>1.2 in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from beautifulsoup4->nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (2.3.2.post1)\n", + "Requirement already satisfied: webencodings in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from bleach->nbconvert>=5->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (0.5.1)\n", + "Requirement already satisfied: pycparser in /opt/modules/shared/conda-envs/python-data-science-0.1.8/lib/python3.9/site-packages (from cffi>=1.0.1->argon2-cffi-bindings->argon2-cffi->notebook>=4.4.1->widgetsnbextension~=3.6.0->ipywidgets->solara) (2.21)\n", + "Installing collected packages: watchdog, humanize, h11, uvicorn, rich-click, pymdown-extensions, reacton, ipyvue, ipyvuetify, solara\n", + "\u001b[33m WARNING: The script watchmedo is installed in '/home/jovyan/.local/bin' which is not on PATH.\n", + " Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.\u001b[0m\u001b[33m\n", + "\u001b[0m\u001b[33m WARNING: The script uvicorn is installed in '/home/jovyan/.local/bin' which is not on PATH.\n", + " Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.\u001b[0m\u001b[33m\n", + "\u001b[0m\u001b[33m WARNING: The script rich-click is installed in '/home/jovyan/.local/bin' which is not on PATH.\n", + " Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.\u001b[0m\u001b[33m\n", + "\u001b[0m\u001b[33m WARNING: The script solara is installed in '/home/jovyan/.local/bin' which is not on PATH.\n", + " Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.\u001b[0m\u001b[33m\n", + "\u001b[0mSuccessfully installed h11-0.14.0 humanize-4.8.0 ipyvue-1.10.1 ipyvuetify-1.8.10 pymdown-extensions-10.4 reacton-1.8.0 rich-click-1.7.1 solara-1.23.0 uvicorn-0.24.0.post1 watchdog-3.0.0\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip install solara" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After running the code cell above to install `solara`, restart the kernel. This can be completed by click the **↺ Restart** button located towards the top of VS Code. Next, run the following code cell to confirm that the `solara` package is accessible." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Name: solara\n", + "Version: 1.23.0\n", + "Summary: \n", + "Home-page: \n", + "Author: \n", + "Author-email: \"Maarten A. Breddels\" \n", + "License: The MIT License (MIT)\n", + " \n", + " Copyright (c) 2022 Maarten A. Breddels\n", + " \n", + " Permission is hereby granted, free of charge, to any person obtaining a copy\n", + " of this software and associated documentation files (the \"Software\"), to deal\n", + " in the Software without restriction, including without limitation the rights\n", + " to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n", + " copies of the Software, and to permit persons to whom the Software is\n", + " furnished to do so, subject to the following conditions:\n", + " \n", + " The above copyright notice and this permission notice shall be included in\n", + " all copies or substantial portions of the Software.\n", + " \n", + " THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", + " IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", + " FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n", + " AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", + " LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n", + " OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n", + " THE SOFTWARE.\n", + "Location: /home/jovyan/.local/lib/python3.9/site-packages\n", + "Requires: cachetools, click, filelock, humanize, ipykernel, ipyvue, ipyvuetify, ipywidgets, jinja2, jupyter-client, markdown, markupsafe, nbformat, pymdown-extensions, reacton, requests, rich-click, starlette, uvicorn, watchdog, websockets\n", + "Required-by: \n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip show solara" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create Solara App\n", + "You are now ready to create your app!\n", + "\n", + "The following cell is a simple example taken directly from Solara's Quickstart [page](https://solara.dev/docs/quickstart). The code cells below will generate a slider widget and text input widget. The slider widget will assign a maximum number of words allowed in the text input widget. Depending on the total number of input words relative to the limit set using the slider, an appropriate message will appear." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, import any dependencies." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import solara" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, declare any reactive variables at the top level. Components using these variables will be re-executed when their values change." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "sentence = solara.reactive(\"Solara makes our team more productive.\")\n", + "word_limit = solara.reactive(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, add components to your app. Users can create custom components without any special distinction from the built-in components provided by the framework. To create a component, define a Python function decorated with `@solara.component`. Single elements are taken as the component's main element. If multiple elements are created, they are automatically wrapped in a Column component. See more information [here](https://solara.dev/docs/fundamentals/components)." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "@solara.component\n", + "def Page():\n", + " # Calculate word_count within the component to ensure re-execution when reactive variables change.\n", + " word_count = len(sentence.value.split())\n", + "\n", + " solara.SliderInt(\"Word limit\", value=word_limit, min=2, max=20)\n", + " solara.InputText(label=\"Your sentence\", value=sentence, continuous_update=True)\n", + "\n", + " # Display messages based on the current word count and word limit.\n", + " if word_count >= int(word_limit.value):\n", + " solara.Error(f\"With {word_count} words, you passed the word limit of {word_limit.value}.\")\n", + " elif word_count >= int(0.8 * word_limit.value):\n", + " solara.Warning(f\"With {word_count} words, you are close to the word limit of {word_limit.value}.\")\n", + " else:\n", + " solara.Success(\"Great short writing!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following cell is necessary to render the Solara app inside Jupyter Notebooks. This can be taken out when launching `solara` as a web app, but it is necessary here.\n", + "\n", + "The next cell should generate a live output when the cell is run. The button is clickable and will display a click count that increases in real time." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "aef9390dfbc34b6bbdbfd4e8d61f8ce2", + "version_major": 2, + "version_minor": 0 + }, + "text/html": [ + "Cannot show widget. You probably want to rerun the code cell above (Click in the code cell, and press Shift+Enter +)." + ], + "text/plain": [ + "Cannot show ipywidgets in text" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# When running code inside Jupyter notebook, use either display(Page()) or Page()\n", + "\n", + "display(Page())" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "python-data-science-0.1.8", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorials/solara/todolist.py b/tutorials/solara/todolist.py new file mode 100644 index 0000000..9a7fd72 --- /dev/null +++ b/tutorials/solara/todolist.py @@ -0,0 +1,195 @@ +"""# Todo application + +Demonstrates the use of reactive variables in combinations with dataclasses. + +With solara we can get a type safe view onto a field in a dataclass, pydantic model, or +attr object. + +This is using the experimental `solara.lab.Ref` class, which is not yet part of the +official API. + + +```python +import dataclasses +import solara +from solara.lab import Ref + +@dataclasses.dataclass(frozen=True) +class TodoItem: + text: str + done: bool + +todo_item = solara.reactive(TodoItem("Buy milk", False)) + +# now text is a reactive variable that is always in sync with todo_item.text +text = Ref(todo_item.fields.text) + + +# we can now modify the reactive text variable +# and see its value reflect in the todo_item +text.value = "Buy skimmed milk" +assert todo_item.value.text == "Buy skimmed milk" + +# Or, the other way around +todo_item.value = TodoItem("Buy whole milk", False) +assert text.value == "Buy whole milk" +``` + +Apart from dataclasses, pydantic models etc, we also supports dictionaries and lists. + +```python +todo_items = solara.reactive([TodoItem("Buy milk", False), TodoItem("Buy eggs", False)]) +todo_item_eggs = Ref(todo_items.fields[1]) +todo_item_eggs.value = TodoItem("Buy eggs", True) +assert todo_items.value[1].done == True + +# However, if a list becomes shorter, and the valid index is now out of range, the +# reactive variables will act as if it is "not connected", and will not trigger +# updates anymore. Accessing the value will raise an IndexError. + +todo_items.value = [TodoItem("Buy milk", False)] +# anyone listening to todo_item_eggs will *not* be notified. +try: + value = todo_item_eggs.value +except IndexError: + print("this is expected") +else: + raise AssertionError("Expected an IndexError") +``` + +""" +import dataclasses +from typing import Callable + +import reacton.ipyvuetify as v + +import solara +from solara.lab.toestand import Ref + + +# our model for a todo item, immutable/frozen avoids common bugs +@dataclasses.dataclass(frozen=True) +class TodoItem: + text: str + done: bool + + +@solara.component +def TodoEdit(todo_item: solara.Reactive[TodoItem], on_delete: Callable[[], None], on_close: Callable[[], None]): + """Takes a reactive todo item and allows editing it. Will not modify the original item until 'save' is clicked.""" + copy = solara.use_reactive(todo_item.value) + + def save(): + todo_item.value = copy.value + on_close() + + with solara.Card("Edit", margin=0): + solara.InputText(label="", value=Ref(copy.fields.text)) + with solara.CardActions(): + v.Spacer() + solara.Button("Save", icon_name="mdi-content-save", on_click=save, outlined=True, text=True) + solara.Button("Close", icon_name="mdi-window-close", on_click=on_close, outlined=True, text=True) + solara.Button("Delete", icon_name="mdi-delete", on_click=on_delete, outlined=True, text=True) + + +@solara.component +def TodoListItem(todo_item: solara.Reactive[TodoItem], on_delete: Callable[[TodoItem], None]): + """Displays a single todo item, modifications are done 'in place'. + + For demonstration purposes, we allow editing the item in a dialog as well. + This will not modify the original item until 'save' is clicked. + """ + edit, set_edit = solara.use_state(False) + with v.ListItem(): + solara.Button(icon_name="mdi-delete", icon=True, on_click=lambda: on_delete(todo_item.value)) + solara.Checkbox(value=Ref(todo_item.fields.done)) # , color="success") + solara.InputText(label="", value=Ref(todo_item.fields.text)) + solara.Button(icon_name="mdi-pencil", icon=True, on_click=lambda: set_edit(True)) + with v.Dialog(v_model=edit, persistent=True, max_width="500px", on_v_model=set_edit): + if edit: # 'reset' the component state on open/close + + def on_delete_in_edit(): + on_delete(todo_item.value) + set_edit(False) + + TodoEdit(todo_item, on_delete=on_delete_in_edit, on_close=lambda: set_edit(False)) + + +@solara.component +def TodoNew(on_new: Callable[[TodoItem], None]): + """Component that managed entering new todo items""" + new_text, set_new_text = solara.use_state("") + text_field = v.TextField(v_model=new_text, on_v_model=set_new_text, label="Enter a new todo item") + + def create_new_item(*ignore_args): + if not new_text: + return + new_item = TodoItem(text=new_text, done=False) + on_new(new_item) + # reset text + set_new_text("") + + v.use_event(text_field, "keydown.enter", create_new_item) + return text_field + + +initial_items = [ + TodoItem("Learn Solara", done=True), + TodoItem("Write cool apps", done=False), + TodoItem("Relax", done=False), +] + + +# We store out reactive state, and our logic in a class for organization +# purposes, but this is not required. +# Note that all the above components do not refer to this class, but only +# to do the Todo items. +# This means all above components are reusable, and can be used in other +# places, while the components below use 'application'/'global' state. +# They are not suited for reuse. + + +class State: + todos = solara.reactive(initial_items) + + @staticmethod + def on_new(item: TodoItem): + State.todos.value = [item] + State.todos.value + + @staticmethod + def on_delete(item: TodoItem): + new_items = list(State.todos.value) + new_items.remove(item) + State.todos.value = new_items + + +@solara.component +def TodoStatus(): + """Status of our todo list""" + items = State.todos.value + count = len(items) + items_done = [item for item in items if item.done] + count_done = len(items_done) + + if count != count_done: + with solara.Row(): + percent = count_done / count * 100 + solara.ProgressLinear(value=percent) + with solara.Row(): + solara.Text(f"Remaining: {count - count_done}") + solara.Text(f"Completed: {count_done}") + else: + solara.Success("All done, awesome!", dense=True) + + +@solara.component +def Page(): + with solara.Card("Todo list", style="min-width: 500px"): + TodoNew(on_new=State.on_new) + if State.todos.value: + TodoStatus() + for index, item in enumerate(State.todos.value): + todo_item = Ref(State.todos.fields[index]) + TodoListItem(todo_item, on_delete=State.on_delete) + else: + solara.Info("No todo items, enter some text above, and hit enter") \ No newline at end of file diff --git a/tutorials/streamlit/COVID_app.py b/tutorials/streamlit/COVID_app.py new file mode 100644 index 0000000..0832d89 --- /dev/null +++ b/tutorials/streamlit/COVID_app.py @@ -0,0 +1,630 @@ +import subprocess +pwd = subprocess.run(['pwd']) +subprocess.run(['mkdir','-p', 'COVID']) +pwd2 = f"{pwd}/COVID" +setupsh = """ + "\ + [server]\n\ + headless = true\n\ + port = $PORT\n\ + enableCORS = false\n\ + \n\ + " > pwd2/config.toml + """ +subprocess.run(['echo',setupsh]) + +import json +from datetime import date +from urllib.request import urlopen +import time + +import altair as alt +import numpy as np +import pandas as pd +import requests +import streamlit as st +import streamlit.components.v1 as components +from pandas import json_normalize + +_ENABLE_PROFILING = False + +if _ENABLE_PROFILING: + import cProfile, pstats, io + from pstats import SortKey + pr = cProfile.Profile() + pr.enable() + +today = date.today() + +st.set_page_config( + page_title="COVID19: EpiCenter for Disease Dynamics", + layout='wide', + initial_sidebar_state='auto', +) + +sidebar_selection = st.sidebar.radio( + 'Select data:', + ['Select Counties', 'California'], +) + +#@st.cache(ttl=3*60*60, suppress_st_warning=True) +@st.cache_data(ttl=3*60*60) +def get_data(): + US_confirmed = 'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_US.csv' + US_deaths = 'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_deaths_US.csv' + confirmed = pd.read_csv(US_confirmed) + deaths = pd.read_csv(US_deaths) + return confirmed, deaths + +confirmed, deaths = get_data() +FIPSs = confirmed.groupby(['Province_State', 'Admin2']).FIPS.unique().apply(pd.Series).reset_index() +FIPSs.columns = ['State', 'County', 'FIPS'] +FIPSs['FIPS'].fillna(0, inplace = True) +FIPSs['FIPS'] = FIPSs.FIPS.astype(int).astype(str).str.zfill(5) + +#@st.cache(ttl=3*60*60, suppress_st_warning=True) +@st.cache_data(ttl=3*60*60) +def get_testing_data(County): + apiKey = '9fe19182c5bf4d1bb105da08e593a578' + if len(County) == 1: + #print(len(County)) + f = FIPSs[FIPSs.County == County[0]].FIPS.values[0] + #print(f) + path1 = 'https://data.covidactnow.org/latest/us/counties/'+f+'.OBSERVED_INTERVENTION.timeseries.json?apiKey='+apiKey + #print(path1) + df = json.loads(requests.get(path1).text) + #print(df.keys()) + data = pd.DataFrame.from_dict(df['actualsTimeseries']) + data['Date'] = pd.to_datetime(data['date']) + data = data.set_index('Date') + #print(data.tail()) + try: + data['new_negative_tests'] = data['cumulativeNegativeTests'].diff() + data.loc[(data['new_negative_tests'] < 0)] = np.nan + except: + data['new_negative_tests'] = np.nan + st.text('Negative test data not avilable') + data['new_negative_tests_rolling'] = data['new_negative_tests'].fillna(0).rolling(14).mean() + + + try: + data['new_positive_tests'] = data['cumulativePositiveTests'].diff() + data.loc[(data['new_positive_tests'] < 0)] = np.nan + except: + data['new_positive_tests'] = np.nan + st.text('test data not avilable') + data['new_positive_tests_rolling'] = data['new_positive_tests'].fillna(0).rolling(14).mean() + data['new_tests'] = data['new_negative_tests']+data['new_positive_tests'] + data['new_tests_rolling'] = data['new_tests'].fillna(0).rolling(14).mean() + data['testing_positivity_rolling'] = (data['new_positive_tests_rolling'] / data['new_tests_rolling'])*100 + #data['testing_positivity_rolling'].tail(14).plot() + #plt.show() + return data['new_tests_rolling'], data['testing_positivity_rolling'].iloc[-1:].values[0] + elif (len(County) > 1) & (len(County) < 5): + new_positive_tests = [] + new_negative_tests = [] + new_tests = [] + for c in County: + f = FIPSs[FIPSs.County == c].FIPS.values[0] + path1 = 'https://data.covidactnow.org/latest/us/counties/'+f+'.OBSERVED_INTERVENTION.timeseries.json?apiKey='+apiKey + df = json.loads(requests.get(path1).text) + data = pd.DataFrame.from_dict(df['actualsTimeseries']) + data['Date'] = pd.to_datetime(data['date']) + data = data.set_index('Date') + try: + data['new_negative_tests'] = data['cumulativeNegativeTests'].diff() + data.loc[(data['new_negative_tests'] < 0)] = np.nan + except: + data['new_negative_tests'] = np.nan + #print('Negative test data not avilable') + + try: + data['new_positive_tests'] = data['cumulativePositiveTests'].diff() + data.loc[(data['new_positive_tests'] < 0)] = np.nan + except: + data['new_positive_tests'] = np.nan + #print('Negative test data not avilable') + data['new_tests'] = data['new_negative_tests']+data['new_positive_tests'] + + new_positive_tests.append(data['new_positive_tests']) + #new_negative_tests.append(data['new_tests']) + new_tests.append(data['new_tests']) + #print(data.head()) + + new_positive_tests_rolling = pd.concat(new_positive_tests, axis = 1).sum(axis = 1) + new_positive_tests_rolling = new_positive_tests_rolling.fillna(0).rolling(14).mean() + #print('new test merging of counties') + #print(pd.concat(new_tests, axis = 1).head().sum(axis = 1)) + new_tests_rolling = pd.concat(new_tests, axis = 1).sum(axis = 1) + new_tests_rolling = new_tests_rolling.fillna(0).rolling(14).mean() + new_tests_rolling = pd.DataFrame(new_tests_rolling).fillna(0) + new_tests_rolling.columns = ['new_tests_rolling'] + #print('whole df') + #print(type(new_tests_rolling)) + #print(new_tests_rolling.head()) + #print('single column') + #print(new_tests_rolling['new_tests_rolling'].head()) + #print('new_positive_tests_rolling') + #print(new_positive_tests_rolling.head()) + #print('new_tests_rolling') + #print(new_tests_rolling.head()) + data_to_show = (new_positive_tests_rolling / new_tests_rolling.new_tests_rolling)*100 + #print(data_to_show.shape) + #print(data_to_show.head()) + #print(data_to_show.columns) + #print(data_to_show.iloc[-1:].values[0]) + return new_tests_rolling, data_to_show.iloc[-1:].values[0] + else: + st.text('Getting testing data for California State') + path1 = 'https://data.covidactnow.org/latest/us/states/CA.OBSERVED_INTERVENTION.timeseries.json' + df = json.loads(requests.get(path1).text) + data = pd.DataFrame.from_dict(df['actualsTimeseries']) + data['Date'] = pd.to_datetime(data['date']) + data = data.set_index('Date') + + try: + data['new_negative_tests'] = data['cumulativeNegativeTests'].diff() + data.loc[(data['new_negative_tests'] < 0)] = np.nan + except: + data['new_negative_tests'] = np.nan + print('Negative test data not available') + data['new_negative_tests_rolling'] = data['new_negative_tests'].fillna(0).rolling(14).mean() + + + try: + data['new_positive_tests'] = data['cumulativePositiveTests'].diff() + data.loc[(data['new_positive_tests'] < 0)] = np.nan + except: + data['new_positive_tests'] = np.nan + st.text('test data not available') + data['new_positive_tests_rolling'] = data['new_positive_tests'].fillna(0).rolling(14).mean() + data['new_tests'] = data['new_negative_tests']+data['new_positive_tests'] + data['new_tests_rolling'] = data['new_tests'].fillna(0).rolling(14).mean() + data['testing_positivity_rolling'] = (data['new_positive_tests_rolling'] / data['new_tests_rolling'])*100 + return data['new_tests_rolling'], data['testing_positivity_rolling'].iloc[-1:].values[0] + + +def plot_county(county): + testing_df, testing_percent = get_testing_data(County=county) + #print(testing_df.head()) + county_confirmed = confirmed[confirmed.Admin2.isin(county)] + county_confirmed_time = county_confirmed.drop(county_confirmed.iloc[:, 0:12], axis=1).T + county_confirmed_time = county_confirmed_time.sum(axis= 1) + county_confirmed_time = county_confirmed_time.reset_index() + county_confirmed_time.columns = ['date', 'cases'] + county_confirmed_time['Datetime'] = pd.to_datetime(county_confirmed_time['date']) + county_confirmed_time = county_confirmed_time.set_index('Datetime') + del county_confirmed_time['date'] + incidence= pd.DataFrame(county_confirmed_time.cases.diff()) + incidence.columns = ['incidence'] + chart_max = incidence.max().values[0]+500 + + county_deaths = deaths[deaths.Admin2.isin(county)] + population = county_deaths.Population.values.sum() + + del county_deaths['Population'] + county_deaths_time = county_deaths.drop(county_deaths.iloc[:, 0:11], axis=1).T + county_deaths_time = county_deaths_time.sum(axis= 1) + + county_deaths_time = county_deaths_time.reset_index() + county_deaths_time.columns = ['date', 'deaths'] + county_deaths_time['Datetime'] = pd.to_datetime(county_deaths_time['date']) + county_deaths_time = county_deaths_time.set_index('Datetime') + del county_deaths_time['date'] + + cases_per100k = ((county_confirmed_time) * 100000 / population) + cases_per100k.columns = ['cases per 100K'] + cases_per100k['rolling average'] = cases_per100k['cases per 100K'].rolling(7).mean() + + deaths_per100k = ((county_deaths_time) * 100000 / population) + deaths_per100k.columns = ['deaths per 100K'] + deaths_per100k['rolling average'] = deaths_per100k['deaths per 100K'].rolling(7).mean() + + + incidence['rolling_incidence'] = incidence.incidence.rolling(7).mean() + metric = (incidence['rolling_incidence'] * 100000 / population).iloc[[-1]] + + if len(county) == 1: + st.subheader('Current situation of COVID-19 cases in '+', '.join(map(str, county))+' county ('+ str(today)+')') + else: + st.subheader('Current situation of COVID-19 cases in '+', '.join(map(str, county))+' counties ('+ str(today)+')') + + c1 = st.container() + c2 = st.container() + c3 = st.container() + + if len(county)==1: + C = county[0] + with c2: + a1, _, a2 = st.columns((3.9, 0.2, 3.9)) + with a1: + f = FIPSs[FIPSs.County == C].FIPS.values[0] + components.iframe("https://covidactnow.org/embed/us/county/"+f, width=350, height=365, scrolling=False) + + with a2: + st.markdown('New cases averaged over last 7 days = %s' %'{:,.1f}'.format(metric.values[0])) + st.markdown("Population under consideration = %s"% '{:,.0f}'.format(population)) + st.markdown("Total cases = %s"% '{:,.0f}'.format(county_confirmed_time.tail(1).values[0][0])) + st.markdown("Total deaths = %s"% '{:,.0f}'.format(county_deaths_time.tail(1).values[0][0])) + st.markdown("% test positivity (14 day average)* = "+"%.2f" % testing_percent) + elif len(county) <= 3: + with c2: + st.write('') + st.write('') + st.markdown("New cases averaged over last 7 days = %s" % "{:,.1f}".format(metric.values[0])) + st.markdown("Population under consideration = %s"% '{:,.0f}'.format(population)) + st.markdown("Total cases = %s"% '{:,.0f}'.format(county_confirmed_time.tail(1).values[0][0])) + st.markdown("Total deaths = %s"% '{:,.0f}'.format(county_deaths_time.tail(1).values[0][0])) + st.markdown("% test positivity (14 day average)* = "+"%.2f" % testing_percent) + with c3: + columns = st.columns(len(county)) + for idx, C in enumerate(county): + with columns[idx]: + st.write('') + st.write('') + f = FIPSs[FIPSs.County == C].FIPS.values[0] + components.iframe("https://covidactnow.org/embed/us/county/"+f, width=350, height=365, scrolling=False) + + ### Experiment with Altair instead of Matplotlib. + with c1: + a2, _, a1 = st.columns((3.9, 0.2, 3.9)) + + incidence = incidence.reset_index() + incidence['nomalized_rolling_incidence'] = incidence['rolling_incidence'] * 100000 / population + incidence['Phase 2 Threshold'] = 25 + incidence['Phase 3 Threshold'] = 10 + scale = alt.Scale( + domain=[ + "rolling_incidence", + "Phase 2 Threshold", + "Phase 3 Threshold" + ], range=['#377eb8', '#e41a1c', '#4daf4a']) + base = alt.Chart( + incidence, + title='(A) Weekly rolling mean of incidence per 100K' + ).transform_calculate( + base_="'rolling_incidence'", + phase2_="'Phase 2 Threshold'", + phase3_="'Phase 3 Threshold'", + ) + + ax4 = base.mark_line(strokeWidth=3).encode( + x=alt.X("Datetime", axis = alt.Axis(title='Date')), + y=alt.Y("nomalized_rolling_incidence", axis=alt.Axis(title='per 100 thousand')), + color=alt.Color("base_:N", scale=scale, title="") + ) + + line1 = base.mark_line(strokeDash=[8, 8], strokeWidth=2).encode( + x=alt.X("Datetime", axis=alt.Axis(title = 'Date')), + y=alt.Y("Phase 2 Threshold", axis=alt.Axis(title='Count')), + color=alt.Color("phase2_:N", scale=scale, title="") + ) + + line2 = base.mark_line(strokeDash=[8, 8], strokeWidth=2).encode( + x=alt.X("Datetime", axis=alt.Axis(title='Date')), + y=alt.Y("Phase 3 Threshold", axis=alt.Axis(title='Count')), + color=alt.Color("phase3_:N", scale=scale, title="") + ) + + with a2: + st.altair_chart(ax4 + line1 + line2, use_container_width=True) + + ax3 = alt.Chart(incidence, title = '(B) Daily incidence (new cases)').mark_bar().encode( + x=alt.X("Datetime",axis = alt.Axis(title = 'Date')), + y=alt.Y("incidence",axis = alt.Axis(title = 'Incidence'), scale=alt.Scale(domain=(0, chart_max), clamp=True)) + ) + + with a1: + st.altair_chart(ax3, use_container_width=True) + + a3, _, a4 = st.columns((3.9, 0.2, 3.9)) + testing_df = pd.DataFrame(testing_df).reset_index() + #print(testing_df.head()) + #print(type(testing_df)) + + base = alt.Chart(testing_df, title = '(D) Daily new tests').mark_line(strokeWidth=3).encode( + x=alt.X("Date",axis = alt.Axis(title = 'Date')), + y=alt.Y("new_tests_rolling",axis = alt.Axis(title = 'Daily new tests')) + ) + with a4: + st.altair_chart(base, use_container_width=True) + + county_confirmed_time = county_confirmed_time.reset_index() + county_deaths_time = county_deaths_time.reset_index() + cases_and_deaths = county_confirmed_time.set_index("Datetime").join(county_deaths_time.set_index("Datetime")) + cases_and_deaths = cases_and_deaths.reset_index() + + # Custom colors for layered charts. + # See https://stackoverflow.com/questions/61543503/add-legend-to-line-bars-to-altair-chart-without-using-size-color. + scale = alt.Scale(domain=["cases", "deaths"], range=['#377eb8', '#e41a1c']) + base = alt.Chart( + cases_and_deaths, + title='(C) Cumulative cases and deaths' + ).transform_calculate( + cases_="'cases'", + deaths_="'deaths'", + ) + + c = base.mark_line(strokeWidth=3).encode( + x=alt.X("Datetime", axis=alt.Axis(title = 'Date')), + y=alt.Y("cases", axis=alt.Axis(title = 'Count')), + color=alt.Color("cases_:N", scale=scale, title="") + ) + + d = base.mark_line(strokeWidth=3).encode( + x=alt.X("Datetime", axis=alt.Axis(title='Date')), + y=alt.Y("deaths", axis=alt.Axis(title = 'Count')), + color=alt.Color("deaths_:N", scale=scale, title="") + ) + with a3: + st.altair_chart(c+d, use_container_width=True) + + +def plot_state(): + #@st.cache_data(ttl=3*60*60, suppress_st_warning=True) + @st.cache_data(ttl=3*60*60) + def get_testing_data_state(): + st.text('Getting testing data for California State') + path1 = 'https://data.covidactnow.org/latest/us/states/CA.OBSERVED_INTERVENTION.timeseries.json' + df = json.loads(requests.get(path1).text) + data = pd.DataFrame.from_dict(df['actualsTimeseries']) + data['Date'] = pd.to_datetime(data['date']) + data = data.set_index('Date') + + try: + data['new_negative_tests'] = data['cumulativeNegativeTests'].diff() + data.loc[(data['new_negative_tests'] < 0)] = np.nan + except: + data['new_negative_tests'] = np.nan + print('Negative test data not available') + data['new_negative_tests_rolling'] = data['new_negative_tests'].fillna(0).rolling(14).mean() + + + try: + data['new_positive_tests'] = data['cumulativePositiveTests'].diff() + data.loc[(data['new_positive_tests'] < 0)] = np.nan + except: + data['new_positive_tests'] = np.nan + st.text('test data not available') + data['new_positive_tests_rolling'] = data['new_positive_tests'].fillna(0).rolling(14).mean() + data['new_tests'] = data['new_negative_tests']+data['new_positive_tests'] + data['new_tests_rolling'] = data['new_tests'].fillna(0).rolling(14).mean() + data['testing_positivity_rolling'] = (data['new_positive_tests_rolling'] / data['new_tests_rolling'])*100 + # return data['new_tests_rolling'], data['testing_positivity_rolling'].iloc[-1:].values[0] + testing_df, testing_percent = data['new_tests_rolling'], data['testing_positivity_rolling'].iloc[-1:].values[0] + county_confirmed = confirmed[confirmed.Province_State == 'California'] + #county_confirmed = confirmed[confirmed.Admin2 == county] + county_confirmed_time = county_confirmed.drop(county_confirmed.iloc[:, 0:12], axis=1).T #inplace=True, axis=1 + county_confirmed_time = county_confirmed_time.sum(axis= 1) + county_confirmed_time = county_confirmed_time.reset_index() + county_confirmed_time.columns = ['date', 'cases'] + county_confirmed_time['Datetime'] = pd.to_datetime(county_confirmed_time['date']) + county_confirmed_time = county_confirmed_time.set_index('Datetime') + del county_confirmed_time['date'] + #print(county_confirmed_time.head()) + incidence = pd.DataFrame(county_confirmed_time.cases.diff()) + incidence.columns = ['incidence'] + + #temp_df_time = temp_df.drop(['date'], axis=0).T #inplace=True, axis=1 + county_deaths = deaths[deaths.Province_State == 'California'] + population = county_deaths.Population.values.sum() + + del county_deaths['Population'] + county_deaths_time = county_deaths.drop(county_deaths.iloc[:, 0:11], axis=1).T #inplace=True, axis=1 + county_deaths_time = county_deaths_time.sum(axis= 1) + + county_deaths_time = county_deaths_time.reset_index() + county_deaths_time.columns = ['date', 'deaths'] + county_deaths_time['Datetime'] = pd.to_datetime(county_deaths_time['date']) + county_deaths_time = county_deaths_time.set_index('Datetime') + del county_deaths_time['date'] + + cases_per100k = ((county_confirmed_time)*100000/population) + cases_per100k.columns = ['cases per 100K'] + cases_per100k['rolling average'] = cases_per100k['cases per 100K'].rolling(7).mean() + + deaths_per100k = ((county_deaths_time)*100000/population) + deaths_per100k.columns = ['deaths per 100K'] + deaths_per100k['rolling average'] = deaths_per100k['deaths per 100K'].rolling(7).mean() + + incidence['rolling_incidence'] = incidence.incidence.rolling(7).mean() + return population, testing_df, testing_percent, county_deaths_time, county_confirmed_time, incidence + # metric = (incidence['rolling_incidence']*100000/population).iloc[[-1]] + + #print(county_deaths_time.tail(1).values[0]) + #print(cases_per100k.head()) + population, testing_df, testing_percent, county_deaths_time, county_confirmed_time, incidence = get_testing_data_state() + st.subheader('Current situation of COVID-19 cases in California ('+ str(today)+')') + c1 = st.container() + c2 = st.container() + c3 = st.container() + + with c2: + a1, _, a2 = st.columns((3.9, 0.2, 3.9)) + with a1: + #f = FIPSs[FIPSs.County == C].FIPS.values[0] + components.iframe("https://covidactnow.org/embed/us/california-ca", width=350, height=365, scrolling=False) + + with a2: + st.markdown("Population under consideration = %s"% '{:,.0f}'.format(population)) + st.markdown("% test positivity (14 day average) = "+"%.2f" % testing_percent) + st.markdown("Total cases = %s"% '{:,.0f}'.format(county_confirmed_time.tail(1).values[0][0])) + st.markdown("Total deaths = %s"% '{:,.0f}'.format(county_deaths_time.tail(1).values[0][0])) + + ### Experiment with Altair instead of Matplotlib. + with c1: + a2, _, a1 = st.columns((3.9, 0.2, 3.9)) + + incidence = incidence.reset_index() + incidence['nomalized_rolling_incidence'] = incidence['rolling_incidence'] * 100000 / population + incidence['Phase 2 Threshold'] = 25 + incidence['Phase 3 Threshold'] = 10 + + scale = alt.Scale( + domain=[ + "rolling_incidence", + "Phase 2 Threshold", + "Phase 3 Threshold" + ], range=['#377eb8', '#e41a1c', '#4daf4a']) + base = alt.Chart( + incidence, + title='(A) Weekly rolling mean of incidence per 100K' + ).transform_calculate( + base_="'rolling_incidence'", + phase2_="'Phase 2 Threshold'", + phase3_="'Phase 3 Threshold'", + ) + + ax4 = base.mark_line(strokeWidth=3).encode( + x=alt.X("Datetime", axis = alt.Axis(title='Date')), + y=alt.Y("nomalized_rolling_incidence", axis=alt.Axis(title='per 100 thousand')), + color=alt.Color("base_:N", scale=scale, title="") + ) + + line1 = base.mark_line(strokeDash=[8, 8], strokeWidth=2).encode( + x=alt.X("Datetime", axis=alt.Axis(title = 'Date')), + y=alt.Y("Phase 2 Threshold", axis=alt.Axis(title='Count')), + color=alt.Color("phase2_:N", scale=scale, title="") + ) + + line2 = base.mark_line(strokeDash=[8, 8], strokeWidth=2).encode( + x=alt.X("Datetime", axis=alt.Axis(title='Date')), + y=alt.Y("Phase 3 Threshold", axis=alt.Axis(title='Count')), + color=alt.Color("phase3_:N", scale=scale, title="") + ) + with a2: + st.altair_chart(ax4 + line1 + line2, use_container_width=True) + + ax3 = alt.Chart(incidence, title = '(B) Daily incidence (new cases)').mark_bar().encode( + x=alt.X("Datetime",axis = alt.Axis(title = 'Date')), + y=alt.Y("incidence",axis = alt.Axis(title = 'Incidence')) + ) + + with a1: + st.altair_chart(ax3, use_container_width=True) + + a3, _, a4 = st.columns((3.9, 0.2, 3.9)) + testing_df = pd.DataFrame(testing_df).reset_index() + #print(testing_df.head()) + #print(type(testing_df)) + + base = alt.Chart(testing_df, title = '(D) Daily new tests').mark_line(strokeWidth=3).encode( + x=alt.X("Date",axis = alt.Axis(title = 'Date')), + y=alt.Y("new_tests_rolling",axis = alt.Axis(title = 'Daily new tests')) + ) + with a4: + st.altair_chart(base, use_container_width=True) + + county_confirmed_time = county_confirmed_time.reset_index() + county_deaths_time = county_deaths_time.reset_index() + cases_and_deaths = county_confirmed_time.set_index("Datetime").join(county_deaths_time.set_index("Datetime")) + cases_and_deaths = cases_and_deaths.reset_index() + + # Custom colors for layered charts. + # See https://stackoverflow.com/questions/61543503/add-legend-to-line-bars-to-altair-chart-without-using-size-color. + scale = alt.Scale(domain=["cases", "deaths"], range=['#377eb8', '#e41a1c']) + base = alt.Chart( + cases_and_deaths, + title='(C) Cumulative cases and deaths' + ).transform_calculate( + cases_="'cases'", + deaths_="'deaths'", + ) + + c = base.mark_line(strokeWidth=3).encode( + x=alt.X("Datetime", axis=alt.Axis(title = 'Date')), + y=alt.Y("cases", axis=alt.Axis(title = 'Count')), + color=alt.Color("cases_:N", scale=scale, title="") + ) + + d = base.mark_line(strokeWidth=3).encode( + x=alt.X("Datetime", axis=alt.Axis(title='Date')), + y=alt.Y("deaths", axis=alt.Axis(title = 'Count')), + color=alt.Color("deaths_:N", scale=scale, title="") + ) + with a3: + st.altair_chart(c+d, use_container_width=True) + + +## functions end here, title, sidebar setting and descriptions start here +t1, t2 = st.columns(2) +with t1: + st.markdown('# COVID-19 Data and Reporting') + +with t2: + st.write("") + st.write("") + st.write(""" + **EpiCenter for Disease Dynamics** | School of Veterinary Medicine - UC Davis + """) + st.write("This example was adapted from the original [here](https://github.com/PanditPranav/SVM_COVID_tracking/tree/main).") + +st.write("") +st.markdown(""" +COVID-Local provides basic key metrics against which to assess pandemic response and progress toward reopening. +Phase 2: Initial re-opening: Current esetimate of <25 cases per 100,000 population per day +Phase 3: Economic recovery: Current estimate of <10 cases per 100,000 population per day +*daily testing data currently available only for Los Angeles County, Orange County, and San Diego County + +for more details related to thresholds please see +See more at https://www.covidlocal.org/metrics/. + +For additional information please contact *epicenter@ucdavis.edu* or visit https://ohi.vetmed.ucdavis.edu/centers/epicenter-disease-dynamics. +""") + + +if sidebar_selection == 'Select Counties': + st.markdown('## Select counties of interest') + CA_counties = confirmed[confirmed.Province_State == 'California'].Admin2.unique().tolist() + counties = st.multiselect('', CA_counties, default=['Yolo', 'Solano', 'Sacramento']) + # Limit to the first 5 counties. + counties = counties[:5] + if not counties: + # If no counties are specified, just plot the state. + st.markdown('> No counties were selected, falling back to showing statistics for California state.') + plot_state() + else: + # Plot the aggregate and per-county details. + plot_county(counties) + for c in counties: + st.write('') + with st.expander(f"Expand for {c} County Details"): + plot_county([c]) +elif sidebar_selection == 'California': + plot_state() + +with st.sidebar.expander("Click to learn more about this dashboard"): + st.markdown(f""" + One of the key metrics for which data are widely available is the estimate of **daily new cases per 100,000 + population**. + + Here, in following graphics, we will track: + + (A) Estimates of daily new cases per 100,000 population (averaged over the last seven days) + + (B) Daily incidence (new cases) + + (C) Cumulative cases and deaths + + (D) Daily new tests* + + Data source: Data for cases are procured automatically from **COVID-19 Data Repository by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University**. + + The data is updated at least once a day or sometimes twice a day in the [COVID-19 Data Repository](https://github.com/CSSEGISandData/COVID-19). + + Infection rate, positive test rate, ICU headroom and contacts traced from https://covidactnow.org/. + + *Calculation of % positive tests depends on consistent reporting of county-wise total number of tests performed routinely. Rolling averages and proportions are not calculated if reporting is inconsistent over a period of 14 days. + + *Report updated on {str(today)}.* + """) + +if _ENABLE_PROFILING: + pr.disable() + s = io.StringIO() + sortby = SortKey.CUMULATIVE + ps = pstats.Stats(pr, stream=s).sort_stats(sortby) + ps.print_stats() + ts = int(time.time()) + with open(f"perf_{ts}.txt", "w") as f: + f.write(s.getvalue()) diff --git a/tutorials/streamlit/grad_desc.py b/tutorials/streamlit/grad_desc.py new file mode 100644 index 0000000..45d3006 --- /dev/null +++ b/tutorials/streamlit/grad_desc.py @@ -0,0 +1,216 @@ +from numpy.core.fromnumeric import repeat +import streamlit as st +import altair as alt +import numpy as np +import pandas as pd +from sklearn.linear_model import LinearRegression + +# st.markdown('''To do: +# * Batch size +# * Divide the coloring step from the line update step +# * Replace the grey line with the line of best fit (scikit-learn) +# * Image for contour lines? +# ''') + +rng = np.random.default_rng() + +pts = 20 + +max_updates = 2000 + +init_alpha = 0.01 + +init_batch = 1 + +init_theta = (4,0) + +# coefs = [a,b,c] where our line is ax + by + c = 0 +def line_fn(coefs): + t0, t1 = coefs + return lambda x: t0 + t1*x + +def draw_line(coefs, dom, color="lightgrey"): + f = line_fn(coefs) + line_dom = [dom[0] - 5, dom[1] + 5] + df = pd.DataFrame({"x": line_dom, "y": [f(x) for x in line_dom]}) + c = ( + alt.Chart(df) + .mark_line(clip=True, color=color) + .encode( + x=alt.X("x", scale=alt.Scale(domain=dom)), + y="y", + ) + ) + return c + +def update_theta(theta,batch,alpha): + t0, t1 = theta + val = 2*(t0 + t1*batch["x"] - batch["y"])/pts + return (t0 - (alpha*val).sum(), t1 - (alpha*val*batch["x"]).sum()) + +def make_theta_array(df, pt_idx, alpha, batch): + theta_arr = [init_theta] + for i in range(max_updates): + theta_arr.append( + update_theta(theta_arr[i],df.loc[pt_idx[i*batch:(i+1)*batch],["x","y"]], alpha) + ) + return theta_arr + +if "batch" in st.session_state: + batch = st.session_state["batch"] +else: + batch = init_batch + +def update(): + theta_arr = [(4,0)] + (_, df, pt_idx) = st.session_state["data"] + alpha = st.session_state["alpha"] + batch = st.session_state["batch"] + st.session_state["theta_arr"] = make_theta_array(df, pt_idx, alpha, batch) + +def get_latex_for_line(m,b): + return f'''$y = {round(m,2)}x {"+" if b >= 0 else ""} {round(b,2)}$''' + +def clear_data(): + del st.session_state["data"] + st.session_state["step_slider"] = 0 + del st.session_state["theta_arr"] + + + + +if "data" in st.session_state: + (fit_coefs, df, pt_idx) = st.session_state["data"] +else: + coefs = 30 * rng.random(size=(2)) - 20 + df = pd.DataFrame(index=range(pts), columns=["x", "y", "color"]) + df["x"] = rng.normal(size=pts, loc=0, scale=3) + true_f = line_fn(coefs) + df["y"] = true_f(df["x"]) + rng.normal(size=pts, loc=0, scale=7) + df["color"] = 0 + + reg = LinearRegression() + reg.fit(df[["x"]], df[["y"]]) + fit_1 = reg.coef_[0][0] + fit_0 = reg.intercept_[0] + + fit_coefs = (fit_0, fit_1) + + pt_idx = np.concatenate([rng.permutation(range(pts)) for i in range((max_updates*batch)//pts + 1)]) + + st.session_state["data"] = (fit_coefs, df, pt_idx) + + +if "theta_arr" in st.session_state: + theta_arr = st.session_state["theta_arr"] +else: + theta_arr = [(4,0)] + + for i in range(max_updates): + theta_arr.append(update_theta(theta_arr[i],df.loc[pt_idx[i*batch:(i+1)*batch], + ["x","y"]], init_alpha)) + + st.session_state["theta_arr"] = theta_arr + + +st.title("Demonstration of Gradient Descent") + +st.write("This example was adapted from the original [here](https://github.com/ChristopherDavisUCI/streamlit_ed/tree/main).") + +st.markdown('''Some data is shown below, together with the line of best fit for that data. +There is a formula for finding that best fit line, but it can be more efficient to find +the line instead using the iterative procedure of *gradient descent*. + +Our goal is to find coefficients $t_0$, $t_1$ +so that the line $y = t_1 x + t_0$ fits the data as well as possible. We start with the guess $y = 4$ and +then gradually update the parameters. + +Choices: +* The number of iterations to perform. +* The *learning rate*, which controls how much the parameters change with each update. +* The *batch size*, how many of the data points you want to consider in each step. For stochastic +gradient descent, use a batch size of 1. +''') + +xmin = df["x"].min() +xmax = df["x"].max() +ymin = df["y"].min() +ymax = df["y"].max() + +chart1 = draw_line(fit_coefs, [xmin, xmax]) + +chart2 = alt.Chart(df).mark_circle().encode( + x=alt.X("x",scale=alt.Scale(domain=[xmin, xmax])), + y=alt.Y("y",scale=alt.Scale(domain=[ymin, ymax])), + color=alt.Color("color:N", legend=None, scale=alt.Scale(domain=[0,1], range=["darkgrey","red"])), + ) + + + + + +fit_0, fit_1 = fit_coefs + + +with st.sidebar: + st.write("Here you can adjust some parameters for the gradient descent algorithm.") + + learn = st.slider("What learning rate?",min_value=0.0,max_value=0.2,step=0.002, value = init_alpha, + key="alpha", on_change = update, format="%.3f") + + batch = st.slider("What batch size?",min_value=1,max_value=pts,step=1, value = init_batch, + key="batch", on_change = update) + +step = st.slider("How many updates do you want to perform?",min_value=0,max_value=max_updates,step=1, + key = "step_slider") + +df["color"] = 0 + +df.loc[pt_idx[step*batch:(step+1)*batch],"color"] = 1 + +t0, t1 = theta_arr[step] + +chart1b = draw_line((t0,t1), [xmin, xmax],color="black") + +c0, c1 = st.columns((7,5)) + +with c0: + st.altair_chart(alt.layer(chart1,chart1b,chart2),use_container_width=True) + +df_theta = pd.DataFrame(theta_arr, columns=["t0","t1"]) + +t0_min, t1_min = df_theta.min(axis=0) - 2 +t0_max, t1_max = df_theta.max(axis=0) + 2 + +chart_theta = alt.Chart(df_theta.loc[:step]).mark_circle().encode( + x=alt.X("t0",scale=alt.Scale(domain=[t0_min, t0_max])), + y=alt.Y("t1",scale=alt.Scale(domain=[t1_min, t1_max])), +) + +chart_fit = alt.Chart(pd.DataFrame({"t0":[fit_0],"t1":[fit_1]})).mark_point( + color="red", + shape="diamond", + size=100, + ).encode( + x = "t0", + y = "t1", + ) + +with c1: + st.altair_chart(chart_theta + chart_fit,use_container_width=True) + +st.button("Get new data",on_click=clear_data) + +st.markdown( + f"""The line of best fit is given by the equation + {get_latex_for_line(fit_1, fit_0)}. (Shown in grey.) + """ +) + +st.markdown(f"Our current guess for the line is given by {get_latex_for_line(t1, t0)}. (Shown in black.)") + +st.write('''The chart on the right shows the estimated coefficients so far (in blue) and the +best-fit coefficients (in red). +''') + +