diff --git a/NEWS.md b/NEWS.md index fb97997..8bef40e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,6 +8,38 @@ editor_options: ## TO DO +1 - use reactive instead of reactiveValues for datasets + +## 2025.01.03-1 + +### Bug Fixes + +1 - Data Overview - after Edit only refresh if updat in rows or sample: fixed with insertion of buttons inside output$pD_over_gt. Avaliate use of reactive instead of reactiveValues for datasets. + +### Improvements + +1 - **export_file_module**: separator order now semicolon as default + +2 - new **import_file_module**: allows input csv and RDS files + +3 - **page_config_module**: new visual and size of input file as parameter + +4 - **spada function**: + +* sidebar now with Dataset Info accordion open by default + +* small visual changes (icons and capital in some titles) + +* **shiny.maxRequestSize** set to 500 MB by default + +* **datasets_names_react**: now names of the datasets are a reactive (used several times) + +* new **buttons in sidebar** accordion to navigate through pages + +* new **buttons in active dataset popover** navigate through pages + +### Bug Fixes + ## 2025.01.02-1 ### Bug Fixes diff --git a/R/export_file_module.R b/R/export_file_module.R index debbf40..3688723 100644 --- a/R/export_file_module.R +++ b/R/export_file_module.R @@ -17,20 +17,20 @@ export_file_ui <- function(id) { checkboxInput(ns('x_rownames'), 'Save row names'), fluidRow( column(3, radioButtons(ns('radio_separator'), 'Separator', - c('Comma' = ',', 'Semicolon' = ';'), inline = T)), - column(3, radioButtons(ns('radio_decimal'), 'Decimal Mark', + c('Semicolon' = ';', 'Comma' = ','), inline = T)), + column(3, radioButtons(ns('radio_decimal'), 'Decimal mark', c('Dot' = '.', 'Comma' = ','), inline = T)) ), fluidRow( column(3, textInput(ns('txt_na'), 'Missing (NA) substitute', value = '')), - column(3, radioButtons(ns('radio_scientific'), 'Scientific Notation', + column(3, radioButtons(ns('radio_scientific'), 'Scientific notation', c('No' = 999999999, 'Allow' = 0), inline = T)) ) ) ) ), card_footer(downloadButton(ns('down_handler'), - 'Export Active Dataset', icon('download'))) + 'Export Active dataset', icon('download'))) ) ) } diff --git a/R/import_file_module.R b/R/import_file_module.R new file mode 100644 index 0000000..d447326 --- /dev/null +++ b/R/import_file_module.R @@ -0,0 +1,125 @@ + +# ui -------------------------------------------------------------------------- +import_file_ui <- function(id) { + file_extensions <- c('csv', 'RDS') + ns <- NS(id) + card( + card_body( + fluidRow( + column(3, textInput(ns('dataset_name'), 'Dataset name', 'dataset')), + column(3, radioButtons(ns('radio_file_ext'), 'File format', + file_extensions, inline = T)), + column(3, fileInput(ns('file'), 'Choose a File')), + ), + conditionalPanel( + condition = sprintf("input['%s'] == 'csv'", ns('radio_file_ext')), + card( + card_header('Csv Parameters', class = 'mini-header'), + card_body( + layout_column_wrap( + checkboxInput(ns('x_csv_header'), 'Header', T), + radioButtons(ns('radio_separator'), 'Separator', + c('Semicolon' = ';', 'Comma' = ','), inline = T), + radioButtons(ns('radio_decimal'), 'Decimal Mark', + c('Dot' = '.', 'Comma' = ','), inline = T), + btn_task(ns('btn_preview_raw'), 'Preview raw file', icon('magnifying-glass')), + ), + ) + ) + ), + ), + card_footer( + btn_task(ns('btn_import'), 'Import file', icon('upload')), + ) + ) +} + +# server ---------------------------------------------------------------------- +import_file_server <- function(id, current_names) { + moduleServer(id, function(input, output, session) { + + data <- reactiveValues(data = NULL, data_name = NULL) + + observe({ + if(is.null(input$file)){ + msg_error('Insert a file') + } else if(!is_valid_name(input$dataset_name) || input$dataset_name %in% current_names){ + msg_error('Name invalid or already in use') + } else { + file <- input$file + ext <- tools::file_ext(file$datapath) + + if (ext == 'csv' && input$radio_file_ext == 'csv') { + + tryCatch( + { data$data <- fread( + file = file$datapath, + sep = input$radio_separator, + dec = input$radio_decimal, + check.names = T, + nrows = Inf, + skip = 0, + header = input$x_csv_header) + + data$data_name <- input$dataset_name + + msg('File imported') + }, + warning = \(w) msg_error('Check parameters'), + error = \(e) msg_error('Check parameters') + ) + + } else if (ext == 'RDS' && input$radio_file_ext == 'RDS') { + + data_temp <- readRDS(file$datapath) + + if(data_temp |> is.data.frame()){ + data$data <- data_temp + data$data_name <- input$dataset_name + + msg('File imported') + } else { + msg_error('Object must be data.frame') + } + } else { + msg_error(paste('Insert a', input$radio_file_ext, 'file')) + } + } + + }) |> bindEvent(input$btn_import) + + observe({ + req(input$file) + + file <- input$file + ext <- tools::file_ext(file$datapath) + + if (ext == 'csv' && input$radio_file_ext == 'csv') { + preview_data <- readLines(file$datapath, n = 8) + + showModal(modalDialog( + size = 'xl', + title = 'Preview Raw File', + HTML(paste(preview_data, collapse = '
')) + )) + + } else { + msg_error(paste('Insert a', input$radio_file_ext, 'file')) + } + }) |> bindEvent(input$btn_preview_raw) + + data_imported <- reactive({ + req(data$data) + req(data$data_name) + req(input$dataset_name) + list('data' = data$data, + 'data_name' = data$data_name, + # return the click integer for update the value outside the module + # even if the name and file was used before + 'btn_click' = input$btn_import) + }) + + return(list(data_imported = data_imported)) + + }) +} diff --git a/R/page_config_module.R b/R/page_config_module.R index e3fbc6f..d0155b7 100644 --- a/R/page_config_module.R +++ b/R/page_config_module.R @@ -6,9 +6,9 @@ page_config_ui <- function(id) { value = 'config', title = 'Config', icon = bs_icon('sliders2'), - card(style = 'background-color: #02517d;', card( - card_body( - h2('Config'), + card(style = 'background-color: #02517d;', layout_columns( + col_widths = c(9, 3), card(card_body( + h4('Colors'), selectInput( ns('sel_palette'), 'Select colors for plots', @@ -18,12 +18,15 @@ page_config_ui <- function(id) { 'Palette 3' = 3 ) ), - fluidRow(column(3, plotOutput( - ns('sample_plot') - ))) - ) + plotOutput(ns('sample_plot')) + )), card(card_body( + h4('Size input files'), + numericInput(ns('input_file_size'), 'Size in MB', 500, min = 0, step = 100), + btn_task(ns('btn_file_size'), 'Apply', icon('check')) + )) )) ) + } # server ---------------------------------------------------------------------- @@ -58,6 +61,12 @@ page_config_server <- function(id) { }) |> bindCache(palette()) + observe({ + options(shiny.maxRequestSize = input$input_file_size * 1024 ^ 2) + msg('New limit applied') + + }) |> bindEvent(input$btn_file_size) + return(list(palette = palette)) }) } diff --git a/R/spada.R b/R/spada.R index 5ff4dec..d4dcc59 100644 --- a/R/spada.R +++ b/R/spada.R @@ -75,7 +75,7 @@ spada <- function(...) { sidebar = sidebar( open = F, accordion( - open = F, + open = T, accordion_panel( style = "background-color: #02517d; color: white;", 'Dataset Info', @@ -172,7 +172,7 @@ spada <- function(...) { card_body( gt_output('pD_over_gt')), fluidRow( - column(2, numericInput('pD_over_size_sample', 'Number of Rows', 100, 100, 1e4, 100)), + column(2, numericInput('pD_over_size_sample', 'Number of rows', 100, 100, 1e4, 100)), column(2, radioButtons( 'pD_over_radio_sample', 'Show', c('First rows' = 'first', 'Sample' = 'sample'), inline = T)) @@ -182,7 +182,7 @@ spada <- function(...) { 'Data', card_body( uiOutput('pD_data_ui_sel_df'), - textInput('pD_data_txt_new_name', 'New Name'), + textInput('pD_data_txt_new_name', 'New name'), layout_column_wrap( btn_task('pD_data_btn_new_name', 'Rename dataset', icon('file-signature')), btn_task('pD_data_btn_active', 'Make dataset Active', icon('check')), @@ -190,7 +190,9 @@ spada <- function(...) { btn_task('pD_data_btn_delete_dataset', 'Delete dataset', icon('trash-can')), ), )), + nav_panel('Import', import_file_ui('pD_import')), nav_panel('Export', export_file_ui('pD_export')), + ) ) ) @@ -263,7 +265,7 @@ spada <- function(...) { ), card_footer( btn_task('pE_convert_btn_apply', 'Apply conversion', - icon('hammer')) + icon('check')) ) ), card( @@ -284,7 +286,7 @@ spada <- function(...) { uiOutput('pE_order_ui_var_rows'), selectInput('pE_order_vars_descending', 'Vars in descending order', '', multiple = T) |> tooltip('If not informed, the order will be Ascending', placement = 'right'), - radioButtons('pE_order_radio_nas', "NA's Placement", + radioButtons('pE_order_radio_nas', "NA's placement", c('Last' = 'last', 'First' = 'first'), inline = T), ), @@ -468,8 +470,11 @@ spada <- function(...) { ### ===================================================================== ### server <- function(input, output, session) { + options(shiny.maxRequestSize = 500 * 1024 ^ 2) + # data -------------------------------------------------------------------- datasets_react <- reactiveValues(data = lapply(datasets, setDT)) + datasets_names_react <- reactive(names(datasets_react$data)) # inicialize with the first dataset informed df <- reactiveValues( @@ -500,18 +505,36 @@ spada <- function(...) { p("Columns with NA's:", sum(colSums(is.na(df$df_active)) > 0)), p('Size (MB):', (object.size(df$df_active) / 2^20) |> as.numeric() |> round(2)), - input_task_button('navbar_df_btn_change', 'Change', + input_task_button('navbar_df_btn_overview', '', + icon = bs_icon('search'), + class = 'btn-task', + style = 'padding: 5px 10px;'), + input_task_button('navbar_df_btn_change', '', icon = bs_icon('shuffle'), class = 'btn-task', - style = 'padding: 5px 10px;') + style = 'padding: 5px 10px;'), + input_task_button('navbar_df_btn_explore', '', + icon = bs_icon('bar-chart-line'), + class = 'btn-task', + style = 'padding: 5px 10px;'), ) }) + observe({ + nav_select('navbar', selected = 'Data') + nav_select('navset_card_pill_data', selected = 'Overview') + }) |> bindEvent(input$navbar_df_btn_overview) + observe({ nav_select('navbar', selected = 'Data') nav_select('navset_card_pill_data', selected = 'Data') }) |> bindEvent(input$navbar_df_btn_change) + observe({ + nav_select('navbar', selected = 'Analysis') + nav_select('navbar', selected = 'Exploratory') + }) |> bindEvent(input$navbar_df_btn_explore) + # side bar -------------------------------------------------------- output$sidebar_df_info <- renderUI({ tagList( @@ -523,19 +546,41 @@ spada <- function(...) { p("Columns with NA's:", sum(colSums(is.na(df$df_active)) > 0)), p('Size (MB):', (object.size(df$df_active) / 2^20) |> as.numeric() |> round(2)), - input_task_button('sidebar_df_btn_change', 'Change', - icon = bs_icon('shuffle'), - class = 'btn-task', - style = 'padding: 5px 10px;') + fluidRow( + column(1), + column(2, input_task_button('sidebar_df_btn_overview', '', + icon = bs_icon('search'), + class = 'btn-task', + style = 'padding: 5px 10px;') |> + tooltip('Overview', placement = 'bottom')), + column(2, input_task_button('sidebar_df_btn_change', '', + icon = bs_icon('shuffle'), + class = 'btn-task', + style = 'padding: 5px 10px;') |> + tooltip('Change dataset', placement = 'bottom')), + column(2, input_task_button('sidebar_df_btn_explore', '', + icon = bs_icon('bar-chart-line'), + class = 'btn-task', + style = 'padding: 5px 10px;') |> + tooltip('Exploratory Analysis', placement = 'bottom')), + ) ) - }) + observe({ + nav_select('navbar', selected = 'Data') + nav_select('navset_card_pill_data', selected = 'Overview') + }) |> bindEvent(input$sidebar_df_btn_overview) + observe({ nav_select('navbar', selected = 'Data') nav_select('navset_card_pill_data', selected = 'Data') }) |> bindEvent(input$sidebar_df_btn_change) + observe({ + nav_select('navbar', selected = 'Analysis') + nav_select('navbar', selected = 'Exploratory') + }) |> bindEvent(input$sidebar_df_btn_explore) # data page events ----------------------------------------------------- df_metadata <- reactive({ @@ -551,6 +596,10 @@ spada <- function(...) { output$pD_over_gt <- render_gt( { req(input$pD_over_size_sample) + + input$pE_convert_btn_apply + input$pE_order_btn_order_rows + input$pE_order_btn_order_cols pD_over_n_show <- max(1, input$pD_over_size_sample) pD_over_n_show <- min(pD_over_n_show, nrow(df$df_active)) @@ -645,7 +694,7 @@ spada <- function(...) { # define active dataset --------------------------------------------------- output$pD_data_ui_sel_df <- renderUI( - selectInput('pD_data_sel_df', 'Select a dataset', names(datasets_react$data)) + selectInput('pD_data_sel_df', 'Select a dataset', datasets_names_react()) ) observe({ @@ -663,11 +712,11 @@ spada <- function(...) { observe({ if(!is_valid_name(input$pD_data_txt_new_name) | - input$pD_data_txt_new_name %in% names(datasets_react$data)){ + input$pD_data_txt_new_name %in% datasets_names_react()){ msg_error('New name is not valid or already in use') } else { - names(datasets_react$data)[names(datasets_react$data) == input$pD_data_sel_df] <- input$pD_data_txt_new_name + names(datasets_react$data)[datasets_names_react() == input$pD_data_sel_df] <- input$pD_data_txt_new_name # update active dataset if necessary if(df$df_active_name == input$pD_data_sel_df){ df$df_active <- datasets_react$data[[input$pD_data_txt_new_name]] @@ -681,7 +730,7 @@ spada <- function(...) { observe({ if(!is_valid_name(input$pD_data_txt_new_name) || - (input$pD_data_txt_new_name %in% names(datasets_react$data))){ + (input$pD_data_txt_new_name %in% datasets_names_react())){ msg_error('Name invalid or already in use') } else { datasets_react$data[[ input$pD_data_txt_new_name ]] <- df$df_active @@ -701,6 +750,16 @@ spada <- function(...) { # export ---------------------------------------------------- export_file_server('pD_export', reactive(df$df_active)) + # import ---------------------------------------------------- + mod_pD_import <- import_file_server('pD_import', datasets_names_react()) + + observe({ + req(mod_pD_import$data_imported()) + + datasets_react$data[[mod_pD_import$data_imported()[['data_name']]]] <- mod_pD_import$data_imported()[['data']] + + }) |> bindEvent(mod_pD_import$data_imported()) + # edit page events -------------------------------------------------------- # filter events --------------------------- @@ -1379,6 +1438,7 @@ spada <- function(...) { stopApp() } }) + } # end of server function ### Run App ----------------------------------------------------------------- diff --git a/docs/news/index.html b/docs/news/index.html index e8efc8e..8e1494d 100644 --- a/docs/news/index.html +++ b/docs/news/index.html @@ -51,16 +51,40 @@

Changelog

TO DO

+

1 - use reactive instead of reactiveValues for datasets

-

2025.01.02-1

+

2025.01.03-1

Bug Fixes

+

1 - Data Overview - after Edit only refresh if updat in rows or sample: fixed with insertion of buttons inside output$pD_over_gt. Avaliate use of reactive instead of reactiveValues for datasets.

+
+
+

Improvements

+

1 - export_file_module: separator order now semicolon as default

+

2 - new import_file_module: allows input csv and RDS files

+

3 - page_config_module: new visual and size of input file as parameter

+

4 - spada function:

+
+
+

Bug Fixes

+
+
+
+

2025.01.02-1

+
+

Bug Fixes

1 - Analysis page - q1 object not found: back to calculate q1 and q3. (#1)

2 - Metadata - Error in zeros count: now df_info function uses suna(x == 0) instead of length(x[x == 0]) (#2)

-

Improvements

+

Improvements

1 - utils functions