Site Defaults

R defaults

_defaults.R
library(ggplot2)
library(ggdark)
library(showtext)
library(colorspace)
library(ggthemes)
library(gt)
library(rlang)

# get plot fonts
font_add_google(name = "Public Sans", family = "Public Sans")
showtext_auto()

# Set global variable for setting fonts
# that aren't set by theme(text=...)
PLOT_FONT <- "Public Sans"


# from the theme _variables.scss
body_bg <- "#222222"
plot_bg <- darken("#375a7f", 0.50)
major <- lighten(
  plot_bg,
  amount = 0.25
)
minor <- lighten(
  plot_bg,
  amount = 0.125
)
strip_bg <- lighten(plot_bg, 0.5)

ptol_red <- "#EE6677"
ptol_blue <- "#4477AA"


theme_set(theme_minimal(base_size = 16) + 
            theme(text = element_text(family = "Public Sans"),
                  plot.background = element_rect(fill = "white", colour = NA),
                  panel.background = element_rect(fill = "white", colour = NA),                  
                  panel.grid = element_blank(),
                  legend.key = element_blank(),
                  #strip.background = element_rect(fill = strip_bg),
                  #strip.text = element_text(color = "white"),
                  axis.ticks = element_blank(),
                  axis.line = element_line(color = "grey60", linewidth = 0.2),
                  legend.background = element_blank()))

theme_dark <- function(){
  theme(
    #panel.border = element_blank(),
    text = element_text(family = "Public Sans", colour = "white"),
    axis.text = element_text(colour = "white"),
    rect = element_rect(colour = "#222", fill = "#222"),
    plot.background = element_rect(fill = "#222", colour = NA),
    panel.background = element_rect(fill = "#424952"),
    strip.background = element_rect(fill="#3d3d3d"),
    strip.text = element_text(color = "white")
  )
}

dark_render <- function(plot){
  ggdark::invert_geom_defaults()
  rlang::try_fetch(
    print(plot),
    error = function(cnd){
      ggdark::invert_geom_defaults()
      abort("Failed.", parent = cnd)
    }
  )
  ggdark::invert_geom_defaults()
}


theme_no_y <- function(){
  theme(
    axis.text.y = element_blank(),
    axis.title.y = element_blank(),
    panel.grid.major.y = element_blank()
  )
}

theme_no_x <- function(){
  theme(
    axis.text.x = element_blank(),
    axis.title.x = element_blank(),
    panel.grid.major.x = element_blank()
  )
}

out2fig = function(out.width, out.width.default = 0.7, fig.width.default = 6) {
  fig.width.default * out.width / out.width.default 
}

options(
  ggplot2.discrete.colour = lapply(1:12, ggthemes::ptol_pal()),
  ggplot2.discrete.fill = lapply(1:12, ggthemes::ptol_pal()),
  ggplot2.ordinal.colour = \(...) scale_color_viridis_d(option = "G", direction = -1, ...),
  ggplot2.ordinal.fill = \(...) scale_fill_viridis_d(option = "G", direction = -1, ...),  
  ggplot2.continuous.colour = \(...) scico::scale_color_scico(palette = "batlow", ...),
  ggplot2.continuous.fill = \(...) scico::scale_fill_scico(palette = "batlow", ...)
)

# set a crop: true hook
knitr::knit_hooks$set(crop = knitr::hook_pdfcrop)


# dark gt theme
dark_gt_theme <- function(tbl){
  
  style_cells <- list(
    cells_body(),
    cells_column_labels(),
    cells_column_spanners(),
    cells_footnotes(),
    cells_row_groups(),
    cells_source_notes(),
    cells_stub(),
    cells_stubhead(),
    cells_title()
  )
  
  summary_info <- tbl$`_summary` |> 
    map(
      ~":GRAND_SUMMARY:" %in% .x$groups
    )  |> 
    list_simplify()
  
  if(any(summary_info)){
    style_cells <- c(
      style_cells,
      list(
        cells_grand_summary(),
        cells_stub_grand_summary()
      )
    )
  }
  
  if(any(!(summary_info %||% T))){
    style_cells <- c(
      style_cells,
      list(
        cells_stub_summary(),
        cells_summary()
      )
    )
  }
  
  tbl |> 
    opt_table_font(
      font = c(
        google_font(name = "Public Sans"),
        default_fonts()
      )
    )|> 
    tab_style(
      style = "
        background-color: var(--bs-body-bg);
        color: var(--bs-body-color)
      ",
      locations = style_cells
    ) 
}

new_post <- function(title = ""){
  
  day <- Sys.Date()
  slug = snakecase::to_snake_case(title, sep_out ="-")
  
  pieces <- stringr::str_split(day, "-")[[1]]
  year <- pieces[1]
  month <- pieces[2]
  month_path = here::here("posts", year, month)
  post_path = fs::path(
    year, 
    month,
    stringr::str_glue("{day}_{slug}")
  )
  
  if(!fs::dir_exists(month_path)){
    fs::dir_create(month_path, recurse = T)
  }
  
  quarto::new_blog_post(
    title = title,
    dest = post_path
  )
}

