From d5d8debd37036a98e06aa7b60b746a37e4e5b9cf Mon Sep 17 00:00:00 2001 From: Lextuga007 Date: Wed, 10 Apr 2024 15:57:19 +0100 Subject: [PATCH] Added dynamics elements slide --- dynamic-elements.html | 784 ++++++++++++++++++++++++++++++++++++++---- dynamic-elements.qmd | 100 +++++- 2 files changed, 808 insertions(+), 76 deletions(-) diff --git a/dynamic-elements.html b/dynamic-elements.html index 7ee77a6..9d79a49 100644 --- a/dynamic-elements.html +++ b/dynamic-elements.html @@ -427,10 +427,10 @@ );
@@ -1218,6 +1699,58 @@
+

Introducing a simplified reactive app

+

The real magic of Shiny happens when you have an app with inputs and outputs

+
+

and the two “react” to each other

+
+
ui <- fluidPage(
+  textInput(inputId = "name", 
+            label = "What's your name?"),
+  textOutput("greeting")
+)
+
+server <- function(input, output, session) {
+  output$greeting <- renderText({
+    paste0("Hello ", input$name, "!")
+  })
+}
+
+
+

Looking for “name” and “greeting”

+
+
    +
  • The ui textInput() is providing the information for the server input$name +
  • +
  • And that is updating the object output$greeting in the server that updates the textOutput("greeting") in the ui
  • +
  • The updates occur every time something is changed
  • +
+
+

The mental model

+

Unlike with R where updates like this occur when we run the code, here Shiny is being told how it could create the string if it needs to. The possibilities are:

+
+
    +
  • This code is never run as the option is not used
  • +
  • It might be run as soon as the app is launched
  • +
  • It might be run once some time after the launch
  • +
  • … there are many possibilities
  • +
+
+
+
+
+
+
+ +
+

Tip!

+
+
+

Think of a Shiny app as providing recipes and not giving commands!

+
+
+
+

Introducing a new input

Four inputs were mentioned previously:

Building a new app

Starting again with a new script, typing shinyapp and then Shift + Tab add the code to ui, save and Run App

-
 checkboxGroupInput(
-    inputId = "number1",
-    label = "What are your favourite languages?",
-    choices = c("SQL", "Python", "R")
-  ),
+
 titlePanel("Basic Demo"), # Title
+ checkboxGroupInput(
+    inputId = "name1",
+    label = "What are your favourite languages?",
+    choices = c("SQL", "Python", "R")
+  ),

Adding an action button

Next we’ll add an action button so, after making a selection, the user presses a button for the next thing to happen:

-
  actionButton(inputId = "number2",
-               label = "List the selections",
-               icon = icon("question"))
+
  actionButton(inputId = "name2",
+               label = "List the selections",
+               icon = icon("question"))
@@ -1272,35 +1806,31 @@

Panels

Before we move to the interactive part of shiny we’ve currently got the layout where everything is appearing on the page as if it were a document.

-
+

Panel functions

To give the app more structure that are more familiar we need to introduce three functions:

- -
-
fluidPage(titlePanel("Basic Demo"),
-                sidebarLayout(
-                sidebarPanel(
-                    checkboxGroupInput(
-                      inputId = "fav_things",
-                      label = "What are your favourite languages?",
-                      choices = c("SQL", "Python", "R")
-                    ),
-                    actionButton(
-                      inputId = "show_fav_things",
-                      label = "Selected",
-                      icon = icon("question")
-                    )
-                ),
-                mainPanel(textOutput(outputId = "print_fav_things"))
-                )
-)
-
+
ui <- fluidPage(titlePanel("Basic Demo"), 
+                sidebarLayout(
+                sidebarPanel(
+                    checkboxGroupInput(
+                      inputId = "name1",
+                      label = "What are your favourite languages?",
+                      choices = c("SQL", "Python", "R")
+                    ),
+                    actionButton(
+                      inputId = "name2",
+                      label = "Selected",
+                      icon = icon("question")
+                    )
+                ),
+                mainPanel(textOutput(outputId = "name3"))
+                )
+)

Tips

    -
  • If sidebarPanel() is used then the code won’t run without mainPanel() -
  • +
  • All three functions sidebarLayout(), sidebarPanel() and mainPanel() are needed to run the app
  • If the copying and pasting loses the formatting, highlight and use Ctrl+i for indentation
  • Alternatively install {styler} which can reformat selection or the entire script
@@ -1316,23 +1846,125 @@

server code

-
observeEvent(input$show_fav_things, {
-    output$print_fav_things <- renderText(input$fav_things)
-    })
+
server <- function(input, output, session) {
+    observeEvent(input$name2, {
+    output$name3 <- renderText(input$name1)
+    })
+}
+
+

Copy and paste this, save and run

+

Exercise - renaming placeholders

+

Replace the names name1, name2, name3 with more descriptive names, paying attention to where the repetitions occur

+
+
+
+
+ +
+10:00 +
-

Copy and paste this into the server function(input, output, session) {}, save and run

