
├── .Rbuildignore ├── .github ├── .gitignore └── workflows │ └── pkgdown.yaml ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── R ├── generate_jsBlock.R ├── get_package_data.R ├── helpers.R ├── image_arrange.R ├── make_tile.R └── snap_tile.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── cran-comments.md ├── docs ├── 404.html ├── CNAME ├── LICENSE-text.html ├── LICENSE.html ├── apple-touch-icon.png ├── authors.html ├── deps │ ├── bootstrap-5.3.1 │ │ ├── bootstrap.bundle.min.js │ │ ├── bootstrap.bundle.min.js.map │ │ └── bootstrap.min.css │ ├── bootstrap-toc-1.0.1 │ │ └── bootstrap-toc.min.js │ ├── clipboard.js-2.0.11 │ │ └── clipboard.min.js │ ├── data-deps.txt │ ├── font-awesome-6.4.2 │ │ ├── css │ │ │ ├── all.css │ │ │ ├── all.min.css │ │ │ ├── v4-shims.css │ │ │ └── v4-shims.min.css │ │ └── webfonts │ │ │ ├── fa-brands-400.ttf │ │ │ ├── fa-brands-400.woff2 │ │ │ ├── fa-regular-400.ttf │ │ │ ├── fa-regular-400.woff2 │ │ │ ├── fa-solid-900.ttf │ │ │ ├── fa-solid-900.woff2 │ │ │ ├── fa-v4compatibility.ttf │ │ │ └── fa-v4compatibility.woff2 │ ├── headroom-0.11.0 │ │ ├── headroom.min.js │ │ └── jQuery.headroom.min.js │ ├── jquery-3.6.0 │ │ ├── jquery-3.6.0.js │ │ ├── jquery-3.6.0.min.js │ │ └── jquery-3.6.0.min.map │ └── search-1.0.0 │ │ ├── autocomplete.jquery.min.js │ │ ├── fuse.min.js │ │ └── mark.min.js ├── favicon-48x48.png ├── favicon.ico ├── favicon.svg ├── index.html ├── katex-auto.js ├── lightswitch.js ├── link.svg ├── logo.png ├── pkgdown.js ├── pkgdown.yml ├── reference │ ├── encode_image.html │ ├── figures │ │ ├── exampletile.png │ │ ├── hsdemo.gif │ │ └── logo.png │ ├── find_imgpaths.html │ ├── find_logopaths.html │ ├── generate_hexsession_js.html │ ├── getLoaded.html │ ├── get_pkg_data.html │ ├── index.html │ ├── make_missingLogos.html │ ├── make_tile.html │ ├── pkgurls.html │ └── snap_tile.html ├── search.json ├── site.webmanifest ├── sitemap.xml ├── web-app-manifest-192x192.png └── web-app-manifest-512x512.png ├── hexsession.Rproj ├── inst ├── extdata │ ├── blankhexsm.png │ ├── hexout_example.html │ ├── rectDark.png │ ├── rectLight.png │ └── rectMed.png └── templates │ └── _hexout.qmd ├── man ├── col_arrange.Rd ├── encode_image.Rd ├── figures │ ├── exampletile.png │ ├── hsdemo.gif │ └── logo.png ├── find_imgpaths.Rd ├── find_logopaths.Rd ├── generate_hexsession_js.Rd ├── getLoaded.Rd ├── get_pkg_data.Rd ├── maincolorRGB.Rd ├── make_missingLogos.Rd ├── make_tile.Rd ├── pkgurls.Rd └── snap_tile.Rd └── pkgdown └── favicon ├── apple-touch-icon.png ├── favicon-48x48.png ├── favicon.ico ├── favicon.svg ├── site.webmanifest ├── web-app-manifest-192x192.png └── web-app-manifest-512x512.png /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^hexsession\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | ^README\.Rmd$ 5 | ^cran-comments\.md$ 6 | ^_pkgdown\.yml$ 7 | ^docs$ 8 | ^pkgdown$ 9 | ^\.github$ 10 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | release: 9 | types: [published] 10 | workflow_dispatch: 11 | 12 | name: pkgdown.yaml 13 | 14 | permissions: read-all 15 | 16 | jobs: 17 | pkgdown: 18 | runs-on: ubuntu-latest 19 | # Only restrict concurrency for non-PR jobs 20 | concurrency: 21 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 22 | env: 23 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 24 | permissions: 25 | contents: write 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - uses: r-lib/actions/setup-pandoc@v2 30 | 31 | - uses: r-lib/actions/setup-r@v2 32 | with: 33 | use-public-rspm: true 34 | 35 | - uses: r-lib/actions/setup-r-dependencies@v2 36 | with: 37 | extra-packages: any::pkgdown, local::. 38 | needs: website 39 | 40 | - name: Build site 41 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 42 | shell: Rscript {0} 43 | 44 | - name: Deploy to GitHub pages 🚀 45 | if: github.event_name != 'pull_request' 46 | uses: JamesIves/github-pages-deploy-action@v4.5.0 47 | with: 48 | clean: false 49 | branch: gh-pages 50 | folder: docs 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .Rdata 4 | .httr-oauth 5 | .DS_Store 6 | .quarto 7 | docs 8 | 9 | /.quarto/ 10 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: hexsession 2 | Title: Create a tile of logos for loaded packages 3 | Version: 0.0.0.9000 4 | Authors@R: 5 | person("Luis D.", "Verde Arregoitia", , "luis@liomys.mx", role = c("aut", "cre"), 6 | comment = c(ORCID = "0000-0001-9520-6543")) 7 | Description: Creates a responsive HTML file with tiled hex logos for all loaded packages in a session, which can be saved as a static screenshot in png format. 8 | License: MIT + file LICENSE 9 | Suggests: 10 | rsvg, 11 | testthat (>= 3.0.0) 12 | Config/testthat/edition: 3 13 | Encoding: UTF-8 14 | Roxygen: list(markdown = TRUE) 15 | RoxygenNote: 7.3.2.9000 16 | URL: https://github.com/luisDVA/hexsession, https://luisdva.github.io/hexsession/ 17 | BugReports: https://github.com/luisDVA/hexsession/issues 18 | Imports: 19 | base64enc, 20 | chromote, 21 | jsonlite, 22 | magick, 23 | purrr, 24 | htmltools, 25 | knitr 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2024 2 | COPYRIGHT HOLDER: hexsession authors 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2024 hexsession authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(col_arrange) 4 | export(encode_image) 5 | export(generate_hexsession_js) 6 | export(make_tile) 7 | export(snap_tile) 8 | importFrom(base64enc,base64encode) 9 | importFrom(grDevices,convertColor) 10 | importFrom(htmltools,HTML) 11 | importFrom(jsonlite,toJSON) 12 | importFrom(knitr,is_html_output) 13 | importFrom(magick,image_data) 14 | importFrom(magick,image_read) 15 | importFrom(magick,image_scale) 16 | importFrom(purrr,map) 17 | importFrom(stats,kmeans) 18 | importFrom(utils,packageDescription) 19 | -------------------------------------------------------------------------------- /R/generate_jsBlock.R: -------------------------------------------------------------------------------- 1 | #' Generate JavaScript file for hexsession 2 | #' 3 | #' @param logopaths Vector of image paths 4 | #' @param urls Vector of URLs 5 | #' @param dark_mode Use dark mode, inherited from make_tile 6 | #' @param output_js Path to save the JavaScript file 7 | #' 8 | #' @importFrom jsonlite toJSON 9 | #' @importFrom base64enc base64encode 10 | #' 11 | #' @export 12 | generate_hexsession_js <- function(logopaths, urls, dark_mode, output_js) { 13 | encoded_paths <- vector("character", length(logopaths)) 14 | for (i in seq_along(logopaths)) { 15 | tryCatch({ 16 | encoded <- base64enc::base64encode(logopaths[i]) 17 | ext <- tolower(tools::file_ext(logopaths[i])) 18 | encoded_paths[i] <- paste0("data:image/", ext, ";base64,", encoded) 19 | }, error = function(e) { 20 | warning(paste("Error encoding file:", logopaths[i], "-", e$message)) 21 | encoded_paths[i] <- NA_character_ 22 | }) 23 | } 24 | 25 | image_url_pairs <- lapply(seq_along(encoded_paths), function(i) { 26 | list( 27 | image = encoded_paths[i], 28 | url = if (is.na(urls[i]) || urls[i] == "NA") NULL else as.character(urls[i]) 29 | ) 30 | }) 31 | 32 | image_url_pairs <- image_url_pairs[!is.na(sapply(image_url_pairs, function(x) x$image))] 33 | 34 | js_content <- sprintf(' 35 | const imageUrlPairs = %s; 36 | const darkMode = %s; 37 | 38 | document.addEventListener("DOMContentLoaded", function() { 39 | const container = document.getElementById("imageContainer"); 40 | 41 | if (darkMode) { 42 | document.documentElement.style.setProperty("--bg-color", "#1a1a1a"); 43 | document.documentElement.style.setProperty("--text-color", "#ffffff"); 44 | document.documentElement.style.setProperty("--tile-bg", "#2a2a2a"); 45 | document.documentElement.style.setProperty("--attribution-bg", "rgba(255, 255, 255, 0.1)"); 46 | document.documentElement.style.setProperty("--link-color", "#66b3ff"); 47 | } else { 48 | document.documentElement.style.setProperty("--bg-color", "#ffffff"); 49 | document.documentElement.style.setProperty("--text-color", "#000000"); 50 | document.documentElement.style.setProperty("--tile-bg", "#f0f0f0"); 51 | document.documentElement.style.setProperty("--attribution-bg", "rgba(0, 0, 0, 0.1)"); 52 | document.documentElement.style.setProperty("--link-color", "#0066cc"); 53 | } 54 | 55 | imageUrlPairs.forEach((pair, index) => { 56 | const div = document.createElement("div"); 57 | const img = document.createElement("img"); 58 | 59 | img.src = pair.image; 60 | img.alt = "Hexagon Image " + (index + 1); 61 | 62 | if (pair.url && typeof pair.url === "string") { 63 | const a = document.createElement("a"); 64 | a.href = pair.url; 65 | a.target = "_blank"; 66 | a.appendChild(img); 67 | div.appendChild(a); 68 | } else { 69 | div.appendChild(img); 70 | } 71 | 72 | container.appendChild(div); 73 | }); 74 | }); 75 | ', 76 | jsonlite::toJSON(image_url_pairs, auto_unbox = TRUE), 77 | tolower(as.character(dark_mode)) 78 | ) 79 | 80 | writeLines(js_content, output_js) 81 | } 82 | -------------------------------------------------------------------------------- /R/get_package_data.R: -------------------------------------------------------------------------------- 1 | #' Get package data 2 | #' @param packages Character vector of package names (default is NULL, uses loaded packages) 3 | #' @return A list containing logopaths and urls for the packages 4 | get_pkg_data <- function(packages = NULL) { 5 | if (is.null(packages)) { 6 | packages <- getLoaded() 7 | } else { 8 | # Check if specified packages are installed 9 | not_installed <- packages[!packages %in% installed.packages()[, "Package"]] 10 | if (length(not_installed) > 0) { 11 | stop("The following packages are not installed: ", paste(not_installed, collapse = ", ")) 12 | } 13 | } 14 | 15 | imagepaths <- find_imgpaths(packages) 16 | logopaths <- find_logopaths(imagepaths, packages) 17 | 18 | # Generate missing logos 19 | missing <- is.na(logopaths) 20 | if (any(missing)) { 21 | generated <- make_missingLogos(packages[missing], logopaths[missing]) 22 | logopaths[missing] <- generated 23 | } 24 | 25 | # Get package URLs 26 | urls <- sapply(packages, function(pkg) { 27 | desc <- packageDescription(pkg) 28 | url <- desc$URL 29 | if (is.null(url)) { 30 | paste0("https://cran.r-project.org/package=", pkg) 31 | } else { 32 | strsplit(url, ",")[[1]][1] # Use the first URL if multiple are provided 33 | } 34 | }) 35 | 36 | list(logopaths = logopaths, urls = urls) 37 | } 38 | -------------------------------------------------------------------------------- /R/helpers.R: -------------------------------------------------------------------------------- 1 | #' Get loaded packages 2 | #' @return A character vector of the attached packages (excludes base packages) 3 | getLoaded <- function() { 4 | # also exclude hexsession 5 | basepkgs <- c( 6 | "stats", "graphics", "grDevices", "utils", "datasets", 7 | "methods", "base", "hexsession" 8 | ) 9 | (.packages())[!(.packages()) %in% basepkgs] 10 | } 11 | 12 | #' Find image paths 13 | #' @param pkgnames Character vector of package names 14 | #' @return A list of image file paths for each package 15 | #' @details 16 | #' Images in svg format will be converted to png. When no image matches 'logo' 17 | #' in the file name the used is will be prompted to select likely logos. 18 | find_imgpaths <- function(pkgnames) { 19 | lapply(pkgnames, function(x) { 20 | img_files <- list.files(system.file(package = x), 21 | pattern = "\\.png$|\\.jpg$|\\.svg$", 22 | recursive = TRUE, 23 | full.names = TRUE 24 | ) 25 | 26 | # Convert SVG to PNG if necessary 27 | img_files <- lapply(img_files, function(file) { 28 | if (tools::file_ext(file) == "svg") { 29 | # Create a new filename with "converted" prefix 30 | file_dir <- tempdir() 31 | file_name <- basename(file) 32 | new_file_name <- paste0("converted_",x,"_", tools::file_path_sans_ext(file_name), ".png") 33 | png_file <- file.path(file_dir, new_file_name) 34 | 35 | # Convert SVG to PNG 36 | 37 | magick::image_write( 38 | magick::image_read_svg(file), 39 | png_file, format = "png") 40 | 41 | return(png_file) 42 | } 43 | return(file) 44 | }) 45 | 46 | unlist(img_files) 47 | }) 48 | } 49 | 50 | #' Find logo paths 51 | #' @param imagepaths List of image paths 52 | #' @param pkgnames Character vector of package names 53 | #' @return A vector of logo paths 54 | find_logopaths <- function(imagepaths, pkgnames) { 55 | logopaths <- purrr::map2(imagepaths, pkgnames, function(x, pkg) { 56 | logo_matches <- x[grepl("hex|logo", x, ignore.case = TRUE)] 57 | 58 | if (length(logo_matches) == 0 && length(x) > 0) { 59 | choices <- c(basename(x), "None of the above") 60 | 61 | choice <- utils::menu( 62 | choices = choices, 63 | title = sprintf("Package: %s - No images match 'logo'. Please select the image with the package logo:", pkg) 64 | ) 65 | 66 | if (choice > 0 && choice <= length(x)) { 67 | return(x[choice]) 68 | } else { 69 | return(NA_character_) 70 | } 71 | } else if (length(logo_matches) == 1) { 72 | return(logo_matches[1]) 73 | } else if (length(logo_matches) > 1) { 74 | choices <- c(basename(logo_matches), "None of the above") 75 | 76 | choice <- utils::menu( 77 | choices = choices, 78 | title = sprintf("Package: %s - Multiple possible logos. Please select the image to use:", pkg) 79 | ) 80 | 81 | if (choice > 0 && choice <= length(logo_matches)) { 82 | return(logo_matches[choice]) 83 | } else { 84 | return(NA_character_) 85 | } 86 | } else { 87 | return(NA_character_) 88 | } 89 | }) 90 | 91 | unlist(logopaths) 92 | } 93 | 94 | #' Get package URLs 95 | #' @param pkgnames Character vector of package names 96 | #' @importFrom utils packageDescription 97 | #' @return A vector of package URLs 98 | pkgurls <- function(pkgnames) { 99 | allurls <- purrr::map(pkgnames, \(x) { 100 | url <- packageDescription(x, fields = "URL") 101 | if (is.null(url) || all(is.na(url)) || all(url == "")) NA_character_ else url 102 | }) 103 | splturls <- purrr::map(allurls, \(x) { 104 | if (all(is.na(x))) NA_character_ else unlist(strsplit(x, ",|\n")) 105 | }) 106 | purrr::map_chr(splturls, \(x) { 107 | if (all(is.na(x))) NA_character_ else x[1] 108 | }) 109 | } 110 | 111 | #' Encode image to Base64 112 | #' @param file_path Path to an image file 113 | #' @return Base64 encoded string of the image 114 | #' @export 115 | encode_image <- function(file_path) { 116 | tryCatch( 117 | { 118 | encoded <- base64enc::base64encode(file_path) 119 | ext <- tolower(tools::file_ext(file_path)) 120 | paste0("data:image/", ext, ";base64,", encoded) 121 | }, 122 | error = function(e) { 123 | warning(paste("Error encoding file:", file_path, "-", e$message)) 124 | NULL 125 | } 126 | ) 127 | } 128 | 129 | #' Create missing logos 130 | #' @param attached_pkgs Character vector of attached package names 131 | #' @param logopaths Vector of existing logo paths 132 | #' @return Vector of paths to new logos 133 | make_missingLogos <- function(attached_pkgs, logopaths) { 134 | pending <- attached_pkgs[is.na(logopaths)] 135 | basehex <- magick::image_read(system.file("extdata", "blankhexsm.png", package = "hexsession")) 136 | 137 | 138 | create_logo <- function(pkgname) { 139 | widthhex <- magick::image_info(basehex)$width 140 | 141 | fig <- magick::image_blank(700, 700) |> 142 | magick::image_annotate(pkgname, size = 100, color = "white") |> 143 | magick::image_trim() |> 144 | magick::image_border("none") |> 145 | magick::image_resize(paste0(widthhex - 30, "x")) 146 | 147 | magick::image_composite(basehex, fig, operator = "SrcOver", gravity = "center") 148 | } 149 | 150 | logoimgs <- purrr::map(pending, create_logo) 151 | # single-logo tiles 152 | if (!is.list(logoimgs)) { 153 | logoimgs <- list(logoimgs) 154 | } 155 | 156 | imgpaths <- paste0(tempfile(), "_", pending, ".png") 157 | 158 | purrr::walk2(logoimgs, imgpaths, 159 | \(x, y) magick::image_write(x, y)) 160 | 161 | as.list(imgpaths) 162 | } 163 | -------------------------------------------------------------------------------- /R/image_arrange.R: -------------------------------------------------------------------------------- 1 | #' Extract the Most Frequent Color from an Image 2 | #' 3 | #' Internal helper. For a given image path, this functions uses k-means 4 | #' clustering to identify the most dominant color in the image. 5 | #' 6 | #' @param imgpath Character string. File path to the image. 7 | #' 8 | #' @return A data frame with one row containing the RGB values of the dominant color. 9 | #' The column name is set to the input image path. 10 | #' 11 | #' @importFrom magick image_read image_scale image_data 12 | #' @importFrom stats kmeans 13 | #' 14 | maincolorRGB <- function(imgpath) { 15 | img <- magick::image_scale(magick::image_read(imgpath), "130") 16 | imgint <- as.integer(magick::image_data(img, channels = "rgb")) 17 | img_df <- data.frame(red = c(imgint[,,1]), green = c(imgint[,,2]), blue = c(imgint[,,3])) 18 | km <- kmeans(img_df, 5) 19 | rgbout <- data.frame(km$centers[which.max(km$size),]) 20 | names(rgbout) <- paste(imgpath) 21 | rgbout 22 | } 23 | 24 | #' Arrange Images by Color 25 | #' 26 | #' Takes a vector of image paths, extracts the main color from each image using 27 | #' k-means clustering, converts the colors to the LAB color space, and sorts the 28 | #' images based on the lightness (L) component of their dominant color. 29 | #' 30 | #' @param image_paths Character vector. A vector of file paths to the images. 31 | #' 32 | #' @return A character vector of image paths, sorted by the lightness of their 33 | #' main color. 34 | #' 35 | #' @importFrom grDevices convertColor 36 | #' @importFrom purrr map 37 | #' 38 | #' @examples 39 | #' img1 <- system.file("extdata/rectLight.png", package = "hexsession") 40 | #' img2 <- system.file("extdata/rectMed.png", package = "hexsession") 41 | #' img3 <- system.file("extdata/rectDark.png", package = "hexsession") 42 | #' sorted_paths <- col_arrange(c(img1,img3,img2)) 43 | #' 44 | #' @export 45 | col_arrange <- function(image_paths) { 46 | # find most common color and convert to rgb 47 | allrgbspace <- do.call(cbind, purrr::map(image_paths, maincolorRGB)) 48 | # 'Lab' color space 49 | lab <- convertColor(t(allrgbspace), 'sRGB', 'Lab') 50 | # sort images by lightness 51 | image_paths[order(lab[, 'L'])] 52 | } 53 | -------------------------------------------------------------------------------- /R/make_tile.R: -------------------------------------------------------------------------------- 1 | #' Generate tile of package logos 2 | #' 3 | #' @description 4 | #' This function returns an interactive html tile view of the packages either 5 | #' listed in the `packages` option, or all of the loaded packages. When rendered 6 | #' interactively, the result is output in the viewer. When rendered in Quarto or 7 | #' RMarkdown, the tile becomes part of the rendered html. If local images are provided, 8 | #' only these images will be used, excluding loaded packages. 9 | #' 10 | #' @param packages Character vector of package names to include (default: NULL, which uses loaded packages) 11 | #' @param dark_mode Draw the tile on a dark background? 12 | #' @param local_images Optional character vector of local image paths to add to the tile 13 | #' @param local_urls Optional character vector of URLs for each of the local images passed 14 | #' @param color_arrange Logical, whether to arrange the images by color along the 'Lab' color space (defaults to FALSE) 15 | #' @return Path to the output file 16 | #' @importFrom jsonlite toJSON 17 | #' @importFrom base64enc base64encode 18 | #' @importFrom knitr is_html_output 19 | #' @importFrom htmltools HTML 20 | #' @export 21 | make_tile <- function(packages = NULL, local_images = NULL, 22 | local_urls = NULL, dark_mode = FALSE, 23 | color_arrange = FALSE) { 24 | temp_dir <- file.path(getwd(), "temp_hexsession") 25 | dir.create(temp_dir, showWarnings = FALSE) 26 | 27 | if (is.null(local_images)) { 28 | package_data <- get_pkg_data(packages) 29 | all_logopaths <- package_data$logopaths 30 | all_urls <- package_data$urls 31 | } else { 32 | all_logopaths <- local_images 33 | all_urls <- local_urls 34 | } 35 | 36 | # Ensure all_urls is the same length as all_logopaths 37 | if (length(all_urls) < length(all_logopaths)) { 38 | all_urls <- c(all_urls, rep(NA, length(all_logopaths) - length(all_urls))) 39 | } else if (length(all_urls) > length(all_logopaths)) { 40 | all_urls <- all_urls[1:length(all_logopaths)] 41 | } 42 | 43 | 44 | # Arrange images by color if requested 45 | if (color_arrange) { 46 | laborder <- col_arrange(all_logopaths) 47 | all_logopaths <- all_logopaths[order(match(all_logopaths,laborder))] 48 | all_urls <- all_urls[order(match(all_logopaths,laborder))] 49 | } 50 | 51 | temp_file <- file.path(temp_dir, "package_data.rds") 52 | saveRDS(list(logopaths = all_logopaths, urls = all_urls), temp_file) 53 | 54 | js_file <- file.path(temp_dir, "hexsession.js") 55 | generate_hexsession_js(all_logopaths, all_urls, dark_mode, js_file) 56 | 57 | template_path <- system.file("templates", "_hexout.qmd", package = "hexsession") 58 | file.copy(template_path, file.path(temp_dir, "_hexout.qmd"), overwrite = TRUE) 59 | 60 | quarto_call <- sprintf( 61 | 'quarto render "%s" -P dark_mode:%s', 62 | file.path(temp_dir, "_hexout.qmd"), tolower(as.character(dark_mode)) 63 | ) 64 | system(quarto_call) 65 | 66 | if (isTRUE(getOption("knitr.in.progress")) && knitr::is_html_output()) { 67 | # Read the HTML content directly 68 | html_content <- readLines(file.path(temp_dir, "_hexout.html"), warn = FALSE) 69 | 70 | # Extract just the body content 71 | body_content <- paste(html_content[grep("
", html_content)], collapse = "\n") 72 | body_content <- gsub("?body.*?>", "", body_content) 73 | 74 | # Custom CSS 75 | custom_css <- ' 76 | 87 | ' 88 | 89 | # Create a div container with the content 90 | div_content <- sprintf( 91 | '
',
92 | body_content
93 | )
94 | # Return the HTML content directly with custom CSS
95 | return(htmltools::HTML(paste0(custom_css, div_content)))
96 |
97 | } else if (isFALSE(getOption("knitr.in.progress"))) {
98 | viewer <- getOption("viewer")
99 | viewer(file.path(temp_dir, "_hexout.html"))
100 | }
101 | }
--------------------------------------------------------------------------------
/R/snap_tile.R:
--------------------------------------------------------------------------------
1 | #' Take screenshot of html image tile
2 | #'
3 | #' @param output_path Path to image file
4 | #' @param screen_width Width of the browser window
5 | #' @param screen_height Height of the browser window
6 | #' @param dark_mode Is the tile being saved dark or light mode?
7 | #' @return Path to the saved image
8 | #' @export
9 | snap_tile <- function(output_path,
10 | screen_width = 800,
11 | screen_height = 700,
12 | dark_mode = FALSE) {
13 |
14 | b_color <- if(dark_mode == TRUE) {
15 | "black"
16 | } else "white"
17 |
18 | html_path <- "temp_hexsession/hexout.html"
19 |
20 | # Check for hex tile HTML file
21 | if (!file.exists(html_path)) {
22 | stop("No file to capture. Did you create your tile of logos?")
23 | }
24 |
25 |
26 | temppath <- tempfile(fileext = ".png")
27 |
28 | screenshot <- function(b, selector) {
29 | b$screenshot(
30 | temppath,
31 | selector = selector,
32 | scale = 2,
33 | delay = 2
34 | )
35 | }
36 |
37 | # Browser session
38 | b <- chromote::ChromoteSession$new(height = screen_height, width = screen_width)
39 |
40 | # Open the hex tile
41 | b$Page$navigate(paste0("file://", normalizePath(html_path)))
42 | Sys.sleep(1) # breathe
43 |
44 | # screenshot
45 | tryCatch({
46 | screenshot(b, selector = "#quarto-content")
47 | }, error = function(e) {
48 | cat("Error taking screenshot:", conditionMessage(e), "\n")
49 | })
50 |
51 | # post process
52 | tryCatch({
53 | magick::image_read(temppath) |>
54 | magick::image_trim() |>
55 | magick::image_border(b_color, "10x10") |>
56 | magick::image_shadow() |>
57 | magick::image_write(output_path, format = "png",
58 | density = 300)
59 |
60 | cat("Image processed and saved to:", output_path, "\n")
61 | }, error = function(e) {
62 | cat("Could not process screenshot:", conditionMessage(e), "\n")
63 | })
64 |
65 | b$close()
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/README.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | output: github_document
3 | ---
4 |
5 |
6 |
7 | ```{r, include = FALSE}
8 | knitr::opts_chunk$set(
9 | collapse = TRUE,
10 | comment = "#>",
11 | fig.path = "man/figures/README-",
12 | out.width = "100%"
13 | )
14 | ```
15 |
16 | # hexsession
17 |
18 |
19 | [](https://lifecycle.r-lib.org/articles/stages.html#experimental)
20 | [](https://CRAN.R-project.org/package=hexsession)
21 |
22 |
23 | The goal of hexsession is to create a tile of hexagonal logos for packages installed on your machine. Tiles can be created for a set of packages specified with a character vector, or for the loaded packages in your session (all packages attached to the search path except for base packages).
24 |
25 | ## Installation
26 |
27 | You can install the development version of hexsession like so:
28 |
29 | ``` r
30 | # install.packages("remotes)
31 | remotes::install_github("luisdva/hexsession")
32 | ```
33 |
34 | ## Using hexsession
35 |
36 | With hexsession installed, we can create a self-contained HTML file with tiled hex logos for all loaded packages in a session. If a package does not have a logo bundled in `man/figures/` or if the image cannot be found easily, a generic-looking logo with the package name will be generated.
37 |
38 | * svg files are internally converted to png
39 | * If the images bundled with a package do not match 'hex' or 'logo', or there are multiple possible options, users are prompted to specify which file to use.
40 |
41 |
42 |
43 | For a given session with libraries loaded in addition to base packages:
44 |
45 | ```{r example, eval=FALSE}
46 | library(hexsession)
47 | make_tile()
48 |
49 | # custom set of packages
50 | make_tile(packages=c("terra","sf","tidyr"))
51 | ```
52 |
53 | The `make_tile()` function will render the HTML output in a new folder in the working directory using a Quarto template file that will also be copied to this new directory.
54 |
55 | For a session with the following packages loaded:
56 |
57 | ```{r, eval=FALSE}
58 | library(annotater)
59 | library(ggforce)
60 | library(purrr)
61 | library(forcats)
62 | library(unheadr)
63 | library(sdmTMB)
64 | library(parsnip)
65 | library(DBI)
66 | library(broom)
67 | library(vctrs)
68 | library(patchwork)
69 |
70 |
71 | hexsession::make_tile()
72 | ```
73 |
74 | The output would look like this:
75 | 
76 | _I don't know how to show the rendered interactive file on the GitHub readme, if anyone does please let me know 😅._
77 |
78 | Once downloaded to your machine and opened in a browser, the [hexout_example.html](inst/extdata/hexout_example.html) shows the interactive, responsive HTML version with cool hover effects that adapts to the size of the browser window and includes hyperlinks to each package website.
79 |
80 | To save a static version of the hex tile, we call `snap_tile()` with a path to the output image and optionally, height and width values to change the viewport size.
81 |
82 | The result:
83 |
84 | 
85 |
86 | ### Dark mode
87 |
88 | To draw the tiles on a dark background, set `dark_mode` to `TRUE` when creating or capturing your hex logos.
89 |
90 | ```{r, eval=FALSE}
91 | hexsession::make_tile(dark_mode = TRUE)
92 | hexsession::snap_tile("test.png",dark_mode = TRUE)
93 | ```
94 |
95 | ## User-provided images and urls
96 |
97 | `make_tile` can now take vectors of additional images and their respective but optional urls to include in the hex grid.
98 |
99 | ```{r, eval=FALSE}
100 | make_tile(packages = c("tidyterra", "sf"),
101 | local_images = c("path/to/your/image1.png",
102 | "path/to/your/image2.png",
103 | local_urls = c("https://an-example.web", "https://another-example.com"))
104 |
105 | ```
106 |
107 | ## Notes
108 |
109 | This packages depends on working installations of magick, Quarto, and chromote and thus needs a Chromium-based web browser (e.g., Chrome, Chromium, Opera, or Vivaldi) installation.
110 |
111 | hexsession is very much work in progress and highly experimental. I am still learning good-practices for packages that create files and directories, use system commands, and launch browser sessions.
112 |
113 | All feedback is welcome in any form (issues, pull requests, etc.)
114 |
115 | ### Credit and LLM disclosure statement
116 |
117 | - css and html code for the responsive hex grid comes from [this tutorial](https://css-tricks.com/hexagons-and-beyond-flexible-responsive-grid-patterns-sans-media-queries/) by Temani Afif.
118 |
119 | - the javascript code to populate the divs in the Quarto template was written with input from the Claude 3.5 Sonnet LLM running in the Continue extension in the Positron IDE.
120 |
121 |
122 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # hexsession
5 |
6 |
7 |
8 | [](https://lifecycle.r-lib.org/articles/stages.html#experimental)
10 | [](https://CRAN.R-project.org/package=hexsession)
12 |
13 |
14 | The goal of hexsession is to create a tile of hexagonal logos for
15 | packages installed on your machine. Tiles can be created for a set of
16 | packages specified with a character vector, or for the loaded packages
17 | in your session (all packages attached to the search path except for
18 | base packages).
19 |
20 | ## Installation
21 |
22 | You can install the development version of hexsession like so:
23 |
24 | ``` r
25 | # install.packages("remotes)
26 | remotes::install_github("luisdva/hexsession")
27 | ```
28 |
29 | ## Using hexsession
30 |
31 | With hexsession installed, we can create a self-contained HTML file with
32 | tiled hex logos for all loaded packages in a session. If a package does
33 | not have a logo bundled in `man/figures/` or if the image cannot be
34 | found easily, a generic-looking logo with the package name will be
35 | generated.
36 |
37 | - svg files are internally converted to png
38 | - If the images bundled with a package do not match ‘hex’ or ‘logo’, or
39 | there are multiple possible options, users are prompted to specify
40 | which file to use.
41 |
42 | For a given session with libraries loaded in addition to base packages:
43 |
44 | ``` r
45 | library(hexsession)
46 | make_tile()
47 |
48 | # custom set of packages
49 | make_tile(packages=c("terra","sf","tidyr"))
50 | ```
51 |
52 | The `make_tile()` function will render the HTML output in a new folder
53 | in the working directory using a Quarto template file that will also be
54 | copied to this new directory.
55 |
56 | For a session with the following packages loaded:
57 |
58 | ``` r
59 | library(annotater)
60 | library(ggforce)
61 | library(purrr)
62 | library(forcats)
63 | library(unheadr)
64 | library(sdmTMB)
65 | library(parsnip)
66 | library(DBI)
67 | library(broom)
68 | library(vctrs)
69 | library(patchwork)
70 |
71 |
72 | hexsession::make_tile()
73 | ```
74 |
75 | The output would look like this:  *I don’t
76 | know how to show the rendered interactive file on the GitHub readme, if
77 | anyone does please let me know 😅.*
78 |
79 | Once downloaded to your machine and opened in a browser, the
80 | [hexout_example.html](inst/extdata/hexout_example.html) shows the
81 | interactive, responsive HTML version with cool hover effects that adapts
82 | to the size of the browser window and includes hyperlinks to each
83 | package website.
84 |
85 | To save a static version of the hex tile, we call `snap_tile()` with a
86 | path to the output image and optionally, height and width values to
87 | change the viewport size.
88 |
89 | The result:
90 |
91 | 
92 |
93 | ### Dark mode
94 |
95 | To draw the tiles on a dark background, set `dark_mode` to `TRUE` when
96 | creating or capturing your hex logos.
97 |
98 | ``` r
99 | hexsession::make_tile(dark_mode = TRUE)
100 | hexsession::snap_tile("test.png",dark_mode = TRUE)
101 | ```
102 |
103 | ## User-provided images and urls
104 |
105 | `make_tile` can now take vectors of additional images and their
106 | respective but optional urls to include in the hex grid.
107 |
108 | ``` r
109 | make_tile(packages = c("tidyterra", "sf"),
110 | local_images = c("path/to/your/image1.png",
111 | "path/to/your/image2.png",
112 | local_urls = c("https://an-example.web", "https://another-example.com"))
113 |
114 | ```
115 |
116 | ## Notes
117 |
118 | This packages depends on working installations of magick, Quarto, and
119 | chromote and thus needs a Chromium-based web browser (e.g., Chrome,
120 | Chromium, Opera, or Vivaldi) installation.
121 |
122 | hexsession is very much work in progress and highly experimental. I am
123 | still learning good-practices for packages that create files and
124 | directories, use system commands, and launch browser sessions.
125 |
126 | All feedback is welcome in any form (issues, pull requests, etc.)
127 |
128 | ### Credit and LLM disclosure statement
129 |
130 | - css and html code for the responsive hex grid comes from [this
131 | tutorial](https://css-tricks.com/hexagons-and-beyond-flexible-responsive-grid-patterns-sans-media-queries/)
132 | by Temani Afif.
133 |
134 | - the javascript code to populate the divs in the Quarto template was
135 | written with input from the Claude 3.5 Sonnet LLM running in the
136 | Continue extension in the Positron IDE.
137 |
--------------------------------------------------------------------------------
/_pkgdown.yml:
--------------------------------------------------------------------------------
1 | url: https://luisdva.github.io/hexsession/
2 | template:
3 | bootstrap: 5
4 |
5 |
--------------------------------------------------------------------------------
/cran-comments.md:
--------------------------------------------------------------------------------
1 | ## R CMD check results
2 |
3 | 0 errors | 0 warnings | 1 note
4 |
5 | * This is a new release.
6 |
--------------------------------------------------------------------------------
/docs/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
80 |