From 0b9c3a0a32b37c8279f2ed53a7263746dc846a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Gustavo=20Schuck?= Date: Fri, 3 Jan 2025 18:37:39 -0300 Subject: [PATCH] News 2025.01.03-1 --- NEWS.md | 32 +++++++++++ R/export_file_module.R | 8 +-- R/import_file_module.R | 125 +++++++++++++++++++++++++++++++++++++++++ R/page_config_module.R | 23 +++++--- R/spada.R | 92 ++++++++++++++++++++++++------ docs/news/index.html | 42 +++++++++++--- docs/pkgdown.yml | 2 +- 7 files changed, 287 insertions(+), 37 deletions(-) create mode 100644 R/import_file_module.R 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:

+
  • 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

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

  • df_info now uses suna instead of length, this change fix errors and provide gain in speed.

  • deletion of format_color_bar and main_value_box functions given that they are now in use anymore

  • @@ -78,7 +102,7 @@

    Improvements

    2024.12.30-1

    -

    Improvements

    +

    Improvements

    1 - General

    • Created zzz.R and inserted utils::globalVariables for global variables (check note)

    • Value boxes: resized to give more space for other elements

    • @@ -93,12 +117,12 @@

      Improvements

      2024.12.29-2

      -

      Bug Fixes

      +

      Bug Fixes

      1 - ‘Edit’ Page > Convert

      • on changing the active dataset the selected variable may not exist in the new acive dataset and an error is raised from data.table (variable does not exist). Fix with if clause testing for presence of var in the active dataset.
      -

      Improvements

      +

      Improvements

      1 - utils functions

      • dt_info: now return number and percentage of zeros for each variable
      • @@ -123,17 +147,17 @@

        Improvements

        2024.12.29-1

        -

        Improvements

        +

        Improvements

        1 - New functionality: copy dataset (Data page)

        2 - Config page now is a module

        3 - New Some reactives now with bindCache

        -

        Bug Fixes

        +

        Bug Fixes

        1 - gt cannot show complex in opt_interactive, now convert to char before apply gt function

        -

        Improvements

        +

        Improvements

        1 - utils functions

        • change color in value boxes (to gray) for better looking

        • number of rows (value box) now with decimals

        • @@ -148,7 +172,7 @@

          Improvements

          2024.12.27-1

          -

          Improvements

          +

          Improvements

          1 - utils functions

          • df_info: improvement in performance (something like half the time in big datasets - 1e6 rows)

          • new function: gt_info to generate metadata with gt package

          • diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index 9ca46f7..3da2e22 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -2,4 +2,4 @@ pandoc: 3.1.11 pkgdown: 2.1.1 pkgdown_sha: ~ articles: {} -last_built: 2025-01-03T02:53Z +last_built: 2025-01-03T21:36Z