-
-

Then replace the names with name1, name2, name3, paying attention to where the repetitions occur

observeEvent()

-

Changing the names won’t affect the ui but shows you were the connections are made and they may not be intuitive.

+

Takes the input from the button in this example and until it’s pressed it won’t print anything.

+

Once selected though any selection or de-selection is updated automatically.

End session

Acknowledgements

Mastering Shiny by Hadley Wickham

Building Web Apps with R Shiny from PsyTeachR

- -
@@ -1387,7 +2019,7 @@

Acknowledgements

divSourceCode.forEach((el) => { if (el.hasAttribute(kCodeLineNumbersAttr)) { const codeLineAttr = el.getAttribute(kCodeLineNumbersAttr); - el.removeAttribute("data-code-line-numbers"); + el.removeAttribute(kCodeLineNumbersAttr); if (handleLinesSelector(deck, codeLineAttr)) { // Only process if attr is a string to select lines to highlights // e.g "1|3,6|8-11" @@ -1514,9 +2146,9 @@

Acknowledgements

if (typeof highlight.last === "number") { spanToHighlight = [].slice.call( codeBlock.querySelectorAll( - ":scope > span:nth-child(n+" + + ":scope > span:nth-of-type(n+" + highlight.first + - "):nth-child(-n+" + + "):nth-of-type(-n+" + highlight.last + ")" ) @@ -1524,7 +2156,7 @@

Acknowledgements

} else if (typeof highlight.first === "number") { spanToHighlight = [].slice.call( codeBlock.querySelectorAll( - ":scope > span:nth-child(" + highlight.first + ")" + ":scope > span:nth-of-type(" + highlight.first + ")" ) ); } @@ -1801,9 +2433,6 @@

Acknowledgements

Reveal = _Reveal; install(); }; - Plugin.togglePdfExport = function () { - togglePdfExport(); - }; } return Plugin; @@ -1859,6 +2488,20 @@

Acknowledgements

return /print-pdf/gi.test(window.location.search); } + // helper for theme toggling + function toggleBackgroundTheme(el, onDarkBackground, onLightBackground) { + if (onDarkBackground) { + el.classList.add('has-dark-background') + } else { + el.classList.remove('has-dark-background') + } + if (onLightBackground) { + el.classList.add('has-light-background') + } else { + el.classList.remove('has-light-background') + } + } + // implement controlsAudo function controlsAuto(deck) { const config = deck.getConfig(); @@ -1966,8 +2609,19 @@

Acknowledgements

} } - // add footer text - function addFooter(deck) { + // tweak slide-number element + function tweakSlideNumber(deck) { + deck.on("slidechanged", function (ev) { + const revealParent = deck.getRevealElement(); + const slideNumberEl = revealParent.querySelector(".slide-number"); + const onDarkBackground = Reveal.getSlideBackground(ev.indexh, ev.indexv).classList.contains('has-dark-background'); + const onLightBackground = Reveal.getSlideBackground(ev.indexh, ev.indexv).classList.contains('has-light-background'); + toggleBackgroundTheme(slideNumberEl, onDarkBackground, onLightBackground); + }) + } + + // add footer text + function addFooter(deck) { const revealParent = deck.getRevealElement(); const defaultFooterDiv = document.querySelector(".footer-default"); if (defaultFooterDiv) { @@ -1982,13 +2636,17 @@

Acknowledgements

prevSlideFooter.remove(); } const currentSlideFooter = ev.currentSlide.querySelector(".footer"); + const onDarkBackground = Reveal.getSlideBackground(ev.indexh, ev.indexv).classList.contains('has-dark-background') + const onLightBackground = Reveal.getSlideBackground(ev.indexh, ev.indexv).classList.contains('has-light-background') if (currentSlideFooter) { defaultFooterDiv.style.display = "none"; const slideFooter = currentSlideFooter.cloneNode(true); handleLinkClickEvents(deck, slideFooter); deck.getRevealElement().appendChild(slideFooter); + toggleBackgroundTheme(slideFooter, onDarkBackground, onLightBackground) } else { defaultFooterDiv.style.display = "block"; + toggleBackgroundTheme(defaultFooterDiv, onDarkBackground, onLightBackground) } }); } @@ -2135,6 +2793,7 @@

Acknowledgements

fixupForPrint(deck); applyGlobalStyles(deck); addLogoImage(deck); + tweakSlideNumber(deck); addFooter(deck); addChalkboardButtons(deck); handleTabbyClicks(); @@ -2163,7 +2822,6 @@

Acknowledgements

