Dans l’article précédent nous avons vu pourquoi les modules sont d’une grande aide dans la création d’applications Shiny. Nous avons également vu un exemple minimal “Hello-World”.
Cela peut parfois être difficile de partager des reactives entre l’application Shiny elle-même et ses modules. Dans cet article nous décrirons les trois cas les plus communs d’échange de données :
- Module → Application
- Application → Module
- Application ↔︎ Module
Si vous souhaitez jouer les exemples en local
Tous les codes utilisés ici sont disponibles sur Github ardata-fr/Shiny-Modules-Tutorials.
Ce dépôt est un package R dont les modules sont les fonctions exportées. Les applications Shiny sont dans le dossier inst
.
Installer le package et lancer localement les applications :
# install.packages("remotes")
remotes::install_github("ardata-fr/Shiny-Modules-Tutorials")
library(shinyModulesTuto)
# Lister les applications disponibles
listEx()
# Lancer la 1ere application
runEx(listEx()[1])
La donnée du module vers l’application
Voir l’application exemple en ligne ou bien lancer la commande :
# Lancer localement l'application exemple
runEx("module-vs-app")
Dans le module
On initialise la reactiveValues
que l’on va retourner :
toReturn <- reactiveValues(
variables = NULL,
variable_name = NULL,
trigger = 0
)
Puis on la met à jour reactiveValues
:
observeEvent(input$AB_load, {
toReturn$variable <- get(input$SI_dataset)[,input$SI_var]
toReturn$variable_name <- input$SI_var
toReturn$trigger <- toReturn$trigger + 1
})
Pour finir elle est retournée à l’application :
return(toReturn)
Dans l’application
On récupère les résultats du module :
# results est une reactiveValues avec 3 slots (defini dans le module)
results <- callModule(module = load_data, id = "id1")
On peut ensuite l’utiliser comme une reactiveValues
classique :
output$PR_results_print <- renderPrint({
print(results$variable_name)
print(results$variable)
})
La donnée de l’application vers le module
Voir l’application exemple en ligne ou bien lancer la commande :
# Lancer localement l'application exemple
runEx("app-vs-module")
Dans l’application
Solution 1 : Utiliser une reactive
On crée la reactive
que l’on donnera ensuite au module :
variable <- reactive({
iris[, input$SI_colname]
})
On passe la reactive
comme un paramètre du module :
callModule(module = show_data, id = "id1",
variable = variable,
variable_name = reactive(input$SI_colname))
NB : variable
étant une reactive
, pas besoin de l’encapsuler dans la fonction reactive()
.
Solution 2 : Utiliser une reactiveValues
On crée la reactiveValues
que l’on donnera ensuite au module :
rv <- reactiveValues(variable = NULL)
observe({
rv$variable <- iris[, input$SI_colname]
})
On passe la reactiveValues
comme un paramètre du module :
callModule(module = show_data, id = "id2",
variable = reactive(rv$variable),
variable_name = reactive(input$SI_colname))
NB : Comme rv$variable
est une reactiveValues
, on l’encapsule dans la fonction reactive()
.
Dans le module
Solutions 1 & 2
Pour les deux solutions (avec une reactive
ou une reactiveValues
dans l’application), on récupère les paramètres depuis le module avec :
output$PL_histogram_var <- renderPlot({
hist(variable(), main = variable_name(), xlab = NULL)
})
NB : On utilise ici les paramètres du module variable
et variable_name
comme des reactives
classiques (cf variable()
& variable_name()
).
Donnée de l’application modifiée dans un module
Voir l’application exemple en ligne ou bien lancer la commande :
# Lancer localement l'application exemple
runEx("app-pong-module")
L’objectif est d’utiliser un module pour modifier une donnée présente dans l’application. L’astuce ici est d’utiliser une deuxième reactiveValues
.
Comme nous avons décrit dans le chapitre précédent comment utiliser les paramètres dans un module, dans cette partie on se concentre uniquement sur l’application.
Considérons le module apply_function
qui prend en paramètre un vecteur numérique sur lequel l’utilisateur peut appliquer une fonction (ex : log(x)
). Ensuite, le module retourne une reactiveValues
contenant plusieurs slots : le vecteur numérique résultant de cette fonction, le nom de la fonction appliquée et un “trigger” qui s’incrémente dès lors qu’une nouvelle fonction est appliquée.
Dans l’application, on initialise la reactiveValues
comme d’habitude :
rv <- reactiveValues(variable = NULL, fun_historic = NULL)
observe({
rv$variable <- iris[, input$SI_colname]
rv$fun_historic <- NULL
})
Que l’on passe ensuite au module apply_function
:
modified_data <- callModule(module = apply_function, id = "id1",
variable = reactive(rv$variable))
La sortie du module est une reactiveValues
avec les 3 slots suivants :
- result (le vecteur numérique résultant de la fonction)
- fun (nom de la fonction appliquée)
- trigger (integer qui s’incrémente dès qu’une nouvelle fonction est appliquée)
Dans l’application, pour mettre à jour la reactiveValues
rv$variable
en fonction du résultat obtenu (modified_data$result
), on utilise un observeEvent
basé sur modified_data$trigger
.
observeEvent(modified_data$trigger, {
rv$variable <- modified_data$result
rv$fun_historic <- c(rv$fun_historic, modified_data$transformation)
})
L’astuce ici consistait à utiliser une reactiveValues
“tampon”. Le vrai schéma devrait donc être :
Application avec l’ensemble des exemples précédents
Voir l’application exemple en ligne ou bien lancer la commande :
# Lancer localement l'application exemple
runEx("whole-app")
Cette application utilise l’ensemble des modules des exemples précédents. De plus, le module apply_scale
ajoute une deuxième façon de modifier la reactiveValues “principale” rv
(applique la fonction scale(x)
).
Le schéma ci-dessous montre que la reactiveValues rv
est le coeur de l’application. Elle contient deux slots, variable
qui est le vecteur numérique ainsi que fun_history
qui est l’ensemble des fonctions qui ont été appliquées.
La reactiveValues rv
est initialisée dans l’application comme ci-dessous :
rv <- reactiveValues(variable = NULL, fun_history = NULL)
Puis elle est mise à jour par trois modules différents :
- Par le module load_data en utilisant la reactiveValues tampon
data_mod1
. - Par le module apply_function en utilisant la reactiveValues tampon
data_mod2
. - Par le module apply_scale en utilisant la reactiveValues tampon
data_mod3
.
Pour faciliter le processus de mise à jour de la reactiveValues rv
, on place des observeEvent
sur les slots trigger
retournés pour les modules. Ainsi on s’assure que la dernière action de l’utilisateur est bien prise en compte.
Déclenchement de la mise à jour à partir des résultats du module load_data
:
# Appel du module load_data
data_mod1 <- callModule(module = load_data, id = "mod1")
# Lorsque data_mod1$trigger change, mise à jour de rv$variable & rv$fun_history
observeEvent(data_mod1$trigger, {
req(data_mod1$trigger>0)
rv$variable <- data_mod1$variable
rv$fun_history <- c()
})
Déclenchement de la mise à jour à partir des résultats du module apply_function
:
# Appel du module apply_function avec le parametre rv$variable
data_mod2 <- callModule(module = apply_function, id = "mod2",
variable = reactive(rv$variable))
# Lorsque data_mod2$trigger change, mise à jour de rv$variable & rv$fun_history
observeEvent(data_mod2$trigger, {
req(data_mod2$trigger>0)
rv$variable <- data_mod2$result
rv$fun_history <- c(rv$fun_history, data_mod2$fun)
})
Déclenchement de la mise à jour à partir des résultats du module apply_scale
:
# Appel du module apply_scale avec le parametre rv$variable
data_mod3 <- callModule(module = apply_scale, id = "mod3",
variable = reactive(rv$variable))
# Lorsque data_mod3$trigger change, mise à jour de rv$variable & rv$fun_history
observeEvent(data_mod3$trigger, {
req(data_mod3$trigger>0)
rv$variable <- data_mod3$result
rv$fun_history <- c(rv$fun_history, "scale")
})
Conclusion
Nous avons décrit les cas ci-dessous (lien vers les applications) :
- Récupérer les données d’un module
- Envoyer des données vers un module
- Comment une reactiveValues de l’application peut être modifiée dans un module
- Un exemple contenant tous les cas ci-dessus
Nous vous recommandons vivement d’utiliser les modules lors du développement d’applications Shiny conséquentes. Vous pouvez aller encore plus loin avec :
- Les modules imbriqués (très simple en réalité)
- L’appel dynamique de modules
Avant de voir ces concepts (il me reste une partie 3 à écrire), j’espère que vous allez vous amuser à créer vos applications Shiny !
Suivez nous: - Sites recommandés: R-bloggers R weekly Twitter #rstats Jobs for R-users