cjerzak commited on
Commit
23b9eb6
·
verified ·
1 Parent(s): 66f6b8b

Update app.R

Browse files
Files changed (1) hide show
  1. app.R +358 -182
app.R CHANGED
@@ -52,22 +52,36 @@
52
  ui <- dashboardPage(
53
  # -- Header
54
  dashboardHeader(
55
- title = span(
56
- style = "font-weight: 600; font-size: 16px;",
57
- a(
58
- href = "http://aidevlab.org",
59
- "aidevlab.org",
60
- target = "_blank",
61
- style = "font-family: 'OCR A Std', monospace; color: white; text-decoration: underline;"
62
- )
63
- )
 
64
  ),
65
 
66
  # -- Sidebar
67
  dashboardSidebar(
 
 
 
 
 
 
 
 
 
 
 
 
68
  sidebarMenu(
69
  id = "tabs",
70
- menuItem("Wealth Map", tabName = "mapTab", icon = icon("map")),
 
71
  menuItem("Improvement Data", tabName = "improvementTab", icon = icon("table")),
72
  menuItem("Trends Over Time", tabName = "trendTab", icon = icon("chart-line"))
73
  ),
@@ -75,187 +89,337 @@
75
  conditionalPanel(
76
  condition = "input.tabs == 'mapTab'",
77
  br(),
78
- # Replaces the old selectInput for time periods with a slider that can animate
79
- sliderInput(
80
- inputId = "time_index",
81
- label = "Select Time Period (Years):",
82
- min = 1,
83
- max = length(time_periods),
84
- value = 1,
85
- step = 1,
86
- animate = animationOptions(interval = 3000, loop = TRUE)
 
 
 
 
87
  ),
88
  # Show the currently selected year range clearly
89
- strong("Currently Selected: "),
90
- textOutput("current_year_range", inline = TRUE),
91
- br(), br(),
 
 
92
 
93
- selectInput("color_palette", "Select Color Palette:",
94
- choices = c("Viridis" = "viridis",
95
- "Plasma" = "plasma",
96
- "Magma" = "magma",
97
- "Inferno"= "inferno",
98
- "Spectral (Brewer)" = "Spectral"),
99
- selected = "plasma"),
100
- sliderInput("opacity", "Map Opacity:", min = 0.2, max = 1, value = 0.8, step = 0.1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  ),
102
- # ---- Here is the minimal "Share" button HTML + JS inlined in Shiny ----
103
- # We wrap it in tags$div(...) and tags$script(HTML(...)) so it is recognized
104
- # by Shiny. You can adjust the styling or placement as needed.
105
- tags$div(
106
- style = "text-align: left; margin: 1em 0 1em 2em;",
107
- HTML('
108
- <button id="share-button"
109
- style="
110
- display: inline-flex;
111
- align-items: center;
112
- justify-content: center;
113
- gap: 8px;
114
- padding: 5px 10px;
115
- font-size: 16px;
116
- font-weight: normal;
117
- color: #000;
118
- background-color: #fff;
119
- border: 1px solid #ddd;
120
- border-radius: 6px;
121
- cursor: pointer;
122
- box-shadow: 0 1.5px 0 #000;
123
- ">
124
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
125
- stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
126
- <circle cx="18" cy="5" r="3"></circle>
127
- <circle cx="6" cy="12" r="3"></circle>
128
- <circle cx="18" cy="19" r="3"></circle>
129
- <line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line>
130
- <line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line>
131
- </svg>
132
- <strong>Share</strong>
133
- </button>
134
- '),
135
- # Insert the JS as well
136
- tags$script(
137
- HTML("
138
- (function() {
139
- const shareBtn = document.getElementById('share-button');
140
- // Reusable helper function to show a small “Copied!” message
141
- function showCopyNotification() {
142
- const notification = document.createElement('div');
143
- notification.innerText = 'Copied to clipboard';
144
- notification.style.position = 'fixed';
145
- notification.style.bottom = '20px';
146
- notification.style.right = '20px';
147
- notification.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
148
- notification.style.color = '#fff';
149
- notification.style.padding = '8px 12px';
150
- notification.style.borderRadius = '4px';
151
- notification.style.zIndex = '9999';
152
- document.body.appendChild(notification);
153
- setTimeout(() => { notification.remove(); }, 2000);
154
- }
155
- shareBtn.addEventListener('click', function() {
156
- const currentURL = window.location.href;
157
- const pageTitle = document.title || 'Check this out!';
158
- // If browser supports Web Share API
159
- if (navigator.share) {
160
- navigator.share({
161
- title: pageTitle,
162
- text: '',
163
- url: currentURL
164
- })
165
- .catch((error) => {
166
- console.log('Sharing failed', error);
167
- });
168
- } else {
169
- // Fallback: Copy URL
170
- if (navigator.clipboard && navigator.clipboard.writeText) {
171
- navigator.clipboard.writeText(currentURL).then(() => {
172
- showCopyNotification();
173
- }, (err) => {
174
- console.error('Could not copy text: ', err);
175
  });
176
  } else {
177
- // Double fallback for older browsers
178
- const textArea = document.createElement('textarea');
179
- textArea.value = currentURL;
180
- document.body.appendChild(textArea);
181
- textArea.select();
182
- try {
183
- document.execCommand('copy');
184
- showCopyNotification();
185
- } catch (err) {
186
- alert('Please copy this link:\\n' + currentURL);
 
 
 
 
 
 
 
 
 
 
187
  }
188
- document.body.removeChild(textArea);
189
  }
190
- }
191
- });
192
- })();
193
- ")
194
  )
195
- )
196
- # ---- End: Minimal Share button snippet ----
197
  ),
198
 
199
  # -- Body
200
  dashboardBody(
201
  tags$head(
 
 
202
  tags$link(rel = "stylesheet", href = "https://fonts.cdnfonts.com/css/ocr-a-std"),
203
- # Make the "play" button whiter/brighter
204
  tags$style(HTML("
205
- body {
206
- font-family: 'OCR A Std', monospace !important;
207
- }
208
- .slider-animate-button {
209
- background-color: #ffffff !important;
210
- color: #000000 !important;
211
- border: 2px solid #000000 !important;
212
- border-radius: 5px !important;
213
- padding: 5px 10px !important;
214
- top: 10px !important;
215
- }
216
- "))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  ),
218
  tabItems(
219
  # ---------- MAP TAB ----------
220
  tabItem(
221
  tabName = "mapTab",
222
  fluidRow(
223
- # Value Boxes across the top for key stats
224
- valueBoxOutput("highest_iwi_vb", width = 4),
225
- valueBoxOutput("lowest_iwi_vb", width = 4),
226
- valueBoxOutput("avg_iwi_vb", width = 4)
 
 
 
 
 
 
227
  ),
228
  fluidRow(
229
- # Map
230
- box(
231
- title = span("Wealth Map of Africa",
232
- style = "font-family: 'OCR A Std', monospace; font-size: 18px;"),
233
- width = 8, solidHeader = TRUE, status = "primary",
234
- leafletOutput("map", height = "550px"),
235
- p("Click anywhere on the map to view the time-series of IWI for that specific location (shown below).")
236
- ),
237
- # Histogram
238
- box(
239
- title = span("IWI Distribution (Selected Period)",
240
- style = "font-family: 'OCR A Std', monospace; font-size: 14px;"),
241
- width = 4, solidHeader = TRUE, status = "info",
242
- plotOutput("iwi_histogram", height = "250px"),
243
- p("This histogram shows the distribution of the International Wealth Index (IWI) values for the selected time period across Africa."),
244
- br(),
245
- strong("Note:"),
246
- " Wealth estimates for areas without human settlements have been excluded from the analysis.",
247
- br(),br(),
248
- p(HTML("<a href='https://doi.org/10.24963/ijcai.2023/684' target='_blank'>[Paper PDF]</a>"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  )
250
  ),
251
  # Time series at clicked location
252
  fluidRow(
253
- box(
254
- title = span("Time Series at Clicked Location",
255
- style = "font-family: 'OCR A Std', monospace; font-size: 14px;"),
256
- width = 12, solidHeader = TRUE, status = "warning",
257
- plotOutput("clicked_ts_plot", height = "300px"),
258
- p("Click on the map to see the full IWI time-series (1990–2019) for that location.")
 
 
 
 
259
  )
260
  )
261
  ),
@@ -264,17 +428,25 @@
264
  tabItem(
265
  tabName = "improvementTab",
266
  fluidRow(
267
- box(
268
- width = 12,
269
- title = span("Poverty Improvement by State",
270
- style = "font-family: 'OCR A Std', monospace; font-size: 18px;"),
271
- status = "primary", solidHeader = TRUE,
272
- p("This table shows the estimated improvement in mean IWI between 1990–1992 and 2017–2019 for each province in Africa.
273
- The 'Improvement' column indicates the change in IWI over this period. You can sort or filter the table,
274
- and use the download button to export the data."),
275
- downloadButton("download_data", "Download CSV", icon = icon("download")),
276
- br(), br(),
277
- DTOutput("improvement_table")
 
 
 
 
 
 
 
 
278
  )
279
  )
280
  ),
@@ -283,14 +455,17 @@
283
  tabItem(
284
  tabName = "trendTab",
285
  fluidRow(
286
- box(
287
- width = 12,
288
- title = span("Average Wealth Index Across Africa Over Time",
289
- style = "font-family: 'OCR A Std', monospace; font-size: 18px;"),
290
- status = "success", solidHeader = TRUE,
291
- p("This chart aggregates the mean IWI across all of Africa in each of the ten time periods.
292
- It provides a high-level view of how wealth (as measured by IWI) has changed over time."),
293
- plotOutput("trend_plot", height = "400px")
 
 
 
294
  )
295
  )
296
  )
@@ -298,6 +473,7 @@
298
  )
299
  )
300
 
 
301
  # ------------------------------
302
  # 4. Server
303
  # ------------------------------
 
52
  ui <- dashboardPage(
53
  # -- Header
54
  dashboardHeader(
55
+ title = span(
56
+ style = "font-weight: 600; font-size: 18px;",
57
+ a(
58
+ href = "http://aidevlab.org",
59
+ "aidevlab.org",
60
+ target = "_blank",
61
+ style = "font-family: 'OCR A Std', monospace; color: white; text-decoration: underline;"
62
+ )
63
+ ),
64
+ titleWidth = 250
65
  ),
66
 
67
  # -- Sidebar
68
  dashboardSidebar(
69
+ width = 250,
70
+ tags$style(HTML("
71
+ @media (max-width: 768px) {
72
+ .sidebar-toggle {
73
+ padding: 15px !important;
74
+ }
75
+ .sidebar-toggle .icon-bar {
76
+ width: 25px !important;
77
+ height: 3px !important;
78
+ }
79
+ }
80
+ ")),
81
  sidebarMenu(
82
  id = "tabs",
83
+ menuItem("Wealth Map", tabName = "mapTab", icon = icon("map"),
84
+ selected = TRUE),
85
  menuItem("Improvement Data", tabName = "improvementTab", icon = icon("table")),
86
  menuItem("Trends Over Time", tabName = "trendTab", icon = icon("chart-line"))
87
  ),
 
89
  conditionalPanel(
90
  condition = "input.tabs == 'mapTab'",
91
  br(),
92
+ # Larger, more touch-friendly time period slider
93
+ div(
94
+ style = "padding: 0 15px;",
95
+ sliderInput(
96
+ inputId = "time_index",
97
+ label = tags$span(style = "font-size: 16px;", "Select Time Period:"),
98
+ min = 1,
99
+ max = length(time_periods),
100
+ value = 1,
101
+ step = 1,
102
+ animate = animationOptions(interval = 3000, loop = TRUE),
103
+ width = "100%"
104
+ )
105
  ),
106
  # Show the currently selected year range clearly
107
+ div(
108
+ style = "padding: 0 15px; margin-bottom: 20px;",
109
+ strong(style = "font-size: 16px;", "Selected Period:"),
110
+ textOutput("current_year_range", inline = TRUE)
111
+ ),
112
 
113
+ div(
114
+ style = "padding: 0 15px;",
115
+ selectInput(
116
+ "color_palette",
117
+ tags$span(style = "font-size: 16px;", "Color Palette:"),
118
+ choices = c("Viridis" = "viridis",
119
+ "Plasma" = "plasma",
120
+ "Magma" = "magma",
121
+ "Inferno"= "inferno",
122
+ "Spectral (Brewer)" = "Spectral"),
123
+ selected = "plasma",
124
+ width = "100%"
125
+ )
126
+ ),
127
+ div(
128
+ style = "padding: 0 15px; margin-bottom: 20px;",
129
+ sliderInput(
130
+ "opacity",
131
+ tags$span(style = "font-size: 16px;", "Map Opacity:"),
132
+ min = 0.2,
133
+ max = 1,
134
+ value = 0.8,
135
+ step = 0.1,
136
+ width = "100%"
137
+ )
138
+ )
139
  ),
140
+ # Share button with improved mobile styling
141
+ tags$div(
142
+ style = "text-align: center; margin: 20px 0;",
143
+ HTML('
144
+ <button id="share-button"
145
+ style="
146
+ display: inline-flex;
147
+ align-items: center;
148
+ justify-content: center;
149
+ gap: 10px;
150
+ padding: 12px 20px;
151
+ font-size: 18px;
152
+ font-weight: bold;
153
+ color: #000;
154
+ background-color: #fff;
155
+ border: 1px solid #ddd;
156
+ border-radius: 8px;
157
+ cursor: pointer;
158
+ box-shadow: 0 2px 0 #000;
159
+ width: 80%;
160
+ ">
161
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
162
+ stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
163
+ <circle cx="18" cy="5" r="3"></circle>
164
+ <circle cx="6" cy="12" r="3"></circle>
165
+ <circle cx="18" cy="19" r="3"></circle>
166
+ <line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line>
167
+ <line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line>
168
+ </svg>
169
+ <strong>Share</strong>
170
+ </button>
171
+ '),
172
+ # Insert the JS as well
173
+ tags$script(
174
+ HTML("
175
+ (function() {
176
+ const shareBtn = document.getElementById('share-button');
177
+ // Reusable helper function to show a small \"Copied!\" message
178
+ function showCopyNotification() {
179
+ const notification = document.createElement('div');
180
+ notification.innerText = 'Copied to clipboard';
181
+ notification.style.position = 'fixed';
182
+ notification.style.bottom = '20px';
183
+ notification.style.left = '50%';
184
+ notification.style.transform = 'translateX(-50%)';
185
+ notification.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
186
+ notification.style.color = '#fff';
187
+ notification.style.padding = '10px 16px';
188
+ notification.style.borderRadius = '6px';
189
+ notification.style.zIndex = '9999';
190
+ notification.style.fontSize = '16px';
191
+ document.body.appendChild(notification);
192
+ setTimeout(() => { notification.remove(); }, 2000);
193
+ }
194
+ shareBtn.addEventListener('click', function() {
195
+ const currentURL = window.location.href;
196
+ const pageTitle = document.title || 'Check this out!';
197
+ // If browser supports Web Share API (most mobile browsers)
198
+ if (navigator.share) {
199
+ navigator.share({
200
+ title: pageTitle,
201
+ text: '',
202
+ url: currentURL
203
+ })
204
+ .catch((error) => {
205
+ console.log('Sharing failed', error);
 
 
 
 
 
 
 
206
  });
207
  } else {
208
+ // Fallback: Copy URL
209
+ if (navigator.clipboard && navigator.clipboard.writeText) {
210
+ navigator.clipboard.writeText(currentURL).then(() => {
211
+ showCopyNotification();
212
+ }, (err) => {
213
+ console.error('Could not copy text: ', err);
214
+ });
215
+ } else {
216
+ // Double fallback for older browsers
217
+ const textArea = document.createElement('textarea');
218
+ textArea.value = currentURL;
219
+ document.body.appendChild(textArea);
220
+ textArea.select();
221
+ try {
222
+ document.execCommand('copy');
223
+ showCopyNotification();
224
+ } catch (err) {
225
+ alert('Please copy this link:\\n' + currentURL);
226
+ }
227
+ document.body.removeChild(textArea);
228
  }
 
229
  }
230
+ });
231
+ })();
232
+ ")
233
+ )
234
  )
 
 
235
  ),
236
 
237
  # -- Body
238
  dashboardBody(
239
  tags$head(
240
+ # Viewport meta tag for proper mobile scaling
241
+ tags$meta(name = "viewport", content = "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"),
242
  tags$link(rel = "stylesheet", href = "https://fonts.cdnfonts.com/css/ocr-a-std"),
243
+ # Additional mobile-friendly styles
244
  tags$style(HTML("
245
+ body {
246
+ font-family: 'OCR A Std', monospace !important;
247
+ }
248
+
249
+ .slider-animate-button {
250
+ background-color: #ffffff !important;
251
+ color: #000000 !important;
252
+ border: 2px solid #000000 !important;
253
+ border-radius: 6px !important;
254
+ padding: 8px 15px !important;
255
+ font-size: 18px !important;
256
+ top: 5px !important;
257
+ }
258
+
259
+ /* Mobile-friendly boxes and layouts */
260
+ @media (max-width: 768px) {
261
+ .box {
262
+ margin-bottom: 20px !important;
263
+ border-radius: 8px !important;
264
+ }
265
+
266
+ .box-header {
267
+ padding: 15px !important;
268
+ }
269
+
270
+ .box-title {
271
+ font-size: 18px !important;
272
+ }
273
+
274
+ .box-body {
275
+ padding: 15px !important;
276
+ }
277
+
278
+ .nav-tabs-custom .nav-tabs li a {
279
+ padding: 15px !important;
280
+ font-size: 16px !important;
281
+ }
282
+
283
+ /* Increase button sizes for touch */
284
+ .btn {
285
+ padding: 12px 18px !important;
286
+ font-size: 16px !important;
287
+ }
288
+
289
+ /* Larger inputs and form controls */
290
+ .form-control {
291
+ height: 45px !important;
292
+ font-size: 16px !important;
293
+ }
294
+
295
+ /* Improve DataTable mobile view */
296
+ .dataTables_wrapper .dataTables_length,
297
+ .dataTables_wrapper .dataTables_filter,
298
+ .dataTables_wrapper .dataTables_info,
299
+ .dataTables_wrapper .dataTables_paginate {
300
+ text-align: center !important;
301
+ float: none !important;
302
+ margin-bottom: 10px !important;
303
+ }
304
+
305
+ /* Make sure text doesn't overflow on small screens */
306
+ p, h1, h2, h3, h4, h5, h6 {
307
+ word-wrap: break-word !important;
308
+ }
309
+ }
310
+
311
+ /* Ensure value boxes stack nicely */
312
+ .small-box {
313
+ border-radius: 8px !important;
314
+ margin-bottom: 20px !important;
315
+ }
316
+
317
+ .small-box .icon {
318
+ font-size: 70px !important;
319
+ }
320
+
321
+ @media (max-width: 768px) {
322
+ .small-box h3 {
323
+ font-size: 24px !important;
324
+ }
325
+
326
+ .small-box p {
327
+ font-size: 16px !important;
328
+ }
329
+
330
+ .small-box .icon {
331
+ display: none !important;
332
+ }
333
+ }
334
+
335
+ /* Make leaflet controls more touch friendly */
336
+ .leaflet-touch .leaflet-control-layers,
337
+ .leaflet-touch .leaflet-bar {
338
+ border: 2px solid rgba(0,0,0,0.2) !important;
339
+ }
340
+
341
+ .leaflet-touch .leaflet-control-zoom-in,
342
+ .leaflet-touch .leaflet-control-zoom-out {
343
+ font-size: 18px !important;
344
+ width: 34px !important;
345
+ height: 34px !important;
346
+ line-height: 34px !important;
347
+ }
348
+
349
+ /* Ensure plots are responsive */
350
+ .shiny-plot-output {
351
+ width: 100% !important;
352
+ max-width: 100% !important;
353
+ }
354
+ "))
355
  ),
356
  tabItems(
357
  # ---------- MAP TAB ----------
358
  tabItem(
359
  tabName = "mapTab",
360
  fluidRow(
361
+ column(
362
+ width = 12,
363
+ # Value Boxes - will stack on mobile
364
+ div(
365
+ class = "row",
366
+ div(class = "col-sm-4 col-xs-12", valueBoxOutput("highest_iwi_vb", width = NULL)),
367
+ div(class = "col-sm-4 col-xs-12", valueBoxOutput("lowest_iwi_vb", width = NULL)),
368
+ div(class = "col-sm-4 col-xs-12", valueBoxOutput("avg_iwi_vb", width = NULL))
369
+ )
370
+ )
371
  ),
372
  fluidRow(
373
+ # Map - full width on mobile
374
+ column(
375
+ width = 12,
376
+ div(
377
+ class = "row",
378
+ div(
379
+ class = "col-md-8 col-sm-12",
380
+ box(
381
+ title = span("Wealth Map of Africa",
382
+ style = "font-family: 'OCR A Std', monospace; font-size: 18px;"),
383
+ width = NULL, solidHeader = TRUE, status = "primary",
384
+ leafletOutput("map", height = "450px"),
385
+ p(style = "padding-top: 10px; font-size: 14px;",
386
+ "Tap anywhere on the map to view the time-series of IWI for that location.")
387
+ )
388
+ ),
389
+ # Histogram - will position below map on mobile
390
+ div(
391
+ class = "col-md-4 col-sm-12",
392
+ box(
393
+ title = span("IWI Distribution",
394
+ style = "font-family: 'OCR A Std', monospace; font-size: 16px;"),
395
+ width = NULL, solidHeader = TRUE, status = "info",
396
+ plotOutput("iwi_histogram", height = "200px"),
397
+ p(style = "font-size: 14px;",
398
+ "Distribution of International Wealth Index values for the selected time period."),
399
+ strong(style = "font-size: 14px;", "Note:"),
400
+ span(style = "font-size: 14px;",
401
+ " Areas without human settlements are excluded."),
402
+ div(
403
+ style = "margin-top: 10px;",
404
+ p(HTML("<a href='https://doi.org/10.24963/ijcai.2023/684' target='_blank' style='font-size: 14px;'>[Paper PDF]</a>"))
405
+ )
406
+ )
407
+ )
408
+ )
409
  )
410
  ),
411
  # Time series at clicked location
412
  fluidRow(
413
+ column(
414
+ width = 12,
415
+ box(
416
+ title = span("Time Series at Tapped Location",
417
+ style = "font-family: 'OCR A Std', monospace; font-size: 16px;"),
418
+ width = NULL, solidHeader = TRUE, status = "warning",
419
+ plotOutput("clicked_ts_plot", height = "250px"),
420
+ p(style = "font-size: 14px;",
421
+ "Tap on the map to see the IWI time-series (1990–2019) for that location.")
422
+ )
423
  )
424
  )
425
  ),
 
428
  tabItem(
429
  tabName = "improvementTab",
430
  fluidRow(
431
+ column(
432
+ width = 12,
433
+ box(
434
+ width = NULL,
435
+ title = span("Poverty Improvement by State",
436
+ style = "font-family: 'OCR A Std', monospace; font-size: 18px;"),
437
+ status = "primary", solidHeader = TRUE,
438
+ p(style = "font-size: 14px;", "This table shows the estimated improvement in mean IWI between 1990–1992 and 2017–2019 for each province in Africa."),
439
+ div(
440
+ style = "margin: 15px 0;",
441
+ downloadButton("download_data", "Download CSV",
442
+ style = "width: 100%; padding: 12px; font-size: 16px;")
443
+ ),
444
+ # Mobile-optimized table
445
+ div(
446
+ style = "overflow-x: auto;",
447
+ DTOutput("improvement_table")
448
+ )
449
+ )
450
  )
451
  )
452
  ),
 
455
  tabItem(
456
  tabName = "trendTab",
457
  fluidRow(
458
+ column(
459
+ width = 12,
460
+ box(
461
+ width = NULL,
462
+ title = span("Average Wealth Index Over Time",
463
+ style = "font-family: 'OCR A Std', monospace; font-size: 18px;"),
464
+ status = "success", solidHeader = TRUE,
465
+ p(style = "font-size: 14px;",
466
+ "Mean IWI across Africa over the ten time periods, showing how wealth has changed over time."),
467
+ plotOutput("trend_plot", height = "350px")
468
+ )
469
  )
470
  )
471
  )
 
473
  )
474
  )
475
 
476
+
477
  # ------------------------------
478
  # 4. Server
479
  # ------------------------------