Reveal.initialize({ 'controlsAuto': true, 'previewLinksAuto': false, -'smaller': false, 'pdfSeparateFragments': false, 'autoAnimateEasing': "ease", 'autoAnimateDuration': 1, @@ -2458,10 +3116,9 @@

Acknowledgements

// clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -2471,8 +3128,17 @@

Acknowledgements

interactive: true, interactiveBorder: 10, theme: 'light-border', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } config['offset'] = [0,0]; config['maxWidth'] = 700; window.tippy(el, config); diff --git a/dynamic-elements.qmd b/dynamic-elements.qmd index ef33f6f..a577c75 100644 --- a/dynamic-elements.qmd +++ b/dynamic-elements.qmd @@ -8,8 +8,59 @@ subtitle: "Session - Adding dynamic elements" #| include: false #| eval: true #| echo: false + +library(countdown) +``` + +## Introducing a simplified reactive app + +The real magic of Shiny happens when you have an app with inputs and outputs + +. . . + +and the two "react" to each other + +```{r} +ui <- fluidPage( + textInput(inputId = "name", + label = "What's your name?"), + textOutput("greeting") +) + +server <- function(input, output, session) { + output$greeting <- renderText({ + paste0("Hello ", input$name, "!") + }) +} ``` +## Looking for "name" and "greeting" + +::: {.incremental} +- The ui `textInput()` is providing the information for the server `input$name` +- And that is updating the object `output$greeting` in the server that updates the `textOutput("greeting")` in the ui +- The updates occur every time something is changed +::: + +## The mental model + +Unlike with R where updates like this occur when we run the code, here Shiny is being told *how it could* create the string if it needs to. The possibilities are: + +::: {.incremental} +- This code is never run as the option is not used +- It might be run as soon as the app is launched +- It might be run once some time after the launch +- ... there are many possibilities +::: + +. . . + +:::{.callout-tip collapse=false appearance='default' icon=true} +## Tip! +Think of a Shiny app as providing recipes and not giving commands! +::: + + ## Introducing a new input Four inputs were mentioned previously: @@ -28,8 +79,9 @@ And there are more! We are going to use `checkboxGroupInput()` in a new app. Starting again with a new script, typing `shinyapp` and then Shift + Tab add the code to `ui`, save and Run App ```{r} + titlePanel("Basic Demo"), # Title checkboxGroupInput( - inputId = "number1", + inputId = "name1", label = "What are your favourite languages?", choices = c("SQL", "Python", "R") ), @@ -40,7 +92,7 @@ Starting again with a new script, typing `shinyapp` and then Shift + Tab add the Next we'll add an action button so, after making a selection, the user presses a button for the next thing to happen: ```{r} - actionButton(inputId = "number2", + actionButton(inputId = "name2", label = "List the selections", icon = icon("question")) ``` @@ -57,29 +109,27 @@ Next we'll add an action button so, after making a selection, the user presses a Before we move to the interactive part of shiny we've currently got the layout where everything is appearing on the page as if it were a document. -. . . +## Panel functions To give the app more structure that are more familiar we need to introduce three functions: -. . . - ```{r} #| code-line-numbers: 2|3|15 -fluidPage(titlePanel("Basic Demo"), +ui <- fluidPage(titlePanel("Basic Demo"), sidebarLayout( sidebarPanel( checkboxGroupInput( - inputId = "fav_things", + inputId = "name1", label = "What are your favourite languages?", choices = c("SQL", "Python", "R") ), actionButton( - inputId = "show_fav_things", + inputId = "name2", label = "Selected", icon = icon("question") ) ), - mainPanel(textOutput(outputId = "print_fav_things")) + mainPanel(textOutput(outputId = "name3")) ) ) ``` @@ -87,7 +137,7 @@ fluidPage(titlePanel("Basic Demo"), ## Tips ::: {.incremental} -- If `sidebarPanel()` is used then the code won't run without `mainPanel()` +- All three functions `sidebarLayout()`, `sidebarPanel()` and `mainPanel()` are needed to run the app - If the copying and pasting loses the formatting, highlight and use `Ctrl+i` for indentation - Alternatively install {styler} which can reformat selection or the entire script ::: @@ -103,22 +153,38 @@ fluidPage(titlePanel("Basic Demo"), ## server code ```{r} -observeEvent(input$show_fav_things, { - output$print_fav_things <- renderText(input$fav_things) +server <- function(input, output, session) { + observeEvent(input$name2, { + output$name3 <- renderText(input$name1) }) +} ``` -Copy and paste this into the server `function(input, output, session) {}`, save and run +Copy and paste this, save and run -. . . +## Exercise - renaming placeholders -Then replace the names with `name1`, `name2`, `name3`, paying attention to where the repetitions occur +Replace the names `name1`, `name2`, `name3` with more descriptive names, paying attention to where the repetitions occur -## `observeEvent()` +```{r} +#| eval: true +#| echo: false +countdown::countdown(minutes = 10, + color_border = "#005EB8", + color_text = "#005EB8", + color_running_text = "white", + color_running_background = "#005EB8", + color_finished_text = "#005EB8", + color_finished_background = "white", + margin = "0.9em", + font_size = "2em") +``` -Changing the names won't affect the ui but shows you were the connections are made and they may not be intuitive. +## `observeEvent()` +Takes the input from the button in this example and until it's pressed it won't print anything. +Once selected though any selection or de-selection is updated automatically. ## End session