alexdum commited on
Commit
c28132e
·
1 Parent(s): 3823d23

feat: simplify date range selection by removing modal inputs and enhance station metadata display with value boxes.

Browse files
Files changed (2) hide show
  1. server.R +48 -147
  2. ui.R +18 -41
server.R CHANGED
@@ -83,12 +83,8 @@ server <- function(input, output, session) {
83
  inputs_to_toggle <- c(
84
  "station_selector",
85
  "date_range",
86
- "modal_date_start",
87
- "modal_date_end",
88
  "zoom_home",
89
- "close_panel",
90
  "main_nav",
91
- "download_data",
92
  "download_hourly"
93
  )
94
 
@@ -332,74 +328,12 @@ server <- function(input, output, session) {
332
  # Sync dropdown
333
  updateSelectizeInput(session, "station_selector", selected = id_val)
334
 
335
- # Trigger Panel
336
- output$show_details_panel <- reactive(TRUE)
337
- outputOptions(output, "show_details_panel", suspendWhenHidden = FALSE)
338
-
339
  # NOTE: Date reset removed to preserve user context
340
  })
341
 
342
  # --- Date Synchronization (Sliding Window & Bi-Directional) ---
343
 
344
- # 1. Sync Panel -> Sidebar (and constraints: 28 <= days <= 366)
345
- observeEvent(input$modal_date_start, {
346
- req(input$modal_date_start, input$modal_date_end)
347
-
348
- # Calculate current diff
349
- diff <- as.numeric(difftime(input$modal_date_end, input$modal_date_start, units = "days"))
350
-
351
- # Validation Logic
352
- if (diff > 366) {
353
- freezeReactiveValue(input, "modal_date_end")
354
- new_end <- as.Date(input$modal_date_start) + 366
355
- updateDateInput(session, "modal_date_end", value = new_end)
356
- # Update tracked state to prevent sidebar from incorrectly detecting changes
357
- previous_date_range(c(input$modal_date_start, new_end))
358
- } else if (diff < 28) {
359
- freezeReactiveValue(input, "modal_date_end")
360
- new_end <- as.Date(input$modal_date_start) + 28
361
- updateDateInput(session, "modal_date_end", value = new_end)
362
- # Update tracked state
363
- previous_date_range(c(input$modal_date_start, new_end))
364
- } else {
365
- # Valid range (28-366 checks pass)
366
- # Update tracked state before syncing to sidebar
367
- previous_date_range(c(input$modal_date_start, input$modal_date_end))
368
- # Sync to Sidebar
369
- updateDateRangeInput(session, "date_range", start = input$modal_date_start, end = input$modal_date_end)
370
- }
371
- })
372
-
373
- observeEvent(input$modal_date_end, {
374
- req(input$modal_date_start, input$modal_date_end)
375
-
376
- # Calculate current diff
377
- diff <- as.numeric(difftime(input$modal_date_end, input$modal_date_start, units = "days"))
378
-
379
- # Validation Logic (Adjust Start Date)
380
- if (diff > 366) {
381
- freezeReactiveValue(input, "modal_date_start")
382
- new_start <- as.Date(input$modal_date_end) - 366
383
- updateDateInput(session, "modal_date_start", value = new_start)
384
- # Update tracked state
385
- previous_date_range(c(new_start, input$modal_date_end))
386
- } else if (diff < 28) {
387
- freezeReactiveValue(input, "modal_date_start")
388
- # If diff < 28, we need to push start back to ensure at least 28 days
389
- new_start <- as.Date(input$modal_date_end) - 28
390
- updateDateInput(session, "modal_date_start", value = new_start)
391
- # Update tracked state
392
- previous_date_range(c(new_start, input$modal_date_end))
393
- } else {
394
- # Valid range
395
- # Update tracked state before syncing to sidebar
396
- previous_date_range(c(input$modal_date_start, input$modal_date_end))
397
- # Sync to Sidebar
398
- updateDateRangeInput(session, "date_range", start = input$modal_date_start, end = input$modal_date_end)
399
- }
400
- })
401
-
402
- # 2. Sync Sidebar -> Panel (and enforce 366 days on sidebar with bi-directional detection)
403
  observeEvent(input$date_range, {
404
  req(input$date_range)
405
  d_start <- input$date_range[1]
@@ -427,7 +361,6 @@ server <- function(input, output, session) {
427
 
428
  if (end_changed && !start_changed) {
429
  # User changed end date -> adjust start date
430
- # Logic: If diff > 366, pull start to (end - 366). If diff < 28, push start to (end - 28)
431
  target_diff <- if (diff > 366) 366 else 28
432
  new_start <- d_end - target_diff
433
 
@@ -436,9 +369,6 @@ server <- function(input, output, session) {
436
 
437
  # Update tracked state
438
  previous_date_range(c(new_start, d_end))
439
- # Sync to Panel
440
- updateDateInput(session, "modal_date_start", value = new_start)
441
- updateDateInput(session, "modal_date_end", value = d_end)
442
  } else {
443
  # User changed start date (or both) -> adjust end date
444
  target_diff <- if (diff > 366) 366 else 28
@@ -449,19 +379,10 @@ server <- function(input, output, session) {
449
 
450
  # Update tracked state
451
  previous_date_range(c(d_start, new_end))
452
- # Sync to Panel
453
- updateDateInput(session, "modal_date_start", value = d_start)
454
- updateDateInput(session, "modal_date_end", value = new_end)
455
  }
456
  } else {
457
  # Update tracked state
458
  previous_date_range(c(d_start, d_end))
459
- # Distinct check to break loops
460
- # Only update panel if it's different to avoid circularity
461
- if (!isTRUE(all.equal(input$modal_date_start, d_start)) || !isTRUE(all.equal(input$modal_date_end, d_end))) {
462
- updateDateInput(session, "modal_date_start", value = d_start)
463
- updateDateInput(session, "modal_date_end", value = d_end)
464
- }
465
  }
466
  })
467
 
@@ -482,7 +403,8 @@ server <- function(input, output, session) {
482
 
483
  # Initial Trigger (Debounced Window)
484
  window_reactive <- reactive({
485
- list(id = current_station_id(), start = input$modal_date_start, end = input$modal_date_end)
 
486
  })
487
  window_debounced <- window_reactive %>% debounce(500)
488
 
@@ -865,6 +787,10 @@ server <- function(input, output, session) {
865
  loading_diagnostics(paste0("Success: ", nrow(clean_df), " rows loaded."))
866
  loading_status(FALSE)
867
  fetch_stage(0)
 
 
 
 
868
  # Show rendering message and then unfreeze after delay
869
  session$sendCustomMessage("freezeUI", list(text = "Rendering plots..."))
870
  later::later(function() {
@@ -896,38 +822,11 @@ server <- function(input, output, session) {
896
  })
897
  outputOptions(output, "has_diag", suspendWhenHidden = FALSE)
898
 
899
- # Station Meta
900
- output$panel_station_name <- renderText({
901
- req(current_station_id())
902
- s <- all_stations() %>% filter(id == current_station_id())
903
- if (nrow(s) > 0) paste0(s$name[1], " (", s$id[1], ")") else current_station_id()
904
- })
905
-
906
- output$panel_station_meta <- renderText({
907
- req(current_station_id())
908
- s <- all_stations() %>% filter(id == current_station_id())
909
- if (nrow(s) > 0) {
910
- paste0(
911
- s$state[1], " | ",
912
- "Lat: ", s$latitude[1], " | Lon: ", s$longitude[1], " | Elev: ", s$elevation[1], "m"
913
- )
914
- } else {
915
- ""
916
- }
917
- })
918
  # Plots
919
  observe({
920
  df <- station_data()
921
  req(df)
922
 
923
- # Filter by Date Range from Modal (local processing)
924
- if (!is.null(input$modal_date_start) && !is.null(input$modal_date_end)) {
925
- df <- df %>% filter(
926
- datetime >= as.POSIXct(input$modal_date_start),
927
- datetime <= as.POSIXct(input$modal_date_end) + days(1)
928
- )
929
- }
930
-
931
  output$temp_plot <- renderPlotly({
932
  create_weather_trends_plot(df)
933
  })
@@ -986,32 +885,53 @@ server <- function(input, output, session) {
986
 
987
  s_name <- meta$name[1]
988
  s_state <- meta$state[1]
 
989
 
990
  # Data Range
991
  df <- station_data()
992
  if (is.null(df) || nrow(df) == 0) {
993
- return(tagList(
994
- h4(s_name, style = "margin-bottom: 5px;"),
995
- p(tags$small(paste("Station ID:", id)), style = "color: #666;")
996
- ))
997
  }
998
 
999
- date_range <- range(as.Date(df$datetime), na.rm = TRUE)
1000
-
1001
- tagList(
1002
- h4(paste0(s_name, ", ", s_state), style = "margin-bottom: 5px;"),
1003
- p(
1004
- tags$small(paste(
1005
- "Station ID:", id, " | ",
1006
- "Data selection:", date_range[1], "to", date_range[2]
1007
- )),
1008
- style = "color: #666; margin-bottom: 5px;"
 
 
 
 
 
 
 
 
 
 
 
 
1009
  ),
1010
- p(
1011
- tags$small("Data source: DWD OpenData (Germany)"),
1012
- tags$br(),
1013
- tags$small("Includes: Air Temperature, Precip, Wind, Solar* all parameters processed"),
1014
- style = "color: #666; font-style: italic;"
 
 
 
 
 
 
 
 
1015
  )
1016
  )
1017
  })
@@ -1045,18 +965,9 @@ server <- function(input, output, session) {
1045
  radius = 8, color = "red", fill = FALSE, opacity = 1, weight = 3
1046
  )
1047
 
1048
- # Show Panel
1049
- output$show_details_panel <- reactive(TRUE)
1050
- outputOptions(output, "show_details_panel", suspendWhenHidden = FALSE)
1051
-
1052
  # NOTE: Date reset removed to preserve user context (same as map marker click)
1053
  })
1054
 
1055
- # Close Panel
1056
- observeEvent(input$close_panel, {
1057
- output$show_details_panel <- reactive(FALSE)
1058
- })
1059
-
1060
  # Dynamic Details Tabs (Hide Wind Rose for Daily)
1061
  output$details_tabs <- renderUI({
1062
  req(input$data_resolution)
@@ -1102,16 +1013,6 @@ server <- function(input, output, session) {
1102
  ))
1103
  }
1104
 
1105
- do.call(bslib::navset_card_pill, tabs)
1106
  })
1107
-
1108
- # Downloads
1109
- output$download_data <- downloadHandler(
1110
- filename = function() {
1111
- paste0("dwd_station_", current_station_id(), ".csv")
1112
- },
1113
- content = function(file) {
1114
- write_csv(station_data(), file)
1115
- }
1116
- )
1117
  }
 
83
  inputs_to_toggle <- c(
84
  "station_selector",
85
  "date_range",
 
 
86
  "zoom_home",
 
87
  "main_nav",
 
88
  "download_hourly"
89
  )
90
 
 
328
  # Sync dropdown
329
  updateSelectizeInput(session, "station_selector", selected = id_val)
330
 
 
 
 
 
331
  # NOTE: Date reset removed to preserve user context
332
  })
