Tableaux dans Shiny : Le graal des inputs!

Auteur : Murielle Delmotte
Tags : Autour de R, Ressources
Date :

Si vous cherchez à intégrer des inputs shiny dans vos tableaux … vous trouverez ici une méthode pour le réaliser.

Les tableaux sont des éléments de base dans les applications Shiny, mais saviez-vous que vous pouvez les rendre encore plus puissants et interactifs en y ajoutant des inputs ?

Dans cet article, nous allons explorer comment créer des tableaux interactifs dans Shiny et découvrir comment les inputs peuvent transformer vos applications en des outils encore plus dynamiques.

Préparez votre Boîte à Outils :

Dans cette aventure, nous aurons besoin de quelques packages jouant des rôles essentiels:

  • {shiny} – Le Maître de l’Interactivité : Il nous permet de créer facilement des applications web en R.
  • {DT} – Le Magicien des Tableaux : Il nous permet de créer des tableaux interactifs, de les personnaliser avec des fonctionnalités avancées, et de les rendre aussi beaux que fonctionnels. Il va nous aider à donner vie à nos données. Article de blog sur {DT}
  • {htmltools} – Le Seigneur des Outils HTML : Il nous permet de créer des éléments HTML personnalisés pour enrichir nos applications Shiny. Il sera notre compagnon pour intégrer des inputs interactifs dans nos tableaux.

Disponibles sur le CRAN, vous pouvez les installer avec les commandes :

install.packages("shiny")
install.packages("DT")
install.packages("htmltools")
library(shiny)
library(DT)
library(htmltools)

Nous sommes maintenant équipés pour attaquer la création de notre application (très simple)

Création de l’Interface Utilisateur (UI) :

L’interface utilisateur (UI) est la première étape de notre voyage.
Une petite étape puisqu’il s’agit de créer l’espace pour notre tableau interactif et donc préparer le terrain pour nos inputs puissants.

ui <- fluidPage(
  DTOutput(outputId = "montableau")
)

Et c’est tout ???

La magie va opérer côté serveur !

Création du Serveur

server <- function(input, output, session) {
  output$montableau <- renderDT({
    iris    
  },
  selection = "none",
  escape = FALSE)
}

L’option selection = "none" permet de déterminer qu’aucune sélection de lignes du tableau ne sera autorisée.

L’option escape = FALSE permet de ne pas échapper les données HTML qui seront dans notre tableau.

shinyApp(ui, server)

