Tutoriel Shiny
Partie 4: Fonctions et réactivité

Lise Vaudor

janvier 2021

Réactivité : base

Dans sa version la plus simple, la chaîne de réactivité ressemble en fait à ceci :

C’est, pour le moment, le seul type de réaction que l’on a abordé.

Fournir des outputs réactifs

Pour rappel, voici comment l’on procède pour fournir un output réactif:

Fournir des outputs réactifs - exemple (1)

Observez le diagramme ci-contre, qui décrit un exemple de Shiny app, comprenant

Je vais utiliser cet exemple pour illustrer le fonctionnement de la réactivité des Shiny apps.

Fournir des outputs réactifs - exemple (2)

Commençons par simplifier et expliquer ce diagramme:

Observez le sens des flèches… Quand un input est modifié, il est aspiré par le contexte réactif… Et non l’inverse (il n’est pas poussé vers le contexte réactif)… Et, oui, la différence est subtile, mais importante pour la suite…

Observez maintenant la couleur de fond du contexte réactif. A gauche, tout est pour le moment en gris, mais par la suite on pourra observer plusieurs états pour les contextes réactifs:

Fournir des outputs réactifs - exemple (3)

Considérons une première “configuration” de Shiny app. (Avec l’exemple correspondant à droite).

Ici, par défaut, seul output_e est affiché. Il est donc attentif, là où output_d est au contraire inerte.

Notez que quand l’utilisateur change de panel (en affichant “tab1” à la place de “tab2”), c’est au contraire output_d qui est attentif tandis que output_e est inerte…

ui

  flowLayout(radioButtons("a","Word 1:", c("Hello","Greetings")),
             radioButtons("b","Word 2:", c("benevolent","great")),
             radioButtons("c","Word 3:", c("Master","Human"))),
  tabsetPanel(tabPanel("tab1",textOutput("d")),
              tabPanel("tab2",textOutput("e")),
              selected="tab2")

server

  output$d <- renderText({Sys.sleep(1);paste(input$a,"!")})
  output$e <- renderText({Sys.sleep(1);paste(input$a,",",input$b,input$c,"!")})

Ici j’ai fait en sorte de mettre le système en pause pendant une seconde (Sys.sleep(1)) pour que vous puissiez bien constater à quel moment le code dans les contextes réactifs est exécuté…

Fournir des outputs réactifs - exemple (4)

Quand cette app est lancée, voilà donc ce qui se passe:

shows output_e est attentif, donc makes output_e est attentif, et aspire les 3 inputs (qui sont tous nouveaux) afin d’exécuter son code.

Par la suite, l’output qui est attentif est actualisé à chaque fois qu’un de ses inputs (ici par exemple input_b) est modifié…

Modulariser les réactions

Vous avez déjà vu que les fonctions servent à “modulariser” du code (i.e., faire en sorte que certains morceaux de code soient “encapsulés” dans une structure qui permet de l’appeler et l’exécuter assez simplement…).

Les reactives, quant à elles, sont des fonctions qui permettent de modulariser du code réactif… L’usage de reactives est particulièrement utile lorsque certains morceaux de code sont utilisés par plusieurs outputs à la fois… Ainsi, comme l’écriture de fonctions en général, l’écriture de reactives permet d’éviter certaines redondances dans le code.

Modulariser les réactions - exemple

Considérons cette nouvelle structure d’appli…

Ici on a introduit une réactive f().

Ici encore, vous trouverez un exemple d’une telle structure d’appli (à droite).

La réactive f() sert ici à créer le chemin de l’image à partir de l’input (“heart” ou “sun”). Les deux ouptuts utilisent cette fonction pour afficher l’image correspondante.

ui

  fluidRow(column(width=4,radioButtons("A","Image:", c("heart","sun"))),
           column(width=4,radioButtons("B","Word 1:", c("Hello","Greetings"))),
           column(width=4,radioButtons("C","Word 2:", c("Master","Human")))),
  tabsetPanel(selected="tab2",
              tabPanel("tab1",uiOutput("D")),
              tabPanel("tab2",uiOutput("E")))

server

  f=reactive({
    Sys.sleep(1)
    result=paste0("www/",input$A,".png")
  })
  output$D <- renderUI({img(src=f())})
  output$E <- renderUI({p(img(src=f())," ",input$B,",",input$C,"!")})

Ici, la seconde de “pause” intervient quand la réactive est exécutée. Remarquez que quand vous changez de tab, la réactive n’est pas réexécutée… La réactive est réexécutée seulement quand son input est modifié!

Provoquer/retarder l’exécution: utilisation des triggers

Dans le mécanisme de réactivité le plus classique, un code est exécuté quand il correspond à un output “attentif” et que l’un de ses inputs est modifié.

Il est possible de contourner ce mécanisme et de faire en sorte que l’exécution d’un code soit déclenché par l’utilisateur.

Ce genre de mécanisme va de pair avec l’utilisation des widgets de type “trigger”:

Déclencher/provoquer l’exécution d’un code

Dans ce cas, le code exécuté ne correspond pas à un output de l’appli. Il peut s’agir d’un code permettant par exemple:

  • l’écriture d’un fichier (tableau de données, figure, raster, etc.)
  • l’affichage d’un résultat (print(...)) dans la console
  • un update de widget

ui

actionButton("go","go!")

server

observeEvent(input$go,{
  updateActionButton(session,
                     inputId="go",
                     label=str_c(str_c(rep("go",input$go+1),
                                       collapse=", "),
                                 "!")
                     )
})

Déclencher/retarder une réaction

Dans ce cas, le code est exécuté non pas dès que l’un de ses inputs est modifié, mais lorsque que l’utilisateur déclenche la réaction.

Le fait pour l’utilisateur de valider l’exécution d’un code permet notamment de gérer l’exécution de codes un peu trop longs pour une “instantanéité” d’affichage des résultats.

actionButton("now","Now!"),
textOutput("time")
reac=eventReactive(input$now,{
  as.character(Sys.time())
})
output$time=renderText({
  str_c("The time right now is: ",reac())
  })

Empêcher des réactions

fluidRow(column(width=4,textInput("word1","First word","Hello")),
         column(width=4,textInput("word2","Second word","Master")),
         column(width=4,textOutput("combi"))
)
output$combi <- renderText({paste(input$word1,isolate(input$word2))})
output$combi <- renderText({paste(input$word1,isolate(input$word2))})

Ici, l’output n’est actualisé que quand le deuxième input est modifié. Une modification du premier input ne déclenche pas le code contenu dans le contexte réactif.