333
 
334
  # --- Date Synchronization (Sliding Window & Bi-Directional) ---
335
 
336
+ # 2. Sidebar constraints: 28 <= days <= 366
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  observeEvent(input$date_range, {
338
  req(input$date_range)
339
  d_start <- input$date_range[1]
 
361
 
362
  if (end_changed && !start_changed) {
363
  # User changed end date -> adjust start date
 
364
  target_diff <- if (diff > 366) 366 else 28
365
  new_start <- d_end - target_diff
366
 
 
369
 
370
  # Update tracked state
371
  previous_date_range(c(new_start, d_end))
 
 
 
372
  } else {
373
  # User changed start date (or both) -> adjust end date
374
  target_diff <- if (diff > 366) 366 else 28
 
379
 
380
  # Update tracked state
381
  previous_date_range(c(d_start, new_end))
 
 
 
382
  }
383
  } else {
384
  # Update tracked state
385
  previous_date_range(c(d_start, d_end))
 
 
 
 
 
 
386
  }
387
  })
388
 
 
403
 
404
  # Initial Trigger (Debounced Window)
405
  window_reactive <- reactive({
406
+ req(input$date_range)
407
+ list(id = current_station_id(), start = input$date_range[1], end = input$date_range[2])
408
  })
409
  window_debounced <- window_reactive %>% debounce(500)
