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 @@
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.
+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
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)
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
1 - General
Created zzz.R and inserted utils::globalVariables for global variables (check note)
Value boxes: resized to give more space for other elements
1 - ‘Edit’ Page > Convert
1 - utils functions
1 - New functionality: copy dataset (Data page)
2 - Config page now is a module
3 - New Some reactives now with bindCache
1 - gt cannot show complex in opt_interactive, now convert to char before apply gt function
1 - utils functions
change color in value boxes (to gray) for better looking
number of rows (value box) now with decimals
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