GithubHelp home page GithubHelp logo

ggtimebox's Introduction

Let’s build a vanilla ggplot2 Stat and use it in an extension function.

tasks_df <- tibble::tribble(~my_task, ~time_minutes,
        "eat breakfast", 15,
        "brush teeth", 3,
        "run", 30,
        "draft email", 5
        )

Part 1. Work on functionality

Step 0. Do it with base ggplot2 can become problem statement later

library(tidyverse)
#> ── Attaching core tidyverse packages ─────────────────── tidyverse 2.0.0.9000 ──
#> βœ” dplyr     1.1.0          βœ” readr     2.1.4     
#> βœ” forcats   1.0.0          βœ” stringr   1.5.0     
#> βœ” ggplot2   3.4.4.9000     βœ” tibble    3.2.1     
#> βœ” lubridate 1.9.2          βœ” tidyr     1.3.0     
#> βœ” purrr     1.0.1          
#> ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
#> βœ– dplyr::filter() masks stats::filter()
#> βœ– dplyr::lag()    masks stats::lag()
#> β„Ή Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

Step 0.b Write like-to-have code code chunk option eval = F

ggplot(data = cars) + 
  aes(speed, dist) + 
  geom_point() + 
  geom_pointmeans(size = 8)

Step 1. Write compute group function and test

Step 2. Pass to ggproto object

reference: https://evamaerey.github.io/mytidytuesday/2022-01-03-easy-geom-recipes/easy_geom_recipes.html

readme2pkg::chunk_to_r("compute_panel_timebox")
compute_panel_timebox <- function(data, scales, break_time = 3, start_hour = 9){
  
  start_time_scalar <- start_hour * 60
  
  data |> 
    dplyr::mutate(full_time = .data$minutes + break_time) |>
    dplyr::mutate(end_time_minutes = cumsum(.data$full_time)) |>
    dplyr::mutate(start_time_minutes = lag(.data$end_time_minutes) |>
                    tidyr::replace_na(0)) |>
    dplyr::mutate(clock_start = Sys.Date() + 
             minutes(start_time_minutes) + hours(start_hour)) |>
    dplyr::mutate(clock_end = Sys.Date() + 
             minutes(end_time_minutes) + hours(start_hour)) |>
    dplyr::mutate(y = start_time_minutes) |>
    dplyr::mutate(x = 0) |>
    dplyr::mutate(ymin = start_time_minutes,
           ymax = end_time_minutes) |>
    dplyr::mutate(xmin = 0,
           xmax = 1) |>
    dplyr::mutate(task_and_minutes = paste(task, minutes)) |>
    dplyr::mutate(clock_hour_minute = 
                    hms::as_hms(clock_start) %>% stringr::str_remove("...$"))
  
}

StatTimebox <- ggplot2::ggproto(`_class` = "StatTimebox",
                     `_inherit` = ggplot2::Stat,
                     compute_panel = compute_panel_timebox,
                     required_aes = c("task", "minutes"),
                     default_aes = ggplot2::aes(label =
                                          ggplot2::after_stat(task_and_minutes)))
readme2pkg::chunk_to_r("stamp_workday")
stamp_workday <- function(...){
  annotate(geom = "rect", 
           ymin = 0*60, ymax = -8*60, 
           xmin = -.45, xmax = .45, ... )
}
readme2pkg::chunk_to_r("stamp_currenttime")
stamp_currenttime <- function(..., color = "magenta", alpha = .5, start_hour = 9){
  
  current_time <- (Sys.time() |> hms::as_hms() |> as.numeric()) / 60 - start_hour*60
  
  ggplot2::geom_hline(yintercept = current_time, color = color, alpha = alpha, ...)
  
  }
tasks_df |>
  select(task = my_task, minutes = time_minutes) |>
  compute_panel_timebox()
