mirror of
https://github.com/ArthurDanjou/ArtStudies.git
synced 2026-01-14 13:54:06 +01:00
Enhance tp2 Rmd (histogram, interactive maps, choropleth fixes); add TP3 Shiny apps and project files; update .gitignore; add shap (+cloudpickle, numba, llvmlite, tqdm, slicer) to pyproject.toml and uv.lock; remove generated tp2 HTML/assets
This commit is contained in:
0
M2/Data Visualisation/.gitignore
vendored
Normal file
0
M2/Data Visualisation/.gitignore
vendored
Normal file
@@ -48,7 +48,6 @@ library(tidyr)
|
||||
library(rmarkdown)
|
||||
library(ggthemes)
|
||||
library(cowplot)
|
||||
|
||||
```
|
||||
|
||||
# Objectifs du TP
|
||||
@@ -91,7 +90,7 @@ ont lieu tous les 5 ans.
|
||||
Dans un premier temps, il faut installer le package et le charger.
|
||||
|
||||
```{r}
|
||||
# install.packages("gapminder") #nolint
|
||||
# install.packages("gapminder")
|
||||
library(gapminder)
|
||||
```
|
||||
|
||||
@@ -159,6 +158,12 @@ ggplot(data = gapminder, aes(x = lifeExp)) +
|
||||
geom_density()
|
||||
```
|
||||
|
||||
```{r}
|
||||
ggplot(data = gapminder, aes(x = lifeExp)) +
|
||||
geom_histogram(aes(y = ..density..), bins = 30) +
|
||||
geom_density()
|
||||
```
|
||||
|
||||
### Cartes
|
||||
|
||||
Afin de visualiser plus précisément les caractéristiques régionales de
|
||||
@@ -226,8 +231,9 @@ library(tidyverse)
|
||||
path <- getwd()
|
||||
accidents <- read_csv(
|
||||
paste0(
|
||||
path,
|
||||
"/data/accidentsVelo.csv"),
|
||||
path,
|
||||
"/data/accidentsVelo.csv"
|
||||
),
|
||||
col_types = cols(Num_Acc = col_double(), date = col_date(format = "%Y-%m-%d"))
|
||||
)
|
||||
|
||||
@@ -245,8 +251,13 @@ accidents <- accidents |>
|
||||
# correct some issues with variables `hrmn`
|
||||
issue <- which(str_length(accidents$hrmn) == 4)
|
||||
correct <- accidents$hrmn[issue]
|
||||
correct <- paste0("0", str_sub(correct, 1, 1), ":",
|
||||
str_sub(correct, 2, 2), str_sub(correct, 4, 4))
|
||||
correct <- paste0(
|
||||
"0",
|
||||
str_sub(correct, 1, 1),
|
||||
":",
|
||||
str_sub(correct, 2, 2),
|
||||
str_sub(correct, 4, 4)
|
||||
)
|
||||
accidents$hrmn[issue] <- correct
|
||||
|
||||
# Extract hour
|
||||
@@ -257,8 +268,9 @@ accidents <- accidents |>
|
||||
# mapping table for french departments
|
||||
departements_francais <- read_excel(
|
||||
paste0(
|
||||
path,
|
||||
"/data/departements-francais.xlsx"),
|
||||
path,
|
||||
"/data/departements-francais.xlsx"
|
||||
),
|
||||
col_types = c("text", "text", "text")
|
||||
)
|
||||
```
|
||||
@@ -321,9 +333,57 @@ intéressantes sont à extraire de ce jeu de données.
|
||||
(latitude, longitude) en France sur toute la période en modulant la
|
||||
gravité.
|
||||
|
||||
```{r}
|
||||
library(mapview)
|
||||
library(sf)
|
||||
|
||||
## Remove NA
|
||||
df_map_dyn <- accidents |>
|
||||
filter(!is.na(lat) & !is.na(long)) |>
|
||||
mutate(
|
||||
lat = as.numeric(str_replace_all(str_trim(lat), ",", ".")),
|
||||
long = as.numeric(str_replace_all(str_trim(long), ",", "."))
|
||||
) |>
|
||||
filter(!is.na(lat) & !is.na(long))
|
||||
|
||||
# Make map and print it
|
||||
mymap <- st_as_sf(df_map_dyn[1:5000, ], coords = c("long", "lat"), crs = 4326)
|
||||
mapview(
|
||||
mymap,
|
||||
cex = 2,
|
||||
legend = TRUE,
|
||||
layer.name = "Gravité",
|
||||
zcol = "grav",
|
||||
map.types = "OpenStreetMap"
|
||||
)
|
||||
```
|
||||
|
||||
2. Faire deux variantes de cette carte, selon le caractère urbain ou
|
||||
non des accidents.
|
||||
|
||||
```{r}
|
||||
# Map for urban accidents
|
||||
df_map_urban <- df_map_dyn |>
|
||||
mutate(
|
||||
agg = dplyr::if_else(
|
||||
agg == 1,
|
||||
"urbanisation",
|
||||
"agglomération",
|
||||
missing = NA_character_
|
||||
)
|
||||
)
|
||||
|
||||
# Make map and print it
|
||||
mymap_urban <- st_as_sf(df_map_urban, coords = c("long", "lat"), crs = 4326)
|
||||
mapview(
|
||||
mymap_urban,
|
||||
cex = 2,
|
||||
legend = TRUE,
|
||||
layer.name = "Urbanisation",
|
||||
zcol = "agg"
|
||||
)
|
||||
```
|
||||
|
||||
3. Commenter ces figures.
|
||||
|
||||
4. Quelles limites voyez-vous à cette représentation ?
|
||||
@@ -333,21 +393,6 @@ Voici un premier code à trou pour vous aider. Pour alléger les temps de
|
||||
production afficher uniquement quelques points. Vous pourrez ajouter
|
||||
l'ensemble du jeu de données quand votre code sera finalisé.
|
||||
|
||||
```{r}
|
||||
library(mapview)
|
||||
library(sf)
|
||||
|
||||
## Remove NA
|
||||
df_map_dyn <- accidents |>
|
||||
filter(!is.na(lat) & !is.na(long)) |>
|
||||
na.omit()
|
||||
|
||||
# Make map and print it
|
||||
mymap <- st_as_sf(df_map_dyn, coords=c("long", "lat"), crs=4326)
|
||||
mapview(mymap, cex = 2, layer.name = "Gravité",
|
||||
zcol = "grav",legend = TRUE )
|
||||
```
|
||||
|
||||
#### Carte choroplèthe {.unnumbered}
|
||||
|
||||
::: exercise-box
|
||||
@@ -360,9 +405,9 @@ mapview(mymap, cex = 2, layer.name = "Gravité",
|
||||
|
||||
Voici un premier code à trou pour vous aider.
|
||||
|
||||
```{r, eval = F}
|
||||
# get french map - level nuts2
|
||||
fr <- gisco_get_nuts(resolution = "20", country = ???, nuts_level = ???) |>
|
||||
```{r}
|
||||
# get french map - level nuts3
|
||||
fr <- gisco_get_nuts(resolution = "20", country = "FRA", nuts_level = 3) |>
|
||||
mutate(res = "20M")
|
||||
|
||||
# Remove white-space to avoid errors.
|
||||
@@ -375,23 +420,28 @@ fr <- fr |>
|
||||
|
||||
# Merge and remove departements outside metropolitan France
|
||||
fr_map <- fr |>
|
||||
left_join(???) |>
|
||||
filter(! dep %in% c("971", ???) )
|
||||
left_join(departements_francais, by = c("NUTS_NAME" = "dep_name")) |>
|
||||
filter(!dep %in% c("971", "972", "973", "974", "976"))
|
||||
|
||||
# count the number of accidents
|
||||
df_acc <- ???
|
||||
df_acc <- accidents |>
|
||||
filter(!is.na(dep)) |>
|
||||
group_by(dep) |>
|
||||
summarise(n = n())
|
||||
|
||||
# merge statistics with the map
|
||||
map_acc <- fr_map |>
|
||||
left_join(df_acc, by = c("dep" = "dep"))
|
||||
left_join(df_acc, by = c("dep" = "dep"))
|
||||
|
||||
# map with all accidents
|
||||
g_map_acc <- ggplot(map_acc) +
|
||||
geom_sf(???) +
|
||||
scale_fill_viridis_c(option = "viridis") +
|
||||
labs(title = "Carte des accidents de vélo",
|
||||
subtitle = "Année 2005-2021",
|
||||
fill = "Nombre d'accidents") +
|
||||
geom_sf(aes(fill = n)) +
|
||||
scale_fill_viridis_c(option = "viridis") +
|
||||
labs(
|
||||
title = "Carte des accidents de vélo",
|
||||
subtitle = "Année 2005-2021",
|
||||
fill = "Nombre d'accidents"
|
||||
) +
|
||||
theme_void()
|
||||
g_map_acc
|
||||
```
|
||||
|
||||
File diff suppressed because one or more lines are too long
470
M2/Data Visualisation/tp2/tp2.rmarkdown
Normal file
470
M2/Data Visualisation/tp2/tp2.rmarkdown
Normal file
@@ -0,0 +1,470 @@
|
||||
---
|
||||
title: "Manipulation des graphiques"
|
||||
author: "Quentin Guibert"
|
||||
date: "Année 2025-2026"
|
||||
institute: "Université Paris-Dauphine | Master ISF"
|
||||
lang: fr
|
||||
link-citations: true
|
||||
output:
|
||||
rmdformats::robobook:
|
||||
highlight: kate
|
||||
use_bookdown: true
|
||||
css: style.css
|
||||
lightbox : true
|
||||
gallery: true
|
||||
code_folding: show
|
||||
theme: flatly
|
||||
toc_float:
|
||||
collapsed: no
|
||||
editor_options:
|
||||
markdown:
|
||||
wrap: 72
|
||||
---
|
||||
|
||||
```{r setup, include=FALSE}
|
||||
## Global options
|
||||
knitr::opts_chunk$set(
|
||||
cache = FALSE,
|
||||
warning = FALSE,
|
||||
message = FALSE,
|
||||
fig.retina = 2
|
||||
)
|
||||
options(encoding = "UTF-8")
|
||||
```
|
||||
|
||||
```{r, echo = FALSE, fig.keep= 'none'}
|
||||
# Chargement des librairies graphiques
|
||||
library(lattice)
|
||||
library(grid)
|
||||
library(ggplot2)
|
||||
require(gridExtra)
|
||||
library(locfit)
|
||||
library(scales)
|
||||
library(formattable)
|
||||
library(RColorBrewer)
|
||||
library(plotly)
|
||||
library(dplyr)
|
||||
library(tidyr)
|
||||
library(rmarkdown)
|
||||
library(ggthemes)
|
||||
library(cowplot)
|
||||
```
|
||||
|
||||
# Objectifs du TP
|
||||
|
||||
L'objectif de ce TP vise à manipuler et utiliser à bon escient les
|
||||
différents types de graphiques du package **ggplot2** (et éventuellement
|
||||
des packages associés).
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
# Prérequis
|
||||
|
||||
- Avoir installer `R` [ici](https://www.r-project.org/).
|
||||
- Avoir installer un IDE, par exemple `RStudio`
|
||||
[ici](https://www.rstudio.com/).
|
||||
- Créer un nouveau projet (`File`, puis `New Projet`) dans un dossier
|
||||
sur votre ordinateur.
|
||||
- Télécharger [ici](https://moodle.psl.eu/course/view.php?id=21176)
|
||||
les fichiers nécessaires au TD.
|
||||
|
||||
Vous pouvez ensuite écrire vos codes soit :
|
||||
|
||||
- En ouvrant un nouveau script `.R` ;
|
||||
- En ouvrant le ouvrant le rapport Rmarkdown
|
||||
`4-td_graphiques - enonce`. Certains codes sont partiels et sont à
|
||||
compléter (indication `???`). N'oubliez pas de modifier l'option
|
||||
`eval = TRUE` pour que les calculs puissent être réalisés.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
# Analyse du lien espérance de vie et GDP
|
||||
|
||||
Dans ce TP, le jeu de données utilisé est le jeu `gapminder` du package
|
||||
**gapminder**. Il comprend des séries de données de PIB par habitant et
|
||||
d'espérance de vie par pays sur la période 1952-1990. Les observations
|
||||
ont lieu tous les 5 ans.
|
||||
|
||||
## Données
|
||||
|
||||
Dans un premier temps, il faut installer le package et le charger.
|
||||
|
||||
```{r}
|
||||
# install.packages("gapminder")
|
||||
library(gapminder)
|
||||
```
|
||||
|
||||
Ce jeu de données contient 1706 observations où chaque ligne correspond
|
||||
à un pays `country` pour une année d'observation `year`.
|
||||
|
||||
```{r}
|
||||
paged_table(gapminder, options = list(rows.print = 15))
|
||||
```
|
||||
|
||||
On se concentre plus particulièrement sur les variables suivantes :
|
||||
|
||||
- `lifeExp` : l'espérance de vie ;
|
||||
- `gdpPercap` : le PIB par habitant en dollars (conversion sur
|
||||
base 2005) ;
|
||||
- `pop` : la taille de la population ;
|
||||
- `continent` : le continent d'appartenance.
|
||||
|
||||
Pour plus détails, voir l'aide `?gapminder`.
|
||||
|
||||
## But de la visualisation
|
||||
|
||||
Dans ce TP, on cherche un ensemble de visualisations permettant de
|
||||
commenter le lien entre GDP et espérance de vie, globalement et à
|
||||
différents échelles géographiques :
|
||||
|
||||
- est-ce que les pays les plus développés en termes de PIB par
|
||||
habitant ont la meilleure espérance de vie ?
|
||||
- peut-on identifier des dynamiques différentes en fonction des pays
|
||||
ou des continents ?
|
||||
- quels facteurs influencent également cette relation ?
|
||||
|
||||
**En vous répartissant en groupes de 3-4 étudiant.e.s.**, choisissez
|
||||
pour chaque groupe, une des 4 visualisations suivantes :
|
||||
|
||||
- visualisations utilisant des dot points.
|
||||
- visualisations permettant de présenter une distribution.
|
||||
- visualisations utilisant des cartes.
|
||||
|
||||
## Différentes visualisations
|
||||
|
||||
### Dot points
|
||||
|
||||
::: exercise-box
|
||||
En partant de cette visualisation, construire une ou plusieurs
|
||||
visualisations permettant d'analyser et de commenter les liens que vous
|
||||
pouvez observer entre `gdpPercap` et `lifeExp`.
|
||||
:::
|
||||
|
||||
```{r}
|
||||
ggplot(data = gapminder, aes(x = gdpPercap, y = lifeExp)) +
|
||||
geom_point()
|
||||
```
|
||||
|
||||
### Distributions
|
||||
|
||||
::: exercise-box
|
||||
En partant de cette visualisation, analyser et commenter les écarts
|
||||
d'espérance de vie sur la planète. Vous pourrez aussi adopter d'autres
|
||||
visualisations permettant de comparer des distributions.
|
||||
:::
|
||||
|
||||
```{r}
|
||||
ggplot(data = gapminder, aes(x = lifeExp)) +
|
||||
geom_density()
|
||||
```
|
||||
|
||||
```{r}
|
||||
ggplot(data = gapminder, aes(x = lifeExp)) +
|
||||
geom_histogram(aes(y = ..density..), bins = 30) +
|
||||
geom_density()
|
||||
```
|
||||
|
||||
### Cartes
|
||||
|
||||
Afin de visualiser plus précisément les caractéristiques régionales de
|
||||
l'espérance de vie et du PIB par habitant, nous décidons de faire des
|
||||
cartes.
|
||||
|
||||
Il faut au préalable récupérer un fond de carte (ici de l'année 2016).
|
||||
Nous prenons les données `gapminder` de 2007.
|
||||
|
||||
```{r}
|
||||
library(giscoR)
|
||||
library(sf)
|
||||
|
||||
world <- gisco_countries
|
||||
world <- subset(world, NAME_ENGL != "Antarctica") # Remove Antartica
|
||||
|
||||
# Merge data
|
||||
world_df <- gapminder |>
|
||||
filter(year == "2007")
|
||||
world_df <- world |>
|
||||
left_join(world_df, by = c("NAME_ENGL" = "country"))
|
||||
|
||||
ggplot(world_df) +
|
||||
geom_sf(color = "black", fill = "lightgreen") +
|
||||
theme_void()
|
||||
```
|
||||
|
||||
::: exercise-box
|
||||
En partant de la visualisation et des données ci-dessus, représenter et
|
||||
analyser les disparités d'espérance de vie et de revenus.
|
||||
:::
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
# Analyse des accidents à vélo en France
|
||||
|
||||
Dans ce TP, nous nous intéressons aux accidents corporels survenus sur
|
||||
une voie ouverte à la circulation publique, impliquant au moins un
|
||||
véhicule et ayant fait au moins une victime ayant nécessité des soins)
|
||||
pour la période 2005-2021.
|
||||
|
||||
L'objectif est de créer un ensemble de visualisations qui permettront de
|
||||
mieux comprendre accidentologie des cyclistes, ce qui trouve de
|
||||
nombreuses sources d'applications notamment en assurance.
|
||||
|
||||
Les données et documentation de ces données sont disponibles
|
||||
[ici](https://www.data.gouv.fr/fr/datasets/bases-de-donnees-annuelles-des-accidents-corporels-de-la-circulation-routiere-annees-de-2005-a-2021/).
|
||||
|
||||
## Données
|
||||
|
||||
Pour récupérer les données :
|
||||
|
||||
1. récupérer le fichier zippé "data_velo.zip".
|
||||
2. créer un dossier "data" à la racine de votre projet.
|
||||
3. placer y le contenu du dossier zippé.
|
||||
|
||||
Les données se chargent avec la commande suivante.
|
||||
|
||||
```{r}
|
||||
library(readxl)
|
||||
library(readr)
|
||||
library(tidyverse)
|
||||
|
||||
# main data
|
||||
path <- getwd()
|
||||
accidents <- read_csv(
|
||||
paste0(
|
||||
path,
|
||||
"/data/accidentsVelo.csv"
|
||||
),
|
||||
col_types = cols(Num_Acc = col_double(), date = col_date(format = "%Y-%m-%d"))
|
||||
)
|
||||
|
||||
# few ajustements
|
||||
accidents <- accidents |>
|
||||
mutate(
|
||||
mois = factor(mois),
|
||||
jour = factor(jour),
|
||||
dep = factor(dep),
|
||||
agg = factor(agg),
|
||||
grav = factor(grav),
|
||||
situ = factor(situ)
|
||||
)
|
||||
|
||||
# correct some issues with variables `hrmn`
|
||||
issue <- which(str_length(accidents$hrmn) == 4)
|
||||
correct <- accidents$hrmn[issue]
|
||||
correct <- paste0(
|
||||
"0",
|
||||
str_sub(correct, 1, 1),
|
||||
":",
|
||||
str_sub(correct, 2, 2),
|
||||
str_sub(correct, 4, 4)
|
||||
)
|
||||
accidents$hrmn[issue] <- correct
|
||||
|
||||
# Extract hour
|
||||
accidents <- accidents |>
|
||||
mutate(hour = paste(date, hrmn, sep = " ")) |>
|
||||
mutate(hour = strptime(hour, "%Y-%m-%d %H:%M")$hour)
|
||||
|
||||
# mapping table for french departments
|
||||
departements_francais <- read_excel(
|
||||
paste0(
|
||||
path,
|
||||
"/data/departements-francais.xlsx"
|
||||
),
|
||||
col_types = c("text", "text", "text")
|
||||
)
|
||||
```
|
||||
|
||||
Ce jeu de données contient 74758 observations où chaque ligne correspond
|
||||
à un accident.
|
||||
|
||||
```{r}
|
||||
paged_table(accidents, options = list(rows.print = 15))
|
||||
```
|
||||
|
||||
On se concentre plus particulièrement sur les variables suivantes :
|
||||
|
||||
- `date`, `an`, `mois`, `jour`, `hour`, : la date, année, mois, jour
|
||||
et heure de l'accident ;
|
||||
- `dep` : le département ;
|
||||
- `agg` : la location (1 = "hors agglomération", 2 = "en
|
||||
agglomération") ;
|
||||
- `lat` : latitude (pas toujours renseignée) ;
|
||||
- `long` : longitude (pas toujours renseignée) ;
|
||||
- `situ` : la situation de l'accident (-1 = Non renseigné, 0 = Aucun,
|
||||
1 = Sur chaussée, 2 = Sur bande d’arrêt d’urgence, 3 = Sur
|
||||
accotement, 4 = Sur trottoir, 5 = Sur piste cyclable, 6 = Sur autre
|
||||
voie spéciale, 8 = Autres) ;
|
||||
- `grav` : la gravité (1 = Indemne, 2 = Tué, 3 = Blessé hospitalisé, 4
|
||||
= Blessé léger).
|
||||
|
||||
D'autres variables intéressantes pourraient être étudiées, mais nous
|
||||
nous limitons à celle-ci dans ce TP.
|
||||
|
||||
## But de la visualisation
|
||||
|
||||
Dans ce TP, on cherche à réaliser une analyse exploratoire du jeu de
|
||||
données afin d'identifier les relations importantes entre le nombre
|
||||
d'accidents et ses déterminants :
|
||||
|
||||
- est-ce que les accidents sont plus situés en agglomération ?
|
||||
- comment les accidents se répartissent dans l'espace et dans le temps
|
||||
?
|
||||
- quels impacts ont les situations de circulation sur la gravité des
|
||||
accidents ?
|
||||
|
||||
En vous répartissant en groupes de 3-4 étudiant.e.s., choisissez pour
|
||||
chaque groupe, une des 4 visualisations suivantes :
|
||||
|
||||
- visualisations utilisant des cartes statiques ou dynamiques.
|
||||
- visualisations utilisant des bar plots.
|
||||
|
||||
L'anayse a réalisé ici est préliminaire, d'autres informations
|
||||
intéressantes sont à extraire de ce jeu de données.
|
||||
|
||||
## Différentes visualisations
|
||||
|
||||
### Carte
|
||||
|
||||
#### Carte dynamique {.unnumbered}
|
||||
|
||||
::: exercise-box
|
||||
1. Tracer une carte dynamique représentant la location des accidents
|
||||
(latitude, longitude) en France sur toute la période en modulant la
|
||||
gravité.
|
||||
|
||||
```{r}
|
||||
library(mapview)
|
||||
library(sf)
|
||||
|
||||
## Remove NA
|
||||
df_map_dyn <- accidents |>
|
||||
filter(!is.na(lat) & !is.na(long)) |>
|
||||
mutate(
|
||||
lat = as.numeric(str_replace_all(str_trim(lat), ",", ".")),
|
||||
long = as.numeric(str_replace_all(str_trim(long), ",", "."))
|
||||
) |>
|
||||
filter(!is.na(lat) & !is.na(long))
|
||||
|
||||
# Make map and print it
|
||||
mymap <- st_as_sf(df_map_dyn[1:5000, ], coords = c("long", "lat"), crs = 4326)
|
||||
mapview(
|
||||
mymap,
|
||||
cex = 2,
|
||||
legend = TRUE,
|
||||
layer.name = "Gravité",
|
||||
zcol = "grav",
|
||||
map.types = "OpenStreetMap"
|
||||
)
|
||||
```
|
||||
|
||||
2. Faire deux variantes de cette carte, selon le caractère urbain ou
|
||||
non des accidents.
|
||||
|
||||
```{r}
|
||||
# Map for urban accidents
|
||||
df_map_urban <- df_map_dyn |>
|
||||
mutate(
|
||||
agg = dplyr::if_else(
|
||||
agg == 1,
|
||||
"urbanisation",
|
||||
"agglomération",
|
||||
missing = NA_character_
|
||||
)
|
||||
)
|
||||
|
||||
# Make map and print it
|
||||
mymap_urban <- st_as_sf(df_map_urban, coords = c("long", "lat"), crs = 4326)
|
||||
mapview(
|
||||
mymap_urban,
|
||||
cex = 2,
|
||||
legend = TRUE,
|
||||
layer.name = "Urbanisation",
|
||||
zcol = "agg"
|
||||
)
|
||||
```
|
||||
|
||||
3. Commenter ces figures.
|
||||
|
||||
4. Quelles limites voyez-vous à cette représentation ?
|
||||
:::
|
||||
|
||||
Voici un premier code à trou pour vous aider. Pour alléger les temps de
|
||||
production afficher uniquement quelques points. Vous pourrez ajouter
|
||||
l'ensemble du jeu de données quand votre code sera finalisé.
|
||||
|
||||
#### Carte choroplèthe {.unnumbered}
|
||||
|
||||
::: exercise-box
|
||||
1. Tracer une carte statistique de type chonoplèthe et représenter le
|
||||
nombre d'accidents par département.
|
||||
|
||||
2. Faire plusieurs variantes de cette carte selon la gravité des
|
||||
accidents.
|
||||
:::
|
||||
|
||||
Voici un premier code à trou pour vous aider.
|
||||
|
||||
```{r}
|
||||
# get french map - level nuts3
|
||||
fr <- gisco_get_nuts(resolution = "20", country = "FRA", nuts_level = 3) |>
|
||||
mutate(res = "20M")
|
||||
|
||||
# Remove white-space to avoid errors.
|
||||
library(stringr)
|
||||
departements_francais <- departements_francais |>
|
||||
mutate(dep_name = str_trim(dep_name))
|
||||
|
||||
fr <- fr |>
|
||||
mutate(NUTS_NAME = str_trim(NUTS_NAME))
|
||||
|
||||
# Merge and remove departements outside metropolitan France
|
||||
fr_map <- fr |>
|
||||
left_join(departements_francais, by = c("NUTS_NAME" = "dep_name")) |>
|
||||
filter(!dep %in% c("971", "972", "973", "974", "976"))
|
||||
|
||||
# count the number of accidents
|
||||
df_acc <- accidents |>
|
||||
filter(!is.na(dep)) |>
|
||||
group_by(dep) |>
|
||||
summarise(n = n())
|
||||
|
||||
# merge statistics with the map
|
||||
map_acc <- fr_map |>
|
||||
left_join(df_acc, by = c("dep" = "dep"))
|
||||
|
||||
# map with all accidents
|
||||
g_map_acc <- ggplot(map_acc) +
|
||||
geom_sf(aes(fill = n)) +
|
||||
scale_fill_viridis_c(option = "viridis") +
|
||||
labs(
|
||||
title = "Carte des accidents de vélo",
|
||||
subtitle = "Année 2005-2021",
|
||||
fill = "Nombre d'accidents"
|
||||
) +
|
||||
theme_void()
|
||||
g_map_acc
|
||||
```
|
||||
|
||||
### Bar plots
|
||||
|
||||
::: exercise-box
|
||||
1. On s'intéresse à la répartition par mois et horaires des accidents
|
||||
(variable `an`, `hour`, `mois`). Tracer des bar plots permettant de
|
||||
montrer la répartition temporelle des accidents.
|
||||
|
||||
2. On s'intéresse à l'impact des situations de circulation. Tracer des
|
||||
bar plots permettant de montrer la répartition de la gravité des
|
||||
accidents selon ces situations.
|
||||
|
||||
3. Voyez-vous une évolution sur la période 2005-2021 ?
|
||||
|
||||
4. Réfléchir à des représentations alternatives pour les phénomènes
|
||||
périodiques.
|
||||
:::
|
||||
|
||||
# Informations de session {.unnumbered}
|
||||
|
||||
```{r}
|
||||
sessionInfo()
|
||||
```
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 69 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,142 +0,0 @@
|
||||
.pagedtable {
|
||||
overflow: auto;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.pagedtable-wrapper {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.pagedtable table {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.pagedtable th {
|
||||
padding: 0 5px 0 5px;
|
||||
border: none;
|
||||
border-bottom: 2px solid #dddddd;
|
||||
|
||||
min-width: 45px;
|
||||
}
|
||||
|
||||
.pagedtable-empty th {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pagedtable td {
|
||||
padding: 0 4px 0 4px;
|
||||
}
|
||||
|
||||
.pagedtable .even {
|
||||
background-color: rgba(140, 140, 140, 0.1);
|
||||
}
|
||||
|
||||
.pagedtable-padding-col {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pagedtable a {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.pagedtable-index-nav {
|
||||
cursor: pointer;
|
||||
padding: 0 5px 0 5px;
|
||||
float: right;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.pagedtable-index-nav-disabled {
|
||||
cursor: default;
|
||||
text-decoration: none;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
a.pagedtable-index-nav-disabled:hover {
|
||||
text-decoration: none;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.pagedtable-indexes {
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.pagedtable-index-current {
|
||||
cursor: default;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
a.pagedtable-index-current:hover {
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.pagedtable-index {
|
||||
width: 30px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.pagedtable-index-separator-left {
|
||||
display: inline-block;
|
||||
color: #333;
|
||||
font-size: 9px;
|
||||
padding: 0 0 0 0;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.pagedtable-index-separator-right {
|
||||
display: inline-block;
|
||||
color: #333;
|
||||
font-size: 9px;
|
||||
padding: 0 4px 0 0;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.pagedtable-footer {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.pagedtable-not-empty .pagedtable-footer {
|
||||
border-top: 2px solid #dddddd;
|
||||
}
|
||||
|
||||
.pagedtable-info {
|
||||
overflow: hidden;
|
||||
color: #999;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.pagedtable-header-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.pagedtable-header-type {
|
||||
color: #999;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.pagedtable-na-cell {
|
||||
font-style: italic;
|
||||
opacity: 0.3;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,236 +0,0 @@
|
||||
/* quarto syntax highlight colors */
|
||||
:root {
|
||||
--quarto-hl-ot-color: #003B4F;
|
||||
--quarto-hl-at-color: #657422;
|
||||
--quarto-hl-ss-color: #20794D;
|
||||
--quarto-hl-an-color: #5E5E5E;
|
||||
--quarto-hl-fu-color: #4758AB;
|
||||
--quarto-hl-st-color: #20794D;
|
||||
--quarto-hl-cf-color: #003B4F;
|
||||
--quarto-hl-op-color: #5E5E5E;
|
||||
--quarto-hl-er-color: #AD0000;
|
||||
--quarto-hl-bn-color: #AD0000;
|
||||
--quarto-hl-al-color: #AD0000;
|
||||
--quarto-hl-va-color: #111111;
|
||||
--quarto-hl-bu-color: inherit;
|
||||
--quarto-hl-ex-color: inherit;
|
||||
--quarto-hl-pp-color: #AD0000;
|
||||
--quarto-hl-in-color: #5E5E5E;
|
||||
--quarto-hl-vs-color: #20794D;
|
||||
--quarto-hl-wa-color: #5E5E5E;
|
||||
--quarto-hl-do-color: #5E5E5E;
|
||||
--quarto-hl-im-color: #00769E;
|
||||
--quarto-hl-ch-color: #20794D;
|
||||
--quarto-hl-dt-color: #AD0000;
|
||||
--quarto-hl-fl-color: #AD0000;
|
||||
--quarto-hl-co-color: #5E5E5E;
|
||||
--quarto-hl-cv-color: #5E5E5E;
|
||||
--quarto-hl-cn-color: #8f5902;
|
||||
--quarto-hl-sc-color: #5E5E5E;
|
||||
--quarto-hl-dv-color: #AD0000;
|
||||
--quarto-hl-kw-color: #003B4F;
|
||||
}
|
||||
|
||||
/* other quarto variables */
|
||||
:root {
|
||||
--quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
}
|
||||
|
||||
/* syntax highlight based on Pandoc's rules */
|
||||
pre > code.sourceCode > span {
|
||||
color: #003B4F;
|
||||
}
|
||||
|
||||
code.sourceCode > span {
|
||||
color: #003B4F;
|
||||
}
|
||||
|
||||
div.sourceCode,
|
||||
div.sourceCode pre.sourceCode {
|
||||
color: #003B4F;
|
||||
}
|
||||
|
||||
/* Normal */
|
||||
code span {
|
||||
color: #003B4F;
|
||||
}
|
||||
|
||||
/* Alert */
|
||||
code span.al {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Annotation */
|
||||
code span.an {
|
||||
color: #5E5E5E;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Attribute */
|
||||
code span.at {
|
||||
color: #657422;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* BaseN */
|
||||
code span.bn {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* BuiltIn */
|
||||
code span.bu {
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* ControlFlow */
|
||||
code span.cf {
|
||||
color: #003B4F;
|
||||
font-weight: bold;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Char */
|
||||
code span.ch {
|
||||
color: #20794D;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Constant */
|
||||
code span.cn {
|
||||
color: #8f5902;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Comment */
|
||||
code span.co {
|
||||
color: #5E5E5E;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* CommentVar */
|
||||
code span.cv {
|
||||
color: #5E5E5E;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Documentation */
|
||||
code span.do {
|
||||
color: #5E5E5E;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* DataType */
|
||||
code span.dt {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* DecVal */
|
||||
code span.dv {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Error */
|
||||
code span.er {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Extension */
|
||||
code span.ex {
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Float */
|
||||
code span.fl {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Function */
|
||||
code span.fu {
|
||||
color: #4758AB;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Import */
|
||||
code span.im {
|
||||
color: #00769E;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Information */
|
||||
code span.in {
|
||||
color: #5E5E5E;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Keyword */
|
||||
code span.kw {
|
||||
color: #003B4F;
|
||||
font-weight: bold;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Operator */
|
||||
code span.op {
|
||||
color: #5E5E5E;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Other */
|
||||
code span.ot {
|
||||
color: #003B4F;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Preprocessor */
|
||||
code span.pp {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* SpecialChar */
|
||||
code span.sc {
|
||||
color: #5E5E5E;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* SpecialString */
|
||||
code span.ss {
|
||||
color: #20794D;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* String */
|
||||
code span.st {
|
||||
color: #20794D;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Variable */
|
||||
code span.va {
|
||||
color: #111111;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* VerbatimString */
|
||||
code span.vs {
|
||||
color: #20794D;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Warning */
|
||||
code span.wa {
|
||||
color: #5E5E5E;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.prevent-inlining {
|
||||
content: "</";
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=27d3d809615f7771493d095567f04340.css.map */
|
||||
@@ -1,845 +0,0 @@
|
||||
import * as tabsets from "./tabsets/tabsets.js";
|
||||
|
||||
const sectionChanged = new CustomEvent("quarto-sectionChanged", {
|
||||
detail: {},
|
||||
bubbles: true,
|
||||
cancelable: false,
|
||||
composed: false,
|
||||
});
|
||||
|
||||
const layoutMarginEls = () => {
|
||||
// Find any conflicting margin elements and add margins to the
|
||||
// top to prevent overlap
|
||||
const marginChildren = window.document.querySelectorAll(
|
||||
".column-margin.column-container > *, .margin-caption, .aside"
|
||||
);
|
||||
|
||||
let lastBottom = 0;
|
||||
for (const marginChild of marginChildren) {
|
||||
if (marginChild.offsetParent !== null) {
|
||||
// clear the top margin so we recompute it
|
||||
marginChild.style.marginTop = null;
|
||||
const top = marginChild.getBoundingClientRect().top + window.scrollY;
|
||||
if (top < lastBottom) {
|
||||
const marginChildStyle = window.getComputedStyle(marginChild);
|
||||
const marginBottom = parseFloat(marginChildStyle["marginBottom"]);
|
||||
const margin = lastBottom - top + marginBottom;
|
||||
marginChild.style.marginTop = `${margin}px`;
|
||||
}
|
||||
const styles = window.getComputedStyle(marginChild);
|
||||
const marginTop = parseFloat(styles["marginTop"]);
|
||||
lastBottom = top + marginChild.getBoundingClientRect().height + marginTop;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.document.addEventListener("DOMContentLoaded", function (_event) {
|
||||
// Recompute the position of margin elements anytime the body size changes
|
||||
if (window.ResizeObserver) {
|
||||
const resizeObserver = new window.ResizeObserver(
|
||||
throttle(() => {
|
||||
layoutMarginEls();
|
||||
if (
|
||||
window.document.body.getBoundingClientRect().width < 990 &&
|
||||
isReaderMode()
|
||||
) {
|
||||
quartoToggleReader();
|
||||
}
|
||||
}, 50)
|
||||
);
|
||||
resizeObserver.observe(window.document.body);
|
||||
}
|
||||
|
||||
const tocEl = window.document.querySelector('nav.toc-active[role="doc-toc"]');
|
||||
const sidebarEl = window.document.getElementById("quarto-sidebar");
|
||||
const leftTocEl = window.document.getElementById("quarto-sidebar-toc-left");
|
||||
const marginSidebarEl = window.document.getElementById(
|
||||
"quarto-margin-sidebar"
|
||||
);
|
||||
// function to determine whether the element has a previous sibling that is active
|
||||
const prevSiblingIsActiveLink = (el) => {
|
||||
const sibling = el.previousElementSibling;
|
||||
if (sibling && sibling.tagName === "A") {
|
||||
return sibling.classList.contains("active");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// dispatch for htmlwidgets
|
||||
// they use slideenter event to trigger resize
|
||||
function fireSlideEnter() {
|
||||
const event = window.document.createEvent("Event");
|
||||
event.initEvent("slideenter", true, true);
|
||||
window.document.dispatchEvent(event);
|
||||
}
|
||||
|
||||
const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]');
|
||||
tabs.forEach((tab) => {
|
||||
tab.addEventListener("shown.bs.tab", fireSlideEnter);
|
||||
});
|
||||
|
||||
// dispatch for shiny
|
||||
// they use BS shown and hidden events to trigger rendering
|
||||
function distpatchShinyEvents(previous, current) {
|
||||
if (window.jQuery) {
|
||||
if (previous) {
|
||||
window.jQuery(previous).trigger("hidden");
|
||||
}
|
||||
if (current) {
|
||||
window.jQuery(current).trigger("shown");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tabby.js listener: Trigger event for htmlwidget and shiny
|
||||
document.addEventListener(
|
||||
"tabby",
|
||||
function (event) {
|
||||
fireSlideEnter();
|
||||
distpatchShinyEvents(event.detail.previousTab, event.detail.tab);
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
// Track scrolling and mark TOC links as active
|
||||
// get table of contents and sidebar (bail if we don't have at least one)
|
||||
const tocLinks = tocEl
|
||||
? [...tocEl.querySelectorAll("a[data-scroll-target]")]
|
||||
: [];
|
||||
const makeActive = (link) => tocLinks[link].classList.add("active");
|
||||
const removeActive = (link) => tocLinks[link].classList.remove("active");
|
||||
const removeAllActive = () =>
|
||||
[...Array(tocLinks.length).keys()].forEach((link) => removeActive(link));
|
||||
|
||||
// activate the anchor for a section associated with this TOC entry
|
||||
tocLinks.forEach((link) => {
|
||||
link.addEventListener("click", () => {
|
||||
if (link.href.indexOf("#") !== -1) {
|
||||
const anchor = link.href.split("#")[1];
|
||||
const heading = window.document.querySelector(
|
||||
`[data-anchor-id="${anchor}"]`
|
||||
);
|
||||
if (heading) {
|
||||
// Add the class
|
||||
heading.classList.add("reveal-anchorjs-link");
|
||||
|
||||
// function to show the anchor
|
||||
const handleMouseout = () => {
|
||||
heading.classList.remove("reveal-anchorjs-link");
|
||||
heading.removeEventListener("mouseout", handleMouseout);
|
||||
};
|
||||
|
||||
// add a function to clear the anchor when the user mouses out of it
|
||||
heading.addEventListener("mouseout", handleMouseout);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const sections = tocLinks.map((link) => {
|
||||
const target = link.getAttribute("data-scroll-target");
|
||||
if (target.startsWith("#")) {
|
||||
return window.document.getElementById(decodeURI(`${target.slice(1)}`));
|
||||
} else {
|
||||
return window.document.querySelector(decodeURI(`${target}`));
|
||||
}
|
||||
});
|
||||
|
||||
const sectionMargin = 200;
|
||||
let currentActive = 0;
|
||||
// track whether we've initialized state the first time
|
||||
let init = false;
|
||||
|
||||
const updateActiveLink = () => {
|
||||
// The index from bottom to top (e.g. reversed list)
|
||||
let sectionIndex = -1;
|
||||
if (
|
||||
window.innerHeight + window.pageYOffset >=
|
||||
window.document.body.offsetHeight
|
||||
) {
|
||||
// This is the no-scroll case where last section should be the active one
|
||||
sectionIndex = 0;
|
||||
} else {
|
||||
// This finds the last section visible on screen that should be made active
|
||||
sectionIndex = [...sections].reverse().findIndex((section) => {
|
||||
if (section) {
|
||||
return window.pageYOffset >= section.offsetTop - sectionMargin;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (sectionIndex > -1) {
|
||||
const current = sections.length - sectionIndex - 1;
|
||||
if (current !== currentActive) {
|
||||
removeAllActive();
|
||||
currentActive = current;
|
||||
makeActive(current);
|
||||
if (init) {
|
||||
window.dispatchEvent(sectionChanged);
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const inHiddenRegion = (top, bottom, hiddenRegions) => {
|
||||
for (const region of hiddenRegions) {
|
||||
if (top <= region.bottom && bottom >= region.top) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const categorySelector = "header.quarto-title-block .quarto-category";
|
||||
const activateCategories = (href) => {
|
||||
// Find any categories
|
||||
// Surround them with a link pointing back to:
|
||||
// #category=Authoring
|
||||
try {
|
||||
const categoryEls = window.document.querySelectorAll(categorySelector);
|
||||
for (const categoryEl of categoryEls) {
|
||||
const categoryText = categoryEl.textContent;
|
||||
if (categoryText) {
|
||||
const link = `${href}#category=${encodeURIComponent(categoryText)}`;
|
||||
const linkEl = window.document.createElement("a");
|
||||
linkEl.setAttribute("href", link);
|
||||
for (const child of categoryEl.childNodes) {
|
||||
linkEl.append(child);
|
||||
}
|
||||
categoryEl.appendChild(linkEl);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
};
|
||||
function hasTitleCategories() {
|
||||
return window.document.querySelector(categorySelector) !== null;
|
||||
}
|
||||
|
||||
function offsetRelativeUrl(url) {
|
||||
const offset = getMeta("quarto:offset");
|
||||
return offset ? offset + url : url;
|
||||
}
|
||||
|
||||
function offsetAbsoluteUrl(url) {
|
||||
const offset = getMeta("quarto:offset");
|
||||
const baseUrl = new URL(offset, window.location);
|
||||
|
||||
const projRelativeUrl = url.replace(baseUrl, "");
|
||||
if (projRelativeUrl.startsWith("/")) {
|
||||
return projRelativeUrl;
|
||||
} else {
|
||||
return "/" + projRelativeUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// read a meta tag value
|
||||
function getMeta(metaName) {
|
||||
const metas = window.document.getElementsByTagName("meta");
|
||||
for (let i = 0; i < metas.length; i++) {
|
||||
if (metas[i].getAttribute("name") === metaName) {
|
||||
return metas[i].getAttribute("content");
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
async function findAndActivateCategories() {
|
||||
// Categories search with listing only use path without query
|
||||
const currentPagePath = offsetAbsoluteUrl(
|
||||
window.location.origin + window.location.pathname
|
||||
);
|
||||
const response = await fetch(offsetRelativeUrl("listings.json"));
|
||||
if (response.status == 200) {
|
||||
return response.json().then(function (listingPaths) {
|
||||
const listingHrefs = [];
|
||||
for (const listingPath of listingPaths) {
|
||||
const pathWithoutLeadingSlash = listingPath.listing.substring(1);
|
||||
for (const item of listingPath.items) {
|
||||
const encodedItem = encodeURI(item);
|
||||
if (
|
||||
encodedItem === currentPagePath ||
|
||||
encodedItem === currentPagePath + "index.html"
|
||||
) {
|
||||
// Resolve this path against the offset to be sure
|
||||
// we already are using the correct path to the listing
|
||||
// (this adjusts the listing urls to be rooted against
|
||||
// whatever root the page is actually running against)
|
||||
const relative = offsetRelativeUrl(pathWithoutLeadingSlash);
|
||||
const baseUrl = window.location;
|
||||
const resolvedPath = new URL(relative, baseUrl);
|
||||
listingHrefs.push(resolvedPath.pathname);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Look up the tree for a nearby linting and use that if we find one
|
||||
const nearestListing = findNearestParentListing(
|
||||
offsetAbsoluteUrl(window.location.pathname),
|
||||
listingHrefs
|
||||
);
|
||||
if (nearestListing) {
|
||||
activateCategories(nearestListing);
|
||||
} else {
|
||||
// See if the referrer is a listing page for this item
|
||||
const referredRelativePath = offsetAbsoluteUrl(document.referrer);
|
||||
const referrerListing = listingHrefs.find((listingHref) => {
|
||||
const isListingReferrer =
|
||||
listingHref === referredRelativePath ||
|
||||
listingHref === referredRelativePath + "index.html";
|
||||
return isListingReferrer;
|
||||
});
|
||||
|
||||
if (referrerListing) {
|
||||
// Try to use the referrer if possible
|
||||
activateCategories(referrerListing);
|
||||
} else if (listingHrefs.length > 0) {
|
||||
// Otherwise, just fall back to the first listing
|
||||
activateCategories(listingHrefs[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (hasTitleCategories()) {
|
||||
findAndActivateCategories();
|
||||
}
|
||||
|
||||
const findNearestParentListing = (href, listingHrefs) => {
|
||||
if (!href || !listingHrefs) {
|
||||
return undefined;
|
||||
}
|
||||
// Look up the tree for a nearby linting and use that if we find one
|
||||
const relativeParts = href.substring(1).split("/");
|
||||
while (relativeParts.length > 0) {
|
||||
const path = relativeParts.join("/");
|
||||
for (const listingHref of listingHrefs) {
|
||||
if (listingHref.startsWith(path)) {
|
||||
return listingHref;
|
||||
}
|
||||
}
|
||||
relativeParts.pop();
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const manageSidebarVisiblity = (el, placeholderDescriptor) => {
|
||||
let isVisible = true;
|
||||
let elRect;
|
||||
|
||||
return (hiddenRegions) => {
|
||||
if (el === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the last element of the TOC
|
||||
const lastChildEl = el.lastElementChild;
|
||||
|
||||
if (lastChildEl) {
|
||||
// Converts the sidebar to a menu
|
||||
const convertToMenu = () => {
|
||||
for (const child of el.children) {
|
||||
child.style.opacity = 0;
|
||||
child.style.overflow = "hidden";
|
||||
child.style.pointerEvents = "none";
|
||||
}
|
||||
|
||||
nexttick(() => {
|
||||
const toggleContainer = window.document.createElement("div");
|
||||
toggleContainer.style.width = "100%";
|
||||
toggleContainer.classList.add("zindex-over-content");
|
||||
toggleContainer.classList.add("quarto-sidebar-toggle");
|
||||
toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom
|
||||
toggleContainer.id = placeholderDescriptor.id;
|
||||
toggleContainer.style.position = "fixed";
|
||||
|
||||
const toggleIcon = window.document.createElement("i");
|
||||
toggleIcon.classList.add("quarto-sidebar-toggle-icon");
|
||||
toggleIcon.classList.add("bi");
|
||||
toggleIcon.classList.add("bi-caret-down-fill");
|
||||
|
||||
const toggleTitle = window.document.createElement("div");
|
||||
const titleEl = window.document.body.querySelector(
|
||||
placeholderDescriptor.titleSelector
|
||||
);
|
||||
if (titleEl) {
|
||||
toggleTitle.append(
|
||||
titleEl.textContent || titleEl.innerText,
|
||||
toggleIcon
|
||||
);
|
||||
}
|
||||
toggleTitle.classList.add("zindex-over-content");
|
||||
toggleTitle.classList.add("quarto-sidebar-toggle-title");
|
||||
toggleContainer.append(toggleTitle);
|
||||
|
||||
const toggleContents = window.document.createElement("div");
|
||||
toggleContents.classList = el.classList;
|
||||
toggleContents.classList.add("zindex-over-content");
|
||||
toggleContents.classList.add("quarto-sidebar-toggle-contents");
|
||||
for (const child of el.children) {
|
||||
if (child.id === "toc-title") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const clone = child.cloneNode(true);
|
||||
clone.style.opacity = 1;
|
||||
clone.style.pointerEvents = null;
|
||||
clone.style.display = null;
|
||||
toggleContents.append(clone);
|
||||
}
|
||||
toggleContents.style.height = "0px";
|
||||
const positionToggle = () => {
|
||||
// position the element (top left of parent, same width as parent)
|
||||
if (!elRect) {
|
||||
elRect = el.getBoundingClientRect();
|
||||
}
|
||||
toggleContainer.style.left = `${elRect.left}px`;
|
||||
toggleContainer.style.top = `${elRect.top}px`;
|
||||
toggleContainer.style.width = `${elRect.width}px`;
|
||||
};
|
||||
positionToggle();
|
||||
|
||||
toggleContainer.append(toggleContents);
|
||||
el.parentElement.prepend(toggleContainer);
|
||||
|
||||
// Process clicks
|
||||
let tocShowing = false;
|
||||
// Allow the caller to control whether this is dismissed
|
||||
// when it is clicked (e.g. sidebar navigation supports
|
||||
// opening and closing the nav tree, so don't dismiss on click)
|
||||
const clickEl = placeholderDescriptor.dismissOnClick
|
||||
? toggleContainer
|
||||
: toggleTitle;
|
||||
|
||||
const closeToggle = () => {
|
||||
if (tocShowing) {
|
||||
toggleContainer.classList.remove("expanded");
|
||||
toggleContents.style.height = "0px";
|
||||
tocShowing = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Get rid of any expanded toggle if the user scrolls
|
||||
window.document.addEventListener(
|
||||
"scroll",
|
||||
throttle(() => {
|
||||
closeToggle();
|
||||
}, 50)
|
||||
);
|
||||
|
||||
// Handle positioning of the toggle
|
||||
window.addEventListener(
|
||||
"resize",
|
||||
throttle(() => {
|
||||
elRect = undefined;
|
||||
positionToggle();
|
||||
}, 50)
|
||||
);
|
||||
|
||||
window.addEventListener("quarto-hrChanged", () => {
|
||||
elRect = undefined;
|
||||
});
|
||||
|
||||
// Process the click
|
||||
clickEl.onclick = () => {
|
||||
if (!tocShowing) {
|
||||
toggleContainer.classList.add("expanded");
|
||||
toggleContents.style.height = null;
|
||||
tocShowing = true;
|
||||
} else {
|
||||
closeToggle();
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Converts a sidebar from a menu back to a sidebar
|
||||
const convertToSidebar = () => {
|
||||
for (const child of el.children) {
|
||||
child.style.opacity = 1;
|
||||
child.style.overflow = null;
|
||||
child.style.pointerEvents = null;
|
||||
}
|
||||
|
||||
const placeholderEl = window.document.getElementById(
|
||||
placeholderDescriptor.id
|
||||
);
|
||||
if (placeholderEl) {
|
||||
placeholderEl.remove();
|
||||
}
|
||||
|
||||
el.classList.remove("rollup");
|
||||
};
|
||||
|
||||
if (isReaderMode()) {
|
||||
convertToMenu();
|
||||
isVisible = false;
|
||||
} else {
|
||||
// Find the top and bottom o the element that is being managed
|
||||
const elTop = el.offsetTop;
|
||||
const elBottom =
|
||||
elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight;
|
||||
|
||||
if (!isVisible) {
|
||||
// If the element is current not visible reveal if there are
|
||||
// no conflicts with overlay regions
|
||||
if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) {
|
||||
convertToSidebar();
|
||||
isVisible = true;
|
||||
}
|
||||
} else {
|
||||
// If the element is visible, hide it if it conflicts with overlay regions
|
||||
// and insert a placeholder toggle (or if we're in reader mode)
|
||||
if (inHiddenRegion(elTop, elBottom, hiddenRegions)) {
|
||||
convertToMenu();
|
||||
isVisible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const tabEls = document.querySelectorAll('a[data-bs-toggle="tab"]');
|
||||
for (const tabEl of tabEls) {
|
||||
const id = tabEl.getAttribute("data-bs-target");
|
||||
if (id) {
|
||||
const columnEl = document.querySelector(
|
||||
`${id} .column-margin, .tabset-margin-content`
|
||||
);
|
||||
if (columnEl)
|
||||
tabEl.addEventListener("shown.bs.tab", function (event) {
|
||||
const el = event.srcElement;
|
||||
if (el) {
|
||||
const visibleCls = `${el.id}-margin-content`;
|
||||
// walk up until we find a parent tabset
|
||||
let panelTabsetEl = el.parentElement;
|
||||
while (panelTabsetEl) {
|
||||
if (panelTabsetEl.classList.contains("panel-tabset")) {
|
||||
break;
|
||||
}
|
||||
panelTabsetEl = panelTabsetEl.parentElement;
|
||||
}
|
||||
|
||||
if (panelTabsetEl) {
|
||||
const prevSib = panelTabsetEl.previousElementSibling;
|
||||
if (
|
||||
prevSib &&
|
||||
prevSib.classList.contains("tabset-margin-container")
|
||||
) {
|
||||
const childNodes = prevSib.querySelectorAll(
|
||||
".tabset-margin-content"
|
||||
);
|
||||
for (const childEl of childNodes) {
|
||||
if (childEl.classList.contains(visibleCls)) {
|
||||
childEl.classList.remove("collapse");
|
||||
} else {
|
||||
childEl.classList.add("collapse");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layoutMarginEls();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Manage the visibility of the toc and the sidebar
|
||||
const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, {
|
||||
id: "quarto-toc-toggle",
|
||||
titleSelector: "#toc-title",
|
||||
dismissOnClick: true,
|
||||
});
|
||||
const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, {
|
||||
id: "quarto-sidebarnav-toggle",
|
||||
titleSelector: ".title",
|
||||
dismissOnClick: false,
|
||||
});
|
||||
let tocLeftScrollVisibility;
|
||||
if (leftTocEl) {
|
||||
tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, {
|
||||
id: "quarto-lefttoc-toggle",
|
||||
titleSelector: "#toc-title",
|
||||
dismissOnClick: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Find the first element that uses formatting in special columns
|
||||
const conflictingEls = window.document.body.querySelectorAll(
|
||||
'[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]'
|
||||
);
|
||||
|
||||
// Filter all the possibly conflicting elements into ones
|
||||
// the do conflict on the left or ride side
|
||||
const arrConflictingEls = Array.from(conflictingEls);
|
||||
const leftSideConflictEls = arrConflictingEls.filter((el) => {
|
||||
if (el.tagName === "ASIDE") {
|
||||
return false;
|
||||
}
|
||||
return Array.from(el.classList).find((className) => {
|
||||
return (
|
||||
className !== "column-body" &&
|
||||
className.startsWith("column-") &&
|
||||
!className.endsWith("right") &&
|
||||
!className.endsWith("container") &&
|
||||
className !== "column-margin"
|
||||
);
|
||||
});
|
||||
});
|
||||
const rightSideConflictEls = arrConflictingEls.filter((el) => {
|
||||
if (el.tagName === "ASIDE") {
|
||||
return true;
|
||||
}
|
||||
|
||||
const hasMarginCaption = Array.from(el.classList).find((className) => {
|
||||
return className == "margin-caption";
|
||||
});
|
||||
if (hasMarginCaption) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Array.from(el.classList).find((className) => {
|
||||
return (
|
||||
className !== "column-body" &&
|
||||
!className.endsWith("container") &&
|
||||
className.startsWith("column-") &&
|
||||
!className.endsWith("left")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const kOverlapPaddingSize = 10;
|
||||
function toRegions(els) {
|
||||
return els.map((el) => {
|
||||
const boundRect = el.getBoundingClientRect();
|
||||
const top =
|
||||
boundRect.top +
|
||||
document.documentElement.scrollTop -
|
||||
kOverlapPaddingSize;
|
||||
return {
|
||||
top,
|
||||
bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
let hasObserved = false;
|
||||
const visibleItemObserver = (els) => {
|
||||
let visibleElements = [...els];
|
||||
const intersectionObserver = new IntersectionObserver(
|
||||
(entries, _observer) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
if (visibleElements.indexOf(entry.target) === -1) {
|
||||
visibleElements.push(entry.target);
|
||||
}
|
||||
} else {
|
||||
visibleElements = visibleElements.filter((visibleEntry) => {
|
||||
return visibleEntry !== entry;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (!hasObserved) {
|
||||
hideOverlappedSidebars();
|
||||
}
|
||||
hasObserved = true;
|
||||
},
|
||||
{}
|
||||
);
|
||||
els.forEach((el) => {
|
||||
intersectionObserver.observe(el);
|
||||
});
|
||||
|
||||
return {
|
||||
getVisibleEntries: () => {
|
||||
return visibleElements;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const rightElementObserver = visibleItemObserver(rightSideConflictEls);
|
||||
const leftElementObserver = visibleItemObserver(leftSideConflictEls);
|
||||
|
||||
const hideOverlappedSidebars = () => {
|
||||
marginScrollVisibility(toRegions(rightElementObserver.getVisibleEntries()));
|
||||
sidebarScrollVisiblity(toRegions(leftElementObserver.getVisibleEntries()));
|
||||
if (tocLeftScrollVisibility) {
|
||||
tocLeftScrollVisibility(
|
||||
toRegions(leftElementObserver.getVisibleEntries())
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
window.quartoToggleReader = () => {
|
||||
// Applies a slow class (or removes it)
|
||||
// to update the transition speed
|
||||
const slowTransition = (slow) => {
|
||||
const manageTransition = (id, slow) => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) {
|
||||
if (slow) {
|
||||
el.classList.add("slow");
|
||||
} else {
|
||||
el.classList.remove("slow");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
manageTransition("TOC", slow);
|
||||
manageTransition("quarto-sidebar", slow);
|
||||
};
|
||||
const readerMode = !isReaderMode();
|
||||
setReaderModeValue(readerMode);
|
||||
|
||||
// If we're entering reader mode, slow the transition
|
||||
if (readerMode) {
|
||||
slowTransition(readerMode);
|
||||
}
|
||||
highlightReaderToggle(readerMode);
|
||||
hideOverlappedSidebars();
|
||||
|
||||
// If we're exiting reader mode, restore the non-slow transition
|
||||
if (!readerMode) {
|
||||
slowTransition(!readerMode);
|
||||
}
|
||||
};
|
||||
|
||||
const highlightReaderToggle = (readerMode) => {
|
||||
const els = document.querySelectorAll(".quarto-reader-toggle");
|
||||
if (els) {
|
||||
els.forEach((el) => {
|
||||
if (readerMode) {
|
||||
el.classList.add("reader");
|
||||
} else {
|
||||
el.classList.remove("reader");
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const setReaderModeValue = (val) => {
|
||||
if (window.location.protocol !== "file:") {
|
||||
window.localStorage.setItem("quarto-reader-mode", val);
|
||||
} else {
|
||||
localReaderMode = val;
|
||||
}
|
||||
};
|
||||
|
||||
const isReaderMode = () => {
|
||||
if (window.location.protocol !== "file:") {
|
||||
return window.localStorage.getItem("quarto-reader-mode") === "true";
|
||||
} else {
|
||||
return localReaderMode;
|
||||
}
|
||||
};
|
||||
let localReaderMode = null;
|
||||
|
||||
const tocOpenDepthStr = tocEl?.getAttribute("data-toc-expanded");
|
||||
const tocOpenDepth = tocOpenDepthStr ? Number(tocOpenDepthStr) : 1;
|
||||
|
||||
// Walk the TOC and collapse/expand nodes
|
||||
// Nodes are expanded if:
|
||||
// - they are top level
|
||||
// - they have children that are 'active' links
|
||||
// - they are directly below an link that is 'active'
|
||||
const walk = (el, depth) => {
|
||||
// Tick depth when we enter a UL
|
||||
if (el.tagName === "UL") {
|
||||
depth = depth + 1;
|
||||
}
|
||||
|
||||
// It this is active link
|
||||
let isActiveNode = false;
|
||||
if (el.tagName === "A" && el.classList.contains("active")) {
|
||||
isActiveNode = true;
|
||||
}
|
||||
|
||||
// See if there is an active child to this element
|
||||
let hasActiveChild = false;
|
||||
for (const child of el.children) {
|
||||
hasActiveChild = walk(child, depth) || hasActiveChild;
|
||||
}
|
||||
|
||||
// Process the collapse state if this is an UL
|
||||
if (el.tagName === "UL") {
|
||||
if (tocOpenDepth === -1 && depth > 1) {
|
||||
// toc-expand: false
|
||||
el.classList.add("collapse");
|
||||
} else if (
|
||||
depth <= tocOpenDepth ||
|
||||
hasActiveChild ||
|
||||
prevSiblingIsActiveLink(el)
|
||||
) {
|
||||
el.classList.remove("collapse");
|
||||
} else {
|
||||
el.classList.add("collapse");
|
||||
}
|
||||
|
||||
// untick depth when we leave a UL
|
||||
depth = depth - 1;
|
||||
}
|
||||
return hasActiveChild || isActiveNode;
|
||||
};
|
||||
|
||||
// walk the TOC and expand / collapse any items that should be shown
|
||||
if (tocEl) {
|
||||
updateActiveLink();
|
||||
walk(tocEl, 0);
|
||||
}
|
||||
|
||||
// Throttle the scroll event and walk peridiocally
|
||||
window.document.addEventListener(
|
||||
"scroll",
|
||||
throttle(() => {
|
||||
if (tocEl) {
|
||||
updateActiveLink();
|
||||
walk(tocEl, 0);
|
||||
}
|
||||
if (!isReaderMode()) {
|
||||
hideOverlappedSidebars();
|
||||
}
|
||||
}, 5)
|
||||
);
|
||||
window.addEventListener(
|
||||
"resize",
|
||||
throttle(() => {
|
||||
if (tocEl) {
|
||||
updateActiveLink();
|
||||
walk(tocEl, 0);
|
||||
}
|
||||
if (!isReaderMode()) {
|
||||
hideOverlappedSidebars();
|
||||
}
|
||||
}, 10)
|
||||
);
|
||||
hideOverlappedSidebars();
|
||||
highlightReaderToggle(isReaderMode());
|
||||
});
|
||||
|
||||
tabsets.init();
|
||||
|
||||
function throttle(func, wait) {
|
||||
let waiting = false;
|
||||
return function () {
|
||||
if (!waiting) {
|
||||
func.apply(this, arguments);
|
||||
waiting = true;
|
||||
setTimeout(function () {
|
||||
waiting = false;
|
||||
}, wait);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function nexttick(func) {
|
||||
return setTimeout(func, 0);
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
// grouped tabsets
|
||||
|
||||
export function init() {
|
||||
window.addEventListener("pageshow", (_event) => {
|
||||
function getTabSettings() {
|
||||
const data = localStorage.getItem("quarto-persistent-tabsets-data");
|
||||
if (!data) {
|
||||
localStorage.setItem("quarto-persistent-tabsets-data", "{}");
|
||||
return {};
|
||||
}
|
||||
if (data) {
|
||||
return JSON.parse(data);
|
||||
}
|
||||
}
|
||||
|
||||
function setTabSettings(data) {
|
||||
localStorage.setItem(
|
||||
"quarto-persistent-tabsets-data",
|
||||
JSON.stringify(data)
|
||||
);
|
||||
}
|
||||
|
||||
function setTabState(groupName, groupValue) {
|
||||
const data = getTabSettings();
|
||||
data[groupName] = groupValue;
|
||||
setTabSettings(data);
|
||||
}
|
||||
|
||||
function toggleTab(tab, active) {
|
||||
const tabPanelId = tab.getAttribute("aria-controls");
|
||||
const tabPanel = document.getElementById(tabPanelId);
|
||||
if (active) {
|
||||
tab.classList.add("active");
|
||||
tabPanel.classList.add("active");
|
||||
} else {
|
||||
tab.classList.remove("active");
|
||||
tabPanel.classList.remove("active");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAll(selectedGroup, selectorsToSync) {
|
||||
for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) {
|
||||
const active = selectedGroup === thisGroup;
|
||||
for (const tab of tabs) {
|
||||
toggleTab(tab, active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findSelectorsToSyncByLanguage() {
|
||||
const result = {};
|
||||
const tabs = Array.from(
|
||||
document.querySelectorAll(`div[data-group] a[id^='tabset-']`)
|
||||
);
|
||||
for (const item of tabs) {
|
||||
const div = item.parentElement.parentElement.parentElement;
|
||||
const group = div.getAttribute("data-group");
|
||||
if (!result[group]) {
|
||||
result[group] = {};
|
||||
}
|
||||
const selectorsToSync = result[group];
|
||||
const value = item.innerHTML;
|
||||
if (!selectorsToSync[value]) {
|
||||
selectorsToSync[value] = [];
|
||||
}
|
||||
selectorsToSync[value].push(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function setupSelectorSync() {
|
||||
const selectorsToSync = findSelectorsToSyncByLanguage();
|
||||
Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => {
|
||||
Object.entries(tabSetsByValue).forEach(([value, items]) => {
|
||||
items.forEach((item) => {
|
||||
item.addEventListener("click", (_event) => {
|
||||
setTabState(group, value);
|
||||
toggleAll(value, selectorsToSync[group]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return selectorsToSync;
|
||||
}
|
||||
|
||||
const selectorsToSync = setupSelectorSync();
|
||||
for (const [group, selectedName] of Object.entries(getTabSettings())) {
|
||||
const selectors = selectorsToSync[group];
|
||||
// it's possible that stale state gives us empty selections, so we explicitly check here.
|
||||
if (selectors) {
|
||||
toggleAll(selectedName, selectors);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1}
|
||||
File diff suppressed because one or more lines are too long
122
M2/Data Visualisation/tp3/sh-black_scholes/app-enonce.R
Normal file
122
M2/Data Visualisation/tp3/sh-black_scholes/app-enonce.R
Normal file
@@ -0,0 +1,122 @@
|
||||
# Original source : https://srdas.github.io/MLBook/Shiny.html#the-application-program
|
||||
|
||||
library(shiny)
|
||||
library(plotly)
|
||||
library(ggplot2)
|
||||
library(ggthemes)
|
||||
|
||||
|
||||
|
||||
##### CONSIGNES #####
|
||||
# 1. Completer le code du serveur (???) permet de compléter l'objet `output` (argument plotCall
|
||||
# et plotPut). Il s'agit de deux figures qui permet de presenter la valeur
|
||||
# d'un call et d'un put en fonction du Strike K.
|
||||
|
||||
# 2. Compléter le code de l'ui (???) afin de creer des box affichant la valeur de l'action
|
||||
# 'Stock Price' et du strike 'Strike Price'. Prendre par dééfaut la valeur 100.
|
||||
|
||||
# 3 . Sur la base du slider pour la Maturité, ajouter un slider pour la volatilité,
|
||||
# le taux sans risque et le taux de dividende.
|
||||
|
||||
# 4. Lancer l'application.
|
||||
|
||||
##### SERVER #####
|
||||
|
||||
# Define server logic for random distribution application
|
||||
server <- function(input, output) {
|
||||
|
||||
#Generate Black-Scholes values
|
||||
BS = function(S,K,T,v,rf,dv) {
|
||||
d1 = (log(S/K) + (rf-dv+0.5*v^2)*T)/(v*sqrt(T))
|
||||
d2 = d1 - v*sqrt(T)
|
||||
bscall = S*exp(-dv*T)*pnorm(d1) - K*exp(-rf*T)*pnorm(d2)
|
||||
bsput = -S*exp(-dv*T)*pnorm(-d1) + K*exp(-rf*T)*pnorm(-d2)
|
||||
res = c(bscall,bsput)
|
||||
}
|
||||
|
||||
#Call option price
|
||||
output$BScall <- renderText({
|
||||
#Get inputs
|
||||
S = input$stockprice
|
||||
K = input$strike
|
||||
T = input$maturity
|
||||
v = input$volatility
|
||||
rf = input$riskfreerate
|
||||
dv = input$divrate
|
||||
res = round(BS(S,K,T,v,rf,dv)[1],4)
|
||||
})
|
||||
|
||||
#Put option price
|
||||
output$BSput <- renderText({
|
||||
#Get inputs
|
||||
S = input$stockprice
|
||||
K = input$strike
|
||||
T = input$maturity
|
||||
v = input$volatility
|
||||
rf = input$riskfreerate
|
||||
dv = input$divrate
|
||||
res = round(BS(S,K,T,v,rf,dv)[2],4)
|
||||
})
|
||||
|
||||
#Call plot
|
||||
output$plotCall <- renderPlotly({
|
||||
S = input$stockprice
|
||||
K = input$strike
|
||||
T = input$maturity
|
||||
v = input$volatility
|
||||
rf = input$riskfreerate
|
||||
dv = input$divrate
|
||||
vcall = NULL; vput = NULL
|
||||
strikes = seq(K-30,K+30)
|
||||
for (k in strikes) {
|
||||
vcall = ???
|
||||
vput = ???
|
||||
}
|
||||
df = data.frame(strikes,vcall,vput)
|
||||
p <- ggplot(???) + ???
|
||||
plotly::ggplotly(p)
|
||||
})
|
||||
|
||||
#Put plot
|
||||
output$plotPut <- ???
|
||||
|
||||
}
|
||||
|
||||
##### UI #####
|
||||
|
||||
ui <- shinyUI(fluidPage(
|
||||
|
||||
titlePanel("Black-Scholes-Merton (1973)"),
|
||||
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
numericInput(???,'Stock Price', ???),
|
||||
numericInput(???,'Strike Price', ???),
|
||||
sliderInput('maturity','Maturity (years)',min=0.1,max=10,value=1,step=0.01),
|
||||
sliderInput(???),
|
||||
sliderInput(???),
|
||||
sliderInput(???),
|
||||
hr(),
|
||||
p('Please refer to following for more details:',
|
||||
a("Black-Scholes (1973)",
|
||||
href = "https://en.wikipedia.org/wiki/Black%E2%80%93Scholes_model")),
|
||||
hr()
|
||||
),
|
||||
|
||||
mainPanel(
|
||||
h2('European call price'),
|
||||
textOutput("BScall"),
|
||||
hr(),
|
||||
h2('European put price'),
|
||||
textOutput("BSput"),
|
||||
hr(),
|
||||
tabsetPanel(
|
||||
tabPanel("Calls", plotlyOutput("plotCall",width="100%")),
|
||||
tabPanel("Puts", plotlyOutput("plotPut",width="100%"))
|
||||
)
|
||||
)
|
||||
)
|
||||
))
|
||||
|
||||
##### Run #####
|
||||
???
|
||||
51
M2/Data Visualisation/tp3/sh-first_app/app.R
Normal file
51
M2/Data Visualisation/tp3/sh-first_app/app.R
Normal file
@@ -0,0 +1,51 @@
|
||||
#
|
||||
# This is a Shiny web application. You can run the application by clicking
|
||||
# the 'Run App' button above.
|
||||
#
|
||||
# Find out more about building applications with Shiny here:
|
||||
#
|
||||
# https://shiny.posit.co/
|
||||
#
|
||||
|
||||
library(shiny)
|
||||
|
||||
# Define UI for application that draws a histogram
|
||||
ui <- fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("Old Faithful Geyser Data"),
|
||||
|
||||
# Sidebar with a slider input for number of bins
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
sliderInput("bins",
|
||||
"Number of bins:",
|
||||
min = 1,
|
||||
max = 50,
|
||||
value = 30)
|
||||
),
|
||||
|
||||
# Show a plot of the generated distribution
|
||||
mainPanel(
|
||||
plotOutput("distPlot")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Define server logic required to draw a histogram
|
||||
server <- function(input, output) {
|
||||
|
||||
output$distPlot <- renderPlot({
|
||||
# generate bins based on input$bins from ui.R
|
||||
x <- faithful[, 2]
|
||||
bins <- seq(min(x), max(x), length.out = input$bins + 1)
|
||||
|
||||
# draw the histogram with the specified number of bins
|
||||
hist(x, breaks = bins, col = 'darkgray', border = 'white',
|
||||
xlab = 'Waiting time to next eruption (in mins)',
|
||||
main = 'Histogram of waiting times')
|
||||
})
|
||||
}
|
||||
|
||||
# Run the application
|
||||
shinyApp(ui = ui, server = server)
|
||||
198
M2/Data Visualisation/tp3/style.css
Normal file
198
M2/Data Visualisation/tp3/style.css
Normal file
@@ -0,0 +1,198 @@
|
||||
.infobox {
|
||||
padding: 1em 1em 1em 4em;
|
||||
margin-bottom: 10px;
|
||||
border: 2px solid orange;
|
||||
border-radius: 10px;
|
||||
background: #f5f5f5 5px center/3em no-repeat;
|
||||
}
|
||||
|
||||
/* Custom box styles for math environments */
|
||||
|
||||
/* Lemma Box */
|
||||
.lemma-box {
|
||||
border: 2px solid #3c8dbc;
|
||||
background-color: #e6f7ff;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* Theorem Box */
|
||||
.theorem-box {
|
||||
border: 2px solid #2ca02c;
|
||||
background-color: #eaffea;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* Corollary Box */
|
||||
.corollary-box {
|
||||
border: 2px solid #2ca02c;
|
||||
background-color: #eaffea;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* Proposition Box */
|
||||
.proposition-box {
|
||||
border: 2px solid #ff7f0e;
|
||||
background-color: #fff3e6;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* Conjecture Box */
|
||||
.conjecture-box {
|
||||
border: 2px solid #9467bd;
|
||||
background-color: #f5e6ff;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* Definition Box */
|
||||
.definition-box {
|
||||
border: 2px solid #d62728;
|
||||
background-color: #ffe6e6;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* Example Box */
|
||||
.example-box {
|
||||
border: 2px solid #17becf;
|
||||
/* background-color: #e6f7ff;*/
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* Exercise Box */
|
||||
.exercise-box {
|
||||
border: 2px solid #1f77b4;
|
||||
background-color: #e6f7ff;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* Hypothesis Box */
|
||||
.hypothesis-box {
|
||||
border: 2px solid #e377c2;
|
||||
background-color: #ffe6f5;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* Remark Box */
|
||||
.remark-box {
|
||||
border: 2px solid #7f7f7f;
|
||||
background-color: #f2f2f2;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* Proof Box */
|
||||
.proof-box {
|
||||
border: 2px solid #bcbd22;
|
||||
background-color: #fafad2;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* Hint Box */
|
||||
.hint-box {
|
||||
border: 2px solid #7f7f7f;
|
||||
background-color: #f2f2f2;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
|
||||
/* Numérotation automatique */
|
||||
|
||||
|
||||
.lemma-box::before {
|
||||
counter-increment: lemma-counter;
|
||||
content: "Lemme " counter(lemma-counter) ". ";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.theorem-box::before {
|
||||
counter-increment: theorem-counter;
|
||||
content: "Théorème " counter(theorem-counter) ". ";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.corollary-box::before {
|
||||
counter-increment: corollary-counter;
|
||||
content: "Corollaire " counter(corollary-counter) ". ";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.proposition-box::before {
|
||||
counter-increment: proposition-counter;
|
||||
content: "Proposition " counter(proposition-counter) ". ";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.conjecture-box::before {
|
||||
counter-increment: conjecture-counter;
|
||||
content: "Conjecture " counter(conjecture-counter) ". ";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.definition-box::before {
|
||||
counter-increment: definition-counter;
|
||||
content: "Définition " counter(definition-counter) ". ";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.example-box::before {
|
||||
counter-increment: example-counter;
|
||||
content: "Exemple " counter(example-counter) ". ";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.exercise-box::before {
|
||||
counter-increment: exercise-counter;
|
||||
content: "Question " counter(exercise-counter) ". ";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hypothesis-box::before {
|
||||
counter-increment: hypothesis-counter;
|
||||
content: "Hypothèse " counter(hypothesis-counter) ". ";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.remark-box::before {
|
||||
counter-increment: remark-counter;
|
||||
content: "Remarque " counter(remark-counter) ". ";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.proof-box::before {
|
||||
counter-increment: proof-counter;
|
||||
content: "Preuve " counter(proof-counter) ". ";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hint-box::before {
|
||||
content: "Indice." ;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Initialisation des compteurs */
|
||||
body {
|
||||
counter-reset: lemma-counter corollary-counter proposition-counter conjecture-counter definition-counter example-counter exercise-counter hypothesis-counter remark-counter proof-counter;
|
||||
}
|
||||
30
M2/Data Visualisation/tp3/tp3.Rmd
Normal file
30
M2/Data Visualisation/tp3/tp3.Rmd
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
title: "Exemple R Markdown"
|
||||
author: "Quentin Guibert"
|
||||
date: "`r Sys.Date()`"
|
||||
output: html_document
|
||||
---
|
||||
|
||||
```{r setup, include=FALSE}
|
||||
knitr::opts_chunk$set(echo = TRUE)
|
||||
```
|
||||
|
||||
## R Markdown
|
||||
|
||||
This is an R Markdown document. Markdown is a simple formatting syntax for authoring HTML, PDF, and MS Word documents. For more details on using R Markdown see <http://rmarkdown.rstudio.com>.
|
||||
|
||||
When you click the **Knit** button a document will be generated that includes both content as well as the output of any embedded R code chunks within the document. You can embed an R code chunk like this:
|
||||
|
||||
```{r cars}
|
||||
summary(cars)
|
||||
```
|
||||
|
||||
## Including Plots
|
||||
|
||||
You can also embed plots, for example:
|
||||
|
||||
```{r pressure, echo=FALSE}
|
||||
plot(pressure)
|
||||
```
|
||||
|
||||
Note that the `echo = FALSE` parameter was added to the code chunk to prevent printing of the R code that generated the plot.
|
||||
17
M2/Data Visualisation/tp3/tp3.Rproj
Normal file
17
M2/Data Visualisation/tp3/tp3.Rproj
Normal file
@@ -0,0 +1,17 @@
|
||||
Version: 1.0
|
||||
|
||||
RestoreWorkspace: Default
|
||||
SaveWorkspace: Default
|
||||
AlwaysSaveHistory: Default
|
||||
|
||||
EnableCodeIndexing: Yes
|
||||
UseSpacesForTab: Yes
|
||||
NumSpacesForTab: 2
|
||||
Encoding: UTF-8
|
||||
|
||||
RnwWeave: Sweave
|
||||
LaTeX: pdfLaTeX
|
||||
|
||||
BuildType: Website
|
||||
|
||||
SpellingDictionary: fr_FR
|
||||
Reference in New Issue
Block a user