#==============================================================================#
# ElementR - Séance Shiny du 16 décembre 2025
# Création pas à pas d'une application shiny
#
# ÉTAPE 6 : Réécriture de l'app pour inclure le chargement d'un gpkg polygonal
#
#
# AD, novembre 2025
#==============================================================================#
# librairies ----
library(sf)
library(tidyverse)
library(paletteer)
library(leaflet)
library(mapsf)
library(shiny)
library(bslib)
# UI ----
# Construction de l'interface utilisateur (ui) avec 3 plots
ui <- page_sidebar(
theme = bs_theme(
bootswatch = "journal",
bg = "#fff",
fg = "#a21818",
primary = "#7B7474",
secondary = "#aaa",
font_scale = 1.2
),
# Nom de la page
title = "Exploration univariée",
# Panneau de contrôle : les contrôleurs apparaissent après l'import des données
sidebar = sidebar(
# sélecteur 0 : bouton upload
fileInput(
inputId = "upload_gpkg",
label = "Charger vos données au format geopackage",
accept = ".gpkg"
),
# Les sélecteurs sont définis côté server après l'import des données
# 0 bis : choix du layer
uiOutput(outputId = "layer_selector"),
# 1er sélecteur : la variable à explorer
uiOutput(outputId = "column_selector"),
# 1er sélecteur bis : stock -> taux si besoin
uiOutput(outputId = "density_selector"),
# 2e sélecteur : la discrétisation
uiOutput(outputId = "discret_selector"),
# 3e sélecteur : le nombre de classe
uiOutput(outputId = "nk_selector")
),
# Plots
# Colonne 1 : la carte
layout_columns(
# Carte choro
card(
card_header("Distribution géographique de la variable"),
leafletOutput(outputId = "map")
),
# Colonne 2 : l'histo et la table
layout_columns(
# Barplot
card(
card_header("Distribution statistique de la variable"),
plotOutput(outputId = "barplot")
),
# Table
card(
card_header("Tableau de données"),
tableOutput(outputId = "table")
),
col_widths = c(12,12)
)
),
cols_width = c(12,6)
)
# SERVER ----
# Construction des plots à afficher dans l'UI : renderPlot(), renderLeaflet(), rendertable()
# + construction des contrôleurs renderUI()
# + définition des variables reactive()
server <- function(input, output, session) {
# 1. Lister les layers disponibles dans le gpkg
layers_disponibles <- reactive({
# req() : s'assurer que l'élément est présent avant de poursuivre
# (le choix du fichier dans ce cas)
req(input$upload_gpkg)
st_layers(input$upload_gpkg$datapath)$name
})
# 2. Sélecteur de layer (seulement si plusieurs layers)
output$layer_selector <- renderUI({
req(layers_disponibles())
layers <- layers_disponibles()
# Si un seul layer, pas besoin de sélecteur
if(length(layers) == 1) {
return(NULL)
}
# Sinon, afficher le sélecteur
selectInput(
inputId = "selected_layer",
label = "Choisissez une couche",
choices = layers,
selected = layers[1]
)
})
# 3. Lecture des données avec le layer sélectionné
donnees <- reactive({
req(input$upload_gpkg, layers_disponibles())
# Si un seul layer, le charger directement
if(length(layers_disponibles()) == 1) {
st_read(input$upload_gpkg$datapath, layer = layers_disponibles()[1])
} else {
# Sinon, attendre la sélection
req(input$selected_layer)
st_read(input$upload_gpkg$datapath, layer = input$selected_layer)
}
})
# 4a. Ajout du sélecteur de variable
output$column_selector <- renderUI({
# S'assurer de la présence des données
req(donnees())
# Ne garder que les variables quantitatives
cols <- donnees() %>%
select_if(., is.numeric) %>%
st_drop_geometry() %>%
names(.)
# Définir le contrôleur
selectInput(
inputId = "variable",
label = "Choisissez une variable à explorer",
choices = cols,
multiple = FALSE,
selected = NULL
)
})
# 4b. Ajout du checkbox
output$density_selector <- renderUI({
# S'assurer de la présence des données et de la variable
req(donnees(), input$variable)
# Définir le contrôleur
checkboxInput(
inputId = "densite",
label = "Calculer une densité (stock/km2)"
)
})
# 4c. Ajout du contôleur de la discrétisation
output$discret_selector <- renderUI({
# S'assurer de la présence des données et de la variable
req(donnees(), input$variable)
# Définir le contrôleur
selectInput(
inputId = "discretisation",
label = "Choisissez une méthode de discrétisation",
choices = c("equal", "quantile", "jenks"),
selected = "quantile"
)
})
# 4d. Ajout du contrôleur du nombre de classe
output$nk_selector <- renderUI({
# S'assurer de la présence des données et de la variable
req(donnees(), input$variable)
# Définir le contrôleur
sliderInput(
inputId = "nclasse",
label = "Choisissez un nombre de classe",
value = 5,
min = 2,
max = 10
)
})
# 5. Récupérer les valeurs sélectionnées en reactive()
# 5a. stockage du nom de la variable (en caractère)
var_selected <- reactive({
req(input$variable)
as.character(input$variable)
})
# 5b. Transfo de la variable de stock en taux si checkbox est checked
data_to_plot <- reactive({
# S'assurer de la présence des variables réactives suivantes
req(donnees(), var_selected())
req(!is.null(input$densite))
# Pour simplifier l'écriture
data_sf <- donnees()
if(input$densite){
# Calculer la densité et l'ajouter comme nouvelle colonne
data_sf <- data_sf %>%
mutate(
area = as.numeric(st_area(.))/1e6,
var_plot = .data[[var_selected()]] / area
)
} else {
# Renommer la variable sélectionnée en "var_plot"
data_sf <- data_sf %>%
mutate(var_plot = .data[[var_selected()]])
}
return(data_sf)
})
# 5c. Les bornes de classe
bks <- reactive({
req(data_to_plot(), input$nclasse, input$discretisation)
mf_get_breaks(
x = data_to_plot()$var_plot,
nbreaks = input$nclasse,
breaks = input$discretisation
)
})
# 5d. La variable visuelle
pal <- reactive({
req(input$nclasse)
paletteer_d("MoMAColors::Exter", n = input$nclasse)
})
# 6. La carte Leaflet
# 6a. Calculer le centroide de la couche pour le setview
centro <- reactive({
req(donnees())
donnees() %>%
st_bbox() %>%
st_as_sfc() %>%
st_centroid() %>%
st_coordinates()
})
# 6b. Initialisation de la carte
output$map <- renderLeaflet({
# La carte
leaflet() %>%
addTiles(group = "OSM") %>%
addProviderTiles(providers$CartoDB.Positron, group = "OSM/CARTO") %>%
setView(lng = centro()[1], lat = centro()[2], zoom = 10) %>%
addLayersControl(
baseGroups = c("OSM/CARTO", "OSM"),
overlayGroups = c("Secteurs"),
options = layersControlOptions(collapsed = FALSE, sortLayers = TRUE)
) %>%
addScaleBar(
position = "bottomright",
options = scaleBarOptions(imperial = FALSE)
)
})
# 7. Mise à jour de la carte selon les sélections
observe({
req(data_to_plot(), bks(), pal())
data_sf <- data_to_plot()
# Discrétisation de la carte leaflet
pal_leaflet <- colorBin(
palette = as.character(pal()),
domain = data_sf$var_plot,
bins = bks()
)
# Discrétisation avec valeurs arrondies pour la légende
pal_leaflet_round <- colorBin(
palette = as.character(pal()),
domain = data_sf$var_plot,
bins = round(bks())
)
leafletProxy("map", session) %>%
clearGroup("Secteurs") %>%
clearControls() %>%
addPolygons(
data = data_sf,
fillColor = ~ pal_leaflet(var_plot),
fillOpacity = .8,
color = "#424242",
weight = 1,
popup = ~ paste0(var_selected(), " : ",
ifelse(input$densite,
round(var_plot, 2),
round(var_plot))),
group = "Secteurs"
) %>%
addLegend(
pal = pal_leaflet_round,
values = data_sf$var_plot,
opacity = 1,
title = ifelse(input$densite,
paste0("Densité - ", var_selected()),
var_selected()),
position = "bottomleft"
)
})
# 8. L'histogramme
output$barplot <- renderPlot({
req(data_to_plot(), bks(), pal())
data_to_plot() %>%
ggplot() +
geom_histogram(
aes(x = var_plot),
breaks = bks(),
fill = pal(),
color = "white") +
labs(x = ifelse(input$densite, "Densité (stock/km²)", var_selected()),
y = "n secteurs") +
theme_minimal(base_size = 16)
})
# 9. La table
output$table <- renderTable({
req(donnees())
donnees_table <- donnees() %>%
st_drop_geometry()
return(donnees_table)
})
}
# Run the application
shinyApp(ui = ui, server = server)