Update app.R
Browse files
app.R
CHANGED
@@ -10,19 +10,26 @@ library(rnaturalearth)
|
|
10 |
library(rnaturalearthdata)
|
11 |
library(countrycode)
|
12 |
library(ggplot2)
|
13 |
-
library(ggiraph)
|
|
|
|
|
|
|
14 |
|
15 |
# =============================
|
16 |
# UI
|
17 |
# =============================
|
18 |
ui <- dashboardPage(
|
|
|
|
|
19 |
skin = "black",
|
|
|
20 |
dashboardHeader(
|
21 |
title = span(
|
22 |
style = "font-weight: 600; font-size: 18px;",
|
23 |
"Country Representation"
|
24 |
)
|
25 |
),
|
|
|
26 |
dashboardSidebar(
|
27 |
sidebarMenu(
|
28 |
menuItem("Map Type", tabName = "cartogramTab", icon = icon("globe"))
|
@@ -38,21 +45,27 @@ ui <- dashboardPage(
|
|
38 |
)
|
39 |
)
|
40 |
),
|
|
|
41 |
dashboardBody(
|
|
|
42 |
tags$head(
|
43 |
tags$link(
|
44 |
href = "https://fonts.googleapis.com/css2?family=OCR+A+Extended&display=swap",
|
45 |
rel = "stylesheet"
|
46 |
),
|
47 |
tags$style(HTML("
|
|
|
48 |
html, body, h1, h2, h3, h4, h5, h6, p, div, span, label, input, button, select,
|
49 |
.box, .content-wrapper, .main-sidebar, .main-header .navbar, .main-header .logo,
|
50 |
.sidebar-menu, .sidebar-menu li a, .sidebar-menu .fa {
|
51 |
font-family: 'OCR A Extended', monospace !important;
|
52 |
}
|
|
|
|
|
53 |
.main-header .navbar {
|
54 |
background: linear-gradient(to right, #3b6978, #204051) !important;
|
55 |
}
|
|
|
56 |
.main-header .logo {
|
57 |
background: #1b2a2f !important;
|
58 |
color: #ffffff !important;
|
@@ -60,23 +73,31 @@ ui <- dashboardPage(
|
|
60 |
font-size: 18px;
|
61 |
font-weight: 600;
|
62 |
}
|
|
|
63 |
.main-sidebar {
|
64 |
background-color: #1b2a2f !important;
|
65 |
}
|
|
|
66 |
.sidebar-menu > li.active > a,
|
67 |
.sidebar-menu > li:hover > a {
|
68 |
background-color: #344e5c !important;
|
69 |
border-left-color: #78cdd7 !important;
|
70 |
color: #ffffff !important;
|
71 |
}
|
|
|
|
|
72 |
.sidebar-menu .fa {
|
73 |
color: #78cdd7 !important;
|
74 |
}
|
|
|
|
|
75 |
.sidebar-menu > li > a {
|
76 |
color: #b8c7ce !important;
|
77 |
font-size: 15px;
|
78 |
font-weight: 500;
|
79 |
}
|
|
|
|
|
80 |
.box {
|
81 |
border-top: none !important;
|
82 |
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
@@ -87,14 +108,17 @@ ui <- dashboardPage(
|
|
87 |
color: #fff;
|
88 |
border-radius: 6px 6px 0 0;
|
89 |
}
|
|
|
90 |
.box .box-body {
|
91 |
padding: 0 !important;
|
92 |
}
|
|
|
93 |
.small, small {
|
94 |
font-size: 75%;
|
95 |
}
|
96 |
"))
|
97 |
),
|
|
|
98 |
tabItems(
|
99 |
tabItem(
|
100 |
tabName = "cartogramTab",
|
@@ -103,15 +127,11 @@ ui <- dashboardPage(
|
|
103 |
width = 12,
|
104 |
title = strong("Global Leadership Project (GLP)"),
|
105 |
solidHeader = TRUE,
|
|
|
106 |
div(
|
107 |
style = "height: 80vh; padding: 10px;",
|
108 |
girafeOutput("cartogramPlot", width = "100%", height = "100%")
|
109 |
)
|
110 |
-
),
|
111 |
-
box(
|
112 |
-
width = 12,
|
113 |
-
title = "Country Details",
|
114 |
-
verbatimTextOutput("countryDetails")
|
115 |
)
|
116 |
)
|
117 |
)
|
@@ -124,7 +144,28 @@ ui <- dashboardPage(
|
|
124 |
# =============================
|
125 |
server <- function(input, output, session) {
|
126 |
|
127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
128 |
rankings_data <- reactive({
|
129 |
read_csv("CountryRepresentationRankings.csv", show_col_types = FALSE) %>%
|
130 |
mutate(iso_a3 = countrycode(
|
@@ -135,24 +176,31 @@ server <- function(input, output, session) {
|
|
135 |
))
|
136 |
})
|
137 |
|
|
|
138 |
world_sf <- reactive({
|
139 |
ne_countries(scale = "medium", returnclass = "sf") %>%
|
140 |
dplyr::select(name, iso_a3, pop_est, geometry) %>%
|
141 |
-
st_transform(crs = "ESRI:54009")
|
142 |
})
|
143 |
|
|
|
144 |
cartogram_sf <- reactive({
|
145 |
merged_sf <- world_sf() %>%
|
146 |
left_join(rankings_data(), by = "iso_a3")
|
147 |
-
|
|
|
|
|
148 |
})
|
149 |
|
|
|
150 |
output$cartogramPlot <- renderGirafe({
|
151 |
req(input$indexChoice)
|
152 |
|
153 |
plot_data <- cartogram_sf()
|
154 |
index_col <- input$indexChoice
|
|
|
155 |
|
|
|
156 |
plot_data$tooltip_text <- paste0(
|
157 |
"<b>Country:</b> ", plot_data$Country, "<br/>",
|
158 |
"<b>Overall:</b> ", ifelse(!is.na(plot_data$Overall), plot_data$Overall, "N/A"), "<br/>",
|
@@ -163,6 +211,7 @@ server <- function(input, output, session) {
|
|
163 |
"<b>Language:</b> ", ifelse(!is.na(plot_data$Language), plot_data$Language, "N/A")
|
164 |
)
|
165 |
|
|
|
166 |
p <- ggplot(plot_data) +
|
167 |
geom_sf_interactive(
|
168 |
aes(
|
@@ -175,6 +224,8 @@ server <- function(input, output, session) {
|
|
175 |
) +
|
176 |
scale_fill_viridis_c(option = "D", na.value = "white") +
|
177 |
coord_sf(expand = FALSE) +
|
|
|
|
|
178 |
theme_void(base_size = 14, base_family = "sans") +
|
179 |
labs(
|
180 |
fill = paste(index_col, "Index"),
|
@@ -191,52 +242,22 @@ server <- function(input, output, session) {
|
|
191 |
legend.key.width = unit(2, "cm")
|
192 |
)
|
193 |
|
|
|
194 |
girafe(
|
195 |
ggobj = p,
|
196 |
width_svg = 10,
|
197 |
height_svg = 6,
|
198 |
options = list(
|
199 |
opts_tooltip(
|
200 |
-
css = "background-color: white;
|
201 |
-
font-family: 'OCR A Extended', monospace;
|
202 |
color: black;"
|
203 |
),
|
204 |
-
opts_selection(
|
205 |
-
|
206 |
-
css = "stroke:yellow;stroke-width:2px;"
|
207 |
-
)
|
208 |
)
|
209 |
)
|
210 |
})
|
211 |
-
|
212 |
-
selected_country_data <- reactive({
|
213 |
-
selected_iso <- input$cartogramPlot_selected
|
214 |
-
if (length(selected_iso) == 0) {
|
215 |
-
return(NULL)
|
216 |
-
}
|
217 |
-
plot_data <- cartogram_sf()
|
218 |
-
country_data <- plot_data %>% filter(iso_a3 == selected_iso)
|
219 |
-
if (nrow(country_data) == 0) {
|
220 |
-
return(NULL)
|
221 |
-
}
|
222 |
-
country_data
|
223 |
-
})
|
224 |
-
|
225 |
-
output$countryDetails <- renderText({
|
226 |
-
country_data <- selected_country_data()
|
227 |
-
if (is.null(country_data)) {
|
228 |
-
return("Click on a country to see details.")
|
229 |
-
}
|
230 |
-
paste0(
|
231 |
-
"Country: ", country_data$Country, "\n",
|
232 |
-
"Overall: ", ifelse(is.na(country_data$Overall), "N/A", country_data$Overall), "\n",
|
233 |
-
"Representation Gap: ", ifelse(is.na(country_data$RepresentationGap), "N/A", country_data$RepresentationGap), "\n",
|
234 |
-
"Ethnicity: ", ifelse(is.na(country_data$Ethnicity), "N/A", country_data$Ethnicity), "\n",
|
235 |
-
"Gender: ", ifelse(is.na(country_data$Gender), "N/A", country_data$Gender), "\n",
|
236 |
-
"Religion: ", ifelse(is.na(country_data$Religion), "N/A", country_data$Religion), "\n",
|
237 |
-
"Language: ", ifelse(is.na(country_data$Language), "N/A", country_data$Language)
|
238 |
-
)
|
239 |
-
})
|
240 |
}
|
241 |
|
242 |
# =============================
|
|
|
10 |
library(rnaturalearthdata)
|
11 |
library(countrycode)
|
12 |
library(ggplot2)
|
13 |
+
library(ggiraph) # For interactive hover tooltips
|
14 |
+
#library(extrafont)
|
15 |
+
#font_import() # run
|
16 |
+
#loadfonts() # load the fonts into R session
|
17 |
|
18 |
# =============================
|
19 |
# UI
|
20 |
# =============================
|
21 |
ui <- dashboardPage(
|
22 |
+
|
23 |
+
# Use black skin for the dashboard
|
24 |
skin = "black",
|
25 |
+
|
26 |
dashboardHeader(
|
27 |
title = span(
|
28 |
style = "font-weight: 600; font-size: 18px;",
|
29 |
"Country Representation"
|
30 |
)
|
31 |
),
|
32 |
+
|
33 |
dashboardSidebar(
|
34 |
sidebarMenu(
|
35 |
menuItem("Map Type", tabName = "cartogramTab", icon = icon("globe"))
|
|
|
45 |
)
|
46 |
)
|
47 |
),
|
48 |
+
|
49 |
dashboardBody(
|
50 |
+
# Bring in the OCR A Extended font from Google Fonts
|
51 |
tags$head(
|
52 |
tags$link(
|
53 |
href = "https://fonts.googleapis.com/css2?family=OCR+A+Extended&display=swap",
|
54 |
rel = "stylesheet"
|
55 |
),
|
56 |
tags$style(HTML("
|
57 |
+
/* Force OCR A Extended font across the entire UI and all HTML elements */
|
58 |
html, body, h1, h2, h3, h4, h5, h6, p, div, span, label, input, button, select,
|
59 |
.box, .content-wrapper, .main-sidebar, .main-header .navbar, .main-header .logo,
|
60 |
.sidebar-menu, .sidebar-menu li a, .sidebar-menu .fa {
|
61 |
font-family: 'OCR A Extended', monospace !important;
|
62 |
}
|
63 |
+
|
64 |
+
/* Header gradient background */
|
65 |
.main-header .navbar {
|
66 |
background: linear-gradient(to right, #3b6978, #204051) !important;
|
67 |
}
|
68 |
+
/* Logo area (left corner of the header) */
|
69 |
.main-header .logo {
|
70 |
background: #1b2a2f !important;
|
71 |
color: #ffffff !important;
|
|
|
73 |
font-size: 18px;
|
74 |
font-weight: 600;
|
75 |
}
|
76 |
+
/* Sidebar background */
|
77 |
.main-sidebar {
|
78 |
background-color: #1b2a2f !important;
|
79 |
}
|
80 |
+
/* Active or hovered tab in the sidebar */
|
81 |
.sidebar-menu > li.active > a,
|
82 |
.sidebar-menu > li:hover > a {
|
83 |
background-color: #344e5c !important;
|
84 |
border-left-color: #78cdd7 !important;
|
85 |
color: #ffffff !important;
|
86 |
}
|
87 |
+
|
88 |
+
/* Sidebar menu item icons */
|
89 |
.sidebar-menu .fa {
|
90 |
color: #78cdd7 !important;
|
91 |
}
|
92 |
+
|
93 |
+
/* Sidebar menu item text */
|
94 |
.sidebar-menu > li > a {
|
95 |
color: #b8c7ce !important;
|
96 |
font-size: 15px;
|
97 |
font-weight: 500;
|
98 |
}
|
99 |
+
|
100 |
+
/* Customize the boxes */
|
101 |
.box {
|
102 |
border-top: none !important;
|
103 |
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
|
108 |
color: #fff;
|
109 |
border-radius: 6px 6px 0 0;
|
110 |
}
|
111 |
+
/* Plot box spacing */
|
112 |
.box .box-body {
|
113 |
padding: 0 !important;
|
114 |
}
|
115 |
+
/* Footer text styling (plot captions, etc.) */
|
116 |
.small, small {
|
117 |
font-size: 75%;
|
118 |
}
|
119 |
"))
|
120 |
),
|
121 |
+
|
122 |
tabItems(
|
123 |
tabItem(
|
124 |
tabName = "cartogramTab",
|
|
|
127 |
width = 12,
|
128 |
title = strong("Global Leadership Project (GLP)"),
|
129 |
solidHeader = TRUE,
|
130 |
+
# Use girafeOutput instead of plotOutput
|
131 |
div(
|
132 |
style = "height: 80vh; padding: 10px;",
|
133 |
girafeOutput("cartogramPlot", width = "100%", height = "100%")
|
134 |
)
|
|
|
|
|
|
|
|
|
|
|
135 |
)
|
136 |
)
|
137 |
)
|
|
|
144 |
# =============================
|
145 |
server <- function(input, output, session) {
|
146 |
|
147 |
+
# Reactive value to store the currently clicked country
|
148 |
+
clicked_country <- reactiveVal(NULL)
|
149 |
+
|
150 |
+
# Observe clicks on the map
|
151 |
+
observeEvent(input$cartogramPlot_selected, {
|
152 |
+
selected_id <- input$cartogramPlot_selected
|
153 |
+
current_clicked <- clicked_country()
|
154 |
+
|
155 |
+
if (identical(selected_id, current_clicked)) {
|
156 |
+
# Unclick: set clicked_country to NULL
|
157 |
+
clicked_country(NULL)
|
158 |
+
} else {
|
159 |
+
# Click new country: update clicked_country
|
160 |
+
clicked_country(selected_id)
|
161 |
+
}
|
162 |
+
}, ignoreNULL = FALSE)
|
163 |
+
|
164 |
+
# 1. Custom matches for countries not recognized by default in 'countrycode'
|
165 |
+
custom_iso_matches <- c("Kosovo" = "XKX",
|
166 |
+
"Somaliland" = "SOM") # or any valid code you prefer
|
167 |
+
|
168 |
+
# 2. Read CSV data and create ISO3 codes with custom matches
|
169 |
rankings_data <- reactive({
|
170 |
read_csv("CountryRepresentationRankings.csv", show_col_types = FALSE) %>%
|
171 |
mutate(iso_a3 = countrycode(
|
|
|
176 |
))
|
177 |
})
|
178 |
|
179 |
+
# 3. Read/prepare world map shapefile
|
180 |
world_sf <- reactive({
|
181 |
ne_countries(scale = "medium", returnclass = "sf") %>%
|
182 |
dplyr::select(name, iso_a3, pop_est, geometry) %>%
|
183 |
+
st_transform(crs = "ESRI:54009") # Mollweide projection
|
184 |
})
|
185 |
|
186 |
+
# 4. Create the joined sf object (currently just a regular map)
|
187 |
cartogram_sf <- reactive({
|
188 |
merged_sf <- world_sf() %>%
|
189 |
left_join(rankings_data(), by = "iso_a3")
|
190 |
+
# Filter out rows with missing 'Overall', if you want to exclude them
|
191 |
+
merged_sf <- merged_sf[!is.na(merged_sf$Overall),]
|
192 |
+
merged_sf
|
193 |
})
|
194 |
|
195 |
+
# 5. Render the interactive map with ggiraph
|
196 |
output$cartogramPlot <- renderGirafe({
|
197 |
req(input$indexChoice)
|
198 |
|
199 |
plot_data <- cartogram_sf()
|
200 |
index_col <- input$indexChoice
|
201 |
+
selected_country_id <- clicked_country()
|
202 |
|
203 |
+
# Build a tooltip string: customize to your preference
|
204 |
plot_data$tooltip_text <- paste0(
|
205 |
"<b>Country:</b> ", plot_data$Country, "<br/>",
|
206 |
"<b>Overall:</b> ", ifelse(!is.na(plot_data$Overall), plot_data$Overall, "N/A"), "<br/>",
|
|
|
211 |
"<b>Language:</b> ", ifelse(!is.na(plot_data$Language), plot_data$Language, "N/A")
|
212 |
)
|
213 |
|
214 |
+
# Use geom_sf_interactive with aes() + get() instead of aes_string()
|
215 |
p <- ggplot(plot_data) +
|
216 |
geom_sf_interactive(
|
217 |
aes(
|
|
|
224 |
) +
|
225 |
scale_fill_viridis_c(option = "D", na.value = "white") +
|
226 |
coord_sf(expand = FALSE) +
|
227 |
+
# Use a generic base_family to avoid font errors
|
228 |
+
#theme_void(base_size = 14, base_family = "Courier New") +
|
229 |
theme_void(base_size = 14, base_family = "sans") +
|
230 |
labs(
|
231 |
fill = paste(index_col, "Index"),
|
|
|
242 |
legend.key.width = unit(2, "cm")
|
243 |
)
|
244 |
|
245 |
+
# Wrap the ggplot in a girafe object, including tooltip styling and click handling
|
246 |
girafe(
|
247 |
ggobj = p,
|
248 |
width_svg = 10,
|
249 |
height_svg = 6,
|
250 |
options = list(
|
251 |
opts_tooltip(
|
252 |
+
css = "background-color: white;
|
253 |
+
font-family: 'OCR A Extended', monospace;
|
254 |
color: black;"
|
255 |
),
|
256 |
+
opts_selection(type = "single", selected = selected_country_id),
|
257 |
+
opts_zoom(max = 5)
|
|
|
|
|
258 |
)
|
259 |
)
|
260 |
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
261 |
}
|
262 |
|
263 |
# =============================
|