library(knitr) opts_chunk$set( echo = TRUE, # collapse = TRUE, # cache = TRUE, out.width = "75%", fig.align = 'center', fig.width = 6, fig.show = "hold" ) options(dplyr.print_min = 6, dplyr.print_max = 6) # Supress crayon output options(crayon.enabled = FALSE) # Useful libraries library(glue) library(patchwork) library(latex2exp) library(kableExtra) # For nice dataframe summaries library(summarytools) st_options( plain.ascii = FALSE, dfSummary.varnumbers = FALSE, dfSummary.style = 'grid', dfSummary.graph.magnif = .75 ) # Tidy! library(tidyverse) # Sober theme for ggplot theme_set( theme_linedraw() + # Set simple theme for ggplot theme( # with some tweaks axis.title.y.left = element_text( angle = 0, # Never rotate y axis title margin = margin(r = 20), # Separate y axis title a little vjust = .5 # Leave y axis title in the middle ), axis.title.y.right = element_text( angle = 0, # Never rotate y axis title margin = margin(l = 20), # Separate y axis title a little vjust = .5 # Leave y axis title in the middle ), axis.ticks.y.right = element_blank(), # No ticks on secondary y axis axis.title.x.bottom = element_text( margin = margin(t = 20) # Separate x axis title a little ), axis.line = element_blank(), # No axis lines panel.border = element_blank(), # No frame panel.grid.minor = element_blank() # No grid minor lines ) ) # Avoid scientific notation and use a comma as decimal separator options( scipen = 15, OutDec = ',' ) # Format a number with thousand separators (default point) # and decimal comma enclosed in curly braces for LaTeX printing. # CAREFUL: if called outside math mode, will print the braces! fm <- function(x, big = '.', decimal = '{,}', ...) { if (!is.numeric(x)) { x } else { prettyNum(x, big.mark = big, decimal.mark = decimal, ...) } } # Set this as a hook for inline R code knitr::knit_hooks$set(inline = fm) # To center the results of a chunk (image, video etc.) # Usage: # out.extra=center() # center <- function(){ if (is_html_output()) { 'class="center"' } } # To embed YT videos in HTML and the link (centered) in LaTeX embed_yt <- function(code) { if (is_html_output()) { include_url( paste0( 'https://www.youtube.com/embed/', code ) ) } else { cat( paste0( '```{=latex}\n', '\\begin{center} \\url{https://youtu.be/', code, '} \\end{center}\n', '```' ) ) } } library(lubridate) library(shiny) library(gt)
```{js javascript-init, echo=FALSE}
// Make off-site links open in a new window/tab function changeTargets() { $("a").attr( "target", function() { // Load local links locally if (this.host == location.host) return "_self" // Load off-site links in a new window else return "_blank"; } ); }
// Execute when document is ready $( changeTargets )
# To do * Continue coding functions in this Rmd * One function per file * Document * Test # Read holidays file Using absolute path only for this Rmd. ```r read_holidays_file <- function() { # readr::read_file( # '/home/fnaufel/Development/00-Present/PlanneR/inst/extdata/holidays.csv' # ) readr::read_file( system.file('extdata', 'holidays.csv', package = 'PlanneR', mustWork = TRUE) ) }
read_holidays_file()
Only convert from text to tibble.
load_holidays <- function(text) { # If text is empty, return NULL if (str_trim(text) == '') { return(NULL) } tryCatch( { df <- readr::read_csv2( text, col_names = c('name', 'from', 'to'), col_types = 'ccc', locale = readr::locale('pt'), quoted_na = FALSE, trim_ws = TRUE ) %>% dplyr::mutate( name = stringr::str_squish(name), from = lubridate::as_date(from, format = '%d/%m/%y'), to = lubridate::as_date(to, format = '%d/%m/%y') ) }, error = function(e) { error_modal('Lista de feriados tem um problema. Verifique.') req(FALSE) return(NULL) } ) # If a from date is missing, error if (any(is.na(df$from))) { error_modal('Um ou mais feriados não tem data. Verifique.') req(FALSE) return(NULL) } df }
df <- read_holidays_file() %>% load_holidays()
df
'Feriado 1; 1/1/2021; 2/1/2021; 3/1/2021 Feriado 2; 4/1/2021' %>% load_holidays()
' Feriado 1; 1/1/2021; 2/1/2021; Feriado 2; ' %>% load_holidays()
' Feriado 1; 1/1/2021; 2/1/2021; Feriado 2; abc; Feriado 3; 123; ' %>% load_holidays()
expand_holidays <- function(df) { # Return NULL if df is empty if (is.null(df)) { return(NULL) } # Save rows that represent single-day holidays (minus `to` column) df1 <- df %>% dplyr::filter(is.na(to)) %>% dplyr::select(-to) %>% dplyr::rename(date = from) # Save rows that represent multiple-day holidays df2 <- df %>% dplyr::filter(!is.na(to)) # If there are no multiple-day holidays, return original df if (nrow(df2) == 0) { return(df1) } # Create list column with vector of dates for each holiday # and unnest this list column expanded_df <- df2 %>% dplyr::mutate( expanded = purrr::pmap(., ~ lubridate::as_date(..2:..3)) ) %>% dplyr::select(-from, -to) %>% tidyr::unnest(cols = expanded) %>% dplyr::rename(date = expanded) # Eliminate rows whose date already appear in df1 expanded_df <- expanded_df %>% dplyr::filter(!(date %in% df1$date)) # Returned merged df df1 %>% rbind(expanded_df) %>% dplyr::arrange(date) }
validate_all <- function( dates, days ) { # Start or end dates must not be NA if (is.null(dates) || any(is.na(dates))) { error_modal('Preencha datas de início e de fim do curso.') return(FALSE) } # End must come before start if (dates[1] >= dates[2]) { error_modal('Data de início deve ser anterior à data de fim.') return(FALSE) } # Course must not last more than a year if (dates[1] %--% dates[2] > years(2) ) { error_modal( 'Curso não pode durar mais de 2 anos. Verifique as datas de início e fim.' ) return(FALSE) } # At least one weekday must be chosen if (is.null(days)) { error_modal('Marque pelo menos um dia da semana.') return(FALSE) } TRUE }
start <- lubridate::dmy('14/06/2021') end <- lubridate::dmy('25/09/2021') days <- c(2, 4, 6) dates <- c(start, end) validate_all(dates, days)
# This function assumes everything is ok with the start, end, and days build_plan <- function(start, end, days, wday_names, holidays, topics) { # Generate ALL dates between start and end all_dates <- lubridate::as_date(start:end) # Create data frame (date, weekday name) and filter for class days df <- tibble(class_date = all_dates) %>% mutate(weekday = lubridate::wday(class_date)) %>% filter(weekday %in% days) %>% mutate(weekday = wday_names[weekday]) if (!is.null(holidays)) { # If holidays df is not empty, join with holidays df to fill in holiday # names on the right dates. I use distinct(class_date) because one date # may be associated with more than one holiday, and I want such a date to # appear only once. df <- df %>% left_join(holidays, by = c('class_date' = 'date')) %>% rename(contents = name) %>% distinct(class_date, .keep_all = TRUE) } else { # If holidays df is empty, just add an empty contents column df <- df %>% mutate(contents = NA_character_) } # Fill in class numbers # Number of available class days n_classes <- nrow( df %>% filter(is.na(contents)) ) # Class numbers numbers <- 1:n_classes # Add class numbers only to non holidays (contents is NA) df <- df %>% filter(is.na(contents)) %>% mutate(class_no = numbers) %>% rbind( df %>% filter(!is.na(contents)) %>% mutate(class_no = NA_integer_) ) %>% arrange(class_date) %>% select(class_date, weekday, class_no, contents) # If no topics yet, return if (is.null(topics)) { return(df) } # n_classes is number of available class days # n_topics is number of topics n_topics <- length(topics) if (n_classes > n_topics) { # Add empty topics difference <- n_classes - n_topics topics <- c(topics, rep(NA, difference)) } else if (n_classes < n_topics) { # truncate topics and emit warning topics <- topics[1:n_classes] difference <- n_topics - n_classes warning_modal( paste0( 'Aulas (', n_classes, ') < Tópicos (', n_topics, '). ', difference, ' tópicos não foram incluídos.' ) ) } # Fill in topics df <- df %>% filter(is.na(contents)) %>% mutate(contents = topics) %>% rbind( df %>% filter(!is.na(contents)) ) %>% arrange(class_date) # Return df }
start <- lubridate::dmy('14/06/2021') end <- lubridate::dmy('25/09/2021') days <- c(2, 4, 6) wday_names <- c( 'DOM', 'SEG', 'TER', 'QUA', 'QUI', 'SEX', 'SAB' ) topics <- paste('Tópico', 1:29) holidays <- read_holidays_file() %>% load_holidays() %>% expand_holidays() plan <- build_plan(start, end, days, wday_names, holidays, topics) plan
build_gt_table <- function(plan) { plan %>% mutate( class_date = strftime(class_date, format = '%d/%m/%y') ) %>% gt() %>% fmt_missing( columns = everything(), missing_text = '' ) %>% cols_label( class_date = 'Data', weekday = 'Dia', class_no = 'Aula', contents = 'Conteúdo' ) %>% cols_align( 'right', columns = c('class_no', 'class_date') ) %>% tab_style( style = list( cell_text(weight = 'bold'), cell_fill() ), locations = cells_column_labels( columns = everything() ) ) %>% tab_style( style = cell_text(size = 'large'), locations = list( cells_column_labels( columns = everything() ), cells_body() ) ) %>% tab_style( style = cell_text(indent = px(15)), locations = list( cells_column_labels(columns = c(weekday, contents)), cells_body(columns = c(weekday, contents)) ) ) %>% tab_style( style = cell_text(indent = px(5)), locations = list( cells_column_labels(columns = c(class_no)), cells_body(columns = c(class_no)) ) ) %>% tab_style( style = cell_text( style = 'italic', color = '#777777' ), locations = cells_body( columns = 'contents', rows = is.na(class_no) ) ) %>% tab_source_note( md( paste0( 'Gerado por [planneR](https://fnaufel.shinyapps.io/planner/): ', 'https://fnaufel.shinyapps.io/planner/. ', '\n', 'Desenvolvido por [fnaufel](https://fnaufel.github.io/site), ', 'com [R](https://cran.r-project.org/), ', '[Shiny](https://shiny.rstudio.com/), ', 'e o pacote [gt.](https://gt.rstudio.com/) ', '\n', '[Licença Creative Commons BY-NC-SA.]', '(https://creativecommons.org/licenses/by-nc-sa/4.0/deed.pt_BR) ', '\n', '[]', '(https://creativecommons.org/licenses/by-nc-sa/4.0/deed.pt_BR)' ) ) ) }
plan_table <- build_gt_table(plan) plan_table
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.