#> # A tibble: 4 Γ— 15
#>   task          minutes full_time end_time_minutes start_time_minutes
#>   <chr>           <dbl>     <dbl>            <dbl>              <dbl>
#> 1 eat breakfast      15        18               18                  0
#> 2 brush teeth         3         6               24                 18
#> 3 run                30        33               57                 24
#> 4 draft email         5         8               65                 57
#> # β„Ή 10 more variables: clock_start <dttm>, clock_end <dttm>, y <dbl>, x <dbl>,
#> #   ymin <dbl>, ymax <dbl>, xmin <dbl>, xmax <dbl>, task_and_minutes <chr>,
#> #   clock_hour_minute <chr>
ggplot(data = tasks_df) + 
  aes(task = my_task, minutes = time_minutes) + 
  layer(stat = StatTimebox, 
        geom = GeomRect, 
        position = "identity") + 
  layer(stat = StatTimebox, 
        geom = GeomText, 
        position = "identity",
        params = list(vjust = 1.1, hjust = -.1)) + 
  coord_trans(y = "reverse")

Step 3. Write user-facing geom_pointmeans() function

readme2pkg::chunk_to_r("stat_timebox")
stat_timebox <- function(mapping = NULL, 
                         data = NULL,
                         geom = GeomRect,
                         position = "identity", 
                         na.rm = FALSE,
                            show.legend = NA,
                            inherit.aes = TRUE, ...) {
  ggplot2::layer(
    stat = StatTimebox,        # proto object from step 2
    geom = geom,   # inherit other behavior
    data = data, 
    mapping = mapping,
    position = position, 
    show.legend = show.legend, 
    inherit.aes = inherit.aes,
    params = list(na.rm = na.rm, ...)
  )
}
readme2pkg::chunk_to_r("coord_transfixed")
CoordTransfixed <- ggplot2::ggproto(`_class` = "CoordTransfixed", 
                       `_inherit` = ggplot2::CoordTrans)

CoordTransfixed$aspect <- function (self, ranges){  # inner function of CoordFixed$aspect
    diff(ranges$y.range)/diff(ranges$x.range) * self$ratio
}

# borrowing from coord_trans set up and adding ratio argument
coord_transfixed <- function (x = "identity", y = "identity", xlim = NULL, ylim = NULL, 
    limx = lifecycle::deprecated(), limy = lifecycle::deprecated(), clip = "on", expand = TRUE, ratio = 1) 
{
    if (lifecycle::is_present(limx)) {
        deprecate_warn0("3.3.0", "coord_trans(limx)", "coord_trans(xlim)")
        xlim <- limx
    }
    if (lifecycle::is_present(limy)) {
        deprecate_warn0("3.3.0", "coord_trans(limy)", "coord_trans(ylim)")
        ylim <- limy
    }
    ggplot2:::check_coord_limits(xlim)
    ggplot2:::check_coord_limits(ylim)
    if (is.character(x)) 
        x <- scales::as.transform(x)
    if (is.character(y)) 
        y <- scales::as.transform(y)
    ggplot2::ggproto(NULL, CoordTransfixed, trans = list(x = x, y = y), 
                     limits = list(x = xlim, y = ylim), expand = expand, 
                     clip = clip, ratio = ratio)
}

coord_canvas <- function(ratio = 1, ...){
  
  coord_transfixed(y = "reverse", ratio = ratio, ...)

}

coord_timebox <- function(ratio = 1/60,  ...){
  
  coord_canvas(ratio = ratio, ...)
  
}

Step 4. Test it out enjoy! (possibly basis of examples and tests)

ggplot(data = tasks_df) +
  aes(task = my_task, 
      minutes = time_minutes) + 
  # stamp_workday(alpha = .25, fill = "magenta") +
  stat_timebox(color = "grey35", alpha = .25, fill = "cadetblue") + 
  stat_timebox(geom = "text", color = "grey30", 
               vjust = 1.2, hjust = -0.1, nudge_x = .5,
               lineheight = .7) + 
  # stamp_currenttime() +
  stat_timebox(geom = "text", color = "grey30", 
               vjust = 1.2, hjust = 1.1,
               lineheight = .7, 
               aes(label = after_stat(clock_hour_minute))) 
#> Warning in stat_timebox(geom = "text", color = "grey30", vjust = 1.2, hjust =
#> -0.1, : Ignoring unknown parameters: `nudge_x`

last_plot() + 
  coord_canvas(ratio = 1/30)

last_plot() + 
  coord_timebox() + 
  scale_x_continuous(expand = expansion(mult = c(.2, 0)))