Mais ce n’est pas `iris“qui vous intéresse … alors poursuivons

Construisons notre tableau

La partie la plus excitante de notre voyage commence : l’intégration d’inputs interactifs dans le tableau.

Les fonctions de {htmltools} vont nous permettre de créer des éléments HTML personnalisés (par exemple un menu déroulant, un champ de saisie numérique, …) et les intégrer dans le tableau.

Nous allons en voir quelques exemples.

Menu Déroulant (selectInput)

tags$select(
  tagList(
    tags$option(value = "a", "Choix a"),
    tags$option(value = "b", "Choix b"),
    tags$option(value = "c", "Choix c"),
  ),
  onchange = "Shiny.setInputValue('menu_deroulant', this.value, {'priority': 'event'})"
) |>
  as.character()
## [1] "<select onchange=\"Shiny.setInputValue(&#39;menu_deroulant&#39;, this.value, {&#39;priority&#39;: &#39;event&#39;})\">\n  <option value=\"a\" >Choix a</option>\n  <option value=\"b\" >Choix b</option>\n  <option value=\"c\" >Choix c</option>\n</select>"

Ici, le tags$select permet de créer le menu déroulant dans lequel vous pouvez choisir parmis plusieurs options.

Le tagList regroupe toutes les options dans une liste de tags.
Chaque option comprend une valeur (value) et son texte à afficher.

Penser à utiliser des fonctions telles que mapply() pour optimiser l’intégration d’option. Par exemple:

mapply(
    tags$option,
    value = letters[1:10],
    letters[1:10],
    SIMPLIFY = FALSE
  )

L’attribut onchange permet de définir l’action qui sera déclenchée lorsque l’utilisateur change la sélection du menu déroulant.

Ici, lorsque la sélection change, la fonction Shiny.setInputValue sera appelée avec trois arguments :

  • le nom de l’input Shiny à mettre à jour (‘menu_deroulant’),
  • la nouvelle valeur sélectionnée (this.value qui est la valeur de l’option sélectionnée)
  • la priorité de l’évènement ({‘priority’: ‘event’}) qui permet de spécifier la priorité élevée de traitement d’un événement déclenché par un input Shiny

Enfin, il ne faudra pas oublier de convertir l’ensemble de cet élement HTML en une chaîne de caractères pour l’intégrer proprement à votre tableau (as.character)

Intégrons le à notre tableau :

##                                                                                                                                                                                                                                                                                         Menu_deroulant
## 1 <select onchange="Shiny.setInputValue(&#39;menu_deroulant&#39;, this.value, {&#39;priority&#39;: &#39;event&#39;})">\n  <option value="a" >Choix a</option>\n  <option value="b" >Choix b</option>\n  <option value="c" >Choix c</option>\n</select>

Et notre tableau dans notre application shiny:

ui <- fluidPage(
  DTOutput(outputId = "montableau")
)
server <- function(input, output, session) {
  output$montableau <- renderDT({
    data.frame(
      Menu_deroulant =
        tags$select(
          tagList(
            tags$option(value = "a", "Choix a",),
            tags$option(value = "b", "Choix b"),
            tags$option(value = "c", "Choix c"),
          ),
          onchange = "Shiny.setInputValue('menu_deroulant', this.value, {'priority': 'event'})"
        ) |>
        as.character()
    )
  },
  selection = "none",
  escape = FALSE)
}
shinyApp(ui, server)

Comment savoir que ça marche ? Et bien maintenant vous pouvez dès maintenant jouer avec votre input$menu_deroulant (un exemple à la fin de cet article)

Champ de Saisie Numérique (numericInput)

tags$input( type = "number",
            value = 5,
            min = 0,
            onchange = "Shiny.setInputValue('champ_num', this.value, {'priority': 'event'})"
        )  |>
        as.character()
## [1] "<input type=\"number\" value=\"5\" min=\"0\" onchange=\"Shiny.setInputValue(&#39;champ_num&#39;, this.value, {&#39;priority&#39;: &#39;event&#39;})\"/>"

La liste des attributs d’un HTML Input sont détaillés ici

Ce code nous permet de créer un Input numerique dont le minimum est 0, la valeur affichée par défaut sera 5 et dont l’identifiant sera champ_num

Bouton Interactif (actionButton)

tags$button( "Clique !",
    onclick = "Shiny.setInputValue('bouton', this.value, {'priority': 'event'})"
) |> as.character()
## [1] "<button onclick=\"Shiny.setInputValue(&#39;bouton&#39;, this.value, {&#39;priority&#39;: &#39;event&#39;})\">Clique !</button>"

On regroupe le tout

ui <- fluidPage(
  DTOutput(outputId = "montableau")
)
server <- function(input, output, session) {
  output$montableau <- renderDT({
    data.frame(
      Menu_deroulant =
        tags$select(
          tagList(
            tags$option(value = "a", "Choix a"),
            tags$option(value = "b", "Choix b"),
            tags$option(value = "c", "Choix c"),
          ),
          onchange = "Shiny.setInputValue('menu_deroulant', this.value, {'priority': 'event'})"
        ) |>
        as.character(),
      Champ_num = tags$input(
        type = "number",
        value = 5,
        min = 0,
        onchange = "Shiny.setInputValue('champ_num', this.value, {'priority': 'event'})"
      )  |>
        as.character(),
      Bouton = tags$button("Clique !",
                           onclick = "Shiny.setInputValue('bouton', this.value, {'priority': 'event'})") |> as.character()
    )
  },
  selection = "none",
  escape = FALSE)
}
shinyApp(ui, server)

Regardons maintenant l’utilisation des ces inputs dans notre application:

ui <- fluidPage(
  DTOutput(outputId = "montableau")
)
server <- function(input, output, session) {
  observeEvent(input$bouton,  {
    showModal(modalDialog(title = "Well Done!",
                          h3(
                            paste("Tu as choisi la lettre", input$menu_deroulant, 
                                   "et le chiffre", input$champ_num)
                            ))
    )
  })
  output$montableau <- renderDT({
    data.frame(
      Menu_deroulant =
        tags$select(
          tagList(
            tags$option(value = "a", "Choix a"),
            tags$option(value = "b", "Choix b"),
            tags$option(value = "c", "Choix c"),
          ),
          onchange = "Shiny.setInputValue('menu_deroulant', this.value, {'priority': 'event'})"
        ) |>
        as.character(),
      Champ_num = tags$input(
        type = "number",
        value = 5,
        min = 0,
        onchange = "Shiny.setInputValue('champ_num', this.value, {'priority': 'event'})"
      )  |>
        as.character(),
      Bouton = tags$button("Clique !",
                           onclick = "Shiny.setInputValue('bouton', this.value, {'priority': 'event'})") |> as.character()
    )
  },
  selection = "none",
  escape = FALSE)
}
shinyApp(ui, server)

Cela ne fonctionne pas encore complétement comme nous le voudrions …

Initialisation des input au lancement de l’application

Au lancement de l’application shiny, les inputs n’ont pas de valeurs associées. En effet, puisque l’utilisation de onclick permet d’affecter à l’input shiny une valeur uniquement lors de l’action de l’utilisateur.

Il est donc nécessaire d’ajouter un tags$script à chaque input, pour initialiser sa valeur.

Par exemple pour l’input$menu_deroulant avec la valeur “a” :

tags$select(
  tags$script(
    "$(document).ready(function() {
                  $('#menu_deroulant').val('a');
                  Shiny.setInputValue('menu_deroulant', 'a', {priority: 'event'});
              });"
  ),
  tagList(
    tags$option(value = "a", "Choix a"),
    tags$option(value = "b", "Choix b"),
    tags$option(value = "c", "Choix c"),
  ),
  onchange = "Shiny.setInputValue('menu_deroulant', this.value, {'priority': 'event'})"
) |>
  as.character()

Cette initialisation peut être mise en fonction afin d’être réutiliser dans chaque input:

#' Initializing the value of an html input
#'
#' @param inputId 
#' @param value Initial value
#'
#' @return a shiny tag
#' @export
#'
#' @examples
#' init_input(inputId = "menu_deroulant", value = "a")
#' init_input(inputId = "champ_num", value = 5)
init_input <- function(inputId, value) {
  tags$script(
      paste0("$(document).ready(function() {
                  $('#", inputId, "').val('", value, "');
                  Shiny.setInputValue('", inputId, "', '", value, "', {priority: 'event'});
              });")
  )
}

Tadaaaaa ! Notre application fonctionnelle:

ui <- fluidPage(
  DTOutput(outputId = "montableau")
)
server <- function(input, output, session) {
  observeEvent(input$bouton, {
    showModal(modalDialog(
      title = "Well Done!",
      h3(
        paste(
          "Tu as choisi la lettre", input$menu_deroulant,
          "et le chiffre", input$champ_num
        )
      )
    ))
  })
  output$montableau <- renderDT(
    {
      data.frame(
        Menu_deroulant =
          tags$select(
            init_input("menu_deroulant", letters[1]),
            tagList(
              tags$option(value = "a", "Choix a"),
              tags$option(value = "b", "Choix b"),
              tags$option(value = "c", "Choix c"),
            ),
            onchange = "Shiny.setInputValue('menu_deroulant', this.value, {'priority': 'event'})"
          ) |>
            as.character(),
        Champ_num = tags$input(
          init_input("champ_num", 5),
          type = "number",
          value = 5,
          min = 0,
          onchange = "Shiny.setInputValue('champ_num', this.value, {'priority': 'event'})"
        ) |>
          as.character(),
        Bouton = tags$button("Clique !",
          onclick = "Shiny.setInputValue('bouton', this.value, {'priority': 'event'})"
        ) |> as.character()
      )
    },
    selection = "none",
    escape = FALSE
  )
}
shinyApp(ui, server)

Pour aller plus loin:

Partager nous vos expériences avec d’autres inputs … un champ textuel, une case à cocher ….


À propos de l'auteur

Murielle Delmotte

Murielle Delmotte

DATA SCIENTIST – Joueuse de R passionnée


Commentaires


À lire également