Prerender

prerender.py
from commonmeta import encode_doi
from pathlib import Path
import yaml
import logging

def get_all_posts(post_base:str = "posts")->list[Path]:
  """Get all post directories

  Args:
      post_base (str, optional): 
        base post path. Defaults to "posts".

  Returns:
      list[Path]: Path to every post
  """
  post_dirs = [
    post
    for year in Path(post_base).glob("*/")
    for month in year.glob("*/")
    for post in month.glob("*/")
  ]
  return post_dirs

def make_metadata(path:Path) -> None:
  """Create empty `_metadata.yml` file.

  Args:
      path (Path): Path to post
  """
  metadata_path = path.joinpath("_metadata.yml")
  if metadata_path.exists():
    return
  else:
    metadata_path.touch()

def make_all_metadata(paths:list[Path]) -> None:
  """Make all `_metadata.yml`

  Args:
      paths (list[Path]): 
        List of all post paths
  """
  for p in paths:
    make_metadata(p)
    
def make_date(path:Path)->str:
  """Return a date string based on the path name

  Args:
      path (Path): Path to post

  Returns:
      (str): Date string
  """
  return path.name.split("_")[0]

def make_doi() -> str:
  """Make doi number

    Returns:
      (str): doi
  """
  doi_url = encode_doi("10.59350")
  doi_number = doi_url.removeprefix("https://doi.org/")
  return doi_number
    
def check_metadata(path:Path)->None:
  """Check and update a `_metadata.yml` file

  Args:
      path (Path): Path to post
  """
  metadata_path = path.joinpath("_metadata.yml")
  dat = yaml.safe_load(metadata_path.read_text())

  if dat is None:
    dat = {
      "date": make_date(path),
      "doi": make_doi()
    }
    metadata_path.write_text(
      yaml.dump(dat)
    )
    return
  
  # just to be safe
  if not isinstance(dat, dict):
    logging.warning(f"Non-dict _metadata for {str(path)}")
    return
  
  # avoid writing if not necessary
  if "date" in dat \
    and "doi" in dat \
    and dat["date"] == make_date(path):

    return
  
  if not "doi" in dat:
    dat["doi"] = make_doi()

  if not "date" in dat:
    dat["date"] = make_date(path)
  elif dat["date"] != make_date(path):
    dat["date"] = make_date(path)
   
  metadata_path.write_text(
      yaml.dump(dat)
  )

def check_all_metadata(paths: list[Path]) -> None:
  """Check and update all `_metadata.yml` files

  Args:
      paths (list[Path]): _description_
  """
  for p in paths:
    check_metadata(p)
  
post_dirs = get_all_posts("posts")
make_all_metadata(post_dirs)
check_all_metadata(post_dirs)
  

Github Action

name: Build Site
on:
  push:
    branches: ["master"]
    
env:
  RENV_PATHS_ROOT: ~/.cache/R/renv
  QUARTO_PYTHON: renv/python/virtualenvs/renv-python-3.12/bin/python
jobs:
  build-docs:
     runs-on: macos-latest
     steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-python@v2
        with:
          python-version: "3.12"
      - name: Set Reticulate
        run: |
          echo "RETICULATE_PYTHON=$(which python)" >> "$GITHUB_ENV"
          echo "RENV_PYTHON=$(which python)" >> "$GITHUB_ENV"
          echo "QUARTO_PYTHON=$(which python)" >> "$GITHUB_ENV"
          pip install -r requirements.txt
      - uses: r-lib/actions/setup-r@v2
      - name: Cache packages
        uses: actions/cache@v4
        with:
          path: ${{ env.RENV_PATHS_ROOT }}
          key: ${{ runner.os }}-renv-${{ hashFiles('**/renv.lock') }}
          restore-keys: |
            ${{ runner.os }}-renv-          
      - name: Restore packages
        shell: Rscript {0}
        run: |
          if (!requireNamespace("renv", quietly = TRUE)) install.packages("renv")
          renv::install("reticulate")
          #reticulate::install_python("3.12:latest")
          renv::use_python(type = "virtualenv")
          renv::restore()
      - name: Set up quarto
        uses: quarto-dev/quarto-actions/setup@v2
      # - name: Set Quarto Python
      #   run: |
      #     echo "QUARTO_PYTHON=renv/python/virtualenvs/renv-python-3.12/bin/python" >> $GITHUB_ENV
      - name: Render and publish to gh pages
        uses: quarto-dev/quarto-actions/publish@v2
        with:
          target: gh-pages

Reuse

CC-BY 4.0