#> Coordinate system already present. Adding new coordinate system, which will
#> replace the existing one.

last_plot() + 
  theme_void()

Step 5. Write messages/warnings etc in the function

Part II. Packaging and documentation 🚧 βœ…

Phase 1. Minimal working package

Bit A. Created package archetecture, running devtools::create(".") in interactive session. 🚧 βœ…

devtools::create(".")

Bit B. Added roxygen skeleton? 🚧 βœ…

Use a roxygen skeleton for auto documentation and making sure proposed functions are exported. Generally, early on, I don’t do much (anything) in terms of filling in the skeleton for documentation, because things may change.

Bit C. Managed dependencies ? 🚧 βœ…

Package dependencies managed, i.e.Β depend::function() in proposed functions and declared in the DESCRIPTION

usethis::use_package("ggplot2")
usethis::use_package("dplyr") 
usethis::use_package("stringr") 
usethis::use_package("hms") 

Bit D. Moved functions R folder? 🚧 βœ…

Use new {readme2pkg} function to do this from readme…

library(tidyverse)

Bit E. Run devtools::check() and addressed errors. 🚧 βœ…

devtools::check(pkg = ".")

Bit F. Build package 🚧 βœ…

devtools::install(upgrade ="never")

Bit G. Write traditional README that uses built package (also serves as a test of build. 🚧 βœ…

The goal of the {ggtedious} package is to make it easy to draw posts (and to learn about package building and testing)

Install package with:

remotes::install_github("EvaMaeRey/ggvanilla")

Once functions are exported you can remove go to two colons, and when things are are really finalized, then go without colons (and rearrange your readme…)

library(ggtimebox)  
library(ggplot2)

tasks_df <- tibble::tribble(~my_task, ~time_minutes,
        "eat breakfast", 15,
        "brush teeth", 3,
        "run", 30,
        "draft email", 5
        )

ggplot(data = tasks_df) +
  aes(task = my_task, 
      minutes = time_minutes) + 
  ggtimebox:::stat_timebox(color = "grey35", alpha = .25, fill = "cadetblue") + 
  ggtimebox:::stat_timebox(geom = "text", color = "grey30", 
               vjust = 1.2, hjust = -0.1, nudge_x = .5,
               lineheight = .7) + 
  ggtimebox:::stat_timebox(geom = "text", color = "grey30", 
               vjust = 1.2, hjust = 1.1,
               lineheight = .7, 
               aes(label = after_stat(clock_hour_minute))) + 
  # ggtimebox:::stamp_currenttime() + 
  ggtimebox:::coord_timebox() +
  scale_x_continuous(expand = expansion(mult = c(.2,0)))
#> Warning in ggtimebox:::stat_timebox(geom = "text", color = "grey30", vjust =
#> 1.2, : Ignoring unknown parameters: `nudge_x`

Bit H. Chosen a license? 🚧 βœ…

usethis::use_mit_license()

Bit I. Add lifecycle badge (experimental) 🚧 βœ…

usethis::use_lifecycle_badge("experimental")

Phase 2: Listen & iterate 🚧 βœ…

Try to get feedback from experts on API, implementation, default decisions. Is there already work that solves this problem?

Phase 3: Let things settle

Bit A. Settle on examples. Put them in the roxygen skeleton and readme. 🚧 βœ…

Bit B. Written formal tests of functions and save to test that folders 🚧 βœ…

That would look like this…

library(testthat)

test_that("calc times 2 works", {
  expect_equal(times_two(4), 8)
  expect_equal(times_two(5), 10)
  
})
readme2pkg::chunk_to_tests_testthat("test_calc_times_two_works")

Bit C. Added a description and author information in the DESCRIPTION file 🚧 βœ…

Bit D. Addressed all notes, warnings and errors. 🚧

Phase 4. Promote to wider audience…

Bit A. Package website built? βœ…

usethis::use_pkgdown()
pkgdown::build_site()

Bit B. Package website deployed? 🚧 βœ…

Phase 5: Harden/commit

Submit to CRAN/RUniverse? 🚧

Appendix: Reports, Environment

Edited Description file?

readLines("DESCRIPTION")

ggtimebox's People

Contributors

evamaerey avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.