From 9f3e156ff95c2166c04b89dc24275ce77ca0fd49 Mon Sep 17 00:00:00 2001 From: hfri Date: Tue, 24 Jun 2025 21:34:42 +0200 Subject: [PATCH 01/17] fixed x-axis limit bug --- server.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server.R b/server.R index 0cfe296..3349c0c 100644 --- a/server.R +++ b/server.R @@ -982,7 +982,8 @@ server <- function(input, output, session) { # Calculate max count for dynamic axis range max_count <- max(heatmap_data_df$Counts, na.rm = TRUE) - xaxis_range <- c(0, max_count + max_count * 0.1) # 10% padding + max_count_bar <- max( bar_data_df$Counts, na.rm = TRUE) + xaxis_range <- c(0, max_count_bar + max_count_bar * 0.1) # 10% padding # Create the bar chart bar_chart <- plot_ly( From 9acf832f9cff95daccfd00da0bb5837081c080bb Mon Sep 17 00:00:00 2001 From: hfri Date: Sun, 20 Jul 2025 11:20:29 +0200 Subject: [PATCH 02/17] Top X filter for seasons bug fixed Top X filter limited to 20 Login- displayed at right upper corner + logout link Tree starts at Fauna level --- server.R | 163 +++++++++++++++++++++++++++++++++---------------------- ui.R | 49 +++++++++-------- 2 files changed, 125 insertions(+), 87 deletions(-) diff --git a/server.R b/server.R index 3349c0c..b9d64df 100644 --- a/server.R +++ b/server.R @@ -287,15 +287,30 @@ server <- function(input, output, session) { # ------- OUTPUT SIDE - SIDE PANEL SHOWING GROUPS, DATA DIVIDED IN TREE --------- output$userstatus <- renderUI({ - # session$user is non-NULL only in authenticated sessions req(input$ok) isolate({ Username <- input$username }) - if (session$userData$authenticated) { - strong(paste(i18n$t("labels.connectedAs"), Username)) + if (isTRUE(session$userData$authenticated)) { + tags$div( + style = "color: white; display: flex; align-items: center; justify-content: flex-end; gap: 10px; min-width: 150px;", + tags$strong(paste(i18n$t("labels.connectedAs"), Username)), + tags$a( + href = "#", + id = "logout_link", + onclick = "event.preventDefault(); Shiny.setInputValue('logout_link', Math.random());", + style = "color: white; text-decoration: underline; cursor: pointer;", + i18n$t("Logout") + ) + ) + } else { + NULL } }) + observeEvent(input$logout_link, { + # Your logout code here, e.g.: + session$reload() + }) # observe group select box observeEvent(input$GroupList, { # input is defined in ui.R @@ -467,7 +482,7 @@ server <- function(input, output, session) { if (rm11_node %in% top_nodes) {invisible(session$userData$tree$RemoveChild(rm11_node))} if (rm12_node %in% top_nodes) {invisible(session$userData$tree$RemoveChild(rm12_node))} - + # Access the top-level nodes top_children <- session$userData$tree$children @@ -493,9 +508,18 @@ server <- function(input, output, session) { } } - - if (length(session$userData$tree$children) == 0) {session$userData$tree <- list()} + + if (length(session$userData$tree$children) > 0) { + # Keep fauna as first-levgel node as the root + session$userData$tree <- session$userData$tree$children[[1]] + + # Optionally, set a friendly name for clarity + session$userData$tree$name <- paste0("Root: ", session$userData$tree$name) + } + jsonTree <- shinyTree::treeToJSON(session$userData$tree, pretty = TRUE, createNewId = FALSE) + + enable_all(c("DateRange", "GroupListDiv", "GetData")) return(jsonTree) }, error = function(e) { @@ -506,6 +530,7 @@ server <- function(input, output, session) { }) }) # end tree + output$conceptTree <- renderTree({ # only render after we have selected a group and retrieved the counts from that group req(session$userData$counts, session$userData$concepts) @@ -867,10 +892,20 @@ server <- function(input, output, session) { session$userData$plot_data() %...>% { df <- . - # Summarise data based on conceptlabel - df <- df %>% - group_by(conceptLabel) %>% - summarise(Counts = n(), .groups = 'drop') + time_input <- input$time_input + seasons <- user_defined_seasons() + + # Seasonal grouping if "season" view is selected + if (time_input == "season") { + df <- df %>% + filter(Period %in% names(seasons)) %>% + group_by(conceptLabel) %>% + summarise(Counts = n(), .groups = 'drop') + } else { + df <- df %>% + group_by(conceptLabel) %>% + summarise(Counts = n(), .groups = 'drop') + } message(paste( "Debug - bar_data(): Bar data transformation complete with", @@ -878,12 +913,13 @@ server <- function(input, output, session) { "rows." )) + # Apply top X row filter topX <- input$topX if (topX > 0) { df <- df %>% top_n(topX, Counts) %>% - arrange(desc(Counts)) + arrange(desc(Counts)) } # Order species according to frequency of detection for the bar chart @@ -964,7 +1000,6 @@ server <- function(input, output, session) { }) - ## --- MAIN OUTPUT ------ # Create a reactiveValues container to hold the data frames @@ -1071,7 +1106,7 @@ server <- function(input, output, session) { # Download handler for the Plotly HTML plot output$download_plotly <- downloadHandler( filename = function() { - paste("combined_plot", ".html", sep = "") + paste("activity_pattern_",session$userData$selectedGroupValue, "_", session$userData$date_from, "_", session$userData$date_to, ".html", sep = "") }, content = function(file) { p <- plot_cache() @@ -1089,8 +1124,8 @@ server <- function(input, output, session) { }, content = function(file) { tmpdir <- tempdir() - bar_file <- file.path(tmpdir, "bar_data.csv") - heatmap_file <- file.path(tmpdir, "heatmap_data.csv") + bar_file <- file.path(tmpdir, paste0("bar_data_",session$userData$selectedGroupValue, "_", session$userData$date_from, "_", session$userData$date_to,".csv")) + heatmap_file <- file.path(tmpdir, paste0("heatmap_data_",session$userData$selectedGroupValue, "_", session$userData$date_from, "_", session$userData$date_to,".csv")) # Check that plot_data has been updated if (is.null(plot_data$bar_data) || is.null(plot_data$heatmap_data)) { @@ -1134,55 +1169,55 @@ server <- function(input, output, session) { th(i18n$t("columns.longitude"), title = i18n$t("labels.WGS84Lon")) )))) - # Raw concept table - output$tableRawConcepts <- DT::renderDataTable({ - message("Rendering raw concept table") - # req(session$userData$processed_obsdata) - - session$userData$processed_obsdata() %...>% { - df <- . - - # convert POSIXct to character that includes timezone, for datatable - df$when <- format(df$when, format = "%Y-%m-%dT%H:%M:%S%z") # %z shows as +0100 etc. - - df <- df %>% - select(conceptLabel, - when, - agentName, - observationId, - observationType, - lat, - lon) - # colnr <- which(names(df) == "when") # need for formatDate/toLocaleString - - DT::datatable( - df, - container = RawConceptsHovertext, - extensions = "Buttons", - options = list( - language = list( - url = paste0( - '//cdn.datatables.net/plug-ins/1.10.11/i18n/', - session$userData$lang_long, - '.json' - ) - ), - paging = TRUE, - searching = TRUE, - fixedColumns = TRUE, - autoWidth = TRUE, - ordering = TRUE, - dom = 'frtip<"sep">B', - #'ip>', #"ftripB", #'<"sep">frtipB', # dom = - pageLength = 999, - buttons = list( - list(extend = "copy", text = i18n$t("commands.copy")), - list(extend = "csv", text = i18n$t("commands.csv")) - ) - ) - ) # %>% DT::formatDate(colnr, "toLocaleString") does not include timezone in locale string - } - }) # end output tableRawConcepts + #' # Raw concept table + #' output$tableRawConcepts <- DT::renderDataTable({ + #' message("Rendering raw concept table") + #' # req(session$userData$processed_obsdata) + #' + #' session$userData$processed_obsdata() %...>% { + #' df <- . + #' + #' # convert POSIXct to character that includes timezone, for datatable + #' df$when <- format(df$when, format = "%Y-%m-%dT%H:%M:%S%z") # %z shows as +0100 etc. + #' + #' df <- df %>% + #' select(conceptLabel, + #' when, + #' agentName, + #' observationId, + #' observationType, + #' lat, + #' lon) + #' # colnr <- which(names(df) == "when") # need for formatDate/toLocaleString + #' + #' DT::datatable( + #' df, + #' container = RawConceptsHovertext, + #' extensions = "Buttons", + #' options = list( + #' language = list( + #' url = paste0( + #' '//cdn.datatables.net/plug-ins/1.10.11/i18n/', + #' session$userData$lang_long, + #' '.json' + #' ) + #' ), + #' paging = TRUE, + #' searching = TRUE, + #' fixedColumns = TRUE, + #' autoWidth = TRUE, + #' ordering = TRUE, + #' dom = 'frtip<"sep">B', + #' #'ip>', #"ftripB", #'<"sep">frtipB', # dom = + #' pageLength = 999, + #' buttons = list( + #' list(extend = "copy", text = i18n$t("commands.copy")), + #' list(extend = "csv", text = i18n$t("commands.csv")) + #' ) + #' ) + #' ) # %>% DT::formatDate(colnr, "toLocaleString") does not include timezone in locale string + #' } + #' }) # end output tableRawConcepts ### TAB RAW OBSERVATIONS diff --git a/ui.R b/ui.R index 2717578..efcfdd4 100644 --- a/ui.R +++ b/ui.R @@ -14,7 +14,7 @@ library(plotly) # multi language -#source("ui_header.R") + tryCatch({ # try to get online version @@ -69,7 +69,11 @@ ui <- fluidPage( "ACTIVITY PATTERN", style = "font-size: 18px;" ), - br(),br(),br(),br(),br(),br(),br() + # Right side: user status + div( + style = "min-width: 150px; text-align: right;", + uiOutput("userstatus") + ) ) ), sidebarLayout( @@ -158,6 +162,7 @@ ui <- fluidPage( tags$style( "#login{background-color:#FB8C00; color:white; font-size:100%}" ), + tags$style( "#message_more_dates{color: red; font-size: 20px; font-style: italic}" ), @@ -166,9 +171,6 @@ ui <- fluidPage( ), ), br(), - # Remove old heading h3(i18n$t("labels.obsReport")) - uiOutput("userstatus"), - br(), # --- Filter Sections --- div(class = "filter-section time-period-box", @@ -301,13 +303,14 @@ ui <- fluidPage( numericInput( inputId = "topX", label = "Top rows:", - value = 10, + value = 5, min = 1, + max = 20, step = 1, width = "100%" ) ), - + # Aligned Radio Buttons (inline, vertically centered) div( style = "display: flex; align-items: flex-end; height: 58px;", # Adjust height to match input height @@ -337,28 +340,28 @@ ui <- fluidPage( 12, div( style = "margin-top: 20px;", - downloadButton("download_plotly", "Download plot (.html)"), + downloadButton("download_plotly", "Download activity pattern plot (.html)"), downloadButton("download_csv", "Download Data (.csv)") ) ) ) ) ), - tabPanel( - i18n$t("labels.rawConceptsTab"), - fluidRow(column( - 12, DT::dataTableOutput("tableRawConcepts") - )), - div( - style = "position: fixed; top: 45%; left: 60%; transform: translate(-50%, -50%);", - add_busy_spinner( - spin = "fading-circle", - width = "100px", - height = "100px" - ) - ) - ), - + # tabPanel( + # i18n$t("labels.rawConceptsTab"), + # fluidRow(column( + # 12, DT::dataTableOutput("tableRawConcepts") + # )), + # div( + # style = "position: fixed; top: 45%; left: 60%; transform: translate(-50%, -50%);", + # add_busy_spinner( + # spin = "fading-circle", + # width = "100px", + # height = "100px" + # ) + # ) + # ), + # # endpanel tabPanel( From a7edf820dcbdcd0b1ae5226ccae8cd075ee7530e Mon Sep 17 00:00:00 2001 From: hfri Date: Sun, 20 Jul 2025 11:27:07 +0200 Subject: [PATCH 03/17] Add new login module --- server.R | 92 ++++++++++++--------------------- ui_login.R | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+), 60 deletions(-) create mode 100644 ui_login.R diff --git a/server.R b/server.R index b9d64df..c5f380a 100644 --- a/server.R +++ b/server.R @@ -36,7 +36,7 @@ plan(multisession) # source function lib source("functions.R") - +source("ui_login.R") ## Some language package stuff ### CB: added these lines for treeToJSON @@ -157,51 +157,16 @@ server <- function(input, output, session) { # -- MAKE POP UP MODAL FOR ENTERING USER CREDENTIALS AND DATA - # Return the UI for a modal dialog with data selection input. If 'failed' # is TRUE, then display a message that the previous value was invalid. - dataModal <- function(failed = FALSE) { - modalDialog( - tags$script(HTML(js)), - title = i18n$t("labels.clueyCredentials"), #"Cluey credentials", - # the selectbox for a server will only show in apps for testing - if (grepl("test", session$clientData$url_pathname)) { - message(paste("Adding selectbox for server because we are running on", session$clientData$url_pathname)) - selectInput("server", label = "Server", - choices = c("focus.sensingclues", "focus.test.sensingclues"), - selected = "focus.sensingclues") - }, - selectInput("lang", label = i18n$t("labels.chooseLanguage"), - choices = language_table$lang_short, selected = session$userData$sel_lang), #lang_short), - textInput("username", i18n$t("labels.Cluey-username")), - passwordInput("password", i18n$t("labels.Cluey-password")), - size = "s", - if (failed) - div(tags$b(i18n$t("labels.invalid-credential"), style = "color: red;")), - footer = tagList( - actionButton("ok", "OK") - ) - ) - } - - resetModal <- function() { - modalDialog( - title = i18n$t("commands.logout"), - size = "s", - footer = tagList( - modalButton(i18n$t("commands.cancel")), - actionButton("reset", "OK") - ) - ) - } - resetModal <- function() { + dataModal <- function() { modalDialog( - title = i18n$t("commands.logout"), - size = "s", - footer = tagList( - modalButton(i18n$t("commands.cancel")), - actionButton("reset", "OK") - ) + mod_login_ui("login", browser_path = session$clientData$url_pathname), + title = div(style = "text-align: center; width: 100%;", i18n$t("labels.clueyCredentials")), + size = "s", + footer = NULL, + easyClose = FALSE, + fade = TRUE ) } @@ -211,15 +176,15 @@ server <- function(input, output, session) { showModal(dataModal()) }) - # When OK button is pressed, attempt to authenticate. If successful, # remove the modal. obs2 <- observe({ req(input$ok) isolate({ - Username <- input$username - Password <- input$password + Username <- input$username + Password <- input$password + session$userData$clueyUser <- Username }) @@ -232,44 +197,51 @@ server <- function(input, output, session) { session$userData$url <- "https://focus.test.sensingclues.org/" } } - message(paste("LOGGING INTO", session$userData$url)) + message(paste0("LOGGING INTO ", session$userData$url)) - session$userData$cookie_mt <- sensingcluesr::login_cluey(username = Username, password = Password, url = session$userData$url) + session$userData$cookie_mt <- sensingcluesr::login_cluey(username = Username, + password = Password, + url = session$userData$url) if (!is.null(session$userData$cookie_mt)) { session$userData$authenticated <- TRUE obs1$suspend() removeModal() # after successful login - session$userData$hierarchy <- sensingcluesr::get_hierarchy(url = session$userData$url, lang = session$userData$lang_short) + session$userData$hierarchy <- sensingcluesr::get_hierarchy(url = session$userData$url, + lang = session$userData$lang_short) session$userData$concepts <- session$userData$hierarchy$concepts - + # get groups needs to be done only once + # debug + # message(paste0("Get initial groups for ",session$userData$clueyUser,' from ',from,' to ',to)) + # session$userData$groups <- sensingcluesr::get_groups(from = from, + # to = to, + # cookie = session$userData$cookie_mt, + # url = session$userData$url) # put start en end date in dateRangeInput updateDateRangeInput(session, "DateRange", start = isolate(session$userData$date_from), - end = isolate(session$userData$date_to), - max = Sys.Date()) + end = isolate(session$userData$date_to)) # which layers are available to the user - layers <- sensingcluesr::get_layer_details(cookie = session$userData$cookie_mt, url = session$userData$url) - # filter layers of the (Multi)Polygon type for the per area tab - session$userData$layers <- layers %>% filter(geometryType %in% c("Polygon", "MultiPolygon")) + session$userData$layers <- sensingcluesr::get_layer_details(cookie = session$userData$cookie_mt, url = session$userData$url) # message(paste0("LAYERS ", paste(session$userData$layers, sep = "|"))) + updateSelectInput(session, "MapLayers", choices = c(i18n$t("labels.noneSelected"), sort(unlist(session$userData$layers$layerName)))) + session$userData$selectedLayer <- i18n$t("labels.noneSelected") # enable input fields/buttons enable("DateRange") enable("GroupListDiv") - enable("GetData") + enable("BuildMap") + enable("MapLayers") } else { session$userData$authenticated <- FALSE # inform user - showModal(dataModal(failed = TRUE)) - } + showNotification(i18n$t("labels.invalid-credential"), type = "error") + } } ) - # -- End modal stuff -- - # Evt. taal wijzigen obs3 <- observe({ diff --git a/ui_login.R b/ui_login.R new file mode 100644 index 0000000..e986c8c --- /dev/null +++ b/ui_login.R @@ -0,0 +1,147 @@ +# ui/mod_login_ui.R + +mod_login_ui <- function(id, browser_path) { + # ns <- NS(id) + + # Main container with set width + div(style = "margin-left: 10px; margin-right: 10px;", + + # Server selector, only shows up for test version + if (grepl("test", browser_path)) { + shinyWidgets::pickerInput( + inputId = "server", + label = "Server", + choices = c("focus.sensingclues", "focus.test.sensingclues"), + selected = "focus.sensingclues", + multiple = FALSE, + width = "100%" + ) + }, + + # LANGUAGE SELECTOR + shinyWidgets::pickerInput( + inputId = "lang", + label = "Select your language", + choices = c("Dutch" = "nl", "English"= "en", "French" = "fr"), + # c("Dutch", "English", "French", "German", "Hindi", "Romanian", "Spanish", "Swahili", "Ukrainian"), + selected = "en", + multiple = FALSE, + options = shinyWidgets::pickerOptions( + # noneSelectedText = "Select your language...", + liveSearch = TRUE, + liveSearchPlaceholder = "Type to search..." + ), + width = "100%" + ), + br(), + + # Username input (full width) + textInput( + inputId = "username", + label = "Enter your Cluey username", + placeholder = "Username", + width = "100%" + ), + + # Password input with toggle icon + tags$div(style = "position: relative; margin-top: 10px;", + # Label + tags$label(`for` = "password", "Password"), + # Eigen input ipv shiny::passwordInput om custom styling mogelijk te maken + tags$input( + id = "password", + type = "password", + class = "form-control", + placeholder = "Password", + style = "width: 100%; padding-right: 30px;" + ), + # Oogje voor toggle + tags$span( + class = "toggle-password", + tags$i(class = "fa fa-eye fa-lg"), + style = "position: absolute; top: 34px; right: 10px; cursor: pointer;" + ) + ), + + # Remember me checkbox onder de login knop + tags$div( + style = "margin-top: 15px; font-size: 0.9em;", + checkboxInput( + inputId = "remember_me", + label = "Remember me", + value = FALSE, + width = NULL + ) + ), + + # Login button (full width) + actionButton( + inputId = "ok", + label = "Log in", + class = "btn btn-primary", + style = "width: 100%; color: white; background-color: #004d40;" + ), + + # "Forgot password" link + tags$a( + href = "https://sensingclues.freshdesk.com/support/solutions/articles/48001167031-forgot-password", + "Forgot password", + target = "_blank", + style = "display: block; text-align: right; margin-top: 8px; + color: #004d40; text-decoration: none; font-weight: 300;" + ), + + # "Create account" link + tags$div( + style = "margin-top: 20px; border-top: 1px solid #ccc; padding-top: 10px; text-align: center; font-size: 0.9em; color: #666;", + "No account yet? ", + tags$a( + href = "https://sensingclues.freshdesk.com/support/solutions/articles/48000944302-create-an-account", + "Create one.", + target = "_blank", + style = "color: #004d40; text-decoration: none; font-weight: 500;" + ) + ), + + # Script to toggle password visibility + tags$script(HTML( + "$(document).on('click', '.toggle-password', function() {\n", + " var input = $(this).siblings('input');\n", + " var type = input.attr('type') === 'password' ? 'text' : 'password';\n", + " input.attr('type', type);\n", + " $(this).find('i').toggleClass('fa-eye fa-eye-slash');\n", + "});" + )), + + # Script to close modal on clicking enter + tags$script(HTML( + ' $(document).keyup(function(event) { + if ($("#password").is(":focus") && (event.keyCode == 13)) { + $("#ok").click(); + } + });' + )), + + tags$script(HTML(glue::glue(" + $(document).ready(function() {{ + // Bij laden van de pagina: check localStorage + if (localStorage.getItem('remember_me') === 'true') {{ + $('#{'remember_me'}').prop('checked', true); + $('#{'username'}').val(localStorage.getItem('username')); + }} + + // Bij klikken op login: onthoud gegevens als aangevinkt + $('#{'ok'}').on('click', function() {{ + if ($('#{'remember_me'}').is(':checked')) {{ + localStorage.setItem('remember_me', 'true'); + localStorage.setItem('username', $('#{'username'}').val()); + }} else {{ + localStorage.removeItem('remember_me'); + localStorage.removeItem('username'); + }} + }}); + }}); + ")) + ) + ) +} \ No newline at end of file From cafc8abe5862f250607f780d7693cdad9574d134 Mon Sep 17 00:00:00 2001 From: Jan Kees Date: Tue, 22 Jul 2025 12:28:58 +0200 Subject: [PATCH 04/17] removed season from the UI --- ui.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui.R b/ui.R index efcfdd4..904fd83 100644 --- a/ui.R +++ b/ui.R @@ -60,7 +60,7 @@ ui <- fluidPage( tags$a( href = "https://sensingclues.org", target = "_blank", - class = "logo", img(src = "logo_white.png")), + class = "logo", img(src = "logo_white.png")) ), # titel @@ -125,7 +125,7 @@ ui <- fluidPage( class = "collapsible-header", HTML('Aboutexpand_more') ), - p("This app lets you explore animal observation data for any period. Use the heatmap to reveal activity trends by hour, month, or season, view total counts per species, and download the underlying dataset."), + p("This app lets you explore animal observation data for any period. Use the heatmap to reveal activity trends by hour or month, view total counts per species or download the underlying dataset."), tags$a( "Learn more", href = "https://www.sensingclues.org/about-activity-pattern", # Change to your real link @@ -251,8 +251,8 @@ ui <- fluidPage( label = i18n$t("Time interval"), choices = list( "Hourly" = "hourly", - "Monthly" = "monthly", - "Seasonal" = "season" + "Monthly" = "monthly" + # "Seasonal" = "season" ), selected = "hourly", width = "100%" From 35213117340e9cdb5249566820cf880ec099742b Mon Sep 17 00:00:00 2001 From: Jan Kees Date: Tue, 22 Jul 2025 12:29:50 +0200 Subject: [PATCH 05/17] 1 extra line for running and testing the app locally --- server.R | 1 + 1 file changed, 1 insertion(+) diff --git a/server.R b/server.R index c5f380a..a57e42b 100644 --- a/server.R +++ b/server.R @@ -25,6 +25,7 @@ library(promises) # load the sensincluesr package library(devtools) devtools::install_github("sensingclues/sensingcluesr@v1.0.3", upgrade = "never") +#library(sensingcluesr) # dynamic color maps for more then 12 colors library(colorRamps) From b5f9a243b2ef92277d44ad6d217b5daf076208c6 Mon Sep 17 00:00:00 2001 From: Jan Kees Date: Tue, 22 Jul 2025 12:40:23 +0200 Subject: [PATCH 06/17] updated About text --- ui.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui.R b/ui.R index 904fd83..774df3f 100644 --- a/ui.R +++ b/ui.R @@ -125,10 +125,10 @@ ui <- fluidPage( class = "collapsible-header", HTML('Aboutexpand_more') ), - p("This app lets you explore animal observation data for any period. Use the heatmap to reveal activity trends by hour or month, view total counts per species or download the underlying dataset."), + p("With this app you can explore pattern in animal observation data. Use the matrix to reveal activity trends by hour or month, view total counts per species or download the underlying datasets."), tags$a( "Learn more", - href = "https://www.sensingclues.org/about-activity-pattern", # Change to your real link + href = "https://www.sensingclues.org/about-activity-pattern", class = "readmore", target = "_blank" ) From 2596bec68d0c880e90a0d53bbc57440f51b2ef5d Mon Sep 17 00:00:00 2001 From: Jan Kees Date: Wed, 23 Jul 2025 11:12:30 +0200 Subject: [PATCH 07/17] adjusted header style to new house style --- app.R | 2 + global.R | 2 + server.R | 9 +- ui.R | 495 +++++++++++++++----------------------------------- www/style.css | 129 ++++++++----- 5 files changed, 241 insertions(+), 396 deletions(-) create mode 100644 global.R diff --git a/app.R b/app.R index 77795e9..802252b 100644 --- a/app.R +++ b/app.R @@ -1,6 +1,8 @@ +# app.R # load the ui and server and call them to start the Shiny application +source("global.R") source("ui.R") source("server.R") diff --git a/global.R b/global.R new file mode 100644 index 0000000..e890c1d --- /dev/null +++ b/global.R @@ -0,0 +1,2 @@ +# global.R + diff --git a/server.R b/server.R index a57e42b..a8afe05 100644 --- a/server.R +++ b/server.R @@ -1,3 +1,5 @@ +# server.R + ## Translating heatmap plot into RShiny App SC - server script ## Author: H. Fricke ## Date: 22-03-2025 @@ -5,11 +7,8 @@ ## To-Do: ## [] method selection does not work - options(shiny.reactlog = TRUE) - - ## SET UP libraries and sourced files # Define server logic (other libraries in ui) library(DT) @@ -24,8 +23,8 @@ library(promises) # load the sensincluesr package library(devtools) -devtools::install_github("sensingclues/sensingcluesr@v1.0.3", upgrade = "never") -#library(sensingcluesr) +#devtools::install_github("sensingclues/sensingcluesr@v1.0.3", upgrade = "never") +library(sensingcluesr) # dynamic color maps for more then 12 colors library(colorRamps) diff --git a/ui.R b/ui.R index 774df3f..395c41f 100644 --- a/ui.R +++ b/ui.R @@ -1,7 +1,4 @@ -# Author: Hanna Fricke -# Description: User interface for activity app. -# TO-DO : -# [] DONT HARDCODE METHOD FUTURE BUT BASE IT ON DATA +# ui.R library(shiny) library(shiny.i18n) # for multilanguage @@ -13,376 +10,182 @@ library(shinyWidgets) library(plotly) # multi language - - - tryCatch({ - # try to get online version - # i18n <- Translator$new(translation_json_path = "https://focus.sensingclues.org/api/labels/list") # Production Environment - i18n <- Translator$new(translation_json_path = "https://focus.test.sensingclues.org/api/labels/list") # Test Environment + # i18n <- Translator$new(translation_json_path = "https://focus.sensingclues.org/api/labels/list") # Prod + i18n <- Translator$new(translation_json_path = "https://focus.test.sensingclues.org/api/labels/list") }, error = function(e) { - message("No labels available online, we will use the old ones from disk.") + message("No labels available online, using local") }) - if (!exists("i18n")) { - # use the stored version i18n <- Translator$new(translation_json_path = "translations.json") } i18n$set_translation_language("en") -# js code to get the browser language - Corrected escaping -js_lang <- "var language = window.navigator.userLanguage || window.navigator.language; - Shiny.onInputChange('browser_language', language); - console.log(language);" +# JS for browser language +js_lang <- "var language = window.navigator.userLanguage || window.navigator.language; + Shiny.onInputChange('browser_language', language);" ui <- fluidPage( useShinyjs(), shiny.i18n::usei18n(i18n), extendShinyjs(text = js_lang, functions = c()), - # Get timezone from browser - Corrected escaping - - tags$script( - "$(document).on('shiny:sessioninitialized', function(event) { - var n = Intl.DateTimeFormat().resolvedOptions().timeZone; - Shiny.onInputChange('user_timezone', n);});" - ), + # Get timezone from browser + tags$script(HTML(" + $(document).on('shiny:sessioninitialized', function() { + var tz = Intl.DateTimeFormat().resolvedOptions().timeZone; + Shiny.onInputChange('user_timezone', tz); + }); + ")), # Load custom stylesheet includeCSS("www/style.css"), - shiny::tagList( - div( - class = "header", - - # combine the two logos, next to each other - div( - # logo SC - tags$a( - href = "https://sensingclues.org", - target = "_blank", - class = "logo", img(src = "logo_white.png")) - ), - - # titel + + ## HEADER + div(class = "header", div( - class = "title", - "ACTIVITY PATTERN", - style = "font-size: 18px;" + tags$a(href = "https://sensingclues.org", target = "_blank", + class = "logo", img(src = "logo_white.png")) ), - # Right side: user status - div( - style = "min-width: 150px; text-align: right;", - uiOutput("userstatus") + div(class = "title", "ACTIVITY PATTERN"), + div(style = "min-width:150px; text-align:right;", + uiOutput("userstatus") ) - ) ), - sidebarLayout( - sidebarPanel( - width = 3, - HTML( - paste0( - "
", - "
" - ) - ), - # --- Collapsible About Box --- - tags$head( - tags$link(rel = "stylesheet", href = "https://fonts.googleapis.com/icon?family=Material+Icons"), - tags$style(HTML(" - .collapsible-section summary::-webkit-details-marker { - display: none; - } - .readmore { - font-weight: normal; - font-size: inherit; - color: #004d40; - text-decoration: underline; - } - .collapsible-header { - display: flex; - align-items: center; - justify-content: space-between; - cursor: pointer; - font-size: 16px; - font-weight: bold; - margin-bottom: 5px; - } - .collapsible-header .expand-icon { - transition: transform 0.3s ease; - font-size: 24px; - color: #555; - } - details[open] .expand-icon { - transform: rotate(180deg); - } - ")) - ), - - tags$details( - id = "aboutCollapse", - class = "collapsible-section", - tags$summary( - class = "collapsible-header", - HTML('Aboutexpand_more') - ), - p("With this app you can explore pattern in animal observation data. Use the matrix to reveal activity trends by hour or month, view total counts per species or download the underlying datasets."), - tags$a( - "Learn more", - href = "https://www.sensingclues.org/about-activity-pattern", - class = "readmore", - target = "_blank" - ) - ), - - tags$script(HTML(" - document.addEventListener('DOMContentLoaded', function() { - var el = document.getElementById('aboutCollapse'); - if (el) { - var summary = el.querySelector('summary'); - summary.addEventListener('click', function(e) { - setTimeout(function() { - var icon = summary.querySelector('.expand-icon'); - if (el.hasAttribute('open')) { - icon.style.transform = 'rotate(180deg)'; - } else { - icon.style.transform = 'rotate(0deg)'; - } - }, 100); - }); - } - }); -")), - # --- End Collapsible About Box --- - - # Custom button styles - tags$head( - tags$style( - "#GetData{background-color:#FB8C00; color:white; font-size:100%}" - ), - tags$style( - "#login{background-color:#FB8C00; color:white; font-size:100%}" - ), + + ## CONTENT WRAPPER (overlaps header) + div(class = "content-wrapper", + sidebarLayout( - tags$style( - "#message_more_dates{color: red; font-size: 20px; font-style: italic}" - ), - tags$style( - "#downloadData{background-color:#FB8C00; color:white; font-size:100%}" - ), - ), - br(), - - # --- Filter Sections --- - div(class = "filter-section time-period-box", - h4("Time Period"), - # Added a container div for easier styling of the date range input width - div(class = "date-range-input-container", - disabled(dateRangeInput("DateRange", i18n$t("labels.selectPeriod"))) - ) - ), - br(), - div( - style = "position: fixed; top: 45%; left: 60%; transform: translate(-50%, -50%);", - add_busy_spinner(spin = "fading-circle", width = "100px", height = "100px") - ), - - div(class = "filter-section data-sources-box", - h4("Data Sources"), - disabled(div( - class = "choosechannel", - id = "GroupListDiv", - pickerInput( - inputId = "GroupList", - label = i18n$t("labels.selectGroup"), # This label might be redundant with the H4 heading, consider removing if needed - choices = list(), - multiple = TRUE, - options = pickerOptions( - actionsBox = TRUE, - noneSelectedText = '', - selectAllText = i18n$t("labels.selectAll"), - deselectAllText = i18n$t("labels.deselectAll") + sidebarPanel( + width = 3, + HTML("

"), + + # Collapsible About Box + tags$head( + tags$link(rel = "stylesheet", href = "https://fonts.googleapis.com/icon?family=Material+Icons") + ), + tags$details( + id = "aboutCollapse", class = "collapsible-section", + tags$summary(class = "collapsible-header", + HTML('Aboutexpand_more') + ), + p("With this app you can explore pattern in animal observation data. Use the matrix to reveal activity trends by hour or month, view total counts per species or download the underlying datasets."), + tags$a("Learn more", href = "https://www.sensingclues.org/about-activity-pattern", + class = "readmore", target = "_blank") + ), + tags$script(HTML(" + document.addEventListener('DOMContentLoaded', function() { + var el = document.getElementById('aboutCollapse'); + if (el) { + el.querySelector('summary').addEventListener('click', function() { + setTimeout(function() { + var icon = el.querySelector('.expand-icon'); + icon.style.transform = el.hasAttribute('open') ? 'rotate(180deg)' : 'rotate(0deg)'; + }, 100); + }); + } + }); + ")), + + # Custom button styles & spinner message + tags$head(tags$style(HTML(" + #GetData, #downloadData, #login { background-color:#FB8C00; color:white; font-size:100%; } + #message_more_dates { color: red; font-size: 20px; font-style: italic; } + "))), + + # Filters + div(class = "filter-section time-period-box", + h4("Time Period"), + div(class = "date-range-input-container", + disabled(dateRangeInput("DateRange", i18n$t("labels.selectPeriod"))) ) - ) - )) - ), - br(), - - div(class = "filter-section concepts-box", - h4("Concepts"), - p("Select concepts (one or more)"), - shinyTree("conceptTree", checkbox = TRUE, theme = "proton") - ), - br(), - # --- End Filter Sections --- - - disabled(actionButton( - "GetData", i18n$t("commands.getdata"), icon = NULL - )), - br() - ), - - mainPanel( - width = 9, - tags$head(tags$style( - # Corrected escaping for the CSS content within HTML() - HTML(".sep { - width: 20px; - height: 1px; - float: left; - }") - )), - tabsetPanel( - type = "tabs", - tabPanel(i18n$t("Activity Pattern"), - fluidPage( - - # === Time Interval Row === - fluidRow( - column( - 12, - div( - style = "display: flex; align-items: center; gap: 20px; margin-top: 10px;", - - # Time Interval Box - div( - style = "width: 100px;", - selectInput( - inputId = "time_input", - label = i18n$t("Time interval"), - choices = list( - "Hourly" = "hourly", - "Monthly" = "monthly" - # "Seasonal" = "season" - ), - selected = "hourly", - width = "100%" - ) - ), - - # Conditional Season Controls - conditionalPanel( - condition = "input.time_input == 'season'", - div( - style = "display: flex; align-items: center; gap: 20px;", - div( - style = "width: 100px;", - numericInput( - "num_seasons", - "# Seasons:", - value = 1, - min = 1, - width = "100%" - ), - bsTooltip( - "num_seasons", - "Select the number of seasons for analysis. Input the calendar month number to specify season. (1 = January, 2 = February, etc.)", - placement = "right", - options = list(container = "body") - ) - ), - div( - style = "display: flex; flex-wrap: wrap; gap: 10px;", - uiOutput("season_inputs") - ) - ) + ), + br(), + div(style = "position: fixed; top: 45%; left: 60%; transform: translate(-50%, -50%);", + add_busy_spinner(spin = "fading-circle", width = "100px", height = "100px") + ), + div(class = "filter-section data-sources-box", + h4("Data Sources"), + disabled(div( + id = "GroupListDiv", class = "choosechannel", + pickerInput("GroupList", label = i18n$t("labels.selectGroup"), + choices = list(), multiple = TRUE, + options = pickerOptions( + actionsBox = TRUE, + noneSelectedText = '', + selectAllText = i18n$t("labels.selectAll"), + deselectAllText = i18n$t("labels.deselectAll") + )) + )) + ), + br(), + div(class = "filter-section concepts-box", + h4("Concepts"), + p("Select concepts (one or more)"), + shinyTree("conceptTree", checkbox = TRUE, theme = "proton") + ), + br(), + disabled(actionButton("GetData", i18n$t("commands.getdata"))), + br() + ), + + mainPanel( + width = 9, + tags$head(tags$style(HTML(".sep { width:20px; height:1px; float:left; }"))), + tabsetPanel( + type = "tabs", + + tabPanel(i18n$t("Activity Pattern"), + fluidPage( + fluidRow( + column(12, + div(style = "display:flex;align-items:center;gap:20px;margin-top:10px;", + div(style = "width:150px;", + selectInput("time_input", i18n$t("Time interval"), + choices = list("Hourly"="hourly","Monthly"="monthly"), + selected = "hourly", width = "100%")), + # conditionalPanel( + # "input.time_input=='season'", + # div(style="display:flex;align-items:center;gap:20px;", + # div(style="width:150px;", + # numericInput("num_seasons", "# Seasons:", value=1, min=1, width="100%"), + # bsTooltip("num_seasons","Select number of seasons","right")), + # div(style="display:flex;flex-wrap:wrap;gap:10px;", uiOutput("season_inputs")) + # ) + # ) + ) ) - ) - ) - ), - - # === TopX and Aggregation Method Row === - fluidRow( - column( - 12, - div( - style = "display: flex; align-items: center; gap: 20px; margin-top: 15px;", - - # Top X Filter Box (match width to Time Interval box) - div( - style = "width: 100px;", - numericInput( - inputId = "topX", - label = "Top rows:", - value = 5, - min = 1, - max = 20, - step = 1, - width = "100%" - ) - ), - - # Aligned Radio Buttons (inline, vertically centered) - div( - style = "display: flex; align-items: flex-end; height: 58px;", # Adjust height to match input height - radioButtons( - inputId = "agg_method", - label = NULL, - choices = list("Counts" = "counts", "Percentage" = "percentage"), - selected = "counts", - inline = TRUE - ) + ), + fluidRow( + column(12, + div(style="display:flex;align-items:center;gap:20px;margin-top:15px;", + div(style="width:150px;", + numericInput("topX","Top rows:",value=5,min=1,max=20,step=1,width="100%")), + div(style="display:flex;align-items:flex-end;height:58px;", + radioButtons("agg_method", NULL, choices=list("Counts"="counts","Percentage"="percentage"), inline=TRUE)) + ) ) - ) + ), + fluidRow(column(12, plotlyOutput("combined_plot"))), + fluidRow(column(12, + div(style="margin-top:20px;", + downloadButton("download_plotly","Download activity pattern plot (.html)"), + downloadButton("download_csv","Download Data (.csv)")) + )) ) - ), - - # === Plot Row === - fluidRow( - column( - 12, - plotlyOutput("combined_plot") - ) - ), - - # === Download Button Row === - fluidRow( - column( - 12, - div( - style = "margin-top: 20px;", - downloadButton("download_plotly", "Download activity pattern plot (.html)"), - downloadButton("download_csv", "Download Data (.csv)") - ) - ) - ) - ) - ), - # tabPanel( - # i18n$t("labels.rawConceptsTab"), - # fluidRow(column( - # 12, DT::dataTableOutput("tableRawConcepts") - # )), - # div( - # style = "position: fixed; top: 45%; left: 60%; transform: translate(-50%, -50%);", - # add_busy_spinner( - # spin = "fading-circle", - # width = "100px", - # height = "100px" - # ) - # ) - # ), - # - # endpanel - - tabPanel( - i18n$t("labels.rawData"), - br(), - column(2, br(), br(), downloadButton( - "downloadData", i18n$t("commands.download") - )), - fluidRow(column( - 12, DT::dataTableOutput("tableRawObservations") - )), - div( - style = "position: fixed; top: 45%; left: 60%; transform: translate(-50%, -50%);", - add_busy_spinner( - spin = "fading-circle", - width = "100px", - height = "100px" + ), + + tabPanel(i18n$t("labels.rawData"), + br(), + column(2, br(), br(), downloadButton("downloadData", i18n$t("commands.download"))), + fluidRow(column(12, DT::dataTableOutput("tableRawObservations"))), + div(style="position:fixed;top:45%;left:60%;transform:translate(-50%,-50%);", + add_busy_spinner(spin="fading-circle",width="100px",height="100px")) ) + ) ) ) - ) ) -) \ No newline at end of file +) diff --git a/www/style.css b/www/style.css index d0900ef..5b680a6 100644 --- a/www/style.css +++ b/www/style.css @@ -1,66 +1,105 @@ -$blue: #1E88E5; - $indigo: #3949AB; - $purple: #5E35B1; - $pink: #D81B60; - $red: #E53935; - $orange: #FB8C00; - $yellow: #FDD835; - $green: #43A047; - $teal: #00897B; - $cyan: #00ACC1; - - $primary: #004D40; - $secondary: #FFAB00; - $success: #00897B; - $info: #607D8B; - $warning: #FB8C00; - $danger: #E53935; - $light: $gray-100; -$dark: $gray-800; +/* www/style.css */ +/* ---------------------------- + RESET & GLOBAL STYLING +---------------------------- */ +html, body { + margin: 0; + padding: 0; +} body { - font-family: Verdana; - + font-family: Verdana, sans-serif; +} +.container-fluid { + padding-left: 0; + padding-right: 0; } - -/* ============================ - Header (20% viewport height with 10% horizontal padding) -============================ */ +/* ---------------------------- + HEADER +---------------------------- */ .header { display: flex; - align-items: center; /* Center elements vertically */ - justify-content: space-between; /* Distribute items across the header */ - background-color: #004d40; - height: 11vh; - padding: 2rem 7vw; /* Adjust horizontal padding */ + align-items: flex-start; /* items at top */ + justify-content: space-between; + background-color: #004d40; /* primary */ + height: 20vh; + box-sizing: border-box; /* include padding in height */ + padding: 12px 7vw 0; /* 12px top, 7vw sides, 0 bottom */ border-bottom: 2px solid #00332b; position: relative; - z-index: 0; + z-index: 10; /* above content-wrapper */ } - .header .logo img { height: 40px; - margin-top: 0.5rem; + margin: 0; } - .header .title { + flex: 1; font-size: 1.5rem; font-weight: bold; letter-spacing: 1px; - color: white; - text-align: center; /* Center the title text */ + color: #fff; + text-align: center; + margin-top: 0; } +/* ---------------------------- + CONTENT WRAPPER +---------------------------- */ +.content-wrapper { + position: relative; + top: -100px; /* overlap amount */ + margin: 0 20px; + background: #fff; + border-radius: 12px; + padding: 20px; + z-index: 20; /* below header */ + box-shadow: 0 4px 10px rgba(0,0,0,0.1); +} -.well { - background-color: #ECEFF1; - # color: #ECEFF1; +/* ---------------------------- + BUTTONS & SPINNER MESSAGE +---------------------------- */ +#GetData, +#downloadData, +#login { + background-color: #FB8C00; /* warning */ + color: #fff; + font-size: 100%; +} +#message_more_dates { + color: red; + font-size: 20px; + font-style: italic; } - \ No newline at end of file +/* ---------------------------- + COLLAPSIBLE “About” +---------------------------- */ +.collapsible-section summary::-webkit-details-marker { + display: none; +} +.readmore { + font-weight: normal; + font-size: inherit; + color: #004d40; + text-decoration: underline; +} +.collapsible-header { + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; + font-size: 16px; + font-weight: bold; + margin-bottom: 5px; +} +.collapsible-header .expand-icon { + transition: transform 0.3s ease; + font-size: 24px; + color: #555; +} +details[open] .expand-icon { + transform: rotate(180deg); +} From 99e850ed3bacc12c4fbb7c67c20807c71f928b61 Mon Sep 17 00:00:00 2001 From: Jan Kees Date: Wed, 23 Jul 2025 11:13:20 +0200 Subject: [PATCH 08/17] reversed comments library sensingcluesr --- server.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.R b/server.R index a8afe05..73a1787 100644 --- a/server.R +++ b/server.R @@ -23,8 +23,8 @@ library(promises) # load the sensincluesr package library(devtools) -#devtools::install_github("sensingclues/sensingcluesr@v1.0.3", upgrade = "never") -library(sensingcluesr) +devtools::install_github("sensingclues/sensingcluesr@v1.0.3", upgrade = "never") +#library(sensingcluesr) # dynamic color maps for more then 12 colors library(colorRamps) From df77019ed78d6cb8b7a7271578cf011fd427e9ee Mon Sep 17 00:00:00 2001 From: Jan Kees Date: Wed, 23 Jul 2025 11:19:49 +0200 Subject: [PATCH 09/17] adjusted spaces in left panel --- ui.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui.R b/ui.R index 395c41f..b0994c6 100644 --- a/ui.R +++ b/ui.R @@ -59,7 +59,7 @@ ui <- fluidPage( sidebarPanel( width = 3, - HTML("

"), + #HTML("
"), # Collapsible About Box tags$head( @@ -96,6 +96,7 @@ ui <- fluidPage( # Filters div(class = "filter-section time-period-box", + br(), h4("Time Period"), div(class = "date-range-input-container", disabled(dateRangeInput("DateRange", i18n$t("labels.selectPeriod"))) From 4b53f10edeef2fbf9f303380763efec57a68d0c6 Mon Sep 17 00:00:00 2001 From: Jan Kees Date: Wed, 23 Jul 2025 15:53:31 +0200 Subject: [PATCH 10/17] updating dev branch with my latest working code1 part 1 --- app.R | 2 - server.R | 9 +- ui.R | 492 ++++++++++++++++++++++++++++++++++++++----------------- 3 files changed, 347 insertions(+), 156 deletions(-) diff --git a/app.R b/app.R index 802252b..77795e9 100644 --- a/app.R +++ b/app.R @@ -1,8 +1,6 @@ -# app.R # load the ui and server and call them to start the Shiny application -source("global.R") source("ui.R") source("server.R") diff --git a/server.R b/server.R index 73a1787..9986be7 100644 --- a/server.R +++ b/server.R @@ -1,5 +1,3 @@ -# server.R - ## Translating heatmap plot into RShiny App SC - server script ## Author: H. Fricke ## Date: 22-03-2025 @@ -7,8 +5,11 @@ ## To-Do: ## [] method selection does not work + options(shiny.reactlog = TRUE) + + ## SET UP libraries and sourced files # Define server logic (other libraries in ui) library(DT) @@ -23,8 +24,8 @@ library(promises) # load the sensincluesr package library(devtools) -devtools::install_github("sensingclues/sensingcluesr@v1.0.3", upgrade = "never") -#library(sensingcluesr) +#devtools::install_github("sensingclues/sensingcluesr@v1.0.3", upgrade = "never") +library(sensingcluesr) # dynamic color maps for more then 12 colors library(colorRamps) diff --git a/ui.R b/ui.R index b0994c6..151bd8b 100644 --- a/ui.R +++ b/ui.R @@ -1,4 +1,7 @@ -# ui.R +# Author: Hanna Fricke +# Description: User interface for activity app. +# TO-DO : +# [] DONT HARDCODE METHOD FUTURE BUT BASE IT ON DATA library(shiny) library(shiny.i18n) # for multilanguage @@ -10,183 +13,372 @@ library(shinyWidgets) library(plotly) # multi language + + + tryCatch({ - # i18n <- Translator$new(translation_json_path = "https://focus.sensingclues.org/api/labels/list") # Prod - i18n <- Translator$new(translation_json_path = "https://focus.test.sensingclues.org/api/labels/list") + # try to get online version + # i18n <- Translator$new(translation_json_path = "https://focus.sensingclues.org/api/labels/list") # Production Environment + i18n <- Translator$new(translation_json_path = "https://focus.test.sensingclues.org/api/labels/list") # Test Environment }, error = function(e) { - message("No labels available online, using local") + message("No labels available online, we will use the old ones from disk.") }) + if (!exists("i18n")) { + # use the stored version i18n <- Translator$new(translation_json_path = "translations.json") } i18n$set_translation_language("en") -# JS for browser language -js_lang <- "var language = window.navigator.userLanguage || window.navigator.language; - Shiny.onInputChange('browser_language', language);" +# js code to get the browser language - Corrected escaping +js_lang <- "var language = window.navigator.userLanguage || window.navigator.language; + Shiny.onInputChange('browser_language', language); + console.log(language);" ui <- fluidPage( useShinyjs(), shiny.i18n::usei18n(i18n), extendShinyjs(text = js_lang, functions = c()), - # Get timezone from browser - tags$script(HTML(" - $(document).on('shiny:sessioninitialized', function() { - var tz = Intl.DateTimeFormat().resolvedOptions().timeZone; - Shiny.onInputChange('user_timezone', tz); - }); - ")), + # Get timezone from browser - Corrected escaping + + tags$script( + "$(document).on('shiny:sessioninitialized', function(event) { + var n = Intl.DateTimeFormat().resolvedOptions().timeZone; + Shiny.onInputChange('user_timezone', n);});" + ), # Load custom stylesheet includeCSS("www/style.css"), - - ## HEADER - div(class = "header", + shiny::tagList( + div( + class = "header", + + # combine the two logos, next to each other div( - tags$a(href = "https://sensingclues.org", target = "_blank", - class = "logo", img(src = "logo_white.png")) + # logo SC + tags$a( + href = "https://sensingclues.org", + target = "_blank", + class = "logo", img(src = "logo_white.png")) ), - div(class = "title", "ACTIVITY PATTERN"), - div(style = "min-width:150px; text-align:right;", - uiOutput("userstatus") + + # titel + div( + class = "title", + "ACTIVITY PATTERN", + style = "font-size: 18px;" + ), + # Right side: user status + div( + style = "min-width: 150px; text-align: right;", + uiOutput("userstatus") ) + ) ), - - ## CONTENT WRAPPER (overlaps header) - div(class = "content-wrapper", - sidebarLayout( - - sidebarPanel( - width = 3, - #HTML("
"), - - # Collapsible About Box - tags$head( - tags$link(rel = "stylesheet", href = "https://fonts.googleapis.com/icon?family=Material+Icons") - ), - tags$details( - id = "aboutCollapse", class = "collapsible-section", - tags$summary(class = "collapsible-header", - HTML('Aboutexpand_more') - ), - p("With this app you can explore pattern in animal observation data. Use the matrix to reveal activity trends by hour or month, view total counts per species or download the underlying datasets."), - tags$a("Learn more", href = "https://www.sensingclues.org/about-activity-pattern", - class = "readmore", target = "_blank") - ), - tags$script(HTML(" - document.addEventListener('DOMContentLoaded', function() { - var el = document.getElementById('aboutCollapse'); - if (el) { - el.querySelector('summary').addEventListener('click', function() { - setTimeout(function() { - var icon = el.querySelector('.expand-icon'); - icon.style.transform = el.hasAttribute('open') ? 'rotate(180deg)' : 'rotate(0deg)'; - }, 100); - }); - } - }); - ")), - - # Custom button styles & spinner message - tags$head(tags$style(HTML(" - #GetData, #downloadData, #login { background-color:#FB8C00; color:white; font-size:100%; } - #message_more_dates { color: red; font-size: 20px; font-style: italic; } - "))), - - # Filters - div(class = "filter-section time-period-box", - br(), - h4("Time Period"), - div(class = "date-range-input-container", - disabled(dateRangeInput("DateRange", i18n$t("labels.selectPeriod"))) - ) - ), - br(), - div(style = "position: fixed; top: 45%; left: 60%; transform: translate(-50%, -50%);", - add_busy_spinner(spin = "fading-circle", width = "100px", height = "100px") - ), - div(class = "filter-section data-sources-box", - h4("Data Sources"), - disabled(div( - id = "GroupListDiv", class = "choosechannel", - pickerInput("GroupList", label = i18n$t("labels.selectGroup"), - choices = list(), multiple = TRUE, - options = pickerOptions( - actionsBox = TRUE, - noneSelectedText = '', - selectAllText = i18n$t("labels.selectAll"), - deselectAllText = i18n$t("labels.deselectAll") - )) - )) - ), - br(), - div(class = "filter-section concepts-box", - h4("Concepts"), - p("Select concepts (one or more)"), - shinyTree("conceptTree", checkbox = TRUE, theme = "proton") - ), - br(), - disabled(actionButton("GetData", i18n$t("commands.getdata"))), - br() + div(class = "content", + sidebarLayout( + sidebarPanel( + width = 3, + # --- Collapsible About Box --- + tags$head( + tags$link(rel = "stylesheet", href = "https://fonts.googleapis.com/icon?family=Material+Icons"), + tags$style(HTML(" + .collapsible-section summary::-webkit-details-marker { + display: none; + } + .readmore { + font-weight: normal; + font-size: inherit; + color: #004d40; + text-decoration: underline; + } + .collapsible-header { + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; + font-size: 16px; + font-weight: bold; + margin-bottom: 5px; + } + .collapsible-header .expand-icon { + transition: transform 0.3s ease; + font-size: 24px; + color: #555; + } + details[open] .expand-icon { + transform: rotate(180deg); + } + ")) + ), + + tags$details( + id = "aboutCollapse", + class = "collapsible-section", + tags$summary( + class = "collapsible-header", + HTML('Aboutexpand_more') + ), + p("With this app you can explore pattern in animal observation data. Use the matrix to reveal activity trends by hour or month, view total counts per species or download the underlying datasets."), + tags$a( + "Learn more", + href = "https://www.sensingclues.org/about-activity-pattern", + class = "readmore", + target = "_blank" + ) + ), + + tags$script(HTML(" + document.addEventListener('DOMContentLoaded', function() { + var el = document.getElementById('aboutCollapse'); + if (el) { + var summary = el.querySelector('summary'); + summary.addEventListener('click', function(e) { + setTimeout(function() { + var icon = summary.querySelector('.expand-icon'); + if (el.hasAttribute('open')) { + icon.style.transform = 'rotate(180deg)'; + } else { + icon.style.transform = 'rotate(0deg)'; + } + }, 100); + }); + } + }); +")), + # --- End Collapsible About Box --- + + # Custom button styles + tags$head( + tags$style( + "#GetData{background-color:#FB8C00; color:white; font-size:100%}" + ), + tags$style( + "#login{background-color:#FB8C00; color:white; font-size:100%}" ), - mainPanel( - width = 9, - tags$head(tags$style(HTML(".sep { width:20px; height:1px; float:left; }"))), - tabsetPanel( - type = "tabs", - - tabPanel(i18n$t("Activity Pattern"), - fluidPage( - fluidRow( - column(12, - div(style = "display:flex;align-items:center;gap:20px;margin-top:10px;", - div(style = "width:150px;", - selectInput("time_input", i18n$t("Time interval"), - choices = list("Hourly"="hourly","Monthly"="monthly"), - selected = "hourly", width = "100%")), - # conditionalPanel( - # "input.time_input=='season'", - # div(style="display:flex;align-items:center;gap:20px;", - # div(style="width:150px;", - # numericInput("num_seasons", "# Seasons:", value=1, min=1, width="100%"), - # bsTooltip("num_seasons","Select number of seasons","right")), - # div(style="display:flex;flex-wrap:wrap;gap:10px;", uiOutput("season_inputs")) - # ) - # ) - ) + tags$style( + "#message_more_dates{color: red; font-size: 20px; font-style: italic}" + ), + tags$style( + "#downloadData{background-color:#FB8C00; color:white; font-size:100%}" + ), + ), + br(), + + # --- Filter Sections --- + div(class = "filter-section time-period-box", + h4("Time Period"), + # Added a container div for easier styling of the date range input width + div(class = "date-range-input-container", + disabled(dateRangeInput("DateRange", i18n$t("labels.selectPeriod"))) + ) + ), + br(), + div( + style = "position: fixed; top: 45%; left: 60%; transform: translate(-50%, -50%);", + add_busy_spinner(spin = "fading-circle", width = "100px", height = "100px") + ), + + div(class = "filter-section data-sources-box", + h4("Data Sources"), + disabled(div( + class = "choosechannel", + id = "GroupListDiv", + pickerInput( + inputId = "GroupList", + label = i18n$t("labels.selectGroup"), # This label might be redundant with the H4 heading, consider removing if needed + choices = list(), + multiple = TRUE, + options = pickerOptions( + actionsBox = TRUE, + noneSelectedText = '', + selectAllText = i18n$t("labels.selectAll"), + deselectAllText = i18n$t("labels.deselectAll") + ) + ) + )) + ), + br(), + + div(class = "filter-section concepts-box", + h4("Concepts"), + p("Select concepts (one or more)"), + shinyTree("conceptTree", checkbox = TRUE, theme = "proton") + ), + br(), + # --- End Filter Sections --- + + disabled(actionButton( + "GetData", i18n$t("commands.getdata"), icon = NULL + )), + br() + ), + + mainPanel( + width = 9, + tags$head(tags$style( + # Corrected escaping for the CSS content within HTML() + # HTML(".sep { + # width: 20px; + # height: 1px; + # float: left; + # }") + )), + tabsetPanel( + type = "tabs", + tabPanel(i18n$t("Activity Pattern"), + fluidPage( + + # === Time Interval Row === + fluidRow( + column( + 12, + div( + style = "display: flex; align-items: center; gap: 20px; margin-top: 10px;", + + # Time Interval Box + div( + style = "width: 150px;", + selectInput( + inputId = "time_input", + label = i18n$t("Time interval"), + choices = list( + "Hourly" = "hourly", + "Monthly" = "monthly" + # "Seasonal" = "season" + ), + selected = "hourly", + width = "100%" + ) + ), + + # Conditional Season Controls + conditionalPanel( + condition = "input.time_input == 'season'", + div( + style = "display: flex; align-items: center; gap: 20px;", + div( + style = "width: 150px;", + numericInput( + "num_seasons", + "# Seasons:", + value = 1, + min = 1, + width = "100%" + ), + bsTooltip( + "num_seasons", + "Select the number of seasons for analysis. Input the calendar month number to specify season. (1 = January, 2 = February, etc.)", + placement = "right", + options = list(container = "body") + ) + ), + div( + style = "display: flex; flex-wrap: wrap; gap: 10px;", + uiOutput("season_inputs") + ) + ) ) - ), - fluidRow( - column(12, - div(style="display:flex;align-items:center;gap:20px;margin-top:15px;", - div(style="width:150px;", - numericInput("topX","Top rows:",value=5,min=1,max=20,step=1,width="100%")), - div(style="display:flex;align-items:flex-end;height:58px;", - radioButtons("agg_method", NULL, choices=list("Counts"="counts","Percentage"="percentage"), inline=TRUE)) - ) + ) + ) + ), + + # === TopX and Aggregation Method Row === + fluidRow( + column( + 12, + div( + style = "display: flex; align-items: center; gap: 20px; margin-top: 15px;", + + # Top X Filter Box (match width to Time Interval box) + div( + style = "width: 150px;", + numericInput( + inputId = "topX", + label = "Top rows:", + value = 5, + min = 1, + max = 20, + step = 1, + width = "100%" + ) + ), + + # Aligned Radio Buttons (inline, vertically centered) + div( + style = "display: flex; align-items: flex-end; height: 58px;", # Adjust height to match input height + radioButtons( + inputId = "agg_method", + label = NULL, + choices = list("Counts" = "counts", "Percentage" = "percentage"), + selected = "counts", + inline = TRUE + ) ) - ), - fluidRow(column(12, plotlyOutput("combined_plot"))), - fluidRow(column(12, - div(style="margin-top:20px;", - downloadButton("download_plotly","Download activity pattern plot (.html)"), - downloadButton("download_csv","Download Data (.csv)")) - )) + ) + ) + ), + + # === Plot Row === + fluidRow( + column( + 12, + plotlyOutput("combined_plot") ) - ), - - tabPanel(i18n$t("labels.rawData"), - br(), - column(2, br(), br(), downloadButton("downloadData", i18n$t("commands.download"))), - fluidRow(column(12, DT::dataTableOutput("tableRawObservations"))), - div(style="position:fixed;top:45%;left:60%;transform:translate(-50%,-50%);", - add_busy_spinner(spin="fading-circle",width="100px",height="100px")) + ), + + # === Download Button Row === + fluidRow( + column( + 12, + div( + style = "margin-top: 20px;", + downloadButton("download_plotly", "Download activity pattern plot (.html)"), + downloadButton("download_csv", "Download Data (.csv)") + ) + ) + ) + ) + ), + # tabPanel( + # i18n$t("labels.rawConceptsTab"), + # fluidRow(column( + # 12, DT::dataTableOutput("tableRawConcepts") + # )), + # div( + # style = "position: fixed; top: 45%; left: 60%; transform: translate(-50%, -50%);", + # add_busy_spinner( + # spin = "fading-circle", + # width = "100px", + # height = "100px" + # ) + # ) + # ), + # + # endpanel + + tabPanel( + i18n$t("labels.rawData"), + br(), + column(2, br(), br(), downloadButton( + "downloadData", i18n$t("commands.download") + )), + fluidRow(column( + 12, DT::dataTableOutput("tableRawObservations") + )), + div( + style = "position: fixed; top: 45%; left: 60%; transform: translate(-50%, -50%);", + add_busy_spinner( + spin = "fading-circle", + width = "100px", + height = "100px" ) - ) ) ) + ) + ) ) -) +) \ No newline at end of file From 42fbd20b588f02495b72168dbc80ae8c37fa0b5d Mon Sep 17 00:00:00 2001 From: Jan Kees Date: Wed, 23 Jul 2025 15:54:06 +0200 Subject: [PATCH 11/17] updating dev branch with my latest working code2 updated style --- www/style.css | 147 ++++++++++++++++++++++++-------------------------- 1 file changed, 70 insertions(+), 77 deletions(-) diff --git a/www/style.css b/www/style.css index 5b680a6..23e1d50 100644 --- a/www/style.css +++ b/www/style.css @@ -1,105 +1,98 @@ -/* www/style.css */ +$blue: #1E88E5; + $indigo: #3949AB; + $purple: #5E35B1; + $pink: #D81B60; + $red: #E53935; + $orange: #FB8C00; + $yellow: #FDD835; + $green: #43A047; + $teal: #00897B; + $cyan: #00ACC1; + + $primary: #004D40; + $secondary: #FFAB00; + $success: #00897B; + $info: #607D8B; + $warning: #FB8C00; + $danger: #E53935; + $light: $gray-100; +$dark: $gray-800; -/* ---------------------------- - RESET & GLOBAL STYLING ----------------------------- */ -html, body { - margin: 0; - padding: 0; -} body { - font-family: Verdana, sans-serif; + font-family: Verdana; + } + .container-fluid { padding-left: 0; padding-right: 0; } +.row { + margin-left: 5px; + margin-right: 5px; +} +.row > [class*="col-"] { + padding-left: 0px; + padding-right: 5px; +} + -/* ---------------------------- - HEADER ----------------------------- */ + +/* ============================ + Header (20% viewport height with 10% horizontal padding) +============================ */ .header { display: flex; - align-items: flex-start; /* items at top */ - justify-content: space-between; - background-color: #004d40; /* primary */ + align-items: flex-start; + justify-content: space-between; /* Distribute items across the header */ + background-color: #004d40; height: 20vh; - box-sizing: border-box; /* include padding in height */ - padding: 12px 7vw 0; /* 12px top, 7vw sides, 0 bottom */ + padding: 2rem 7vw; /* Adjust horizontal padding */ border-bottom: 2px solid #00332b; position: relative; - z-index: 10; /* above content-wrapper */ + z-index: 0; } + .header .logo img { height: 40px; - margin: 0; + margin-top: 0.5rem; } + .header .title { - flex: 1; font-size: 1.5rem; font-weight: bold; letter-spacing: 1px; - color: #fff; - text-align: center; - margin-top: 0; + color: white; + text-align: center; /* Center the title text */ } -/* ---------------------------- - CONTENT WRAPPER ----------------------------- */ -.content-wrapper { - position: relative; - top: -100px; /* overlap amount */ - margin: 0 20px; - background: #fff; - border-radius: 12px; - padding: 20px; - z-index: 20; /* below header */ - box-shadow: 0 4px 10px rgba(0,0,0,0.1); +body { + z-index: 1000; + position: relative; /* z-index only works on positioned elements */ } -/* ---------------------------- - BUTTONS & SPINNER MESSAGE ----------------------------- */ -#GetData, -#downloadData, -#login { - background-color: #FB8C00; /* warning */ - color: #fff; - font-size: 100%; -} -#message_more_dates { - color: red; - font-size: 20px; - font-style: italic; +.content { + position: relative; /* enable positioning */ + top: -10vh; /* pull up over the header by 20px (adjust as needed) */ + margin: 0 50px; /* xx px left/right margins */ + z-index: 2; /* above .header (which should be z-index:1 or 0) */ + background: white; /* or whatever bg you need */ + border-radius: 10px; /* rounded corners */ + box-shadow: 0 2px 8px rgba(0,0,0,0.2); /* optional drop-shadow */ + overflow: hidden; /* clip child content to rounded corners */ + height: 90vh; /* hoogte = xx% van viewport */ + overflow-y: auto; /* scrollen als inhoud groter is */ } -/* ---------------------------- - COLLAPSIBLE “About” ----------------------------- */ -.collapsible-section summary::-webkit-details-marker { - display: none; -} -.readmore { - font-weight: normal; - font-size: inherit; - color: #004d40; - text-decoration: underline; -} -.collapsible-header { - display: flex; - align-items: center; - justify-content: space-between; - cursor: pointer; - font-size: 16px; - font-weight: bold; - margin-bottom: 5px; -} -.collapsible-header .expand-icon { - transition: transform 0.3s ease; - font-size: 24px; - color: #555; -} -details[open] .expand-icon { - transform: rotate(180deg); + +.well { + background-color: #ECEFF1; + # color: #ECEFF1; } + + \ No newline at end of file From acce9459374ed428af9d7dfa7157878c702ec357 Mon Sep 17 00:00:00 2001 From: Jan Kees Date: Wed, 23 Jul 2025 15:57:51 +0200 Subject: [PATCH 12/17] removed small white space --- www/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/style.css b/www/style.css index 23e1d50..b3602af 100644 --- a/www/style.css +++ b/www/style.css @@ -28,7 +28,7 @@ body { padding-right: 0; } .row { - margin-left: 5px; + margin-left: 0px; margin-right: 5px; } .row > [class*="col-"] { From 1ae0d56a0f4ab9856a890c4c6f0e2ddc4edcff16 Mon Sep 17 00:00:00 2001 From: Jan Kees Date: Wed, 23 Jul 2025 16:05:49 +0200 Subject: [PATCH 13/17] adjusted heights of sidebar and body --- ui.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui.R b/ui.R index 151bd8b..5fa453d 100644 --- a/ui.R +++ b/ui.R @@ -80,6 +80,7 @@ ui <- fluidPage( sidebarLayout( sidebarPanel( width = 3, + style = "height: 90vh; overflow-y: auto;", # --- Collapsible About Box --- tags$head( tags$link(rel = "stylesheet", href = "https://fonts.googleapis.com/icon?family=Material+Icons"), @@ -218,6 +219,7 @@ ui <- fluidPage( mainPanel( width = 9, + style = "height: 90%; overflow-y: auto;", tags$head(tags$style( # Corrected escaping for the CSS content within HTML() # HTML(".sep { From c8dfc5878cbcd89c9447f8dc28347d3a35152e1e Mon Sep 17 00:00:00 2001 From: Jan Kees Date: Wed, 23 Jul 2025 16:24:47 +0200 Subject: [PATCH 14/17] placed buttons in footer --- ui.R | 43 +++++++++++++++++++++++++++---------------- www/style.css | 2 +- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/ui.R b/ui.R index 5fa453d..80c747e 100644 --- a/ui.R +++ b/ui.R @@ -80,7 +80,7 @@ ui <- fluidPage( sidebarLayout( sidebarPanel( width = 3, - style = "height: 90vh; overflow-y: auto;", + style = "height: 85vh; overflow-y: auto;", # --- Collapsible About Box --- tags$head( tags$link(rel = "stylesheet", href = "https://fonts.googleapis.com/icon?family=Material+Icons"), @@ -219,7 +219,7 @@ ui <- fluidPage( mainPanel( width = 9, - style = "height: 90%; overflow-y: auto;", + style = "height: 85%; overflow-y: auto;", tags$head(tags$style( # Corrected escaping for the CSS content within HTML() # HTML(".sep { @@ -330,20 +330,31 @@ ui <- fluidPage( plotlyOutput("combined_plot") ) ), - - # === Download Button Row === - fluidRow( - column( - 12, - div( - style = "margin-top: 20px;", - downloadButton("download_plotly", "Download activity pattern plot (.html)"), - downloadButton("download_csv", "Download Data (.csv)") - ) - ) - ) - ) - ), + ), + ), + # === Download Button Row === + tags$footer( + fluidRow( + column( + 12, + div( + style = "text-align: center;", + downloadButton("download_plotly", "Download activity pattern plot (.html)"), + downloadButton("download_csv", "Download Data (.csv)"), + style = paste( + "position: fixed;", + "bottom: 0;", + "left: 0;", + "width: 100%;", + "background-color: #fff;", + "padding: 10px;", + "box-shadow: 0 -2px 5px rgba(0,0,0,0.1);", + sep = " " + ) + ) + ) + ) + ), # tabPanel( # i18n$t("labels.rawConceptsTab"), # fluidRow(column( diff --git a/www/style.css b/www/style.css index b3602af..c3a514d 100644 --- a/www/style.css +++ b/www/style.css @@ -80,7 +80,7 @@ body { border-radius: 10px; /* rounded corners */ box-shadow: 0 2px 8px rgba(0,0,0,0.2); /* optional drop-shadow */ overflow: hidden; /* clip child content to rounded corners */ - height: 90vh; /* hoogte = xx% van viewport */ + height: 80vh; /* hoogte = xx% van viewport */ overflow-y: auto; /* scrollen als inhoud groter is */ } From 1ac5cf2bf11b07323c22d5f6317dbdabd01c99f9 Mon Sep 17 00:00:00 2001 From: Jan Kees Date: Wed, 23 Jul 2025 17:11:45 +0200 Subject: [PATCH 15/17] adjusted heights --- ui.R | 29 +++++++++++++++-------------- www/style.css | 2 +- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/ui.R b/ui.R index 80c747e..cabd37d 100644 --- a/ui.R +++ b/ui.R @@ -330,10 +330,10 @@ ui <- fluidPage( plotlyOutput("combined_plot") ) ), - ), - ), - # === Download Button Row === - tags$footer( + # ), + # ), + # # === Download Button Row === + # tags$footer( fluidRow( column( 12, @@ -341,19 +341,20 @@ ui <- fluidPage( style = "text-align: center;", downloadButton("download_plotly", "Download activity pattern plot (.html)"), downloadButton("download_csv", "Download Data (.csv)"), - style = paste( - "position: fixed;", - "bottom: 0;", - "left: 0;", - "width: 100%;", - "background-color: #fff;", - "padding: 10px;", - "box-shadow: 0 -2px 5px rgba(0,0,0,0.1);", - sep = " " - ) + # style = paste( + # "position: fixed;", + # "bottom: 0;", + # "left: 0;", + # "width: 100%;", + # "background-color: #fff;", + # "padding: 10px;", + # "box-shadow: 0 -2px 5px rgba(0,0,0,0.1);", + # sep = " " + # ) ) ) ) + ) ), # tabPanel( # i18n$t("labels.rawConceptsTab"), diff --git a/www/style.css b/www/style.css index c3a514d..e714dc9 100644 --- a/www/style.css +++ b/www/style.css @@ -80,7 +80,7 @@ body { border-radius: 10px; /* rounded corners */ box-shadow: 0 2px 8px rgba(0,0,0,0.2); /* optional drop-shadow */ overflow: hidden; /* clip child content to rounded corners */ - height: 80vh; /* hoogte = xx% van viewport */ + height: 85vh; /* hoogte = xx% van viewport */ overflow-y: auto; /* scrollen als inhoud groter is */ } From c4d6848055500726c26c24796f8c2bda39985d38 Mon Sep 17 00:00:00 2001 From: Jan Kees Date: Wed, 23 Jul 2025 17:38:30 +0200 Subject: [PATCH 16/17] gui optimisation --- ui.R | 111 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 54 insertions(+), 57 deletions(-) diff --git a/ui.R b/ui.R index cabd37d..aa95805 100644 --- a/ui.R +++ b/ui.R @@ -183,6 +183,7 @@ ui <- fluidPage( ), div(class = "filter-section data-sources-box", + style = "position: relative; z-index: 7000;", h4("Data Sources"), disabled(div( class = "choosechannel", @@ -204,6 +205,7 @@ ui <- fluidPage( br(), div(class = "filter-section concepts-box", + style = "position: relative; z-index: 5000;", h4("Concepts"), p("Select concepts (one or more)"), shinyTree("conceptTree", checkbox = TRUE, theme = "proton") @@ -330,69 +332,64 @@ ui <- fluidPage( plotlyOutput("combined_plot") ) ), - # ), - # ), - # # === Download Button Row === - # tags$footer( - fluidRow( - column( - 12, - div( - style = "text-align: center;", - downloadButton("download_plotly", "Download activity pattern plot (.html)"), - downloadButton("download_csv", "Download Data (.csv)"), - # style = paste( - # "position: fixed;", - # "bottom: 0;", - # "left: 0;", - # "width: 100%;", - # "background-color: #fff;", - # "padding: 10px;", - # "box-shadow: 0 -2px 5px rgba(0,0,0,0.1);", - # sep = " " - # ) - ) + fluidRow( + # style = paste( + # "position: fixed;", + # "bottom: 0;", + # "left: 0;", + # "width: 100%;", + # "background-color: #fff;", + # "padding: 10px;", + # "box-shadow: 0 -2px 5px rgba(0,0,0,0.1);", + # "text-align: center;", + # "z-index: 2000;", + # sep = " " + # ), + column( + 12, + downloadButton("download_plotly", "Download activity pattern plot (.html)"), + downloadButton("download_csv", "Download Data (.csv)") + ) + ) ) - ) - ) ), - # tabPanel( - # i18n$t("labels.rawConceptsTab"), - # fluidRow(column( - # 12, DT::dataTableOutput("tableRawConcepts") - # )), - # div( - # style = "position: fixed; top: 45%; left: 60%; transform: translate(-50%, -50%);", - # add_busy_spinner( - # spin = "fading-circle", - # width = "100px", - # height = "100px" - # ) - # ) - # ), - # - # endpanel - - tabPanel( - i18n$t("labels.rawData"), - br(), - column(2, br(), br(), downloadButton( - "downloadData", i18n$t("commands.download") - )), - fluidRow(column( - 12, DT::dataTableOutput("tableRawObservations") - )), - div( - style = "position: fixed; top: 45%; left: 60%; transform: translate(-50%, -50%);", - add_busy_spinner( - spin = "fading-circle", - width = "100px", - height = "100px" + # tabPanel( + # i18n$t("labels.rawConceptsTab"), + # fluidRow(column( + # 12, DT::dataTableOutput("tableRawConcepts") + # )), + # div( + # style = "position: fixed; top: 45%; left: 60%; transform: translate(-50%, -50%);", + # add_busy_spinner( + # spin = "fading-circle", + # width = "100px", + # height = "100px" + # ) + # ) + # ), + # + # endpanel + + tabPanel( + i18n$t("labels.rawData"), + br(), + column(2, br(), br(), downloadButton( + "downloadData", i18n$t("commands.download") + )), + fluidRow(column( + 12, DT::dataTableOutput("tableRawObservations") + )), + div( + style = "position: fixed; top: 45%; left: 60%; transform: translate(-50%, -50%);", + add_busy_spinner( + spin = "fading-circle", + width = "100px", + height = "100px" + ) ) ) ) ) ) - ) ) ) \ No newline at end of file From 4a05b1ac161e2294a2dde9e7880ff73d48384a69 Mon Sep 17 00:00:00 2001 From: Jan Kees Date: Fri, 25 Jul 2025 03:48:35 +0200 Subject: [PATCH 17/17] switched sensingcluesr code --- server.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.R b/server.R index 9986be7..a57e42b 100644 --- a/server.R +++ b/server.R @@ -24,8 +24,8 @@ library(promises) # load the sensincluesr package library(devtools) -#devtools::install_github("sensingclues/sensingcluesr@v1.0.3", upgrade = "never") -library(sensingcluesr) +devtools::install_github("sensingclues/sensingcluesr@v1.0.3", upgrade = "never") +#library(sensingcluesr) # dynamic color maps for more then 12 colors library(colorRamps)