410
 
 
787
  loading_diagnostics(paste0("Success: ", nrow(clean_df), " rows loaded."))
788
  loading_status(FALSE)
789
  fetch_stage(0)
790
+
791
+ # Navigate to Dashboard tab
792
+ updateNavbarPage(session, "main_nav", selected = "Dashboard")
793
+
794
  # Show rendering message and then unfreeze after delay
795
  session$sendCustomMessage("freezeUI", list(text = "Rendering plots..."))
796
  later::later(function() {
 
822
  })
823
  outputOptions(output, "has_diag", suspendWhenHidden = FALSE)
824
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
825
  # Plots
826
  observe({
827
  df <- station_data()
828
  req(df)
829
 
 
 
 
 
 
 
 
 
830
  output$temp_plot <- renderPlotly({
831
  create_weather_trends_plot(df)
832
  })
 
885
 
886
  s_name <- meta$name[1]
887
  s_state <- meta$state[1]
888
+ s_elev <- meta$elevation[1]
889
 
890
  # Data Range
891
  df <- station_data()
892
  if (is.null(df) || nrow(df) == 0) {
893
+ dates_text <- "No data loaded"
894
+ } else {
895
+ date_range <- range(as.Date(df$datetime), na.rm = TRUE)
896
+ dates_text <- paste(date_range[1], "to", date_range[2])
897
  }
898
 
899
+ layout_columns(
900
+ fill = FALSE,
901
+ value_box(
902
+ title = "Station",
903
+ value = s_name,
904
+ showcase = bsicons::bs_icon("geo-alt-fill"),
905
+ p(paste("ID:", id)),
906
+ theme = "primary"
907
+ ),
908
+ value_box(
909
+ title = "State / Location",
910
+ value = s_state,
911
+ showcase = bsicons::bs_icon("map-fill"),
912
+ p(paste0("Lat: ", meta$latitude[1], " | Lon: ", meta$longitude[1])),
913
+ theme = "secondary"
914
+ ),
915
+ value_box(
916
+ title = "Elevation",
917
+ value = paste0(s_elev, " m"),
918
+ showcase = bsicons::bs_icon("align-bottom"),
919
+ p("Above mean sea level"),
920
+ theme = "info"
921
  ),
922
+ value_box(
923
+ title = "Data Period",
924
+ value = dates_text,
925
+ showcase = bsicons::bs_icon("calendar3"),
926
+ p(paste("Resolution:", input$data_resolution)),
927
+ theme = "success"
928
+ ),
929
+ value_box(
930
+ title = "Actions",
931
+ value = "Download",
932
+ showcase = bsicons::bs_icon("download"),
933
+ downloadButton("download_hourly", "Export CSV", class = "btn-sm btn-light", style = "width: 100%;"),
934
+ theme = "warning"
935
  )
936
  )
937
  })
 
965
  radius = 8, color = "red", fill = FALSE, opacity = 1, weight = 3
966
  )
967
 
 
 
 
 
968
  # NOTE: Date reset removed to preserve user context (same as map marker click)
969
  })
970
 
 
 
 
 
 
971
  # Dynamic Details Tabs (Hide Wind Rose for Daily)
972
  output$details_tabs <- renderUI({
973
  req(input$data_resolution)
 
1013
  ))
1014
  }
1015
 
1016
+ do.call(bslib::navset_pill, tabs)
1017
  })
 
 
 
 
 
 
 
 
 
 
1018
  }
ui.R CHANGED
@@ -228,41 +228,6 @@ ui <- page_navbar(
228
  id = "zoom_home_panel",
229
  top = 80, left = 10,
230
  actionButton("zoom_home", bsicons::bs_icon("house-fill"), class = "btn-home", title = "Zoom to all stations")
231
- ),
232
-
233
- # Floating Panel
234
- conditionalPanel(
235
- condition = "output.show_details_panel",
236
- absolutePanel(
237
- id = "station_detail_panel",
238
- class = "panel panel-default",
239
- top = 20, right = 20, width = 550,
240
- draggable = TRUE,
241
-
242
- # Header
243
- div(
244
- style = "padding-bottom: 10px; border-bottom: 1px solid #eee; margin-bottom: 10px; padding-right: 30px;",
245
- h4(textOutput("panel_station_name"), style = "margin: 0; font-size: 1.1rem; font-weight: bold;"),
246
- div(style = "font-size: 0.85rem; color: #666;", textOutput("panel_station_meta")),
247
- actionButton("close_panel", icon("times"), class = "close-btn-custom", label = NULL)
248
- ),
249
-
250
- # Controls
251
- div(
252
- style = "margin-bottom: 10px;",
253
- fluidRow(
254
- column(6, dateInput("modal_date_start", "Start Date", value = NULL)),
255
- column(6, dateInput("modal_date_end", "End Date", value = NULL))
256
- ),
257
- div(
258
- style = "text-align: right;",
259
- conditionalPanel(condition = "output.station_ready", downloadButton("download_data", "Export CSV", class = "btn-sm btn-outline-success"))
260
- )
261
- ),
262
-
263
- # Content Tabs
264
- uiOutput("details_tabs")
265
- )
266
  )
267
  )
268
  ),
@@ -271,20 +236,32 @@ ui <- page_navbar(
271
  DT::dataTableOutput("table")
272
  ),
273
  nav_panel(
274
- title = "Data",
 
275
  conditionalPanel(
276
  condition = "!output.station_ready",
277
  div(
278
  style = "height: 600px; display: flex; align-items: center; justify-content: center;",
279
- p("Select a station from the map or the Stations Info table to view the data.", style = "color: #999;")
280
  )
281
  ),
282
  conditionalPanel(
283
  condition = "output.station_ready",
284
- htmlOutput("station_info_header"),
285
- div(style = "margin-bottom: 10px;"),
286
- downloadButton("download_hourly", "Export Hourly CSV", class = "btn-outline-success", style = "margin-bottom: 10px;"),
287
- DT::dataTableOutput("hourly_data_table")
 
 
 
 
 
 
 
 
 
 
 
288
  )
289
  ),
290
  nav_spacer(),
 
228
  id = "zoom_home_panel",
229
  top = 80, left = 10,
230
  actionButton("zoom_home", bsicons::bs_icon("house-fill"), class = "btn-home", title = "Zoom to all stations")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  )
232
  )
233
  ),
 
236
  DT::dataTableOutput("table")
237
  ),
238
  nav_panel(
239
+ title = "Dashboard",
240
+ value = "Dashboard",
241
  conditionalPanel(
242
  condition = "!output.station_ready",
243
  div(
244
  style = "height: 600px; display: flex; align-items: center; justify-content: center;",
245
+ p("Select a station from the map or the Stations Info table to view the dashboard.", style = "color: #999;")
246
  )
247
  ),
248
  conditionalPanel(
249
  condition = "output.station_ready",
250
+ uiOutput("station_info_header"),
251
+ navset_card_pill(
252
+ id = "dashboard_subtabs",
253
+ nav_panel(
254
+ title = "Plots",
255
+ uiOutput("details_tabs")
256
+ ),
257
+ nav_panel(
258
+ title = "Data",
259
+ div(
260
+ style = "margin-top: 10px;",
261
+ DT::dataTableOutput("hourly_data_table")
262
+ )
263
+ )
264
+ )
265
  )
266
  ),
267
  nav_spacer(),