Arnaud MILET
http://rpubs.com/D-SIDD/shiny_2
Nous avons vu précédemment que les parties ui et server peuvent être dans le même ficchier ou dans deux fichiers distincts. Dans la suite, nous verrons des applications avec des fichiers distincts. Notons seulement que losqu’on travaille sur un seul fichier on utilise la fonction shinyApp() avec les arguments ui et server:
Le fichier ui.R (ui pour user interface) commence toujours par la fonction shinyUI():
Il s’agit du fichier dans lequel est codé ce qu’on visualise de l’interface (des colonnes, des lignes, des boutons, des sliders, des sorties, …).
R génère du code HTML qui sera interprété par les navigateurs. Le code R générateur de HTML est contenu dans le fichier ui.R.
Par exemple, la fonction shiny::fluidPage() permet de générer les balises html <div class="container-fluid"></div>:
Nous pouvons passer directement du code HTML dans le fichier ui.R en utilisant la fonction HTML:
Vous pouvez aussi utiliser l’objet tags: une liste qui permet de recréer 110 tags HTML:
## [1] "a" "abbr" "address" "area" "article"
## [6] "aside" "audio" "b" "base" "bdi"
## [11] "bdo" "blockquote" "body" "br" "button"
## [16] "canvas" "caption" "cite" "code" "col"
## [21] "colgroup" "command" "data" "datalist" "dd"
## [26] "del" "details" "dfn" "div" "dl"
## [31] "dt" "em" "embed" "eventsource" "fieldset"
## [36] "figcaption" "figure" "footer" "form" "h1"
## [41] "h2" "h3" "h4" "h5" "h6"
## [46] "head" "header" "hgroup" "hr" "html"
## [51] "i" "iframe" "img" "input" "ins"
## [56] "kbd" "keygen" "label" "legend" "li"
## [61] "link" "mark" "map" "menu" "meta"
## [66] "meter" "nav" "noscript" "object" "ol"
## [71] "optgroup" "option" "output" "p" "param"
## [76] "pre" "progress" "q" "ruby" "rp"
## [81] "rt" "s" "samp" "script" "section"
## [86] "select" "small" "source" "span" "strong"
## [91] "style" "sub" "summary" "sup" "table"
## [96] "tbody" "td" "textarea" "tfoot" "th"
## [101] "thead" "time" "title" "tr" "track"
## [106] "u" "ul" "var" "video" "wbr"
Insérer un lien cliquable peut alors se faire avec le tag a:
Si vous le souhaitez, vous pouvez créer votre interface uniquement en code HTML en créant un fichier index.html dans le dossier wwww. Vous pouvez alors vous passer du fichier ui.R:
<application-dir>
|-- app.R
|-- www
|-- index.html
L’exemple “008_html” démontre parfaitement cette possibilité:
Il s’agit de l’équivalent de l’exemple “006-tabsets” se passant de la partie ui grâce à l’existance du fichier index.html.
www/index.html
<!DOCTYPE html>
<html>
<head>
<script src="shared/jquery.js" type="text/javascript"></script>
<script src="shared/shiny.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="shared/shiny.css"/>
</head>
<body>
<h1>HTML UI</h1>
<p>
<label>Distribution type:</label><br />
<select name="dist">
<option value="norm">Normal</option>
<option value="unif">Uniform</option>
<option value="lnorm">Log-normal</option>
<option value="exp">Exponential</option>
</select>
</p>
<p>
<label>Number of observations:</label><br />
<input type="number" name="n" value="500" min="1" max="1000" />
</p>
<h3>Summary of data:</h3>
<pre id="summary" class="shiny-text-output"></pre>
<h3>Plot of data:</h3>
<div id="plot" class="shiny-plot-output"
style="width: 100%; height: 300px"></div>
<h3>Head of data:</h3>
<div id="table" class="shiny-html-output"></div>
</body>
</html>Il ne reste plus qu’à construire l’application avec la fonction shinyApp():
> Pour un statisticien, il semble donc plus simple de se passer du code HTML et de passer directement par les fonctions que R propose.
Les widgets sont les composants de l’interface graphique qui permettent aux utilisateurs de fournir des valeurs aux paramètres d’entrée.
Vous pouvez avoir un aperçu de l’ensemble des widgets disponibles pour Shiny via la Shiny Widgets Gallery.
Pour choisir une ou plusieurs valeurs parmi plusieurs valeurs prédéfinies, plusieurs widgets sont disponibles:
Attention, bien qu’ils soient construits pour entrer des valeurs numériques, ils fournissent une valeur de type chaîne de caractère au serveur (ie “33” et non 33).
Les triggers permettent à l’utilisateur de lancer certains processus. Ils sont particulièrement utiles pour lancer des processus un peu longs. cf. partie 3 sur la réactivité
Les inputs (entrées) servent à collecter des informations auprès de l’utilisateur:
Chaque entrée (input) a au moins un argument permettant de l’identifier (inputId). Cet argument sera utile pour récupérer l’information côté server:
selectInput("var",
label = "Choose a variable to display",
choices = c("Percent White",
"Percent Black",
"Percent Hispanic",
"Percent Asian"),
selected = "Percent White")Ces fonctions sont toutes insérées dans l’objet ui. Elles génèrent du code HTML interprétable par les navigateurs:
shinyUI(
fluidPage(
titlePanel("censusVis"),
sidebarLayout(
sidebarPanel(
helpText("Create demographic maps with
information from the 2010 US Census."),
selectInput("var",
label = "Choose a variable to display",
choices = c("Percent White",
"Percent Black",
"Percent Hispanic",
"Percent Asian"),
selected = "Percent White"),
sliderInput("range",
label = "Range of interest:",
min = 0, max = 100, value = c(0, 100))
),
mainPanel(
textOutput("selected_var")
)
)
)
)shinyWidgets est un package permettant d’intégrer des switchbutton, des jolies boites de dialogues, des select picker…. Plus d’informations ici: https://github.com/dreamRs/shinyWidgets
Une liste très complète (mais probablement non exhaustive) est disponible ici: https://github.com/nanxstats/awesome-shiny-extensions#special-input
Shiny propose une famille de fonctions qui definissent un objet (un emplacement sur l’interface) comme étant le résultat d’une sortie (output). Chaque fonction **Output créé une sortie spécifique qui sera alimentée par le serveur par une fonction de type render.
Quelques exemples:
| Output.function | Génère |
|---|---|
| dataTableOutput | DataTable |
| htmlOutput | HTML |
| imageOutput | image |
| plotOutput | plot |
| tableOutput | table |
| textOutput | text |
| uiOutput | HTML |
| verbatimTextOutput | text |
| leafletOutput | Map |
Chaque sortie (output) a au moins un argument permettant de l’identifier (output id). L’output est en quelque sorte un espace que l’on reserve sur notre interface pour une sortie (tableau, graphique, texte, …). Cet emplacement reste vide jusqu’à ce que la sortie soit générée côté serveur et renvoyer vers l’interface. L’argument id est donc utile pour générer la sortie côté serveur et pour la renvoyer vers le bon emplacement côté interface:
Le fichier server.R commence toujours par la fonction shinyServer():
La fonction shinyServer() est systématiquement suivie de function(input, output) {} qui permet de récupérer les inputs et outputs de l’interface ui:
Toutes les informations contenues dans les inputs sont stockées dans la liste input. Pour récuperer l’information contenue dans chaque input, il suffit d’écrire côté server: input$“inputID” :
input$bins
#input$bins se réfère à l'information contenue dans le selectinput ayant pour identifiant 'bins' coté uiPour renvoyer une information, nous utilisons les fonction de type render (associées aux fonctions de type Output). Si côté ui nous avons un output de type plot générer par la fonction plotOutput dont l’identifiant est ‘displot’ alors le server va l’alimenter par la fonction renderPlot qui l’affectera:
On utilise les accolades lorsqu’il y a plusieurs opérations et que l’on souhaite passer à la ligne entre chaque pour faciliter la lecture du code (sinon, il suffit de mettre un “;” entre chaque opération).
Faire le lien entre les inputs et les outputs revient à utiliser les caractéristiques “reactive” de shiny: dès que l’input sera modifié, il sera pris en compte dans l’output:
render**() et **Output()Dans les exemples précédents, on a vu différents exemples d’outputs possibles. Notamment, des outputs de type
Il est également possible de produire des outputs de type
Remarquez, dans le graphique ci-dessus, comme une fonction renderXxx() côté Server correspond à une fonction xxxOutput() côté UI.
Rappel: voici comment ça se passe au niveau syntaxe:
L’emplacement où vous définirez vos objets déterminera où ils seront visibles. Il y a 3 niveaux de visibilité:
La fonction est appelée à l’ouverture de chaque session. L’objet session est un objet utilisé pour un contrôle plus précis de la session de chaque utilisateur
Chaque objet défini à l’intérieur de cette fonction est défini pour une session indépendamment des autres utilisateurs (sessions).
Vous pouvez avoir besoin de partager des objets entre sessions: nombre d’utilisateurs connectés simultanément ou des données volumineuses qu’on ne chargerait qu’une fois,… Il faut placer ces objets en dehors du serveurs (en dehors de la fonction shinyServer()).
# A read-only data set that will load once, when Shiny starts, and will be
# available to each user session
bigDataSet <- read.csv("bigdata.csv")
# A non-reactive function that will be available to each user session
utilityFunction <- function(x) {
# Function code here
# ...
}Si un objet change, alors le changement sera visible par tous les utilisateurs si vous utilisez <<- sinon le changement ne sera visible que par l’utilisateur dans sa session:
varA <- 1
varB <- 1
listA <- list(X = 1, Y = 2)
listB <- list(X = 1, Y = 2)
server <- function(input, output, session) {
# Create a local variable varA, which will be a copy of the shared variable
# varA plus 1. This local copy of varA is not be visible in other sessions.
varA <- varA + 1
# Modify the shared variable varB. It will be visible in other sessions.
varB <<- varB + 1
# Makes a local copy of listA
listA$X <- 5
# Modify the shared copy of listB
listB$X <<- 5
# ...
}Vous pouvez créer un fichier global.R qui sera exécuter dès le lancement d’une application. Cela est similaire à l’écriture en dehors de la fonction shinyServer() sauf qu’ici les objets sont aussi visibles côté ui.
Il existe des valeurs non saisies (url, résolution,…) qui sont stockées dans un objet appelé session$clientData.
ui <- bootstrapPage(
h3("URL components"),
verbatimTextOutput("urlText"),
h3("Parsed query string"),
verbatimTextOutput("queryText")
)
server <- function(input, output, session) {
# Return the components of the URL in a string:
output$urlText <- renderText({
paste(sep = "",
"protocol: ", session$clientData$url_protocol, "\n",
"hostname: ", session$clientData$url_hostname, "\n",
"pathname: ", session$clientData$url_pathname, "\n",
"port: ", session$clientData$url_port, "\n",
"search: ", session$clientData$url_search, "\n"
)
})
# Parse the GET query string
output$queryText <- renderText({
query <- parseQueryString(session$clientData$url_search)
# Return a string with key-value pairs
paste(names(query), query, sep = "=", collapse=", ")
})
}
shinyApp(ui, server)
Le code suivant affiche toutes les variables de session$clientData.
Notons que l’on peut transmettre des variables via l’url d’une application en complétant l’url initiale par ?mavariable1=valeur1&mavariable2=valeur2&mavariable2=valeur2. Les valeurs sont stockées dans l’objet session$clientData$url_search:
ui <- pageWithSidebar(
headerPanel("Shiny Client Data"),
sidebarPanel(
sliderInput("obs", "Number of observations:",
min = 0, max = 1000, value = 500)
),
mainPanel(
h3("clientData values"),
verbatimTextOutput("clientdataText"),
plotOutput("myplot")
)
)
server <- function(input, output, session) {
# Store in a convenience variable
cdata <- session$clientData
# Values from cdata returned as text
output$clientdataText <- renderText({
cnames <- names(cdata)
allvalues <- lapply(cnames, function(name) {
paste(name, cdata[[name]], sep = " = ")
})
paste(allvalues, collapse = "\n")
})
# A histogram
output$myplot <- renderPlot({
hist(rnorm(input$obs), main = "Generated in renderPlot()")
})
}
shinyApp(ui, server)
La fonction parseQueryString() permet de renvoyer une liste des valeurs données dans l’url:
## $mavariable1
## [1] "valeur1"
##
## $mavariable2
## [1] "valeur2"
##
## $mavariable2
## [1] "valeur2"
L’insertion des lignes ci-dessous du côté server permet de récupérer uniquement les valeurs entrées en url et de les renvoyer vers un output de type texte appelé par exemple ici queryText:
Le code suivant permet de lire les données contenues dans le fichier landuse_extract_com.rds. Ce fichier est issu d’une extraction de la carte collaborative OpenStreetMap. Il contient un objet cartographique (class sf) dont les polygones correpondent aux parcelles de la ville de Montpellier pour lesquelles des contributeurs ont attribué un type d’usage (résidence, bassin, terre exploitées, millitaire, vignes, …).
## Linking to GEOS 3.6.1, GDAL 2.2.3, PROJ 4.9.3
## -- Attaching packages ----------------------------------- tidyverse 1.3.0 --
## v ggplot2 3.2.1 v purrr 0.3.3
## v tibble 2.1.3 v dplyr 0.8.3
## v tidyr 1.0.0 v stringr 1.4.0
## v readr 1.3.1 v forcats 0.4.0
## -- Conflicts -------------------------------------- tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag() masks stats::lag()
landuse_extract_com <- readRDS("../Elements/data/landuse_extract_com.rds")
landuse_extract_com_resu <- landuse_extract_com%>%
group_by(usage)%>%
summarise(superficie=sum(superficie))
library(tmap)## Warning: package 'tmap' was built under R version 3.6.2
Le code suivant permet d’afficher un histogramme de la répartition des superficies pour les parcelles répertoriées “farmland” dans OpenStreetMap pour Montpellier:
landuse_extract_com%>%
filter(usage== "farmland")%>%
as.data.frame()%>%
"["(,"superficie")%>%
hist(main=paste0("Histogramme de la répartition\ndes superficies de type \"","farmland","\" à Montpellier"),
ylab="Fréquence",
include.lowest=T)landuse.csv pour remplir l’input. En testant, selectInput() et selectizeInput(), quels sont les différences que vous pouvez noter? La page suivante https://shiny.rstudio.com/gallery/selectize-examples.html pourra certainement vous éclairer.TRUEou FALSE si le nombre de polygones (ou parcelles) de type “conservation” contenues dans la base landuse_extract_com est égal ou non à 0:## [1] FALSE
En intégrant un test côté server, faites en sorte qu’il n’y ait plus d’erreur renvoyée.
shinyWidgets permet notamment de renvoyer des messages d’alerte aux utilisateurs. Vous pouvez installer ce package via la commande devtools::install_github("dreamRs/shinyWidgets") En utilisant la fonction sendSweetAlert de ce package, renvoyez un message d’alerte quand l’utilisateur sélectionne un type d’usage ne correspondant à aucune parcelle (problème ci-dessus)resume<-landuse_extract_com%>%
as.data.frame()%>%
group_by(usage)%>%
summarise(n=n(),
surface_tot=sum(superficie),
surface_min=min(superficie),
surface_max=max(superficie),
surface_moy=mean(superficie),
surface_q25=quantile(superficie,0.25),
surface_med=quantile(superficie,0.5),
surface_q75=quantile(superficie,0.75),
surface_ICQR=IQR(superficie))%>%
rbind(
landuse_extract_com%>%
as.data.frame()%>%
summarise(n=n(),
surface_tot=sum(superficie),
surface_min=min(superficie),
surface_max=max(superficie),
surface_moy=mean(superficie),
surface_q25=quantile(superficie,0.25),
surface_med=quantile(superficie,0.5),
surface_q75=quantile(superficie,0.75),
surface_ICQR=IQR(superficie))%>%
mutate(usage="Total")%>%
select(usage,everything())
)
resume<-resume%>%
mutate(part=n/resume%>%filter(usage=="Total")%>%as.data.frame()%>%"["(,"n"),
part_surf=surface_tot/resume%>%filter(usage=="Total")%>%as.data.frame()%>%"["(,"surface_tot"))%>%
select(-usage)%>%
filter(usage=="farmland")%>%
setNames(c("Nombre","Surface totale","Surface minimum","Surface maximum","Surface moyenne","Surface Q25","Surface médiane","Surface Q75","Ecart interquartile de Surface","Part du nombre total de parcelles","Part de la superficie totale des parcelles"))| Nombre | 1.000000e+02 |
| Surface totale | 1.993886e+06 |
| Surface minimum | 9.127238e+02 |
| Surface maximum | 1.476459e+05 |
| Surface moyenne | 1.993886e+04 |
| Surface Q25 | 5.865825e+03 |
| Surface médiane | 1.228544e+04 |
| Surface Q75 | 2.261751e+04 |
| Ecart interquartile de Surface | 1.675169e+04 |
| Part du nombre total de parcelles | 2.440810e-02 |
| Part de la superficie totale des parcelles | 3.498910e-02 |
Intégrez le tableau ci-dessus dans l’interface.
Stats_desc <- c("Nombre","Surface totale","Surface minimum","Surface maximum","Surface moyenne","Surface Q25","Surface médiane","Surface Q75","Ecart interquartile de Surface","Part du nombre total de parcelles","Part de la superficie totale des parcelles")
knitr::kable(Stats_desc,col.names="Statistiques descriptives")| Statistiques descriptives |
|---|
| Nombre |
| Surface totale |
| Surface minimum |
| Surface maximum |
| Surface moyenne |
| Surface Q25 |
| Surface médiane |
| Surface Q75 |
| Ecart interquartile de Surface |
| Part du nombre total de parcelles |
| Part de la superficie totale des parcelles |
resume_pie <-resume%>%
mutate(part_surf=as.numeric(part_surf))%>%
filter(usage != "Total")%>%
select(usage,part_surf)
library(plotly)##
## Attaching package: 'plotly'
## The following object is masked from 'package:ggplot2':
##
## last_plot
## The following object is masked from 'package:stats':
##
## filter
## The following object is masked from 'package:graphics':
##
## layout
p <- plot_ly(resume_pie, labels = ~usage, values = ~part_surf, type = 'pie') %>%
layout(title = 'Répartition des usages',
xaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE),
yaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE))
planduse_extract_com <- landuse_extract_com%>%
mutate(usage=as.factor(usage),
Infos = paste0(usage,": <br/>",round(superficie,0)," m<sup>2</sup>"),
Identifiant = 1:nrow(landuse_extract_com))
library(leaflet)
factpal <- colorFactor("viridis", levels = levels(landuse_extract_com$usage),
na.color = "#808080")
leaflet(data = landuse_extract_com) %>%
addProviderTiles(providers$Stamen.Toner)%>%
addPolygons(
layerId = ~Identifiant,
fillColor =~factpal(usage),
weight = 2,
opacity = 1,
color = "white",
dashArray = "3",
fillOpacity = 0.7,
popup = ~Infos
)%>%
addLegend("bottomright", pal = factpal, values = ~usage,
title = "Utilisation des parcelles",
opacity = 1
)En utilisant les fonctions leafletOutput() côté ui et renderLeaflet() côté server, intégrez cette carte interactive à la suite de l’application. Vous pouvez également vous appuyer sur l’aide disponible sur Rstudio: https://rstudio.github.io/leaflet/shiny.html.
plot_ly(resume_pie, labels = ~usage, values = ~part_surf, type = 'pie',marker = list(colors=~factpal(usage))) %>%
layout(title = 'Répartition des usages',
legend=list(traceorder="normal"),
xaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE),
yaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE))A l’issue de cet exercice, l’application pourrait ressembler à: