| 1 |
#' Check the structure of a templated project |
|
| 2 |
#' |
|
| 3 |
#' Check if the parts of a template are in place, based on the template's spec. |
|
| 4 |
#' |
|
| 5 |
#' @param template Name of the template (e.g., \code{"function"}).
|
|
| 6 |
#' @param name Name of the template instance (e.g., \code{"test_function"}).
|
|
| 7 |
#' @param dir Path to the base directory of the template. |
|
| 8 |
#' @param spec The template's spec; specify if template spec files do not exist. |
|
| 9 |
#' @examples |
|
| 10 |
#' \dontrun{
|
|
| 11 |
#' # precheck before a new template is made |
|
| 12 |
#' check_template("template_new", list(name = "new", context = "new", files = list("new.R")))
|
|
| 13 |
#' |
|
| 14 |
#' # check the new template after creation |
|
| 15 |
#' check_template("new")
|
|
| 16 |
#' } |
|
| 17 |
#' @return A list with results of the check: |
|
| 18 |
#' \tabular{rl}{
|
|
| 19 |
#' \strong{dir} \tab Path of the checked directory. \cr
|
|
| 20 |
#' \strong{files} \tab Path(s) of the checked files. \cr
|
|
| 21 |
#' \strong{spec} \tab A list with the template's spec. \cr
|
|
| 22 |
#' \strong{status} \tab A named logical vector indicating whether each components exist. \cr
|
|
| 23 |
#' \strong{incomplete} \tab A character vector with any existing files that still have template text. \cr
|
|
| 24 |
#' \strong{exists} \tab \code{all(status)} \cr
|
|
| 25 |
#' \strong{message} \tab A character vector including messages associated with failures. \cr
|
|
| 26 |
#' } |
|
| 27 |
#' @export |
|
| 28 | ||
| 29 |
check_template <- function(template, name = "", dir = ".", spec = NULL) {
|
|
| 30 | 45x |
if (missing(template)) {
|
| 31 | ! |
cli_abort("{.arg template} must be specified")
|
| 32 |
} |
|
| 33 | 45x |
template <- sub("^init_", "", template)
|
| 34 | 45x |
report <- list( |
| 35 | 45x |
dir = dir, |
| 36 | 45x |
files = list(), |
| 37 | 45x |
spec = spec, |
| 38 | 45x |
status = c(spec = !is.null(spec), dir = FALSE, strict = TRUE, set = TRUE), |
| 39 | 45x |
incomplete = character(), |
| 40 | 45x |
exists = FALSE, |
| 41 | 45x |
message = character() |
| 42 |
) |
|
| 43 | 45x |
if (is.null(spec)) {
|
| 44 | 45x |
path <- paste0( |
| 45 | 45x |
system.file(package = "community"), |
| 46 | 45x |
if (file.exists(paste0(system.file(package = "community"), "/inst"))) {
|
| 47 | ! |
"/inst" |
| 48 |
}, |
|
| 49 | 45x |
"/specs/", |
| 50 | 45x |
sub(".json", "", template, fixed = TRUE),
|
| 51 | 45x |
".json" |
| 52 |
) |
|
| 53 | 45x |
report$status["spec"] <- file.exists(path) |
| 54 | 45x |
if (!report$status["spec"]) {
|
| 55 | ! |
report$status[] <- FALSE |
| 56 | ! |
return(report) |
| 57 |
} |
|
| 58 | 45x |
spec <- jsonlite::read_json(path) |
| 59 |
} |
|
| 60 | 45x |
report$spec <- spec |
| 61 | 45x |
if (missing(name)) {
|
| 62 | 39x |
name <- spec$name |
| 63 |
} |
|
| 64 | 45x |
strict <- vapply( |
| 65 | 45x |
spec$files, |
| 66 | 45x |
function(f) is.character(f) && length(f) == 1, |
| 67 | 45x |
TRUE |
| 68 |
) |
|
| 69 | 45x |
dir <- paste0(normalizePath(paste0(dir, "/", spec$dir), "/", FALSE), "/") |
| 70 | 45x |
report$dir <- dir |
| 71 | 45x |
report$status["dir"] <- dir.exists(dir) |
| 72 | 45x |
if (spec$context != spec$name) {
|
| 73 | 2x |
check_context <- check_template(spec$context, dir = dir) |
| 74 | 2x |
if (!check_context$exists) {
|
| 75 | ! |
cli_abort(c( |
| 76 | ! |
"context {spec$context} check failed for {name}:",
|
| 77 | ! |
check_context$message |
| 78 |
)) |
|
| 79 |
} |
|
| 80 |
} |
|
| 81 | 45x |
if (!report$status["dir"]) {
|
| 82 | 6x |
report$message <- c( |
| 83 | 6x |
x = paste0( |
| 84 | 6x |
"the required directory ({.path ",
|
| 85 | 6x |
spec$dir, |
| 86 | 6x |
"}) is not present in {.path ",
|
| 87 | 6x |
normalizePath(dir, "/", FALSE), |
| 88 |
"}" |
|
| 89 |
) |
|
| 90 |
) |
|
| 91 |
} |
|
| 92 | 45x |
if (any(strict)) {
|
| 93 | 45x |
files <- gsub( |
| 94 | 45x |
"{name}",
|
| 95 | 45x |
name, |
| 96 | 45x |
paste0(dir, unlist(spec$files[strict])), |
| 97 | 45x |
fixed = TRUE |
| 98 |
) |
|
| 99 | 45x |
report$files <- files |
| 100 | 45x |
present <- file.exists(files) |
| 101 | 45x |
report$status["strict"] <- all(present) |
| 102 | 45x |
if (!report$status["strict"]) {
|
| 103 | 17x |
report$message <- c( |
| 104 | 17x |
report$message, |
| 105 | 17x |
x = paste0( |
| 106 | 17x |
"required file", |
| 107 | 17x |
if (sum(!present) == 1) " is" else "s are", |
| 108 | 17x |
" not present: ", |
| 109 | 17x |
paste0("{.path ", files[!present], "}", collapse = ", ")
|
| 110 |
) |
|
| 111 |
) |
|
| 112 |
} else {
|
|
| 113 | 28x |
for (f in files[present]) {
|
| 114 |
if ( |
|
| 115 | 173x |
!dir.exists(f) && |
| 116 | 173x |
grepl( |
| 117 | 173x |
"<template:", |
| 118 | 173x |
paste(readLines(f, warn = FALSE), collapse = ""), |
| 119 | 173x |
fixed = TRUE |
| 120 |
) |
|
| 121 |
) {
|
|
| 122 | 9x |
report$incomplete <- c(report$incomplete, f) |
| 123 |
} |
|
| 124 |
} |
|
| 125 |
} |
|
| 126 |
} |
|
| 127 | 45x |
if (any(!strict)) {
|
| 128 | ! |
file_set <- spec$files[!strict][[1]] |
| 129 | ! |
if (length(file_set) == 1) {
|
| 130 | ! |
files <- gsub( |
| 131 | ! |
"{name}",
|
| 132 | ! |
spec$name, |
| 133 | ! |
paste0(dir, file_set[[1]]), |
| 134 | ! |
fixed = TRUE |
| 135 |
) |
|
| 136 | ! |
report$files <- c(report$files, files) |
| 137 | ! |
present <- file.exists(files) |
| 138 | ! |
report$status["set"] <- any(present) |
| 139 | ! |
if (!report$status["set"]) {
|
| 140 | ! |
report$message <- c( |
| 141 | ! |
report$message, |
| 142 | ! |
x = paste( |
| 143 | ! |
"one of these files is required, but none were present:", |
| 144 | ! |
paste(files, collapse = ", ") |
| 145 |
) |
|
| 146 |
) |
|
| 147 |
} else {
|
|
| 148 | ! |
for (f in files[present]) {
|
| 149 |
if ( |
|
| 150 | ! |
!dir.exists(f) && |
| 151 | ! |
grepl( |
| 152 | ! |
"<template:", |
| 153 | ! |
paste(readLines(f, warn = FALSE), collapse = ""), |
| 154 | ! |
fixed = TRUE |
| 155 |
) |
|
| 156 |
) {
|
|
| 157 | ! |
report$incomplete <- c(report$incomplete, f) |
| 158 |
} |
|
| 159 |
} |
|
| 160 |
} |
|
| 161 |
} else {
|
|
| 162 | ! |
file_set <- lapply( |
| 163 | ! |
file_set, |
| 164 | ! |
function(fl) gsub("{name}", spec$name, paste0(dir, fl), fixed = TRUE)
|
| 165 |
) |
|
| 166 | ! |
report$files <- c(report$files, unlist(file_set)) |
| 167 | ! |
present <- vapply(file_set, function(fl) all(file.exists(fl)), TRUE) |
| 168 | ! |
report$status["set"] <- any(present) |
| 169 | ! |
if (!report$status["set"]) {
|
| 170 | ! |
report$message <- c( |
| 171 | ! |
report$message, |
| 172 | ! |
paste( |
| 173 | ! |
x = "none of the required file sets were complete:", |
| 174 | ! |
file_set |
| 175 |
) |
|
| 176 |
) |
|
| 177 |
} else {
|
|
| 178 | ! |
for (fl in file_set[present]) {
|
| 179 | ! |
for (f in fl) {
|
| 180 |
if ( |
|
| 181 | ! |
!dir.exists(f) && |
| 182 | ! |
grepl( |
| 183 | ! |
"<template:", |
| 184 | ! |
paste(readLines(f, warn = FALSE), collapse = ""), |
| 185 | ! |
fixed = TRUE |
| 186 |
) |
|
| 187 |
) {
|
|
| 188 | ! |
report$incomplete <- c(report$incomplete, f) |
| 189 |
} |
|
| 190 |
} |
|
| 191 |
} |
|
| 192 |
} |
|
| 193 |
} |
|
| 194 |
} |
|
| 195 | 45x |
if (report$status["dir"] && any(!report$status[c("strict", "set")])) {
|
| 196 | 11x |
report$message <- c( |
| 197 | 11x |
report$message, |
| 198 | 11x |
i = "use {.fn template_{name}} to create required structure"
|
| 199 |
) |
|
| 200 |
} |
|
| 201 | 45x |
if (all(report$status)) {
|
| 202 | 28x |
report$exists <- TRUE |
| 203 |
} |
|
| 204 | 45x |
report |
| 205 |
} |
| 1 |
#' Add a map to a webpage |
|
| 2 |
#' |
|
| 3 |
#' Adds a Leaflet map to a webpage, based on specified or selected inputs. |
|
| 4 |
#' |
|
| 5 |
#' @param shapes A list or list of lists specifying GeoJSON files. Each list should have at least |
|
| 6 |
#' a \code{url} entry (the URL of the file), and will need a \code{name} entry (associating it
|
|
| 7 |
#' with one of the site's datasets) if the site has multiple datasets. The file's features |
|
| 8 |
#' must each have a \code{properties} field containing an ID found in the data -- by default
|
|
| 9 |
#' this is assumed to be called \code{"geoid"}, but this can be specified with an \code{id_property}
|
|
| 10 |
#' entry in the list. For example \code{shapes = list(name = "data", }\code{
|
|
| 11 |
#' url = "https://example.com/shapes.geojson", id_property = "id")}. A \code{time} entry can also
|
|
| 12 |
#' specify different maps for the same dataset, based on the selected time, along with a \code{resolution}
|
|
| 13 |
#' entry to specify how to match the year; either \code{"decade"} (default) or \code{"exact"}.
|
|
| 14 |
#' @param overlays additional layers to add to the map, based on the selected variable; a list or list of |
|
| 15 |
#' lists with entries at least for \code{variable} (name of the variable associated with the layer) and
|
|
| 16 |
#' \code{source} (path to the layer file, or a list with entries including \code{url} and
|
|
| 17 |
#' \code{time}). Entries can also include a \code{filter} entry, with a list or list of lists of conditions,
|
|
| 18 |
#' including entries for \code{feature} (name of the feature on which to condition entity inclusion),
|
|
| 19 |
#' \code{operator}, and \code{value}.
|
|
| 20 |
#' @param color The name of a variable, or id of a variable selector, to be used to color polygons. |
|
| 21 |
#' @param color_time The ID of a selector to specify which timepoint of \code{color} to use.
|
|
| 22 |
#' @param dataview The ID of an \code{\link{input_dataview}} component.
|
|
| 23 |
#' @param id Unique ID for the map. |
|
| 24 |
#' @param click The ID of an input to set to a clicked polygon's ID. |
|
| 25 |
#' @param subto A vector of output IDs to receive hover events from. |
|
| 26 |
#' @param background_shapes The name of a dataset (shapes) to show within a selection, regardless of |
|
| 27 |
#' selected dataset. Useful to show lower-level regions within higher-level regions. |
|
| 28 |
#' @param options A list of configuration options, potentially extracted from a saved leaflet object (see |
|
| 29 |
#' \href{https://leafletjs.com/reference-1.7.1.html#map-example}{Leaflet documentation}).
|
|
| 30 |
#' @param overlays_from_measures Logical; if \code{TRUE}, will look for overlay information in
|
|
| 31 |
#' measurement information. |
|
| 32 |
#' @param tiles A list or list of lists containing provider information (see |
|
| 33 |
#' \href{https://leaflet-extras.github.io/leaflet-providers/preview/}{leaflet providers}; e.g.,
|
|
| 34 |
#' \code{list(}\code{url = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"}, \code{options = list(maxZoom = 19))}).
|
|
| 35 |
#' @param attribution A list with tile attribution information to be included in a credits section. To add |
|
| 36 |
#' attributions to the map, include them in \code{tile}'s \code{options} list.
|
|
| 37 |
#' @examples |
|
| 38 |
#' output_map() |
|
| 39 |
#' @return A character vector of the content to be added. |
|
| 40 |
#' @export |
|
| 41 | ||
| 42 |
output_map <- function( |
|
| 43 |
shapes = NULL, |
|
| 44 |
overlays = NULL, |
|
| 45 |
color = NULL, |
|
| 46 |
color_time = NULL, |
|
| 47 |
dataview = NULL, |
|
| 48 |
id = NULL, |
|
| 49 |
click = NULL, |
|
| 50 |
subto = NULL, |
|
| 51 |
background_shapes = NULL, |
|
| 52 |
options = list(), |
|
| 53 |
overlays_from_measures = TRUE, |
|
| 54 |
tiles = list( |
|
| 55 |
url = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
|
| 56 |
options = list(maxZoom = 19) |
|
| 57 |
), |
|
| 58 |
attribution = list( |
|
| 59 |
name = "OpenStreetMap", |
|
| 60 |
url = "https://www.openstreetmap.org/copyright" |
|
| 61 |
) |
|
| 62 |
) {
|
|
| 63 | 3x |
caller <- parent.frame() |
| 64 | 3x |
building <- !is.null(attr(caller, "name")) && |
| 65 | 3x |
attr(caller, "name") == "community_site_parts" |
| 66 | 3x |
if (is.null(id)) {
|
| 67 | 2x |
id <- paste0("map", caller$uid)
|
| 68 |
} |
|
| 69 | 3x |
if (building) {
|
| 70 | 1x |
dependencies <- jsonlite::read_json(system.file( |
| 71 | 1x |
"dependencies.json", |
| 72 | 1x |
package = "community" |
| 73 |
)) |
|
| 74 | 1x |
caller$dependencies$leaflet_style <- dependencies$leaflet$css |
| 75 | 1x |
caller$dependencies$leaflet <- dependencies$leaflet$js |
| 76 | 1x |
caller$credits$leaflet <- dependencies$leaflet$info |
| 77 | 1x |
options$overlays_from_measures <- overlays_from_measures |
| 78 | 1x |
options$subto <- if (!is.null(subto) && length(subto) == 1) {
|
| 79 | ! |
list(subto) |
| 80 |
} else {
|
|
| 81 | 1x |
subto |
| 82 |
} |
|
| 83 | 1x |
if (is.null(options[["center"]])) {
|
| 84 | 1x |
options$center <- c(40, -95) |
| 85 |
} |
|
| 86 | 1x |
if (is.null(options[["zoom"]])) {
|
| 87 | 1x |
options$zoom <- 4 |
| 88 |
} |
|
| 89 |
if ( |
|
| 90 | 1x |
!is.null(background_shapes) && is.null(options[["background_shapes"]]) |
| 91 |
) {
|
|
| 92 | ! |
options$background_shapes <- background_shapes |
| 93 |
} |
|
| 94 | 1x |
if (is.character(shapes)) {
|
| 95 | ! |
shapes <- lapply(shapes, function(s) list(url = s)) |
| 96 |
} |
|
| 97 | 1x |
if (is.list(shapes) && !is.list(shapes[[1]])) {
|
| 98 | ! |
shapes <- list(shapes) |
| 99 |
} |
|
| 100 | 1x |
snames <- names(shapes) |
| 101 | 1x |
for (i in seq_along(shapes)) {
|
| 102 | ! |
if (!is.null(snames[i])) {
|
| 103 | ! |
shapes[[i]]$name <- snames[i] |
| 104 |
} |
|
| 105 | ! |
if (is.null(shapes[[i]]$id_property)) shapes[[i]]$id_property <- "geoid" |
| 106 |
} |
|
| 107 | 1x |
if (!is.null(overlays)) {
|
| 108 | ! |
if (is.character(overlays)) {
|
| 109 | ! |
overlays <- lapply(overlays, function(s) list(url = s)) |
| 110 |
} |
|
| 111 | ! |
if (is.list(overlays) && !is.list(overlays[[1]])) {
|
| 112 | ! |
overlays <- list(overlays) |
| 113 |
} |
|
| 114 | ! |
snames <- names(overlays) |
| 115 | ! |
for (i in seq_along(overlays)) {
|
| 116 | ! |
if (!is.null(snames[i])) overlays[[i]]$name <- snames[i] |
| 117 |
} |
|
| 118 |
} |
|
| 119 | 1x |
caller$map[[id]] <- Filter( |
| 120 | 1x |
length, |
| 121 | 1x |
list( |
| 122 | 1x |
shapes = unname(shapes), |
| 123 | 1x |
overlays = unname(overlays), |
| 124 | 1x |
options = options, |
| 125 | 1x |
tiles = tiles |
| 126 |
) |
|
| 127 |
) |
|
| 128 |
} |
|
| 129 | 3x |
r <- paste( |
| 130 | 3x |
c( |
| 131 | 3x |
'<div class="auto-output leaflet"', |
| 132 | 3x |
if (!is.null(dataview)) paste0('data-view="', dataview, '"'),
|
| 133 | 3x |
if (!is.null(click)) paste0('data-click="', click, '"'),
|
| 134 | 3x |
if (!is.null(color)) paste0('data-color="', color, '"'),
|
| 135 | 3x |
if (!is.null(color_time)) paste0('data-colorTime="', color_time, '"'),
|
| 136 | 3x |
paste0('id="', id, '"'),
|
| 137 | 3x |
'data-autoType="map"></div>' |
| 138 |
), |
|
| 139 | 3x |
collapse = " " |
| 140 |
) |
|
| 141 | 3x |
if (building) {
|
| 142 | 1x |
caller$content <- c(caller$content, r) |
| 143 | 1x |
if (!missing(attribution) || missing(tiles)) {
|
| 144 | 1x |
if (!is.null(attribution$name)) {
|
| 145 | 1x |
caller$credits[[attribution$name]] <- attribution |
| 146 | ! |
} else if (!is.null(attribution[[1]]$name)) {
|
| 147 | ! |
for (a in attribution) {
|
| 148 | ! |
caller$credits[[a$name]] <- a |
| 149 |
} |
|
| 150 |
} |
|
| 151 |
} |
|
| 152 | 1x |
caller$uid <- caller$uid + 1 |
| 153 |
} |
|
| 154 | 3x |
r |
| 155 |
} |
| 1 |
#' Render a Website |
|
| 2 |
#' |
|
| 3 |
#' Write HTML output from the \code{site.R} file in a site project.
|
|
| 4 |
#' |
|
| 5 |
#' @param dir Path to the site project directory. |
|
| 6 |
#' @param file Name of the R file to build the site from. |
|
| 7 |
#' @param name Name of the HTML file to be created. |
|
| 8 |
#' @param variables A character vector of variable names to include from the data. If no specified, |
|
| 9 |
#' all variables are included. |
|
| 10 |
#' @param options A list with options to be passed to the site. These will be written to \code{docs/settings.json},
|
|
| 11 |
#' which can be edited by hand. |
|
| 12 |
#' @param bundle_data Logical; if \code{TRUE}, will write the data to the site file; useful when
|
|
| 13 |
#' running the site locally without a server (viewing the file directly in a browser). |
|
| 14 |
#' Otherwise, the data will be loaded separately through an http request. |
|
| 15 |
#' @param bundle_package Logical; if \code{TRUE}, will include parts of the \code{datapackage.json} file in the
|
|
| 16 |
#' \code{settings.json} and \code{index.html} files. Otherwise, this will be loaded separately through an http request.
|
|
| 17 |
#' @param bundle_libs Logical; if \code{TRUE}, will download dependencies to the \code{docs/lib} directory.
|
|
| 18 |
#' This can allow you to run the site offline for all but Leaflet tiles and any remote resources specified in |
|
| 19 |
#' \code{file} (such as map shapes) or metadata (such as map overlays).
|
|
| 20 |
#' @param libs_overwrite Logical; if \code{TRUE}, will re-download existing dependencies.
|
|
| 21 |
#' @param libs_base_only Logical; if \code{TRUE}, will only download the base community dependencies to be served locally.
|
|
| 22 |
#' @param remote_data_handler Logical; if \code{FALSE}, will load the data handler script from the site's directory,
|
|
| 23 |
#' (which is updated on rebuild) even when \code{version} is custom. Useful for locally testing an API.
|
|
| 24 |
#' @param open_after Logical; if \code{TRUE}, will open the site in a browser after it is built.
|
|
| 25 |
#' @param aggregate Logical; if \code{TRUE}, and there is a larger datasets with IDs that partially match
|
|
| 26 |
#' IDs in a smaller dataset or that has a map to those IDs, and there are NAs in the smaller dataset, will |
|
| 27 |
#' attempt to fill NAs with averages from the larger dataset. |
|
| 28 |
#' @param sparse_time Logical; if \code{FALSE}, will not trim times from a variable that are all missing.
|
|
| 29 |
#' @param force Logical; if \code{TRUE}, will reprocess data even if the source data is older than the existing
|
|
| 30 |
#' processed version. |
|
| 31 |
#' @param version Version of the base script and stylesheet: \code{"stable"} (default) for the current stable release,
|
|
| 32 |
#' \code{"dev"} for the current unstable release, or \code{"local"} for a copy of the development files
|
|
| 33 |
#' (\code{community.js} and \code{community.css}) served from \code{http://localhost:8000}. Can also
|
|
| 34 |
#' be a URL where files can be found (\code{{version}/community.js} and \code{{version}/community.css}).
|
|
| 35 |
#' @param parent Directory path or repository URL of a data site from which to use data, rather than using local data. |
|
| 36 |
#' @param include_api Logical; if \code{TRUE}, will write the \code{docs/functions/api.js} file.
|
|
| 37 |
#' @param endpoint URL of the served API. |
|
| 38 |
#' @param tag_id Google tag ID (in the form of \code{GTM-XXXXXX}, were \code{GTM-} might be different depending on the
|
|
| 39 |
#' tag type (such as \code{G-} or \code{GT-}); see \href{tagmanager.google.com}{https://tagmanager.google.com}),
|
|
| 40 |
#' which will enables tracking, conditional on the \code{settings.tracking} setting.
|
|
| 41 |
#' @param serve Logical; if \code{TRUE}, starts a local server from the site's \code{docs} directory.
|
|
| 42 |
#' Once a server is running, you can use \code{\link[httpuv]{stopAllServers}} to stop it.
|
|
| 43 |
#' @param host The IPv4 address to listen to if \code{serve} is \code{TRUE}; defaults to \code{"127.0.0.1"}.
|
|
| 44 |
#' @param port The port to listen on if \code{serve} is \code{TRUE}; defaults to 3000.
|
|
| 45 |
#' @param verbose Logical; if \code{FALSE}, will not show status messages.
|
|
| 46 |
#' @examples |
|
| 47 |
#' \dontrun{
|
|
| 48 |
#' # run from within a site project directory, initialized with `init_site()` |
|
| 49 |
#' site_build(".")
|
|
| 50 |
#' |
|
| 51 |
#' # serve locally and view the site |
|
| 52 |
#' site_build(".", serve = TRUE, open_after = TRUE)
|
|
| 53 |
#' } |
|
| 54 |
#' @return Invisible path to the written file. |
|
| 55 |
#' @seealso To initialize a site project, use \code{\link{init_site}}.
|
|
| 56 |
#' @export |
|
| 57 | ||
| 58 |
site_build <- function( |
|
| 59 |
dir, |
|
| 60 |
file = "site.R", |
|
| 61 |
name = "index.html", |
|
| 62 |
variables = NULL, |
|
| 63 |
options = list(), |
|
| 64 |
bundle_data = FALSE, |
|
| 65 |
bundle_package = FALSE, |
|
| 66 |
bundle_libs = FALSE, |
|
| 67 |
libs_overwrite = FALSE, |
|
| 68 |
libs_base_only = FALSE, |
|
| 69 |
remote_data_handler = TRUE, |
|
| 70 |
open_after = FALSE, |
|
| 71 |
aggregate = TRUE, |
|
| 72 |
sparse_time = TRUE, |
|
| 73 |
force = FALSE, |
|
| 74 |
version = "stable", |
|
| 75 |
parent = NULL, |
|
| 76 |
include_api = FALSE, |
|
| 77 |
endpoint = NULL, |
|
| 78 |
tag_id = NULL, |
|
| 79 |
serve = FALSE, |
|
| 80 |
host = "127.0.0.1", |
|
| 81 |
port = 3000, |
|
| 82 |
verbose = TRUE |
|
| 83 |
) {
|
|
| 84 | 1x |
if (missing(dir)) {
|
| 85 | ! |
cli_abort('{.arg dir} must be specified (e.g., dir = ".")')
|
| 86 |
} |
|
| 87 | 1x |
page <- paste0(dir, "/", file) |
| 88 | 1x |
if (!file.exists(page)) {
|
| 89 | ! |
cli_abort("{.file {page}} does not exist")
|
| 90 |
} |
|
| 91 | 1x |
out <- paste(c(dir, "docs", name), collapse = "/") |
| 92 | 1x |
data_preprocess <- function(aggregate) {
|
| 93 | 1x |
ddir <- paste0(dir, "/docs/data/") |
| 94 | 1x |
f <- paste0(ddir, "datapackage.json") |
| 95 | 1x |
if (!file.exists(f)) {
|
| 96 | ! |
sf <- list.files( |
| 97 | ! |
dir, |
| 98 | ! |
"datapackage\\.json$", |
| 99 | ! |
recursive = TRUE, |
| 100 | ! |
full.names = TRUE |
| 101 |
) |
|
| 102 | ! |
if (length(sf)) {
|
| 103 | ! |
f <- sf[[1]] |
| 104 | ! |
bundle_package <<- TRUE |
| 105 | ! |
cli_warn("datapackage was not in {.path {ddir}}, so bundling it")
|
| 106 | ! |
ddir <- paste0(dirname(f), "/") |
| 107 |
} |
|
| 108 |
} |
|
| 109 | 1x |
path <- paste0(dir, "/docs/") |
| 110 | 1x |
info <- meta <- list() |
| 111 | 1x |
vars <- variables |
| 112 | 1x |
if (!is.null(parent) && (force || !file.exists(f) || file.size(f) < 250)) {
|
| 113 | ! |
if (file.exists(paste0(parent, "/docs/data/datapackage.json"))) {
|
| 114 | ! |
f <- paste0(parent, "/docs/data/datapackage.json") |
| 115 |
} else {
|
|
| 116 | ! |
tryCatch( |
| 117 | ! |
download.file( |
| 118 | ! |
paste0(parent, "/data/datapackage.json"), |
| 119 | ! |
f, |
| 120 | ! |
quiet = TRUE |
| 121 |
), |
|
| 122 | ! |
error = function(e) NULL |
| 123 |
) |
|
| 124 |
} |
|
| 125 |
} |
|
| 126 | 1x |
time_vars <- NULL |
| 127 | 1x |
if (file.exists(f)) {
|
| 128 | 1x |
meta <- jsonlite::read_json(f) |
| 129 | 1x |
previous_data <- list() |
| 130 | 1x |
ids_maps <- list() |
| 131 | 1x |
ids_maps_paths <- NULL |
| 132 | 1x |
child <- id_lengths <- NULL |
| 133 | 1x |
dataset_order <- order(-vapply(meta$resources, "[[", 0, "bytes")) |
| 134 | 1x |
var_codes <- unique(unlist( |
| 135 | 1x |
lapply( |
| 136 | 1x |
meta$resources, |
| 137 | 1x |
function(d) vapply(d$schema$fields, "[[", "", "name") |
| 138 |
), |
|
| 139 | 1x |
use.names = FALSE |
| 140 |
)) |
|
| 141 | 1x |
var_codes <- structure( |
| 142 | 1x |
paste0("X", seq_along(var_codes)),
|
| 143 | 1x |
names = var_codes |
| 144 |
) |
|
| 145 | 1x |
for (oi in seq_along(dataset_order)) {
|
| 146 | 1x |
i <- dataset_order[oi] |
| 147 | 1x |
d <- meta$resources[[i]] |
| 148 | 1x |
temp <- list() |
| 149 | 1x |
time_vars <- c(time_vars, d$time) |
| 150 | 1x |
for (v in d$schema$fields) {
|
| 151 | 11x |
if ((length(d$time) && v$name == d$time[[1]]) || v$name %in% vars) {
|
| 152 | ! |
temp[[v$name]] <- v |
| 153 |
} |
|
| 154 |
} |
|
| 155 | 1x |
if (length(variables)) {
|
| 156 | ! |
vars <- vars[vars %in% names(temp)] |
| 157 | ! |
if (!identical(vars, variables)) {
|
| 158 | ! |
cli_warn(paste0( |
| 159 | ! |
"{?a requested variable was/some requested variables were} not present in {.file ",
|
| 160 | ! |
d$filename, |
| 161 |
"}:", |
|
| 162 | ! |
" {.val {variables[!variables %in% vars]}}"
|
| 163 |
)) |
|
| 164 |
} |
|
| 165 | ! |
d$schema$fields <- unname(temp[vars]) |
| 166 |
} |
|
| 167 | 1x |
if (is.null(parent)) {
|
| 168 | 1x |
file <- paste0(ddir, d$filename) |
| 169 | 1x |
path <- paste0(dir, "/docs/", d$name, ".json") |
| 170 | 1x |
if (file.exists(file)) {
|
| 171 | 1x |
if (length(d$ids)) {
|
| 172 | 1x |
for (i in seq_along(d$ids)) {
|
| 173 | 1x |
if ( |
| 174 | 1x |
length(d$ids[[i]]$map) == 1 && |
| 175 | 1x |
is.character(d$ids[[i]]$map) && |
| 176 | 1x |
file.exists(paste0(dir, "/docs/", d$ids[[i]]$map)) |
| 177 |
) {
|
|
| 178 | ! |
ids_maps_paths <- c(ids_maps_paths, d$ids[[i]]$map) |
| 179 |
} |
|
| 180 |
} |
|
| 181 |
} |
|
| 182 | 1x |
if ( |
| 183 | 1x |
force || |
| 184 | 1x |
(!file.exists(path) || file.mtime(file) > file.mtime(path)) |
| 185 |
) {
|
|
| 186 | 1x |
if (verbose) {
|
| 187 | 1x |
cli_progress_step( |
| 188 | 1x |
"processing {d$name}",
|
| 189 | 1x |
msg_done = paste("processed", d$name)
|
| 190 |
) |
|
| 191 |
} |
|
| 192 | 1x |
sep <- if (grepl(".csv", file, fixed = TRUE)) "," else "\t"
|
| 193 | 1x |
cols <- scan(file, "", nlines = 1, sep = sep, quiet = TRUE) |
| 194 | 1x |
vars <- vapply(d$schema$fields, "[[", "", "name") |
| 195 | 1x |
types <- vapply( |
| 196 | 1x |
d$schema$fields, |
| 197 | 1x |
function(e) if (e$type == "string") "c" else "n", |
| 198 |
"" |
|
| 199 |
) |
|
| 200 | 1x |
names(types) <- vars |
| 201 | 1x |
if (length(d$ids) && length(d$ids[[1]]$variable)) {
|
| 202 | 1x |
types[d$ids[[1]]$variable] <- "c" |
| 203 |
} |
|
| 204 | 1x |
types <- types[cols] |
| 205 | 1x |
types[is.na(types)] <- "-" |
| 206 | 1x |
data <- as.data.frame(read_delim_arrow( |
| 207 | 1x |
gzfile(file), |
| 208 | 1x |
sep, |
| 209 | 1x |
col_names = cols, |
| 210 | 1x |
col_types = paste(types, collapse = ""), |
| 211 | 1x |
skip = 1 |
| 212 |
)) |
|
| 213 | 1x |
time <- NULL |
| 214 | 1x |
if (length(d$time) && d$time[[1]] %in% colnames(data)) {
|
| 215 | ! |
time <- d$time[[1]] |
| 216 | ! |
data <- data[order(data[[d$time[[1]]]]), ] |
| 217 |
} |
|
| 218 | 1x |
if (length(d$ids) && d$ids[[1]]$variable %in% colnames(data)) {
|
| 219 | 1x |
ids <- gsub( |
| 220 | 1x |
"^\\s+|\\s+$", |
| 221 |
"", |
|
| 222 | 1x |
format(data[[d$ids[[1]]$variable]], scientific = FALSE) |
| 223 |
) |
|
| 224 | 1x |
if (is.null(time) && anyDuplicated(ids)) {
|
| 225 | ! |
cli_abort(paste( |
| 226 | ! |
"no time variable was specified, yet {?an id was/ids were} duplicated:",
|
| 227 | ! |
"{.val {unique(ids[duplicated(ids)])}}"
|
| 228 |
)) |
|
| 229 |
} |
|
| 230 | 1x |
data <- data[, |
| 231 | 1x |
colnames(data) != d$ids[[1]]$variable, |
| 232 | 1x |
drop = FALSE |
| 233 |
] |
|
| 234 |
} else {
|
|
| 235 | ! |
ids <- rownames(data) |
| 236 |
} |
|
| 237 | 1x |
rownames(data) <- NULL |
| 238 | 1x |
sdata <- split(data, ids) |
| 239 |
# aggregating if needed |
|
| 240 | 1x |
pn <- nchar(names(sdata)[1]) |
| 241 | 1x |
fixed_ids <- pn > 1 && |
| 242 | 1x |
all(nchar(names(sdata)) == pn) && |
| 243 | 1x |
!any(grepl("[^0-9]", names(sdata)))
|
| 244 | 1x |
aggregated <- FALSE |
| 245 | 1x |
if (aggregate && length(previous_data) && anyNA(data)) {
|
| 246 | ! |
cn <- colnames(sdata[[1]]) |
| 247 | ! |
ids_map <- NULL |
| 248 | ! |
if (length(d$ids)) {
|
| 249 | ! |
if (is.character(d$ids[[1]]$map)) {
|
| 250 | ! |
mf <- paste0( |
| 251 | ! |
c(dir, ""), |
| 252 | ! |
rep(c("", "/docs/"), each = 2),
|
| 253 |
"/", |
|
| 254 | ! |
d$ids[[1]]$map |
| 255 |
) |
|
| 256 | ! |
mf <- mf[file.exists(mf)] |
| 257 | ! |
ids_map <- if (!is.null(ids_maps[[d$ids[[1]]$map]])) {
|
| 258 | ! |
ids_maps[[d$ids[[1]]$map]] |
| 259 |
} else {
|
|
| 260 | ! |
if (verbose) {
|
| 261 | ! |
cli_progress_update(status = "loading ID map") |
| 262 |
} |
|
| 263 | ! |
tryCatch( |
| 264 | ! |
jsonlite::read_json( |
| 265 | ! |
if (length(mf)) mf[[1]] else d$ids[[1]]$map |
| 266 |
), |
|
| 267 | ! |
error = function(e) {
|
| 268 | ! |
cli_alert_warning( |
| 269 | ! |
"failed to read ID map: {e$message}"
|
| 270 |
) |
|
| 271 |
} |
|
| 272 |
) |
|
| 273 |
} |
|
| 274 | ! |
ids_maps[[d$ids[[1]]$map]] <- ids_map |
| 275 | 1x |
if ( |
| 276 | ! |
((length(mf) && |
| 277 | ! |
!grepl("/docs/", mf[[1]], fixed = TRUE)) ||
|
| 278 | ! |
bundle_data) && |
| 279 | ! |
!is.null(ids_map) |
| 280 |
) {
|
|
| 281 | ! |
d$ids[[1]]$map <- ids_map |
| 282 |
} |
|
| 283 |
} else {
|
|
| 284 | ! |
ids_map <- d$ids[[1]]$map |
| 285 |
} |
|
| 286 |
} |
|
| 287 | ! |
cids <- NULL |
| 288 | ! |
for (pname in rev(names(previous_data))) {
|
| 289 | 1x |
if ( |
| 290 | ! |
pname %in% |
| 291 | ! |
names(ids_map) && |
| 292 | ! |
length(ids_map[[pname]]) && |
| 293 | ! |
!is.null(ids_map[[pname]][[1]][[d$name]]) |
| 294 |
) {
|
|
| 295 | ! |
child <- pname |
| 296 | ! |
cids <- vapply( |
| 297 | ! |
ids_map[[pname]], |
| 298 | ! |
function(e) {
|
| 299 | ! |
if (is.null(e[[d$name]])) "" else e[[d$name]] |
| 300 |
}, |
|
| 301 |
"" |
|
| 302 | ! |
)[names(previous_data[[pname]])] |
| 303 | ! |
break |
| 304 | 1x |
} else if ( |
| 305 | ! |
fixed_ids && |
| 306 | ! |
pname %in% names(id_lengths) && |
| 307 | ! |
id_lengths[[pname]] > pn |
| 308 |
) {
|
|
| 309 | ! |
child <- pname |
| 310 | ! |
cids <- substr(names(previous_data[[pname]]), 1, pn) |
| 311 | ! |
break |
| 312 |
} |
|
| 313 |
} |
|
| 314 | 1x |
if ( |
| 315 | ! |
!is.null(child) && |
| 316 | ! |
any(cn %in% names(previous_data[[child]][[1]])) && |
| 317 | ! |
!is.null(cids) |
| 318 |
) {
|
|
| 319 | ! |
if (verbose) {
|
| 320 | ! |
cli_progress_update( |
| 321 | ! |
status = "attempting aggregation from {child}"
|
| 322 |
) |
|
| 323 |
} |
|
| 324 | ! |
for (id in names(sdata)) {
|
| 325 | ! |
did <- sdata[[id]] |
| 326 | ! |
if (anyNA(did)) {
|
| 327 | ! |
children <- which(cids == id) |
| 328 | ! |
if (length(children)) {
|
| 329 | ! |
cd <- do.call(rbind, previous_data[[child]][children]) |
| 330 | ! |
if (is.null(time)) {
|
| 331 | ! |
aggs <- vapply( |
| 332 | ! |
cd, |
| 333 | ! |
function(v) {
|
| 334 | ! |
if (is.numeric(v) && !all(is.na(v))) {
|
| 335 | ! |
mean(v, na.rm = TRUE) |
| 336 |
} else {
|
|
| 337 | ! |
NA |
| 338 |
} |
|
| 339 |
}, |
|
| 340 | ! |
0 |
| 341 |
) |
|
| 342 | ! |
aggs <- aggs[ |
| 343 | ! |
!is.na(aggs) & |
| 344 | ! |
names(aggs) %in% cn & |
| 345 | ! |
names(aggs) != "time" |
| 346 |
] |
|
| 347 | ! |
aggs <- aggs[is.na(sdata[[id]][, names(aggs)])] |
| 348 | ! |
if (length(aggs)) {
|
| 349 | ! |
aggregated <- TRUE |
| 350 | ! |
sdata[[id]][, names(aggs)] <- aggs |
| 351 |
} |
|
| 352 |
} else {
|
|
| 353 | ! |
cd <- split(cd, cd[[time]]) |
| 354 | ! |
for (ct in names(cd)) {
|
| 355 | ! |
aggs <- vapply( |
| 356 | ! |
cd[[ct]], |
| 357 | ! |
function(v) {
|
| 358 | ! |
if (is.numeric(v) && !all(is.na(v))) {
|
| 359 | ! |
mean(v, na.rm = TRUE) |
| 360 |
} else {
|
|
| 361 | ! |
NA |
| 362 |
} |
|
| 363 |
}, |
|
| 364 | ! |
0 |
| 365 |
) |
|
| 366 | ! |
aggs <- aggs[!is.na(aggs) & names(aggs) %in% cn] |
| 367 | ! |
if (length(aggs)) {
|
| 368 | ! |
su <- sdata[[id]][[time]] == ct |
| 369 | ! |
aggs <- aggs[is.na(sdata[[id]][su, names(aggs)])] |
| 370 | ! |
if (length(aggs)) {
|
| 371 | ! |
aggregated <- TRUE |
| 372 | ! |
sdata[[id]][su, names(aggs)] <- aggs |
| 373 |
} |
|
| 374 |
} |
|
| 375 |
} |
|
| 376 |
} |
|
| 377 |
} |
|
| 378 |
} |
|
| 379 |
} |
|
| 380 |
} |
|
| 381 |
} |
|
| 382 | 1x |
data <- do.call(rbind, sdata) |
| 383 | 1x |
times <- if (is.null(time)) rep(1, nrow(data)) else data[[time]] |
| 384 | 1x |
ntimes <- length(unique(times)) |
| 385 | 1x |
if (fixed_ids) {
|
| 386 | ! |
id_lengths[d$name] <- pn |
| 387 |
} |
|
| 388 | 1x |
previous_data[[d$name]] <- sdata |
| 389 | 1x |
evars <- vars |
| 390 | 1x |
if (!length(evars)) {
|
| 391 | ! |
evars <- colnames(data)[colnames(data) %in% names(var_codes)] |
| 392 |
} |
|
| 393 | 1x |
if (!is.null(time) && time %in% evars) {
|
| 394 | ! |
evars <- evars[evars != time] |
| 395 |
} |
|
| 396 | 1x |
evars <- evars[evars %in% names(var_codes)] |
| 397 | 1x |
var_meta <- lapply(evars, function(vn) {
|
| 398 | 11x |
list( |
| 399 | 11x |
code = var_codes[[vn]], |
| 400 | 11x |
time_range = if (sparse_time) {
|
| 401 | 11x |
v <- data[[vn]] |
| 402 | 11x |
range <- which(unname(tapply( |
| 403 | 11x |
v, |
| 404 | 11x |
times, |
| 405 | 11x |
function(sv) !all(is.na(sv)) |
| 406 |
))) - |
|
| 407 | 11x |
1 |
| 408 | 11x |
if (length(range)) {
|
| 409 | 11x |
range[c(1, length(range))] |
| 410 |
} else {
|
|
| 411 | ! |
c(-1, -1) |
| 412 |
} |
|
| 413 |
} else {
|
|
| 414 | ! |
c(0, ntimes - 1) |
| 415 |
} |
|
| 416 |
) |
|
| 417 |
}) |
|
| 418 | 1x |
names(var_meta) <- evars |
| 419 | 1x |
if (verbose) {
|
| 420 | 1x |
cli_progress_update(status = "finalizing {d$name}")
|
| 421 |
} |
|
| 422 | 1x |
sdata <- lapply(sdata, function(e) {
|
| 423 | 32x |
e <- e[, evars, drop = FALSE] |
| 424 | 32x |
e <- as.list(e) |
| 425 | 32x |
if (sparse_time) {
|
| 426 | 32x |
for (f in evars) {
|
| 427 | 352x |
if (f %in% names(e)) {
|
| 428 | 352x |
e[[f]] <- if ( |
| 429 | 352x |
var_meta[[f]]$time_range[[1]] == -1 || |
| 430 | 352x |
all(is.na(e[[f]])) |
| 431 |
) {
|
|
| 432 | ! |
NULL |
| 433 |
} else {
|
|
| 434 | 352x |
e[[f]][ |
| 435 | 352x |
seq( |
| 436 | 352x |
var_meta[[f]]$time_range[[1]], |
| 437 | 352x |
var_meta[[f]]$time_range[[2]] |
| 438 |
) + |
|
| 439 | 352x |
1 |
| 440 |
] |
|
| 441 |
} |
|
| 442 |
} |
|
| 443 |
} |
|
| 444 |
} |
|
| 445 | 32x |
names(e) <- var_codes[names(e)] |
| 446 | 32x |
e |
| 447 |
}) |
|
| 448 | 1x |
sdata[["_meta"]] <- list( |
| 449 | 1x |
time = list( |
| 450 | 1x |
value = unique(times), |
| 451 | 1x |
name = d$time |
| 452 |
), |
|
| 453 | 1x |
variables = Filter( |
| 454 | 1x |
function(l) l$time_range[1] != -1 && l$time_range[2] != -1, |
| 455 | 1x |
var_meta |
| 456 |
) |
|
| 457 |
) |
|
| 458 | 1x |
if (verbose) {
|
| 459 | 1x |
cli_progress_update(status = "writing {d$name}")
|
| 460 |
} |
|
| 461 | 1x |
jsonlite::write_json( |
| 462 | 1x |
sdata, |
| 463 | 1x |
path, |
| 464 | 1x |
auto_unbox = TRUE, |
| 465 | 1x |
digits = 6, |
| 466 | 1x |
dataframe = "row" |
| 467 |
) |
|
| 468 | 1x |
if (verbose) cli_progress_done("wrote {d$name} site file")
|
| 469 |
} |
|
| 470 |
} else {
|
|
| 471 | ! |
cli_alert_warning("file does not exist: {.path {file}}")
|
| 472 |
} |
|
| 473 |
} |
|
| 474 | 1x |
info[[d$name]] <- d |
| 475 |
} |
|
| 476 |
} else {
|
|
| 477 | ! |
data_files <- list.files(ddir, "\\.(?:csv|tsv|txt)") |
| 478 | ! |
if (length(data_files)) {
|
| 479 | ! |
init_data( |
| 480 | ! |
sub("^.*/", "", normalizePath(dir, "/", FALSE)),
|
| 481 | ! |
dir = dir, |
| 482 | ! |
filename = data_files |
| 483 |
) |
|
| 484 | ! |
if (file.exists(f)) {
|
| 485 | ! |
return(data_preprocess(aggregate)) |
| 486 |
} |
|
| 487 |
} |
|
| 488 |
} |
|
| 489 | 1x |
if (length(info)) {
|
| 490 | 1x |
Filter( |
| 491 | 1x |
length, |
| 492 | 1x |
list( |
| 493 | 1x |
url = if (is.null(parent)) "" else parent, |
| 494 | 1x |
package = sub(paste0(dir, "/docs/"), "", f, fixed = TRUE), |
| 495 | 1x |
datasets = if (length(meta$resources) == 1) {
|
| 496 | 1x |
list(names(info)) |
| 497 |
} else {
|
|
| 498 | ! |
names(info) |
| 499 |
}, |
|
| 500 | 1x |
variables = if (!is.null(variables)) vars[!vars %in% time_vars], |
| 501 | 1x |
info = info, |
| 502 | 1x |
measure_info = meta$measure_info, |
| 503 | 1x |
entity_info = ids_maps_paths, |
| 504 | 1x |
files = vapply(info, "[[", "", "filename") |
| 505 |
) |
|
| 506 |
) |
|
| 507 |
} |
|
| 508 |
} |
|
| 509 | 1x |
path <- paste0(dir, "/docs/settings.json") |
| 510 | 1x |
settings <- if (file.exists(path) && file.size(path)) {
|
| 511 | ! |
jsonlite::read_json(path) |
| 512 |
} else {
|
|
| 513 | 1x |
list(settings = options) |
| 514 |
} |
|
| 515 | 1x |
defaults <- list( |
| 516 | 1x |
digits = 2, |
| 517 | 1x |
summary_selection = "all", |
| 518 | 1x |
color_by_order = FALSE, |
| 519 | 1x |
boxplots = TRUE, |
| 520 | 1x |
theme_dark = FALSE, |
| 521 | 1x |
partial_init = TRUE, |
| 522 | 1x |
palette = "vik", |
| 523 | 1x |
hide_url_parameters = FALSE, |
| 524 | 1x |
background_shapes = TRUE, |
| 525 | 1x |
background_top = FALSE, |
| 526 | 1x |
background_polygon_outline = 2, |
| 527 | 1x |
polygon_outline = 1.5, |
| 528 | 1x |
iqr_box = TRUE, |
| 529 | 1x |
color_scale_center = "none", |
| 530 | 1x |
table_autoscroll = TRUE, |
| 531 | 1x |
table_scroll_behavior = "smooth", |
| 532 | 1x |
table_autosort = TRUE, |
| 533 | 1x |
hide_tooltips = FALSE, |
| 534 | 1x |
map_animations = "all", |
| 535 | 1x |
trace_limit = 20, |
| 536 | 1x |
map_overlay = TRUE, |
| 537 | 1x |
circle_radius = 7, |
| 538 | 1x |
tracking = FALSE, |
| 539 | 1x |
show_empty_times = FALSE |
| 540 |
) |
|
| 541 | 1x |
for (s in names(defaults)) {
|
| 542 | 24x |
if (!is.null(options[[s]])) {
|
| 543 | ! |
settings$settings[[s]] <- options[[s]] |
| 544 | 24x |
} else if (is.null(settings$settings[[s]])) {
|
| 545 | 24x |
settings$settings[[s]] <- defaults[[s]] |
| 546 |
} |
|
| 547 |
} |
|
| 548 | 1x |
times <- unname(vapply( |
| 549 | 1x |
settings$metadata$info, |
| 550 | 1x |
function(d) if (length(d$time)) d$time else "", |
| 551 |
"" |
|
| 552 |
)) |
|
| 553 | 1x |
times <- times[times != ""] |
| 554 | 1x |
if (!is.null(variables)) {
|
| 555 | ! |
variables <- variables[!grepl("^_", variables)]
|
| 556 |
} |
|
| 557 |
if ( |
|
| 558 | 1x |
(is.null(settings$aggregated) || settings$aggregated != aggregate) || |
| 559 | 1x |
(length(variables) && |
| 560 | 1x |
!is.null(settings$metadata) && |
| 561 | 1x |
length(settings$metadata$variables) && |
| 562 | 1x |
!identical( |
| 563 | 1x |
as.character(settings$metadata$variables), |
| 564 | 1x |
variables[!variables %in% times] |
| 565 |
)) |
|
| 566 |
) {
|
|
| 567 | 1x |
force <- TRUE |
| 568 |
} |
|
| 569 | 1x |
if (!is.null(variables)) {
|
| 570 | ! |
variables <- unique(c(times, variables)) |
| 571 |
} |
|
| 572 | 1x |
settings$metadata <- data_preprocess(aggregate) |
| 573 | 1x |
measure_info <- settings$metadata$measure_info |
| 574 | 1x |
coverage_file <- paste0(dir, "/docs/data/coverage.csv") |
| 575 | 1x |
if (file.exists(coverage_file)) {
|
| 576 | ! |
coverage <- read.csv(coverage_file, row.names = 1) |
| 577 | ! |
have_metadata <- unique( |
| 578 | ! |
if (!is.null(measure_info)) {
|
| 579 | ! |
vapply( |
| 580 | ! |
names(measure_info), |
| 581 | ! |
function(v) if (!is.null(measure_info[[v]]$short_name)) v else "", |
| 582 |
"" |
|
| 583 |
) |
|
| 584 |
} else {
|
|
| 585 | ! |
unlist( |
| 586 | ! |
lapply(settings$metadata$info, function(d) {
|
| 587 | ! |
vapply( |
| 588 | ! |
d$schema$fields, |
| 589 | ! |
function(e) if (!is.null(e$info$short_name)) e$name else "", |
| 590 |
"" |
|
| 591 |
) |
|
| 592 |
}), |
|
| 593 | ! |
use.names = FALSE |
| 594 |
) |
|
| 595 |
} |
|
| 596 |
) |
|
| 597 | ! |
if (length(have_metadata)) {
|
| 598 | ! |
if (!is.null(measure_info)) {
|
| 599 | ! |
have_metadata <- unique(c( |
| 600 | ! |
have_metadata, |
| 601 | ! |
names(render_info_names(measure_info)) |
| 602 |
)) |
|
| 603 |
} |
|
| 604 | ! |
metadata_bin <- structure( |
| 605 | ! |
numeric(nrow(coverage)), |
| 606 | ! |
names = rownames(coverage) |
| 607 |
) |
|
| 608 | ! |
metadata_bin[have_metadata[have_metadata %in% names(metadata_bin)]] <- 1 |
| 609 |
if ( |
|
| 610 | ! |
is.null(coverage$metadata) || !all(coverage$metadata == metadata_bin) |
| 611 |
) {
|
|
| 612 | ! |
write.csv( |
| 613 | ! |
cbind( |
| 614 | ! |
metadata = metadata_bin, |
| 615 | ! |
coverage[, colnames(coverage) != "metadata"] |
| 616 |
), |
|
| 617 | ! |
coverage_file |
| 618 |
) |
|
| 619 |
} |
|
| 620 |
} |
|
| 621 |
} |
|
| 622 | 1x |
parts <- make_build_environment() |
| 623 | 1x |
stable <- version == "stable" || grepl("^[Vv]\\d", version)
|
| 624 | 1x |
dependencies <- jsonlite::read_json(system.file( |
| 625 | 1x |
"dependencies.json", |
| 626 | 1x |
package = "community" |
| 627 |
)) |
|
| 628 | 1x |
parts$dependencies <- c( |
| 629 | 1x |
if (stable) {
|
| 630 | 1x |
list( |
| 631 | 1x |
base_style = list( |
| 632 | 1x |
type = "stylesheet", |
| 633 | 1x |
src = "https://miserman.github.io/community/dist/css/community.v2.min.css" |
| 634 |
), |
|
| 635 | 1x |
base = list( |
| 636 | 1x |
type = "script", |
| 637 | 1x |
loading = "", |
| 638 | 1x |
src = "https://miserman.github.io/community/dist/js/community.v2.min.js" |
| 639 |
) |
|
| 640 |
) |
|
| 641 | 1x |
} else if (version == "dev") {
|
| 642 | ! |
list( |
| 643 | ! |
base_style = list( |
| 644 | ! |
type = "stylesheet", |
| 645 | ! |
src = "https://miserman.github.io/community/dist/css/community.min.css" |
| 646 |
), |
|
| 647 | ! |
base = list( |
| 648 | ! |
type = "script", |
| 649 | ! |
loading = "", |
| 650 | ! |
src = "https://miserman.github.io/community/dist/js/community.min.js" |
| 651 |
) |
|
| 652 |
) |
|
| 653 |
} else {
|
|
| 654 | ! |
if (version == "local") {
|
| 655 | ! |
version <- "http://localhost:8000" |
| 656 |
} |
|
| 657 | ! |
if (verbose) {
|
| 658 | ! |
cli_alert_info( |
| 659 | ! |
"loading resources from {.url {if (grepl('^http', version)) version else paste0('http://', host, ':', port, '/', version)}}"
|
| 660 |
) |
|
| 661 |
} |
|
| 662 | ! |
list( |
| 663 | ! |
base_style = list( |
| 664 | ! |
type = "stylesheet", |
| 665 | ! |
src = paste0(version, "/community.css") |
| 666 |
), |
|
| 667 | ! |
base = list( |
| 668 | ! |
type = "script", |
| 669 | ! |
loading = "", |
| 670 | ! |
src = paste0(version, "/community.js") |
| 671 |
) |
|
| 672 |
) |
|
| 673 |
}, |
|
| 674 | 1x |
c( |
| 675 | 1x |
lapply( |
| 676 | 1x |
structure(names(cache_scripts), names = names(cache_scripts)), |
| 677 | 1x |
function(f) {
|
| 678 | 1x |
cached <- cache_scripts[[f]][[if (stable) "stable" else "dev"]] |
| 679 | 1x |
dir.create(paste0(dir, "/", cached$location), FALSE, TRUE) |
| 680 | 1x |
scripts <- paste0( |
| 681 | 1x |
sub("(?:\\.v2)?(?:\\.min)?\\.js", "", basename(cached$source)),
|
| 682 | 1x |
c("", ".min", ".v2.min"),
|
| 683 | 1x |
".js" |
| 684 |
) |
|
| 685 | 1x |
script <- scripts[stable + 2] |
| 686 | 1x |
lf <- paste0(dir, "/", cached$location, "/", script) |
| 687 | 1x |
lff <- paste0("dist/dev/", sub(".min", "", script, fixed = TRUE))
|
| 688 | 1x |
if (stable || version == "dev") {
|
| 689 | 1x |
lff <- paste0(dir, "/docs/dist/docs/dist/js/", script) |
| 690 | 1x |
if (file.exists(lff) && md5sum(lff)[[1]] == cached$md5) {
|
| 691 | ! |
file.copy(lff, lf, TRUE) |
| 692 | ! |
file.copy(paste0(lff, ".map"), paste0(lf, ".map"), TRUE) |
| 693 |
} |
|
| 694 | 1x |
unlink(paste0( |
| 695 | 1x |
dir, |
| 696 |
"/", |
|
| 697 | 1x |
cached$location, |
| 698 |
"/", |
|
| 699 | 1x |
scripts[scripts != script] |
| 700 |
)) |
|
| 701 | 1x |
if (!file.exists(lf) || md5sum(lf)[[1]] != cached$md5) {
|
| 702 | 1x |
tryCatch( |
| 703 | 1x |
download.file(cached$source, lf, quiet = TRUE), |
| 704 | 1x |
error = function(e) NULL |
| 705 |
) |
|
| 706 | 1x |
tryCatch( |
| 707 | 1x |
download.file( |
| 708 | 1x |
paste0(cached$source, ".map"), |
| 709 | 1x |
paste0(lf, ".map"), |
| 710 | 1x |
quiet = TRUE |
| 711 |
), |
|
| 712 | 1x |
error = function(e) NULL |
| 713 |
) |
|
| 714 |
} |
|
| 715 | 1x |
if (!file.exists(lf)) {
|
| 716 | ! |
cli_abort("failed to download script from {cached$source}")
|
| 717 |
} |
|
| 718 | 1x |
list(type = "script", src = sub("^.*docs/", "", lf))
|
| 719 |
} else {
|
|
| 720 | ! |
lff <- paste0(version, "/data_handler.js") |
| 721 | ! |
if (file.exists(lff)) {
|
| 722 | ! |
file.copy(lff, lf, TRUE) |
| 723 | ! |
} else if (!file.exists(lf) || md5sum(lf)[[1]] != cached$md5) {
|
| 724 | ! |
tryCatch( |
| 725 | ! |
download.file(lff, lf, quiet = TRUE), |
| 726 | ! |
error = function(e) NULL |
| 727 |
) |
|
| 728 |
} |
|
| 729 | ! |
if (!file.exists(lf)) {
|
| 730 | ! |
cli_abort("failed to retrieve script from {lff}")
|
| 731 |
} |
|
| 732 | ! |
list( |
| 733 | ! |
type = "script", |
| 734 | ! |
src = if (remote_data_handler) lff else sub("^.*docs/", "", lf)
|
| 735 |
) |
|
| 736 |
} |
|
| 737 |
} |
|
| 738 |
), |
|
| 739 | 1x |
if (!is.null(tag_id)) {
|
| 740 | ! |
list( |
| 741 | ! |
ga = list( |
| 742 | ! |
type = "script", |
| 743 | ! |
src = paste0("https://www.googletagmanager.com/gtag/js?id=", tag_id)
|
| 744 |
) |
|
| 745 |
) |
|
| 746 |
}, |
|
| 747 | 1x |
list( |
| 748 | 1x |
custom_style = list(type = "stylesheet", src = "style.css"), |
| 749 | 1x |
custom = list(type = "script", src = "script.js"), |
| 750 | 1x |
bootstrap_style = dependencies$bootstrap$css, |
| 751 | 1x |
bootstrap = dependencies$bootstrap$js |
| 752 |
) |
|
| 753 |
) |
|
| 754 |
) |
|
| 755 | 1x |
data_handlers <- list.files(paste0(dir, "/docs"), "data_handler") |
| 756 | 1x |
unlink(paste0( |
| 757 | 1x |
dir, |
| 758 | 1x |
"/docs/", |
| 759 | 1x |
data_handlers[ |
| 760 | 1x |
!data_handlers %in% |
| 761 | 1x |
paste0(parts$dependencies$data_handler$src, c("", ".map"))
|
| 762 |
] |
|
| 763 |
)) |
|
| 764 | 1x |
parts$credits$bootstrap <- dependencies$bootstrap$info |
| 765 | 1x |
parts$credits$colorbrewer <- dependencies$colorbrewer$info |
| 766 | 1x |
parts$credits$scico <- dependencies$scico$info |
| 767 | 1x |
src <- parse( |
| 768 | 1x |
text = gsub( |
| 769 | 1x |
"community::site_build", |
| 770 | 1x |
"site_build", |
| 771 | 1x |
readLines(page, warn = FALSE), |
| 772 | 1x |
fixed = TRUE |
| 773 |
) |
|
| 774 |
) |
|
| 775 | 1x |
source(local = parts, exprs = src) |
| 776 | 1x |
libdir <- paste0(dir, "/docs/lib/") |
| 777 | 1x |
if (missing(bundle_libs)) {
|
| 778 | 1x |
bundle_libs <- libs_overwrite || libs_base_only |
| 779 |
} |
|
| 780 | 1x |
if (bundle_libs) {
|
| 781 | ! |
dir.create(libdir, FALSE) |
| 782 | ! |
manifest_file <- paste0(libdir, "manifest.json") |
| 783 | ! |
manifest <- if (file.exists(manifest_file)) {
|
| 784 | ! |
jsonlite::read_json(manifest_file) |
| 785 |
} else {
|
|
| 786 | ! |
list() |
| 787 |
} |
|
| 788 | ! |
for (dn in names(parts$dependencies)) {
|
| 789 |
if ( |
|
| 790 | ! |
if (libs_base_only) {
|
| 791 | ! |
dn %in% c("base", "base_style")
|
| 792 |
} else {
|
|
| 793 | ! |
!grepl("^(?:ga$|custom|data_handler)", dn)
|
| 794 |
} |
|
| 795 |
) {
|
|
| 796 | ! |
d <- parts$dependencies[[dn]] |
| 797 | ! |
f <- paste0("lib/", dn, "/", basename(d$src))
|
| 798 | ! |
if (is.null(manifest[[dn]])) {
|
| 799 | ! |
manifest[[dn]] <- list(file = f, source = d$src) |
| 800 |
} |
|
| 801 | ! |
lf <- paste0(dir, "/docs/", f) |
| 802 | ! |
stale <- libs_overwrite || d$src != manifest[[dn]]$source |
| 803 | ! |
if (!file.exists(lf) || stale) {
|
| 804 | ! |
if (stale) {
|
| 805 | ! |
unlink(dirname(lf), TRUE) |
| 806 |
} |
|
| 807 | ! |
dir.create(dirname(lf), FALSE) |
| 808 | ! |
loc <- paste0(dir, "/docs/", d$src) |
| 809 | ! |
if (file.exists(loc)) {
|
| 810 | ! |
file.copy(loc, lf) |
| 811 |
} else {
|
|
| 812 | ! |
download.file(d$src, lf) |
| 813 |
} |
|
| 814 | ! |
manifest[[dn]] <- list(file = f, source = d$src) |
| 815 |
} |
|
| 816 | ! |
map <- readLines(lf, warn = FALSE) |
| 817 | ! |
map <- map[length(map)] |
| 818 | ! |
if (grepl("sourceMappingURL", map, fixed = TRUE)) {
|
| 819 | ! |
mf <- paste0( |
| 820 | ! |
dirname(lf), |
| 821 |
"/", |
|
| 822 | ! |
regmatches(map, regexec("=([^ ]+)", map))[[1]][2]
|
| 823 |
) |
|
| 824 | ! |
if (!file.exists(mf)) {
|
| 825 | ! |
download.file(paste0(dirname(d$src), "/", basename(mf)), mf) |
| 826 |
} |
|
| 827 |
} |
|
| 828 | ! |
parts$dependencies[[dn]]$src <- f |
| 829 | ! |
parts$dependencies[[dn]]$hash <- NULL |
| 830 |
} |
|
| 831 |
} |
|
| 832 | ! |
jsonlite::write_json(manifest, manifest_file, auto_unbox = TRUE) |
| 833 |
} else {
|
|
| 834 | 1x |
unlink(libdir, TRUE) |
| 835 |
} |
|
| 836 | 1x |
for (e in c( |
| 837 | 1x |
"rules", |
| 838 | 1x |
"variables", |
| 839 | 1x |
"dataviews", |
| 840 | 1x |
"info", |
| 841 | 1x |
"text", |
| 842 | 1x |
"select", |
| 843 | 1x |
"combobox", |
| 844 | 1x |
"button", |
| 845 | 1x |
"datatable", |
| 846 | 1x |
"table", |
| 847 | 1x |
"plotly", |
| 848 | 1x |
"echarts", |
| 849 | 1x |
"map", |
| 850 | 1x |
"legend", |
| 851 | 1x |
"credits", |
| 852 | 1x |
"credit_output", |
| 853 | 1x |
"tutorials" |
| 854 |
)) {
|
|
| 855 | 17x |
settings[[e]] <- if (length(parts[[e]])) {
|
| 856 | ! |
if (is.list(parts[[e]])) parts[[e]] else list(parts[[e]]) |
| 857 |
} else {
|
|
| 858 | 13x |
NULL |
| 859 |
} |
|
| 860 | 17x |
if (!is.null(names(settings[[e]]))) {
|
| 861 | 4x |
settings[[e]] <- settings[[e]][!duplicated(names(settings[[e]]))] |
| 862 |
} |
|
| 863 |
} |
|
| 864 | 1x |
if (!is.null(settings$map)) {
|
| 865 | ! |
for (m in settings$map) {
|
| 866 | ! |
if (!is.null(m$shapes)) {
|
| 867 | ! |
for (s in m$shapes) {
|
| 868 | ! |
if (!is.null(s$url) && file.exists(s$url)) {
|
| 869 | ! |
settings$map[["_raw"]][[s$url]] <- paste( |
| 870 | ! |
readLines(s$url), |
| 871 | ! |
collapse = "" |
| 872 |
) |
|
| 873 |
} |
|
| 874 |
} |
|
| 875 | ! |
for (v in m$overlays) {
|
| 876 | ! |
for (s in v$source) {
|
| 877 | ! |
if (!is.list(s)) {
|
| 878 | ! |
s <- list(url = s) |
| 879 |
} |
|
| 880 |
if ( |
|
| 881 | ! |
!is.null(s$url) && |
| 882 | ! |
file.exists(s$url) && |
| 883 | ! |
!s$url %in% names(settings$map[["_raw"]]) |
| 884 |
) {
|
|
| 885 | ! |
settings$map[["_raw"]][[s$url]] <- paste( |
| 886 | ! |
readLines(s$url), |
| 887 | ! |
collapse = "" |
| 888 |
) |
|
| 889 |
} |
|
| 890 |
} |
|
| 891 |
} |
|
| 892 |
} |
|
| 893 |
} |
|
| 894 |
} |
|
| 895 | 1x |
if (!is.null(endpoint)) {
|
| 896 | ! |
settings$endpoint <- endpoint |
| 897 |
} |
|
| 898 | 1x |
if (!is.null(tag_id)) {
|
| 899 | ! |
settings$tag_id <- tag_id |
| 900 |
} |
|
| 901 | 1x |
if (!bundle_package) {
|
| 902 | 1x |
settings$metadata$info <- settings$metadata$measure_info <- settings$entity_info <- NULL |
| 903 |
} |
|
| 904 | 1x |
entity_info <- NULL |
| 905 | 1x |
if (length(settings$metadata$entity_info)) {
|
| 906 | ! |
entity_info <- unique(settings$metadata$entity_info) |
| 907 | ! |
settings$metadata$entity_info <- NULL |
| 908 | ! |
if (bundle_package) {
|
| 909 | ! |
settings$entity_info <- lapply( |
| 910 | ! |
structure(paste0(dir, "/docs/", entity_info), names = entity_info), |
| 911 | ! |
jsonlite::read_json, |
| 912 | ! |
simplify = FALSE |
| 913 |
) |
|
| 914 |
} |
|
| 915 |
} |
|
| 916 | 1x |
settings$aggregated <- aggregate |
| 917 | 1x |
jsonlite::write_json( |
| 918 | 1x |
settings, |
| 919 | 1x |
paste0(dir, "/docs/settings.json"), |
| 920 | 1x |
auto_unbox = TRUE, |
| 921 | 1x |
pretty = TRUE |
| 922 |
) |
|
| 923 | 1x |
if (include_api || file.exists(paste0(dir, "/docs/functions/api.js"))) {
|
| 924 | ! |
dir.create(paste0(dir, "/docs/functions"), FALSE, TRUE) |
| 925 | ! |
writeLines( |
| 926 | ! |
c( |
| 927 | ! |
"'use strict'", |
| 928 | ! |
"const settings = require('../settings.json')",
|
| 929 | ! |
if (length(entity_info)) {
|
| 930 | ! |
paste0( |
| 931 | ! |
"settings.entity_info = {",
|
| 932 | ! |
paste0( |
| 933 |
"'", |
|
| 934 | ! |
entity_info, |
| 935 | ! |
"': require('../",
|
| 936 | ! |
entity_info, |
| 937 |
"')", |
|
| 938 | ! |
collapse = ", " |
| 939 |
), |
|
| 940 |
"}" |
|
| 941 |
) |
|
| 942 |
}, |
|
| 943 | ! |
if (!bundle_package) {
|
| 944 | ! |
c( |
| 945 | ! |
"settings.metadata.info = {}",
|
| 946 | ! |
"const dp = require('../data/datapackage.json')",
|
| 947 | ! |
"if (dp.measure_info) settings.metadata.measure_info = dp.measure_info", |
| 948 | ! |
"dp.resources.forEach(r => (settings.metadata.info[r.name] = r))" |
| 949 |
) |
|
| 950 |
}, |
|
| 951 | ! |
paste0( |
| 952 | ! |
"const DataHandler = require('../",
|
| 953 | ! |
if (version == "local") {
|
| 954 | ! |
parts$dependencies$data_handler$src |
| 955 |
} else {
|
|
| 956 | ! |
basename(parts$dependencies$data_handler$src) |
| 957 |
}, |
|
| 958 |
"')," |
|
| 959 |
), |
|
| 960 | ! |
" data = new DataHandler(settings, void 0, {",
|
| 961 | ! |
paste0( |
| 962 |
" ", |
|
| 963 | ! |
vapply( |
| 964 | ! |
settings$metadata$datasets, |
| 965 | ! |
function(f) paste0(f, ": require('../", f, ".json')"),
|
| 966 |
"" |
|
| 967 |
), |
|
| 968 |
"," |
|
| 969 |
), |
|
| 970 |
" })", |
|
| 971 | ! |
"module.exports.handler = async function (event) {",
|
| 972 | ! |
" return data.export(event.queryStringParameters)", |
| 973 |
"}" |
|
| 974 |
), |
|
| 975 | ! |
paste0(dir, "/docs/functions/api.js") |
| 976 |
) |
|
| 977 |
} |
|
| 978 | 1x |
last_deps <- grep("^(?:custom|base)", names(parts$dependencies))
|
| 979 | 1x |
if (bundle_data) {
|
| 980 | ! |
settings$data <- structure( |
| 981 | ! |
lapply( |
| 982 | ! |
settings$metadata$datasets, |
| 983 | ! |
function(f) jsonlite::read_json(paste0(dir, "/docs/", f, ".json")) |
| 984 |
), |
|
| 985 | ! |
names = settings$metadata$datasets |
| 986 |
) |
|
| 987 |
} |
|
| 988 | 1x |
r <- c( |
| 989 | 1x |
"<!doctype html>", |
| 990 | 1x |
paste( |
| 991 | 1x |
"<!-- page generated from", |
| 992 | 1x |
sub("^.*/", "", file),
|
| 993 | 1x |
"by community::site_build() -->" |
| 994 |
), |
|
| 995 | 1x |
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">', |
| 996 | 1x |
"<head>", |
| 997 | 1x |
'<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />', |
| 998 | 1x |
'<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />', |
| 999 | 1x |
'<meta name="viewport" content="width=device-width,initial-scale=1" />', |
| 1000 | 1x |
unlist(lapply( |
| 1001 | 1x |
parts$dependencies[c( |
| 1002 | 1x |
seq_along(parts$dependencies)[-last_deps], |
| 1003 | 1x |
last_deps |
| 1004 |
)], |
|
| 1005 | 1x |
head_import, |
| 1006 | 1x |
dir = dir |
| 1007 |
)), |
|
| 1008 | 1x |
paste0( |
| 1009 | 1x |
'<meta name="generator" content="community v', |
| 1010 | 1x |
packageVersion("community"),
|
| 1011 |
'" />' |
|
| 1012 |
), |
|
| 1013 | 1x |
unlist(parts$head[!duplicated(names(parts$head))], use.names = FALSE), |
| 1014 | 1x |
"</head>", |
| 1015 | 1x |
"<body>", |
| 1016 | 1x |
'<div id="site_wrap" style="visibility: hidden; position: absolute; height: 100%; left: 0; right: 0">', |
| 1017 | 1x |
if (!is.null(parts$header)) parts$header, |
| 1018 | 1x |
if (!is.null(parts$body)) parts$body, |
| 1019 | 1x |
'<div class="content container-fluid">', |
| 1020 | 1x |
if (!is.null(parts$content)) parts$content, |
| 1021 | 1x |
"</div>", |
| 1022 | 1x |
"</div>", |
| 1023 | 1x |
paste0( |
| 1024 | 1x |
'<div id="load_screen" style="position: absolute; top: 0; right: 0; bottom: 0; left: 0; background-color: inherit">', |
| 1025 | 1x |
'<div class="d-flex justify-content-center align-items-center" style="height: 50%">', |
| 1026 | 1x |
'<div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div>', |
| 1027 | 1x |
"</div>", |
| 1028 | 1x |
'<noscript style="width: 100%; text-align: center; padding: 5em">Please enable JavaScript to view this site.</noscript>', |
| 1029 | 1x |
"</div>" |
| 1030 |
), |
|
| 1031 | 1x |
paste0( |
| 1032 | 1x |
'<script type="application/javascript">\nconst site = ', |
| 1033 | 1x |
jsonlite::toJSON(settings, auto_unbox = TRUE), |
| 1034 | 1x |
"\nnew Community(site)\n</script>" |
| 1035 |
), |
|
| 1036 | 1x |
parts$script, |
| 1037 | 1x |
"</body>", |
| 1038 | 1x |
"</html>" |
| 1039 |
) |
|
| 1040 | 1x |
writeLines(r, out) |
| 1041 | 1x |
cli_bullets(c( |
| 1042 | 1x |
v = paste("built", name, "file:"),
|
| 1043 | 1x |
"*" = paste0("{.path ", out, "}")
|
| 1044 |
)) |
|
| 1045 | 1x |
if (serve) {
|
| 1046 | 1x |
site_start_server(dir, host, port) |
| 1047 |
} |
|
| 1048 | 1x |
if (open_after && isAvailable()) {
|
| 1049 | ! |
viewer(if (serve) paste0("http://", host, ":", port) else out)
|
| 1050 |
} |
|
| 1051 | 1x |
invisible(out) |
| 1052 |
} |
| 1 |
#' Adds a legend to a website |
|
| 2 |
#' |
|
| 3 |
#' Adds a legend based on a specified color palette. |
|
| 4 |
#' |
|
| 5 |
#' @param palette Name of an included color palette, or palette selection input; |
|
| 6 |
#' for discrete scales, one of \code{"rdylbu7"}, \code{"orrd7"}, \code{"gnbu7"},
|
|
| 7 |
#' \code{"brbg7"}, \code{"puor7"}, \code{"prgn6"}, \code{"reds5"}, \code{"greens5"}, \code{"greys4"}, \code{"paired4"} (from
|
|
| 8 |
#' \href{https://colorbrewer2.org}{colorbrewer}); for continuous scales, one of \code{"grey"}, \code{"brown"}, \code{"purple"},
|
|
| 9 |
#' \code{"prgn"}, \code{"puor"}, \code{"rbbu"}, \code{"prgn"}, \code{"vik"} (default), or \code{"lajolla"}.
|
|
| 10 |
#' @param variable Name of a variable or ID of a variable selector to display values of. Defaults to |
|
| 11 |
#' the \code{y} variable of \code{dataview} if one is specified.
|
|
| 12 |
#' @param dataview The ID of an \code{\link{input_dataview}} component.
|
|
| 13 |
#' @param id Unique ID of the legend element. |
|
| 14 |
#' @param subto A vector of output IDs to receive hover events from. |
|
| 15 |
#' @param click The ID of an input to set to an entity's ID near the current cursor location on the current scale. |
|
| 16 |
#' @param class Class names to add to the legend element. |
|
| 17 |
#' @param show_na Logical; if \code{FALSE}, does not add the separate section showing the color of missing values.
|
|
| 18 |
#' @examples |
|
| 19 |
#' output_legend() |
|
| 20 |
#' @return A character vector of the contents to be added. |
|
| 21 |
#' @export |
|
| 22 | ||
| 23 |
output_legend <- function( |
|
| 24 |
palette = "", |
|
| 25 |
variable = NULL, |
|
| 26 |
dataview = NULL, |
|
| 27 |
id = NULL, |
|
| 28 |
click = NULL, |
|
| 29 |
subto = NULL, |
|
| 30 |
class = "", |
|
| 31 |
show_na = TRUE |
|
| 32 |
) {
|
|
| 33 | 3x |
caller <- parent.frame() |
| 34 | 3x |
building <- !is.null(attr(caller, "name")) && |
| 35 | 3x |
attr(caller, "name") == "community_site_parts" |
| 36 | 3x |
if (is.null(id)) {
|
| 37 | 3x |
id <- paste0("legend", caller$uid)
|
| 38 |
} |
|
| 39 | 3x |
r <- c( |
| 40 | 3x |
if (show_na) {
|
| 41 | 3x |
c( |
| 42 | 3x |
'<div class="legend-wrap">', |
| 43 | 3x |
'<div class="legend-na">', |
| 44 | 3x |
'<div class="legend-ticks"></div>', |
| 45 | 3x |
'<div class="legend-scale"><span class="na"></span></div>', |
| 46 | 3x |
'<div class="legend-summary"><p>NA</p></div>', |
| 47 | 3x |
"</div>" |
| 48 |
) |
|
| 49 |
}, |
|
| 50 | 3x |
paste( |
| 51 | 3x |
c( |
| 52 | 3x |
'<div id="', |
| 53 | 3x |
id, |
| 54 | 3x |
'" data-autoType="legend" class="auto-output legend', |
| 55 | 3x |
if (class != "") c(" ", class),
|
| 56 |
'"', |
|
| 57 | 3x |
if (!is.null(variable)) paste0(' data-variable="', variable, '"'),
|
| 58 | 3x |
if (!is.null(dataview)) paste0(' data-view="', dataview, '"'),
|
| 59 | 3x |
if (!is.null(click)) paste0(' data-click="', click, '"'),
|
| 60 |
">" |
|
| 61 |
), |
|
| 62 | 3x |
collapse = "" |
| 63 |
), |
|
| 64 | 3x |
'<div class="legend-ticks"></div>', |
| 65 | 3x |
'<div class="legend-scale"></div>', |
| 66 | 3x |
'<div class="legend-summary"></div>', |
| 67 | 3x |
"</div>", |
| 68 | 3x |
if (show_na) "</div>" |
| 69 |
) |
|
| 70 | 3x |
if (building) {
|
| 71 | 1x |
caller$legend[[id]] <- list(palette = palette, subto = subto) |
| 72 | 1x |
caller$content <- c(caller$content, r) |
| 73 | 1x |
caller$uid <- caller$uid + 1 |
| 74 |
} |
|
| 75 | 3x |
r |
| 76 |
} |
| 1 |
#' Add checkboxes, radio buttons, or switches to a website |
|
| 2 |
#' |
|
| 3 |
#' Adds a set of checkbox, radio buttons, or switches to a website. |
|
| 4 |
#' |
|
| 5 |
#' @param label Label of the input for the user. |
|
| 6 |
#' @param options A vector of options, the name of a variable from which to pull levels, or either \code{"datasets"}
|
|
| 7 |
#' or \code{"variables"} to select names of datasets or variables.
|
|
| 8 |
#' @param default A vector of items to check by default (or "all" or "none") if \code{multi} is \code{TRUE}, or
|
|
| 9 |
#' the option to select by default. |
|
| 10 |
#' @param display A display version of the options. |
|
| 11 |
#' @param id Unique id of the element to be created. |
|
| 12 |
#' @param ... Additional attributes to set on the element. |
|
| 13 |
#' @param note Text to display as a tooltip for the input. |
|
| 14 |
#' @param variable The name of a variable from which to get levels (overwritten by \code{depends}).
|
|
| 15 |
#' @param dataset The name of an included dataset, where \code{variable} should be looked for; only applies when
|
|
| 16 |
#' there are multiple datasets with the same variable name. |
|
| 17 |
#' @param depends The id of another input on which the options depend; this will take president over \code{dataset}
|
|
| 18 |
#' and \code{variable}, depending on this type of input \code{depends} points to.
|
|
| 19 |
#' @param multi Logical; if \code{FALSE}, only one option can be selected at a time, turning the checkboxes into radio
|
|
| 20 |
#' buttons. |
|
| 21 |
#' @param as.switch Logical; if \code{TRUE}, displays checkboxes or radio buttons as switches.
|
|
| 22 |
#' @examples |
|
| 23 |
#' \dontrun{
|
|
| 24 |
#' input_checkbox("Label", c("a", "b", "c"))
|
|
| 25 |
#' } |
|
| 26 |
#' @return A character vector of the contents to be added. |
|
| 27 |
#' @seealso For a single switch or checkbox, use \code{\link{input_switch}}.
|
|
| 28 |
#' @export |
|
| 29 | ||
| 30 |
input_checkbox <- function( |
|
| 31 |
label, |
|
| 32 |
options, |
|
| 33 |
default = "all", |
|
| 34 |
display = options, |
|
| 35 |
id = label, |
|
| 36 |
..., |
|
| 37 |
note = NULL, |
|
| 38 |
variable = NULL, |
|
| 39 |
dataset = NULL, |
|
| 40 |
depends = NULL, |
|
| 41 |
multi = TRUE, |
|
| 42 |
as.switch = FALSE |
|
| 43 |
) {
|
|
| 44 | 3x |
if (multi && length(default) == 1) {
|
| 45 | 3x |
if ((is.logical(default) && default) || default == "all") {
|
| 46 | 3x |
default <- options |
| 47 | ! |
} else if ((is.logical(default) && !default) || default == "none") {
|
| 48 | ! |
default <- NULL |
| 49 |
} |
|
| 50 | ! |
} else if (!multi && is.character(default)) {
|
| 51 | ! |
default <- which( |
| 52 | ! |
(if (default %in% display) display else options) == default |
| 53 |
) |
|
| 54 | ! |
if (!length(default)) default <- 1 |
| 55 |
} |
|
| 56 | 3x |
id <- gsub("\\s", "", id)
|
| 57 | 3x |
a <- list(...) |
| 58 | 3x |
type <- if (multi) "checkbox" else "radio" |
| 59 | 3x |
r <- c( |
| 60 | 3x |
'<div class="wrapper checkbox-wrapper">', |
| 61 | 3x |
paste0( |
| 62 | 3x |
"<fieldset", |
| 63 | 3x |
if (length(a)) {
|
| 64 | ! |
paste("", paste(names(a), paste0('"', unlist(a), '"'), sep = "="))
|
| 65 |
}, |
|
| 66 |
">" |
|
| 67 |
), |
|
| 68 | 3x |
paste0("<legend>", label, "</legend>"),
|
| 69 | 3x |
paste0( |
| 70 | 3x |
'<div class="auto-input" role="group" data-autoType="', |
| 71 | 3x |
type, |
| 72 | 3x |
'" id="', |
| 73 | 3x |
id, |
| 74 |
'" ', |
|
| 75 | 3x |
if (is.character(options) && length(options) == 1) {
|
| 76 | ! |
paste0('data-optionSource="', options, '"')
|
| 77 |
}, |
|
| 78 | 3x |
if (!is.null(default)) {
|
| 79 | 3x |
paste0(' data-default="', paste(default, collapse = ","), '"')
|
| 80 |
}, |
|
| 81 | 3x |
if (!is.null(depends)) paste0(' data-depends="', depends, '"'),
|
| 82 | 3x |
if (!is.null(note)) paste0(' aria-description="', note, '"'),
|
| 83 | 3x |
if (!is.null(dataset)) paste0(' data-dataset="', dataset, '"'),
|
| 84 | 3x |
if (!is.null(variable)) paste0(' data-variable="', variable, '"'),
|
| 85 | 3x |
if (as.switch) paste0(' data-switch="', as.switch, '"'),
|
| 86 | 3x |
if (length(a)) {
|
| 87 | ! |
unlist(lapply( |
| 88 | ! |
seq_along(a), |
| 89 | ! |
function(i) paste0(" ", names(a)[i], '="', a[[i]], '"')
|
| 90 |
)) |
|
| 91 |
}, |
|
| 92 |
">" |
|
| 93 |
), |
|
| 94 | 3x |
if (length(options) > 1) {
|
| 95 | 3x |
unlist( |
| 96 | 3x |
lapply(seq_along(options), function(i) {
|
| 97 | 9x |
c( |
| 98 | 9x |
paste0( |
| 99 | 9x |
'<div class="form-check', |
| 100 | 9x |
if (as.switch) " form-switch", |
| 101 |
'">' |
|
| 102 |
), |
|
| 103 | 9x |
paste0( |
| 104 | 9x |
'<input type="', |
| 105 | 9x |
type, |
| 106 | 9x |
'" autocomplete="off" class="form-check-input" name="', |
| 107 | 9x |
id, |
| 108 | 9x |
'_options" id="', |
| 109 | 9x |
id, |
| 110 | 9x |
"_option", |
| 111 | 9x |
i, |
| 112 | 9x |
if (as.switch) '" role="switch', |
| 113 | 9x |
'" value="', |
| 114 | 9x |
options[i], |
| 115 |
'"', |
|
| 116 | 9x |
if ((multi && options[i] %in% default) || i == default) {
|
| 117 | 9x |
" checked" |
| 118 |
}, |
|
| 119 |
">" |
|
| 120 |
), |
|
| 121 | 9x |
paste0( |
| 122 | 9x |
'<label class="form-check-label" for="', |
| 123 | 9x |
id, |
| 124 | 9x |
"_option", |
| 125 | 9x |
i, |
| 126 |
'">', |
|
| 127 | 9x |
display[i], |
| 128 | 9x |
"</label>" |
| 129 |
), |
|
| 130 | 9x |
"</div>" |
| 131 |
) |
|
| 132 |
}), |
|
| 133 | 3x |
use.names = FALSE |
| 134 |
) |
|
| 135 |
}, |
|
| 136 | 3x |
"</div>", |
| 137 | 3x |
"</fieldset>", |
| 138 | 3x |
"</div>" |
| 139 |
) |
|
| 140 | 3x |
caller <- parent.frame() |
| 141 |
if ( |
|
| 142 | 3x |
!is.null(attr(caller, "name")) && |
| 143 | 3x |
attr(caller, "name") == "community_site_parts" |
| 144 |
) {
|
|
| 145 | 1x |
caller$content <- c(caller$content, r) |
| 146 |
} |
|
| 147 | 3x |
r |
| 148 |
} |
| 1 |
#' @rdname site_build |
|
| 2 |
#' @examples |
|
| 3 |
#' \dontrun{
|
|
| 4 |
#' # serve a site that has already been built |
|
| 5 |
#' # from the parent directory of a "docs" directory to be served |
|
| 6 |
#' site_start_server(".")
|
|
| 7 |
#' } |
|
| 8 |
#' @export |
|
| 9 | ||
| 10 |
site_start_server <- function(dir, host = "127.0.0.1", port = 3000) {
|
|
| 11 | 1x |
static_path <- list("/" = staticPath(paste0(dir, "/docs"), TRUE))
|
| 12 | 1x |
server_exists <- FALSE |
| 13 | 1x |
for (s in listServers()) {
|
| 14 | ! |
if (s$getHost() == host && s$getPort() == port) {
|
| 15 | ! |
if (!identical(s$getStaticPaths(), static_path)) {
|
| 16 | ! |
stopServer(s) |
| 17 |
} else {
|
|
| 18 | ! |
server_exists <- TRUE |
| 19 |
} |
|
| 20 | ! |
break |
| 21 |
} |
|
| 22 |
} |
|
| 23 | 1x |
if (!server_exists) {
|
| 24 | 1x |
s <- tryCatch( |
| 25 | 1x |
startServer(host, port, list(staticPaths = static_path)), |
| 26 | 1x |
error = function(e) NULL |
| 27 |
) |
|
| 28 | 1x |
if (is.null(s)) {
|
| 29 | ! |
cli_warn(paste0("failed to create server on ", host, ":", port))
|
| 30 |
} |
|
| 31 |
} |
|
| 32 | 1x |
cli_alert_info(paste0("listening on ", host, ":", port))
|
| 33 |
} |
| 1 |
#' Initialize a Data Commons |
|
| 2 |
#' |
|
| 3 |
#' Initialize a project to keep track of separate dataset repositories and distributions. |
|
| 4 |
#' |
|
| 5 |
#' @param dir Path to the desired data commons directory. |
|
| 6 |
#' @param name Name of the data commons. |
|
| 7 |
#' @param repos A vector of repository names to add to \code{commons.json}.
|
|
| 8 |
#' @param default_user GitHub username to prepend to repository names if needed. |
|
| 9 |
#' @param remote Name of the data commons' GitHub repository (\code{"username/reponame"}).
|
|
| 10 |
#' @param url URL of the data commons' monitor site; defaults to the GitHub Pages URL associated with \code{remote}
|
|
| 11 |
#' if provided (\code{"https://username.github.io/reponame"}).
|
|
| 12 |
#' @param refresh_after Logical; if \code{FALSE}, will not run \code{\link{datacommons_refresh}}
|
|
| 13 |
#' after initiating the project. Defaults to \code{TRUE} when first creating a data commons project.
|
|
| 14 |
#' @param overwrite Logical; if \code{TRUE}, will overwrite existing datacommons files in \code{dir}.
|
|
| 15 |
#' The included \code{.js} and \code{.sh} files are always rewritten, and if \code{name},
|
|
| 16 |
#' \code{repos}, or \code{default_user} is specified, \code{commons.json} will also be rewritten
|
|
| 17 |
#' regardless of \code{overwrite}.
|
|
| 18 |
#' @param serve Logical; if \code{TRUE}, will serve the \code{docs} directory.
|
|
| 19 |
#' @param host The IPv4 address to listen to if \code{serve} is \code{TRUE}; defaults to \code{"127.0.0.1"}.
|
|
| 20 |
#' @param port The port to listen on if \code{serve} is \code{TRUE}; defaults to 3000.
|
|
| 21 |
#' @param use_local Logical; if \code{TRUE}, will use a \code{datacommons.js} script located in
|
|
| 22 |
#' a local \code{dist/docs/dev} directory, relative to \code{dir}.
|
|
| 23 |
#' @param verbose Logical; if \code{FALSE}, suppresses messages.
|
|
| 24 |
#' @details |
|
| 25 |
#' The shell scripts included in the project's \code{scripts} directory can be used to retrieve
|
|
| 26 |
#' and update repositories over SSH. |
|
| 27 |
#' |
|
| 28 |
#' This will clone or pull repositories listed in \code{scripts/repos.txt}:
|
|
| 29 |
#' \code{sh scripts/get_repos.sh}
|
|
| 30 |
#' |
|
| 31 |
#' This will add, commit, and push all changes in all repositories: |
|
| 32 |
#' \code{sh scripts/update_repos.sh "commit message"}
|
|
| 33 |
#' @examples |
|
| 34 |
#' \dontrun{
|
|
| 35 |
#' init_datacommons( |
|
| 36 |
#' "../datacommons", |
|
| 37 |
#' name = "Data Commons", |
|
| 38 |
#' remote = "" |
|
| 39 |
#' ) |
|
| 40 |
#' } |
|
| 41 |
#' @return Path to the datacommons directory. |
|
| 42 |
#' @export |
|
| 43 | ||
| 44 |
init_datacommons <- function( |
|
| 45 |
dir, |
|
| 46 |
name = "Data Commons", |
|
| 47 |
repos = NULL, |
|
| 48 |
default_user = "", |
|
| 49 |
remote = NULL, |
|
| 50 |
url = NULL, |
|
| 51 |
refresh_after = FALSE, |
|
| 52 |
overwrite = FALSE, |
|
| 53 |
serve = FALSE, |
|
| 54 |
host = "127.0.0.1", |
|
| 55 |
port = 3000, |
|
| 56 |
use_local = FALSE, |
|
| 57 |
verbose = interactive() |
|
| 58 |
) {
|
|
| 59 | 6x |
if (missing(dir)) {
|
| 60 | ! |
cli_abort('{.arg dir} must be speficied (e.g., dir = ".")')
|
| 61 |
} |
|
| 62 | 6x |
check <- check_template("datacommons", dir = dir)
|
| 63 | 6x |
if (missing(refresh_after) && !check$exists) {
|
| 64 | ! |
refresh_after <- TRUE |
| 65 |
} |
|
| 66 | 6x |
odir <- substitute(dir) |
| 67 | 6x |
dir <- normalizePath(paste0(dir, "/", check$spec$dir), "/", FALSE) |
| 68 | 6x |
dir.create(paste0(dir, "/repos"), FALSE, TRUE) |
| 69 | 6x |
dir.create(paste0(dir, "/manifest"), FALSE) |
| 70 | 6x |
dir.create(paste0(dir, "/cache"), FALSE) |
| 71 | 6x |
dir.create(paste0(dir, "/views"), FALSE) |
| 72 | 6x |
dir.create(paste0(dir, "/docs"), FALSE) |
| 73 | 6x |
dir.create(paste0(dir, "/scripts"), FALSE) |
| 74 | 6x |
paths <- paste0( |
| 75 | 6x |
dir, |
| 76 |
"/", |
|
| 77 | 6x |
c( |
| 78 | 6x |
"commons.json", |
| 79 | 6x |
"README.md", |
| 80 | 6x |
".gitignore", |
| 81 | 6x |
"project.Rproj", |
| 82 | 6x |
"scripts/repos.txt", |
| 83 | 6x |
"scripts/get_repos.sh", |
| 84 | 6x |
"scripts/update_repos.sh", |
| 85 | 6x |
"docs/index.html", |
| 86 | 6x |
"docs/request.js" |
| 87 |
) |
|
| 88 |
) |
|
| 89 | 6x |
if (overwrite) {
|
| 90 | ! |
unlink(paths, TRUE) |
| 91 |
} |
|
| 92 |
if ( |
|
| 93 | 6x |
file.exists(paths[5]) && |
| 94 | 6x |
(!length(repos) || |
| 95 | 6x |
(file.exists(paths[1]) && file.mtime(paths[5]) > file.mtime(paths[1]))) |
| 96 |
) {
|
|
| 97 | 5x |
repos <- unique(c(repos, readLines(paths[5], warn = FALSE))) |
| 98 |
} |
|
| 99 | 6x |
if (file.exists(paths[1])) {
|
| 100 | 5x |
existing <- jsonlite::read_json(paths[1]) |
| 101 | 5x |
if (missing(name)) {
|
| 102 | 5x |
name <- existing$name |
| 103 |
} |
|
| 104 | ! |
if (!length(repos)) repos <- existing$repositories |
| 105 |
} |
|
| 106 | 6x |
if (length(repos)) {
|
| 107 | 6x |
if (default_user != "") {
|
| 108 | ! |
repos <- paste0(default_user, "/", repos) |
| 109 |
} |
|
| 110 | 6x |
repos <- unlist( |
| 111 | 6x |
regmatches(repos, regexec("[^/]+/[^/#@]+$", repos)),
|
| 112 | 6x |
use.names = FALSE |
| 113 |
) |
|
| 114 |
} |
|
| 115 | 6x |
jsonlite::write_json( |
| 116 | 6x |
list(name = name, repositories = repos), |
| 117 | 6x |
paths[1], |
| 118 | 6x |
auto_unbox = TRUE, |
| 119 | 6x |
pretty = TRUE |
| 120 |
) |
|
| 121 | 6x |
if (!file.exists(paths[2])) {
|
| 122 | 1x |
writeLines( |
| 123 | 1x |
c( |
| 124 | 1x |
paste("#", name),
|
| 125 |
"", |
|
| 126 | 1x |
"Consists of the repositories listed in [commons.json](commons.json).", |
| 127 |
"", |
|
| 128 | 1x |
"You can clone this repository and run these commands to establish and work from local data:", |
| 129 | 1x |
"```R", |
| 130 | 1x |
'# remotes::install_github("miserman/community")',
|
| 131 | 1x |
"library(community)", |
| 132 |
"", |
|
| 133 | 1x |
"# clone and/or pull repositories and distributions:", |
| 134 | 1x |
'datacommons_refresh(".")',
|
| 135 |
"", |
|
| 136 | 1x |
"# map files:", |
| 137 | 1x |
'datacommons_map_files(".")',
|
| 138 |
"", |
|
| 139 | 1x |
"# refresh a view (rebuild a view's site data):", |
| 140 | 1x |
'datacommons_view(".", "view_name")',
|
| 141 |
"", |
|
| 142 | 1x |
"# run the monitor site locally:", |
| 143 | 1x |
'init_datacommons(".", serve = TRUE)',
|
| 144 |
"```", |
|
| 145 |
"" |
|
| 146 |
), |
|
| 147 | 1x |
paths[2] |
| 148 |
) |
|
| 149 |
} |
|
| 150 | 6x |
if (!file.exists(paths[3])) {
|
| 151 | 1x |
writeLines( |
| 152 | 1x |
c( |
| 153 | 1x |
".Rproj.user", |
| 154 | 1x |
".Rhistory", |
| 155 | 1x |
".Rdata", |
| 156 | 1x |
".httr-oauth", |
| 157 | 1x |
".DS_Store", |
| 158 | 1x |
"*.Rproj", |
| 159 | 1x |
"node_modules", |
| 160 | 1x |
"package-lock.json", |
| 161 | 1x |
"repos", |
| 162 | 1x |
"cache", |
| 163 | 1x |
"docs/dist", |
| 164 |
"" |
|
| 165 |
), |
|
| 166 | 1x |
paths[3] |
| 167 |
) |
|
| 168 |
} |
|
| 169 | 6x |
if (!file.exists(paths[4]) && !any(grepl("\\.Rproj$", list.files(dir)))) {
|
| 170 | 1x |
writeLines("Version: 1.0\n", paths[4])
|
| 171 |
} |
|
| 172 | 6x |
writeLines(if (length(repos)) Filter(nchar, repos) else "", paths[5]) |
| 173 | 6x |
inst <- paste0( |
| 174 | 6x |
system.file(package = "community"), |
| 175 | 6x |
c("/inst", ""),
|
| 176 | 6x |
"/templates/datacommons/" |
| 177 |
) |
|
| 178 | 6x |
inst <- inst[which(file.exists(inst))[1]] |
| 179 | 6x |
file.copy(paste0(inst, "get_repos.sh"), paths[6], TRUE) |
| 180 | 6x |
file.copy(paste0(inst, "update_repos.sh"), paths[7], TRUE) |
| 181 | 6x |
manifest_files <- paste0(dir, "/manifest/", c("repos", "files"), ".json")
|
| 182 | 6x |
measure_infos <- paste0(dir, "/cache/measure_info.json") |
| 183 | 6x |
dependencies <- jsonlite::read_json(system.file( |
| 184 | 6x |
"dependencies.json", |
| 185 | 6x |
package = "community" |
| 186 |
)) |
|
| 187 | 6x |
writeLines( |
| 188 | 6x |
c( |
| 189 | 6x |
"<!doctype html>", |
| 190 | 6x |
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">', |
| 191 | 6x |
"<head>", |
| 192 | 6x |
'<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />', |
| 193 | 6x |
'<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />', |
| 194 | 6x |
'<meta name="viewport" content="width=device-width,initial-scale=1" />', |
| 195 | 6x |
"<title>Data Commons Monitor</title>", |
| 196 | 6x |
'<meta name="description" content="Data commons monitoring site.">', |
| 197 | 6x |
unlist(lapply( |
| 198 | 6x |
c( |
| 199 | 6x |
if (use_local) {
|
| 200 | ! |
list( |
| 201 | ! |
list(type = "stylesheet", src = "dist/dev/datacommons.css"), |
| 202 | ! |
list(type = "script", src = "dist/dev/datacommons.js") |
| 203 |
) |
|
| 204 |
} else {
|
|
| 205 | 6x |
list( |
| 206 | 6x |
list( |
| 207 | 6x |
type = "stylesheet", |
| 208 | 6x |
src = "https://miserman.github.io/community/dist/css/datacommons.min.css" |
| 209 |
), |
|
| 210 | 6x |
list( |
| 211 | 6x |
type = "script", |
| 212 | 6x |
src = "https://miserman.github.io/community/dist/js/datacommons.min.js" |
| 213 |
) |
|
| 214 |
) |
|
| 215 |
}, |
|
| 216 | 6x |
list( |
| 217 | 6x |
bootstrap_style = dependencies$bootstrap$css, |
| 218 | 6x |
bootstrap = dependencies$bootstrap$js |
| 219 |
) |
|
| 220 |
), |
|
| 221 | 6x |
head_import, |
| 222 | 6x |
dir = dir |
| 223 |
)), |
|
| 224 | 6x |
paste0( |
| 225 | 6x |
'<meta name="generator" content="community v', |
| 226 | 6x |
packageVersion("community"),
|
| 227 |
'" />' |
|
| 228 |
), |
|
| 229 | 6x |
paste( |
| 230 | 6x |
c( |
| 231 | 6x |
'<script type="text/javascript">', |
| 232 | 6x |
"var commons", |
| 233 | 6x |
paste0( |
| 234 | 6x |
"window.onload = function(){commons = new DataCommons(",
|
| 235 | 6x |
gsub( |
| 236 | 6x |
"\\s+", |
| 237 |
"", |
|
| 238 | 6x |
paste0(readLines(paste0(dir, "/commons.json")), collapse = "") |
| 239 |
), |
|
| 240 |
", {",
|
|
| 241 | 6x |
"repos:", |
| 242 | 6x |
if (file.exists(manifest_files[1])) {
|
| 243 | 5x |
paste0(readLines(manifest_files[1]), collapse = "") |
| 244 |
} else {
|
|
| 245 |
"{}"
|
|
| 246 |
}, |
|
| 247 | 6x |
",files:", |
| 248 | 6x |
if (file.exists(manifest_files[2])) {
|
| 249 | 4x |
paste0(readLines(manifest_files[2]), collapse = "") |
| 250 |
} else {
|
|
| 251 |
"{}"
|
|
| 252 |
}, |
|
| 253 | 6x |
",variables:", |
| 254 | 6x |
if (file.exists(measure_infos)) {
|
| 255 | 4x |
paste0(readLines(measure_infos), collapse = "") |
| 256 |
} else {
|
|
| 257 |
"{}"
|
|
| 258 |
}, |
|
| 259 |
"}, ", |
|
| 260 | 6x |
jsonlite::toJSON( |
| 261 | 6x |
Filter( |
| 262 | 6x |
length, |
| 263 | 6x |
lapply( |
| 264 | 6x |
list.dirs(paste0(dir, "/views"), FALSE)[-1], |
| 265 | 6x |
function(v) {
|
| 266 | 4x |
f <- paste0(dir, "/views/", v, "/", "view.json") |
| 267 | 4x |
if (file.exists(f)) {
|
| 268 | 4x |
list(name = v, view = jsonlite::read_json(f)) |
| 269 |
} |
|
| 270 |
} |
|
| 271 |
) |
|
| 272 |
), |
|
| 273 | 6x |
auto_unbox = TRUE |
| 274 |
), |
|
| 275 |
")}" |
|
| 276 |
), |
|
| 277 | 6x |
"</script>" |
| 278 |
), |
|
| 279 | 6x |
collapse = "\n" |
| 280 |
), |
|
| 281 | 6x |
"</head>", |
| 282 | 6x |
"<body>", |
| 283 | 6x |
'<div id="site_wrap" style="position: fixed; height: 100%; width: 100%">', |
| 284 | 6x |
page_navbar( |
| 285 | 6x |
title = paste(name, "Monitor"), |
| 286 | 6x |
input_button("variables", id = "variables_tab_button"),
|
| 287 | 6x |
input_button("repos", id = "repos_tab_button"),
|
| 288 | 6x |
input_button("views", id = "views_tab_button")
|
| 289 |
), |
|
| 290 | 6x |
'<div class="content container-fluid">', |
| 291 | 6x |
"</div>", |
| 292 | 6x |
"</div>", |
| 293 | 6x |
'<noscript style="width: 100%; text-align: center; padding: 5em">Please enable JavaScript to view this site.</noscript>', |
| 294 | 6x |
"</body>", |
| 295 | 6x |
"</html>" |
| 296 |
), |
|
| 297 | 6x |
paths[8] |
| 298 |
) |
|
| 299 | 6x |
file.copy(paste0(inst, "request.js"), paths[9], TRUE) |
| 300 | 6x |
if (verbose) {
|
| 301 | ! |
cli_bullets(c( |
| 302 | ! |
v = paste(if (check$exists) "updated" else "created", "{name}:"),
|
| 303 | ! |
"*" = paste0("{.path ", normalizePath(dir, "/", FALSE), "}"),
|
| 304 | ! |
i = if (!length(repos)) {
|
| 305 | ! |
paste0( |
| 306 | ! |
"add repository names to {.file {paste0(dir, '/commons.json')}} or {.file {paste0(dir, '/scripts/repos.txt')}},",
|
| 307 | ! |
" then use {.code datacommons_refresh(",
|
| 308 | ! |
odir, |
| 309 | ! |
")} to clone them" |
| 310 |
) |
|
| 311 |
} |
|
| 312 |
)) |
|
| 313 |
} |
|
| 314 | 6x |
if (refresh_after && length(repos)) {
|
| 315 | 1x |
datacommons_refresh(dir, verbose = verbose) |
| 316 |
} |
|
| 317 | 6x |
if (serve) {
|
| 318 | ! |
site_start_server(dir, host, port) |
| 319 |
} |
|
| 320 | 6x |
invisible(dir) |
| 321 |
} |
| 1 |
#' Check Data Repositories |
|
| 2 |
#' |
|
| 3 |
#' Performs a series of checks to see if data in a given repository can be ingested by a datacommons project. |
|
| 4 |
#' |
|
| 5 |
#' @param dir Root directory of the data repository. |
|
| 6 |
#' @param search_pattern Regular expression used to search for data files. |
|
| 7 |
#' @param exclude Subdirectories to exclude from the file search. |
|
| 8 |
#' @param value Name of the column containing variable values. |
|
| 9 |
#' @param value_name Name of the column containing variable names. |
|
| 10 |
#' @param id Column name of IDs that uniquely identify entities. |
|
| 11 |
#' @param time Column name of the variable representing time. |
|
| 12 |
#' @param dataset Column name used to separate data into sets (such as by region), or a vector |
|
| 13 |
#' of datasets, with \code{id}s as names, used to map IDs to datasets.
|
|
| 14 |
#' @param entity_info A vector of variable names to go into making \code{entity_info.json}.
|
|
| 15 |
#' @param check_values Logical; if \code{FALSE}, will perform more intensive checks on values.
|
|
| 16 |
#' If not specified, these are skipped if there are more that 5 million rows in the given dataset, |
|
| 17 |
#' in which case \code{TRUE} will force checks.
|
|
| 18 |
#' @param attempt_repair Logical; if \code{TRUE}, will attempt to fix most warnings in data files.
|
|
| 19 |
#' Use with caution, as this will often remove rows (given \code{NA}s) and rewrite the file.
|
|
| 20 |
#' @param write_infos Logical; if \code{TRUE}, will save standardized and rendered versions of each measure info file.
|
|
| 21 |
#' @param verbose Logical; If \code{FALSE}, will not print status messages or check results.
|
|
| 22 |
#' @examples |
|
| 23 |
#' \dontrun{
|
|
| 24 |
#' # from a data repository |
|
| 25 |
#' check_repository() |
|
| 26 |
#' |
|
| 27 |
#' # to automatically fix most warnings |
|
| 28 |
#' check_repository(attempt_repair = TRUE) |
|
| 29 |
#' } |
|
| 30 |
#' @return An invisible list of check results, in the form of paths to files and/or measure name. |
|
| 31 |
#' These may include \strong{general} entries:
|
|
| 32 |
#' \itemize{
|
|
| 33 |
#' \item \strong{\code{info}} (always): All measurement information (\code{measure_info.json}) files found.
|
|
| 34 |
#' \item \strong{\code{data}} (always): All data files found.
|
|
| 35 |
#' \item \strong{\code{not_considered}}: Subset of data files that do not contain the minimal
|
|
| 36 |
#' columns (\code{id} and \code{value}), and so are not checked further.
|
|
| 37 |
#' \item \strong{\code{summary}} (always): Summary of results.
|
|
| 38 |
#' } |
|
| 39 |
#' or those relating to issues with \strong{measure information} (see \code{\link{data_measure_info}}) files:
|
|
| 40 |
#' \itemize{
|
|
| 41 |
#' \item \strong{\code{info_malformed}}: Files that are not in the expected format (a single object with
|
|
| 42 |
#' named entries for each measure), but can be converted automatically. |
|
| 43 |
#' \item \strong{\code{info_incomplete}}: Measure entries that are missing some of the required fields.
|
|
| 44 |
#' \item \strong{\code{info_invalid}}: Files that could not be read in (probably because they do not contain valid JSON).
|
|
| 45 |
#' \item \strong{\code{info_refs_names}}: Files with a \code{_references} entry with no names
|
|
| 46 |
#' (where it should be a named list). |
|
| 47 |
#' \item \strong{\code{info_refs_missing}}: Files with an entry in its \code{_references} entry that
|
|
| 48 |
#' is missing one or more required entries (\code{author}, \code{year}, and/or \code{title}).
|
|
| 49 |
#' \item \strong{\code{info_refs_*}}: Files with an entry in its \code{_references} entry that has an entry
|
|
| 50 |
#' (\code{*}) that is a list (where they should all be strings).
|
|
| 51 |
#' \item \strong{\code{info_refs_author_entry}}: Files with an entry in its \code{_references} entry that has an
|
|
| 52 |
#' \code{author} entry that is missing a \code{family} entry.
|
|
| 53 |
#' \item \strong{\code{info_source_missing}}: Measures with an entry in its \code{source} entry that is missing a
|
|
| 54 |
#' required entry (\code{name} and/or \code{date_accessed}).
|
|
| 55 |
#' \item \strong{\code{info_source_*}}: Measures with an entry (\code{*}) in its \code{source} entry that is a
|
|
| 56 |
#' list (where they should all be strings). |
|
| 57 |
#' \item \strong{\code{info_citation}}: Measures with a \code{citation} entry that cannot be found in any
|
|
| 58 |
#' \code{_references} entries across measure info files within the repository.
|
|
| 59 |
#' \item \strong{\code{info_layer_source}}: Measures with an entry in its \code{layer} entry that is missing a
|
|
| 60 |
#' \code{source} entry.
|
|
| 61 |
#' \item \strong{\code{info_layer_source_url}}: Measures with an entry in its \code{layer} entry that has a list
|
|
| 62 |
#' \code{source} entry that is missing a \code{url} entry. \code{source} entries can either be a string containing a
|
|
| 63 |
#' URL, or a list with a \code{url} entry.
|
|
| 64 |
#' \item \strong{\code{info_layer_filter}}: Measures with an entry in its \code{layer} entry that has a \code{filter}
|
|
| 65 |
#' entry that is missing required entries (\code{feature}, \code{operator}, and/or \code{value}).
|
|
| 66 |
#' } |
|
| 67 |
#' or relating to data files with \strong{warnings}:
|
|
| 68 |
#' \itemize{
|
|
| 69 |
#' \item \strong{\code{warn_compressed}}: Files that do not have compression extensions
|
|
| 70 |
#' (\code{.gz}, \code{.bz2}, or \code{.xz}).
|
|
| 71 |
#' \item \strong{\code{warn_blank_colnames}}: Files with blank column names (often due to saving files with row names).
|
|
| 72 |
#' \item \strong{\code{warn_value_nas}}: Files that have \code{NA}s in their \code{value} columns; \code{NA}s here
|
|
| 73 |
#' are redundant with the tall format, and so, should be removed. |
|
| 74 |
#' \item \strong{\code{warn_double_ints}}: Variable names that have an \code{int} type, but with values that have
|
|
| 75 |
#' remainders. |
|
| 76 |
#' \item \strong{\code{warn_small_percents}}: Variable names that have a \code{percent} type, but that are all under
|
|
| 77 |
#' \code{1} (which are assumed to be raw proportions).
|
|
| 78 |
#' \item \strong{\code{warn_small_values}}: Variable names with many values (over 40%) that are under \code{.00001}, and
|
|
| 79 |
#' no values under \code{0} or over \code{1}. These values should be scaled in some way to be displayed reliably.
|
|
| 80 |
#' \item \strong{\code{warn_value_name_nas}}: Files that have \code{NA}s in their \code{name} column.
|
|
| 81 |
#' \item \strong{\code{warn_entity_info_nas}}: Files that have \code{NA}s in any of their \code{entity_info} columns.
|
|
| 82 |
#' \item \strong{\code{warn_dataset_nas}}: Files that have \code{NA}s in their \code{dataset} column.
|
|
| 83 |
#' \item \strong{\code{warn_time_nas}}: Files that have \code{NA}s in their \code{time} column.
|
|
| 84 |
#' \item \strong{\code{warn_id_nas}}: Files that have \code{NA}s in their \code{id} column.
|
|
| 85 |
#' \item \strong{\code{warn_scientific}}: Files with IDs that appear to have scientific notation (e.g., \code{1e+5});
|
|
| 86 |
#' likely introduced when the ID column was converted from numbers to characters -- IDs should always be saved as |
|
| 87 |
#' characters. |
|
| 88 |
#' \item \strong{\code{warn_bg_agg}}: Files with IDs that appear to be census block group GEOIDs,
|
|
| 89 |
#' that do not include their tract parents (i.e., IDs consisting of 12 digits, and there are no IDs consisting of |
|
| 90 |
#' their first 11 digits). These are automatically aggregated by \code{\link{site_build}}, but they should
|
|
| 91 |
#' be manually aggregated. |
|
| 92 |
#' \item \strong{\code{warn_tr_agg}}: Files with IDs that appear to be census tract GEOIDs,
|
|
| 93 |
#' that do not include their county parents (i.e., IDs consisting of 11 digits, and there are no IDs consisting of |
|
| 94 |
#' their first 5 digits). These are automatically aggregated by \code{\link{site_build}}, but they should
|
|
| 95 |
#' be manually aggregated. |
|
| 96 |
#' \item \strong{\code{warn_missing_info}}: Measures in files that do not have a corresponding \code{measure_info.json}
|
|
| 97 |
#' entry. Sometimes this happens because the entry includes a prefix that cannot be derived from the file name |
|
| 98 |
#' (which is the part after a year, such as \code{category} from \code{set_geo_2015_category.csv.xz}).
|
|
| 99 |
#' It is recommended that entries not include prefixes, and that measure names be specific |
|
| 100 |
#' (e.g., \code{category_count} rather than \code{count} with a \code{category:count} entry).
|
|
| 101 |
#' } |
|
| 102 |
#' or relating to data files with \strong{failures}:
|
|
| 103 |
#' \itemize{
|
|
| 104 |
#' \item \strong{\code{fail_read}}: Files that could not be read in.
|
|
| 105 |
#' \item \strong{\code{fail_rows}}: Files containing no rows.
|
|
| 106 |
#' \item \strong{\code{fail_time}}: Files with no \code{time} column.
|
|
| 107 |
#' \item \strong{\code{fail_idlen_county}}: Files with "county" \code{dataset}s with corresponding IDs
|
|
| 108 |
#' that are not consistently 5 characters long. |
|
| 109 |
#' \item \strong{\code{fail_idlen_tract}}: Files with "tract" \code{dataset}s with corresponding IDs
|
|
| 110 |
#' that are not consistently 11 characters long. |
|
| 111 |
#' \item \strong{\code{fail_idlen_block_group}}: Files with "block group" \code{dataset}s with corresponding IDs
|
|
| 112 |
#' that are not consistently 12 characters long. |
|
| 113 |
#' } |
|
| 114 |
#' @export |
|
| 115 | ||
| 116 |
check_repository <- function( |
|
| 117 |
dir = ".", |
|
| 118 |
search_pattern = "\\.csv(?:\\.[gbx]z2?)?$", |
|
| 119 |
exclude = NULL, |
|
| 120 |
value = "value", |
|
| 121 |
value_name = "measure", |
|
| 122 |
id = "geoid", |
|
| 123 |
time = "year", |
|
| 124 |
dataset = "region_type", |
|
| 125 |
entity_info = c("region_type", "region_name"),
|
|
| 126 |
check_values = TRUE, |
|
| 127 |
attempt_repair = FALSE, |
|
| 128 |
write_infos = FALSE, |
|
| 129 |
verbose = TRUE |
|
| 130 |
) {
|
|
| 131 | 3x |
if (!dir.exists(dir)) {
|
| 132 | ! |
cli_abort("{.path {dir}} does not exist")
|
| 133 |
} |
|
| 134 | 3x |
project_check <- check_template("repository", dir = dir)
|
| 135 | 3x |
if (project_check$exists) {
|
| 136 | 2x |
if (length(project_check$incomplete)) {
|
| 137 | 2x |
cli_alert_warning( |
| 138 | 2x |
"please update template content in {.file {project_check$incomplete}}"
|
| 139 |
) |
|
| 140 |
} |
|
| 141 |
} |
|
| 142 | 3x |
files <- list.files(dir, search_pattern, recursive = TRUE, full.names = TRUE) |
| 143 | 3x |
files <- sort(files[ |
| 144 | 3x |
!grepl( |
| 145 | 3x |
paste0( |
| 146 | 3x |
"[/\\](?:docs|code|working|original", |
| 147 | 3x |
if (length(exclude)) paste0("|", paste(exclude, collapse = "|")),
|
| 148 |
")[/\\]" |
|
| 149 |
), |
|
| 150 | 3x |
files, |
| 151 | 3x |
TRUE |
| 152 |
) |
|
| 153 |
]) |
|
| 154 | 3x |
if (!length(files)) {
|
| 155 | ! |
cli_abort("no files found")
|
| 156 |
} |
|
| 157 | 3x |
i <- 0 |
| 158 | 3x |
if (verbose) {
|
| 159 | 2x |
cli_h1("measure info")
|
| 160 |
} |
|
| 161 | 3x |
meta <- list() |
| 162 | 3x |
info_files <- sort(list.files( |
| 163 | 3x |
dir, |
| 164 | 3x |
"^measure_info[^.]*\\.json$", |
| 165 | 3x |
full.names = TRUE, |
| 166 | 3x |
recursive = TRUE |
| 167 |
)) |
|
| 168 | 3x |
info_files <- info_files[ |
| 169 | 3x |
!grepl("docs/data", info_files, fixed = TRUE) &
|
| 170 | 3x |
!duplicated(gsub("_rendered|/code/|/data/", "", info_files))
|
| 171 |
] |
|
| 172 | 3x |
results <- list(data = files, info = info_files) |
| 173 | 3x |
required_fields <- c( |
| 174 | 3x |
"category", |
| 175 | 3x |
"long_name", |
| 176 | 3x |
"short_name", |
| 177 | 3x |
"long_description", |
| 178 | 3x |
"aggregation_method", |
| 179 | 3x |
"data_type" |
| 180 |
) |
|
| 181 | 3x |
required_refs <- c("author", "year", "title")
|
| 182 | 3x |
required_source <- c("name", "date_accessed")
|
| 183 | 3x |
required_layer_filter <- c("feature", "operator", "value")
|
| 184 | 3x |
known_references <- NULL |
| 185 | 3x |
flagged_references <- list() |
| 186 | 3x |
if (verbose) {
|
| 187 | 2x |
cli_progress_step( |
| 188 | 2x |
"checking {i} of {length(info_files)} measure info files",
|
| 189 | 2x |
"checked {length(info_files)} measure info files",
|
| 190 | 2x |
spinner = TRUE |
| 191 |
) |
|
| 192 |
} |
|
| 193 | 3x |
all_issues <- list() |
| 194 | 3x |
for (f in info_files) {
|
| 195 | 7x |
m <- tryCatch( |
| 196 | 7x |
data_measure_info( |
| 197 | 7x |
f, |
| 198 | 7x |
render = TRUE, |
| 199 | 7x |
write = write_infos, |
| 200 | 7x |
verbose = FALSE, |
| 201 | 7x |
open_after = FALSE |
| 202 |
), |
|
| 203 | 7x |
error = function(e) NULL |
| 204 |
) |
|
| 205 | 7x |
if (is.null(m)) {
|
| 206 | ! |
cli_abort("measure info is malformed: {.file {f}}")
|
| 207 |
} |
|
| 208 | 7x |
i <- i + 1 |
| 209 | 7x |
if (verbose) {
|
| 210 | 2x |
cli_progress_update() |
| 211 |
} |
|
| 212 | 7x |
issues <- NULL |
| 213 | 7x |
if (!is.null(m$unit) && !is.null(m$short_name)) {
|
| 214 | ! |
issues <- "recoverably malformed (should be an object with named entries for each measure)" |
| 215 | ! |
results$info_malformed <- c(results$info_malformed, f) |
| 216 | ! |
m <- list(m) |
| 217 | ! |
names(m) <- m[[1]]$measure |
| 218 |
} |
|
| 219 | 7x |
if ("_references" %in% names(m)) {
|
| 220 | 3x |
refs <- m[["_references"]] |
| 221 | 3x |
if (is.null(names(refs))) {
|
| 222 | ! |
if (length(refs)) {
|
| 223 | ! |
results$info_refs_names[[f]] <- c(results$info_refs_names, f) |
| 224 | ! |
issues <- c(issues, "{.arg _references} entries have no names")
|
| 225 |
} |
|
| 226 |
} else {
|
|
| 227 | 3x |
for (e in names(refs)) {
|
| 228 | 7x |
known_references <- unique(c(known_references, e)) |
| 229 | 7x |
su <- !required_refs %in% names(refs[[e]]) |
| 230 | 7x |
if (any(su)) {
|
| 231 | 1x |
missing_required <- required_refs[su] |
| 232 | 1x |
results$info_refs_missing[[f]] <- c( |
| 233 | 1x |
results$info_refs_missing[[f]], |
| 234 | 1x |
paste0(e, ":", paste(missing_required, collapse = ",")) |
| 235 |
) |
|
| 236 | 1x |
issues <- c( |
| 237 | 1x |
issues, |
| 238 | 1x |
paste0( |
| 239 | 1x |
"{.arg _references} {.strong {.field ",
|
| 240 | 1x |
e, |
| 241 | 1x |
"}} is missing ", |
| 242 | 1x |
if (sum(su) > 1) "entries: " else "an entry: ", |
| 243 | 1x |
paste0("{.pkg ", missing_required, "}", collapse = ", ")
|
| 244 |
) |
|
| 245 |
) |
|
| 246 |
} |
|
| 247 | 7x |
if ("author" %in% names(refs[[e]])) {
|
| 248 |
if ( |
|
| 249 | 7x |
!is.list(refs[[e]]$author) || !is.null(names(refs[[e]]$author)) |
| 250 |
) {
|
|
| 251 | 4x |
refs[[e]]$author <- list(refs[[e]]$author) |
| 252 |
} |
|
| 253 | 7x |
for (i in seq_along(refs[[e]]$author)) {
|
| 254 |
if ( |
|
| 255 | 11x |
is.list(refs[[e]]$author[[i]]) && |
| 256 | 11x |
is.null(refs[[e]]$author[[i]]$family) |
| 257 |
) {
|
|
| 258 | 1x |
results$info_refs_author_entry[[f]] <- c( |
| 259 | 1x |
results$info_refs_author_entry[[f]], |
| 260 | 1x |
paste0(e, ":", i) |
| 261 |
) |
|
| 262 | 1x |
issues <- c( |
| 263 | 1x |
issues, |
| 264 | 1x |
paste0( |
| 265 | 1x |
"{.arg _references} {.strong {.field ",
|
| 266 | 1x |
e, |
| 267 | 1x |
"}}'s number ", |
| 268 | 1x |
i, |
| 269 | 1x |
" author is missing a {.pkg family} entry"
|
| 270 |
) |
|
| 271 |
) |
|
| 272 |
} |
|
| 273 |
} |
|
| 274 |
} |
|
| 275 | 7x |
for (re in c( |
| 276 | 7x |
"year", |
| 277 | 7x |
"title", |
| 278 | 7x |
"journal", |
| 279 | 7x |
"volume", |
| 280 | 7x |
"page", |
| 281 | 7x |
"doi", |
| 282 | 7x |
"version", |
| 283 | 7x |
"url" |
| 284 |
)) {
|
|
| 285 | 56x |
if (is.list(refs[[e]][[re]])) {
|
| 286 | 2x |
type <- paste0("info_refs_", re)
|
| 287 | 2x |
results[[type]][[f]] <- c(results[[type]][[f]], e) |
| 288 | 2x |
issues <- c( |
| 289 | 2x |
issues, |
| 290 | 2x |
paste0( |
| 291 | 2x |
"{.arg _references} {.strong {.field ",
|
| 292 | 2x |
e, |
| 293 | 2x |
"}}'s {.pkg ",
|
| 294 | 2x |
re, |
| 295 | 2x |
"} entry is a list" |
| 296 |
) |
|
| 297 |
) |
|
| 298 |
} |
|
| 299 |
} |
|
| 300 |
} |
|
| 301 |
} |
|
| 302 |
} |
|
| 303 | 7x |
for (n in sort(names(m))) {
|
| 304 | 29x |
if (!grepl("^_", n)) {
|
| 305 | 26x |
cm <- Filter( |
| 306 | 26x |
function(e) length(e) && (length(e) > 1 || e != ""), |
| 307 | 26x |
m[[n]] |
| 308 |
) |
|
| 309 | 26x |
entries <- names(cm) |
| 310 | 26x |
mf <- required_fields[!required_fields %in% entries] |
| 311 | 26x |
if (length(mf)) {
|
| 312 | 8x |
results$info_incomplete[[f]] <- c(results$info_incomplete[[f]], n) |
| 313 | 8x |
issues <- c( |
| 314 | 8x |
issues, |
| 315 | 8x |
paste0( |
| 316 | 8x |
"{.strong {.field ",
|
| 317 | 8x |
n, |
| 318 | 8x |
"}} is missing ", |
| 319 | 8x |
if (length(mf) > 1) "fields" else "a field", |
| 320 |
": ", |
|
| 321 | 8x |
paste(paste0("{.pkg ", mf, "}"), collapse = ", ")
|
| 322 |
) |
|
| 323 |
) |
|
| 324 |
} |
|
| 325 | 26x |
if ("sources" %in% entries) {
|
| 326 | 23x |
if (!is.null(names(cm$sources))) {
|
| 327 | 2x |
cm$sources <- list(cm$sources) |
| 328 |
} |
|
| 329 | 23x |
for (i in seq_along(cm$sources)) {
|
| 330 | 46x |
s <- cm$sources[[i]] |
| 331 | 46x |
if (length(s) && is.list(s)) {
|
| 332 | 46x |
su <- !required_source %in% names(s) |
| 333 | 46x |
if (any(su)) {
|
| 334 | 1x |
missing_required <- required_source[su] |
| 335 | 1x |
results$info_source_missing[[f]] <- c( |
| 336 | 1x |
results$info_source_missing[[f]], |
| 337 | 1x |
paste0(m, ":", paste(missing_required, collapse = ",")) |
| 338 |
) |
|
| 339 | 1x |
issues <- c( |
| 340 | 1x |
issues, |
| 341 | 1x |
paste0( |
| 342 | 1x |
"{.strong {.field ",
|
| 343 | 1x |
n, |
| 344 | 1x |
"}}'s number ", |
| 345 | 1x |
i, |
| 346 | 1x |
" {.arg source} entry is missing ",
|
| 347 | 1x |
if (sum(su) > 1) "entries: " else "an entry: ", |
| 348 | 1x |
paste0("{.pkg ", missing_required, "}", collapse = ", ")
|
| 349 |
) |
|
| 350 |
) |
|
| 351 |
} |
|
| 352 |
} |
|
| 353 | 46x |
for (re in c(required_source, "location", "location_url")) {
|
| 354 | 184x |
if (is.list(s[[re]])) {
|
| 355 | 1x |
type <- paste0("info_source_", re)
|
| 356 | 1x |
results[[type]][[f]] <- c(results[[type]][[f]], n) |
| 357 | 1x |
issues <- c( |
| 358 | 1x |
issues, |
| 359 | 1x |
paste0( |
| 360 | 1x |
"{.strong {.field ",
|
| 361 | 1x |
n, |
| 362 | 1x |
"}}'s number ", |
| 363 | 1x |
i, |
| 364 | 1x |
" {.arg source} entry's {.pkg ",
|
| 365 | 1x |
re, |
| 366 | 1x |
"} entry is a list" |
| 367 |
) |
|
| 368 |
) |
|
| 369 |
} |
|
| 370 |
} |
|
| 371 |
} |
|
| 372 |
} |
|
| 373 | 26x |
if ("citations" %in% entries) {
|
| 374 | 11x |
citations <- unlist(cm$citations, use.names = FALSE) |
| 375 | 11x |
su <- !citations %in% known_references |
| 376 | 11x |
if (any(su)) {
|
| 377 | 1x |
name <- paste0(f, ":::", n) |
| 378 | 1x |
flagged_references[[name]] <- citations[su] |
| 379 |
} |
|
| 380 |
} |
|
| 381 | 26x |
if ("layer" %in% entries) {
|
| 382 | 17x |
if ("source" %in% names(cm$layer)) {
|
| 383 |
if ( |
|
| 384 | 16x |
is.list(cm$layer$source) && !"url" %in% names(cm$layer$source) |
| 385 |
) {
|
|
| 386 | 1x |
results$info_layer_source_url[[f]] <- c( |
| 387 | 1x |
results$info_layer_source_url[[f]], |
| 388 | 1x |
n |
| 389 |
) |
|
| 390 | 1x |
issues <- c( |
| 391 | 1x |
issues, |
| 392 | 1x |
paste0( |
| 393 | 1x |
"{.strong {.field ",
|
| 394 | 1x |
n, |
| 395 | 1x |
"}}'s {.arg source} entry is a list, but doesn't have a {.pkg url} entry"
|
| 396 |
) |
|
| 397 |
) |
|
| 398 |
} |
|
| 399 |
} else {
|
|
| 400 | 1x |
results$info_layer_source[[f]] <- c( |
| 401 | 1x |
results$info_layer_source[[f]], |
| 402 | 1x |
n |
| 403 |
) |
|
| 404 | 1x |
issues <- c( |
| 405 | 1x |
issues, |
| 406 | 1x |
paste0( |
| 407 | 1x |
"{.strong {.field ",
|
| 408 | 1x |
n, |
| 409 | 1x |
"}}'s {.arg layer} entry is missing a {.pkg source} entry"
|
| 410 |
) |
|
| 411 |
) |
|
| 412 |
} |
|
| 413 | 17x |
if ("filter" %in% names(cm$layer)) {
|
| 414 | 14x |
if (!is.null(names(cm$layer$filter))) {
|
| 415 | 7x |
cm$layer$filter <- list(cm$layer$filter) |
| 416 |
} |
|
| 417 | 14x |
for (i in seq_along(cm$layer$filter)) {
|
| 418 | 20x |
missing_required <- required_layer_filter[ |
| 419 | 20x |
!required_layer_filter %in% names(cm$layer$filter[[i]]) |
| 420 |
] |
|
| 421 | 20x |
if (length(missing_required)) {
|
| 422 | 2x |
results$info_layer_filter[[f]] <- c( |
| 423 | 2x |
results$info_layer_filter[[f]], |
| 424 | 2x |
n |
| 425 |
) |
|
| 426 | 2x |
issues <- c( |
| 427 | 2x |
issues, |
| 428 | 2x |
paste0( |
| 429 | 2x |
"{.strong {.field ",
|
| 430 | 2x |
n, |
| 431 | 2x |
"}}'s number ", |
| 432 | 2x |
i, |
| 433 | 2x |
" {.arg filter} entry is missing ",
|
| 434 | 2x |
if (length(missing_required) > 1) {
|
| 435 | 2x |
"entries: " |
| 436 |
} else {
|
|
| 437 | ! |
"an entry: " |
| 438 |
}, |
|
| 439 | 2x |
paste( |
| 440 | 2x |
paste0("{.pkg ", missing_required, "}"),
|
| 441 | 2x |
collapse = ", " |
| 442 |
) |
|
| 443 |
) |
|
| 444 |
) |
|
| 445 |
} |
|
| 446 |
} |
|
| 447 |
} |
|
| 448 |
} |
|
| 449 |
} |
|
| 450 |
} |
|
| 451 | 7x |
if (length(issues)) {
|
| 452 | 4x |
names(issues) <- rep(">", length(issues))
|
| 453 | 4x |
all_issues[[f]] <- issues |
| 454 |
} |
|
| 455 | 7x |
if (length(m)) {
|
| 456 | 7x |
meta <- c(meta, m) |
| 457 |
} else {
|
|
| 458 | ! |
results$info_invalid <- c(results$info_invalid, f) |
| 459 |
} |
|
| 460 |
} |
|
| 461 | 3x |
rendered_names <- names(meta) |
| 462 | 3x |
if (verbose) {
|
| 463 | 2x |
cli_progress_done() |
| 464 |
} |
|
| 465 | 3x |
if (verbose && !length(meta)) {
|
| 466 | ! |
cli_alert_danger("no valid measure info")
|
| 467 |
} |
|
| 468 | 3x |
if (length(flagged_references)) {
|
| 469 | 1x |
for (r in sort(names(flagged_references))) {
|
| 470 | 1x |
su <- !flagged_references[[r]] %in% known_references |
| 471 | 1x |
if (any(su)) {
|
| 472 | 1x |
f <- strsplit(r, ":::", fixed = TRUE)[[1]] |
| 473 | 1x |
results$info_citation[[f[1]]] <- c( |
| 474 | 1x |
results$info_citation[[f[1]]], |
| 475 | 1x |
paste0( |
| 476 | 1x |
f[2], |
| 477 |
": ", |
|
| 478 | 1x |
paste(flagged_references[[r]][su], collapse = ", ") |
| 479 |
) |
|
| 480 |
) |
|
| 481 | 1x |
all_issues[[f[1]]] <- c( |
| 482 | 1x |
all_issues[[f[1]]], |
| 483 | 1x |
c( |
| 484 | 1x |
">" = paste0( |
| 485 | 1x |
"unknown {.arg citation} ",
|
| 486 | 1x |
if (sum(su) > 1) "entries" else "entry", |
| 487 | 1x |
" in {.strong {.field ",
|
| 488 | 1x |
f[2], |
| 489 |
"}}: ", |
|
| 490 | 1x |
paste0( |
| 491 | 1x |
"{.pkg ",
|
| 492 | 1x |
flagged_references[[r]][su], |
| 493 |
"}", |
|
| 494 | 1x |
collapse = ", " |
| 495 |
) |
|
| 496 |
) |
|
| 497 |
) |
|
| 498 |
) |
|
| 499 |
} |
|
| 500 |
} |
|
| 501 |
} |
|
| 502 | 3x |
if (verbose && length(all_issues)) {
|
| 503 | 2x |
cli_h2("{length(all_issues)} measure info file{? has/s have} issues")
|
| 504 | 2x |
for (f in names(all_issues)) {
|
| 505 | 2x |
cli_alert_danger("{.file {f}}:")
|
| 506 | 2x |
cli_bullets(all_issues[[f]]) |
| 507 |
} |
|
| 508 |
} |
|
| 509 | ||
| 510 | 3x |
i <- 0 |
| 511 | 3x |
if (verbose) {
|
| 512 | 2x |
cli_h1("data")
|
| 513 | 2x |
cli_progress_step( |
| 514 | 2x |
"checking {i} of {length(files)} data file{?/s}",
|
| 515 | 2x |
"checked {length(files)} data file{?/s}",
|
| 516 | 2x |
spinner = TRUE |
| 517 |
) |
|
| 518 |
} |
|
| 519 | 3x |
census_geolayers <- c(county = 5, tract = 11, "block group" = 12) |
| 520 | 3x |
required <- c(id, value_name, value) |
| 521 | 3x |
dataset_map <- NULL |
| 522 | 3x |
if (length(dataset) > 1) {
|
| 523 | ! |
dataset_map <- dataset |
| 524 | ! |
dataset <- "dataset" |
| 525 |
} |
|
| 526 | 3x |
vars <- unique(c(required, time, dataset, entity_info)) |
| 527 | 3x |
entity_info <- entity_info[!entity_info %in% c(required, time)] |
| 528 | 3x |
files_short <- sub("^/", "", sub(dir, "", files, fixed = TRUE))
|
| 529 | 3x |
for (i in seq_along(files)) {
|
| 530 | 13x |
if (verbose) {
|
| 531 | 6x |
cli_progress_update() |
| 532 |
} |
|
| 533 | 13x |
path <- files[[i]] |
| 534 | 13x |
f <- files_short[[i]] |
| 535 | 13x |
sep <- if (grepl(".csv", path, fixed = TRUE)) "," else "\t"
|
| 536 | 13x |
cols <- tryCatch( |
| 537 | 13x |
scan(path, "", sep = sep, nlines = 1, quiet = TRUE), |
| 538 | 13x |
error = function(e) NULL |
| 539 |
) |
|
| 540 | 13x |
lcols <- tolower(cols) |
| 541 | 13x |
su <- !cols %in% vars & lcols %in% vars |
| 542 | 13x |
if (any(su)) {
|
| 543 | 1x |
cols[su] <- lcols[su] |
| 544 |
} |
|
| 545 | 13x |
if (all(required %in% cols)) {
|
| 546 | 11x |
d <- if (is.null(cols)) {
|
| 547 | ! |
NULL |
| 548 |
} else {
|
|
| 549 | 11x |
tryCatch( |
| 550 | 11x |
as.data.frame(read_delim_arrow( |
| 551 | 11x |
gzfile(path), |
| 552 | 11x |
sep, |
| 553 | 11x |
skip = 1, |
| 554 | 11x |
col_names = cols, |
| 555 | 11x |
col_types = paste( |
| 556 | 11x |
c("c", "n")[as.integer(cols %in% c(value, time)) + 1L],
|
| 557 | 11x |
collapse = "" |
| 558 |
) |
|
| 559 |
)), |
|
| 560 | 11x |
error = function(e) NULL |
| 561 |
) |
|
| 562 |
} |
|
| 563 | 11x |
if (is.null(d)) {
|
| 564 | ! |
results$fail_read <- c(results$fail_read, f) |
| 565 |
} else {
|
|
| 566 | 11x |
if (nrow(d)) {
|
| 567 | 10x |
ck_values <- check_values && length(meta) |
| 568 | 10x |
if (missing(check_values) && nrow(d) > 5e6) {
|
| 569 | ! |
cli_alert_info(paste( |
| 570 | ! |
"skipping value checks for {.field {f}} due to size ({prettyNum(nrow(d), big.mark = ',')} rows);",
|
| 571 | ! |
"set {.arg check_values} to {.pkg TRUE} to force checks"
|
| 572 |
)) |
|
| 573 | ! |
ck_values <- FALSE |
| 574 |
} |
|
| 575 | 10x |
d[[id]] <- sub("^\\s+|\\s+$", "", d[[id]])
|
| 576 | 10x |
if (!time %in% cols) {
|
| 577 | 1x |
results$fail_time <- c(results$fail_time, f) |
| 578 |
} |
|
| 579 | 10x |
all_entity_info <- all(entity_info %in% cols) |
| 580 | ||
| 581 | 10x |
if (attempt_repair) {
|
| 582 | 2x |
repairs <- NULL |
| 583 | 2x |
if (!grepl("\\.[bgx]z2?$", f)) {
|
| 584 | 2x |
repairs <- "warn_compression" |
| 585 |
} |
|
| 586 | 2x |
if (any(cols == "")) {
|
| 587 | 1x |
repairs <- c(repairs, "warn_blank_colnames") |
| 588 | 1x |
d <- d[, cols != ""] |
| 589 |
} |
|
| 590 | 2x |
if (anyNA(d[[value]])) {
|
| 591 | 2x |
d <- d[!is.na(d[[value]]), ] |
| 592 | 2x |
repairs <- c(repairs, "warn_value_nas") |
| 593 | 2x |
if (ck_values) d[[value]][is.na(d[[value]])] <- 0 |
| 594 |
} |
|
| 595 | 2x |
su <- grep("\\de[+-]?\\d", d[[id]])
|
| 596 | 2x |
if (length(su)) {
|
| 597 | ! |
d[[id]][su] <- gsub( |
| 598 | ! |
"^\\s+|\\s+$", |
| 599 |
"", |
|
| 600 | ! |
format(as.numeric(d[[id]][su]), scientific = FALSE) |
| 601 |
) |
|
| 602 | ! |
repairs <- c(repairs, "warn_scientific") |
| 603 |
} |
|
| 604 | 2x |
if (nrow(d)) {
|
| 605 | 2x |
if (anyNA(d[[id]])) {
|
| 606 | ! |
repairs <- c(repairs, "warn_id_nas") |
| 607 | ! |
d <- d[!is.na(d[[id]]), ] |
| 608 |
} |
|
| 609 |
} |
|
| 610 | 2x |
if (nrow(d)) {
|
| 611 | 2x |
if (anyNA(d[[value_name]])) {
|
| 612 | ! |
repairs <- c(repairs, "warn_value_name_nas") |
| 613 | ! |
d <- d[!is.na(d[[value_name]]), ] |
| 614 |
} |
|
| 615 |
} |
|
| 616 | 2x |
if (length(dataset_map)) {
|
| 617 | ! |
data$dataset <- dataset_map[data[[id]]] |
| 618 | ! |
cols <- c(cols, "dataset") |
| 619 |
} |
|
| 620 | 2x |
if (nrow(d) && dataset %in% cols) {
|
| 621 | 2x |
if (anyNA(d[[dataset]])) {
|
| 622 | ! |
repairs <- c(repairs, "warn_dataset_nas") |
| 623 | ! |
d <- d[!is.na(d[[dataset]]), ] |
| 624 |
} |
|
| 625 |
} |
|
| 626 | 2x |
if (nrow(d) && time %in% cols) {
|
| 627 | 2x |
if (anyNA(d[[time]])) {
|
| 628 | ! |
repairs <- c(repairs, "warn_time_nas") |
| 629 | ! |
d <- d[!is.na(d[[time]]), ] |
| 630 |
} |
|
| 631 |
} |
|
| 632 | 2x |
if (nrow(d) && all_entity_info) {
|
| 633 | 2x |
if (anyNA(d[, entity_info])) {
|
| 634 | 2x |
repairs <- c(repairs, "warn_entity_info_nas") |
| 635 | 2x |
d <- d[rowSums(is.na(d[, entity_info, drop = FALSE])) == 0, ] |
| 636 |
} |
|
| 637 |
} |
|
| 638 | 2x |
if (ck_values && nrow(d)) {
|
| 639 | 2x |
md <- split(d[[value]], d[[value_name]]) |
| 640 | 2x |
for (m in names(md)) {
|
| 641 | 6x |
mm <- meta[[m]] |
| 642 | 6x |
mvs <- md[[m]] |
| 643 | 6x |
if (!is.null(mm)) {
|
| 644 | 6x |
type <- mm$aggregation_method |
| 645 | 6x |
if (is.null(type) || type == "") {
|
| 646 | 6x |
type <- if ( |
| 647 | 6x |
!is.null(mm$measure_type) && mm$measure_type == "" |
| 648 |
) {
|
|
| 649 | ! |
mm$type |
| 650 |
} else {
|
|
| 651 | 6x |
mm$measure_type |
| 652 |
} |
|
| 653 | ! |
if (is.null(type)) type <- "" |
| 654 |
} |
|
| 655 | 6x |
if (grepl("percent", type, fixed = TRUE)) {
|
| 656 | 2x |
if (any(mvs > 0) && !any(mvs > 1)) {
|
| 657 | 2x |
d[[value]][d[[value_name]] == m] <- d[[value]][ |
| 658 | 2x |
d[[value_name]] == m |
| 659 |
] * |
|
| 660 | 2x |
100 |
| 661 | 2x |
repairs <- c(repairs, "warn_small_percents") |
| 662 |
} |
|
| 663 |
} |
|
| 664 |
} |
|
| 665 |
} |
|
| 666 |
} |
|
| 667 | 2x |
if (length(repairs)) {
|
| 668 | 2x |
if (!nrow(d)) {
|
| 669 | ! |
if (verbose) {
|
| 670 | ! |
cli_alert_danger( |
| 671 | ! |
"{.strong attempting repairs ({repairs}) removed all rows of {.file {f}}}"
|
| 672 |
) |
|
| 673 |
} |
|
| 674 |
} else {
|
|
| 675 | 2x |
tf <- sub("\\..+(?:\\.[bgx]z2?)?$", ".csv.xz", path)
|
| 676 | 2x |
w <- tryCatch( |
| 677 |
{
|
|
| 678 | 2x |
write.csv(d, xzfile(tf), row.names = FALSE) |
| 679 | 2x |
TRUE |
| 680 |
}, |
|
| 681 | 2x |
error = function(e) NULL |
| 682 |
) |
|
| 683 | 2x |
if (is.null(w)) {
|
| 684 | ! |
if (verbose) {
|
| 685 | ! |
cli_alert_danger( |
| 686 | ! |
"failed to write repairs ({.field {repairs}}) to {.file {f}}"
|
| 687 |
) |
|
| 688 |
} |
|
| 689 |
} else {
|
|
| 690 | 2x |
if (path != tf) {
|
| 691 | 2x |
unlink(path) |
| 692 |
} |
|
| 693 | 2x |
if (verbose) {
|
| 694 | 2x |
cli_alert_info( |
| 695 | 2x |
"wrote repairs ({.field {repairs}}) to {.file {tf}}"
|
| 696 |
) |
|
| 697 |
} |
|
| 698 |
} |
|
| 699 |
} |
|
| 700 |
} |
|
| 701 |
} else {
|
|
| 702 | 8x |
if (!grepl("[bgx]z2?$", f)) {
|
| 703 | 2x |
results$warn_compressed <- c(results$warn_compressed, f) |
| 704 |
} |
|
| 705 | 8x |
if (any(cols == "")) {
|
| 706 | 1x |
results$warn_blank_colnames <- c(results$warn_blank_colnames, f) |
| 707 |
} |
|
| 708 | 8x |
if (anyNA(d[[value]])) {
|
| 709 | 3x |
results$warn_value_nas <- c(results$warn_value_nas, f) |
| 710 | 3x |
if (ck_values) d[[value]][is.na(d[[value]])] <- 0 |
| 711 |
} |
|
| 712 | 8x |
if (anyNA(d[[id]])) {
|
| 713 | 1x |
results$warn_id_nas <- c(results$warn_id_nas, f) |
| 714 | 1x |
d[[id]][is.na(d[[id]])] <- "NA" |
| 715 |
} |
|
| 716 | 8x |
if (any(grepl("\\de[+-]\\d", d[[id]]))) {
|
| 717 | 1x |
results$warn_scientific <- c(results$warn_scientific, f) |
| 718 |
} |
|
| 719 | 8x |
if (anyNA(d[[value_name]])) {
|
| 720 | 1x |
results$warn_value_name_nas <- c(results$warn_value_name_nas, f) |
| 721 | 1x |
d[[value_name]][is.na(d[[value_name]])] <- "NA" |
| 722 |
} |
|
| 723 | 8x |
if (dataset %in% cols && anyNA(d[[dataset]])) {
|
| 724 | 1x |
results$warn_dataset_nas <- c(results$warn_dataset_nas, f) |
| 725 | 1x |
d[[dataset]][is.na(d[[dataset]])] <- "NA" |
| 726 |
} |
|
| 727 | 8x |
if (all_entity_info && anyNA(d[, entity_info])) {
|
| 728 | 1x |
results$warn_entity_info_nas <- c(results$warn_entity_info_nas, f) |
| 729 |
} |
|
| 730 | 8x |
if (time %in% cols && anyNA(d[[time]])) {
|
| 731 | 1x |
results$warn_time_nas <- c(results$warn_time_nas, f) |
| 732 | 1x |
d[[time]][is.na(d[[time]])] <- "NA" |
| 733 |
} |
|
| 734 |
} |
|
| 735 | ||
| 736 | 10x |
if (nrow(d)) {
|
| 737 | 10x |
if (dataset %in% cols) {
|
| 738 | 3x |
for (l in names(census_geolayers)) {
|
| 739 | 9x |
if (l %in% d[[dataset]]) {
|
| 740 | 3x |
su <- d[[dataset]] == l |
| 741 | 3x |
n_match <- sum(nchar(d[[id]][su]) == census_geolayers[[l]]) |
| 742 | 3x |
if (n_match && n_match < sum(su)) {
|
| 743 | 3x |
e <- paste0("fail_idlen_", sub(" ", "", l, fixed = TRUE))
|
| 744 | 3x |
results[[e]] <- c(results[[e]], f) |
| 745 |
} |
|
| 746 |
} |
|
| 747 |
} |
|
| 748 |
} |
|
| 749 | ||
| 750 | 10x |
measures <- unique(d[[value_name]]) |
| 751 | 10x |
measures <- sort(measures[measures != "NA"]) |
| 752 | 10x |
su <- !measures %in% rendered_names |
| 753 | 10x |
if (any(su)) {
|
| 754 | 3x |
su[su] <- !make_full_name(f, measures[su]) %in% names(meta) |
| 755 |
} |
|
| 756 | 10x |
if (any(su)) {
|
| 757 | 3x |
results$warn_missing_info[[f]] <- c( |
| 758 | 3x |
results$warn_missing_info[[f]], |
| 759 | 3x |
measures[su] |
| 760 |
) |
|
| 761 |
} |
|
| 762 | ||
| 763 | 10x |
smids <- split(d[[id]], d[[value_name]]) |
| 764 | 10x |
if (ck_values) {
|
| 765 | 10x |
md <- split(d[[value]], d[[value_name]]) |
| 766 |
} |
|
| 767 | 10x |
for (m in measures) {
|
| 768 | 30x |
mids <- smids[[m]] |
| 769 | 30x |
id_chars <- nchar(mids) |
| 770 | 30x |
su <- which(id_chars == 12) |
| 771 | 30x |
if (length(su)) {
|
| 772 | 15x |
su <- su[grep("[^0-9]", mids[su], invert = TRUE)]
|
| 773 |
if ( |
|
| 774 | 15x |
length(su) && |
| 775 | 15x |
!any(unique(substring(mids[su], 1, 11)) %in% mids) |
| 776 |
) {
|
|
| 777 | 1x |
results$warn_bg_agg[[f]] <- c(results$warn_bg_agg[[f]], m) |
| 778 |
} |
|
| 779 |
} |
|
| 780 | 30x |
su <- which(id_chars == 11) |
| 781 | 30x |
if (length(su)) {
|
| 782 | 19x |
su <- su[grep("[^0-9]", mids[su], invert = TRUE)]
|
| 783 |
if ( |
|
| 784 | 19x |
length(su) && |
| 785 | 19x |
!any(unique(substring(mids[su], 1, 5)) %in% mids) |
| 786 |
) {
|
|
| 787 | 1x |
results$warn_tr_agg[[f]] <- c(results$warn_tr_agg[[f]], m) |
| 788 |
} |
|
| 789 |
} |
|
| 790 | ||
| 791 | 30x |
if (ck_values) {
|
| 792 | 30x |
mm <- meta[[m]] |
| 793 | 30x |
mvs <- md[[m]] |
| 794 | 30x |
if (!is.null(mm)) {
|
| 795 | 27x |
type <- mm$aggregation_method |
| 796 | 27x |
if (is.null(type) || type == "") {
|
| 797 | 10x |
type <- if ( |
| 798 | 10x |
!is.null(mm$measure_type) && mm$measure_type == "" |
| 799 |
) {
|
|
| 800 | 2x |
mm$type |
| 801 |
} else {
|
|
| 802 | 8x |
mm$measure_type |
| 803 |
} |
|
| 804 | 1x |
if (is.null(type)) type <- "" |
| 805 |
} |
|
| 806 | 27x |
maxv <- max(mvs) |
| 807 | 27x |
if (grepl("percent", type, fixed = TRUE)) {
|
| 808 | 5x |
if (maxv > 0 && !any(mvs > 1)) {
|
| 809 | 1x |
results$warn_small_percents[[f]] <- c( |
| 810 | 1x |
results$warn_small_percents[[f]], |
| 811 | 1x |
m |
| 812 |
) |
|
| 813 |
} |
|
| 814 |
} |
|
| 815 | 27x |
if (!is.null(mm$data_type) && mm$data_type == "integer") {
|
| 816 | 5x |
if (any(mvs %% 1 != 0)) {
|
| 817 | 3x |
results$warn_double_ints[[f]] <- c( |
| 818 | 3x |
results$warn_double_ints[[f]], |
| 819 | 3x |
m |
| 820 |
) |
|
| 821 |
} |
|
| 822 |
} else {
|
|
| 823 | 22x |
vm <- min(mvs) |
| 824 |
if ( |
|
| 825 | 22x |
vm >= 0 && maxv < 1 && mean(mvs > 0 & mvs < 1e-4) > .4 |
| 826 |
) {
|
|
| 827 | 2x |
results$warn_small_values[[f]] <- c( |
| 828 | 2x |
results$warn_small_values[[f]], |
| 829 | 2x |
m |
| 830 |
) |
|
| 831 |
} |
|
| 832 |
} |
|
| 833 |
} |
|
| 834 |
} |
|
| 835 |
} |
|
| 836 |
} |
|
| 837 |
} else {
|
|
| 838 | 1x |
results$fail_rows <- c(results$fail_rows, f) |
| 839 |
} |
|
| 840 |
} |
|
| 841 |
} else {
|
|
| 842 | 2x |
results$not_considered <- c(results$not_considered, f) |
| 843 |
} |
|
| 844 |
} |
|
| 845 | 3x |
if (verbose) {
|
| 846 | 2x |
cli_progress_done() |
| 847 |
} |
|
| 848 | ||
| 849 | 3x |
long_paths <- files_short[nchar(files_short) > 140] |
| 850 | 3x |
n_long_paths <- length(long_paths) |
| 851 | 3x |
if (verbose && n_long_paths) {
|
| 852 | ! |
cli_alert_warning( |
| 853 | ! |
"{.strong {n_long_paths} {?path is/paths are} very long (over 140 character):}"
|
| 854 |
) |
|
| 855 | ! |
cli_bullets(structure( |
| 856 | ! |
paste0("(", nchar(long_paths), ") {.field ", long_paths, "}"),
|
| 857 | ! |
names = rep(">", n_long_paths)
|
| 858 |
)) |
|
| 859 |
} |
|
| 860 | ||
| 861 | 3x |
res_summary <- c(FAIL = 0, WARN = 0, SKIP = 0, PASS = 0) |
| 862 | 3x |
if (length(results$not_considered)) {
|
| 863 | 2x |
res_summary["SKIP"] <- length(results$not_considered) |
| 864 | 2x |
if (verbose) {
|
| 865 | 1x |
cli_alert_info(paste( |
| 866 | 1x |
'{.strong skipped {res_summary["SKIP"]} file{?/s} because {?it does/they do}',
|
| 867 | 1x |
"not include all base columns ({.pkg {required}}):}"
|
| 868 |
)) |
|
| 869 | 1x |
cli_bullets(structure( |
| 870 | 1x |
paste0("{.field ", results$not_considered, "}"),
|
| 871 | 1x |
names = rep(">", length(results$not_considered))
|
| 872 |
)) |
|
| 873 |
} |
|
| 874 |
} |
|
| 875 | ||
| 876 | 3x |
warnings <- unique(unlist( |
| 877 | 3x |
lapply(grep("^warn_", sort(names(results)), value = TRUE), function(w) {
|
| 878 | 10x |
if (is.list(results[[w]])) names(results[[w]]) else results[[w]] |
| 879 |
}), |
|
| 880 | 3x |
use.names = FALSE |
| 881 |
)) |
|
| 882 | 3x |
n_warn <- length(warnings) |
| 883 | 3x |
if (n_warn) {
|
| 884 | 3x |
res_summary["WARN"] <- n_warn |
| 885 | 3x |
if (verbose) {
|
| 886 | 2x |
cli_h2("{n_warn} file{? has/s have} warnings")
|
| 887 |
} |
|
| 888 | 3x |
sections <- list( |
| 889 | 3x |
warn_compressed = "not compressed:", |
| 890 | 3x |
warn_blank_colnames = "contains blank column names:", |
| 891 | 3x |
warn_value_nas = "{.pkg {value}} column contains NAs (which are redundant):",
|
| 892 | 3x |
warn_id_nas = "{.pkg {id}} column contains NAs:",
|
| 893 | 3x |
warn_scientific = "{.pkg {id}} column appears to contain values in scientific notation:",
|
| 894 | 3x |
warn_value_name_nas = "{.pkg {value_name}} column contains NAs:",
|
| 895 | 3x |
warn_dataset_nas = "{.pkg {dataset}} column contains NAs:",
|
| 896 | 3x |
warn_time_nas = "{.pkg {time}} column contains NAs:",
|
| 897 | 3x |
warn_entity_info_nas = "entity information column{?/s} ({.pkg {entity_info}}) contain{?s/} NAs:"
|
| 898 |
) |
|
| 899 | 3x |
for (s in names(sections)) {
|
| 900 | 27x |
if (verbose && length(results[[s]])) {
|
| 901 | 9x |
cli_alert_warning(paste0("{.strong ", sections[[s]], "}"))
|
| 902 | 9x |
cli_bullets(structure( |
| 903 | 9x |
paste0("{.field ", results[[s]], "}"),
|
| 904 | 9x |
names = rep(">", length(results[[s]]))
|
| 905 |
)) |
|
| 906 |
} |
|
| 907 |
} |
|
| 908 | 3x |
sections <- list( |
| 909 | 3x |
warn_missing_info = "missing measure info entries:", |
| 910 | 3x |
warn_small_percents = "no values with a {.pkg percent} type are over 1",
|
| 911 | 3x |
warn_double_ints = "values with an {.pkg integer} data_type have decimals",
|
| 912 | 3x |
warn_small_values = "non-zero values are very small (under .00001) -- they will display as 0s", |
| 913 | 3x |
warn_bg_agg = "may have block groups that have not been aggregated to tracts:", |
| 914 | 3x |
warn_tr_agg = "may have tracts that have not been aggregated to counties:" |
| 915 |
) |
|
| 916 | 3x |
for (s in names(sections)) {
|
| 917 | 18x |
if (length(results[[s]])) {
|
| 918 | 8x |
if (verbose) {
|
| 919 | 6x |
cli_alert_warning(paste0("{.strong ", sections[[s]], "}"))
|
| 920 |
} |
|
| 921 | 8x |
if (s == "warn_missing_info") {
|
| 922 | 2x |
meta_base <- sub("^[^:]*:", "", names(meta))
|
| 923 |
} |
|
| 924 | 8x |
missing_info <- unlist( |
| 925 | 8x |
lapply( |
| 926 | 8x |
names(results[[s]]), |
| 927 | 8x |
if (s == "warn_missing_info") {
|
| 928 | 2x |
function(f) {
|
| 929 | 3x |
vars <- results[[s]][[f]] |
| 930 | 3x |
paste0( |
| 931 | 3x |
if (length(vars) > 20) {
|
| 932 | ! |
paste(prettyNum(length(vars), big.mark = ","), "variables") |
| 933 |
} else {
|
|
| 934 | 3x |
sub( |
| 935 |
"}, ([^}]+)}$", |
|
| 936 | 3x |
"}, and \\1}", |
| 937 | 3x |
paste0( |
| 938 | 3x |
paste0("{.pkg ", vars, "}"),
|
| 939 | 3x |
vapply( |
| 940 | 3x |
vars, |
| 941 | 3x |
function(m) {
|
| 942 | 3x |
w <- meta_base == m |
| 943 | 3x |
if (any(w)) {
|
| 944 | ! |
paste0( |
| 945 | ! |
" (base matches {.emph ",
|
| 946 | ! |
names(meta)[which(w)[1]], |
| 947 |
"})" |
|
| 948 |
) |
|
| 949 |
} else {
|
|
| 950 |
"" |
|
| 951 |
} |
|
| 952 |
}, |
|
| 953 |
"" |
|
| 954 |
), |
|
| 955 | 3x |
collapse = ", " |
| 956 |
) |
|
| 957 |
) |
|
| 958 |
}, |
|
| 959 | 3x |
" in {.field ",
|
| 960 | 3x |
f, |
| 961 |
"}" |
|
| 962 |
) |
|
| 963 |
} |
|
| 964 |
} else {
|
|
| 965 | 6x |
function(f) {
|
| 966 | 8x |
vars <- results[[s]][[f]] |
| 967 | 8x |
paste0( |
| 968 | 8x |
if (length(vars) > 20) {
|
| 969 | ! |
paste(prettyNum(length(vars), big.mark = ","), "variables") |
| 970 |
} else {
|
|
| 971 | 8x |
paste0("{.pkg ", vars, "}", collapse = ", ")
|
| 972 |
}, |
|
| 973 | 8x |
" in {.field ",
|
| 974 | 8x |
f, |
| 975 |
"}" |
|
| 976 |
) |
|
| 977 |
} |
|
| 978 |
} |
|
| 979 |
), |
|
| 980 | 8x |
use.names = FALSE |
| 981 |
) |
|
| 982 | 8x |
if (verbose) {
|
| 983 | 6x |
cli_bullets(structure( |
| 984 | 6x |
missing_info, |
| 985 | 6x |
names = rep(">", length(missing_info))
|
| 986 |
)) |
|
| 987 |
} |
|
| 988 |
} |
|
| 989 |
} |
|
| 990 |
} |
|
| 991 | ||
| 992 | 3x |
failures <- unique(unlist( |
| 993 | 3x |
results[grep("^fail_", names(results))],
|
| 994 | 3x |
use.names = FALSE |
| 995 |
)) |
|
| 996 | 3x |
n_fails <- length(failures) |
| 997 | 3x |
if (n_fails) {
|
| 998 | 2x |
res_summary["FAIL"] <- n_fails |
| 999 | 2x |
if (verbose) {
|
| 1000 | 2x |
cli_h2("{n_fails} file{?/s} failed checks")
|
| 1001 |
} |
|
| 1002 | 2x |
sections <- list( |
| 1003 | 2x |
fail_read = "failed to read in:", |
| 1004 | 2x |
fail_rows = "contains no data:", |
| 1005 | 2x |
fail_time = "no {.pkg {time}} column:",
|
| 1006 | 2x |
fail_idlen_county = "not all county GEOIDs are 5 characters long:", |
| 1007 | 2x |
fail_idlen_tract = "not all tract GEOIDs are 11 characters long:", |
| 1008 | 2x |
fail_idlen_block_group = "not all block group GEOIDs are 12 characters long:" |
| 1009 |
) |
|
| 1010 | 2x |
for (s in names(sections)) {
|
| 1011 | 12x |
if (verbose && length(results[[s]])) {
|
| 1012 | 4x |
cli_alert_danger(paste0("{.strong ", sections[[s]], "}"))
|
| 1013 | 4x |
cli_bullets(structure( |
| 1014 | 4x |
paste0("{.field ", results[[s]], "}"),
|
| 1015 | 4x |
names = rep(">", length(results[[s]]))
|
| 1016 |
)) |
|
| 1017 |
} |
|
| 1018 |
} |
|
| 1019 |
} |
|
| 1020 | ||
| 1021 | 3x |
res_summary["PASS"] <- sum( |
| 1022 | 3x |
!files_short %in% c(results$not_considered, warnings, failures) |
| 1023 |
) |
|
| 1024 | 3x |
results$summary <- res_summary |
| 1025 | ||
| 1026 | 3x |
if (verbose) {
|
| 1027 | 2x |
cat("\n")
|
| 1028 | 2x |
print(res_summary) |
| 1029 |
} |
|
| 1030 | 3x |
invisible(results) |
| 1031 |
} |
| 1 |
#' Add a plot to a webpage |
|
| 2 |
#' |
|
| 3 |
#' Adds a Plotly plot to a webpage, based on specified or selected variables. |
|
| 4 |
#' |
|
| 5 |
#' @param x The name of a variable, or ID of a variable selector to plot along the x-axis. |
|
| 6 |
#' @param y The name of a variable, or ID of a variable selector to plot along the y-axis. |
|
| 7 |
#' @param color The name of a variable, or ID of a variable selector to use to color lines. |
|
| 8 |
#' @param color_time The ID of a selector to specify which timepoint of \code{color} to use.
|
|
| 9 |
#' @param dataview The ID of an \code{\link{input_dataview}} component.
|
|
| 10 |
#' @param id Unique ID for the plot. |
|
| 11 |
#' @param click The ID of an input to set to a clicked line's ID. |
|
| 12 |
#' @param subto A vector of output IDs to receive hover events from. |
|
| 13 |
#' @param options A list of configuration options, with named entries for any of \code{data}, \code{layout},
|
|
| 14 |
#' or \code{options}, potentially extracted from a saved plotly object (see
|
|
| 15 |
#' \href{https://plotly.com/javascript/configuration-options}{Plotly documentation}), if \code{plotly} is \code{TRUE}.
|
|
| 16 |
#' @param plotly Logical; if \code{TRUE}, uses \href{https://plotly.com/javascript}{Plotly}.
|
|
| 17 |
#' @examples |
|
| 18 |
#' # for mpg ~ wt * am in a site based on mtcars data |
|
| 19 |
#' output_plot("wt", "mpg", "am")
|
|
| 20 |
#' @return A character vector of the content to be added. |
|
| 21 |
#' @export |
|
| 22 | ||
| 23 |
output_plot <- function( |
|
| 24 |
x = NULL, |
|
| 25 |
y = NULL, |
|
| 26 |
color = NULL, |
|
| 27 |
color_time = NULL, |
|
| 28 |
dataview = NULL, |
|
| 29 |
id = NULL, |
|
| 30 |
click = NULL, |
|
| 31 |
subto = NULL, |
|
| 32 |
options = list(), |
|
| 33 |
plotly = TRUE |
|
| 34 |
) {
|
|
| 35 | 4x |
caller <- parent.frame() |
| 36 | 4x |
building <- !is.null(attr(caller, "name")) && |
| 37 | 4x |
attr(caller, "name") == "community_site_parts" |
| 38 | 4x |
if (is.null(id)) {
|
| 39 | 2x |
id <- paste0("plot", caller$uid)
|
| 40 |
} |
|
| 41 | 4x |
entries <- c("layout", "config", "data")
|
| 42 | 4x |
if (is.character(options)) {
|
| 43 | ! |
options <- jsonlite::fromJSON(options) |
| 44 |
} |
|
| 45 | 4x |
if ("x" %in% names(options)) {
|
| 46 | ! |
options <- options$x |
| 47 |
} |
|
| 48 | 4x |
options <- options[entries[entries %in% names(options)]] |
| 49 | 4x |
defaults <- list( |
| 50 | 4x |
layout = list( |
| 51 | 4x |
hovermode = "closest", |
| 52 | 4x |
margin = list(t = 25, r = 10, b = 40, l = 60) |
| 53 |
), |
|
| 54 | 4x |
config = list( |
| 55 | 4x |
showSendToCloud = FALSE, |
| 56 | 4x |
responsive = TRUE, |
| 57 | 4x |
showTips = FALSE, |
| 58 | 4x |
displaylogo = FALSE, |
| 59 | 4x |
modeBarButtonsToAdd = c("hoverclosest", "hovercompare")
|
| 60 |
), |
|
| 61 | 4x |
data = data.frame( |
| 62 | 4x |
hoverinfo = "text", |
| 63 | 4x |
mode = "lines+markers", |
| 64 | 4x |
type = "scatter" |
| 65 |
) |
|
| 66 |
) |
|
| 67 | 4x |
so <- names(options) |
| 68 | 4x |
for (e in names(defaults)) {
|
| 69 | 12x |
if (!e %in% so) {
|
| 70 | 12x |
options[[e]] <- defaults[[e]] |
| 71 |
} else {
|
|
| 72 | ! |
soo <- names(options[[e]]) |
| 73 | ! |
for (eo in names(defaults[[e]])) {
|
| 74 | ! |
if (!eo %in% soo) options[[e]][[eo]] <- defaults[[e]][[eo]] |
| 75 |
} |
|
| 76 |
} |
|
| 77 |
} |
|
| 78 | 4x |
options$subto <- if (!is.null(subto) && length(subto) == 1) {
|
| 79 | ! |
list(subto) |
| 80 |
} else {
|
|
| 81 | 4x |
subto |
| 82 |
} |
|
| 83 | 4x |
type <- if (plotly) "plotly" else "echarts" |
| 84 | 4x |
r <- paste( |
| 85 | 4x |
c( |
| 86 | 4x |
'<div class="plotly-wrap"><div class="auto-output plotly"', |
| 87 | 4x |
if (!is.null(dataview)) paste0('data-view="', dataview, '"'),
|
| 88 | 4x |
if (!is.null(click)) paste0('data-click="', click, '"'),
|
| 89 | 4x |
if (!is.null(x)) paste0('data-x="', x, '"'),
|
| 90 | 4x |
if (!is.null(y)) paste0('data-y="', y, '"'),
|
| 91 | 4x |
if (!is.null(color)) paste0('data-color="', color, '"'),
|
| 92 | 4x |
if (!is.null(color_time)) paste0('data-colorTime="', color_time, '"'),
|
| 93 | 4x |
paste0('id="', id, '" data-autoType="', type, '"></table></div></div>')
|
| 94 |
), |
|
| 95 | 4x |
collapse = " " |
| 96 |
) |
|
| 97 | 4x |
if (building) {
|
| 98 | 2x |
dependencies <- jsonlite::read_json(system.file( |
| 99 | 2x |
"dependencies.json", |
| 100 | 2x |
package = "community" |
| 101 |
)) |
|
| 102 | 2x |
caller$dependencies$plotly <- dependencies$plotly$js |
| 103 | 2x |
caller$credits$plotly <- dependencies$plotly$info |
| 104 | 2x |
if (plotly) {
|
| 105 | 2x |
caller$plotly[[id]] <- options |
| 106 |
} else {
|
|
| 107 | ! |
caller$echarts[[id]] <- options |
| 108 |
} |
|
| 109 | 2x |
caller$content <- c(caller$content, r) |
| 110 | 2x |
caller$uid <- caller$uid + 1 |
| 111 |
} |
|
| 112 | 4x |
r |
| 113 |
} |
| 1 |
#' Add dynamic text to a website |
|
| 2 |
#' |
|
| 3 |
#' Adds a textual output based on the current state of input elements. |
|
| 4 |
#' |
|
| 5 |
#' @param text A vector of text to be parsed; see details. |
|
| 6 |
#' @param tag Tag name of the element containing the text. |
|
| 7 |
#' @param id Unique ID of the output element. |
|
| 8 |
#' @param class Class names to add to the output's element. |
|
| 9 |
#' @param condition A conditional statement to decide visibility of the entire output element. |
|
| 10 |
#' @details |
|
| 11 |
#' \describe{
|
|
| 12 |
#' \item{Input References}{\code{text} can include references to inputs by ID within curly brackets
|
|
| 13 |
#' (e.g., \code{"{input_id}").}}
|
|
| 14 |
#' \item{Conditions}{Multiple entries in \code{text} translate to separate elements. Each entry can be
|
|
| 15 |
#' conditioned on a statement within curly brackets following an initial question mark |
|
| 16 |
#' (e.g., \code{"?{input_a != 1}Input A is not 1"}). If no statement is included after the question mark,
|
|
| 17 |
#' the entry will be conditioned on a referred to input (\code{TRUE} if anything is selected).}
|
|
| 18 |
#' \item{Buttons}{Embedded reset buttons can be specified within square brackets (e.g., \code{"Reset[r input_id]"}).
|
|
| 19 |
#' Text before the brackets will be the button's display text, with multiple words included within parentheses |
|
| 20 |
#' (e.g., \code{"(Reset Input A)[r input_a]"}). If the text is a reference, this will be the default reset
|
|
| 21 |
#' reference (e.g., \code{"{input_a}[r]"} is the same as \code{"{input_a}[r input_a]"}).}
|
|
| 22 |
#' } |
|
| 23 |
#' @examples |
|
| 24 |
#' # text that shows the current value of `input_a`, and resets it on click |
|
| 25 |
#' output_text("Selection: {input_a}[r]")
|
|
| 26 |
#' |
|
| 27 |
#' # adds a parenthetical if the value of the input is 0 |
|
| 28 |
#' output_text(c("Selection: {input_a}[r]", "?{input_a == 0}(input is zero)"))
|
|
| 29 |
#' @return A character vector of the containing element of the text. |
|
| 30 |
#' @export |
|
| 31 | ||
| 32 |
output_text <- function( |
|
| 33 |
text, |
|
| 34 |
tag = "p", |
|
| 35 |
id = NULL, |
|
| 36 |
class = NULL, |
|
| 37 |
condition = NULL |
|
| 38 |
) {
|
|
| 39 | 4x |
caller <- parent.frame() |
| 40 | 4x |
building <- !is.null(attr(caller, "name")) && |
| 41 | 4x |
attr(caller, "name") == "community_site_parts" |
| 42 | 4x |
if (is.null(id)) {
|
| 43 | 3x |
id <- paste0("text", caller$uid)
|
| 44 |
} |
|
| 45 | 4x |
parsed <- list() |
| 46 | 4x |
if (!is.null(names(text))) {
|
| 47 | ! |
text <- list(text) |
| 48 |
} |
|
| 49 | 4x |
parse_text <- function(e) {
|
| 50 | 5x |
res <- list() |
| 51 | ||
| 52 |
# extracting expressions |
|
| 53 | 5x |
ex <- gsub("^\\{|\\}$", "", regmatches(e, gregexpr("\\{.*?\\}", e))[[1]])
|
| 54 | ||
| 55 |
# extracting conditional expressions |
|
| 56 | 5x |
if (grepl("^\\?", e)) {
|
| 57 | 1x |
if (grepl("^\\?\\{", e)) {
|
| 58 | 1x |
res$condition <- parse_rule(ex[1]) |
| 59 | 1x |
ex <- ex[-1] |
| 60 | 1x |
e <- sub("^\\?\\{.*?\\}", "", e)
|
| 61 |
} else {
|
|
| 62 | ! |
res$condition <- parse_rule(paste(ex, collapse = " & ")) |
| 63 | ! |
e <- gsub("?", "", e, fixed = TRUE)
|
| 64 |
} |
|
| 65 |
} |
|
| 66 | ||
| 67 |
# extracting buttons |
|
| 68 | 5x |
if (grepl("[", e, fixed = TRUE)) {
|
| 69 | 1x |
m <- gregexpr("(?:\\([^)[]*?\\)|\\{[^}[]*?\\}|\\b\\w+?)?\\[.*?\\]", e)
|
| 70 | 1x |
rb <- regmatches(e, m)[[1]] |
| 71 | 1x |
if (length(rb)) {
|
| 72 | 1x |
res$button <- list() |
| 73 | 1x |
for (b in seq_along(rb)) {
|
| 74 | 1x |
rbb <- rb[b] |
| 75 | 1x |
bid <- paste0("b", b)
|
| 76 | 1x |
res$button[[bid]] <- list( |
| 77 | 1x |
text = as.list(sub( |
| 78 |
"}", |
|
| 79 |
"", |
|
| 80 | 1x |
strsplit(gsub("^\\(|\\)?\\[.*$", "", rbb), "{", fixed = TRUE)[[
|
| 81 | 1x |
1 |
| 82 |
]], |
|
| 83 | 1x |
fixed = TRUE |
| 84 |
)), |
|
| 85 | 1x |
type = if (grepl("[r", rbb, fixed = TRUE)) {
|
| 86 | 1x |
"reset" |
| 87 | 1x |
} else if (grepl("[n", rbb, fixed = TRUE)) {
|
| 88 | ! |
"note" |
| 89 |
} else {
|
|
| 90 | ! |
"update" |
| 91 |
}, |
|
| 92 | 1x |
target = strsplit( |
| 93 | 1x |
gsub("^[^[]*\\[[^\\s]+\\s?|\\]$", "", rbb, perl = TRUE),
|
| 94 |
"," |
|
| 95 | 1x |
)[[1]] |
| 96 |
) |
|
| 97 | 1x |
if (!length(res$button[[bid]]$target)) {
|
| 98 | ! |
res$button[[bid]]$target <- strsplit( |
| 99 | ! |
if (grepl("{", rbb, fixed = TRUE)) {
|
| 100 | ! |
gsub("^[^{].*\\{|\\}.*$", "", rbb)
|
| 101 |
} else {
|
|
| 102 | ! |
sub("\\[.*$", "", rbb)
|
| 103 |
}, |
|
| 104 |
"," |
|
| 105 | ! |
)[[1]] |
| 106 |
} |
|
| 107 |
} |
|
| 108 | 1x |
regmatches(e, m) <- as.list(paste0( |
| 109 | 1x |
"_SPLT_", |
| 110 | 1x |
paste0("b", seq_along(rb)),
|
| 111 | 1x |
"_SPLT_" |
| 112 |
)) |
|
| 113 |
} |
|
| 114 |
} |
|
| 115 | ||
| 116 | 5x |
res$text <- Filter(nchar, strsplit(e, "[{}]|_SPLT_")[[1]])
|
| 117 | 5x |
res |
| 118 |
} |
|
| 119 | 4x |
for (i in seq_along(text)) {
|
| 120 | 5x |
e <- text[[i]] |
| 121 | 5x |
if (is.null(names(e))) {
|
| 122 | 5x |
parsed[[i]] <- parse_text(e) |
| 123 |
} else {
|
|
| 124 | ! |
parsed[[i]] <- lapply(seq_along(e), function(i) {
|
| 125 | ! |
r <- parse_text(e[[i]]) |
| 126 | ! |
r$condition <- parse_rule(names(e)[i]) |
| 127 | ! |
r |
| 128 |
}) |
|
| 129 |
} |
|
| 130 |
} |
|
| 131 | 4x |
r <- paste0( |
| 132 | 4x |
c( |
| 133 |
"<", |
|
| 134 | 4x |
tag, |
| 135 | 4x |
' data-autoType="text" id="', |
| 136 | 4x |
id, |
| 137 |
'"', |
|
| 138 | 4x |
' class="auto-output output-text', |
| 139 | 4x |
if (!is.null(class)) paste("", class),
|
| 140 |
'"', |
|
| 141 |
"></", |
|
| 142 | 4x |
tag, |
| 143 |
">" |
|
| 144 |
), |
|
| 145 | 4x |
collapse = "" |
| 146 |
) |
|
| 147 | 4x |
if (building) {
|
| 148 | 2x |
caller$text[[id]] <- c( |
| 149 | 2x |
list(text = parsed), |
| 150 | 2x |
if (!is.null(condition)) condition <- parse_rule(condition) |
| 151 |
) |
|
| 152 | 2x |
caller$content <- c(caller$content, r) |
| 153 | 2x |
caller$uid <- caller$uid + 1 |
| 154 |
} |
|
| 155 | 4x |
r |
| 156 |
} |
| 1 |
#' Interact with a Data Commons View |
|
| 2 |
#' |
|
| 3 |
#' Add, edit, or refresh a view within a data commons project. |
|
| 4 |
#' |
|
| 5 |
#' @param commons Path to the data commons project. |
|
| 6 |
#' @param name Name of the view (it's directory in the project's \code{"views"} directory).
|
|
| 7 |
#' Defaults to the first view. |
|
| 8 |
#' @param output Path to a site project's main directory. |
|
| 9 |
#' @param ... Passes arguments to \code{\link{data_reformat_sdad}} if the view is to be executed.
|
|
| 10 |
#' @param variables A vector of variables, to be added to the view's \code{view.json} file.
|
|
| 11 |
#' @param ids A vector of ids, to be added to the view's \code{view.json} file.
|
|
| 12 |
#' @param files A regular expression string used to filter files containing \code{variables}.
|
|
| 13 |
#' @param run_after Path to a script to be sourced after refreshing the view, or code to |
|
| 14 |
#' be added to such a script (e.g., \code{"../data_site/build.R"}).
|
|
| 15 |
#' @param run_before Path to a script to be sourced before refreshing the view, or code to |
|
| 16 |
#' be added to such a script. |
|
| 17 |
#' @param measure_info A list of variable metadata to include in the \code{measure_info.json}
|
|
| 18 |
#' file created from such files in each data repository (such as general entries like |
|
| 19 |
#' \code{"_references"}). These will supersede any entries of the same name found in data repositories.
|
|
| 20 |
#' @param remote Name of the view's GitHub repository (\code{"username/reponame"}).
|
|
| 21 |
#' @param url URL of the view's site; defaults to the GitHub Pages URL associated with \code{remote}
|
|
| 22 |
#' if provided (\code{"https://username.github.io/reponame"}).
|
|
| 23 |
#' @param children A list of child sites associated with the view. Each entry should contain at least a |
|
| 24 |
#' \code{remote} entry (GitHub repository, including user name and repo name), and optionally \code{name}
|
|
| 25 |
#' and \code{url} (link to the served site), which will otherwise be derived from \code{remote}.
|
|
| 26 |
#' @param execute Logical; if \code{FALSE}, will create/update, but not run the view.
|
|
| 27 |
#' @param prefer_repo Logical; if \code{FALSE}, will prefer distribution files (such as from Dataverse)
|
|
| 28 |
#' over those in the repositories. |
|
| 29 |
#' @param preselect_files Logical; if \code{TRUE}, will select files by ID coverage before processing them,
|
|
| 30 |
#' which can save time, but might miss data spread across multiple files. |
|
| 31 |
#' @param refresh_map Logical; if \code{TRUE}, overwrites any existing map files.
|
|
| 32 |
#' @param overwrite Logical; if \code{TRUE}, reformatted files in \code{output}.
|
|
| 33 |
#' @param verbose Logical; if \code{FALSE}, will not show status messages.
|
|
| 34 |
#' @examples |
|
| 35 |
#' \dontrun{
|
|
| 36 |
#' # create a view within a data commons project |
|
| 37 |
#' datacommons_view(".", "view_name", variables = c("variable_a", "variable_b"))
|
|
| 38 |
#' |
|
| 39 |
#' # refresh that view |
|
| 40 |
#' datacommons_view(".", "view_name")
|
|
| 41 |
#' } |
|
| 42 |
#' @return An invisible version of the view list (the view's \code{view.json} file).
|
|
| 43 |
#' @export |
|
| 44 | ||
| 45 |
datacommons_view <- function( |
|
| 46 |
commons, |
|
| 47 |
name, |
|
| 48 |
output = NULL, |
|
| 49 |
..., |
|
| 50 |
variables = NULL, |
|
| 51 |
ids = NULL, |
|
| 52 |
files = NULL, |
|
| 53 |
run_after = NULL, |
|
| 54 |
run_before = NULL, |
|
| 55 |
measure_info = list(), |
|
| 56 |
remote = NULL, |
|
| 57 |
url = NULL, |
|
| 58 |
children = list(), |
|
| 59 |
execute = TRUE, |
|
| 60 |
prefer_repo = TRUE, |
|
| 61 |
preselect_files = FALSE, |
|
| 62 |
refresh_map = FALSE, |
|
| 63 |
overwrite = FALSE, |
|
| 64 |
verbose = TRUE |
|
| 65 |
) {
|
|
| 66 | 3x |
if (missing(commons)) {
|
| 67 | ! |
cli_abort('{.arg commons} must be speficied (e.g., commons = ".")')
|
| 68 |
} |
|
| 69 | 3x |
if (missing(name)) {
|
| 70 | ! |
name <- list.files(paste0(commons, "/views"))[1] |
| 71 | ! |
if (is.na(name)) {
|
| 72 | ! |
cli_abort( |
| 73 | ! |
"{.arg name} must be specified since no views are present in {commons}"
|
| 74 |
) |
|
| 75 |
} |
|
| 76 |
} |
|
| 77 | 3x |
check <- check_template("datacommons", dir = commons)
|
| 78 | 3x |
view_dir <- normalizePath(paste0(commons, "/views/", name), "/", FALSE) |
| 79 | 3x |
dir.create(view_dir, FALSE, TRUE) |
| 80 | 3x |
paths <- paste0( |
| 81 | 3x |
view_dir, |
| 82 |
"/", |
|
| 83 | 3x |
c("view.json", "manifest.json", "run_after.R", "run_before.R")
|
| 84 |
) |
|
| 85 | 3x |
base_run_after <- run_after |
| 86 | 3x |
if (!is.null(run_after)) {
|
| 87 | ! |
if (length(run_after) > 1 || !grepl("\\w\\.\\w+$", run_after)) {
|
| 88 | ! |
if (verbose) {
|
| 89 | ! |
cli_alert_info("writting {.file run_after.R}")
|
| 90 |
} |
|
| 91 | ! |
writeLines(run_after, paths[3]) |
| 92 | ! |
base_run_after <- run_after <- paths[3] |
| 93 | ! |
} else if (!file.exists(run_after)) {
|
| 94 | ! |
base_run_after <- paste0(commons, "/", run_after) |
| 95 |
} |
|
| 96 |
} |
|
| 97 |
if ( |
|
| 98 | 3x |
!is.null(run_before) && (length(run_before) > 1 || !file.exists(run_before)) |
| 99 |
) {
|
|
| 100 | ! |
if (verbose) {
|
| 101 | ! |
cli_alert_info("writting {.file run_before.R}")
|
| 102 |
} |
|
| 103 | ! |
writeLines(run_before, paths[4]) |
| 104 | ! |
run_before <- paths[4] |
| 105 |
} |
|
| 106 | 3x |
write_view <- FALSE |
| 107 | 3x |
if (!is.null(variables)) {
|
| 108 | 3x |
variables <- variables[!grepl("^_", variables)]
|
| 109 |
} |
|
| 110 | 3x |
if (!file.exists(paths[1])) {
|
| 111 | 2x |
if (verbose) {
|
| 112 | ! |
cli_alert_info("writting new {.file view.json}")
|
| 113 |
} |
|
| 114 | 2x |
view <- list( |
| 115 | 2x |
name = name, |
| 116 | 2x |
remote = remote, |
| 117 | 2x |
url = url, |
| 118 | 2x |
output = output, |
| 119 | 2x |
run_after = run_after, |
| 120 | 2x |
run_before = run_before, |
| 121 | 2x |
variables = variables, |
| 122 | 2x |
ids = ids, |
| 123 | 2x |
files = files, |
| 124 | 2x |
children = children |
| 125 |
) |
|
| 126 | 2x |
write_view <- TRUE |
| 127 |
} else {
|
|
| 128 | 1x |
view <- jsonlite::read_json(paths[1]) |
| 129 | 1x |
if (!is.null(remote) && !identical(view$remote, remote)) {
|
| 130 | ! |
view$remote <- remote |
| 131 | ! |
write_view <- TRUE |
| 132 |
} |
|
| 133 | 1x |
if (!is.null(url) && !identical(view$url, url)) {
|
| 134 | ! |
view$url <- url |
| 135 | ! |
write_view <- TRUE |
| 136 |
} |
|
| 137 | 1x |
if (!is.null(output) && !identical(view$output, output)) {
|
| 138 | ! |
view$output <- output |
| 139 | ! |
write_view <- TRUE |
| 140 |
} |
|
| 141 | 1x |
if (!is.null(run_after) && !identical(view$run_after, run_after)) {
|
| 142 | ! |
view$run_after <- run_after |
| 143 | ! |
write_view <- TRUE |
| 144 | 1x |
} else if (length(view$run_after)) {
|
| 145 | ! |
base_run_after <- view$run_after |
| 146 | ! |
if (!file.exists(base_run_after)) {
|
| 147 | ! |
base_run_after <- paste0(commons, "/", base_run_after) |
| 148 |
} |
|
| 149 |
} |
|
| 150 | 1x |
if (!is.null(run_before) && !identical(view$run_before, run_before)) {
|
| 151 | ! |
view$run_before <- run_before |
| 152 | ! |
write_view <- TRUE |
| 153 |
} |
|
| 154 | 1x |
if (!is.null(variables) && !identical(view$variables, variables)) {
|
| 155 | 1x |
view$variables <- variables |
| 156 | 1x |
write_view <- TRUE |
| 157 |
} |
|
| 158 | 1x |
if (!is.null(ids) && !identical(view$ids, ids)) {
|
| 159 | 1x |
view$ids <- ids |
| 160 | 1x |
write_view <- TRUE |
| 161 |
} |
|
| 162 | 1x |
if (!is.null(ids) && !identical(view$files, files)) {
|
| 163 | 1x |
view$files <- files |
| 164 | 1x |
write_view <- TRUE |
| 165 |
} |
|
| 166 | 1x |
if (!is.null(children) && !identical(view$children, children)) {
|
| 167 | ! |
view$children <- children |
| 168 | ! |
write_view <- TRUE |
| 169 |
} |
|
| 170 | 1x |
if (verbose && write_view) {
|
| 171 | ! |
cli_alert_info("updating existing {.file view.json}")
|
| 172 |
} |
|
| 173 |
} |
|
| 174 | 3x |
outbase <- outdir <- view$output |
| 175 | 3x |
if (!is.null(outdir)) {
|
| 176 | 3x |
if (!dir.exists(outdir)) {
|
| 177 | 1x |
if (dir.exists(paste0(commons, "/", outdir))) {
|
| 178 | ! |
outdir <- paste0(commons, "/", outdir) |
| 179 |
} else {
|
|
| 180 | 1x |
dir.create(outdir, FALSE, TRUE) |
| 181 |
} |
|
| 182 |
} |
|
| 183 | 3x |
outbase <- sub("/docs(?:/data)?$", "", outdir)
|
| 184 |
} |
|
| 185 | 3x |
if (length(view$remote)) {
|
| 186 | ! |
remote_parts <- strsplit( |
| 187 | ! |
sub("^(?:https?://)?(?:www\\.)?github\\.com/", "", view$remote),
|
| 188 |
"/" |
|
| 189 | ! |
)[[1]] |
| 190 | ! |
if (is.null(view$url)) {
|
| 191 | ! |
view$url <- paste0( |
| 192 | ! |
"https://", |
| 193 | ! |
remote_parts[1], |
| 194 | ! |
".github.io/", |
| 195 | ! |
remote_parts[2] |
| 196 |
) |
|
| 197 |
} |
|
| 198 | ! |
if (!is.null(outdir)) {
|
| 199 | ! |
if (!dir.exists(outbase)) {
|
| 200 | ! |
outbase <- dirname(outbase) |
| 201 | ! |
dir.create(outbase, FALSE, TRUE) |
| 202 | ! |
wdir <- getwd() |
| 203 | ! |
setwd(outbase) |
| 204 | ! |
if (verbose) {
|
| 205 | ! |
cli_alert_info(paste0( |
| 206 | ! |
"cloning remote view: {.url https://github.com/",
|
| 207 | ! |
view$remote, |
| 208 |
"}" |
|
| 209 |
)) |
|
| 210 |
} |
|
| 211 | ! |
overwrite <- TRUE |
| 212 | ! |
tryCatch( |
| 213 | ! |
system2( |
| 214 | ! |
"git", |
| 215 | ! |
c("clone", paste0("https://github.com/", view$remote, ".git")),
|
| 216 | ! |
stdout = TRUE |
| 217 |
), |
|
| 218 | ! |
error = function(e) warning("remote clone failed: ", e$message)
|
| 219 |
) |
|
| 220 | ! |
setwd(wdir) |
| 221 |
} |
|
| 222 |
} |
|
| 223 |
} |
|
| 224 | 3x |
if (length(view$children)) {
|
| 225 | ! |
if (!is.null(names(view$children))) {
|
| 226 | ! |
view$children <- list(view$children) |
| 227 |
} |
|
| 228 | ! |
view$children <- lapply(view$children, function(ch) {
|
| 229 | ! |
if (is.null(ch$name)) {
|
| 230 | ! |
ch$name <- sub("^.*/", "", ch$remote)
|
| 231 |
} |
|
| 232 | ! |
if (is.null(ch$url)) {
|
| 233 | ! |
remote_parts <- strsplit( |
| 234 | ! |
sub("^(?:https?://)?(?:www\\.)?github\\.com/", "", ch$remote),
|
| 235 |
"/" |
|
| 236 | ! |
)[[1]] |
| 237 | ! |
ch$url <- paste0( |
| 238 | ! |
"https://", |
| 239 | ! |
remote_parts[1], |
| 240 | ! |
".github.io/", |
| 241 | ! |
remote_parts[2] |
| 242 |
) |
|
| 243 |
} |
|
| 244 | ! |
ch |
| 245 |
}) |
|
| 246 |
} |
|
| 247 | 3x |
if (length(view$variables)) {
|
| 248 | 3x |
view$variables <- as.character(view$variables) |
| 249 |
} |
|
| 250 | 3x |
if (length(view$ids)) {
|
| 251 | 3x |
view$ids <- as.character(view$ids) |
| 252 |
} |
|
| 253 | 3x |
if (!is.null(outbase) && !dir.exists(outbase)) {
|
| 254 | ! |
init_site(outbase, view$name, quiet = TRUE) |
| 255 |
} |
|
| 256 | 3x |
if (is.null(view$output)) {
|
| 257 | ! |
outdir <- view_dir |
| 258 |
} |
|
| 259 | 3x |
if (write_view) {
|
| 260 | 3x |
jsonlite::write_json(view, paths[1], auto_unbox = TRUE) |
| 261 |
} |
|
| 262 | 3x |
if (execute) {
|
| 263 | 3x |
source_env <- new.env() |
| 264 | 3x |
source_env$datacommons_view <- function(...) {}
|
| 265 | 3x |
if (length(view$run_before) && file.exists(view$run_before)) {
|
| 266 | ! |
if (verbose) {
|
| 267 | ! |
cli_alert_info("running pre-view script ({.file {view$run_before}})")
|
| 268 |
} |
|
| 269 | ! |
src <- parse( |
| 270 | ! |
text = gsub( |
| 271 | ! |
"community::datacommons_view", |
| 272 | ! |
"datacommons_view", |
| 273 | ! |
readLines(view$run_before, warn = FALSE), |
| 274 | ! |
fixed = TRUE |
| 275 |
) |
|
| 276 |
) |
|
| 277 | ! |
source(local = source_env, exprs = src) |
| 278 |
} |
|
| 279 | 3x |
if (verbose) {
|
| 280 | ! |
cli_alert_info("checking for file maps")
|
| 281 |
} |
|
| 282 | 3x |
map <- datacommons_map_files( |
| 283 | 3x |
commons, |
| 284 | 3x |
overwrite = refresh_map, |
| 285 | 3x |
verbose = verbose |
| 286 |
) |
|
| 287 | 3x |
files <- map$variables[ |
| 288 | 3x |
(if (length(view$files)) {
|
| 289 | ! |
grepl(view$files, map$variables$file) |
| 290 |
} else {
|
|
| 291 | 3x |
TRUE |
| 292 |
}) & |
|
| 293 | 3x |
(if (length(view$variables)) {
|
| 294 | 3x |
map$variables$full_name %in% |
| 295 | 3x |
view$variables | |
| 296 | 3x |
map$variables$dir_name %in% view$variables | |
| 297 | 3x |
map$variables$variable %in% view$variables |
| 298 |
} else {
|
|
| 299 | ! |
TRUE |
| 300 |
}) & |
|
| 301 | 3x |
(if (length(view$ids)) {
|
| 302 | 3x |
sub("^[^/]+/[^/]+/", "", map$variables$file) %in%
|
| 303 | 3x |
unique(unlist( |
| 304 | 3x |
lapply(map$ids[view$ids %in% names(map$ids)], "[[", "files"), |
| 305 | 3x |
use.names = FALSE |
| 306 |
)) |
|
| 307 |
} else {
|
|
| 308 | ! |
TRUE |
| 309 |
}), |
|
| 310 |
, |
|
| 311 | 3x |
drop = FALSE |
| 312 |
] |
|
| 313 | 3x |
manifest <- NULL |
| 314 | 3x |
if (nrow(files)) {
|
| 315 | 3x |
cfs <- paste0("/", files$file)
|
| 316 | 3x |
files <- files[ |
| 317 | 3x |
order( |
| 318 | 3x |
grepl(if (prefer_repo) "cache/" else "repos/", files$file) - |
| 319 | 3x |
Reduce( |
| 320 |
"+", |
|
| 321 | 3x |
lapply(view$ids, function(id) cfs %in% map$ids[[id]]$file) |
| 322 |
) |
|
| 323 |
), |
|
| 324 |
] |
|
| 325 | 3x |
files <- files[ |
| 326 | 3x |
!duplicated(paste(files$dir_name, basename(files$file))), |
| 327 |
, |
|
| 328 | 3x |
drop = FALSE |
| 329 |
] |
|
| 330 | 3x |
if (preselect_files) {
|
| 331 | ! |
sel_files <- unique(unlist( |
| 332 | ! |
lapply(split(files, files$dir_name), function(fs) {
|
| 333 | ! |
if (nrow(fs) == 1) {
|
| 334 | ! |
fs$file |
| 335 |
} else {
|
|
| 336 | ! |
ccfs <- sub("^/", "", fs$file)
|
| 337 | ! |
ifm <- vapply( |
| 338 | ! |
map$ids[view$ids], |
| 339 | ! |
function(im) ccfs %in% sub("^/", "", im$files),
|
| 340 | ! |
logical(length(ccfs)) |
| 341 |
) |
|
| 342 | ! |
is <- colSums(ifm) != 0 |
| 343 | ! |
sel <- NULL |
| 344 | ! |
for (i in seq_along(ccfs)) {
|
| 345 | ! |
if (any(is[ifm[i, ]])) {
|
| 346 | ! |
sel <- c(sel, fs$file[i]) |
| 347 | ! |
is[ifm[i, ]] <- FALSE |
| 348 |
} |
|
| 349 |
} |
|
| 350 | ! |
sel |
| 351 |
} |
|
| 352 |
}), |
|
| 353 | ! |
use.names = FALSE |
| 354 |
)) |
|
| 355 | ! |
files <- files[files$file %in% sel_files, ] |
| 356 |
} |
|
| 357 | 3x |
files <- files[ |
| 358 | 3x |
order(file.mtime(paste0(commons, "/", files$file)), decreasing = TRUE), |
| 359 |
] |
|
| 360 | 3x |
if (verbose) {
|
| 361 | ! |
cli_alert_info("updating manifest: {.file {paths[2]}}")
|
| 362 |
} |
|
| 363 | 3x |
repo_manifest <- jsonlite::read_json(paste0( |
| 364 | 3x |
commons, |
| 365 | 3x |
"/manifest/repos.json" |
| 366 |
)) |
|
| 367 | 3x |
manifest <- lapply(split(files, files$repo), function(r) {
|
| 368 | 3x |
hr <- repo_manifest[[r$repo[[1]]]] |
| 369 | 3x |
files <- paste0(commons, "/", unique(r$file)) |
| 370 | 3x |
names(files) <- sub("^[^/]+/[^/]+/", "", unique(r$file))
|
| 371 | 3x |
list( |
| 372 | 3x |
files = lapply(files, function(f) {
|
| 373 | 13x |
name <- sub("^/[^/]+/[^/]+/", "", sub(commons, "", f, fixed = TRUE))
|
| 374 | 13x |
if (grepl("repos/", f, fixed = TRUE)) {
|
| 375 | 13x |
m <- hr$files[[name]] |
| 376 | 13x |
m$baseurl <- hr$url |
| 377 |
} else {
|
|
| 378 | ! |
m <- hr$distributions$dataverse$files[[name]] |
| 379 | ! |
m$baseurl <- hr$distributions$dataverse$server |
| 380 |
} |
|
| 381 | 13x |
m |
| 382 |
}) |
|
| 383 |
) |
|
| 384 |
}) |
|
| 385 | 3x |
if (is.character(measure_info)) {
|
| 386 | ! |
measure_info <- if ( |
| 387 | ! |
length(measure_info) == 1 && file.exists(measure_info) |
| 388 |
) {
|
|
| 389 | ! |
jsonlite::read_json(measure_info) |
| 390 |
} else {
|
|
| 391 | ! |
as.list(measure_info) |
| 392 |
} |
|
| 393 |
} |
|
| 394 | 3x |
base_vars <- sub("^[^:/]+[:/]", "", view$variables)
|
| 395 | 3x |
for (r in unique(files$repo)) {
|
| 396 | 3x |
measure_info_files <- sort(list.files( |
| 397 | 3x |
paste0(commons, "/repos/", sub("^.+/", "", r)),
|
| 398 | 3x |
"^measure_info[^.]*\\.json$", |
| 399 | 3x |
full.names = TRUE, |
| 400 | 3x |
recursive = TRUE |
| 401 |
)) |
|
| 402 | 3x |
measure_info_files <- measure_info_files[ |
| 403 | 3x |
!grepl("/docs/data/", measure_info_files, fixed = TRUE) &
|
| 404 | 3x |
!duplicated(gsub("_rendered|/code/|/data/", "", measure_info_files))
|
| 405 |
] |
|
| 406 | 3x |
ri <- lapply(measure_info_files, function(f) {
|
| 407 | 15x |
m <- tryCatch(jsonlite::read_json(f), error = function(e) {
|
| 408 | ! |
cli_alert_warning("failed to read measure info: {.file {f}}")
|
| 409 | ! |
NULL |
| 410 |
}) |
|
| 411 | 15x |
if (all(c("measure", "type", "short_description") %in% names(m))) {
|
| 412 | ! |
m <- list(m) |
| 413 | ! |
names(m) <- m[[1]]$measure |
| 414 |
} |
|
| 415 | 15x |
remote <- paste0( |
| 416 | 15x |
get_git_remote(sub("(^.+repos/[^/]+/).*$", "\\1.git/config", f)),
|
| 417 |
"/" |
|
| 418 |
) |
|
| 419 | 15x |
source_file <- sub( |
| 420 |
"^/[^/]+/[^/]+/", |
|
| 421 | 15x |
remote, |
| 422 | 15x |
sub(commons, "", f, fixed = TRUE) |
| 423 |
) |
|
| 424 | 15x |
for (mn in names(m)) {
|
| 425 | 39x |
if (substring(mn, 1, 1) != "_") {
|
| 426 | 33x |
m[[mn]]$source_file <- source_file |
| 427 |
} |
|
| 428 |
} |
|
| 429 | 15x |
m |
| 430 |
}) |
|
| 431 | 3x |
if (length(ri)) {
|
| 432 | 3x |
ri <- unlist(ri, recursive = FALSE) |
| 433 | 3x |
nri <- names(ri) |
| 434 | 3x |
if (any(nri == "")) {
|
| 435 | ! |
for (mname in which(nri == "")) {
|
| 436 | ! |
names(ri)[mname] <- ri[[mname]]$measure |
| 437 |
} |
|
| 438 |
} |
|
| 439 | 3x |
es <- nri[substring(nri, 1, 1) == "_" & !nri %in% view$variables] |
| 440 | 3x |
if (length(es)) {
|
| 441 | 3x |
for (e in es) {
|
| 442 | 6x |
if (!is.null(names(ri[[e]]))) {
|
| 443 | 6x |
if (is.null(measure_info[[e]])) {
|
| 444 | 3x |
measure_info[[e]] <- list() |
| 445 |
} |
|
| 446 | 6x |
su <- !names(ri[[e]]) %in% names(measure_info[[e]]) |
| 447 | 6x |
if (any(su)) {
|
| 448 | 3x |
measure_info[[e]] <- c(measure_info[[e]], ri[[e]][su]) |
| 449 |
} |
|
| 450 |
} |
|
| 451 |
} |
|
| 452 |
} |
|
| 453 | 3x |
if (length(view$variables) && any(!nri %in% view$variables)) {
|
| 454 | 3x |
for (i in seq_along(nri)) {
|
| 455 | 39x |
n <- nri[i] |
| 456 | 39x |
if (n %in% base_vars) {
|
| 457 | 12x |
names(ri)[i] <- view$variables[which(base_vars == n)[1]] |
| 458 |
} else {
|
|
| 459 | 27x |
n <- sub("^[^:]*:", "", nri[i])
|
| 460 | 27x |
if (n %in% view$variables) {
|
| 461 | ! |
names(ri)[i] <- n |
| 462 |
} |
|
| 463 |
} |
|
| 464 |
} |
|
| 465 | 3x |
nri <- names(ri) |
| 466 |
} |
|
| 467 | 3x |
rendered_names <- render_info_names(ri) |
| 468 | 3x |
ri <- ri[ |
| 469 | 3x |
(if (length(view$variables)) {
|
| 470 | 3x |
nri %in% rendered_names[names(rendered_names) %in% view$variables] |
| 471 |
} else {
|
|
| 472 | ! |
TRUE |
| 473 |
}) & |
|
| 474 | 3x |
!nri %in% names(measure_info) |
| 475 |
] |
|
| 476 | 3x |
if (length(ri)) {
|
| 477 | 3x |
measure_info[names(ri)] <- lapply( |
| 478 | 3x |
ri, |
| 479 | 3x |
function(e) {
|
| 480 | 12x |
if (is.null(names(e)) && !is.null(names(e[[1]]))) e[[1]] else e |
| 481 |
} |
|
| 482 |
) |
|
| 483 |
} |
|
| 484 |
} |
|
| 485 |
} |
|
| 486 | 3x |
args <- list(...) |
| 487 | 3x |
if (length(measure_info)) {
|
| 488 | 3x |
args$measure_info <- measure_info |
| 489 |
} |
|
| 490 | 3x |
args$files <- paste0(commons, "/", unique(files$file)) |
| 491 | 3x |
args$out <- outdir |
| 492 | 3x |
args$variables <- view$variables |
| 493 | 3x |
args$ids <- view$ids |
| 494 | 3x |
args$overwrite <- overwrite |
| 495 | 3x |
args$verbose <- verbose |
| 496 | 3x |
do.call(data_reformat_sdad, args) |
| 497 |
} else {
|
|
| 498 | ! |
cli_warn("no files were found")
|
| 499 |
} |
|
| 500 | 3x |
if (length(base_run_after) && file.exists(base_run_after)) {
|
| 501 | ! |
if (verbose) {
|
| 502 | ! |
cli_alert_info("running post-view script ({.file {base_run_after}})")
|
| 503 |
} |
|
| 504 | ! |
src <- parse( |
| 505 | ! |
text = gsub( |
| 506 | ! |
"community::datacommons_view", |
| 507 | ! |
"datacommons_view", |
| 508 | ! |
readLines(base_run_after, warn = FALSE), |
| 509 | ! |
fixed = TRUE |
| 510 |
) |
|
| 511 |
) |
|
| 512 | ! |
source(local = source_env, exprs = src) |
| 513 |
} |
|
| 514 | 3x |
jsonlite::write_json( |
| 515 | 3x |
manifest, |
| 516 | 3x |
paste0(outdir, "/manifest.json"), |
| 517 | 3x |
auto_unbox = TRUE, |
| 518 | 3x |
pretty = TRUE |
| 519 |
) |
|
| 520 |
} |
|
| 521 | 3x |
init_datacommons(commons, refresh_after = FALSE, verbose = FALSE) |
| 522 | 3x |
invisible(view) |
| 523 |
} |
| 1 |
#' Create new template initializer |
|
| 2 |
#' |
|
| 3 |
#' Create a new initializer function, and a spec file against which initialized templates can be checked. |
|
| 4 |
#' |
|
| 5 |
#' @param name Name of the template to be checked. |
|
| 6 |
#' @param files List of paths to required files, relative to dir. |
|
| 7 |
#' \code{"{name}"} included in a path string will be replaced with \code{name} during checks.
|
|
| 8 |
#' A list within the main list is treated as either alternatives files (when there is a single character vector), |
|
| 9 |
#' or alternative strict sets of files (must contain all of at least one list; |
|
| 10 |
#' when there are multiple character vectors). |
|
| 11 |
#' @param dir Package directory. |
|
| 12 |
#' @param spec_dir Parent directory of the \code{files}.
|
|
| 13 |
#' @param context name of the template's context: itself, or another templated structure. |
|
| 14 |
#' @param overwrite logical; if \code{TRUE}, new files will replace existing ones.
|
|
| 15 |
#' @examples |
|
| 16 |
#' \dontrun{
|
|
| 17 |
#' |
|
| 18 |
#' # creates a version of the function spec. |
|
| 19 |
#' init_template("function", list("R/{name}.R", "tests/testthat/text-{name}.R"))
|
|
| 20 |
#' |
|
| 21 |
#' # creates a version of the shiny function, showing alternative sets |
|
| 22 |
#' init_template("shiny", list(
|
|
| 23 |
#' list( |
|
| 24 |
#' c("ui.R", "server.R"),
|
|
| 25 |
#' "app.R" |
|
| 26 |
#' ), |
|
| 27 |
#' "README.md" |
|
| 28 |
#' ), spec_dir = "app") |
|
| 29 |
#' } |
|
| 30 |
#' @return Creates a name.json file (in \code{dir/inst/specs} if it exists, or the current working directory),
|
|
| 31 |
#' and invisibly returns its path. |
|
| 32 |
#' @export |
|
| 33 | ||
| 34 |
init_template <- function( |
|
| 35 |
name, |
|
| 36 |
files, |
|
| 37 |
dir = ".", |
|
| 38 |
spec_dir = ".", |
|
| 39 |
context = name, |
|
| 40 |
overwrite = FALSE |
|
| 41 |
) {
|
|
| 42 | 1x |
if (missing(name)) {
|
| 43 | ! |
cli_abort("{.arg name} must be specified")
|
| 44 |
} |
|
| 45 | 1x |
if (missing(files)) {
|
| 46 | ! |
cli_abort("{.arg files} must be specified")
|
| 47 |
} |
|
| 48 | 1x |
name <- sub("^init_", "", name)
|
| 49 | 1x |
dir <- normalizePath(dir, "/", FALSE) |
| 50 | 1x |
spec <- list( |
| 51 | 1x |
name = name, |
| 52 | 1x |
context = context, |
| 53 | 1x |
dir = spec_dir, |
| 54 | 1x |
files = files |
| 55 |
) |
|
| 56 | 1x |
test_path <- paste0(dir, "/tests/testthat/test-init_", name, ".R") |
| 57 | 1x |
template_test <- file.exists(test_path) |
| 58 | 1x |
init_function(paste0("init_", name), dir = dir, overwrite = overwrite)
|
| 59 | 1x |
if (overwrite || !template_test) {
|
| 60 | 1x |
writeLines( |
| 61 | 1x |
paste0( |
| 62 | 1x |
"test_that(\"check_template passes\", {",
|
| 63 | 1x |
"\n dir <- tempdir(TRUE)", |
| 64 | 1x |
"\n on.exit(unlink(dir, TRUE, TRUE))", |
| 65 | 1x |
if (spec$name != spec$context) {
|
| 66 | ! |
paste0( |
| 67 | ! |
"\n init_", |
| 68 | ! |
spec$context, |
| 69 | ! |
"(\"test_context\", dir = dir)\n dir <- paste0(dir, \"/test_context\")" |
| 70 |
) |
|
| 71 |
}, |
|
| 72 | 1x |
"\n init_", |
| 73 | 1x |
name, |
| 74 | 1x |
"(\"test_", |
| 75 | 1x |
name, |
| 76 | 1x |
"\", dir = dir)", |
| 77 | 1x |
"\n expect_true(check_template(\"", |
| 78 | 1x |
name, |
| 79 | 1x |
"\", \"test_", |
| 80 | 1x |
name, |
| 81 | 1x |
"\", dir = dir)$exists)", |
| 82 | 1x |
"\n})", |
| 83 | 1x |
sep = "" |
| 84 |
), |
|
| 85 | 1x |
test_path |
| 86 |
) |
|
| 87 |
} |
|
| 88 | 1x |
path <- normalizePath( |
| 89 | 1x |
paste0( |
| 90 | 1x |
dir, |
| 91 | 1x |
if (file.exists(paste0(dir, "/inst"))) "/inst", |
| 92 | 1x |
"/specs/", |
| 93 | 1x |
name, |
| 94 | 1x |
".json" |
| 95 |
), |
|
| 96 |
"/", |
|
| 97 | 1x |
FALSE |
| 98 |
) |
|
| 99 | 1x |
if (overwrite || !file.exists(path)) {
|
| 100 | 1x |
jsonlite::write_json(spec, path, auto_unbox = TRUE) |
| 101 |
} |
|
| 102 | 1x |
if (interactive()) {
|
| 103 | ! |
cli_bullets(c( |
| 104 | ! |
v = "created a spec file for {name}:",
|
| 105 | ! |
"*" = paste0("{.file ", path, "}")
|
| 106 |
)) |
|
| 107 |
} |
|
| 108 | 1x |
invisible(path) |
| 109 |
} |
| 1 |
#' Create a website |
|
| 2 |
#' |
|
| 3 |
#' Create a repository for a static website for data documentation and exploration. |
|
| 4 |
#' |
|
| 5 |
#' @param dir Directory in which to create the site's structure. Will be created if it does not exist. |
|
| 6 |
#' @param title Title of the site. |
|
| 7 |
#' @param template Name of a template to use, which are pre-constructed \code{site.R} and \code{build.R}
|
|
| 8 |
#' files. If \code{FALSE} or not found, no such files will be made.
|
|
| 9 |
#' @param with_data Logical; if \code{TRUE}, a data sub-directory and datapackage will be created.
|
|
| 10 |
#' @param node_project Logical; if \code{TRUE}, includes files used to run the site from a Node.js server.
|
|
| 11 |
#' @param include_api Logical; if \code{TRUE}, will make a \code{netlify.toml} config file to specify the
|
|
| 12 |
#' function directory for the API function, if included by \code{\link{site_build}}.
|
|
| 13 |
#' @param overwrite Logical; if \code{TRUE}, will overwrite existing site files in \code{dir}.
|
|
| 14 |
#' @param quiet Logical; if \code{TRUE}, suppresses messages and does not navigate to the file when finished.
|
|
| 15 |
#' @examples |
|
| 16 |
#' \dontrun{
|
|
| 17 |
#' # initialize site in the current working directory |
|
| 18 |
#' init_site(".")
|
|
| 19 |
#' } |
|
| 20 |
#' @return Path to the created site directory. |
|
| 21 |
#' @export |
|
| 22 | ||
| 23 |
init_site <- function( |
|
| 24 |
dir, |
|
| 25 |
title = "app", |
|
| 26 |
template = "mtcars", |
|
| 27 |
with_data = FALSE, |
|
| 28 |
node_project = FALSE, |
|
| 29 |
include_api = FALSE, |
|
| 30 |
overwrite = FALSE, |
|
| 31 |
quiet = !interactive() |
|
| 32 |
) {
|
|
| 33 | 7x |
if (missing(dir)) {
|
| 34 | ! |
cli_abort('{.arg dir} must be speficied (e.g., dir = ".")')
|
| 35 |
} |
|
| 36 | 7x |
check <- check_template("site", dir = dir)
|
| 37 | 7x |
if (!quiet && check$exists && !overwrite) {
|
| 38 | ! |
cli_bullets(c( |
| 39 | ! |
`!` = "site files already exist", |
| 40 | ! |
i = "add {.code overwrite = TRUE} to overwrite them"
|
| 41 |
)) |
|
| 42 |
} |
|
| 43 | 7x |
dir <- normalizePath(paste0(dir, "/", check$spec$dir), "/", FALSE) |
| 44 | 7x |
dir.create(dir, FALSE, TRUE) |
| 45 | 7x |
dir <- normalizePath(dir, "/", FALSE) |
| 46 | 7x |
paths <- paste0( |
| 47 | 7x |
dir, |
| 48 |
"/", |
|
| 49 | 7x |
c( |
| 50 | 7x |
"README.md", |
| 51 | 7x |
"site.R", |
| 52 | 7x |
"package.json", |
| 53 | 7x |
"server.js", |
| 54 | 7x |
".gitignore", |
| 55 | 7x |
"build.R", |
| 56 | 7x |
"project.Rproj", |
| 57 | 7x |
"netlify.toml" |
| 58 |
) |
|
| 59 |
) |
|
| 60 | 7x |
if (overwrite) {
|
| 61 | ! |
unlink(paths, TRUE) |
| 62 |
} |
|
| 63 | 7x |
if (!file.exists(paths[1])) {
|
| 64 | 4x |
writeLines( |
| 65 | 4x |
c( |
| 66 | 4x |
paste("#", title),
|
| 67 | 4x |
"<template: Describe the site>", |
| 68 | 4x |
"\n## Run", |
| 69 | 4x |
"```R", |
| 70 | 4x |
'# remotes::install_github("miserman/community")',
|
| 71 | 4x |
"library(community)", |
| 72 | 4x |
"\n# from the site directory:", |
| 73 | 4x |
'site_build(".")',
|
| 74 |
"```" |
|
| 75 |
), |
|
| 76 | 4x |
paths[1] |
| 77 |
) |
|
| 78 |
} |
|
| 79 | 7x |
template <- paste0( |
| 80 | 7x |
system.file(package = "community"), |
| 81 | 7x |
c("/inst", ""),
|
| 82 | 7x |
"/templates/", |
| 83 | 7x |
template, |
| 84 |
"/" |
|
| 85 |
) |
|
| 86 | 7x |
template <- template[which(file.exists(template))[1]] |
| 87 | 7x |
if (!is.na(template)) {
|
| 88 | 7x |
if (!file.exists(paths[2])) {
|
| 89 | 4x |
file.copy(paste0(template, "site.R"), paths[2]) |
| 90 |
} |
|
| 91 | 3x |
if (!file.exists(paths[6])) file.copy(paste0(template, "build.R"), paths[6]) |
| 92 |
} |
|
| 93 | 7x |
if (node_project && !file.exists(paths[3])) {
|
| 94 | ! |
jsonlite::write_json( |
| 95 | ! |
list( |
| 96 | ! |
name = gsub("\\s+", "_", tolower(title)),
|
| 97 | ! |
version = "1.0.0", |
| 98 | ! |
description = "", |
| 99 | ! |
main = "server.js", |
| 100 | ! |
directories = list(doc = "docs"), |
| 101 | ! |
scripts = list(start = "node server.js"), |
| 102 | ! |
dependencies = list(express = "latest"), |
| 103 | ! |
author = "", |
| 104 | ! |
license = "ISC" |
| 105 |
), |
|
| 106 | ! |
paths[3], |
| 107 | ! |
auto_unbox = TRUE, |
| 108 | ! |
pretty = TRUE |
| 109 |
) |
|
| 110 |
} |
|
| 111 | 7x |
if (node_project && !file.exists(paths[4])) {
|
| 112 | ! |
writeLines( |
| 113 | ! |
c( |
| 114 | ! |
"'use strict'", |
| 115 | ! |
"const express = require('express'), app = express()",
|
| 116 | ! |
"app.use(express.static('docs'))",
|
| 117 | ! |
"app.listen(3000, function () {",
|
| 118 | ! |
" console.log('listening on port 3000')",
|
| 119 |
"})" |
|
| 120 |
), |
|
| 121 | ! |
paths[4] |
| 122 |
) |
|
| 123 |
} |
|
| 124 | 7x |
if (!file.exists(paths[5])) {
|
| 125 | 4x |
writeLines( |
| 126 | 4x |
c( |
| 127 | 4x |
".Rproj.user", |
| 128 | 4x |
".Rhistory", |
| 129 | 4x |
".Rdata", |
| 130 | 4x |
".httr-oauth", |
| 131 | 4x |
".DS_Store", |
| 132 | 4x |
".netlify", |
| 133 | 4x |
"*.Rproj", |
| 134 | 4x |
"node_modules", |
| 135 | 4x |
"package-lock.json", |
| 136 | 4x |
"docs/dist" |
| 137 |
), |
|
| 138 | 4x |
paths[5] |
| 139 |
) |
|
| 140 |
} |
|
| 141 | 7x |
if (!file.exists(paths[7]) && !any(grepl("\\.Rproj$", list.files(dir)))) {
|
| 142 | 6x |
writeLines("Version: 1.0\n", paths[7])
|
| 143 |
} |
|
| 144 | 7x |
if (include_api && !file.exists(paths[8])) {
|
| 145 | ! |
writeLines( |
| 146 | ! |
c( |
| 147 | ! |
"[build]", |
| 148 | ! |
" publish = 'docs'", |
| 149 | ! |
"[[redirects]]", |
| 150 | ! |
" from = '/api'", |
| 151 | ! |
" to = '/.netlify/functions/api'", |
| 152 | ! |
" status = 200", |
| 153 | ! |
"[functions]", |
| 154 | ! |
" directory = 'docs/functions'" |
| 155 |
), |
|
| 156 | ! |
paths[8] |
| 157 |
) |
|
| 158 |
} |
|
| 159 | 7x |
dir.create(paste0(dir, "/docs"), FALSE) |
| 160 | 7x |
dir.create(paste0(dir, "/docs/functions"), FALSE) |
| 161 | 7x |
docs <- grep("/docs/", check$files, fixed = TRUE, value = TRUE)
|
| 162 | 7x |
if (any(!file.exists(docs))) {
|
| 163 | 6x |
file.create(docs[!file.exists(docs)]) |
| 164 |
} |
|
| 165 | 7x |
if (with_data && !file.exists(paste0(dir, "/docs/data/datapackage.json"))) {
|
| 166 | 2x |
dir.create(paste0(dir, "/docs/data"), FALSE) |
| 167 | 2x |
init_data(title, dir = paste0(dir, "/docs/data"), quiet = TRUE) |
| 168 |
} |
|
| 169 | 7x |
if (!quiet) {
|
| 170 | ! |
cli_bullets(c( |
| 171 | ! |
v = "created a site skeleton for {title}:",
|
| 172 | ! |
"*" = paste0("{.path ", normalizePath(dir, "/", FALSE), "}")
|
| 173 |
)) |
|
| 174 | ! |
if (file.exists(paths[2])) navigateToFile(paths[2]) |
| 175 |
} |
|
| 176 | 7x |
invisible(dir) |
| 177 |
} |
| 1 |
#' Create a Data Repository |
|
| 2 |
#' |
|
| 3 |
#' Create a repository for a dataset, which may include data documentation and/or a data site. |
|
| 4 |
#' |
|
| 5 |
#' @param dir Directory in which to create the repository's structure. Will be created if it does not exist. |
|
| 6 |
#' @param datasets A character vector of dataset names; for each of these, a subdirectory will be made |
|
| 7 |
#' containing \code{code} and \code{data} directories.
|
|
| 8 |
#' @param init_data Logical; if \code{FALSE}, will not run \code{\link{init_data}} on the repository.
|
|
| 9 |
#' @param init_site Logical; if \code{FALSE}, will not run \code{\link{init_site}} on the repository.
|
|
| 10 |
#' @param init_git Logical; if \code{FALSE}, will not run \code{git init} on the repository.
|
|
| 11 |
#' @param template A character indicating which site and build template to use, between |
|
| 12 |
#' \code{sdad_dashboard} (default) and \code{repository_site}.
|
|
| 13 |
#' @param overwrite Logical; if \code{TRUE}, will overwrite existing site files in \code{dir}.
|
|
| 14 |
#' @param quiet Logical; if \code{TRUE}, suppresses messages.
|
|
| 15 |
#' @examples |
|
| 16 |
#' \dontrun{
|
|
| 17 |
#' # initialize repository in the current working directory |
|
| 18 |
#' init_repository(".")
|
|
| 19 |
#' } |
|
| 20 |
#' @return Path to the created repository directory. |
|
| 21 |
#' @export |
|
| 22 | ||
| 23 |
init_repository <- function( |
|
| 24 |
dir, |
|
| 25 |
datasets = NULL, |
|
| 26 |
init_data = TRUE, |
|
| 27 |
init_site = TRUE, |
|
| 28 |
init_git = TRUE, |
|
| 29 |
template = "sdad_dashboard", |
|
| 30 |
overwrite = FALSE, |
|
| 31 |
quiet = !interactive() |
|
| 32 |
) {
|
|
| 33 | 3x |
if (missing(dir)) {
|
| 34 | ! |
cli_abort('{.arg dir} must be speficied (e.g., dir = ".")')
|
| 35 |
} |
|
| 36 | 3x |
check <- check_template("repository", dir = dir)
|
| 37 | 3x |
datasets_inited <- file.exists(paste0(dir, "/", datasets, "/data")) |
| 38 | 3x |
dir <- normalizePath(paste0(dir, "/", check$spec$dir), "/", FALSE) |
| 39 | 3x |
dir.create(dir, FALSE, TRUE) |
| 40 | 3x |
dir.create(paste0(dir, "/docs"), FALSE) |
| 41 | 3x |
paths <- paste0(dir, "/", c("README.md", ".gitignore", "build.R", "site.R"))
|
| 42 | 3x |
if (!file.exists(paths[1])) {
|
| 43 | 2x |
writeLines( |
| 44 | 2x |
c( |
| 45 | 2x |
"<template: Describe the repository>", |
| 46 | 2x |
"\n# Structure", |
| 47 | 2x |
"This is a community data repository, created with the `community::init_repository()` function.", |
| 48 | 2x |
"1. `{set}/code/distribution/ingest.R` should download and prepare data from a public source, and output files to `{set}/data/distribution`.",
|
| 49 | 2x |
"2. `{set}/data/distribution/measure_info.json` should contain metadata for each of the measures in the distribution data file(s).",
|
| 50 | 2x |
if (init_site) {
|
| 51 | 2x |
paste( |
| 52 | 2x |
"3. `build.R` will convert the distribution data to site-ready versions,", |
| 53 | 2x |
"and `site.R` specifies the interface of the repository-specific data site." |
| 54 |
) |
|
| 55 |
} |
|
| 56 |
), |
|
| 57 | 2x |
paths[1] |
| 58 |
) |
|
| 59 |
} |
|
| 60 | 3x |
if (!file.exists(paths[2])) {
|
| 61 | 2x |
writeLines( |
| 62 | 2x |
c( |
| 63 | 2x |
".Rproj.user", |
| 64 | 2x |
".Rhistory", |
| 65 | 2x |
".RData", |
| 66 | 2x |
".httr-oauth", |
| 67 | 2x |
".DS_Store", |
| 68 | 2x |
".netlify", |
| 69 | 2x |
"*.Rproj", |
| 70 | 2x |
"node_modules", |
| 71 | 2x |
"package-lock.json", |
| 72 | 2x |
"dist", |
| 73 | 2x |
"original" |
| 74 |
), |
|
| 75 | 2x |
paths[2] |
| 76 |
) |
|
| 77 |
} |
|
| 78 | 3x |
if (init_site) {
|
| 79 | 3x |
td <- paste0( |
| 80 | 3x |
system.file(package = "community"), |
| 81 | 3x |
c("/inst", ""),
|
| 82 | 3x |
"/templates/", |
| 83 | 3x |
template, |
| 84 |
"/" |
|
| 85 |
) |
|
| 86 | 3x |
td <- td[which(file.exists(td))[1]] |
| 87 | 3x |
if (is.na(td)) {
|
| 88 | ! |
td <- paste0( |
| 89 | ! |
system.file(package = "community"), |
| 90 | ! |
"/templates/sdad_dashboard" |
| 91 |
) |
|
| 92 |
} |
|
| 93 | 3x |
if (overwrite) {
|
| 94 | ! |
unlink(paste0(dir, c("/build.R", "/site.R")))
|
| 95 |
} |
|
| 96 | 3x |
if (!file.exists(paste0(dir, "/build.R"))) {
|
| 97 | 2x |
file.copy(paste0(td, "/build.R"), paste0(dir, "/build.R")) |
| 98 |
} |
|
| 99 | 3x |
if (!file.exists(paste0(dir, "/site.R"))) {
|
| 100 | 2x |
file.copy(paste0(td, "/site.R"), paste0(dir, "/site.R")) |
| 101 |
} |
|
| 102 | 3x |
init_site(dir, with_data = init_data, quiet = TRUE) |
| 103 | ! |
} else if (init_data) {
|
| 104 | ! |
init_data("data", quiet = TRUE)
|
| 105 |
} |
|
| 106 | 3x |
if (is.character(datasets) && any(!datasets_inited)) {
|
| 107 | 3x |
for (i in seq_along(datasets)) {
|
| 108 | 3x |
dataset <- datasets[i] |
| 109 | 3x |
dirs <- paste0(dir, "/", dataset, c("/code/distribution", "/data"))
|
| 110 | 3x |
if (!any(file.exists(dirs))) {
|
| 111 | 3x |
dir.create(dirs[[1]], FALSE, TRUE) |
| 112 | 3x |
ingest_file <- paste0(dirs[[1]], "/ingest.R") |
| 113 | 3x |
if (!file.exists(ingest_file)) {
|
| 114 | 3x |
writeLines( |
| 115 | 3x |
"# <template: use this file to set up the creation and/or preparation of the data>", |
| 116 | 3x |
ingest_file |
| 117 |
) |
|
| 118 |
} |
|
| 119 | 3x |
dir.create(paste0(dirs[[2]], "/original"), FALSE, TRUE) |
| 120 | 3x |
dir.create(paste0(dirs[[2]], "/working"), FALSE) |
| 121 | 3x |
dir.create(paste0(dirs[[2]], "/distribution"), FALSE) |
| 122 | 3x |
info_file <- paste0(dirs[[2]], "/distribution/measure_info.json") |
| 123 | 3x |
if (!file.exists(info_file)) writeLines("{}", info_file)
|
| 124 |
} |
|
| 125 |
} |
|
| 126 |
} |
|
| 127 |
if ( |
|
| 128 | 3x |
init_git && !file.exists(paste0(dir, "/.git")) && Sys.which("git") != ""
|
| 129 |
) {
|
|
| 130 | 1x |
wd <- getwd() |
| 131 | 1x |
on.exit(setwd(wd)) |
| 132 | 1x |
setwd(dir) |
| 133 | 1x |
system2("git", "init")
|
| 134 |
} |
|
| 135 | 3x |
invisible(dir) |
| 136 |
} |
| 1 |
#' Reformat an SDAD-formatted dataset |
|
| 2 |
#' |
|
| 3 |
#' Unify multiple files, which each contain a tall set of variables associated with regions. |
|
| 4 |
#' |
|
| 5 |
#' The basic assumption is that there are (a) entities which (b) exist in a hierarchy, and |
|
| 6 |
#' (c1) have a static set of features and (c2) a set of variable features which |
|
| 7 |
#' (d) are assessed at multiple time points. |
|
| 8 |
#' |
|
| 9 |
#' For example (and generally), entities are (a) regions, with (b) smaller regions making up larger regions, |
|
| 10 |
#' and which (c1) have names, and (c2) population and demographic counts (d) between 2009 and 2019. |
|
| 11 |
#' |
|
| 12 |
#' @param files A character vector of file paths, or the path to a directory containing data files. |
|
| 13 |
#' @param out Path to a directory to write files to; if not specified, files will not be written. |
|
| 14 |
#' @param variables Vector of variable names (in the \code{value_name} column) to be included.
|
|
| 15 |
#' @param ids Vector of IDs (in the \code{id} column) to be included.
|
|
| 16 |
#' @param value Name of the column containing variable values. |
|
| 17 |
#' @param value_name Name of the column containing variable names; assumed to be a single variable per file if |
|
| 18 |
#' not present. |
|
| 19 |
#' @param id Column name of IDs which uniquely identify entities. |
|
| 20 |
#' @param time Column name of the variable representing time. |
|
| 21 |
#' @param dataset Column name used to separate entity scales. |
|
| 22 |
#' @param entity_info A list containing variable names to extract and create an ids map from ( |
|
| 23 |
#' \code{entity_info.json}, created in the output directory). Entries can be named to rename the
|
|
| 24 |
#' variables they refer to in entity features. |
|
| 25 |
#' @param measure_info Measure info to add file information to (as \code{origin}) to, and write to \code{out}.
|
|
| 26 |
#' @param metadata A matrix-like object with additional information associated with entities, |
|
| 27 |
#' (such as region types and names) to be merged by \code{id}.
|
|
| 28 |
#' @param formatters A list of functions to pass columns through, with names identifying those columns |
|
| 29 |
#' (e.g., \code{list(region_name = function(x) sub(",.*$", "", x))} to strip text after a comma in the
|
|
| 30 |
#' "region_name" column). |
|
| 31 |
#' @param compression A character specifying the type of compression to use on the created files, |
|
| 32 |
#' between \code{"gzip"}, \code{"bzip2"}, and \code{"xz"}. Set to \code{FALSE} to disable compression.
|
|
| 33 |
#' @param read_existing Logical; if \code{FALSE}, will not read in existing sets.
|
|
| 34 |
#' @param overwrite Logical; if \code{TRUE}, will overwrite existing reformatted files, even if
|
|
| 35 |
#' the source files are older than it. |
|
| 36 |
#' @param get_coverage Logical; if \code{FALSE}, will not calculate a summary of variable coverage (\code{coverage.csv}).
|
|
| 37 |
#' @param verbose Logical; if \code{FALSE}, will not print status messages.
|
|
| 38 |
#' @examples |
|
| 39 |
#' dir <- paste0(tempdir(), "/reformat_example") |
|
| 40 |
#' dir.create(dir, FALSE) |
|
| 41 |
#' |
|
| 42 |
#' # minimal example |
|
| 43 |
#' data <- data.frame( |
|
| 44 |
#' geoid = 1:10, |
|
| 45 |
#' value = 1 |
|
| 46 |
#' ) |
|
| 47 |
#' write.csv(data, paste0(dir, "/data.csv"), row.names = FALSE) |
|
| 48 |
#' (data_reformat_sdad(dir)) |
|
| 49 |
#' |
|
| 50 |
#' # multiple variables |
|
| 51 |
#' data <- data.frame( |
|
| 52 |
#' geoid = 1:10, |
|
| 53 |
#' value = 1, |
|
| 54 |
#' measure = paste0("v", 1:2)
|
|
| 55 |
#' ) |
|
| 56 |
#' write.csv(data, paste0(dir, "/data.csv"), row.names = FALSE) |
|
| 57 |
#' (data_reformat_sdad(dir)) |
|
| 58 |
#' |
|
| 59 |
#' # multiple datasets |
|
| 60 |
#' data <- data.frame( |
|
| 61 |
#' geoid = 1:10, |
|
| 62 |
#' value = 1, |
|
| 63 |
#' measure = paste0("v", 1:2),
|
|
| 64 |
#' region_type = rep(c("a", "b"), each = 5)
|
|
| 65 |
#' ) |
|
| 66 |
#' write.csv(data, paste0(dir, "/data.csv"), row.names = FALSE) |
|
| 67 |
#' (data_reformat_sdad(dir)) |
|
| 68 |
#' @return An invisible list of the unified variable datasets, split into datasets. |
|
| 69 |
#' @export |
|
| 70 | ||
| 71 |
data_reformat_sdad <- function( |
|
| 72 |
files, |
|
| 73 |
out = NULL, |
|
| 74 |
variables = NULL, |
|
| 75 |
ids = NULL, |
|
| 76 |
value = "value", |
|
| 77 |
value_name = "measure", |
|
| 78 |
id = "geoid", |
|
| 79 |
time = "year", |
|
| 80 |
dataset = "region_type", |
|
| 81 |
entity_info = c(type = "region_type", name = "region_name"), |
|
| 82 |
measure_info = list(), |
|
| 83 |
metadata = NULL, |
|
| 84 |
formatters = NULL, |
|
| 85 |
compression = "xz", |
|
| 86 |
read_existing = TRUE, |
|
| 87 |
overwrite = FALSE, |
|
| 88 |
get_coverage = TRUE, |
|
| 89 |
verbose = TRUE |
|
| 90 |
) {
|
|
| 91 | 4x |
base_dir <- "./" |
| 92 | 4x |
if (length(files) == 1 && dir.exists(files)) {
|
| 93 | ! |
base_dir <- files |
| 94 | ! |
files <- list.files(files, full.names = TRUE) |
| 95 |
} |
|
| 96 | 4x |
if (any(!file.exists(files))) {
|
| 97 | ! |
files <- files[!file.exists(files)] |
| 98 | ! |
cli_abort("file{? does/s do} not exist: {files}")
|
| 99 |
} |
|
| 100 | 4x |
if (!is.null(metadata) && !id %in% colnames(metadata)) {
|
| 101 | ! |
cli_abort("{.arg metadata} does not have an id ({id}) column")
|
| 102 |
} |
|
| 103 | 4x |
vars <- c(value, value_name, id, time, dataset) |
| 104 | 4x |
spec <- c( |
| 105 | 4x |
missing(value), |
| 106 | 4x |
missing(value_name), |
| 107 | 4x |
missing(id), |
| 108 | 4x |
missing(time), |
| 109 | 4x |
missing(dataset), |
| 110 | 4x |
rep(missing(entity_info), length(entity_info)) |
| 111 |
) |
|
| 112 | 4x |
data <- list() |
| 113 | 4x |
names <- list() |
| 114 | 4x |
i <- 0 |
| 115 | 4x |
if (verbose) {
|
| 116 | 1x |
cli_progress_step( |
| 117 | 1x |
"reading in {i}/{length(files)} original file{?s}",
|
| 118 | 1x |
spinner = TRUE |
| 119 |
) |
|
| 120 |
} |
|
| 121 | 4x |
max_age <- max(file.mtime(files)) |
| 122 | 4x |
check_variables <- check_ids <- FALSE |
| 123 | 4x |
if (length(ids)) {
|
| 124 | 3x |
check_ids <- TRUE |
| 125 | 3x |
ids <- unique(as.character(ids)) |
| 126 |
} |
|
| 127 | 4x |
for (f in files) {
|
| 128 | 15x |
if (verbose) {
|
| 129 | 2x |
i <- i + 1 |
| 130 | 2x |
cli_progress_update() |
| 131 |
} |
|
| 132 | 15x |
d <- attempt_read(f, id) |
| 133 | 15x |
if (is.null(d)) {
|
| 134 | ! |
if (verbose) {
|
| 135 | ! |
cli_warn("failed to read in file: {f}")
|
| 136 |
} |
|
| 137 | ! |
next |
| 138 |
} |
|
| 139 | 15x |
if (!id %in% colnames(d)) {
|
| 140 | ! |
if (verbose) {
|
| 141 | ! |
cli_warn("file has no ID column: {f}")
|
| 142 |
} |
|
| 143 | ! |
next |
| 144 |
} |
|
| 145 | 15x |
if (anyNA(d[[id]])) {
|
| 146 | ! |
d <- d[!is.na(d[[id]]), ] |
| 147 |
} |
|
| 148 | 15x |
if (!nrow(d)) {
|
| 149 | ! |
if (verbose) {
|
| 150 | ! |
cli_warn("file has no observations: {f}")
|
| 151 |
} |
|
| 152 | ! |
next |
| 153 |
} |
|
| 154 | 15x |
lcols <- tolower(colnames(d)) |
| 155 | 15x |
if (any(!vars %in% colnames(d))) {
|
| 156 | 4x |
l <- !colnames(d) %in% vars & lcols %in% vars |
| 157 | 4x |
colnames(d)[l] <- lcols[l] |
| 158 |
} |
|
| 159 | 15x |
d[[id]] <- gsub("^\\s+|\\s+$", "", d[[id]])
|
| 160 | 15x |
if (check_ids) {
|
| 161 | 13x |
su <- grepl("\\de[+-]\\d", d[[id]], perl = TRUE)
|
| 162 | 13x |
if (any(su)) {
|
| 163 | ! |
d[[id]][su] <- gsub( |
| 164 | ! |
"^\\s+|\\s+$", |
| 165 |
"", |
|
| 166 | ! |
format(as.numeric(d[[id]][su]), scientific = FALSE) |
| 167 |
) |
|
| 168 |
} |
|
| 169 | 13x |
su <- d[[id]] %in% ids |
| 170 | 13x |
if (!all(su)) {
|
| 171 | 13x |
d <- d[su, ] |
| 172 |
} |
|
| 173 | 13x |
if (!nrow(d)) {
|
| 174 | 2x |
if (verbose) {
|
| 175 | ! |
cli_warn("file has none of the requested IDs: {f}")
|
| 176 |
} |
|
| 177 | 2x |
next |
| 178 |
} |
|
| 179 |
} |
|
| 180 | 13x |
if (any(su <- !vars %in% colnames(d))) {
|
| 181 | 4x |
if (all(su)) {
|
| 182 | ! |
cli_warn("no variables found in file {f}")
|
| 183 | ! |
next |
| 184 |
} |
|
| 185 | 4x |
if (any(!spec[su])) {
|
| 186 | ! |
cli_warn( |
| 187 | ! |
"table from {f} does not have {?a column name/column names} {.var {vars[su][!spec[su]]}}"
|
| 188 |
) |
|
| 189 | ! |
next |
| 190 |
} |
|
| 191 | 4x |
vars <- vars[!su] |
| 192 | 4x |
spec <- spec[!su] |
| 193 |
} |
|
| 194 | 13x |
names <- c(names, list(colnames(d))) |
| 195 | 13x |
if (grepl("repos/", f, fixed = TRUE)) {
|
| 196 | 11x |
remote <- get_git_remote(sub("(^.+repos/[^/]+/).*$", "\\1.git/config", f))
|
| 197 | 11x |
if (length(remote)) {
|
| 198 | 11x |
d$file <- paste0(remote, sub("^.+repos/[^/]+", "", f))
|
| 199 |
} |
|
| 200 | ! |
if (!"file" %in% colnames(d)) d$file <- sub("^.+repos/", "", f)
|
| 201 |
} else {
|
|
| 202 | 2x |
if (!grepl("/$", base_dir)) {
|
| 203 | ! |
base_dir <- paste0(base_dir, "/") |
| 204 |
} |
|
| 205 | 2x |
remote <- get_git_remote(paste0(base_dir, ".git/config")) |
| 206 | 2x |
d$file <- gsub( |
| 207 |
"//+", |
|
| 208 |
"/", |
|
| 209 | 2x |
if (length(remote)) {
|
| 210 | ! |
paste0(remote, "/", sub(base_dir, "", f, fixed = TRUE)) |
| 211 |
} else {
|
|
| 212 | 2x |
sub(base_dir, "", f, fixed = TRUE) |
| 213 |
} |
|
| 214 |
) |
|
| 215 |
} |
|
| 216 | 13x |
data <- c(data, list(d)) |
| 217 |
} |
|
| 218 | 4x |
if (verbose) {
|
| 219 | 1x |
cli_progress_done() |
| 220 |
} |
|
| 221 | 4x |
common <- Reduce(intersect, names) |
| 222 | 4x |
if (!value %in% vars) {
|
| 223 | 1x |
a <- common[!common %in% vars] |
| 224 | 1x |
if (!length(a)) {
|
| 225 | ! |
cli_abort("could not figure out which column might contain values")
|
| 226 |
} |
|
| 227 | 1x |
if (length(a) > 1) {
|
| 228 | ! |
a <- a[which(vapply(a, function(col) is.numeric(d[[col]]), TRUE))] |
| 229 |
} |
|
| 230 | 1x |
if (!length(a)) {
|
| 231 | ! |
cli_abort(c( |
| 232 | ! |
"no potential value columns were numeric", |
| 233 | ! |
i = "check variable classes, or specify {.arg value}"
|
| 234 |
)) |
|
| 235 |
} |
|
| 236 | 1x |
value <- a[1] |
| 237 | 1x |
vars <- c(value, vars) |
| 238 |
} |
|
| 239 | 4x |
all <- unique(unlist(names)) |
| 240 | 4x |
all <- all[all %in% vars & (all == id | !all %in% colnames(metadata))] |
| 241 | 4x |
vars <- c(all, "file") |
| 242 | 4x |
if (length(variables)) {
|
| 243 | 3x |
check_variables <- TRUE |
| 244 | 3x |
variables <- unique(as.character(variables)) |
| 245 |
} |
|
| 246 | 4x |
data <- do.call( |
| 247 | 4x |
rbind, |
| 248 | 4x |
lapply(seq_along(data), function(i) {
|
| 249 | 13x |
d <- data[[i]] |
| 250 | 13x |
mv <- vars[!vars %in% colnames(d)] |
| 251 | 13x |
if (length(mv)) {
|
| 252 | ! |
d[, vars[!vars %in% colnames(d)]] <- "" |
| 253 |
} |
|
| 254 | 13x |
d <- d[, vars] |
| 255 | 13x |
if (anyNA(d)) {
|
| 256 | ! |
d <- d[rowSums(is.na(d)) == 0, ] |
| 257 |
} |
|
| 258 | 13x |
if (check_variables) {
|
| 259 | 11x |
ovars <- unique(d[[value_name]]) |
| 260 | 11x |
su <- !ovars %in% variables |
| 261 | 11x |
if (any(su)) {
|
| 262 | 11x |
names(ovars) <- ovars |
| 263 | 11x |
ovars[] <- make_full_name(d$file[[1]], ovars) |
| 264 | 11x |
su <- su & ovars %in% variables |
| 265 | 11x |
for (i in which(su)) {
|
| 266 | 41x |
d[[value_name]][d[[value_name]] == names(ovars)[i]] <- ovars[i] |
| 267 |
} |
|
| 268 |
} |
|
| 269 | 11x |
d <- d[d[[value_name]] %in% variables, ] |
| 270 |
} |
|
| 271 | 13x |
d |
| 272 |
}) |
|
| 273 |
) |
|
| 274 | 4x |
if (is.null(data) || !nrow(data)) {
|
| 275 | ! |
cli_abort("no datasets contained selected variables and/or IDs")
|
| 276 |
} |
|
| 277 | 4x |
cn <- colnames(data) |
| 278 | 4x |
if (!id %in% vars) {
|
| 279 | ! |
id <- "id" |
| 280 | ! |
vars <- c(id, vars) |
| 281 | ! |
data <- cbind( |
| 282 | ! |
id = unlist(lapply(table(data$file), seq_len), use.names = FALSE), |
| 283 | ! |
data |
| 284 |
) |
|
| 285 |
} |
|
| 286 | 4x |
data[[id]] <- as.character(data[[id]]) |
| 287 | 4x |
if (!is.null(metadata)) {
|
| 288 | ! |
su <- colnames(data) != id & colnames(data) %in% colnames(metadata) |
| 289 | ! |
if (any(su)) {
|
| 290 | ! |
data <- data[, colnames(data) == id | !su, drop = FALSE] |
| 291 |
} |
|
| 292 | ! |
if (verbose) {
|
| 293 | ! |
cli_progress_step("merging in metadata", msg_done = "merged in metadata")
|
| 294 |
} |
|
| 295 | ! |
metadata <- as.data.frame(metadata[ |
| 296 | ! |
!duplicated(metadata[[id]]) & metadata[[id]] %in% data[[id]], |
| 297 |
]) |
|
| 298 | ! |
if (!nrow(metadata)) {
|
| 299 | ! |
cli_abort("{.arg metadata} had no ids in common with data")
|
| 300 |
} |
|
| 301 | ! |
rownames(metadata) <- metadata[[id]] |
| 302 | ! |
metadata[[id]] <- NULL |
| 303 | ! |
su <- data[[id]] %in% rownames(metadata) |
| 304 | ! |
if (!all(su)) {
|
| 305 | ! |
if (verbose) {
|
| 306 | ! |
cli_warn( |
| 307 | ! |
"{sum(!su)} rows contain IDs not in {.arg metadata} IDs, and will be dropped"
|
| 308 |
) |
|
| 309 |
} |
|
| 310 | ! |
data <- data[su, ] |
| 311 |
} |
|
| 312 | ! |
data <- cbind(data, metadata[data[[id]], , drop = FALSE]) |
| 313 | ! |
cn <- colnames(data) |
| 314 | ! |
vars <- c(vars, colnames(metadata)) |
| 315 | ! |
if (verbose) cli_progress_done() |
| 316 |
} |
|
| 317 | 4x |
if (!is.null(formatters)) {
|
| 318 | ! |
for (n in names(formatters)) {
|
| 319 | ! |
if (n %in% cn) {
|
| 320 | ! |
data[[n]] <- formatters[[n]](data[[n]]) |
| 321 |
} |
|
| 322 |
} |
|
| 323 |
} |
|
| 324 | 4x |
if (!dataset %in% vars) {
|
| 325 | 3x |
dataset <- "dataset" |
| 326 | 3x |
vars <- c(vars, dataset) |
| 327 | 3x |
data$dataset <- dataset |
| 328 |
} |
|
| 329 | 4x |
if (!time %in% vars) {
|
| 330 | ! |
time <- "time" |
| 331 | ! |
vars <- c(vars, time) |
| 332 | ! |
data$time <- 1 |
| 333 |
} |
|
| 334 | 4x |
if (!any(value_name %in% vars)) {
|
| 335 | ! |
vars <- c(vars, value_name) |
| 336 | ! |
data[[value_name]] <- sub("\\.[^.]+$", "", basename(data$file))
|
| 337 |
} |
|
| 338 | 4x |
data[[dataset]] <- gsub("\\s+", "_", data[[dataset]])
|
| 339 | 4x |
datasets <- sort(unique(data[[dataset]])) |
| 340 | 4x |
present_vars <- unique(data[[value_name]]) |
| 341 | 4x |
if (check_variables) {
|
| 342 | 3x |
present_vars <- variables[variables %in% present_vars] |
| 343 | 3x |
if (verbose) {
|
| 344 | ! |
absent_variables <- variables[!variables %in% present_vars] |
| 345 | ! |
if (length(absent_variables)) {
|
| 346 | ! |
cli_warn( |
| 347 | ! |
"requested variable{?s} not found in datasets: {.val {absent_variables}}"
|
| 348 |
) |
|
| 349 |
} |
|
| 350 |
} |
|
| 351 |
} |
|
| 352 | 4x |
times <- sort(unique(data[[time]])) |
| 353 | 4x |
if (all(nchar(times) == 4)) {
|
| 354 | 3x |
times <- seq(min(times), max(times)) |
| 355 |
} |
|
| 356 | 4x |
n <- length(times) |
| 357 | 4x |
files <- paste0(out, "/", gsub("\\s+", "_", tolower(datasets)), ".csv")
|
| 358 | 4x |
if (is.character(compression) && grepl("^[gbx]", compression, FALSE)) {
|
| 359 | 4x |
compression <- tolower(substr(compression, 1, 1)) |
| 360 | 4x |
files <- paste0(files, ".", c(g = "gz", b = "bz2", x = "xz")[[compression]]) |
| 361 |
} else {
|
|
| 362 | ! |
compression <- FALSE |
| 363 |
} |
|
| 364 | 4x |
names(files) <- datasets |
| 365 | 4x |
write <- vapply( |
| 366 | 4x |
files, |
| 367 | 4x |
function(f) {
|
| 368 | 5x |
is.null(out) || overwrite || !file.exists(f) || max_age > file.mtime(f) |
| 369 |
}, |
|
| 370 | 4x |
TRUE |
| 371 |
) |
|
| 372 | 4x |
if (!is.null(out) && (is.list(entity_info) || is.character(entity_info))) {
|
| 373 | 4x |
entity_info_file <- paste0(out, "/entity_info.json") |
| 374 | 4x |
if (overwrite || !file.exists(entity_info_file) || any(write)) {
|
| 375 | 4x |
entity_info <- as.list(entity_info) |
| 376 | 4x |
entity_info <- entity_info[unlist(entity_info) %in% colnames(data)] |
| 377 | 4x |
if (length(entity_info)) {
|
| 378 | 1x |
if (verbose) {
|
| 379 | 1x |
cli_progress_step( |
| 380 | 1x |
"writing entity file", |
| 381 | 1x |
msg_done = paste0( |
| 382 | 1x |
"wrote entity metadata file: {.file ",
|
| 383 | 1x |
entity_info_file, |
| 384 |
"}" |
|
| 385 |
) |
|
| 386 |
) |
|
| 387 |
} |
|
| 388 | 1x |
e <- data[, unique(c(id, dataset, unlist(entity_info))), drop = FALSE] |
| 389 | 1x |
if (!is.null(names(entity_info))) {
|
| 390 | 1x |
for (en in names(entity_info)) {
|
| 391 | 1x |
if (en != "" && entity_info[[en]] %in% colnames(e)) {
|
| 392 | 1x |
colnames(e)[colnames(e) == entity_info[[en]]] <- en |
| 393 |
} |
|
| 394 |
} |
|
| 395 |
} |
|
| 396 | 1x |
jsonlite::write_json( |
| 397 | 1x |
lapply(split(e, e[, 2]), function(g) {
|
| 398 | 2x |
lapply( |
| 399 | 2x |
split(g[, -(1:2), drop = FALSE], g[, 1]), |
| 400 | 2x |
function(l) lapply(l, function(r) r[which(r != "")[1]]) |
| 401 |
) |
|
| 402 |
}), |
|
| 403 | 1x |
entity_info_file, |
| 404 | 1x |
auto_unbox = TRUE, |
| 405 | 1x |
digits = 6 |
| 406 |
) |
|
| 407 | 1x |
if (verbose) cli_progress_done() |
| 408 |
} |
|
| 409 |
} |
|
| 410 |
} |
|
| 411 | 4x |
svars <- c(id, value, value_name, time, "file", dataset) |
| 412 | 4x |
data <- unique(data[, svars[svars %in% vars]]) |
| 413 | 4x |
if (length(measure_info)) {
|
| 414 | 3x |
dynamic_names <- render_info_names(measure_info) |
| 415 |
} |
|
| 416 | 4x |
sets <- lapply(datasets, function(dn) {
|
| 417 | 4x |
if ( |
| 418 | 5x |
read_existing && !is.null(out) && file.exists(files[[dn]]) && !write[[dn]] |
| 419 |
) {
|
|
| 420 | 2x |
if (verbose) {
|
| 421 | ! |
cli_progress_step( |
| 422 | ! |
"reading in existing {dn} dataset",
|
| 423 | ! |
msg_done = "read existing {dn} dataset"
|
| 424 |
) |
|
| 425 |
} |
|
| 426 | 2x |
read.csv(gzfile(files[[dn]]), check.names = FALSE) |
| 427 |
} else {
|
|
| 428 | 3x |
d <- if (dataset %in% vars) data[data[[dataset]] == dn, ] else data |
| 429 | 3x |
dc <- list() |
| 430 | 3x |
ids <- unique(d[[id]]) |
| 431 | 3x |
i <- 0 |
| 432 | 3x |
if (verbose) {
|
| 433 | 2x |
cli_progress_step( |
| 434 | 2x |
"creating {dn} dataset (ID {i}/{length(ids)})",
|
| 435 | 2x |
msg_done = "created {dn} dataset ({length(ids)} IDs)",
|
| 436 | 2x |
spinner = TRUE |
| 437 |
) |
|
| 438 |
} |
|
| 439 | 3x |
d <- d[!duplicated(paste(d[[id]], d[[value_name]], d[[time]])), ] |
| 440 | 3x |
if (length(measure_info)) {
|
| 441 | 1x |
source <- unique(d[, c(value_name, "file")]) |
| 442 | 1x |
source <- structure(source[[2]], names = source[[1]]) |
| 443 | 1x |
for (measure in names(source)) {
|
| 444 | 18x |
iname <- if (length(measure_info[[dynamic_names[measure]]])) {
|
| 445 | 4x |
dynamic_names[measure] |
| 446 |
} else {
|
|
| 447 | 14x |
measure |
| 448 |
} |
|
| 449 | 18x |
if (length(measure_info[[iname]])) {
|
| 450 | 4x |
measure_info[[iname]]$origin <<- unique(c( |
| 451 | 4x |
measure_info[[iname]]$origin, |
| 452 | 4x |
source[[measure]] |
| 453 |
)) |
|
| 454 |
} |
|
| 455 |
} |
|
| 456 |
} |
|
| 457 | 3x |
sd <- split(d, d[[id]]) |
| 458 | 3x |
ssel <- c(time, value) |
| 459 | 3x |
for (i in seq_along(ids)) {
|
| 460 | 9x |
if (verbose) {
|
| 461 | 5x |
cli_progress_update() |
| 462 |
} |
|
| 463 | 9x |
e <- ids[[i]] |
| 464 | 9x |
ed <- sd[[e]] |
| 465 | 9x |
r <- data.frame( |
| 466 | 9x |
ID = rep(as.character(e), n), |
| 467 | 9x |
time = times, |
| 468 | 9x |
check.names = FALSE, |
| 469 | 9x |
matrix( |
| 470 | 9x |
NA, |
| 471 | 9x |
n, |
| 472 | 9x |
length(present_vars), |
| 473 | 9x |
dimnames = list(times, present_vars) |
| 474 |
) |
|
| 475 |
) |
|
| 476 | 9x |
if (all(c(value_name, value) %in% names(ed))) {
|
| 477 | 9x |
ed <- ed[!is.na(ed[[value]]), ] |
| 478 | 9x |
ed <- split(ed[, ssel], ed[[value_name]]) |
| 479 | 9x |
for (v in names(ed)) {
|
| 480 | 79x |
vals <- ed[[v]] |
| 481 | 79x |
if (nrow(vals)) r[as.character(vals[[time]]), v] <- vals[[value]] |
| 482 |
} |
|
| 483 |
} |
|
| 484 | 9x |
rownames(r) <- NULL |
| 485 | 9x |
dc[[i]] <- r |
| 486 |
} |
|
| 487 | 3x |
do.call(rbind, dc) |
| 488 |
} |
|
| 489 |
}) |
|
| 490 | 4x |
names(sets) <- datasets |
| 491 | 4x |
if (length(measure_info)) {
|
| 492 | 3x |
measure_info_file <- paste0(out, "/measure_info.json") |
| 493 | 3x |
if (verbose) {
|
| 494 | ! |
cli_alert_info("updating measure info: {.file {measure_info_file}}")
|
| 495 |
} |
|
| 496 | 3x |
jsonlite::write_json( |
| 497 | 3x |
measure_info[sort(names(measure_info))], |
| 498 | 3x |
measure_info_file, |
| 499 | 3x |
auto_unbox = TRUE, |
| 500 | 3x |
pretty = TRUE |
| 501 |
) |
|
| 502 |
} |
|
| 503 | 4x |
if (!is.null(out)) {
|
| 504 | 4x |
if (get_coverage && read_existing) {
|
| 505 | 4x |
if (verbose) {
|
| 506 | 1x |
cli_progress_step( |
| 507 | 1x |
"updating coverage report", |
| 508 | 1x |
msg_done = "updated coverage report" |
| 509 |
) |
|
| 510 |
} |
|
| 511 | 4x |
variables <- sort( |
| 512 | 4x |
if (length(variables)) {
|
| 513 | 3x |
variables |
| 514 |
} else {
|
|
| 515 | 1x |
unique(unlist(lapply(sets, colnames), use.names = FALSE)) |
| 516 |
} |
|
| 517 |
) |
|
| 518 | 4x |
allcounts <- structure(numeric(length(variables)), names = variables) |
| 519 | 4x |
write.csv( |
| 520 | 4x |
vapply( |
| 521 | 4x |
sets, |
| 522 | 4x |
function(d) {
|
| 523 | 5x |
counts <- colSums(!is.na(d)) |
| 524 | 5x |
counts <- counts[names(counts) %in% variables] |
| 525 | 5x |
allcounts[names(counts)] <- counts |
| 526 | 5x |
allcounts |
| 527 |
}, |
|
| 528 | 4x |
numeric(length(variables)) |
| 529 |
), |
|
| 530 | 4x |
paste0(out, "/coverage.csv") |
| 531 |
) |
|
| 532 | 1x |
if (verbose) cli_progress_done() |
| 533 |
} |
|
| 534 | 4x |
if (any(write)) {
|
| 535 | 2x |
if (verbose) {
|
| 536 | 1x |
cli_progress_step( |
| 537 | 1x |
"writing data files", |
| 538 | 1x |
msg_done = "wrote reformatted datasets:" |
| 539 |
) |
|
| 540 |
} |
|
| 541 | 2x |
for (i in seq_along(sets)) {
|
| 542 | 3x |
if (write[[i]]) {
|
| 543 | 3x |
if (is.character(compression)) {
|
| 544 | 3x |
o <- do.call(paste0(compression, "zfile"), list(files[[i]])) |
| 545 |
} |
|
| 546 | 3x |
write_csv_arrow(sets[[i]], o) |
| 547 |
} |
|
| 548 |
} |
|
| 549 | 2x |
if (verbose) {
|
| 550 | 1x |
cli_progress_done() |
| 551 | 1x |
cli_bullets(structure( |
| 552 | 1x |
paste0("{.file ", files[write], "}"),
|
| 553 | 1x |
names = rep("*", sum(write))
|
| 554 |
)) |
|
| 555 |
} |
|
| 556 | 2x |
} else if (verbose) {
|
| 557 | ! |
cli_bullets(c( |
| 558 | ! |
v = "all files are already up to date:", |
| 559 | ! |
structure( |
| 560 | ! |
paste0("{.file ", files, "}"),
|
| 561 | ! |
names = rep("*", length(files))
|
| 562 |
) |
|
| 563 |
)) |
|
| 564 |
} |
|
| 565 |
} |
|
| 566 | 4x |
invisible(sets) |
| 567 |
} |
| 1 |
#' @rdname download_dataverse_data |
|
| 2 |
#' @return \code{download_dataverse_info}: A list with the dataset's metadata.
|
|
| 3 |
#' @export |
|
| 4 | ||
| 5 |
download_dataverse_info <- function( |
|
| 6 |
id, |
|
| 7 |
server = NULL, |
|
| 8 |
key = NULL, |
|
| 9 |
refresh = FALSE, |
|
| 10 |
branch = NULL, |
|
| 11 |
version = ":latest", |
|
| 12 |
verbose = FALSE |
|
| 13 |
) {
|
|
| 14 | 3x |
if (missing(id)) {
|
| 15 | ! |
cli_abort("an id must be specified")
|
| 16 |
} |
|
| 17 |
if ( |
|
| 18 | 3x |
!grepl("doi", tolower(id), fixed = TRUE) &&
|
| 19 | 3x |
(grepl("github", id, fixed = TRUE) || grepl("^[^/]+/[^/]+$", id))
|
| 20 |
) {
|
|
| 21 | ! |
if (is.null(branch) && grepl("@|/tree/", id)) {
|
| 22 | ! |
branch <- regmatches(id, regexec("(?:@|tree/)([^/]+)", id))[[1]][2]
|
| 23 | ! |
if (is.na(branch)) branch <- NULL |
| 24 |
} |
|
| 25 | ! |
id <- regmatches(id, regexec("^(?:.*github\\.com/)?([^/]+/[^/@]+)", id))[[
|
| 26 | ! |
1 |
| 27 | ! |
]][2] |
| 28 | ! |
repo <- tryCatch( |
| 29 | ! |
jsonlite::read_json( |
| 30 | ! |
paste0("https://api.github.com/repos/", id)
|
| 31 |
), |
|
| 32 | ! |
error = function(e) NULL |
| 33 |
) |
|
| 34 | ! |
if (!is.null(repo$default_branch)) {
|
| 35 | ! |
if (verbose) {
|
| 36 | ! |
cli_alert_info("getting ID from Github repository {id}")
|
| 37 |
} |
|
| 38 | ! |
dataset_doi <- NULL |
| 39 | ! |
tryCatch( |
| 40 | ! |
load(file(paste0( |
| 41 | ! |
"https://raw.githubusercontent.com/", |
| 42 | ! |
id, |
| 43 |
"/", |
|
| 44 | ! |
if (is.null(branch)) repo$default_branch else branch, |
| 45 | ! |
"/R/sysdata.rda" |
| 46 |
))), |
|
| 47 | ! |
error = function(e) NULL |
| 48 |
) |
|
| 49 | ! |
if (!is.null(dataset_doi)) {
|
| 50 | ! |
id <- dataset_doi[[1]] |
| 51 |
} else {
|
|
| 52 | ! |
cli_abort(paste0( |
| 53 | ! |
"{.arg id} points to a Github repository that does not have an appropriate",
|
| 54 | ! |
"{.file /R/sysdata.rda} file"
|
| 55 |
)) |
|
| 56 |
} |
|
| 57 |
} |
|
| 58 |
} |
|
| 59 | 3x |
id <- sub("^(http|doi)[^\\d]*", "", id, perl = TRUE)
|
| 60 | 3x |
temp <- paste0(tempdir(), "/", gsub("\\W", "", id), ".json")
|
| 61 | 3x |
if (refresh) {
|
| 62 | ! |
unlink(temp) |
| 63 |
} |
|
| 64 | 3x |
if (!file.exists(temp)) {
|
| 65 | 2x |
if (is.null(server)) {
|
| 66 | 1x |
server <- if (Sys.which("curl") != "") {
|
| 67 | 1x |
if (verbose) {
|
| 68 | ! |
cli_alert_info("getting server from DOI ({id}) redirect")
|
| 69 |
} |
|
| 70 | 1x |
tryCatch( |
| 71 |
{
|
|
| 72 | 1x |
url <- gsub( |
| 73 |
"<[^>]*>", |
|
| 74 |
"", |
|
| 75 | 1x |
system2("curl", paste0("https://doi.org/", id), stdout = TRUE)[5]
|
| 76 |
) |
|
| 77 | 1x |
if (grepl("^http", url)) {
|
| 78 | ! |
gsub("^https?://|/citation.*$", "", url)
|
| 79 |
} else {
|
|
| 80 | 1x |
NA |
| 81 |
} |
|
| 82 |
}, |
|
| 83 | 1x |
error = function(e) {
|
| 84 | ! |
if (verbose) {
|
| 85 | ! |
cli_alert_info("failed to get server from DOI ({id}) redirect")
|
| 86 |
} |
|
| 87 | 1x |
NA |
| 88 |
} |
|
| 89 |
) |
|
| 90 |
} else {
|
|
| 91 | 1x |
NA |
| 92 |
} |
|
| 93 | 1x |
if (is.na(server)) {
|
| 94 | 1x |
if (verbose) {
|
| 95 | ! |
cli_alert_info("looking for server in fall-backs")
|
| 96 |
} |
|
| 97 | 1x |
server <- Sys.getenv("DATAVERSE_SERVER")
|
| 98 | 1x |
if (server == "") {
|
| 99 | 1x |
server <- getOption("dataverse.server")
|
| 100 | 1x |
if (is.null(server)) server <- "dataverse.lib.virginia.edu" |
| 101 |
} |
|
| 102 |
} |
|
| 103 |
} |
|
| 104 | 2x |
if (is.null(key)) {
|
| 105 | 2x |
if (verbose) {
|
| 106 | ! |
cli_alert_info("looking for API key in fall-backs")
|
| 107 |
} |
|
| 108 | 2x |
key <- Sys.getenv("DATAVERSE_KEY", getOption("dataverse.key", ""))
|
| 109 |
} |
|
| 110 | 2x |
if (!grepl("://", server, fixed = TRUE)) {
|
| 111 | 2x |
server <- paste0("https://", server)
|
| 112 |
} |
|
| 113 | 2x |
server <- sub("/api/.*$", "/", gsub("//+$", "/", paste0(server, "/")))
|
| 114 |
} |
|
| 115 | 3x |
res <- tryCatch( |
| 116 |
{
|
|
| 117 | 3x |
if (!file.exists(temp)) {
|
| 118 | 2x |
if (verbose) {
|
| 119 | ! |
cli_alert_info("downloading dataset metadata for {id} from {server}")
|
| 120 |
} |
|
| 121 | 2x |
if (is.character(key) && key != "") {
|
| 122 | ! |
if (verbose) {
|
| 123 | ! |
cli_alert_info("trying with key")
|
| 124 |
} |
|
| 125 | ! |
download.file( |
| 126 | ! |
paste0( |
| 127 | ! |
server, |
| 128 | ! |
"api/datasets/:persistentId/versions/", |
| 129 | ! |
version, |
| 130 | ! |
"?persistentId=doi:", |
| 131 | ! |
id |
| 132 |
), |
|
| 133 | ! |
temp, |
| 134 | ! |
quiet = TRUE, |
| 135 | ! |
headers = c("X-Dataverse-key" = key)
|
| 136 |
) |
|
| 137 | ! |
if (file.exists(temp)) {
|
| 138 | ! |
res <- jsonlite::read_json(temp) |
| 139 | ! |
if (is.null(res$data)) {
|
| 140 | ! |
unlink(temp) |
| 141 | ! |
stop(res$message) |
| 142 |
} |
|
| 143 | ! |
res <- res$data |
| 144 |
} else {
|
|
| 145 | ! |
stop("download failed")
|
| 146 |
} |
|
| 147 |
} else {
|
|
| 148 | 2x |
if (verbose) {
|
| 149 | ! |
cli_alert_info("trying without key")
|
| 150 |
} |
|
| 151 | 2x |
res <- jsonlite::read_json( |
| 152 | 2x |
paste0( |
| 153 | 2x |
server, |
| 154 | 2x |
"api/datasets/:persistentId/versions/", |
| 155 | 2x |
version, |
| 156 | 2x |
"?persistentId=doi:", |
| 157 | 2x |
id |
| 158 |
) |
|
| 159 | 2x |
)$data |
| 160 |
} |
|
| 161 | 2x |
res$server <- server |
| 162 | 2x |
jsonlite::write_json(res, temp, auto_unbox = TRUE) |
| 163 | 2x |
res |
| 164 |
} else {
|
|
| 165 | 1x |
if (verbose) {
|
| 166 | ! |
cli_alert_info("reading in existing metadata for {id}")
|
| 167 |
} |
|
| 168 | 1x |
jsonlite::read_json(temp) |
| 169 |
} |
|
| 170 |
}, |
|
| 171 | 3x |
error = function(e) e$message |
| 172 |
) |
|
| 173 | 3x |
if (is.character(res)) {
|
| 174 | ! |
if (file.exists(temp)) {
|
| 175 | ! |
cli_abort(cli_bullets(c( |
| 176 | ! |
x = "downloaded the metadata, but failed to read it in: {res}",
|
| 177 | ! |
i = paste0("check {.file ", temp, "}")
|
| 178 |
))) |
|
| 179 |
} else {
|
|
| 180 | ! |
cli_abort(cli_bullets(c( |
| 181 | ! |
x = "failed to retrive info", |
| 182 | ! |
i = paste0( |
| 183 | ! |
"tried for this dataset: {.url ",
|
| 184 | ! |
server, |
| 185 | ! |
"dataset.xhtml?persistentId=doi:", |
| 186 | ! |
id, |
| 187 |
"}" |
|
| 188 |
), |
|
| 189 | ! |
if (length(res)) c("!" = paste("got this error:", res))
|
| 190 |
))) |
|
| 191 |
} |
|
| 192 |
} |
|
| 193 | 3x |
res |
| 194 |
} |
| 1 |
#' Make a Copy of a Data Site |
|
| 2 |
#' |
|
| 3 |
#' Copies baseline files from an existing data site. Useful for making different sites |
|
| 4 |
#' based on the same data. |
|
| 5 |
#' |
|
| 6 |
#' @param parent Directory or GitHub repository name of the existing site to be copied. |
|
| 7 |
#' @param dir Directory of the child site to put copies in. |
|
| 8 |
#' @param update Logical; if \code{TRUE}, replaces existing site files if they are older than existing
|
|
| 9 |
#' files (from a local directory). Same as \code{overwrite} for remote sites. By default, only the
|
|
| 10 |
#' \code{datapackage.json} file is updated.
|
|
| 11 |
#' @param overwrite Logical; if \code{TRUE}, overwrites any existing site files. \code{datapackage.json}
|
|
| 12 |
#' is always overwritten. |
|
| 13 |
#' @param protect A vector of file paths to prevent from being overwritten, relative to the site directory. |
|
| 14 |
#' @param include A vector of paths to additional files to update from the parent site, relative to the |
|
| 15 |
#' site's base directory. |
|
| 16 |
#' @param quiet Logical; if \code{TRUE}, does not send messages.
|
|
| 17 |
#' @examples |
|
| 18 |
#' \dontrun{
|
|
| 19 |
#' site_make_child("miserman/community_example", "../community_example")
|
|
| 20 |
#' } |
|
| 21 |
#' @return Invisible path to the child directory. |
|
| 22 |
#' @export |
|
| 23 | ||
| 24 |
site_make_child <- function( |
|
| 25 |
parent, |
|
| 26 |
dir, |
|
| 27 |
update = FALSE, |
|
| 28 |
overwrite = FALSE, |
|
| 29 |
protect = "site.R", |
|
| 30 |
include = NULL, |
|
| 31 |
quiet = !interactive() |
|
| 32 |
) {
|
|
| 33 | 1x |
if (missing(dir)) {
|
| 34 | ! |
cli_abort('{.arg dir} must be speficied (e.g., dir = "child_site")')
|
| 35 |
} |
|
| 36 | 1x |
check <- check_template("site", dir = dir)
|
| 37 | 1x |
if (!quiet && any(file.exists(check$files)) && !overwrite) {
|
| 38 | ! |
cli_bullets(c( |
| 39 | ! |
`!` = "site files already exist", |
| 40 | ! |
i = "add {.code overwrite = TRUE} to overwrite them"
|
| 41 |
)) |
|
| 42 |
} |
|
| 43 | 1x |
dir <- normalizePath(paste0(dir, "/", check$spec$dir), "/", FALSE) |
| 44 | 1x |
dir.create(dir, FALSE, TRUE) |
| 45 | 1x |
dir.create(paste0(dir, "/docs/data"), FALSE, TRUE) |
| 46 | 1x |
files <- unique(c( |
| 47 | 1x |
unlist(check$spec$files, use.names = FALSE), |
| 48 | 1x |
"docs/data/datapackage.json", |
| 49 | 1x |
include |
| 50 |
)) |
|
| 51 | 1x |
filled <- copied <- structure( |
| 52 | 1x |
!file.exists(paste0(dir, "/", files)), |
| 53 | 1x |
names = files |
| 54 |
) |
|
| 55 | 1x |
copied[] <- FALSE |
| 56 | 1x |
if (!file.exists(paste0(dir, "/build.R"))) {
|
| 57 | 1x |
copied["build.R"] <- TRUE |
| 58 | 1x |
args <- lapply(match.call()[-1], eval, parent.frame()) |
| 59 | 1x |
writeLines( |
| 60 | 1x |
paste( |
| 61 | 1x |
c( |
| 62 | 1x |
paste0("# this is a child site spawned from ", parent, ":"),
|
| 63 | 1x |
paste0( |
| 64 | 1x |
"site_make_child(\n ", |
| 65 | 1x |
paste( |
| 66 | 1x |
vapply( |
| 67 | 1x |
names(args), |
| 68 | 1x |
function(a) {
|
| 69 | 2x |
if (a %in% c("parent", "dir")) {
|
| 70 | 2x |
paste0(a, ' = "', normalizePath(args[[a]], "/", FALSE), '"') |
| 71 |
} else {
|
|
| 72 | ! |
paste(a, "=", args[[a]]) |
| 73 |
} |
|
| 74 |
}, |
|
| 75 |
"" |
|
| 76 |
), |
|
| 77 | 1x |
collapse = ",\n " |
| 78 |
), |
|
| 79 | 1x |
"\n)" |
| 80 |
), |
|
| 81 |
"" |
|
| 82 |
), |
|
| 83 | 1x |
collapse = "\n" |
| 84 |
), |
|
| 85 | 1x |
paste0(dir, "/build.R") |
| 86 |
) |
|
| 87 |
} |
|
| 88 | 1x |
init_site(dir, with_data = FALSE, quiet = TRUE) |
| 89 | 1x |
never_update <- c("build.R", "README.rm", protect)
|
| 90 | 1x |
always_update <- c("docs/data/datapackage.json", include)
|
| 91 | 1x |
if (!dir.exists(parent)) {
|
| 92 | 1x |
parent <- regmatches( |
| 93 | 1x |
parent, |
| 94 | 1x |
regexec("^(?:.*github\\.com/)?([^/]+/[^/@]+)", parent)
|
| 95 | 1x |
)[[1]][2] |
| 96 | 1x |
repo <- tryCatch( |
| 97 | 1x |
jsonlite::read_json( |
| 98 | 1x |
paste0("https://api.github.com/repos/", parent, "/contents")
|
| 99 |
), |
|
| 100 | 1x |
error = function(e) e$message |
| 101 |
) |
|
| 102 | 1x |
if (is.character(repo)) {
|
| 103 | ! |
cli_abort( |
| 104 | ! |
"treated {.arg parent} as a GitHub repository, but failed to retrieve it: {repo}"
|
| 105 |
) |
|
| 106 |
} |
|
| 107 | 1x |
if (missing(update)) {
|
| 108 | 1x |
update <- FALSE |
| 109 |
} |
|
| 110 | 1x |
repo <- c( |
| 111 | 1x |
repo, |
| 112 | 1x |
tryCatch( |
| 113 | 1x |
jsonlite::read_json( |
| 114 | 1x |
paste0("https://api.github.com/repos/", parent, "/contents/docs")
|
| 115 |
), |
|
| 116 | 1x |
error = function(e) NULL |
| 117 |
), |
|
| 118 | 1x |
tryCatch( |
| 119 | 1x |
jsonlite::read_json( |
| 120 | 1x |
paste0("https://api.github.com/repos/", parent, "/contents/docs/data")
|
| 121 |
), |
|
| 122 | 1x |
error = function(e) NULL |
| 123 |
) |
|
| 124 |
) |
|
| 125 | 1x |
for (f in repo) {
|
| 126 | 32x |
if (f$path %in% files[!files %in% never_update]) {
|
| 127 | 7x |
dest <- paste0(dir, "/", f$path) |
| 128 |
if ( |
|
| 129 | 7x |
f$path %in% always_update || overwrite || update || filled[[f$path]] |
| 130 |
) {
|
|
| 131 | 7x |
unlink(dest) |
| 132 | 7x |
tryCatch( |
| 133 | 7x |
download.file(f$download_url, dest, quiet = TRUE), |
| 134 | 7x |
error = function(e) NULL |
| 135 |
) |
|
| 136 | 7x |
copied[[f$path]] <- file.exists(dest) |
| 137 |
} |
|
| 138 |
} |
|
| 139 |
} |
|
| 140 |
} else {
|
|
| 141 | ! |
for (f in files[!files %in% never_update]) {
|
| 142 | ! |
pf <- paste0(parent, "/", f) |
| 143 | ! |
dest <- paste0(dir, "/", f) |
| 144 |
if ( |
|
| 145 | ! |
file.exists(pf) && |
| 146 | ! |
(f %in% |
| 147 | ! |
always_update || |
| 148 | ! |
overwrite || |
| 149 | ! |
filled[[f]] || |
| 150 | ! |
(update && file.mtime(pf) > file.mtime(dest))) |
| 151 |
) {
|
|
| 152 | ! |
unlink(dest) |
| 153 | ! |
file.copy(pf, dest) |
| 154 | ! |
copied[[f]] <- file.exists(dest) |
| 155 |
} |
|
| 156 |
} |
|
| 157 |
} |
|
| 158 | 1x |
if (!quiet) {
|
| 159 | ! |
if (any(copied)) {
|
| 160 | ! |
cli_bullets(c( |
| 161 | ! |
v = "copied from {.path {parent}}:",
|
| 162 | ! |
"*" = paste0("{.path ", names(which(copied)), "}")
|
| 163 |
)) |
|
| 164 |
} |
|
| 165 | ! |
if (any(filled & !copied)) {
|
| 166 | ! |
cli_bullets(c( |
| 167 | ! |
v = "created from template:", |
| 168 | ! |
"*" = paste0("{.path ", names(which(filled & !copied)), "}")
|
| 169 |
)) |
|
| 170 |
} |
|
| 171 | ! |
if (!any(filled | copied)) {
|
| 172 | ! |
cli_alert_success("no site files were replaced")
|
| 173 |
} |
|
| 174 |
} |
|
| 175 | 1x |
invisible(dir) |
| 176 |
} |
| 1 |
#' Add a select input to a website |
|
| 2 |
#' |
|
| 3 |
#' Adds an input to select from the entered options. |
|
| 4 |
#' |
|
| 5 |
#' @param label Label of the input for the user. |
|
| 6 |
#' @param options A vector of options, the name of a variable from which to pull levels, or \code{"datasets"},
|
|
| 7 |
#' \code{"variables"}, \code{"ids"}, or \code{"palettes"} to select names of datasets, variables, entity ids, or
|
|
| 8 |
#' color palettes. If there is a map with overlay layers with properties, can also be \code{"overlay_properties"},
|
|
| 9 |
#' to select between properties. |
|
| 10 |
#' @param default Which of the options to default to; either its index or value. |
|
| 11 |
#' @param display A display version of the options. |
|
| 12 |
#' @param id Unique ID of the element to be created. |
|
| 13 |
#' @param ... Additional attributes to set on the select element. |
|
| 14 |
#' @param note Text to display as a tooltip for the input. |
|
| 15 |
#' @param group_feature Name of a measure or entity feature to use as a source of option grouping, |
|
| 16 |
#' if \code{options} is \code{"variables"} or \code{"ids"}.
|
|
| 17 |
#' @param variable The name of a variable from which to get levels (overwritten by \code{depends}).
|
|
| 18 |
#' @param dataset The name of an included dataset, where \code{variable} should be looked for; only applies when
|
|
| 19 |
#' there are multiple datasets with the same variable name. |
|
| 20 |
#' @param depends The ID of another input on which the options depend; this will take president over \code{dataset}
|
|
| 21 |
#' and \code{variable}, depending on this type of input \code{depends} points to.
|
|
| 22 |
#' @param dataview The ID of an \code{\link{input_dataview}}, used to filter the set of options, and potentially
|
|
| 23 |
#' specify dataset if none is specified here. |
|
| 24 |
#' @param subset Determines the subset of options shown if \code{options} is \code{"ids"}; mainly \code{"filtered"}
|
|
| 25 |
#' (default) to apply all filters, including the current selection, or \code{"full_filter"} to apply all
|
|
| 26 |
#' feature and variable filters, but not the current selection. \code{"siblings"} is a special case given a selection,
|
|
| 27 |
#' which will show other IDs with the same parent. |
|
| 28 |
#' @param selection_subset Subset to use when a selection is made; defaults to \code{"full_filter"}.
|
|
| 29 |
#' @param filters A list with names of \code{meta} entries (from \code{variable} entry in \code{\link{data_add}}'s
|
|
| 30 |
#' \code{meta} list), and values of target values for those entries, or the IDs of value selectors.
|
|
| 31 |
#' @param reset_button If specified, adds a button after the select element that will revert the selection |
|
| 32 |
#' to its default; either \code{TRUE}, or text for the reset button's label.
|
|
| 33 |
#' @param button_class Class name to add to the reset button. |
|
| 34 |
#' @param as.row Logical; if \code{TRUE}, the label and input are in separate columns within a row.
|
|
| 35 |
#' @param floating_label Logical; if \code{FALSE} or \code{as.row} is \code{TRUE}, labels are separate from
|
|
| 36 |
#' their inputs. |
|
| 37 |
#' @examples |
|
| 38 |
#' \dontrun{
|
|
| 39 |
#' input_select() |
|
| 40 |
#' } |
|
| 41 |
#' @return A character vector of the contents to be added. |
|
| 42 |
#' @export |
|
| 43 | ||
| 44 |
input_select <- function( |
|
| 45 |
label, |
|
| 46 |
options, |
|
| 47 |
default = -1, |
|
| 48 |
display = options, |
|
| 49 |
id = label, |
|
| 50 |
..., |
|
| 51 |
note = NULL, |
|
| 52 |
group_feature = NULL, |
|
| 53 |
variable = NULL, |
|
| 54 |
dataset = NULL, |
|
| 55 |
depends = NULL, |
|
| 56 |
dataview = NULL, |
|
| 57 |
subset = "filtered", |
|
| 58 |
selection_subset = "full_filter", |
|
| 59 |
filters = NULL, |
|
| 60 |
reset_button = FALSE, |
|
| 61 |
button_class = NULL, |
|
| 62 |
as.row = FALSE, |
|
| 63 |
floating_label = TRUE |
|
| 64 |
) {
|
|
| 65 | 21x |
id <- gsub("\\s", "", id)
|
| 66 | 21x |
a <- list(...) |
| 67 | 21x |
if (as.row) {
|
| 68 | ! |
floating_label <- FALSE |
| 69 |
} |
|
| 70 | 21x |
r <- c( |
| 71 | 21x |
'<div class="wrapper select-wrapper">', |
| 72 | 21x |
if (!floating_label) paste0('<label for="', id, '">', label, "</label>"),
|
| 73 | 21x |
paste0( |
| 74 | 21x |
'<div class="', |
| 75 | 21x |
paste( |
| 76 | 21x |
c( |
| 77 | 21x |
if (reset_button) "input-group", |
| 78 | 21x |
if (floating_label) "form-floating" |
| 79 |
), |
|
| 80 | 21x |
collapse = " " |
| 81 |
), |
|
| 82 |
'">' |
|
| 83 |
), |
|
| 84 | 21x |
paste0( |
| 85 | 21x |
'<select class="auto-input form-select" data-autoType="select" id="', |
| 86 | 21x |
id, |
| 87 |
'" ', |
|
| 88 | 21x |
if (is.character(options) && length(options) == 1) {
|
| 89 | 1x |
paste0('data-optionSource="', options, '"')
|
| 90 |
}, |
|
| 91 | 21x |
if (!is.null(default)) paste0(' data-default="', default, '"'),
|
| 92 | 21x |
if (!is.null(note)) paste0(' aria-description="', note, '"'),
|
| 93 | 21x |
if (!is.null(dataview)) paste0(' data-view="', dataview, '"'),
|
| 94 | 21x |
if (!is.null(subset)) paste0(' data-subset="', subset, '"'),
|
| 95 | 21x |
if (!is.null(selection_subset)) {
|
| 96 | 21x |
paste0(' data-selectionSubset="', selection_subset, '"')
|
| 97 |
}, |
|
| 98 | 21x |
if (!is.null(depends)) paste0(' data-depends="', depends, '"'),
|
| 99 | 21x |
if (!is.null(dataset)) paste0(' data-dataset="', dataset, '"'),
|
| 100 | 21x |
if (!is.null(variable)) paste0(' data-variable="', variable, '"'),
|
| 101 | 21x |
if (length(a)) {
|
| 102 | ! |
unlist(lapply( |
| 103 | ! |
seq_along(a), |
| 104 | ! |
function(i) paste0(" ", names(a)[i], '="', a[[i]], '"')
|
| 105 |
)) |
|
| 106 |
}, |
|
| 107 |
">" |
|
| 108 |
), |
|
| 109 | 21x |
if (is.list(options)) {
|
| 110 | 1x |
i <- 0 |
| 111 | 1x |
if (is.null(names(options))) {
|
| 112 | ! |
names(options) <- seq_along(options) |
| 113 |
} |
|
| 114 | 1x |
unlist( |
| 115 | 1x |
lapply(names(options), function(g) {
|
| 116 | 2x |
group <- paste0('<optgroup label="', g, '">')
|
| 117 | 2x |
for (gi in seq_along(options[[g]])) {
|
| 118 | 4x |
i <<- i + 1 |
| 119 | 4x |
group <- c( |
| 120 | 4x |
group, |
| 121 | 4x |
paste0( |
| 122 | 4x |
'<option value="', |
| 123 | 4x |
options[[g]][[gi]], |
| 124 |
'"', |
|
| 125 | 4x |
if (i == default) "selected", |
| 126 |
">", |
|
| 127 | 4x |
display[[g]][[gi]], |
| 128 | 4x |
"</option>" |
| 129 |
) |
|
| 130 |
) |
|
| 131 |
} |
|
| 132 | 2x |
c(group, "</optgroup>") |
| 133 |
}), |
|
| 134 | 1x |
use.names = FALSE |
| 135 |
) |
|
| 136 | 21x |
} else if ( |
| 137 | 21x |
length(options) > 1 || |
| 138 | 21x |
!options %in% |
| 139 | 21x |
c("datasets", "variables", "ids", "palettes", "overlay_properties")
|
| 140 |
) {
|
|
| 141 | 19x |
unlist( |
| 142 | 19x |
lapply(seq_along(options), function(i) {
|
| 143 | 55x |
paste0( |
| 144 | 55x |
'<option value="', |
| 145 | 55x |
options[i], |
| 146 |
'"', |
|
| 147 | 55x |
if (i == default) "selected", |
| 148 |
">", |
|
| 149 | 55x |
display[i], |
| 150 | 55x |
"</option>" |
| 151 |
) |
|
| 152 |
}), |
|
| 153 | 19x |
use.names = FALSE |
| 154 |
) |
|
| 155 |
}, |
|
| 156 | 21x |
"</select>", |
| 157 | 21x |
if (floating_label) paste0('<label for="', id, '">', label, "</label>"),
|
| 158 | 21x |
if (!missing(reset_button)) {
|
| 159 | ! |
paste( |
| 160 | ! |
c( |
| 161 | ! |
'<button type="button" class="btn btn-link', |
| 162 | ! |
if (!is.null(button_class)) paste("", button_class),
|
| 163 | ! |
' select-reset">', |
| 164 | ! |
if (is.character(reset_button)) reset_button else "Reset", |
| 165 | ! |
"</button>" |
| 166 |
), |
|
| 167 | ! |
collapse = "" |
| 168 |
) |
|
| 169 |
}, |
|
| 170 | 21x |
"</div>", |
| 171 | 21x |
"</div>" |
| 172 |
) |
|
| 173 | 21x |
if (as.row) {
|
| 174 | ! |
r <- to_input_row(r) |
| 175 |
} |
|
| 176 | 21x |
caller <- parent.frame() |
| 177 |
if ( |
|
| 178 | 21x |
!is.null(attr(caller, "name")) && |
| 179 | 21x |
attr(caller, "name") == "community_site_parts" |
| 180 |
) {
|
|
| 181 | 17x |
if (!is.null(group_feature)) {
|
| 182 | ! |
caller$select[[id]]$group <- group_feature |
| 183 |
} |
|
| 184 | 17x |
if (!is.null(filters)) {
|
| 185 | ! |
caller$select[[id]]$filters <- as.list(filters) |
| 186 |
} |
|
| 187 | 17x |
caller$content <- c(caller$content, r) |
| 188 |
} |
|
| 189 | 21x |
r |
| 190 |
} |
| 1 |
#' Download Dataverse Dataset Files |
|
| 2 |
#' |
|
| 3 |
#' Download one or all files in a Dataverse dataset. |
|
| 4 |
#' |
|
| 5 |
#' @param id The dataset's persistent ID (e.g., \code{"doi:10.18130/V3/SWK71R"}), or a GitHub repository
|
|
| 6 |
#' (\code{"username/repo"}) with a \code{R/sysdata.rda} file containing a \code{dataset_doi}.
|
|
| 7 |
#' @param outdir Directory in which to save files; defaults to a temporary directory. |
|
| 8 |
#' @param files Names (full or partial) of files to download, or a number or vector of numbers |
|
| 9 |
#' identifying file by index as listed on Dataverse; downloads all files in a dataset if not specified. |
|
| 10 |
#' @param version Version of the dataset to download. Specifying this will download all files, |
|
| 11 |
#' even if only a selection is requested. |
|
| 12 |
#' @param server Dataverse server; tries to get this from the DOI redirect, but falls back on |
|
| 13 |
#' \code{Sys.getenv("DATAVERSE_SERVER")}, then \code{getOption("dataverse.server")}, then
|
|
| 14 |
#' \code{"dataverse.lib.virginia.edu"}.
|
|
| 15 |
#' @param key Dataverse API key; only needed if the requested dataset is not published. If not specified, |
|
| 16 |
#' looks for the key in \code{Sys.getenv("DATAVERSE_KEY")} and \code{getOption("dataverse.key")}.
|
|
| 17 |
#' @param load Logical; if \code{FALSE}, files will be downloaded but not loaded.
|
|
| 18 |
#' @param decompress Logical; if \code{TRUE}, will attempt to decompress compressed files.
|
|
| 19 |
#' @param refresh Logical; if \code{TRUE}, downloads and replaces any existing files.
|
|
| 20 |
#' @param branch Name of the repository branch, if \code{id} is the name of a repository; uses the default branch
|
|
| 21 |
#' if not specified. |
|
| 22 |
#' @param verbose Logical; if \code{TRUE}, prints status updates and warnings.
|
|
| 23 |
#' @examples |
|
| 24 |
#' \dontrun{
|
|
| 25 |
#' metadata <- download_dataverse_info("doi:10.18130/V3/SWK71R", verbose = TRUE)
|
|
| 26 |
#' data <- download_dataverse_data("doi:10.18130/V3/SWK71R", verbose = TRUE)
|
|
| 27 |
#' } |
|
| 28 |
#' @return \code{download_dataverse_data}: If \code{load} is \code{TRUE}, an invisible list with
|
|
| 29 |
#' an entry for each data file if there are multiple files, or the loaded data file if a single file |
|
| 30 |
#' is requested. Tabular data files are loaded as \code{data.table}s. If \code{load} is \code{FALSE},
|
|
| 31 |
#' a list with the dataset's metadata. |
|
| 32 |
#' @export |
|
| 33 | ||
| 34 |
download_dataverse_data <- function( |
|
| 35 |
id, |
|
| 36 |
outdir = tempdir(), |
|
| 37 |
files = NULL, |
|
| 38 |
version = ":latest", |
|
| 39 |
server = NULL, |
|
| 40 |
key = NULL, |
|
| 41 |
load = TRUE, |
|
| 42 |
decompress = FALSE, |
|
| 43 |
refresh = FALSE, |
|
| 44 |
branch = NULL, |
|
| 45 |
verbose = FALSE |
|
| 46 |
) {
|
|
| 47 | 2x |
if (missing(id)) {
|
| 48 | ! |
cli_abort("{.arg id} must be specified")
|
| 49 |
} |
|
| 50 | 2x |
if (!is.character(outdir)) {
|
| 51 | ! |
cli_abort("{.arg outdir} must be a character")
|
| 52 |
} |
|
| 53 | 2x |
meta <- download_dataverse_info( |
| 54 | 2x |
id, |
| 55 | 2x |
server = server, |
| 56 | 2x |
key = key, |
| 57 | 2x |
refresh = refresh, |
| 58 | 2x |
branch = branch |
| 59 |
) |
|
| 60 | 2x |
fs <- vapply(meta$files, function(m) m$dataFile$filename, "") |
| 61 | 2x |
which_files <- if (!is.null(files)) {
|
| 62 | 2x |
if (is.numeric(files)) {
|
| 63 | 1x |
files[files <= length(fs)] |
| 64 |
} else {
|
|
| 65 | 1x |
grep( |
| 66 | 1x |
paste0( |
| 67 |
"(?:", |
|
| 68 | 1x |
paste(gsub(".", "\\.", files, fixed = TRUE), collapse = "|"),
|
| 69 |
")" |
|
| 70 |
), |
|
| 71 | 1x |
fs, |
| 72 | 1x |
TRUE |
| 73 |
) |
|
| 74 |
} |
|
| 75 |
} else {
|
|
| 76 | ! |
seq_along(fs) |
| 77 |
} |
|
| 78 | 2x |
if (!length(which_files)) {
|
| 79 | ! |
cli_abort(cli_bullets(c( |
| 80 | ! |
x = "{.arg files} could not be matched to available files",
|
| 81 | ! |
i = paste0("check {.url ", meta$persistentUrl, "}")
|
| 82 |
))) |
|
| 83 |
} |
|
| 84 | 2x |
outdir <- paste0(normalizePath(outdir, "/", FALSE), "/") |
| 85 | 2x |
dir.create(outdir, FALSE, TRUE) |
| 86 | 2x |
data <- list() |
| 87 | 2x |
ffsx <- paste0(outdir, fs) |
| 88 | 2x |
ffs <- sub("\\.[gbx]z2?$", "", ffsx)
|
| 89 | 2x |
if (refresh) {
|
| 90 | ! |
unlink(c(ffsx, ffs)) |
| 91 |
} |
|
| 92 | 2x |
if (is.null(key)) {
|
| 93 | 2x |
if (verbose) {
|
| 94 | ! |
cli_alert_info("looking for API key in fall-backs")
|
| 95 |
} |
|
| 96 | 2x |
key <- Sys.getenv("DATAVERSE_KEY")
|
| 97 | 2x |
if (key == "") {
|
| 98 | 2x |
key <- getOption("dataverse.key")
|
| 99 |
} |
|
| 100 |
} |
|
| 101 | 2x |
if (length(which_files) == length(fs) || !missing(version)) {
|
| 102 | ! |
zf <- paste0(outdir, gsub("\\W", "", meta$datasetPersistentId), ".zip")
|
| 103 | ! |
if (verbose) {
|
| 104 | ! |
cli_alert_info("downloading dataset: {meta$datasetPersistentId}")
|
| 105 |
} |
|
| 106 | ! |
if (is.character(key)) {
|
| 107 | ! |
if (verbose) {
|
| 108 | ! |
cli_alert_info("trying with key")
|
| 109 |
} |
|
| 110 | ! |
tryCatch( |
| 111 | ! |
system2( |
| 112 | ! |
"curl", |
| 113 | ! |
c( |
| 114 | ! |
paste0("-H X-Dataverse-key:", key),
|
| 115 | ! |
"-o", |
| 116 | ! |
zf, |
| 117 | ! |
paste0( |
| 118 | ! |
meta$server, |
| 119 | ! |
"api/access/dataset/:persistentId/versions/", |
| 120 | ! |
version, |
| 121 | ! |
"?persistentId=", |
| 122 | ! |
meta$datasetPersistentId |
| 123 |
) |
|
| 124 |
), |
|
| 125 | ! |
stdout = TRUE |
| 126 |
), |
|
| 127 | ! |
error = function(e) NULL |
| 128 |
) |
|
| 129 |
} else {
|
|
| 130 | ! |
if (verbose) {
|
| 131 | ! |
cli_alert_info("trying without key")
|
| 132 |
} |
|
| 133 | ! |
tryCatch( |
| 134 | ! |
download.file( |
| 135 | ! |
paste0( |
| 136 | ! |
meta$server, |
| 137 | ! |
"api/access/dataset/:persistentId/versions/", |
| 138 | ! |
version, |
| 139 | ! |
"?persistentId=", |
| 140 | ! |
meta$datasetPersistentId |
| 141 |
), |
|
| 142 | ! |
zf, |
| 143 | ! |
quiet = TRUE, |
| 144 | ! |
mode = "wb" |
| 145 |
), |
|
| 146 | ! |
error = function(e) NULL |
| 147 |
) |
|
| 148 |
} |
|
| 149 | ! |
if (file.exists(zf)) {
|
| 150 | ! |
unzip(zf, exdir = sub("/$", "", outdir))
|
| 151 | ! |
unlink(zf) |
| 152 | ! |
} else if (verbose) {
|
| 153 | ! |
cli_alert_info( |
| 154 | ! |
"failed to download dataset {meta$id}; trying individual files..."
|
| 155 |
) |
|
| 156 |
} |
|
| 157 |
} |
|
| 158 | 2x |
for (i in which_files) {
|
| 159 | 2x |
m <- meta$files[[i]] |
| 160 | 2x |
meta$files[[i]]$local <- ffs[i] |
| 161 | 2x |
if (!file.exists(ffs[i]) && !file.exists(ffsx[i])) {
|
| 162 | 2x |
if (verbose) {
|
| 163 | ! |
cli_alert_info("downloading file: {.file {m$label}}")
|
| 164 |
} |
|
| 165 | 2x |
if (is.null(key)) {
|
| 166 | 2x |
if (verbose) {
|
| 167 | ! |
cli_alert_info("trying without key")
|
| 168 |
} |
|
| 169 | 2x |
tryCatch( |
| 170 | 2x |
download.file( |
| 171 | 2x |
paste0(meta$server, "api/access/datafile/", m$dataFile$id), |
| 172 | 2x |
ffsx[i], |
| 173 | 2x |
quiet = TRUE, |
| 174 | 2x |
mode = "wb" |
| 175 |
), |
|
| 176 | 2x |
error = function(e) NULL |
| 177 |
) |
|
| 178 |
} else {
|
|
| 179 | ! |
if (verbose) {
|
| 180 | ! |
cli_alert_info("trying with key")
|
| 181 |
} |
|
| 182 | ! |
tryCatch( |
| 183 | ! |
system2( |
| 184 | ! |
"curl", |
| 185 | ! |
c( |
| 186 | ! |
paste0("-H X-Dataverse-key:", key),
|
| 187 | ! |
"-o", |
| 188 | ! |
ffsx[i], |
| 189 | ! |
paste0(meta$server, "api/access/datafile/", m$dataFile$id) |
| 190 |
), |
|
| 191 | ! |
stdout = TRUE |
| 192 |
), |
|
| 193 | ! |
error = function(e) NULL |
| 194 |
) |
|
| 195 |
} |
|
| 196 | 2x |
if (verbose && !file.exists(ffsx[i])) {
|
| 197 | ! |
cli_alert_info("failed to download file: {.file {m$label}}")
|
| 198 |
} |
|
| 199 |
} |
|
| 200 | 2x |
if (file.exists(ffsx[i])) {
|
| 201 | 2x |
if (verbose && m$dataFile$md5 != md5sum(ffsx[i])) {
|
| 202 | ! |
cli_warn( |
| 203 | ! |
"file was downloaded but its checksum did not match: {.file {ffsx[i]}}"
|
| 204 |
) |
|
| 205 |
} |
|
| 206 | 2x |
if (decompress && grepl("[gbx]z2?$", ffsx[i])) {
|
| 207 | ! |
if (verbose) {
|
| 208 | ! |
cli_alert_info("decompressing file: {.file {ffsx[i]}}")
|
| 209 |
} |
|
| 210 | ! |
system2( |
| 211 | ! |
c(xz = "xz", bz = "bunzip2", gz = "gzip")[substring( |
| 212 | ! |
ffsx[i], |
| 213 | ! |
nchar(ffsx[i]) - 1 |
| 214 |
)], |
|
| 215 | ! |
c("-df", shQuote(ffsx[i]))
|
| 216 |
) |
|
| 217 |
} |
|
| 218 |
} |
|
| 219 | 2x |
if (load && file.exists(if (decompress) ffs[i] else ffsx[i])) {
|
| 220 | 2x |
if (verbose) {
|
| 221 | ! |
cli_alert_info("loading file: {.file {ffs[i]}}")
|
| 222 |
} |
|
| 223 | 2x |
fn <- sub("\\..*", "", m$label)
|
| 224 | 2x |
json <- grepl("\\.json$", ffs[i])
|
| 225 | 2x |
data[[fn]] <- tryCatch( |
| 226 | 2x |
if (json) {
|
| 227 | ! |
jsonlite::read_json(ffs[i], simplifyVector = TRUE) |
| 228 |
} else {
|
|
| 229 | 2x |
read_delim_arrow( |
| 230 | 2x |
gzfile(ffsx[i]), |
| 231 | 2x |
if (grepl("csv", format, fixed = TRUE)) "," else "\t"
|
| 232 |
) |
|
| 233 |
}, |
|
| 234 | 2x |
error = function(e) NULL |
| 235 |
) |
|
| 236 | 2x |
if (verbose && is.null(data[[fn]])) {
|
| 237 | ! |
cli_warn("file was downloaded but failed to load: {.file {ffs[i]}}")
|
| 238 |
} |
|
| 239 |
} |
|
| 240 |
} |
|
| 241 | 2x |
if (!decompress) {
|
| 242 | 2x |
ffs <- ffsx |
| 243 |
} |
|
| 244 | 2x |
ffs <- ffs[which_files] |
| 245 | 2x |
if (verbose && any(!file.exists(ffs))) {
|
| 246 | ! |
cli_warn("failed to download file{?s}: {.file {ffs[!file.exists(ffs)]}}")
|
| 247 |
} |
|
| 248 | 2x |
invisible( |
| 249 | 2x |
if (load) {
|
| 250 | 2x |
if (length(data) == 1) data[[1]] else data |
| 251 |
} else {
|
|
| 252 | ! |
meta |
| 253 |
} |
|
| 254 |
) |
|
| 255 |
} |
| 1 |
init_package <- function(name = "package", dir = ".") {
|
|
| 2 | 2x |
dir.create(paste0(dir, "/", name, "/R"), FALSE, TRUE) |
| 3 | 2x |
dir.create(paste0(dir, "/", name, "/inst/specs"), FALSE, TRUE) |
| 4 | 2x |
dir.create(paste0(dir, "/", name, "/tests/testthat"), FALSE, TRUE) |
| 5 |
} |
|
| 6 | ||
| 7 |
parse_rule <- function(condition) {
|
|
| 8 | 11x |
comb_type <- grepl("|", condition, fixed = TRUE)
|
| 9 | 11x |
conds <- strsplit( |
| 10 | 11x |
gsub("\\s*([&|><=]+|!=+)\\s*", " \\1 ", gsub("=+", "=", condition)),
|
| 11 |
" [&|]+ " |
|
| 12 | 11x |
)[[1]] |
| 13 | 11x |
lapply(conds, function(co) {
|
| 14 | 17x |
co <- strsplit(co, "\\s")[[1]] |
| 15 | 17x |
if (length(co) == 1) {
|
| 16 | 7x |
co <- c(sub("^!+", "", co), if (grepl("^!\\w", co)) "!" else "", "")
|
| 17 |
} |
|
| 18 | 17x |
Filter( |
| 19 | 17x |
function(e) {
|
| 20 | 68x |
length(e) != 1 || if (is.logical(e)) e else TRUE |
| 21 |
}, |
|
| 22 | 17x |
if (tolower(co[2]) %in% c("true", "false")) {
|
| 23 | ! |
list( |
| 24 | ! |
id = co[1], |
| 25 | ! |
type = if (tolower(co[2]) == "true") "" else "!", |
| 26 | ! |
value = "" |
| 27 |
) |
|
| 28 |
} else {
|
|
| 29 | 17x |
list( |
| 30 | 17x |
id = co[1], |
| 31 | 17x |
type = co[2], |
| 32 | 17x |
value = if (grepl("^\\d+$", co[3])) {
|
| 33 | 9x |
as.numeric(co[3]) |
| 34 |
} else {
|
|
| 35 | 8x |
gsub("[\"']", "", co[3])
|
| 36 |
}, |
|
| 37 | 17x |
any = comb_type |
| 38 |
) |
|
| 39 |
} |
|
| 40 |
) |
|
| 41 |
}) |
|
| 42 |
} |
|
| 43 | ||
| 44 |
process_conditions <- function(conditions, ids, caller) {
|
|
| 45 | 6x |
for (i in seq_along(conditions)) {
|
| 46 | 7x |
if (conditions[i] != "") {
|
| 47 | 1x |
display <- TRUE |
| 48 | 1x |
if (grepl("^[dl][^:]*:", conditions[i], TRUE)) {
|
| 49 | 1x |
if (grepl("^l", conditions[i], TRUE)) {
|
| 50 | 1x |
display <- FALSE |
| 51 |
} |
|
| 52 | 1x |
conditions[i] <- sub("^[dl][^:]*:\\s*", "", conditions[i], TRUE)
|
| 53 |
} |
|
| 54 | 1x |
caller$rules <- c( |
| 55 | 1x |
caller$rules, |
| 56 | 1x |
list(list( |
| 57 | 1x |
condition = parse_rule(conditions[i]), |
| 58 | 1x |
effects = if (display) list(display = ids[i]) else list(lock = ids[i]) |
| 59 |
)) |
|
| 60 |
) |
|
| 61 |
} |
|
| 62 |
} |
|
| 63 |
} |
|
| 64 | ||
| 65 |
to_input_row <- function(e) {
|
|
| 66 | ! |
c( |
| 67 | ! |
'<div class="col">', |
| 68 | ! |
e[2], |
| 69 | ! |
"</div>", |
| 70 | ! |
'<div class="col">', |
| 71 | ! |
e[-c(1:2, length(e))], |
| 72 | ! |
"</div>" |
| 73 |
) |
|
| 74 |
} |
|
| 75 | ||
| 76 |
make_build_environment <- function() {
|
|
| 77 | 30x |
e <- new.env() |
| 78 | 30x |
attr(e, "name") <- "community_site_parts" |
| 79 | 30x |
e$site_build <- function(...) {}
|
| 80 | 30x |
e$uid <- 0 |
| 81 | 30x |
e |
| 82 |
} |
|
| 83 | ||
| 84 |
calculate_sha <- function(file, level) {
|
|
| 85 | 5x |
if (Sys.which("openssl") != "") {
|
| 86 | 5x |
tryCatch( |
| 87 | 5x |
strsplit( |
| 88 | 5x |
system2( |
| 89 | 5x |
"openssl", |
| 90 | 5x |
c("dgst", paste0("-sha", level), shQuote(file)),
|
| 91 | 5x |
TRUE |
| 92 |
), |
|
| 93 |
" ", |
|
| 94 | 5x |
fixed = TRUE |
| 95 | 5x |
)[[1]][2], |
| 96 | 5x |
error = function(e) "" |
| 97 |
) |
|
| 98 |
} else {
|
|
| 99 |
"" |
|
| 100 |
} |
|
| 101 |
} |
|
| 102 | ||
| 103 |
head_import <- function(d, dir = ".") {
|
|
| 104 |
if ( |
|
| 105 | 35x |
!is.null(d$src) && |
| 106 | 35x |
(!d$src %in% c("script.js", "style.css") ||
|
| 107 | 35x |
(file.exists(paste0(dir, "/docs/", d$src)) && |
| 108 | 35x |
file.size(paste0(dir, "/docs/", d$src)))) |
| 109 |
) {
|
|
| 110 | 33x |
paste( |
| 111 | 33x |
c( |
| 112 |
"<", |
|
| 113 | 33x |
if (d$type == "script") {
|
| 114 | 18x |
'script type="application/javascript" src="' |
| 115 |
} else {
|
|
| 116 | 15x |
'link href="' |
| 117 |
}, |
|
| 118 | 33x |
d$src, |
| 119 |
'"', |
|
| 120 | 33x |
if (!is.null(d$hash)) {
|
| 121 | 18x |
c(' integrity="', d$hash, '"', ' crossorigin="anonymous"')
|
| 122 |
}, |
|
| 123 | 33x |
if (d$type == "stylesheet") {
|
| 124 | 15x |
c( |
| 125 | 15x |
' rel="', |
| 126 | 15x |
if (!is.null(d$loading)) d$loading else "preload", |
| 127 | 15x |
'" as="style" media="all"', |
| 128 | 15x |
' onload="this.onload=null;this.rel=\'stylesheet\'"' |
| 129 |
) |
|
| 130 |
}, |
|
| 131 | 33x |
if (d$type == "script") {
|
| 132 | 18x |
if (is.null(d$loading)) {
|
| 133 | 15x |
" async" |
| 134 |
} else {
|
|
| 135 | 2x |
if (d$loading == "") "" else c(" ", d$loading)
|
| 136 |
} |
|
| 137 |
}, |
|
| 138 |
">", |
|
| 139 | 33x |
if (d$type == "script") "</script>" |
| 140 |
), |
|
| 141 | 33x |
collapse = "" |
| 142 |
) |
|
| 143 |
} |
|
| 144 |
} |
|
| 145 | ||
| 146 |
make_full_name <- function(filename, variable) {
|
|
| 147 | 20x |
sub( |
| 148 |
"^:", |
|
| 149 |
"", |
|
| 150 | 20x |
paste0( |
| 151 | 20x |
sub( |
| 152 |
"^.*[\\\\/]", |
|
| 153 |
"", |
|
| 154 | 20x |
gsub( |
| 155 | 20x |
"^.*\\d{4}(?:q\\d)?_|\\.\\w{3,4}(?:\\.[gbx]z2?)?$|\\..*$",
|
| 156 |
"", |
|
| 157 | 20x |
basename(filename) |
| 158 |
) |
|
| 159 |
), |
|
| 160 |
":", |
|
| 161 | 20x |
variable |
| 162 |
) |
|
| 163 |
) |
|
| 164 |
} |
|
| 165 | ||
| 166 |
replace_equations <- function(info) {
|
|
| 167 | 5x |
lapply(info, function(e) {
|
| 168 | 6x |
if (!is.list(e)) {
|
| 169 | ! |
e <- list(default = e) |
| 170 |
} |
|
| 171 | 6x |
descriptions <- grep("description", names(e), fixed = TRUE)
|
| 172 | 6x |
if (length(descriptions)) {
|
| 173 | 6x |
for (d in descriptions) {
|
| 174 | 6x |
p <- gregexpr( |
| 175 | 6x |
"(?:\\$|\\\\\\[|\\\\\\(|\\\\begin\\{math\\})(.+?)(?:\\$|\\\\\\]|\\\\\\)|\\\\end\\{math\\})(?=\\s|$)",
|
| 176 | 6x |
e[[d]], |
| 177 | 6x |
perl = TRUE |
| 178 | 6x |
)[[1]] |
| 179 | 6x |
if (p[[1]] != -1) {
|
| 180 | 5x |
re <- paste("", e[[d]], "")
|
| 181 | 5x |
fm <- regmatches(e[[d]], p) |
| 182 | 5x |
for (i in seq_along(p)) {
|
| 183 | 5x |
mp <- attr(p, "capture.start")[i, ] |
| 184 | 5x |
eq <- substring(e[[d]], mp, mp + attr(p, "capture.length")[i, ] - 1) |
| 185 | 5x |
parsed <- tryCatch(katex_mathml(eq), error = function(e) NULL) |
| 186 | 5x |
if (!is.null(parsed)) {
|
| 187 | 5x |
re <- paste( |
| 188 | 5x |
strsplit(re, fm[[i]], fixed = TRUE)[[1]], |
| 189 | 5x |
collapse = sub("^<[^>]*>", "", sub("<[^>]*>$", "", parsed))
|
| 190 |
) |
|
| 191 |
} |
|
| 192 |
} |
|
| 193 | 5x |
e[[d]] <- gsub("^ | $", "", re)
|
| 194 |
} |
|
| 195 |
} |
|
| 196 |
} |
|
| 197 | 6x |
if (is.list(e$categories)) {
|
| 198 | ! |
e$categories <- replace_equations(e$categories) |
| 199 |
} |
|
| 200 | 6x |
if (is.list(e$variants)) {
|
| 201 | ! |
e$variants <- replace_equations(e$variants) |
| 202 |
} |
|
| 203 | 6x |
e |
| 204 |
}) |
|
| 205 |
} |
|
| 206 | ||
| 207 |
preprocess <- function(l) {
|
|
| 208 | 78x |
if (!is.list(l)) {
|
| 209 | 51x |
l <- sapply(l, function(n) list()) |
| 210 |
} |
|
| 211 | 78x |
ns <- names(l) |
| 212 | 78x |
for (i in seq_along(l)) {
|
| 213 | 105x |
name <- if (ns[i] == "blank") "" else ns[i] |
| 214 | 105x |
l[[i]]$name <- name |
| 215 | 47x |
if (is.null(l[[i]]$default)) l[[i]]$default <- name |
| 216 |
} |
|
| 217 | 78x |
l |
| 218 |
} |
|
| 219 | ||
| 220 |
replace_dynamic <- function(e, p, s, v = NULL, default = "default") {
|
|
| 221 | 752x |
m <- gregexpr(p, e) |
| 222 | 752x |
if (m[[1]][[1]] != -1) {
|
| 223 | 249x |
t <- regmatches(e, m)[[1]] |
| 224 | 249x |
tm <- structure(gsub("\\{[^.]+\\.?|\\}", "", t), names = t)
|
| 225 | 249x |
tm <- tm[!duplicated(names(tm))] |
| 226 | 249x |
tm[tm == ""] <- default |
| 227 | 249x |
for (tar in names(tm)) {
|
| 228 | 275x |
us <- (if (is.null(v) || substring(tar, 2, 2) == "c") s else v) |
| 229 | 275x |
entry <- tm[[tar]] |
| 230 | 275x |
if (is.null(us[[entry]]) && grepl("description", entry, fixed = TRUE)) {
|
| 231 | 52x |
entry <- default <- "description" |
| 232 |
} |
|
| 233 | 275x |
if (is.null(us[[entry]]) && entry == default) {
|
| 234 | 112x |
entry <- "default" |
| 235 |
} |
|
| 236 | 275x |
if (is.null(us[[entry]])) {
|
| 237 | ! |
cli_abort("failed to render measure info from {tar}")
|
| 238 |
} |
|
| 239 | 275x |
e <- gsub(tar, us[[entry]], e, fixed = TRUE) |
| 240 |
} |
|
| 241 |
} |
|
| 242 | 752x |
e |
| 243 |
} |
|
| 244 | ||
| 245 |
prepare_source <- function(o, s, p) {
|
|
| 246 | 184x |
if (length(o)) {
|
| 247 | 83x |
lapply(o, function(e) {
|
| 248 | 57x |
if (is.character(e) && length(e) == 1) replace_dynamic(e, p, s) else e |
| 249 |
}) |
|
| 250 |
} else {
|
|
| 251 | 101x |
list(name = "", default = "") |
| 252 |
} |
|
| 253 |
} |
|
| 254 | ||
| 255 |
render_info_names <- function(infos) {
|
|
| 256 | 6x |
r <- lapply(names(infos), function(n) render_info(infos[n], TRUE)) |
| 257 | 6x |
structure(rep(names(infos), vapply(r, length, 0)), names = unlist(r)) |
| 258 |
} |
|
| 259 | ||
| 260 |
render_info <- function(info, names_only = FALSE) {
|
|
| 261 | 65x |
base_name <- names(info) |
| 262 | 65x |
base <- info[[1]] |
| 263 | 65x |
if (is.null(base$categories) && is.null(base$variants)) {
|
| 264 | 26x |
return(if (names_only) base_name else info) |
| 265 |
} |
|
| 266 | 39x |
categories <- preprocess(base$categories) |
| 267 | 39x |
variants <- preprocess(base$variants) |
| 268 | 39x |
base$categories <- NULL |
| 269 | 39x |
base$variants <- NULL |
| 270 | 39x |
expanded <- NULL |
| 271 | 39x |
vars <- strsplit( |
| 272 | 39x |
as.character(outer( |
| 273 | 39x |
if (is.null(names(categories))) "" else names(categories), |
| 274 | 39x |
if (is.null(names(variants))) "" else names(variants), |
| 275 | 39x |
paste, |
| 276 | 39x |
sep = "|||" |
| 277 |
)), |
|
| 278 |
"|||", |
|
| 279 | 39x |
fixed = TRUE |
| 280 |
) |
|
| 281 | 39x |
for (var in vars) {
|
| 282 | 92x |
cs <- if (var[1] == "") list() else categories[[var[1]]] |
| 283 | 92x |
vs <- if (length(var) == 1 || var[2] == "") list() else variants[[var[2]]] |
| 284 | 92x |
cs <- prepare_source(cs, vs, "\\{variants?(?:\\.[^}]+?)?\\}")
|
| 285 | 92x |
vs <- prepare_source(vs, cs, "\\{categor(?:y|ies)(?:\\.[^}]+?)?\\}")
|
| 286 | 92x |
s <- c(cs, vs[!names(vs) %in% names(cs)]) |
| 287 | 92x |
p <- "\\{(?:categor(?:y|ies)|variants?)(?:\\.[^}]+?)?\\}"
|
| 288 | 92x |
key <- replace_dynamic(base_name, p, cs, vs) |
| 289 | 92x |
if (names_only) {
|
| 290 | 58x |
expanded <- c(expanded, key) |
| 291 |
} else {
|
|
| 292 | 34x |
expanded[[key]] <- c( |
| 293 | 34x |
structure( |
| 294 | 34x |
lapply(names(base), function(n) {
|
| 295 | 400x |
e <- base[[n]] |
| 296 | 400x |
if (is.character(e) && length(e) == 1) {
|
| 297 | 340x |
e <- replace_dynamic(e, p, cs, vs, n) |
| 298 |
} |
|
| 299 | 400x |
e |
| 300 |
}), |
|
| 301 | 34x |
names = names(base) |
| 302 |
), |
|
| 303 | 34x |
s[ |
| 304 | 34x |
!names(s) %in% |
| 305 | 34x |
c( |
| 306 | 34x |
"default", |
| 307 | 34x |
"name", |
| 308 | 34x |
if (any(base[c("long_description", "short_description")] != "")) {
|
| 309 | 34x |
"description" |
| 310 |
}, |
|
| 311 | 34x |
names(base) |
| 312 |
) |
|
| 313 |
] |
|
| 314 |
) |
|
| 315 |
} |
|
| 316 |
} |
|
| 317 | 39x |
expanded |
| 318 |
} |
|
| 319 | ||
| 320 |
get_git_remote <- function(config) {
|
|
| 321 | 29x |
if (file.exists(config)) {
|
| 322 | 27x |
conf <- readLines(config) |
| 323 | 27x |
branch <- grep("[branch", conf, fixed = TRUE, value = TRUE)
|
| 324 | 27x |
url <- grep("url =", conf, fixed = TRUE, value = TRUE)
|
| 325 | 27x |
if (length(branch) && length(url)) {
|
| 326 | 27x |
paste0( |
| 327 | 27x |
gsub("^.+=\\s|\\.git", "", url[[1]]),
|
| 328 | 27x |
"/blob/", |
| 329 | 27x |
gsub('^[^"]+"|"\\]', "", branch[[1]])
|
| 330 |
) |
|
| 331 |
} |
|
| 332 |
} |
|
| 333 |
} |
|
| 334 | ||
| 335 |
attempt_read <- function(file, id_cols) {
|
|
| 336 | 27x |
tryCatch( |
| 337 |
{
|
|
| 338 | 27x |
sep <- if (grepl(".csv", file, fixed = TRUE)) "," else "\t"
|
| 339 | 27x |
cols <- scan(file, "", nlines = 1, sep = sep, quiet = TRUE) |
| 340 | 27x |
types <- rep("?", length(cols))
|
| 341 | 27x |
types[cols %in% id_cols] <- "c" |
| 342 | 27x |
read_delim_arrow( |
| 343 | 27x |
gzfile(file), |
| 344 | 27x |
sep, |
| 345 | 27x |
col_names = cols, |
| 346 | 27x |
col_types = paste(types, collapse = ""), |
| 347 | 27x |
skip = 1 |
| 348 |
) |
|
| 349 |
}, |
|
| 350 | 27x |
error = function(e) NULL |
| 351 |
) |
|
| 352 |
} |
| 1 |
#' Add a tooltip-like display to a website |
|
| 2 |
#' |
|
| 3 |
#' Adds an output to display information about a hovered-over or selected data point. |
|
| 4 |
#' |
|
| 5 |
#' @param title Title text, or the source of title text. |
|
| 6 |
#' @param body A list of entries in the info body section, which can be raw text or references to features or |
|
| 7 |
#' data variables. |
|
| 8 |
#' @param row_style A character specifying how rows should be displayed: \code{"table"} (default)
|
|
| 9 |
#' to place names and values in separate columns of a table row, or \code{"stack"} to place names over values.
|
|
| 10 |
#' Repeats over rows. |
|
| 11 |
#' @param default A list with entries for \code{"title"} and \code{"body"}, which are treated as raw text.
|
|
| 12 |
#' @param dataview The ID of a dataview, used for the persistent display. |
|
| 13 |
#' @param variable Name of the variable from which to display variable information and values. If not specified, |
|
| 14 |
#' this will default to the coloring variable of maps and plots, or the y variable of a dataview. |
|
| 15 |
#' @param subto A vector of output IDs to receive hover events from. |
|
| 16 |
#' @param id Unique id of the element. |
|
| 17 |
#' @param variable_info Logical; if \code{TRUE} (default), variable names can be clicked for more information.
|
|
| 18 |
#' @param floating Logical; if \code{TRUE}, the information pane will appear next to the cursor.
|
|
| 19 |
#' @examples |
|
| 20 |
#' \dontrun{
|
|
| 21 |
#' output_info("Initial View", "Hover over plot elements for more information.")
|
|
| 22 |
#' } |
|
| 23 |
#' @return A character vector of the content to be added. |
|
| 24 |
#' @export |
|
| 25 | ||
| 26 |
output_info <- function( |
|
| 27 |
title = NULL, |
|
| 28 |
body = NULL, |
|
| 29 |
row_style = "table", |
|
| 30 |
default = NULL, |
|
| 31 |
dataview = NULL, |
|
| 32 |
variable = NULL, |
|
| 33 |
subto = NULL, |
|
| 34 |
id = NULL, |
|
| 35 |
variable_info = TRUE, |
|
| 36 |
floating = FALSE |
|
| 37 |
) {
|
|
| 38 | 4x |
caller <- parent.frame() |
| 39 | 4x |
building <- !is.null(attr(caller, "name")) && |
| 40 | 4x |
attr(caller, "name") == "community_site_parts" |
| 41 | 4x |
if (is.null(id)) {
|
| 42 | 4x |
id <- paste0("info", caller$uid)
|
| 43 |
} |
|
| 44 | 4x |
r <- paste0( |
| 45 | 4x |
'<div class="auto-output text-display', |
| 46 | 4x |
if (floating) ' floating"' else '"', |
| 47 | 4x |
if (!is.null(dataview)) paste0(' data-view="', dataview, '"'),
|
| 48 | 4x |
' data-autoType="info" id="', |
| 49 | 4x |
id, |
| 50 | 4x |
'"></div>' |
| 51 |
) |
|
| 52 | 4x |
row_style <- rep_len(row_style, length(body)) |
| 53 | 4x |
if (building) {
|
| 54 | 2x |
caller$content <- c(caller$content, r) |
| 55 | 2x |
caller$info[[id]] <- Filter( |
| 56 | 2x |
function(e) length(e) > 1 || (length(e) && e != "" && !isFALSE(e)), |
| 57 | 2x |
list( |
| 58 | 2x |
title = if (is.null(title)) "" else title, |
| 59 | 2x |
body = lapply(seq_along(body), function(i) {
|
| 60 | 1x |
list( |
| 61 | 1x |
name = if (is.null(names(body))) "" else names(body)[i], |
| 62 | 1x |
value = body[[i]], |
| 63 | 1x |
style = row_style[[i]] |
| 64 |
) |
|
| 65 |
}), |
|
| 66 | 2x |
default = as.list(default), |
| 67 | 2x |
floating = floating |
| 68 |
) |
|
| 69 |
) |
|
| 70 | 2x |
if (!is.null(dataview)) {
|
| 71 | ! |
caller$info[[id]]$dataview <- dataview |
| 72 |
} |
|
| 73 | 2x |
if (!is.null(variable)) {
|
| 74 | ! |
caller$info[[id]]$variable <- variable |
| 75 |
} |
|
| 76 | 2x |
if (!is.null(subto)) {
|
| 77 | 1x |
caller$info[[id]]$subto <- subto |
| 78 |
} |
|
| 79 | 2x |
if (variable_info) {
|
| 80 | 2x |
caller$info[[id]]$variable_info <- variable_info |
| 81 |
} |
|
| 82 | 2x |
caller$uid <- caller$uid + 1 |
| 83 |
} |
|
| 84 | 4x |
r |
| 85 |
} |
| 1 |
#' Adds a group of tabs to a website |
|
| 2 |
#' |
|
| 3 |
#' Adds a group of tabs, each of which contains input and/or output components. |
|
| 4 |
#' |
|
| 5 |
#' @param ... A separately entered list for each tab and its content. Named entries in each tab entry can be |
|
| 6 |
#' \code{"name"} (for the text appearing in the navigation tab), \code{"id"}, \code{"class"}, and \code{"condition"}.
|
|
| 7 |
#' Unnamed entries in each list entry are considered the content to be added to the tab's pane. See examples. |
|
| 8 |
#' @param id Unique ID of the tabgroup. |
|
| 9 |
#' @param class A class name to add to the tabgroup. |
|
| 10 |
#' @param condition A string representing the display condition of the entire tabgroup. |
|
| 11 |
#' @details See the \href{https://getbootstrap.com/docs/5.1/layout/grid}{Bootstrap grid documentation}.
|
|
| 12 |
#' @examples |
|
| 13 |
#' \dontrun{
|
|
| 14 |
#' page_tabgroup( |
|
| 15 |
#' "Map" = list(id = "map_tab", output_map()), |
|
| 16 |
#' "Data" = list(output_table()), |
|
| 17 |
#' ) |
|
| 18 |
#' } |
|
| 19 |
#' @return A character vector of the content to be added. |
|
| 20 |
#' @export |
|
| 21 | ||
| 22 |
page_tabgroup <- function(..., id = NULL, class = NULL, condition = NULL) {
|
|
| 23 | 3x |
caller <- parent.frame() |
| 24 | 3x |
building <- !is.null(attr(caller, "name")) && |
| 25 | 3x |
attr(caller, "name") == "community_site_parts" |
| 26 | 3x |
parts <- new.env() |
| 27 | 3x |
attr(parts, "name") <- "community_site_parts" |
| 28 | 3x |
parts$uid <- caller$uid |
| 29 | 3x |
elements <- substitute(...()) |
| 30 | 3x |
n <- length(elements) |
| 31 | 3x |
pre <- if (!is.null(id)) id else paste0("tg", parts$uid)
|
| 32 | 3x |
ids <- paste0(pre, seq_len(n)) |
| 33 | 3x |
head <- rep( |
| 34 | 3x |
'<button type="button" data-bs-toggle="tab" aria-controls="', |
| 35 | 3x |
n |
| 36 |
) |
|
| 37 | 3x |
body <- rep('<div role="tabpanel" aria-labelledby="', n)
|
| 38 | 3x |
for (i in seq_along(elements)) {
|
| 39 | 1x |
e <- elements[[i]] |
| 40 | 1x |
ns <- names(e) |
| 41 | 1x |
if (!"name" %in% ns) {
|
| 42 | 1x |
e$name <- names(elements)[i] |
| 43 |
} |
|
| 44 | 1x |
if (!"id" %in% ns) {
|
| 45 | 1x |
e$id <- ids[i] |
| 46 |
} |
|
| 47 | 1x |
if (!"class" %in% ns) {
|
| 48 | 1x |
e$class <- "" |
| 49 |
} |
|
| 50 | 1x |
if (!"condition" %in% ns) {
|
| 51 | 1x |
e$condition <- "" |
| 52 |
} |
|
| 53 | 1x |
head[i] <- paste( |
| 54 | 1x |
c( |
| 55 | 1x |
head[i], |
| 56 | 1x |
e$id, |
| 57 | 1x |
'" class="nav-link', |
| 58 | 1x |
if (i == 1) " active", |
| 59 | 1x |
if (i == 1) '" aria-current="page', |
| 60 | 1x |
'" data-bs-target="#', |
| 61 | 1x |
e$id, |
| 62 | 1x |
'" id="', |
| 63 | 1x |
e$id, |
| 64 | 1x |
'-tab">', |
| 65 | 1x |
e$name, |
| 66 | 1x |
"</button>" |
| 67 |
), |
|
| 68 | 1x |
collapse = "" |
| 69 |
) |
|
| 70 | 1x |
body[i] <- paste0( |
| 71 | 1x |
c( |
| 72 | 1x |
body[i], |
| 73 | 1x |
e$id, |
| 74 | 1x |
'-tab" class="tab-pane fade', |
| 75 | 1x |
if (i == 1) " show active", |
| 76 | 1x |
if (e$class != "") c(" ", e$class),
|
| 77 | 1x |
'" id="', |
| 78 | 1x |
e$id, |
| 79 |
'"', |
|
| 80 | 1x |
if (e$condition != "") c(' condition="', e$condition, '"'),
|
| 81 |
">", |
|
| 82 | 1x |
unlist(eval(e[names(e) == ""], parts), use.names = FALSE), |
| 83 | 1x |
"</div>" |
| 84 |
), |
|
| 85 | 1x |
collapse = "" |
| 86 |
) |
|
| 87 |
} |
|
| 88 | 3x |
r <- c( |
| 89 | 3x |
"<nav>", |
| 90 | 3x |
paste( |
| 91 | 3x |
c( |
| 92 | 3x |
"<div", |
| 93 | 3x |
if (!is.null(id)) c(' id="', id, '"'),
|
| 94 | 3x |
' class="nav nav-tabs', |
| 95 | 3x |
if (!is.null(class)) c(" ", class),
|
| 96 |
'"', |
|
| 97 | 3x |
if (!is.null(condition)) c(' condition="', condition, '"'),
|
| 98 |
">" |
|
| 99 |
), |
|
| 100 | 3x |
collapse = "" |
| 101 |
), |
|
| 102 | 3x |
head, |
| 103 | 3x |
"</div>", |
| 104 | 3x |
"</nav>", |
| 105 | 3x |
'<div class="tab-content">', |
| 106 | 3x |
body, |
| 107 | 3x |
"</div>" |
| 108 |
) |
|
| 109 | 3x |
if (building) {
|
| 110 | 1x |
caller$content <- c(caller$content, r) |
| 111 | 1x |
for (n in names(parts)) {
|
| 112 | 1x |
if (n != "content" && n != "uid") {
|
| 113 | ! |
caller[[n]] <- c(caller[[n]], parts[[n]]) |
| 114 |
} |
|
| 115 |
} |
|
| 116 | 1x |
caller$uid <- parts$uid + 1 |
| 117 |
} |
|
| 118 | 3x |
r |
| 119 |
} |
| 1 |
#' Map Data Commons Files |
|
| 2 |
#' |
|
| 3 |
#' Extract variables and IDs from files in datacommons repositories |
|
| 4 |
#' |
|
| 5 |
#' @param dir Directory of the data commons projects. |
|
| 6 |
#' @param search_pattern A regular expression string used be passed to \code{\link{list.files}}.
|
|
| 7 |
#' @param variable_location The name of a column contain variable names in each dataset, or a function to retrieve |
|
| 8 |
#' variable names (e.g., \code{colnames}).
|
|
| 9 |
#' @param id_location The name of a column contain IDs in each dataset, or a function to retrieve |
|
| 10 |
#' IDs (e.g., \code{rownames}).
|
|
| 11 |
#' @param reader A function capable of handling a connection in its first argument, which returns a matrix-like object. |
|
| 12 |
#' @param overwrite Logical; if \code{TRUE}, creates a new map even if one exists.
|
|
| 13 |
#' @param verbose Logical; if \code{FALSE}, does not print status messages.
|
|
| 14 |
#' @examples |
|
| 15 |
#' \dontrun{
|
|
| 16 |
#' # from a data commons project directory |
|
| 17 |
#' map <- datacommons_map_files(".")
|
|
| 18 |
#' } |
|
| 19 |
#' @return An invisible \code{list}, including a \code{data.frame} of the mapped variables, with \code{variable} (variable name),
|
|
| 20 |
#' \code{repo} (the repository containing the file), \code{dir_name} (variable name with a prefix from the parent directories),
|
|
| 21 |
#' \code{full_name} (variable name with a prefix from the last part of the file's name, after a year or year range),
|
|
| 22 |
#' and \code{file} (path to the file) columns, and a \code{list} of the mapped IDs, with an entry for each ID,
|
|
| 23 |
#' each of which with entries for \code{repos} (repositories in which the ID appears) and \code{files} (files in which the ID appears).
|
|
| 24 |
#' @export |
|
| 25 | ||
| 26 |
datacommons_map_files <- function( |
|
| 27 |
dir, |
|
| 28 |
search_pattern = "\\.csv(?:\\.[gbx]z2?)?$", |
|
| 29 |
variable_location = "measure", |
|
| 30 |
id_location = "geoid", |
|
| 31 |
reader = read.csv, |
|
| 32 |
overwrite = FALSE, |
|
| 33 |
verbose = TRUE |
|
| 34 |
) {
|
|
| 35 | 5x |
if (missing(dir)) {
|
| 36 | ! |
cli_abort("{.arg dir} must be specified")
|
| 37 |
} |
|
| 38 | 5x |
dir <- paste0(normalizePath(dir, "/", FALSE), "/") |
| 39 | 5x |
check <- check_template("datacommons", dir = dir)
|
| 40 | 5x |
if (!check$exists) {
|
| 41 | ! |
cli_abort(c( |
| 42 | ! |
x = "{.arg dir} does not appear to point to a data commons project",
|
| 43 | ! |
i = paste0('initialize it with {.code init_datacommons("', dir, '")}')
|
| 44 |
)) |
|
| 45 |
} |
|
| 46 | 5x |
if (!dir.exists(paste0(dir, "repos"))) {
|
| 47 | ! |
cli_abort(c( |
| 48 | ! |
x = "no {.path repos} directory found in {.arg dir}",
|
| 49 | ! |
i = paste0( |
| 50 | ! |
'use {.code datacommons_refresh("',
|
| 51 | ! |
dir, |
| 52 | ! |
'")} to bring in remote data' |
| 53 |
) |
|
| 54 |
)) |
|
| 55 |
} |
|
| 56 | 5x |
commons <- jsonlite::read_json(paste0(dir, "commons.json")) |
| 57 | 5x |
all_files <- list.files( |
| 58 | 5x |
paste0(dir, c("cache", "repos")),
|
| 59 | 5x |
search_pattern, |
| 60 | 5x |
full.names = TRUE, |
| 61 | 5x |
recursive = TRUE |
| 62 |
) |
|
| 63 | 5x |
all_files <- sort(all_files[ |
| 64 | 5x |
!grepl("[/\\](?:code|docs|working|original)[/\\]|variable_map", all_files)
|
| 65 |
]) |
|
| 66 | 5x |
if (!length(all_files)) {
|
| 67 | ! |
cli_abort("no files were found")
|
| 68 |
} |
|
| 69 | 5x |
res <- paste0(dir, "cache/", c("variable_map.csv", "id_map.rds"))
|
| 70 | 5x |
if (overwrite) {
|
| 71 | 1x |
unlink(res) |
| 72 |
} |
|
| 73 |
if ( |
|
| 74 | 5x |
all(file.exists(res)) && all(file.mtime(res) > max(file.mtime(all_files))) |
| 75 |
) {
|
|
| 76 | 4x |
if (verbose) {
|
| 77 | ! |
cli_alert_success("the maps are up to date")
|
| 78 |
} |
|
| 79 | 4x |
return(invisible(list(variables = read.csv(res[1]), ids = readRDS(res[2])))) |
| 80 |
} |
|
| 81 | 1x |
i <- 1 |
| 82 | 1x |
map <- idmap <- list() |
| 83 | 1x |
noread <- novars <- noids <- empty <- NULL |
| 84 | 1x |
repos <- sort(unlist(commons$repositories)) |
| 85 | 1x |
manifest <- measure_info <- list() |
| 86 | 1x |
if (verbose) {
|
| 87 | 1x |
cli_progress_step( |
| 88 | 1x |
"scanning files in repos: {i}/{length(repos)}",
|
| 89 | 1x |
msg_done = "created file maps: {.file {res}}",
|
| 90 | 1x |
spinner = TRUE |
| 91 |
) |
|
| 92 |
} |
|
| 93 | 1x |
for (i in seq_along(repos)) {
|
| 94 | 1x |
r <- repos[[i]] |
| 95 | 1x |
manifest[[r]] <- list() |
| 96 | 1x |
files <- sort(list.files( |
| 97 | 1x |
paste0(dir, c("repos", "cache"), "/", sub("^[^/]+/", "", r)),
|
| 98 | 1x |
search_pattern, |
| 99 | 1x |
full.names = TRUE, |
| 100 | 1x |
recursive = TRUE, |
| 101 | 1x |
ignore.case = TRUE |
| 102 |
)) |
|
| 103 | 1x |
measure_info_files <- sort(list.files( |
| 104 | 1x |
paste0(dir, "repos/", sub("^.+/", "", r)),
|
| 105 | 1x |
"^measure_info[^.]*\\.json$", |
| 106 | 1x |
full.names = TRUE, |
| 107 | 1x |
recursive = TRUE |
| 108 |
)) |
|
| 109 | 1x |
measure_info_files <- measure_info_files[ |
| 110 | 1x |
!duplicated(gsub("_rendered|/code/|/data/", "", measure_info_files))
|
| 111 |
] |
|
| 112 | 1x |
if (length(measure_info_files)) {
|
| 113 | 1x |
measure_info <- c( |
| 114 | 1x |
measure_info, |
| 115 | 1x |
lapply( |
| 116 | 1x |
structure( |
| 117 | 1x |
measure_info_files, |
| 118 | 1x |
names = sub( |
| 119 | 1x |
paste0(dir, "repos/"), |
| 120 | 1x |
paste0(sub("/.*$", "", r), "/"),
|
| 121 | 1x |
measure_info_files, |
| 122 | 1x |
fixed = TRUE |
| 123 |
) |
|
| 124 |
), |
|
| 125 | 1x |
function(f) {
|
| 126 | 5x |
tryCatch( |
| 127 | 5x |
data_measure_info( |
| 128 | 5x |
f, |
| 129 | 5x |
render = TRUE, |
| 130 | 5x |
write = FALSE, |
| 131 | 5x |
verbose = FALSE, |
| 132 | 5x |
open_after = FALSE, |
| 133 | 5x |
include_empty = FALSE |
| 134 |
), |
|
| 135 | 5x |
error = function(e) {
|
| 136 | ! |
cli_alert_warning("failed to read measure info: {.file {f}}")
|
| 137 | ! |
NULL |
| 138 |
} |
|
| 139 |
) |
|
| 140 |
} |
|
| 141 |
) |
|
| 142 |
) |
|
| 143 |
} |
|
| 144 | 1x |
files <- files[files %in% all_files] |
| 145 | 1x |
for (f in files) {
|
| 146 | 7x |
d <- attempt_read(f, id_location) |
| 147 | 7x |
if (!is.null(d)) {
|
| 148 | 7x |
if (nrow(d)) {
|
| 149 | 7x |
lcols <- tolower(colnames(d)) |
| 150 | 7x |
vars <- c(id_location, variable_location) |
| 151 | 7x |
if (any(!vars %in% colnames(d))) {
|
| 152 | 1x |
l <- !colnames(d) %in% vars & lcols %in% vars |
| 153 | 1x |
colnames(d)[l] <- lcols[l] |
| 154 |
} |
|
| 155 |
if ( |
|
| 156 | 7x |
is.character(variable_location) && |
| 157 | 7x |
!variable_location %in% colnames(d) |
| 158 |
) {
|
|
| 159 | 1x |
novars <- c(novars, f) |
| 160 | 1x |
next |
| 161 |
} |
|
| 162 | 6x |
if (is.character(id_location) && !id_location %in% colnames(d)) {
|
| 163 | ! |
noids <- c(noids, f) |
| 164 | ! |
next |
| 165 |
} |
|
| 166 | 6x |
hash <- md5sum(f)[[1]] |
| 167 | 6x |
relf <- sub( |
| 168 | 6x |
paste0(dir, "repos/", sub("^.+/", "", r), "/"),
|
| 169 |
"", |
|
| 170 | 6x |
f, |
| 171 | 6x |
fixed = TRUE |
| 172 |
) |
|
| 173 | 6x |
manifest[[r]][[hash]]$name <- relf |
| 174 | 6x |
manifest[[r]][[hash]]$providers <- c( |
| 175 | 6x |
manifest[[r]][[hash]]$provider, |
| 176 | 6x |
if (grepl("repos/", f, fixed = TRUE)) "github" else "dataverse"
|
| 177 |
) |
|
| 178 | 6x |
vars <- if (is.function(variable_location)) {
|
| 179 | ! |
variable_location(d) |
| 180 |
} else {
|
|
| 181 | 6x |
d[[variable_location]] |
| 182 |
} |
|
| 183 | 6x |
if (length(vars)) {
|
| 184 | 6x |
vars <- unique(vars[!is.na(vars)]) |
| 185 | 6x |
map[[f]] <- data.frame( |
| 186 | 6x |
variable = vars, |
| 187 | 6x |
dir_name = paste0( |
| 188 | 6x |
gsub( |
| 189 | 6x |
paste0(dir, "|cache/|repos/|data/|distribution/"), |
| 190 |
"", |
|
| 191 | 6x |
paste0(dirname(f), "/") |
| 192 |
), |
|
| 193 | 6x |
vars |
| 194 |
), |
|
| 195 | 6x |
full_name = make_full_name(f, vars), |
| 196 | 6x |
repo = r, |
| 197 | 6x |
file = sub(dir, "", f, fixed = TRUE) |
| 198 |
) |
|
| 199 | 6x |
manifest[[r]][[hash]]$variables <- vars |
| 200 |
} else {
|
|
| 201 | ! |
novars <- c(novars, f) |
| 202 |
} |
|
| 203 | 6x |
ids <- if (is.function(id_location)) {
|
| 204 | ! |
id_location(d) |
| 205 |
} else {
|
|
| 206 | 6x |
d[[id_location]] |
| 207 |
} |
|
| 208 | 6x |
if (length(ids)) {
|
| 209 | 6x |
ids <- gsub( |
| 210 | 6x |
"^\\s+|\\s+$", |
| 211 |
"", |
|
| 212 | 6x |
format(unique(ids), scientific = FALSE) |
| 213 |
) |
|
| 214 | 6x |
idmap[[f]] <- data.frame(id = ids, repo = r, file = relf) |
| 215 | 6x |
manifest[[r]][[hash]]$ids <- ids |
| 216 |
} else {
|
|
| 217 | ! |
noids <- c(noids, f) |
| 218 |
} |
|
| 219 |
} else {
|
|
| 220 | ! |
empty <- c(empty, f) |
| 221 |
} |
|
| 222 |
} else {
|
|
| 223 | ! |
noread <- c(noread, f) |
| 224 |
} |
|
| 225 |
} |
|
| 226 | 1x |
if (verbose) cli_progress_update() |
| 227 |
} |
|
| 228 | 1x |
if (verbose) {
|
| 229 | 1x |
cli_progress_done() |
| 230 |
} |
|
| 231 | 1x |
if (length(measure_info)) {
|
| 232 | 1x |
jsonlite::write_json( |
| 233 | 1x |
measure_info, |
| 234 | 1x |
paste0(dir, "cache/measure_info.json"), |
| 235 | 1x |
auto_unbox = TRUE |
| 236 |
) |
|
| 237 |
} |
|
| 238 | 1x |
map <- do.call(rbind, unname(map)) |
| 239 | 1x |
idmap <- do.call(rbind, unname(idmap)) |
| 240 | 1x |
if (verbose) {
|
| 241 | 1x |
if (length(noread)) {
|
| 242 | ! |
cli_warn("file{?s} could not be read in: {noread}")
|
| 243 |
} |
|
| 244 | 1x |
if (length(empty)) {
|
| 245 | ! |
cli_warn("{?files have/file had} no rows: {empty}")
|
| 246 |
} |
|
| 247 | 1x |
if (length(novars)) {
|
| 248 | 1x |
cli_warn( |
| 249 | 1x |
"{.arg {variable_location}} was not in {?some files'/a file's} column names: {novars}"
|
| 250 |
) |
|
| 251 |
} |
|
| 252 | 1x |
if (length(noids)) {
|
| 253 | ! |
cli_warn( |
| 254 | ! |
"{.arg {id_location}} was not in {?some files'/a file's} column names: {noids}"
|
| 255 |
) |
|
| 256 |
} |
|
| 257 |
} |
|
| 258 | 1x |
if (!length(idmap)) {
|
| 259 | ! |
cli_abort("no IDs were mapped")
|
| 260 |
} |
|
| 261 | 1x |
dir.create(paste0(dir, "manifest"), FALSE) |
| 262 | 1x |
jsonlite::write_json( |
| 263 | 1x |
manifest, |
| 264 | 1x |
paste0(dir, "manifest/files.json"), |
| 265 | 1x |
auto_unbox = TRUE, |
| 266 | 1x |
pretty = TRUE |
| 267 |
) |
|
| 268 | 1x |
dir.create(paste0(dir, "cache"), FALSE) |
| 269 | 1x |
idmap <- lapply( |
| 270 | 1x |
split(idmap, idmap$id), |
| 271 | 1x |
function(d) list(repos = unique(d$repo), files = unique(d$file)) |
| 272 |
) |
|
| 273 | 1x |
saveRDS(idmap, res[2], compress = "xz") |
| 274 | 1x |
write.csv(map, res[1], row.names = FALSE) |
| 275 | 1x |
init_datacommons(dir, refresh_after = FALSE, verbose = FALSE) |
| 276 | 1x |
invisible(list(variables = map, ids = idmap)) |
| 277 |
} |
| 1 |
#' Adds an organizational panel to a website |
|
| 2 |
#' |
|
| 3 |
#' Adds a panel to a website outside of the main content area. |
|
| 4 |
#' |
|
| 5 |
#' @param title Text to appear in the panel's header area. |
|
| 6 |
#' @param ... Elements to appear in the panel's body area. |
|
| 7 |
#' @param foot Content to appear in the panel's footer area. |
|
| 8 |
#' @param position The side of the screen on which the panel appears; \code{"left"} (default) or \code{"right"}.
|
|
| 9 |
#' @param wraps The class of wrapper to place elements in; either \code{"row"}, \code{"col"}, or \code{""}
|
|
| 10 |
#' (to not wrap the element). Can specify 1 for every element, or a different class for each element. |
|
| 11 |
#' @param sizes The relative size of each wrapper, between 1 and 12, or \code{"auto"}; default is equal size.
|
|
| 12 |
#' @param breakpoints Bootstrap breakpoint of each wrapper; one of \code{""} (extra small), \code{"sm"},
|
|
| 13 |
#' \code{"md"}, \code{"lg"}, \code{"xl"}, or \code{"xxl"}.
|
|
| 14 |
#' @param conditions A character for each element representing the conditions in which that should be showing |
|
| 15 |
#' (e.g., \code{c("", "input_a == a", "")}); \code{""} means the element's display is not conditional.
|
|
| 16 |
#' Adding \code{"lock: "} before the condition will disable inputs rather than hide the element.
|
|
| 17 |
#' @param id Unique ID of the section. |
|
| 18 |
#' @details See the \href{https://getbootstrap.com/docs/5.1/layout/grid}{Bootstrap grid documentation}.
|
|
| 19 |
#' @examples |
|
| 20 |
#' \dontrun{
|
|
| 21 |
#' page_panel( |
|
| 22 |
#' "<h1>Title</h1>", |
|
| 23 |
#' "<p>body</p>", |
|
| 24 |
#' ) |
|
| 25 |
#' } |
|
| 26 |
#' @return A character vector of the content to be added. |
|
| 27 |
#' @export |
|
| 28 | ||
| 29 |
page_panel <- function( |
|
| 30 |
title = "Side Panel", |
|
| 31 |
..., |
|
| 32 |
foot = NULL, |
|
| 33 |
position = "left", |
|
| 34 |
wraps = NA, |
|
| 35 |
sizes = NA, |
|
| 36 |
breakpoints = NA, |
|
| 37 |
conditions = "", |
|
| 38 |
id = NULL |
|
| 39 |
) {
|
|
| 40 | 3x |
caller <- parent.frame() |
| 41 | 3x |
building <- !is.null(attr(caller, "name")) && |
| 42 | 3x |
attr(caller, "name") == "community_site_parts" |
| 43 | 3x |
parts <- new.env() |
| 44 | 3x |
attr(parts, "name") <- "community_site_parts" |
| 45 | 3x |
parts$uid <- caller$uid |
| 46 | 3x |
pid <- paste0("panel", parts$uid)
|
| 47 | 3x |
elements <- substitute(...()) |
| 48 | 3x |
footer <- if (missing(foot)) NULL else substitute(foot) |
| 49 | 3x |
n <- length(elements) |
| 50 | 3x |
wraps <- rep_len(wraps, n) |
| 51 | 3x |
sizes <- rep_len(sizes, n) |
| 52 | 3x |
breakpoints <- rep_len(breakpoints, n) |
| 53 | 3x |
conditions <- rep_len(conditions, n) |
| 54 | 3x |
ids <- paste0("panel", parts$uid, seq_len(n))
|
| 55 | 3x |
title <- substitute(title) |
| 56 | 3x |
r <- c( |
| 57 | 3x |
paste0('<div class="card panel panel-', position, '" id="', pid, '">'),
|
| 58 | 3x |
paste0( |
| 59 | 3x |
c('<div class="card-header">', eval(title, parts, caller), "</div>"),
|
| 60 | 3x |
collapse = "" |
| 61 |
), |
|
| 62 | 3x |
'<div class="card-body">', |
| 63 | 3x |
unlist( |
| 64 | 3x |
lapply(seq_len(n), function(i) {
|
| 65 | 3x |
wrap <- !is.na(wraps[i]) || conditions[i] != "" |
| 66 | 3x |
c( |
| 67 | 3x |
if (wrap) {
|
| 68 | ! |
paste( |
| 69 | ! |
c( |
| 70 | ! |
'<div class="', |
| 71 | ! |
if (is.na(wraps[i])) "" else wraps[i], |
| 72 | ! |
if (!is.na(breakpoints[i])) c("-", breakpoints[i]),
|
| 73 | ! |
if (!is.na(sizes[i])) c("-", sizes[i]),
|
| 74 |
'"', |
|
| 75 | ! |
if (conditions[i] != "") c(' id="', ids[i], '"'),
|
| 76 |
">" |
|
| 77 |
), |
|
| 78 | ! |
collapse = "" |
| 79 |
) |
|
| 80 |
}, |
|
| 81 | 3x |
eval(elements[[i]], parts, caller), |
| 82 | 3x |
if (wrap) "</div>" |
| 83 |
) |
|
| 84 |
}), |
|
| 85 | 3x |
use.names = FALSE |
| 86 |
), |
|
| 87 | 3x |
"</div>", |
| 88 | 3x |
if (length(footer)) {
|
| 89 | ! |
c( |
| 90 | ! |
'<div class="card-footer">', |
| 91 | ! |
unlist( |
| 92 | ! |
lapply(if (is.list(footer)) footer else list(footer), eval, parts), |
| 93 | ! |
use.names = FALSE |
| 94 |
), |
|
| 95 | ! |
"</div>" |
| 96 |
) |
|
| 97 |
}, |
|
| 98 | 3x |
paste0( |
| 99 | 3x |
'<button type="button" title="toggle panel" aria-controls="', |
| 100 | 3x |
pid, |
| 101 | 3x |
'" aria-expanded="true" class="btn panel-toggle">‖</button>' |
| 102 |
), |
|
| 103 | 3x |
"</div>" |
| 104 |
) |
|
| 105 | 3x |
if (building) {
|
| 106 | 1x |
caller$body <- c(caller$body, r) |
| 107 | 1x |
for (n in names(parts)) {
|
| 108 | 2x |
if (n != "content" && n != "uid") {
|
| 109 | ! |
caller[[n]] <- c(caller[[n]], parts[[n]]) |
| 110 |
} |
|
| 111 |
} |
|
| 112 | 1x |
process_conditions(conditions, ids, caller) |
| 113 | 1x |
caller$uid <- parts$uid + 1 |
| 114 |
} |
|
| 115 | 3x |
r |
| 116 |
} |
| 1 |
#' Add a single switch or checkbox to a website |
|
| 2 |
#' |
|
| 3 |
#' Adds a single toggle, displayed as a switch or checkbox to a website. |
|
| 4 |
#' |
|
| 5 |
#' @param label Label of the input for the user. |
|
| 6 |
#' @param id Unique id of the element to be created. |
|
| 7 |
#' @param ... Additional attributes to set on the element. |
|
| 8 |
#' @param note Text to display as a tooltip for the input. |
|
| 9 |
#' @param default_on Logical; if \code{TRUE}, the switch will start on.
|
|
| 10 |
#' @param as.checkbox Logical; if \code{TRUE}, display the switch as a checkbox.
|
|
| 11 |
#' @examples |
|
| 12 |
#' \dontrun{
|
|
| 13 |
#' input_switch("Label")
|
|
| 14 |
#' } |
|
| 15 |
#' @return A character vector of the contents to be added. |
|
| 16 |
#' @seealso For a group of switches, checkboxes, or radio buttons, use \code{\link{input_checkbox}}.
|
|
| 17 |
#' @export |
|
| 18 | ||
| 19 |
input_switch <- function( |
|
| 20 |
label, |
|
| 21 |
id = label, |
|
| 22 |
..., |
|
| 23 |
note = NULL, |
|
| 24 |
default_on = FALSE, |
|
| 25 |
as.checkbox = FALSE |
|
| 26 |
) {
|
|
| 27 | 3x |
id <- gsub("\\s", "", id)
|
| 28 | 3x |
a <- list(...) |
| 29 | 3x |
r <- c( |
| 30 | 3x |
paste0( |
| 31 | 3x |
'<div class="wrapper switch-wrapper"', |
| 32 | 3x |
if (length(a)) {
|
| 33 | ! |
unlist(lapply( |
| 34 | ! |
seq_along(a), |
| 35 | ! |
function(i) paste0(" ", names(a)[i], '="', a[[i]], '"')
|
| 36 |
)) |
|
| 37 |
}, |
|
| 38 |
">" |
|
| 39 |
), |
|
| 40 | 3x |
paste0('<div class="form-check', if (!as.checkbox) " form-switch", '">'),
|
| 41 | 3x |
paste0( |
| 42 | 3x |
'<label class="form-check-label" for="', |
| 43 | 3x |
id, |
| 44 |
'">', |
|
| 45 | 3x |
label, |
| 46 | 3x |
"</label>" |
| 47 |
), |
|
| 48 | 3x |
paste0( |
| 49 | 3x |
'<input data-autoType="switch" type="checkbox" autocomplete="off"', |
| 50 | 3x |
' class="auto-input form-check-input"', |
| 51 | 3x |
if (!as.checkbox) ' role="switch"', |
| 52 | 3x |
' id="', |
| 53 | 3x |
id, |
| 54 |
'"', |
|
| 55 | 3x |
if (!is.null(note)) paste0(' aria-description="', note, '"'),
|
| 56 | 3x |
if (default_on) " checked", |
| 57 |
">" |
|
| 58 |
), |
|
| 59 | 3x |
"</div>", |
| 60 | 3x |
"</div>" |
| 61 |
) |
|
| 62 | 3x |
caller <- parent.frame() |
| 63 |
if ( |
|
| 64 | 3x |
!is.null(attr(caller, "name")) && |
| 65 | 3x |
attr(caller, "name") == "community_site_parts" |
| 66 |
) {
|
|
| 67 | 1x |
caller$content <- c(caller$content, r) |
| 68 |
} |
|
| 69 | 3x |
r |
| 70 |
} |
| 1 |
#' Refresh Data Commons Repositories |
|
| 2 |
#' |
|
| 3 |
#' Clone and/or pull repositories that are part of a data commons project. |
|
| 4 |
#' |
|
| 5 |
#' @param dir Directory of the data commons projects, as created by \code{\link{init_datacommons}}.
|
|
| 6 |
#' @param clone_method Means of cloning new repositories; either \code{"http"} (default) or \code{"ssh"}.
|
|
| 7 |
#' @param include_distributions Logical; if \code{TRUE}, will attempt to locate and cache copies of datasets
|
|
| 8 |
#' pointed to from the data repositories (so far just from Dataverse, implicitly from DOI files). |
|
| 9 |
#' @param refresh_distributions Logical; if \code{TRUE}, will download fresh copies of the distribution metadata.
|
|
| 10 |
#' @param only_new Logical; if \code{TRUE}, only repositories that do not yet exist will be processed.
|
|
| 11 |
#' @param reset_repos Logical; if \code{TRUE}, will fetch and hard reset the repositories to remove any local changes.
|
|
| 12 |
#' @param reset_on_fail Logical; if \code{TRUE}, will reset only if a regular pull fails.
|
|
| 13 |
#' @param rescan_only Logical; if \code{TRUE}, will only read the files that are already in place, without checking for
|
|
| 14 |
#' updates from the remote repository. |
|
| 15 |
#' @param run_checks Logical; if \code{FALSE}, will not run \code{\link{check_repository}} on each repository.
|
|
| 16 |
#' @param dataset_map A named vector of ID to dataset mappings to pass to \code{\link{check_repository}}
|
|
| 17 |
#' if \code{run_checks} is \code{TRUE}.
|
|
| 18 |
#' @param force_value_check Logical; if \code{TRUE}, will always intensively check values, even on large files.
|
|
| 19 |
#' @param verbose Logical; if \code{FALSE}, will not show updated repositories.
|
|
| 20 |
#' @examples |
|
| 21 |
#' \dontrun{
|
|
| 22 |
#' # refresh from a data commons working directory |
|
| 23 |
#' datacommons_refresh(".")
|
|
| 24 |
#' } |
|
| 25 |
#' @return An invisible character vector of updated repositories. |
|
| 26 |
#' @export |
|
| 27 | ||
| 28 |
datacommons_refresh <- function( |
|
| 29 |
dir, |
|
| 30 |
clone_method = "http", |
|
| 31 |
include_distributions = FALSE, |
|
| 32 |
refresh_distributions = FALSE, |
|
| 33 |
only_new = FALSE, |
|
| 34 |
reset_repos = FALSE, |
|
| 35 |
reset_on_fail = FALSE, |
|
| 36 |
rescan_only = FALSE, |
|
| 37 |
run_checks = TRUE, |
|
| 38 |
dataset_map = "region_type", |
|
| 39 |
force_value_check = FALSE, |
|
| 40 |
verbose = TRUE |
|
| 41 |
) {
|
|
| 42 | 1x |
if (missing(dir)) {
|
| 43 | ! |
cli_abort('{.arg dir} must be specified (e.g., as ".")')
|
| 44 |
} |
|
| 45 | 1x |
if (Sys.which("git") == "") {
|
| 46 | ! |
cli_abort(c( |
| 47 | ! |
x = "the {.emph git} command could not be located",
|
| 48 | ! |
i = "you might need to install git: {.url https://git-scm.com/downloads}"
|
| 49 |
)) |
|
| 50 |
} |
|
| 51 | 1x |
check <- check_template("datacommons", dir = dir)
|
| 52 | 1x |
if (!check$exists) {
|
| 53 | ! |
cli_abort(c( |
| 54 | ! |
x = "{.arg dir} does not appear to point to a data commons project",
|
| 55 | ! |
i = paste0('initialize it with {.code init_datacommons("', dir, '")}')
|
| 56 |
)) |
|
| 57 |
} |
|
| 58 | 1x |
dir <- normalizePath(dir, "/", FALSE) |
| 59 | 1x |
commons <- jsonlite::read_json(paste0(dir, "/commons.json")) |
| 60 | 1x |
repos <- sort(unique(unlist(Filter( |
| 61 | 1x |
length, |
| 62 | 1x |
c( |
| 63 | 1x |
commons$repositories, |
| 64 | 1x |
readLines(paste0(dir, "/scripts/repos.txt")) |
| 65 |
) |
|
| 66 |
)))) |
|
| 67 | 1x |
if (!length(repos)) {
|
| 68 | ! |
cli_abort("no repositories are listed in {.file commons.json}.")
|
| 69 |
} |
|
| 70 | 1x |
repos <- gsub("^[\"']+|['\"]+$|^.*github\\.com/", "", repos)
|
| 71 | 1x |
su <- !grepl("/", repos, fixed = TRUE)
|
| 72 | 1x |
if (any(su)) {
|
| 73 | ! |
repos <- repos[su] |
| 74 | ! |
cli_abort("repo{?s are/ is} missing a username prefix: {.files {repos}}")
|
| 75 |
} |
|
| 76 | 1x |
repos <- sub("^([^/]+/[^/#@]+)[^/]*$", "\\1", repos)
|
| 77 | 1x |
if (!identical(unlist(commons$repositories, use.names = FALSE), repos)) {
|
| 78 | ! |
commons$repositories <- repos |
| 79 | ! |
jsonlite::write_json( |
| 80 | ! |
commons, |
| 81 | ! |
paste0(dir, "/commons.json"), |
| 82 | ! |
auto_unbox = TRUE, |
| 83 | ! |
pretty = TRUE |
| 84 |
) |
|
| 85 |
} |
|
| 86 | 1x |
writeLines(repos, paste0(dir, "/scripts/repos.txt")) |
| 87 | 1x |
if (only_new) {
|
| 88 | ! |
repos <- repos[!file.exists(paste0(dir, "/repos/", sub("^.*/", "", repos)))]
|
| 89 | ! |
if (!length(repos)) {
|
| 90 | ! |
if (verbose) {
|
| 91 | ! |
cli_alert_success("no new repositories")
|
| 92 |
} |
|
| 93 | ! |
return(invisible(repos)) |
| 94 |
} |
|
| 95 |
} |
|
| 96 | 1x |
updated <- dist_updated <- failed <- logical(length(repos)) |
| 97 | 1x |
wd <- getwd() |
| 98 | 1x |
on.exit(setwd(wd)) |
| 99 | 1x |
repo_dir <- paste0(normalizePath(paste0(dir, "/repos/"), "/", FALSE), "/") |
| 100 | 1x |
dir.create(repo_dir, FALSE, TRUE) |
| 101 | 1x |
setwd(repo_dir) |
| 102 | 1x |
method <- if (clone_method == "ssh") {
|
| 103 | ! |
"git@github.com:" |
| 104 |
} else {
|
|
| 105 | 1x |
"https://github.com/" |
| 106 |
} |
|
| 107 | 1x |
if (include_distributions) {
|
| 108 | ! |
dir.create(paste0(dir, "/cache"), FALSE) |
| 109 |
} |
|
| 110 | 1x |
manifest_file <- paste0(dir, "/manifest/repos.json") |
| 111 | 1x |
repo_manifest <- list() |
| 112 | 1x |
for (i in seq_along(repos)) {
|
| 113 | 1x |
r <- repos[[i]] |
| 114 | 1x |
rn <- sub("^.*/", "", r)
|
| 115 | 1x |
cr <- paste0(repo_dir, rn, "/") |
| 116 | 1x |
if (!rescan_only) {
|
| 117 | 1x |
change_dir <- dir.exists(rn) |
| 118 | 1x |
if (verbose) {
|
| 119 | ! |
cli_alert_info(paste(if (change_dir) "pulling" else "cloning", rn)) |
| 120 |
} |
|
| 121 | 1x |
if (change_dir) {
|
| 122 | ! |
setwd(cr) |
| 123 |
} |
|
| 124 | 1x |
s <- tryCatch( |
| 125 | 1x |
if (change_dir) {
|
| 126 | ! |
if (reset_repos || reset_on_fail) {
|
| 127 | ! |
attempt <- if (reset_on_fail) {
|
| 128 | ! |
system2("git", "pull", stdout = TRUE)
|
| 129 |
} else {
|
|
| 130 | ! |
NULL |
| 131 |
} |
|
| 132 | ! |
if (!is.null(attr(attempt, "status"))) {
|
| 133 | ! |
system2("git", "clean --f", stdout = TRUE)
|
| 134 | ! |
system2("git", "fetch", stdout = TRUE)
|
| 135 | ! |
system2("git", "reset --hard FETCH_HEAD", stdout = TRUE)
|
| 136 |
} else {
|
|
| 137 | ! |
attempt |
| 138 |
} |
|
| 139 |
} else {
|
|
| 140 | ! |
system2("git", "pull", stdout = TRUE)
|
| 141 |
} |
|
| 142 |
} else {
|
|
| 143 | 1x |
system2("git", c("clone", paste0(method, r, ".git")), stdout = TRUE)
|
| 144 |
}, |
|
| 145 | 1x |
error = function(e) e$message |
| 146 |
) |
|
| 147 | 1x |
if (change_dir) {
|
| 148 | ! |
setwd(repo_dir) |
| 149 |
} |
|
| 150 | 1x |
if (length(s) != 1 || s != "Already up to date.") {
|
| 151 | 1x |
if (!is.null(attr(s, "status"))) {
|
| 152 | ! |
failed[i] <- TRUE |
| 153 | ! |
cli_alert_warning(c( |
| 154 | ! |
x = paste0("failed to retrieve ", r, ": ", paste(s, collapse = " "))
|
| 155 |
)) |
|
| 156 |
} else {
|
|
| 157 | 1x |
updated[i] <- TRUE |
| 158 |
} |
|
| 159 | ! |
} else if (!length(list.files(rn))) {
|
| 160 | ! |
system2("rm", c("-rf", rn))
|
| 161 |
} |
|
| 162 |
} |
|
| 163 | 1x |
repo_manifest[[r]]$base_url <- get_git_remote(paste0(cr, ".git/config")) |
| 164 | 1x |
files <- sort(list.files( |
| 165 | 1x |
cr, |
| 166 | 1x |
"\\.(?:csv|tsv|txt|dat|rda|rdata)(?:\\.[gbx]z2?)?$", |
| 167 | 1x |
full.names = TRUE, |
| 168 | 1x |
recursive = TRUE, |
| 169 | 1x |
ignore.case = TRUE |
| 170 |
)) |
|
| 171 | 1x |
files <- normalizePath(files, "/") |
| 172 | 1x |
for (f in files) {
|
| 173 | 7x |
repo_manifest[[r]]$files[[sub("^.*/repos/[^/]+/", "", f)]] <- list(
|
| 174 | 7x |
size = file.size(f), |
| 175 | 7x |
sha = system2("git", c("hash-object", shQuote(f)), stdout = TRUE),
|
| 176 | 7x |
md5 = md5sum(f)[[1]] |
| 177 |
) |
|
| 178 |
} |
|
| 179 | 1x |
doi <- repo_manifest[[r]]$distributions$dataverse$doi |
| 180 | 1x |
if (include_distributions && !is.null(doi)) {
|
| 181 | ! |
if (verbose) {
|
| 182 | ! |
ul <- cli_ul() |
| 183 | ! |
iul <- cli_ul() |
| 184 | ! |
cli_li("including Dataverse distribution for {.emph {doi}}")
|
| 185 |
} |
|
| 186 | ! |
meta_file <- paste0(dir, "/cache/", rn, "/dataverse_metadata.json") |
| 187 | ! |
meta <- if (!refresh_distributions && file.exists(meta_file)) {
|
| 188 | ! |
jsonlite::read_json(meta_file, simplifyVector = TRUE) |
| 189 |
} else {
|
|
| 190 | ! |
tryCatch( |
| 191 | ! |
download_dataverse_info(doi, refresh = refresh_distributions), |
| 192 | ! |
error = function(e) NULL |
| 193 |
) |
|
| 194 |
} |
|
| 195 | ! |
if (is.null(meta)) {
|
| 196 | ! |
if (verbose) {
|
| 197 | ! |
cli_li(col_red( |
| 198 | ! |
"failed to download Dataverse metadata for {.emph {doi}}"
|
| 199 |
)) |
|
| 200 | ! |
cli_end(iul) |
| 201 | ! |
cli_end(ul) |
| 202 |
} |
|
| 203 |
} else {
|
|
| 204 | ! |
if (is.null(meta$latestVersion)) {
|
| 205 | ! |
meta$latestVersion <- list(files = meta$files) |
| 206 |
} |
|
| 207 | ! |
dir.create(paste0(dir, "/cache/", rn), FALSE) |
| 208 | ! |
jsonlite::write_json(meta, meta_file, auto_unbox = TRUE) |
| 209 | ! |
repo_manifest[[r]]$distributions$dataverse$id <- meta$datasetId |
| 210 | ! |
repo_manifest[[r]]$distributions$dataverse$server <- meta$server |
| 211 | ! |
repo_manifest[[r]]$distributions$dataverse$files <- list() |
| 212 | ! |
if (length(meta$latestVersion$files)) {
|
| 213 | ! |
for (f in meta$latestVersion$files) {
|
| 214 | ! |
existing <- paste0(dir, "/cache/", rn, "/", f$dataFile$filename) |
| 215 | ! |
if (file.exists(existing)) {
|
| 216 | ! |
if (verbose) {
|
| 217 | ! |
cli_li( |
| 218 | ! |
"checking existing version of {.file {f$dataFile$filename}}"
|
| 219 |
) |
|
| 220 |
} |
|
| 221 | ! |
if (md5sum(existing) != f$dataFile$md5) unlink(existing) |
| 222 |
} |
|
| 223 | ! |
if (!file.exists(existing)) {
|
| 224 | ! |
if (verbose) {
|
| 225 | ! |
cli_li("downloading {.file {f$dataFile$filename}}")
|
| 226 |
} |
|
| 227 | ! |
res <- tryCatch( |
| 228 | ! |
download_dataverse_data( |
| 229 | ! |
doi, |
| 230 | ! |
paste0(dir, "/cache/", rn), |
| 231 | ! |
files = f$label, |
| 232 | ! |
load = FALSE, |
| 233 | ! |
decompress = FALSE |
| 234 |
), |
|
| 235 | ! |
error = function(e) NULL |
| 236 |
) |
|
| 237 | ! |
if (is.null(res)) {
|
| 238 | ! |
if (verbose) {
|
| 239 | ! |
cli_li(col_red( |
| 240 | ! |
"failed to download {.file {f$dataFile$filename}}"
|
| 241 |
)) |
|
| 242 |
} |
|
| 243 |
} else {
|
|
| 244 | ! |
dist_updated[i] <- TRUE |
| 245 |
} |
|
| 246 |
} |
|
| 247 | ! |
if (file.exists(existing)) {
|
| 248 | ! |
repo_manifest[[r]]$distributions$dataverse$files[[sub( |
| 249 | ! |
"^.*/cache/[^/]+/", |
| 250 |
"", |
|
| 251 | ! |
existing |
| 252 | ! |
)]] <- list( |
| 253 | ! |
id = f$dataFile$id, |
| 254 | ! |
size = file.size(existing), |
| 255 | ! |
md5 = md5sum(existing)[[1]] |
| 256 |
) |
|
| 257 |
} |
|
| 258 |
} |
|
| 259 |
} |
|
| 260 |
} |
|
| 261 | ! |
if (verbose) {
|
| 262 | ! |
cli_end(iul) |
| 263 | ! |
cli_end(ul) |
| 264 |
} |
|
| 265 |
} |
|
| 266 | 1x |
if (run_checks) {
|
| 267 | 1x |
if (verbose) {
|
| 268 | ! |
cli_progress_step("running checks...", msg_done = "ran checks:")
|
| 269 |
} |
|
| 270 | 1x |
repo_manifest[[r]]$repo_checks <- tryCatch( |
| 271 | 1x |
check_repository(cr, dataset = dataset_map, verbose = FALSE), |
| 272 | 1x |
error = function(e) NULL |
| 273 |
) |
|
| 274 | 1x |
repo_manifest[[r]]$repo_checks <- lapply( |
| 275 | 1x |
repo_manifest[[r]]$repo_checks[ |
| 276 | 1x |
grep( |
| 277 | 1x |
"^summary|^(?:info|warn|fail)_", |
| 278 | 1x |
names(repo_manifest[[r]]$repo_checks) |
| 279 |
) |
|
| 280 |
], |
|
| 281 | 1x |
function(l) {
|
| 282 | 5x |
if (is.character(l)) {
|
| 283 | 1x |
l <- sub("^.*/repos/[^/]+/", "", l)
|
| 284 |
} |
|
| 285 | 5x |
if (!is.null(names(l))) {
|
| 286 | 4x |
names(l) <- sub("^.*/repos/[^/]+/", "", names(l))
|
| 287 |
} |
|
| 288 | 5x |
l |
| 289 |
} |
|
| 290 |
) |
|
| 291 | 1x |
if (verbose) {
|
| 292 | ! |
cli_progress_done() |
| 293 | ! |
if (length(repo_manifest[[r]]$repo_checks$summary)) {
|
| 294 | ! |
print(repo_manifest[[r]]$repo_checks$summary) |
| 295 | ! |
cat("\n")
|
| 296 |
} |
|
| 297 |
} |
|
| 298 |
} |
|
| 299 |
} |
|
| 300 | 1x |
if (verbose) {
|
| 301 | ! |
if (any(updated)) {
|
| 302 | ! |
updated_repos <- repos[updated] |
| 303 | ! |
cli_alert_success( |
| 304 | ! |
"updated data repositor{?ies/y}: {.file {updated_repos}}"
|
| 305 |
) |
|
| 306 |
} |
|
| 307 | ! |
if (any(dist_updated)) {
|
| 308 | ! |
updated_distributions <- repos[dist_updated] |
| 309 | ! |
cli_alert_success( |
| 310 | ! |
"updated distributed file{?s} in: {.file {updated_distributions}}"
|
| 311 |
) |
|
| 312 |
} |
|
| 313 | ! |
if (any(failed)) {
|
| 314 | ! |
failed_repos <- repos[failed] |
| 315 | ! |
cli_alert_danger( |
| 316 | ! |
"failed to retrieve repositor{?ies/y}: {.file {failed_repos}}"
|
| 317 |
) |
|
| 318 | ! |
} else if (!any(updated | dist_updated)) {
|
| 319 | ! |
cli_alert_success("all data repositories are up to date")
|
| 320 |
} |
|
| 321 |
} |
|
| 322 | 1x |
if (length(repo_manifest)) {
|
| 323 | 1x |
su <- names(repo_manifest) %in% repos |
| 324 | 1x |
if (any(su)) {
|
| 325 | 1x |
jsonlite::write_json(repo_manifest[su], manifest_file, auto_unbox = TRUE) |
| 326 |
} else {
|
|
| 327 | ! |
cli_warn("no repos were found in the existing repo manifest")
|
| 328 |
} |
|
| 329 |
} |
|
| 330 | 1x |
init_datacommons(dir, refresh_after = FALSE, verbose = FALSE) |
| 331 | 1x |
invisible(repos[updated | dist_updated]) |
| 332 |
} |
| 1 |
#' Adds an organizational section to a website |
|
| 2 |
#' |
|
| 3 |
#' Adds a section (such as a row or column) to a website, optionally placing its elements within rows or columns. |
|
| 4 |
#' |
|
| 5 |
#' @param ... Elements to appear in the section. |
|
| 6 |
#' @param type The class of the top-level section; usually either \code{"row"} or \code{"col"};
|
|
| 7 |
#' \code{NULL} for no class.
|
|
| 8 |
#' @param wraps The class of wrapper to place elements in; either \code{"row"}, \code{"col"}, or \code{""}
|
|
| 9 |
#' (to not wrap the element). Can specify 1 for every element, or a different class for each element. |
|
| 10 |
#' @param sizes The relative size of each wrapper, between 1 and 12, or \code{"auto"}; default is equal size.
|
|
| 11 |
#' @param breakpoints Bootstrap breakpoint of each wrapper; one of \code{""} (extra small), \code{"sm"},
|
|
| 12 |
#' \code{"md"}, \code{"lg"}, \code{"xl"}, or \code{"xxl"}.
|
|
| 13 |
#' @param conditions A character for each element representing the conditions in which that should be showing |
|
| 14 |
#' (e.g., \code{c("", "input_a == a", "")}); \code{""} means the element's display is not conditional.
|
|
| 15 |
#' Adding \code{"lock: "} before the condition will disable inputs rather than hide the element.
|
|
| 16 |
#' @param id Unique ID of the section. |
|
| 17 |
#' @details See the \href{https://getbootstrap.com/docs/5.1/layout/grid}{Bootstrap grid documentation}.
|
|
| 18 |
#' @examples |
|
| 19 |
#' \dontrun{
|
|
| 20 |
#' page_section( |
|
| 21 |
#' "<p>column</p>", |
|
| 22 |
#' "<p>row</p>", |
|
| 23 |
#' type = "row", |
|
| 24 |
#' wraps = c("col", "row")
|
|
| 25 |
#' ) |
|
| 26 |
#' } |
|
| 27 |
#' @return A character vector of the content to be added. |
|
| 28 |
#' @export |
|
| 29 | ||
| 30 |
page_section <- function( |
|
| 31 |
..., |
|
| 32 |
type = "row", |
|
| 33 |
wraps = NA, |
|
| 34 |
sizes = NA, |
|
| 35 |
breakpoints = NA, |
|
| 36 |
conditions = "", |
|
| 37 |
id = NULL |
|
| 38 |
) {
|
|
| 39 | 4x |
caller <- parent.frame() |
| 40 | 4x |
building <- !is.null(attr(caller, "name")) && |
| 41 | 4x |
attr(caller, "name") == "community_site_parts" |
| 42 | 4x |
parts <- new.env() |
| 43 | 4x |
attr(parts, "name") <- "community_site_parts" |
| 44 | 4x |
parts$uid <- caller$uid |
| 45 | 4x |
elements <- substitute(...()) |
| 46 | 4x |
n <- length(elements) |
| 47 | 4x |
wraps <- rep_len(wraps, n) |
| 48 | 4x |
sizes <- rep_len(sizes, n) |
| 49 | 4x |
breakpoints <- rep_len(breakpoints, n) |
| 50 | 4x |
conditions <- rep_len(conditions, n) |
| 51 | 4x |
ids <- paste0("sec", parts$uid, seq_len(n))
|
| 52 | 4x |
r <- c( |
| 53 | 4x |
paste( |
| 54 | 4x |
c( |
| 55 | 4x |
"<div", |
| 56 | 4x |
if (!is.null(id)) c(' id="', id, '"'),
|
| 57 | 4x |
if (!is.null(type)) c(' class="', type, '"'),
|
| 58 |
">" |
|
| 59 |
), |
|
| 60 | 4x |
collapse = "" |
| 61 |
), |
|
| 62 | 4x |
unlist( |
| 63 | 4x |
lapply(seq_len(n), function(i) {
|
| 64 | 5x |
wrap <- !is.na(wraps[i]) || conditions[i] != "" |
| 65 | 5x |
c( |
| 66 | 5x |
if (wrap) {
|
| 67 | 2x |
paste( |
| 68 | 2x |
c( |
| 69 | 2x |
'<div class="', |
| 70 | 2x |
if (is.na(wraps[i])) "" else wraps[i], |
| 71 | 2x |
if (!is.na(breakpoints[i])) c("-", breakpoints[i]),
|
| 72 | 2x |
if (!is.na(sizes[i])) c("-", sizes[i]),
|
| 73 |
'"', |
|
| 74 | 2x |
if (conditions[i] != "") paste0(' id="', ids[i], '"'),
|
| 75 |
">" |
|
| 76 |
), |
|
| 77 | 2x |
collapse = "" |
| 78 |
) |
|
| 79 |
}, |
|
| 80 | 5x |
eval(elements[[i]], parts, caller), |
| 81 | 5x |
if (wrap) "</div>" |
| 82 |
) |
|
| 83 |
}), |
|
| 84 | 4x |
use.names = FALSE |
| 85 |
), |
|
| 86 | 4x |
"</div>" |
| 87 |
) |
|
| 88 | 4x |
if (building) {
|
| 89 | 2x |
caller$content <- c(caller$content, r) |
| 90 | 2x |
for (n in names(parts)) {
|
| 91 | 8x |
if (n != "content" && n != "uid") {
|
| 92 | 4x |
caller[[n]] <- c(caller[[n]], parts[[n]]) |
| 93 |
} |
|
| 94 |
} |
|
| 95 | 2x |
process_conditions(conditions, ids, caller) |
| 96 | 2x |
caller$uid <- parts$uid + 1 |
| 97 |
} |
|
| 98 | 4x |
r |
| 99 |
} |
| 1 |
#' Attempt to locate variables in a set of mapped variables. |
|
| 2 |
#' |
|
| 3 |
#' Somewhat fuzzily match entered variable names to mapped variable names, which |
|
| 4 |
#' might be useful if variable names are specified in a view, but are changed slightly |
|
| 5 |
#' in their source repositories. |
|
| 6 |
#' |
|
| 7 |
#' @param missed A vector of variable names or keywords to search for in the full set of mapped variables, |
|
| 8 |
#' or the name of or path to a data commons view, from which to extract missed variables. |
|
| 9 |
#' @param map Path to the \code{variable_map.csv} file created by \code{\link{datacommons_map_files}},
|
|
| 10 |
#' the path to a data commons project, or a variable map \code{data.frame}.
|
|
| 11 |
#' @param sep A regular expression to be treated as a term separator. |
|
| 12 |
#' @param top Number of possible matches to return per \code{missed} entry.
|
|
| 13 |
#' @param metric Name of the similarity metric to use; see \code{\link[lingmatch]{lma_simets}}.
|
|
| 14 |
#' @examples |
|
| 15 |
#' \dontrun{
|
|
| 16 |
#' # from a data commons project directory |
|
| 17 |
#' datacommons_find_variables(c("variable_a", "variable_b"))
|
|
| 18 |
#' |
|
| 19 |
#' # try to find matches to any missed variables in a view |
|
| 20 |
#' datacommons_find_variables("view_name")
|
|
| 21 |
#' } |
|
| 22 |
#' @return A list with an entry for each entered variable, containing \code{top} possible matches,
|
|
| 23 |
#' which are entries from the variable map, with an added . |
|
| 24 |
#' @export |
|
| 25 | ||
| 26 |
datacommons_find_variables <- function( |
|
| 27 |
missed, |
|
| 28 |
map = ".", |
|
| 29 |
sep = "[_:]", |
|
| 30 |
top = 3, |
|
| 31 |
metric = "cosine" |
|
| 32 |
) {
|
|
| 33 | 1x |
if (missing(missed)) {
|
| 34 | ! |
cli_abort("{.arg missed} must be provided")
|
| 35 |
} |
|
| 36 | 1x |
nm <- length(missed) |
| 37 | 1x |
variable_map <- NULL |
| 38 | 1x |
if (is.character(map)) {
|
| 39 | 1x |
if (file.exists(map)) {
|
| 40 | 1x |
variable_map <- if (dir.exists(map)) {
|
| 41 | 1x |
if (nm == 1 && file.exists(paste0(map, "/views/", missed))) {
|
| 42 | ! |
missed <- paste0(map, "/views/", missed) |
| 43 |
} |
|
| 44 | 1x |
datacommons_map_files(map, verbose = FALSE)$variables |
| 45 |
} else {
|
|
| 46 | ! |
read.csv(map) |
| 47 |
} |
|
| 48 |
} else {
|
|
| 49 | ! |
cli_abort("{.arg map} appears to be a path, but it does not exist")
|
| 50 |
} |
|
| 51 |
} else {
|
|
| 52 | ! |
variable_map <- map |
| 53 |
} |
|
| 54 | 1x |
if (is.null(variable_map$full_name)) {
|
| 55 | ! |
cli_abort( |
| 56 | ! |
"{.arg map} does not appear to be or point to a valid variable map"
|
| 57 |
) |
|
| 58 |
} |
|
| 59 | 1x |
full_names <- unique(variable_map$full_name) |
| 60 | 1x |
if (nm == 1 && file.exists(missed)) {
|
| 61 | ! |
missed <- jsonlite::read_json( |
| 62 | ! |
if (dir.exists(missed)) paste0(missed, "/view.json") else missed |
| 63 |
) |
|
| 64 | ! |
missed <- as.character(missed$variables) |
| 65 | ! |
if (!length(missed)) {
|
| 66 | ! |
cli_abort( |
| 67 | ! |
"did not find any variables in the {.arg missed} view definition"
|
| 68 |
) |
|
| 69 |
} |
|
| 70 | ! |
missed <- missed[!missed %in% full_names] |
| 71 | ! |
if (!length(missed)) {
|
| 72 | ! |
cli_abort("all variables in the {.arg missed} view definition were found")
|
| 73 |
} |
|
| 74 | ! |
nm <- length(missed) |
| 75 |
} |
|
| 76 | 1x |
mi <- seq_len(nm) |
| 77 | 1x |
snames <- gsub(sep, " ", c(missed, full_names)) |
| 78 | 1x |
dtm <- lma_dtm(snames, numbers = TRUE, punct = TRUE, to.lower = FALSE) |
| 79 | 1x |
sim <- lma_simets(dtm[mi, ], dtm[-mi, ], metric, pairwise = FALSE) |
| 80 | 1x |
if (is.null(dim(sim))) {
|
| 81 | 1x |
sim <- matrix(sim, nm) |
| 82 |
} |
|
| 83 | 1x |
top <- seq_len(min(top, length(full_names))) |
| 84 | 1x |
res <- lapply(mi, function(i) {
|
| 85 | 1x |
v <- missed[[i]] |
| 86 | 1x |
if (v %in% full_names) {
|
| 87 | ! |
cbind(variable_map[variable_map$full_name == v, ], similarity = 1) |
| 88 |
} else {
|
|
| 89 | 1x |
do.call( |
| 90 | 1x |
rbind, |
| 91 | 1x |
lapply(order(sim[i, ], decreasing = TRUE)[top], function(o) {
|
| 92 | 3x |
vr <- variable_map[ |
| 93 | 3x |
variable_map$full_name == full_names[[o]], |
| 94 |
, |
|
| 95 | 3x |
drop = FALSE |
| 96 |
] |
|
| 97 | 3x |
vr$similarity <- sim[i, o] |
| 98 | 3x |
vr |
| 99 |
}) |
|
| 100 |
) |
|
| 101 |
} |
|
| 102 |
}) |
|
| 103 | 1x |
names(res) <- missed |
| 104 | 1x |
res |
| 105 |
} |
| 1 |
#' Format a JavaScript Color Palette |
|
| 2 |
#' |
|
| 3 |
#' Make a specially-formatted color palette based on color codes. |
|
| 4 |
#' |
|
| 5 |
#' @param colors A vector of color names or HEX codes, or a matrix-like object with |
|
| 6 |
#' colors in columns, and their RGB values in separate rows. |
|
| 7 |
#' @param continuous Logical; if \code{TRUE}, \code{colors} are treated as points in a linear
|
|
| 8 |
#' gradient. One provided color will be from white to that color. Two provided colors will |
|
| 9 |
#' be between those colors. Three or four provided colors will be between the first and |
|
| 10 |
#' last color, with the central color (or average of the central colors) as the midpoint. |
|
| 11 |
#' @param divergent Logical; if \code{TRUE}, marks continuous scales as divergent,
|
|
| 12 |
#' which will reverse the lower half of the scale. |
|
| 13 |
#' @param polynomial Logical; if \code{TRUE}, will fit a polynomial regression model to each color
|
|
| 14 |
#' channel in the specified \code{colors} sequence. Used to either compress a long sequence
|
|
| 15 |
#' (e.g., model a fully manually specified scale), or interpolate a scale between anchors. |
|
| 16 |
#' @param degrees Number of polynomial degrees, if \code{polynomial} is \code{TRUE}.
|
|
| 17 |
#' @param pad If \code{polynomial} is \code{TRUE}, number of repeated observations of the
|
|
| 18 |
#' initial and final colors in the sequence to add in order to reduce warping at the edges. |
|
| 19 |
#' @param name Name of the palette. |
|
| 20 |
#' @param preview Logical; if \code{TRUE}, makes a plot showing the palette colors / scale.
|
|
| 21 |
#' @param print Logical; if \code{FALSE}, will not print a version of the palette.
|
|
| 22 |
#' @examples |
|
| 23 |
#' # a discrete palette |
|
| 24 |
#' util_make_palette(c("red", "green", "blue"), FALSE)
|
|
| 25 |
#' |
|
| 26 |
#' # a continuous palette |
|
| 27 |
#' util_make_palette("red")
|
|
| 28 |
#' |
|
| 29 |
#' # a divergent continuous palette |
|
| 30 |
#' util_make_palette(c("red", "green"), divergent = TRUE)
|
|
| 31 |
#' @return An invisible list of the created palette. |
|
| 32 |
#' @export |
|
| 33 | ||
| 34 |
util_make_palette <- function( |
|
| 35 |
colors, |
|
| 36 |
continuous = length(colors) < 5, |
|
| 37 |
divergent = length(colors) > 2, |
|
| 38 |
polynomial = FALSE, |
|
| 39 |
degrees = 6, |
|
| 40 |
pad = 10, |
|
| 41 |
name = "custom", |
|
| 42 |
preview = TRUE, |
|
| 43 |
print = TRUE |
|
| 44 |
) {
|
|
| 45 | 7x |
if (missing(polynomial) && (!missing(degrees) || !missing(pad))) {
|
| 46 | ! |
polynomial <- TRUE |
| 47 |
} |
|
| 48 | 7x |
if (polynomial) {
|
| 49 | 1x |
if (missing(divergent)) {
|
| 50 | 1x |
divergent <- FALSE |
| 51 |
} |
|
| 52 | 1x |
if (!missing(continuous) && !continuous) {
|
| 53 | ! |
cli_alert_warning( |
| 54 | ! |
"{.arg polynomial} if {.val TRUE}, so {.arg continuous} will also be {.val TRUE}"
|
| 55 |
) |
|
| 56 |
} |
|
| 57 | 1x |
continuous <- TRUE |
| 58 |
} |
|
| 59 | 7x |
if (is.character(colors)) {
|
| 60 | 6x |
cols <- col2rgb(colors) |
| 61 |
} else {
|
|
| 62 | 1x |
cols <- colors |
| 63 | 1x |
if (is.null(dim(cols))) {
|
| 64 | 1x |
cols <- if (is.list(cols)) {
|
| 65 | ! |
as.data.frame(cols) |
| 66 |
} else {
|
|
| 67 | 1x |
matrix(cols, 3, dimnames = list(c("red", "green", "blue")))
|
| 68 |
} |
|
| 69 | ! |
} else if (ncol(cols) == 3 && nrow(cols) != 3) {
|
| 70 | ! |
cols <- t(cols) |
| 71 |
} |
|
| 72 |
} |
|
| 73 | 7x |
if (nrow(cols) != 3) {
|
| 74 | ! |
cli_abort("{.arg colors} could not be resolved to a matrix of RGB vectors")
|
| 75 |
} |
|
| 76 | 7x |
palette <- if (continuous) {
|
| 77 | 4x |
if (polynomial) {
|
| 78 | 1x |
rownames(cols) <- c("red", "green", "blue")
|
| 79 | 1x |
colnames(cols) <- NULL |
| 80 | 1x |
x <- seq.int(0, 1, length.out = ncol(cols)) |
| 81 | 1x |
if (max(cols) <= 1) {
|
| 82 | ! |
cols <- cols * 256 |
| 83 |
} |
|
| 84 | 1x |
ori <- list(x = x, cols = cols) |
| 85 | 1x |
if (is.numeric(pad) && pad > 0) {
|
| 86 | 1x |
x <- c(numeric(pad), x, rep(1, pad)) |
| 87 | 1x |
cols <- cbind( |
| 88 | 1x |
matrix( |
| 89 | 1x |
rep(as.numeric(cols[, 1]), pad), |
| 90 | 1x |
3, |
| 91 | 1x |
dimnames = list(rownames(cols)) |
| 92 |
), |
|
| 93 | 1x |
cols, |
| 94 | 1x |
matrix( |
| 95 | 1x |
rep(as.numeric(cols[, ncol(cols)]), pad), |
| 96 | 1x |
3, |
| 97 | 1x |
dimnames = list(rownames(cols)) |
| 98 |
) |
|
| 99 |
) |
|
| 100 |
} |
|
| 101 | 1x |
coefs <- vapply( |
| 102 | 1x |
1:3, |
| 103 | 1x |
function(ch) {
|
| 104 | 3x |
as.numeric( |
| 105 | 3x |
lm( |
| 106 | 3x |
cols[ch, ] ~ poly(x, degree = degrees, raw = TRUE, simple = TRUE) |
| 107 | 3x |
)$coefficients |
| 108 |
) |
|
| 109 |
}, |
|
| 110 | 1x |
numeric(degrees + 1) |
| 111 |
) |
|
| 112 | 1x |
if (anyNA(coefs)) {
|
| 113 | ! |
cli_abort( |
| 114 | ! |
"this combination of inputs resulted in missing coefficient estimates" |
| 115 |
) |
|
| 116 |
} |
|
| 117 | 1x |
if (preview) {
|
| 118 | 1x |
mm <- cbind(1, poly(ori$x, degrees, raw = TRUE)) |
| 119 | 1x |
plot( |
| 120 | 1x |
NA, |
| 121 | 1x |
xlim = c(0, 1), |
| 122 | 1x |
ylim = c(0, 1), |
| 123 | 1x |
axes = FALSE, |
| 124 | 1x |
pch = 15, |
| 125 | 1x |
cex = 2, |
| 126 | 1x |
main = "Palette Comparison", |
| 127 | 1x |
ylab = "Palette", |
| 128 | 1x |
xlab = "Value" |
| 129 |
) |
|
| 130 | 1x |
mtext(paste0("Degrees: ", degrees, ", Padding: ", pad), 3)
|
| 131 | 1x |
axis(1) |
| 132 | 1x |
axis(2, c(.70, .30), c("Original", "Derived"), lwd = 0)
|
| 133 | 1x |
n <- length(ori$x) |
| 134 | 1x |
points( |
| 135 | 1x |
ori$x, |
| 136 | 1x |
rep(.70, n), |
| 137 | 1x |
pch = "|", |
| 138 | 1x |
cex = 7, |
| 139 | 1x |
col = do.call(rgb, as.data.frame(t(ori$cols) / 256)) |
| 140 |
) |
|
| 141 | 1x |
points( |
| 142 | 1x |
ori$x, |
| 143 | 1x |
rep(.30, n), |
| 144 | 1x |
pch = "|", |
| 145 | 1x |
cex = 7, |
| 146 | 1x |
col = do.call( |
| 147 | 1x |
rgb, |
| 148 | 1x |
lapply(1:3, function(ch) {
|
| 149 | 3x |
cv <- (mm %*% coefs[, ch]) / 256 |
| 150 | 3x |
cv[cv < 0] <- 0 |
| 151 | 3x |
cv[cv > 1] <- 1 |
| 152 | 3x |
cv |
| 153 |
}) |
|
| 154 |
) |
|
| 155 |
) |
|
| 156 |
} |
|
| 157 | 1x |
list( |
| 158 | 1x |
name = name, |
| 159 | 1x |
type = paste0("continuous", "-polynomial"),
|
| 160 | 1x |
colors = coefs |
| 161 |
) |
|
| 162 |
} else {
|
|
| 163 | 3x |
if (length(colors) < 3) {
|
| 164 | 2x |
if (length(colors) == 1) {
|
| 165 | 1x |
cols <- cbind(c(0, 0, 0), cols) |
| 166 |
} |
|
| 167 | 2x |
cols <- cbind(cols[, 1], rowMeans(cols), cols[, 2]) |
| 168 |
} else {
|
|
| 169 | 1x |
if (ncol(cols) != 3) {
|
| 170 | 1x |
cols <- cbind(cols[, 1], rowMeans(cols[, 2:3]), cols[, 4]) |
| 171 |
} |
|
| 172 |
} |
|
| 173 | 3x |
cols <- t(cols) |
| 174 | 3x |
list( |
| 175 | 3x |
name = name, |
| 176 | 3x |
type = paste0("continuous", if (divergent) "-divergent"),
|
| 177 | 3x |
colors = list( |
| 178 | 3x |
rbind(cols[3, ], cols[2, ] - cols[3, ]), |
| 179 | 3x |
cols[2, ], |
| 180 | 3x |
rbind(cols[1, ], cols[2, ] - cols[1, ]) |
| 181 |
) |
|
| 182 |
) |
|
| 183 |
} |
|
| 184 |
} else {
|
|
| 185 | 3x |
list( |
| 186 | 3x |
name = name, |
| 187 | 3x |
type = "discrete", |
| 188 | 3x |
colors = unlist( |
| 189 | 3x |
lapply( |
| 190 | 3x |
as.data.frame(cols / 255), |
| 191 | 3x |
function(col) do.call(rgb, as.list(col)) |
| 192 |
), |
|
| 193 | 3x |
use.names = FALSE |
| 194 |
) |
|
| 195 |
) |
|
| 196 |
} |
|
| 197 | 7x |
if (print) {
|
| 198 | 2x |
cat(jsonlite::toJSON(palette, auto_unbox = TRUE, pretty = TRUE)) |
| 199 |
} |
|
| 200 | 7x |
invisible(palette) |
| 201 |
} |
| 1 |
#' Add a table to a webpage |
|
| 2 |
#' |
|
| 3 |
#' Adds a table to a webpage, based on specified or selected variables. |
|
| 4 |
#' |
|
| 5 |
#' @param variables The ID of a variable selecting input, or a list specifying columns (if \code{wide} is
|
|
| 6 |
#' \code{TRUE}) or included variables. Each entry should be a list with at least have a \code{name} entry with a
|
|
| 7 |
#' variable name. A \code{title} entry can be used to set a different display name for the variable. \code{name}
|
|
| 8 |
#' can also refer to feature names, which can be specified with a \code{source} entry set to \code{"features"}.
|
|
| 9 |
#' For example, \code{list(title = "Variable A", name = "a", source = "features")}. A vector can also be used
|
|
| 10 |
#' to specify variable names, with names setting titles (e.g., \code{c("Variable A" = "a")}). If not specified,
|
|
| 11 |
#' sources are attempted to be resolved automatically. |
|
| 12 |
#' @param dataset The name of a dataset, or ID of a dataset selector, to find \code{variables} in;
|
|
| 13 |
#' used if \code{dataview} is not specified.
|
|
| 14 |
#' @param dataview The ID of an \code{\link{input_dataview}} component.
|
|
| 15 |
#' @param id Unique ID of the table. |
|
| 16 |
#' @param click The ID of an input to set to a clicked row's entity ID. |
|
| 17 |
#' @param subto A vector of output IDs to receive hover events from. |
|
| 18 |
#' @param options A list of configuration options if \code{datatables} is \code{TRUE}, see
|
|
| 19 |
#' \href{https://datatables.net/reference/option}{DataTables Documentation}; otherwise,
|
|
| 20 |
#' only the \code{scrollY} option has an effect.
|
|
| 21 |
#' @param features A list of features columns to include if multiple variables are included and \code{wide} is
|
|
| 22 |
#' \code{TRUE}.
|
|
| 23 |
#' @param filters A list with names of \code{meta} entries (from \code{variable} entry in \code{\link{data_add}}'s
|
|
| 24 |
#' \code{meta} list), and values of target values for those entries, or the IDs of value selectors.
|
|
| 25 |
#' @param wide Logical; if \code{FALSE}, variables and years are spread across rows rather than columns.
|
|
| 26 |
#' If \code{variables} specifies a single variable, \code{wide = FALSE} will show the variable in a column, and
|
|
| 27 |
#' \code{wide = TRUE} will show the variable across time columns.
|
|
| 28 |
#' @param class Class names to add to the table. |
|
| 29 |
#' @param datatables Logical; if \code{TRUE}, uses \href{https://datatables.net}{DataTables}.
|
|
| 30 |
#' @examples |
|
| 31 |
#' output_table() |
|
| 32 |
#' @return A character vector of the content to be added. |
|
| 33 |
#' @export |
|
| 34 | ||
| 35 |
output_table <- function( |
|
| 36 |
variables = NULL, |
|
| 37 |
dataset = NULL, |
|
| 38 |
dataview = NULL, |
|
| 39 |
id = NULL, |
|
| 40 |
click = NULL, |
|
| 41 |
subto = NULL, |
|
| 42 |
options = NULL, |
|
| 43 |
features = NULL, |
|
| 44 |
filters = NULL, |
|
| 45 |
wide = TRUE, |
|
| 46 |
class = "compact", |
|
| 47 |
datatables = TRUE |
|
| 48 |
) {
|
|
| 49 | 5x |
caller <- parent.frame() |
| 50 | 5x |
building <- !is.null(attr(caller, "name")) && |
| 51 | 5x |
attr(caller, "name") == "community_site_parts" |
| 52 | 5x |
if (is.null(id)) {
|
| 53 | 4x |
id <- paste0("table", caller$uid)
|
| 54 |
} |
|
| 55 | 5x |
defaults <- list( |
| 56 | 5x |
paging = TRUE, |
| 57 | 5x |
scrollY = 500, |
| 58 | 5x |
scrollX = 500, |
| 59 | 5x |
scrollCollapse = TRUE, |
| 60 | 5x |
scroller = TRUE, |
| 61 | 5x |
deferRender = TRUE, |
| 62 | 5x |
fixedColumns = TRUE, |
| 63 | 5x |
fixedHeader = TRUE |
| 64 |
) |
|
| 65 | 5x |
if (!is.null(options$height)) {
|
| 66 | ! |
options$scrollY <- options$height |
| 67 | ! |
options$height <- NULL |
| 68 |
} |
|
| 69 | 5x |
so <- names(options) |
| 70 | 5x |
if (!datatables && (!wide || (length(so) && any(so != "scrollY")))) {
|
| 71 | ! |
cli_warn(paste( |
| 72 | ! |
"because {.arg datatables} is disabled, the {.arg wide} argument is ignored,",
|
| 73 | ! |
"and all {.arg options} except {.arg options$scrollY} are ignored"
|
| 74 |
)) |
|
| 75 |
} |
|
| 76 | 5x |
for (n in names(defaults)) {
|
| 77 | 40x |
if (!n %in% so) options[[n]] <- defaults[[n]] |
| 78 |
} |
|
| 79 | 5x |
type <- if (datatables) "datatable" else "table" |
| 80 | 5x |
r <- paste( |
| 81 | 5x |
c( |
| 82 | 5x |
paste0( |
| 83 | 5x |
if (!datatables) {
|
| 84 | 1x |
paste0( |
| 85 | 1x |
'<div class="table-wrapper" style="max-height: ', |
| 86 | 1x |
options$scrollY, |
| 87 | 1x |
if (is.numeric(options$scrollY)) "px", |
| 88 |
'">' |
|
| 89 |
) |
|
| 90 |
}, |
|
| 91 | 5x |
'<table class="auto-output tables', |
| 92 | 5x |
if (is.null(class)) "" else paste("", class),
|
| 93 |
'"' |
|
| 94 |
), |
|
| 95 | 5x |
if (!is.null(dataview)) paste0('data-view="', dataview, '"'),
|
| 96 | 5x |
if (!is.null(click)) paste0('data-click="', click, '"'),
|
| 97 | 5x |
paste0( |
| 98 | 5x |
'id="', |
| 99 | 5x |
id, |
| 100 | 5x |
'" data-autoType="', |
| 101 | 5x |
type, |
| 102 | 5x |
'"></table>', |
| 103 | 5x |
if (!datatables) "</div>" |
| 104 |
) |
|
| 105 |
), |
|
| 106 | 5x |
collapse = " " |
| 107 |
) |
|
| 108 | 5x |
if (building) {
|
| 109 | 2x |
if (!is.null(variables)) {
|
| 110 | 1x |
if (!is.character(variables) || length(variables) > 1) {
|
| 111 | ! |
if (!is.list(variables)) {
|
| 112 | ! |
variables <- as.list(variables) |
| 113 | ! |
} else if (!is.list(variables[[1]])) {
|
| 114 | ! |
variables <- list(variables) |
| 115 |
} |
|
| 116 | ! |
vnames <- names(variables) |
| 117 | ! |
for (i in seq_along(variables)) {
|
| 118 | ! |
if (is.null(names(variables[[i]]))) {
|
| 119 | ! |
variables[[i]] <- list(name = variables[[i]][[1]]) |
| 120 |
} |
|
| 121 | ! |
if (!is.null(vnames[i])) variables[[i]]$title <- vnames[i] |
| 122 |
} |
|
| 123 |
} |
|
| 124 | 1x |
options$variables <- variables |
| 125 |
} |
|
| 126 | 2x |
if (!is.null(features)) {
|
| 127 | ! |
if (!is.character(features) || length(features) > 1) {
|
| 128 | ! |
if (!is.list(features)) {
|
| 129 | ! |
features <- as.list(features) |
| 130 | ! |
} else if (!is.list(features[[1]]) && "name" %in% names(features)) {
|
| 131 | ! |
features <- list(features) |
| 132 |
} |
|
| 133 | ! |
vnames <- names(features) |
| 134 | ! |
for (i in seq_along(features)) {
|
| 135 | ! |
if (is.null(names(features[[i]]))) {
|
| 136 | ! |
features[[i]] <- list(name = features[[i]][[1]]) |
| 137 |
} |
|
| 138 | ! |
if (!is.null(vnames[i])) features[[i]]$title <- vnames[i] |
| 139 |
} |
|
| 140 |
} |
|
| 141 | ! |
options$features <- unname(features) |
| 142 |
} |
|
| 143 | 2x |
options$subto <- if (!is.null(subto) && length(subto) == 1) {
|
| 144 | ! |
list(subto) |
| 145 |
} else {
|
|
| 146 | 2x |
subto |
| 147 |
} |
|
| 148 | 2x |
options$filters <- filters |
| 149 | 2x |
options$dataset <- dataset |
| 150 | 2x |
options$single_variable <- wide && length(variables) == 1 |
| 151 | 2x |
options$wide <- if (!wide && length(variables) == 1) TRUE else wide |
| 152 | 2x |
if (datatables) {
|
| 153 | 2x |
dependencies <- jsonlite::read_json(system.file( |
| 154 | 2x |
"dependencies.json", |
| 155 | 2x |
package = "community" |
| 156 |
)) |
|
| 157 | 2x |
caller$dependencies$jquery <- dependencies$jquery$js |
| 158 | 2x |
caller$dependencies$datatables_style <- dependencies$datatable$css |
| 159 | 2x |
caller$dependencies$datatables <- dependencies$datatable$js |
| 160 | 2x |
caller$credits$datatables <- dependencies$datatable$info |
| 161 |
} |
|
| 162 | 2x |
if (datatables) {
|
| 163 | 2x |
caller$datatable[[id]] <- options |
| 164 |
} else {
|
|
| 165 | ! |
caller$table[[id]] <- options |
| 166 |
} |
|
| 167 | 2x |
caller$content <- c(caller$content, r) |
| 168 | 2x |
caller$uid <- caller$uid + 1 |
| 169 |
} |
|
| 170 | 5x |
r |
| 171 |
} |
| 1 |
#' Adds documentation of a dataset to a datapackage |
|
| 2 |
#' |
|
| 3 |
#' Add information about variables in a dataset to a \code{datapackage.json} metadata file.
|
|
| 4 |
#' |
|
| 5 |
#' @param filename A character vector of paths to plain-text tabular data files, relative to \code{dir}.
|
|
| 6 |
#' @param meta Information about each data file. A list with a list entry for each entry in |
|
| 7 |
#' \code{filename}; see details. If a single list is provided for multiple data files, it will apply to all.
|
|
| 8 |
#' @param packagename Package to add the metadata to; path to the \code{.json} file relative to
|
|
| 9 |
#' \code{dir}, or a list with the read-in version.
|
|
| 10 |
#' @param dir Directory in which to look for \code{filename}, and write \code{packagename}.
|
|
| 11 |
#' @param write Logical; if \code{FALSE}, returns the \code{paths} metadata without reading or rewriting
|
|
| 12 |
#' \code{packagename}.
|
|
| 13 |
#' @param refresh Logical; if \code{FALSE}, will retain any existing dataset information.
|
|
| 14 |
#' @param sha A number specifying the Secure Hash Algorithm function, |
|
| 15 |
#' if \code{openssl} is available (checked with \code{Sys.which('openssl')}).
|
|
| 16 |
#' @param clean Logical; if \code{TRUE}, strips special characters before saving.
|
|
| 17 |
#' @param pretty Logical; if \code{TRUE}, will pretty-print the datapackage.
|
|
| 18 |
#' @param summarize_ids: Logical; if \code{TRUE}, will include ID columns in schema field summaries.
|
|
| 19 |
#' @param open_after Logical; if \code{TRUE}, opens the written datapackage after saving.
|
|
| 20 |
#' @param verbose Logical; if \code{FALSE}, will not show status messages.
|
|
| 21 |
#' @details |
|
| 22 |
#' \code{meta} should be a list with unnamed entries for entry in \code{filename},
|
|
| 23 |
#' and each entry can include a named entry for any of these: |
|
| 24 |
#' \describe{
|
|
| 25 |
#' \item{source}{
|
|
| 26 |
#' A list or list of lists with entries for at least \code{name}, and ideally for \code{url}.
|
|
| 27 |
#' } |
|
| 28 |
#' \item{ids}{
|
|
| 29 |
#' A list or list of lists with entries for at least \code{variable} (the name of a variable in the dataset).
|
|
| 30 |
#' Might also include \code{map} with a list or path to a JSON file resulting in a list with an
|
|
| 31 |
#' entry for each ID, and additional information about that entity, to be read in a its features. |
|
| 32 |
#' All files will be loaded to help with aggregation, but local files will be included in the datapackage, |
|
| 33 |
#' whereas hosted files will be loaded client-side. |
|
| 34 |
#' } |
|
| 35 |
#' \item{time}{
|
|
| 36 |
#' A string giving the name of a variable in the dataset representing a repeated observation of the same entity. |
|
| 37 |
#' } |
|
| 38 |
#' \item{variables}{
|
|
| 39 |
#' A list with named entries providing more information about the variables in the dataset. |
|
| 40 |
#' See \code{\link{data_measure_info}}.
|
|
| 41 |
#' } |
|
| 42 |
#' } |
|
| 43 |
#' @examples |
|
| 44 |
#' \dontrun{
|
|
| 45 |
#' # write example data |
|
| 46 |
#' write.csv(mtcars, "mtcars.csv") |
|
| 47 |
#' |
|
| 48 |
#' # add it to an existing datapackage.json file in the current working directory |
|
| 49 |
#' data_add("mtcars.csv")
|
|
| 50 |
#' } |
|
| 51 |
#' @return An invisible version of the updated datapackage, which is also written to |
|
| 52 |
#' \code{datapackage.json} if \code{write = TRUE}.
|
|
| 53 |
#' @seealso Initialize the \code{datapackage.json} file with \code{\link{init_data}}.
|
|
| 54 |
#' @export |
|
| 55 | ||
| 56 |
data_add <- function( |
|
| 57 |
filename, |
|
| 58 |
meta = list(), |
|
| 59 |
packagename = "datapackage.json", |
|
| 60 |
dir = ".", |
|
| 61 |
write = TRUE, |
|
| 62 |
refresh = TRUE, |
|
| 63 |
sha = "512", |
|
| 64 |
clean = FALSE, |
|
| 65 |
pretty = FALSE, |
|
| 66 |
summarize_ids = FALSE, |
|
| 67 |
open_after = FALSE, |
|
| 68 |
verbose = interactive() |
|
| 69 |
) {
|
|
| 70 | 5x |
if (missing(filename)) {
|
| 71 | ! |
cli_abort("{.arg filename} must be specified")
|
| 72 |
} |
|
| 73 | 5x |
setnames <- names(filename) |
| 74 | 5x |
if (file.exists(filename[[1]])) {
|
| 75 | 2x |
if (dir == ".") {
|
| 76 | 2x |
dir <- dirname(filename[[1]]) |
| 77 |
} |
|
| 78 | 2x |
filename <- basename(filename) |
| 79 |
} |
|
| 80 |
if ( |
|
| 81 | 5x |
check_template("site", dir = dir)$status[["strict"]] &&
|
| 82 | 5x |
all(file.exists(paste0(dir, "/docs/data/", filename))) |
| 83 |
) {
|
|
| 84 | ! |
dir <- paste0(dir, "/docs/data") |
| 85 |
} |
|
| 86 | 5x |
if (any(!file.exists(paste0(dir, "/", filename)))) {
|
| 87 | ! |
filename <- filename[!file.exists(filename)] |
| 88 | ! |
cli_abort("{?a file/files} did not exist: {filename}")
|
| 89 |
} |
|
| 90 | 5x |
package <- if ( |
| 91 | 5x |
is.character(packagename) && file.exists(paste0(dir, "/", packagename)) |
| 92 |
) {
|
|
| 93 | 2x |
paste0(dir, "/", packagename) |
| 94 |
} else {
|
|
| 95 | 3x |
packagename |
| 96 |
} |
|
| 97 | 5x |
if (write) {
|
| 98 | 3x |
if (is.character(package)) {
|
| 99 | 3x |
package <- paste0(dir, "/", packagename) |
| 100 | 3x |
package <- if (file.exists(package)) {
|
| 101 | 2x |
packagename <- package |
| 102 | 2x |
jsonlite::read_json(package) |
| 103 |
} else {
|
|
| 104 | 1x |
init_data( |
| 105 | 1x |
if (!is.null(setnames)) setnames[[1]] else filename[[1]], |
| 106 | 1x |
dir = dir |
| 107 |
) |
|
| 108 |
} |
|
| 109 |
} |
|
| 110 | 3x |
if (!is.list(package)) {
|
| 111 | ! |
cli_abort(c( |
| 112 | ! |
"{.arg package} does not appear to be in the right format",
|
| 113 | ! |
i = "this should be (or be read in from JSON as) a list with a {.code resource} entry"
|
| 114 |
)) |
|
| 115 |
} |
|
| 116 |
} |
|
| 117 | 5x |
if (!is.list(package)) {
|
| 118 | 2x |
package <- list() |
| 119 |
} |
|
| 120 | 5x |
collect_metadata <- function(file) {
|
| 121 | 5x |
f <- paste0(dir, "/", filename[[file]]) |
| 122 | 5x |
m <- if (single_meta) meta else metas[[file]] |
| 123 | 5x |
format <- if (grepl(".parquet", f, fixed = TRUE)) {
|
| 124 | ! |
"parquet" |
| 125 | 5x |
} else if (grepl(".csv", f, fixed = TRUE)) {
|
| 126 | 5x |
"csv" |
| 127 | 5x |
} else if (grepl(".rds", f, fixed = TRUE)) {
|
| 128 | ! |
"rds" |
| 129 |
} else {
|
|
| 130 | ! |
"tsv" |
| 131 |
} |
|
| 132 | 5x |
if (is.na(format)) {
|
| 133 | ! |
format <- "rds" |
| 134 |
} |
|
| 135 | 5x |
info <- file.info(f) |
| 136 | 5x |
metas <- list() |
| 137 | 5x |
unpack_meta <- function(n) {
|
| 138 | 18x |
if (!length(m[[n]])) {
|
| 139 | 17x |
list() |
| 140 | 1x |
} else if (is.list(m[[n]][[1]])) {
|
| 141 | ! |
m[[n]] |
| 142 |
} else {
|
|
| 143 | 1x |
list(m[[n]]) |
| 144 |
} |
|
| 145 |
} |
|
| 146 | 5x |
ids <- unpack_meta("ids")
|
| 147 | 5x |
idvars <- NULL |
| 148 | 5x |
for (i in seq_along(ids)) {
|
| 149 | 1x |
if (is.list(ids[[i]])) {
|
| 150 | 5x |
if ( |
| 151 | 1x |
length(ids[[i]]$map) == 1 && |
| 152 | 1x |
is.character(ids[[i]]$map) && |
| 153 | 1x |
file.exists(ids[[i]]$map) |
| 154 |
) {
|
|
| 155 | ! |
ids[[i]]$map_content <- paste( |
| 156 | ! |
readLines(ids[[i]]$map, warn = FALSE), |
| 157 | ! |
collapse = "" |
| 158 |
) |
|
| 159 |
} |
|
| 160 |
} else {
|
|
| 161 | ! |
ids[[i]] <- list(variable = ids[[i]]) |
| 162 |
} |
|
| 163 | 1x |
if (!ids[[i]]$variable %in% idvars) idvars <- c(idvars, ids[[i]]$variable) |
| 164 |
} |
|
| 165 | 5x |
data <- if (format == "rds") {
|
| 166 | ! |
tryCatch(readRDS(f), error = function(e) NULL) |
| 167 | 5x |
} else if (format == "parquet") {
|
| 168 | ! |
tryCatch(read_parquet(f), error = function(e) NULL) |
| 169 |
} else {
|
|
| 170 | 5x |
attempt_read(f, c("geography", "time", idvars))
|
| 171 |
} |
|
| 172 | 5x |
if (is.null(data)) {
|
| 173 | ! |
cli_abort(c( |
| 174 | ! |
paste0("failed to read in the data file ({.file {f}})"),
|
| 175 | ! |
i = "check that it is in a compatible format" |
| 176 |
)) |
|
| 177 |
} |
|
| 178 | 5x |
if (!all(rownames(data) == seq_len(nrow(data)))) {
|
| 179 | ! |
data <- cbind(`_row` = rownames(data), data) |
| 180 |
} |
|
| 181 | 5x |
timevar <- unlist(unpack_meta("time"))
|
| 182 | 5x |
times <- if (is.null(timevar)) rep(1, nrow(data)) else data[[timevar]] |
| 183 | 5x |
times_unique <- unique(times) |
| 184 | 5x |
if (!single_meta) {
|
| 185 | 3x |
varinf <- unpack_meta("variables")
|
| 186 | 3x |
if (length(varinf) == 1 && is.character(varinf[[1]])) {
|
| 187 | ! |
if (!file.exists(varinf[[1]])) {
|
| 188 | ! |
varinf[[1]] <- paste0(dir, "/", varinf[[1]]) |
| 189 |
} |
|
| 190 | ! |
if (file.exists(varinf[[1]])) {
|
| 191 | ! |
if (varinf[[1]] %in% names(metas)) {
|
| 192 | ! |
varinf <- metas[[varinf[[1]]]] |
| 193 |
} else {
|
|
| 194 | ! |
varinf <- metas[[varinf[[1]]]] <- data_measure_info( |
| 195 | ! |
varinf[[1]], |
| 196 | ! |
write = FALSE, |
| 197 | ! |
render = TRUE |
| 198 |
) |
|
| 199 |
} |
|
| 200 | ! |
varinf <- varinf[varinf != ""] |
| 201 |
} |
|
| 202 |
} |
|
| 203 | 3x |
varinf_full <- names(varinf) |
| 204 | 3x |
varinf_suf <- sub("^[^:]+:", "", varinf_full)
|
| 205 |
} |
|
| 206 | 5x |
res <- list( |
| 207 | 5x |
bytes = as.integer(info$size), |
| 208 | 5x |
encoding = stri_enc_detect(f)[[1]][1, 1], |
| 209 | 5x |
md5 = md5sum(f)[[1]], |
| 210 | 5x |
format = format, |
| 211 | 5x |
name = if (!is.null(setnames)) {
|
| 212 | 1x |
setnames[file] |
| 213 | 5x |
} else if (!is.null(m$name)) {
|
| 214 | ! |
m$name |
| 215 |
} else {
|
|
| 216 | 4x |
sub("\\.[^.]*$", "", basename(filename[[file]]))
|
| 217 |
}, |
|
| 218 | 5x |
filename = filename[[file]], |
| 219 | 5x |
versions = get_versions(f), |
| 220 | 5x |
source = unpack_meta("source"),
|
| 221 | 5x |
ids = ids, |
| 222 | 5x |
id_length = if (length(idvars)) {
|
| 223 | 1x |
id_lengths <- nchar(data[[idvars[1]]]) |
| 224 | 1x |
id_lengths <- id_lengths[!is.na(id_lengths)] |
| 225 | 1x |
if (all(id_lengths == id_lengths[1])) id_lengths[1] else 0 |
| 226 |
} else {
|
|
| 227 | 4x |
0 |
| 228 |
}, |
|
| 229 | 5x |
time = timevar, |
| 230 | 5x |
profile = "data-resource", |
| 231 | 5x |
created = as.character(info$mtime), |
| 232 | 5x |
last_modified = as.character(info$ctime), |
| 233 | 5x |
row_count = nrow(data), |
| 234 | 5x |
entity_count = if (length(idvars)) {
|
| 235 | 1x |
length(unique(data[[idvars[1]]])) |
| 236 |
} else {
|
|
| 237 | 4x |
nrow(data) |
| 238 |
}, |
|
| 239 | 5x |
schema = list( |
| 240 | 5x |
fields = lapply( |
| 241 | 5x |
if (summarize_ids) {
|
| 242 | ! |
colnames(data) |
| 243 |
} else {
|
|
| 244 | 5x |
colnames(data)[!colnames(data) %in% idvars] |
| 245 |
}, |
|
| 246 | 5x |
function(cn) {
|
| 247 | 59x |
v <- data[[cn]] |
| 248 | 59x |
invalid <- !is.finite(v) |
| 249 | 59x |
r <- list(name = cn, duplicates = sum(duplicated(v))) |
| 250 | 59x |
if (!single_meta) {
|
| 251 | 36x |
if (cn %in% varinf_full) {
|
| 252 | ! |
r$info <- varinf[[cn]] |
| 253 | 36x |
} else if (cn %in% varinf_suf) {
|
| 254 | ! |
r$info <- varinf[[which(varinf_suf == cn)]] |
| 255 |
} |
|
| 256 | 36x |
r$info <- r$info[r$info != ""] |
| 257 |
} |
|
| 258 | 59x |
su <- !is.na(v) |
| 259 | 59x |
if (any(su)) {
|
| 260 | 59x |
r$time_range <- which(times_unique %in% range(times[su])) - 1 |
| 261 | 59x |
r$time_range <- if (length(r$time_range)) {
|
| 262 | 59x |
r$time_range[c(1, length(r$time_range))] |
| 263 |
} else {
|
|
| 264 | ! |
c(-1, -1) |
| 265 |
} |
|
| 266 |
} else {
|
|
| 267 | ! |
r$time_range <- c(-1, -1) |
| 268 |
} |
|
| 269 | 59x |
if (!is.character(v) && all(invalid)) {
|
| 270 | ! |
r$type <- "unknown" |
| 271 | ! |
r$missing <- length(v) |
| 272 | 59x |
} else if (is.numeric(v)) {
|
| 273 | 55x |
r$type <- if (all(invalid | as.integer(v) == v)) {
|
| 274 | 30x |
"integer" |
| 275 |
} else {
|
|
| 276 | 25x |
"float" |
| 277 |
} |
|
| 278 | 55x |
r$missing <- sum(invalid) |
| 279 | 55x |
r$mean <- round(mean(v, na.rm = TRUE), 6) |
| 280 | 55x |
r$sd <- round(sd(v, na.rm = TRUE), 6) |
| 281 | 55x |
r$min <- round(min(v, na.rm = TRUE), 6) |
| 282 | 55x |
r$max <- round(max(v, na.rm = TRUE), 6) |
| 283 |
} else {
|
|
| 284 | 4x |
r$type <- "string" |
| 285 | 4x |
if (!is.factor(v)) {
|
| 286 | 4x |
v <- as.factor(as.character(v)) |
| 287 |
} |
|
| 288 | 4x |
r$missing <- sum(is.na(v) | is.nan(v) | grepl("^[\\s.-]$", v))
|
| 289 | 4x |
r$table <- structure(as.list(tabulate(v)), names = levels(v)) |
| 290 |
} |
|
| 291 | 59x |
r |
| 292 |
} |
|
| 293 |
) |
|
| 294 |
) |
|
| 295 |
) |
|
| 296 | 5x |
if (!single_meta && "_references" %in% names(varinf)) {
|
| 297 | ! |
res[["_references"]] <- varinf[["_references"]] |
| 298 |
} |
|
| 299 | 5x |
if (Sys.which("openssl") != "") {
|
| 300 | 5x |
res[[paste0("sha", sha)]] <- calculate_sha(f, sha)
|
| 301 |
} |
|
| 302 | 5x |
res |
| 303 |
} |
|
| 304 | 5x |
single_meta <- FALSE |
| 305 | 5x |
metas <- if (!is.null(names(meta))) {
|
| 306 | 2x |
if (!is.null(setnames) && all(setnames %in% names(meta))) {
|
| 307 | ! |
meta[setnames] |
| 308 |
} else {
|
|
| 309 | 2x |
single_meta <- TRUE |
| 310 | 2x |
if (length(meta$variables) == 1 && is.character(meta$variables)) {
|
| 311 | ! |
if (!file.exists(meta$variables)) {
|
| 312 | ! |
meta$variables <- paste0(dir, "/", meta$variables) |
| 313 |
} |
|
| 314 | ! |
if (file.exists(meta$variables)) {
|
| 315 | ! |
meta$variables <- jsonlite::read_json(meta$variables) |
| 316 |
} |
|
| 317 |
} |
|
| 318 | 2x |
meta$variables <- replace_equations(meta$variables) |
| 319 | 2x |
meta |
| 320 |
} |
|
| 321 |
} else {
|
|
| 322 | 3x |
meta[seq_along(filename)] |
| 323 |
} |
|
| 324 | 5x |
if (!single_meta) {
|
| 325 | 3x |
metas <- lapply(metas, function(m) {
|
| 326 | 3x |
m$variables <- replace_equations(m$variables) |
| 327 | 3x |
m |
| 328 |
}) |
|
| 329 |
} |
|
| 330 | 5x |
metadata <- lapply(seq_along(filename), collect_metadata) |
| 331 | 5x |
if (single_meta) {
|
| 332 | 2x |
package$measure_info <- lapply(meta$variables, function(e) e[e != ""]) |
| 333 |
} |
|
| 334 | 5x |
names <- vapply(metadata, "[[", "", "filename") |
| 335 | 5x |
for (resource in package$resources) {
|
| 336 | 1x |
if (length(resource$versions)) {
|
| 337 | ! |
su <- which(names %in% resource$filename) |
| 338 | ! |
if (length(su)) {
|
| 339 | ! |
if (length(metadata[[su]]$versions)) {
|
| 340 | ! |
metadata[[su]]$versions <- rbind( |
| 341 | ! |
metadata[[su]]$versions, |
| 342 | ! |
if (is.data.frame(resource$versions)) {
|
| 343 | ! |
resource$versions |
| 344 |
} else {
|
|
| 345 | ! |
as.data.frame(do.call(cbind, resource$versions)) |
| 346 |
} |
|
| 347 |
) |
|
| 348 | ! |
metadata[[su]]$versions <- metadata[[su]]$versions[ |
| 349 | ! |
!duplicated(metadata[[su]]$versions), |
| 350 |
] |
|
| 351 |
} |
|
| 352 |
} |
|
| 353 |
} |
|
| 354 |
} |
|
| 355 | 5x |
if (refresh) {
|
| 356 | 5x |
package$resources <- metadata |
| 357 |
} else {
|
|
| 358 | ! |
package$resources <- c( |
| 359 | ! |
metadata, |
| 360 | ! |
package$resources[ |
| 361 | ! |
!(vapply(package$resources, "[[", "", "filename") %in% names) |
| 362 |
] |
|
| 363 |
) |
|
| 364 |
} |
|
| 365 | 5x |
if (clean) {
|
| 366 | ! |
cf <- lma_dict("special", perl = TRUE, as.function = gsub)
|
| 367 | ! |
package <- jsonlite::fromJSON(cf(jsonlite::toJSON( |
| 368 | ! |
package, |
| 369 | ! |
auto_unbox = TRUE |
| 370 |
))) |
|
| 371 |
} |
|
| 372 | 5x |
if (write) {
|
| 373 | 3x |
packagename <- if (is.character(packagename)) {
|
| 374 | 3x |
packagename |
| 375 |
} else {
|
|
| 376 | ! |
"datapackage.json" |
| 377 |
} |
|
| 378 | 3x |
jsonlite::write_json( |
| 379 | 3x |
package, |
| 380 | 3x |
if (file.exists(packagename)) {
|
| 381 | 2x |
packagename |
| 382 |
} else {
|
|
| 383 | 1x |
paste0(dir, "/", packagename) |
| 384 |
}, |
|
| 385 | 3x |
auto_unbox = TRUE, |
| 386 | 3x |
digits = 6, |
| 387 | 3x |
dataframe = "columns", |
| 388 | 3x |
pretty = pretty |
| 389 |
) |
|
| 390 | 3x |
if (verbose) {
|
| 391 | ! |
cli_bullets(c( |
| 392 | ! |
v = paste( |
| 393 | ! |
if (refresh) "updated resource in" else "added resource to", |
| 394 | ! |
"datapackage.json:" |
| 395 |
), |
|
| 396 | ! |
"*" = paste0("{.path ", packagename, "}")
|
| 397 |
)) |
|
| 398 | ! |
if (open_after) navigateToFile(packagename) |
| 399 |
} |
|
| 400 |
} |
|
| 401 | 5x |
invisible(package) |
| 402 |
} |
|
| 403 | ||
| 404 |
get_versions <- function(file) {
|
|
| 405 | 5x |
log <- suppressWarnings(system2( |
| 406 | 5x |
"git", |
| 407 | 5x |
c("log", "--", file),
|
| 408 | 5x |
stdout = TRUE |
| 409 |
)) |
|
| 410 | 5x |
if (is.null(attr(log, "status"))) {
|
| 411 | ! |
log_entries <- strsplit(paste(log, collapse = "|"), "commit ")[[ |
| 412 | ! |
1 |
| 413 |
]] |
|
| 414 | ! |
log_entries <- do.call( |
| 415 | ! |
rbind, |
| 416 | ! |
Filter( |
| 417 | ! |
function(x) length(x) == 4L, |
| 418 | ! |
strsplit( |
| 419 | ! |
log_entries[log_entries != ""], |
| 420 | ! |
"\\|+(?:[^:]+:)?\\s*" |
| 421 |
) |
|
| 422 |
) |
|
| 423 |
) |
|
| 424 | ! |
if (length(log_entries)) {
|
| 425 | ! |
colnames(log_entries) <- c( |
| 426 | ! |
"hash", |
| 427 | ! |
"author", |
| 428 | ! |
"date", |
| 429 | ! |
"message" |
| 430 |
) |
|
| 431 | ! |
as.data.frame(log_entries) |
| 432 |
} |
|
| 433 |
} |
|
| 434 |
} |
| 1 |
#' Add a unified data view to a website |
|
| 2 |
#' |
|
| 3 |
#' Collects specified inputs to create a filtered dataset from which outputs can pull. |
|
| 4 |
#' |
|
| 5 |
#' @param id An ID of the data view, for association with output elements. Defaults to \code{'view'} appended
|
|
| 6 |
#' with a zero-index of views, based on the order in which they were specified |
|
| 7 |
#' (e.g., \code{'view0'} for the first view).
|
|
| 8 |
#' @param y Primary variable of interest, used by default to color elements in outputs, and shown on the y-axis |
|
| 9 |
#' of plots; name of a variable or ID of a variable selector. |
|
| 10 |
#' @param x Secondary variable, shown by default on the x-axis of plots, and across columns in a single-variable table; |
|
| 11 |
#' name of a variable or ID of a variable selector. |
|
| 12 |
#' @param time Name of a variable giving names to multiple time points (such as a vector of years). Defaults to |
|
| 13 |
#' the time specified in an associated metadata, or a sequence along each variable. |
|
| 14 |
#' @param time_agg Specifies how multiple time points should be treated when a single value is required. |
|
| 15 |
#' Default is to use the last time with data. This could point to an input which selects a time. |
|
| 16 |
#' @param time_filters A list with entries specifying which years to display. Each entry should be a list |
|
| 17 |
#' with entries for \code{"variable"} (which variable is being filtered, which can be \code{"index"}),
|
|
| 18 |
#' \code{"type"} (specifying the operator, such as \code{">"}), and \code{"value"}. The value of each entry
|
|
| 19 |
#' can be static (e.g., referring to a variable) or the ID of an input. |
|
| 20 |
#' @param dataset Select which dataset to pull from; the name of an included dataset, or ID of a |
|
| 21 |
#' selector of dataset names. |
|
| 22 |
#' @param ids Select which IDs to include; a vector of IDs that appear in the specified dataset, or the ID of a |
|
| 23 |
#' selector of IDs. If an IDs map is included in the site's datapackage, mapped components can be referred to here. |
|
| 24 |
#' @param features Select IDs based on their features; a named list or vector, with names corresponding to |
|
| 25 |
#' the names of features included in an \code{ids} field of the site's datapackage, and values corresponding to
|
|
| 26 |
#' a value or vector of values, or a selector of values. |
|
| 27 |
#' @param variables Select IDs based on the values of their variables; a list of lists with entries for |
|
| 28 |
#' \code{"variable"} (name of the variable), \code{"type"} (comparison operator), and \code{"value"} (value to
|
|
| 29 |
#' check against). For example, \code{list(list(variable = "a", type = ">", value = 0))}. Each entry
|
|
| 30 |
#' may also refer to another input. |
|
| 31 |
#' @param palette The name of the color palette used in maps and plots (from |
|
| 32 |
#' \href{https://colorbrewer2.org}{colorbrewer}); one of \code{"rdylbu7"} (default), \code{"orrd7"}, \code{"gnbu7"},
|
|
| 33 |
#' \code{"brbg7"}, \code{"puor7"}, \code{"prgn6"}, \code{"reds5"}, \code{"greens5"}, \code{"greys4"}, \code{"paired4"}.
|
|
| 34 |
#' @examples |
|
| 35 |
#' \dontrun{
|
|
| 36 |
#' input_dataview() |
|
| 37 |
#' } |
|
| 38 |
#' @return A list of the entered options. |
|
| 39 |
#' @export |
|
| 40 | ||
| 41 |
input_dataview <- function( |
|
| 42 |
id = NULL, |
|
| 43 |
y = NULL, |
|
| 44 |
x = NULL, |
|
| 45 |
time = NULL, |
|
| 46 |
time_agg = "last", |
|
| 47 |
time_filters = list(), |
|
| 48 |
dataset = NULL, |
|
| 49 |
ids = NULL, |
|
| 50 |
features = NULL, |
|
| 51 |
variables = NULL, |
|
| 52 |
palette = "" |
|
| 53 |
) {
|
|
| 54 | 3x |
caller <- parent.frame() |
| 55 | 3x |
building <- !is.null(attr(caller, "name")) && |
| 56 | 3x |
attr(caller, "name") == "community_site_parts" |
| 57 | 3x |
r <- list(palette = tolower(palette)) |
| 58 | 3x |
if (!is.null(y)) {
|
| 59 | ! |
r$y <- y |
| 60 |
} |
|
| 61 | 3x |
if (!is.null(x)) {
|
| 62 | ! |
r$x <- x |
| 63 |
} |
|
| 64 | 3x |
if (!is.null(time)) {
|
| 65 | ! |
r$time <- time |
| 66 |
} |
|
| 67 | 3x |
if (!is.null(time_agg)) {
|
| 68 | 3x |
r$time_agg <- time_agg |
| 69 |
} |
|
| 70 | 3x |
if (!length(time_filters)) {
|
| 71 | 3x |
r$time_filters <- time_filters |
| 72 |
} |
|
| 73 | 3x |
if (!is.null(dataset)) {
|
| 74 | 3x |
r$dataset <- dataset |
| 75 |
} |
|
| 76 | 3x |
if (!is.null(ids)) {
|
| 77 | 3x |
r$ids <- ids |
| 78 |
} |
|
| 79 | 3x |
if (!is.null(features)) {
|
| 80 | 3x |
r$features <- as.list(features) |
| 81 |
} |
|
| 82 | 3x |
if (!is.null(variables)) {
|
| 83 | ! |
r$variables <- if (!is.list(variables[[1]])) list(variables) else variables |
| 84 |
} |
|
| 85 | 3x |
if (length(r) && building) {
|
| 86 | 1x |
caller$dataviews[[ |
| 87 | 1x |
if (is.null(id)) paste0("view", length(caller$dataviews)) else id
|
| 88 | 1x |
]] <- r |
| 89 |
} |
|
| 90 | 3x |
r |
| 91 |
} |
| 1 |
#' Add a combobox select input to a website |
|
| 2 |
#' |
|
| 3 |
#' Adds an input to select from the entered options, |
|
| 4 |
#' allowing for multiple selection, dynamic filtering, and custom entries. |
|
| 5 |
#' |
|
| 6 |
#' @param label Label of the input for the user. |
|
| 7 |
#' @param options A vector of options, the name of a variable from which to pull levels, or \code{"datasets"},
|
|
| 8 |
#' \code{"variables"}, \code{"ids"}, or \code{"palettes"} to select names of datasets, variables, entity ids, or
|
|
| 9 |
#' color palettes. If there is a map with overlay layers with properties, can also be \code{"overlay_properties"},
|
|
| 10 |
#' to select between properties. |
|
| 11 |
#' @param default Which of the options to default to; either its index or value. |
|
| 12 |
#' @param display A display version of the options. |
|
| 13 |
#' @param id Unique ID of the element to be created. |
|
| 14 |
#' @param ... Additional attributes to set on the input element. |
|
| 15 |
#' @param strict Logical; if \code{FALSE}, allows arbitrary user input, rather than limiting input to the
|
|
| 16 |
#' option set. |
|
| 17 |
#' @param numeric Logical; if \code{TRUE}, will treat all numeric inputs as custom values,
|
|
| 18 |
#' rather than as potential option indices. |
|
| 19 |
#' @param search Logical; if \code{FALSE}, does not dynamically filter the option set on user input.
|
|
| 20 |
#' @param multi Logical; if \code{TRUE}, allows multiple options to be selected.
|
|
| 21 |
#' @param accordion Logical; if \code{TRUE}, option groups will be collapsible.
|
|
| 22 |
#' @param clearable Logical; if \code{TRUE}, adds a button to clear the selection.
|
|
| 23 |
#' @param note Text to display as a tooltip for the input. |
|
| 24 |
#' @param group_feature Name of a measure or entity feature to use as a source of option grouping, |
|
| 25 |
#' if \code{options} is \code{"variables"} or \code{"ids"}.
|
|
| 26 |
#' @param variable The name of a variable from which to get levels (overwritten by \code{depends}).
|
|
| 27 |
#' @param dataset The name of an included dataset, where \code{variable} should be looked for; only applies when
|
|
| 28 |
#' there are multiple datasets with the same variable name. |
|
| 29 |
#' @param depends The ID of another input on which the options depend; this will take president over \code{dataset}
|
|
| 30 |
#' and \code{variable}, depending on this type of input \code{depends} points to.
|
|
| 31 |
#' @param dataview The ID of an \code{\link{input_dataview}}, used to filter the set of options, and potentially
|
|
| 32 |
#' specify dataset if none is specified here. |
|
| 33 |
#' @param subset Determines the subset of options shown if \code{options} is \code{"ids"}; mainly \code{"filtered"}
|
|
| 34 |
#' (default) to apply all filters, including the current selection, or \code{"full_filter"} to apply all
|
|
| 35 |
#' feature and variable filters, but not the current selection. \code{"siblings"} is a special case given a selection,
|
|
| 36 |
#' which will show other IDs with the same parent. |
|
| 37 |
#' @param selection_subset Subset to use when a selection is made; defaults to \code{"full_filter"}.
|
|
| 38 |
#' @param filters A list with names of \code{meta} entries (from \code{variable} entry in \code{\link{data_add}}'s
|
|
| 39 |
#' \code{meta} list), and values of target values for those entries, or the IDs of value selectors.
|
|
| 40 |
#' @param reset_button If specified, adds a button after the input element that will revert the selection |
|
| 41 |
#' to its default; either \code{TRUE}, or text for the reset button's label.
|
|
| 42 |
#' @param button_class Class name to add to the reset button. |
|
| 43 |
#' @param as.row Logical; if \code{TRUE}, the label and input are in separate columns within a row.
|
|
| 44 |
#' @param floating_label Logical; if \code{FALSE} or \code{as.row} is \code{TRUE}, labels are separate from
|
|
| 45 |
#' their inputs. |
|
| 46 |
#' @seealso See \code{\link{input_select}} for a more standard select input, or \code{\link{input_text}}
|
|
| 47 |
#' for a free-form input. |
|
| 48 |
#' @examples |
|
| 49 |
#' \dontrun{
|
|
| 50 |
#' input_combobox("Options", c("a", "b"))
|
|
| 51 |
#' } |
|
| 52 |
#' @return A character vector of the contents to be added. |
|
| 53 |
#' @export |
|
| 54 | ||
| 55 |
input_combobox <- function( |
|
| 56 |
label, |
|
| 57 |
options, |
|
| 58 |
default = -1, |
|
| 59 |
display = options, |
|
| 60 |
id = label, |
|
| 61 |
..., |
|
| 62 |
strict = TRUE, |
|
| 63 |
numeric = FALSE, |
|
| 64 |
search = TRUE, |
|
| 65 |
multi = FALSE, |
|
| 66 |
accordion = FALSE, |
|
| 67 |
clearable = FALSE, |
|
| 68 |
note = NULL, |
|
| 69 |
group_feature = NULL, |
|
| 70 |
variable = NULL, |
|
| 71 |
dataset = NULL, |
|
| 72 |
depends = NULL, |
|
| 73 |
dataview = NULL, |
|
| 74 |
subset = "filtered", |
|
| 75 |
selection_subset = "full_filter", |
|
| 76 |
filters = NULL, |
|
| 77 |
reset_button = FALSE, |
|
| 78 |
button_class = NULL, |
|
| 79 |
as.row = FALSE, |
|
| 80 |
floating_label = TRUE |
|
| 81 |
) {
|
|
| 82 | 4x |
id <- gsub("\\s", "", id)
|
| 83 | 4x |
a <- list(...) |
| 84 | 4x |
if (as.row) {
|
| 85 | ! |
floating_label <- FALSE |
| 86 |
} |
|
| 87 | 4x |
r <- c( |
| 88 | 4x |
'<div class="wrapper combobox-wrapper">', |
| 89 | 4x |
if (!floating_label) {
|
| 90 | ! |
paste0( |
| 91 | ! |
'<label id="', |
| 92 | ! |
id, |
| 93 | ! |
'-label" for="', |
| 94 | ! |
id, |
| 95 | ! |
'-input">', |
| 96 | ! |
label, |
| 97 | ! |
"</label>" |
| 98 |
) |
|
| 99 |
}, |
|
| 100 | 4x |
paste0( |
| 101 | 4x |
'<div class="', |
| 102 | 4x |
paste( |
| 103 | 4x |
c( |
| 104 | 4x |
if (reset_button) "input-group", |
| 105 | 4x |
if (floating_label) "form-floating" |
| 106 |
), |
|
| 107 | 4x |
collapse = " " |
| 108 |
), |
|
| 109 |
'">' |
|
| 110 |
), |
|
| 111 | 4x |
paste0( |
| 112 | 4x |
'<div class="auto-input form-select combobox combobox-component" data-autoType="combobox"', |
| 113 | 4x |
' id="', |
| 114 | 4x |
id, |
| 115 |
'" ', |
|
| 116 | 4x |
if (is.character(options) && length(options) == 1) {
|
| 117 | ! |
paste0('data-optionSource="', options, '"')
|
| 118 |
}, |
|
| 119 | 4x |
if (!is.null(default)) paste0(' data-default="', default, '"'),
|
| 120 | 4x |
if (!is.null(note)) paste0(' aria-description="', note, '"'),
|
| 121 | 4x |
if (!is.null(dataview)) paste0(' data-view="', dataview, '"'),
|
| 122 | 4x |
if (!is.null(subset)) paste0(' data-subset="', subset, '"'),
|
| 123 | 4x |
if (!is.null(selection_subset)) {
|
| 124 | 4x |
paste0(' data-selectionsubset="', selection_subset, '"')
|
| 125 |
}, |
|
| 126 | 4x |
if (!is.null(depends)) paste0(' data-depends="', depends, '"'),
|
| 127 | 4x |
if (!is.null(dataset)) paste0(' data-dataset="', dataset, '"'),
|
| 128 | 4x |
if (!is.null(variable)) paste0(' data-variable="', variable, '"'),
|
| 129 | 4x |
if (length(a)) {
|
| 130 | ! |
unlist(lapply( |
| 131 | ! |
seq_along(a), |
| 132 | ! |
function(i) paste0(" ", names(a)[i], '="', a[[i]], '"')
|
| 133 |
)) |
|
| 134 |
}, |
|
| 135 | 4x |
'><div class="combobox-selection combobox-component">', |
| 136 | 4x |
'<span aria-live="assertive" aria-atomic="true" role="log" class="combobox-component"></span>', |
| 137 | 4x |
'<input class="combobox-input combobox-component" role="combobox" type="text" ', |
| 138 | 4x |
'aria-expanded="false" aria-autocomplete="list" aria-controls="', |
| 139 | 4x |
id, |
| 140 | 4x |
'-listbox" aria-controls="', |
| 141 | 4x |
id, |
| 142 | 4x |
'-listbox" id="', |
| 143 | 4x |
id, |
| 144 | 4x |
'-input" autocomplete="off"></div>', |
| 145 | 4x |
if (clearable) {
|
| 146 | ! |
'<button type="button" class="btn-close" title="clear selection"></button>' |
| 147 |
}, |
|
| 148 | 4x |
"</div>" |
| 149 |
), |
|
| 150 | 4x |
paste0( |
| 151 | 4x |
'<div class="combobox-options combobox-component', |
| 152 | 4x |
if (multi) " multi", |
| 153 | 4x |
'" role="listbox"', |
| 154 | 4x |
' id="', |
| 155 | 4x |
id, |
| 156 | 4x |
'-listbox" aria-labelledby="', |
| 157 | 4x |
id, |
| 158 | 4x |
'-label">' |
| 159 |
), |
|
| 160 | 4x |
if (is.list(options)) {
|
| 161 | 1x |
i <- 0 |
| 162 | 1x |
if (is.null(names(options))) {
|
| 163 | ! |
names(options) <- seq_along(options) |
| 164 |
} |
|
| 165 | 1x |
if (missing(accordion)) {
|
| 166 | 1x |
accordion <- TRUE |
| 167 |
} |
|
| 168 | 1x |
unlist( |
| 169 | 1x |
lapply(names(options), function(g) {
|
| 170 | 2x |
group <- paste0( |
| 171 | 2x |
'<div class="combobox-group combobox-component', |
| 172 | 2x |
if (accordion) " accordion-item", |
| 173 | 2x |
'" data-group="', |
| 174 | 2x |
g, |
| 175 |
'">' |
|
| 176 |
) |
|
| 177 | 2x |
if (accordion) {
|
| 178 | 2x |
gid <- paste0(id, "_", gsub("[\\s,/._-]+", "", g))
|
| 179 | 2x |
group <- c( |
| 180 | 2x |
group, |
| 181 | 2x |
paste0( |
| 182 | 2x |
'<div id="', |
| 183 | 2x |
gid, |
| 184 | 2x |
'-label" class="accordion-header combobox-component">' |
| 185 |
), |
|
| 186 | 2x |
paste0( |
| 187 | 2x |
'<button role="button" ', |
| 188 | 2x |
'data-bs-toggle="collapse" data-bs-target="#', |
| 189 | 2x |
gid, |
| 190 | 2x |
'" aria-expanded=false aria-controls="', |
| 191 | 2x |
gid, |
| 192 | 2x |
'" class="accordion-button combobox-component collapsed">', |
| 193 | 2x |
g, |
| 194 | 2x |
"</button></div>" |
| 195 |
), |
|
| 196 | 2x |
paste0( |
| 197 | 2x |
'<div id="', |
| 198 | 2x |
gid, |
| 199 | 2x |
'" class="combobox-component accordion-collapse collapse" ', |
| 200 | 2x |
'data-group="', |
| 201 | 2x |
g, |
| 202 | 2x |
'" data-bs-parent="#', |
| 203 | 2x |
id, |
| 204 | 2x |
'-listbox"><div class="accordion-body combobox-component">' |
| 205 |
) |
|
| 206 |
) |
|
| 207 |
} |
|
| 208 | 2x |
for (gi in seq_along(options[[g]])) {
|
| 209 | 4x |
i <<- i + 1 |
| 210 | 4x |
group <- c( |
| 211 | 4x |
group, |
| 212 | 4x |
paste0( |
| 213 | 4x |
'<div class="combobox-option combobox-component', |
| 214 | 4x |
if (i == default) " selected", |
| 215 | 4x |
'" role="option" tabindex="0"', |
| 216 | 4x |
' data-group="', |
| 217 | 4x |
g, |
| 218 | 4x |
'" id="', |
| 219 | 4x |
id, |
| 220 | 4x |
"-option", |
| 221 | 4x |
i, |
| 222 | 4x |
'" data-value="', |
| 223 | 4x |
options[[g]][[gi]], |
| 224 | 4x |
'" aria-selected="', |
| 225 | 4x |
if (i == default) "true" else "false", |
| 226 |
'">', |
|
| 227 | 4x |
display[[g]][[gi]], |
| 228 | 4x |
"</div>" |
| 229 |
) |
|
| 230 |
) |
|
| 231 |
} |
|
| 232 | 2x |
c(group, "</div>", if (accordion) "</div></div>") |
| 233 |
}), |
|
| 234 | 1x |
use.names = FALSE |
| 235 |
) |
|
| 236 | 4x |
} else if ( |
| 237 | 4x |
length(options) > 1 || |
| 238 | 4x |
!options %in% c("datasets", "variables", "ids", "palettes")
|
| 239 |
) {
|
|
| 240 | 3x |
unlist( |
| 241 | 3x |
lapply(seq_along(options), function(i) {
|
| 242 | 9x |
paste0( |
| 243 | 9x |
'<div class="combobox-component', |
| 244 | 9x |
if (i == default) " selected", |
| 245 | 9x |
'" role="option" tabindex="0"', |
| 246 | 9x |
' id="', |
| 247 | 9x |
id, |
| 248 | 9x |
"-option", |
| 249 | 9x |
i, |
| 250 | 9x |
'" data-value="', |
| 251 | 9x |
options[i], |
| 252 | 9x |
'" aria-selected="', |
| 253 | 9x |
if (i == default) "true" else "false", |
| 254 |
'">', |
|
| 255 | 9x |
display[i], |
| 256 | 9x |
"</div>" |
| 257 |
) |
|
| 258 |
}), |
|
| 259 | 3x |
use.names = FALSE |
| 260 |
) |
|
| 261 |
}, |
|
| 262 | 4x |
"</div>", |
| 263 | 4x |
if (floating_label) {
|
| 264 | 4x |
paste0( |
| 265 | 4x |
'<label id="', |
| 266 | 4x |
id, |
| 267 | 4x |
'-label" for="', |
| 268 | 4x |
id, |
| 269 | 4x |
'-input">', |
| 270 | 4x |
label, |
| 271 | 4x |
"</label>" |
| 272 |
) |
|
| 273 |
}, |
|
| 274 | 4x |
if (!missing(reset_button)) {
|
| 275 | ! |
paste( |
| 276 | ! |
c( |
| 277 | ! |
'<button type="button" class="btn btn-link', |
| 278 | ! |
if (!is.null(button_class)) paste("", button_class),
|
| 279 | ! |
' select-reset">', |
| 280 | ! |
if (is.character(reset_button)) reset_button else "Reset", |
| 281 | ! |
"</button>" |
| 282 |
), |
|
| 283 | ! |
collapse = "" |
| 284 |
) |
|
| 285 |
}, |
|
| 286 | 4x |
"</div>", |
| 287 | 4x |
"</div>" |
| 288 |
) |
|
| 289 | 4x |
if (missing(accordion) && !is.null(group_feature)) {
|
| 290 | ! |
accordion <- TRUE |
| 291 |
} |
|
| 292 | 4x |
if (as.row) {
|
| 293 | ! |
r <- to_input_row(r) |
| 294 |
} |
|
| 295 | 4x |
caller <- parent.frame() |
| 296 |
if ( |
|
| 297 | 4x |
!is.null(attr(caller, "name")) && |
| 298 | 4x |
attr(caller, "name") == "community_site_parts" |
| 299 |
) {
|
|
| 300 | 1x |
if (strict) {
|
| 301 | 1x |
caller$combobox[[id]]$strict <- strict |
| 302 |
} |
|
| 303 | 1x |
if (numeric) {
|
| 304 | ! |
caller$combobox[[id]]$numeric <- numeric |
| 305 |
} |
|
| 306 | 1x |
if (search) {
|
| 307 | 1x |
caller$combobox[[id]]$search <- search |
| 308 |
} |
|
| 309 | 1x |
if (multi) {
|
| 310 | ! |
caller$combobox[[id]]$multi <- multi |
| 311 |
} |
|
| 312 | 1x |
if (accordion) {
|
| 313 | ! |
caller$combobox[[id]]$accordion <- accordion && |
| 314 | ! |
(is.list(options) || !is.null(group_feature)) |
| 315 |
} |
|
| 316 | 1x |
if (!is.null(group_feature)) {
|
| 317 | ! |
caller$combobox[[id]]$group <- group_feature |
| 318 |
} |
|
| 319 | 1x |
if (!is.null(filters)) {
|
| 320 | ! |
caller$combobox[[id]]$filters <- as.list(filters) |
| 321 |
} |
|
| 322 | 1x |
caller$content <- c(caller$content, r) |
| 323 |
} |
|
| 324 | 4x |
r |
| 325 |
} |
| 1 |
#' Adds a modal dialog (popup) to a website |
|
| 2 |
#' |
|
| 3 |
#' Adds a button which triggers a modal dialog (popup) with specified content. |
|
| 4 |
#' |
|
| 5 |
#' @param text Text in the triggering button. |
|
| 6 |
#' @param ... Elements to appear in the popup's body area. |
|
| 7 |
#' @param title Content to appear in the popup's header area. Defaults to \code{text}.
|
|
| 8 |
#' @param footer A list of elements to include in the footer. |
|
| 9 |
#' @param wraps The class of wrapper to place elements in; either \code{"row"}, \code{"col"}, or \code{""}
|
|
| 10 |
#' (to not wrap the element). Can specify 1 for every element, or a different class for each element. |
|
| 11 |
#' @param sizes The relative size of each wrapper, between 1 and 12, or \code{"auto"}; default is equal size.
|
|
| 12 |
#' @param breakpoints Bootstrap breakpoint of each wrapper; one of \code{""} (extra small), \code{"sm"},
|
|
| 13 |
#' \code{"md"}, \code{"lg"}, \code{"xl"}, or \code{"xxl"}.
|
|
| 14 |
#' @param conditions A character for each element representing the conditions in which that should be showing |
|
| 15 |
#' (e.g., \code{c("", "input_a == a", "")}); \code{""} means the element's display is not conditional.
|
|
| 16 |
#' Adding \code{"lock: "} before the condition will disable inputs rather than hide the element.
|
|
| 17 |
#' @param id Unique ID of the section. |
|
| 18 |
#' @details See the \href{https://getbootstrap.com/docs/5.1/layout/grid}{Bootstrap grid documentation}.
|
|
| 19 |
#' @examples |
|
| 20 |
#' \dontrun{
|
|
| 21 |
#' page_popup( |
|
| 22 |
#' "<h1>Title</h1>", |
|
| 23 |
#' "<p>body</p>", |
|
| 24 |
#' ) |
|
| 25 |
#' } |
|
| 26 |
#' @return A character vector of the content to be added. |
|
| 27 |
#' @export |
|
| 28 | ||
| 29 |
page_popup <- function( |
|
| 30 |
text = "Popup", |
|
| 31 |
..., |
|
| 32 |
title = text, |
|
| 33 |
footer = NULL, |
|
| 34 |
wraps = NA, |
|
| 35 |
sizes = NA, |
|
| 36 |
breakpoints = NA, |
|
| 37 |
conditions = "", |
|
| 38 |
id = NULL |
|
| 39 |
) {
|
|
| 40 | 3x |
caller <- parent.frame() |
| 41 | 3x |
building <- !is.null(attr(caller, "name")) && |
| 42 | 3x |
attr(caller, "name") == "community_site_parts" |
| 43 | 3x |
parts <- new.env() |
| 44 | 3x |
attr(parts, "name") <- "community_site_parts" |
| 45 | 3x |
parts$uid <- caller$uid |
| 46 | 3x |
elements <- substitute(...()) |
| 47 | 3x |
n <- length(elements) |
| 48 | 3x |
wraps <- rep_len(wraps, n) |
| 49 | 3x |
sizes <- rep_len(sizes, n) |
| 50 | 3x |
breakpoints <- rep_len(breakpoints, n) |
| 51 | 3x |
conditions <- rep_len(conditions, n) |
| 52 | 3x |
ids <- paste0("modal", parts$uid, seq_len(n))
|
| 53 | 3x |
r <- paste0( |
| 54 | 3x |
'<button type="button" class="btn popup-button" data-bs-toggle="modal" data-bs-target="#dialog', |
| 55 | 3x |
parts$uid, |
| 56 |
'"', |
|
| 57 | 3x |
if (!is.null(id)) paste0(' id="', id, '"'),
|
| 58 |
">", |
|
| 59 | 3x |
text, |
| 60 | 3x |
"</button>" |
| 61 |
) |
|
| 62 | 3x |
b <- c( |
| 63 | 3x |
paste0( |
| 64 | 3x |
'<div class="modal" tabindex="-1" id="dialog', |
| 65 | 3x |
parts$uid, |
| 66 | 3x |
'"><div class="modal-dialog"><div class="modal-content">' |
| 67 |
), |
|
| 68 | 3x |
paste0( |
| 69 | 3x |
'<div class="modal-header"><div class="modal-title">', |
| 70 | 3x |
paste(title, collapse = ""), |
| 71 | 3x |
"</div>", |
| 72 | 3x |
'<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>', |
| 73 | 3x |
"</div>" |
| 74 |
), |
|
| 75 | 3x |
'<div class="modal-body">', |
| 76 | 3x |
unlist( |
| 77 | 3x |
lapply(seq_len(n), function(i) {
|
| 78 | 3x |
c( |
| 79 | 3x |
if (!is.na(wraps[i]) || conditions[i] != "") {
|
| 80 | ! |
paste( |
| 81 | ! |
c( |
| 82 | ! |
'<div class="', |
| 83 | ! |
if (is.na(wraps[i])) "" else wraps[i], |
| 84 | ! |
if (!is.na(breakpoints[i])) c("-", breakpoints[i]),
|
| 85 | ! |
if (!is.na(sizes[i])) c("-", sizes[i]),
|
| 86 |
'"', |
|
| 87 | ! |
if (conditions[i] != "") c(' id="', ids[i], '"'),
|
| 88 |
">" |
|
| 89 |
), |
|
| 90 | ! |
collapse = "" |
| 91 |
) |
|
| 92 |
}, |
|
| 93 | 3x |
eval(elements[[i]], parts), |
| 94 | 3x |
if (!is.na(wraps[i])) "</div>" |
| 95 |
) |
|
| 96 |
}), |
|
| 97 | 3x |
use.names = FALSE |
| 98 |
), |
|
| 99 | 3x |
"</div>", |
| 100 | 3x |
if (!is.null(footer)) {
|
| 101 | ! |
c( |
| 102 | ! |
'<div class="modal-footer">', |
| 103 | ! |
unlist( |
| 104 | ! |
lapply(footer, function(e) {
|
| 105 | ! |
eval(e, parts, caller) |
| 106 |
}), |
|
| 107 | ! |
use.names = FALSE |
| 108 |
), |
|
| 109 | ! |
"</div>" |
| 110 |
) |
|
| 111 |
}, |
|
| 112 | 3x |
"</div></div></div>" |
| 113 |
) |
|
| 114 | 3x |
if (building) {
|
| 115 | 1x |
caller$body <- c(caller$body, b) |
| 116 | 1x |
caller$content <- c(caller$content, r) |
| 117 | 1x |
for (n in names(parts)) {
|
| 118 | 2x |
if (n != "content" && n != "uid") {
|
| 119 | ! |
caller[[n]] <- c(caller[[n]], parts[[n]]) |
| 120 |
} |
|
| 121 |
} |
|
| 122 | 1x |
process_conditions(conditions, ids, caller) |
| 123 | 1x |
caller$uid <- parts$uid + 1 |
| 124 |
} |
|
| 125 | 3x |
r |
| 126 |
} |
| 1 |
#' Create a new package function |
|
| 2 |
#' |
|
| 3 |
#' Create initial script and test files for a function that is to be added to a package. |
|
| 4 |
#' |
|
| 5 |
#' @param name Name of the function. Should start with the function's category, followed by a specific name, |
|
| 6 |
#' separated by an underscore (\code{'_'}) (e.g., \code{'init_function'}).
|
|
| 7 |
#' @param dir Path to the package's development directory; default is the current working directory. |
|
| 8 |
#' @param overwrite Logical; if \code{TRUE}, replaces existing files with templates.
|
|
| 9 |
#' @examples |
|
| 10 |
#' \dontrun{
|
|
| 11 |
#' |
|
| 12 |
#' # creates a skeleton for a `measure_new` function |
|
| 13 |
#' init_function("measure_new")
|
|
| 14 |
#' } |
|
| 15 |
#' @return Creates files in \code{dir/R} and \code{dir/tests/testthat}, attempts to navigate to the code file,
|
|
| 16 |
#' and returns a character vector to their paths. |
|
| 17 |
#' @export |
|
| 18 | ||
| 19 |
init_function <- function(name, dir = ".", overwrite = FALSE) {
|
|
| 20 | 2x |
if (missing(name)) {
|
| 21 | ! |
cli_abort("{.arg name} must be specified")
|
| 22 |
} |
|
| 23 | 2x |
name <- sub("\\.[Rr]$", "", name[[1]])
|
| 24 | 2x |
dir <- paste0(normalizePath(dir, "/"), "/") |
| 25 | 2x |
if (!check_template("package", dir = dir)$exists) {
|
| 26 | ! |
cli_abort(paste( |
| 27 | ! |
"{.arg dir} must be a package directory,",
|
| 28 | ! |
"but {.code check_template('package')} failed"
|
| 29 |
)) |
|
| 30 |
} |
|
| 31 | 2x |
paths <- paste0(dir, c("R/", "tests/testthat/test-"), name, ".R")
|
| 32 | 2x |
if (!overwrite && any(file.exists(paths))) {
|
| 33 | ! |
cli_abort("files exist -- set overwrite to {.code TRUE} to overwrite them")
|
| 34 |
} |
|
| 35 | 2x |
if (!grepl("_", name, fixed = TRUE)) {
|
| 36 | ! |
cli_abort("name should be in a {.emph prefix_suffix} format")
|
| 37 |
} |
|
| 38 | 2x |
writeLines( |
| 39 | 2x |
paste0( |
| 40 | 2x |
"#' <template: Short, high-level description of function.>", |
| 41 | 2x |
"\n#'\n#' <template: Full description of function.>\n#'", |
| 42 | 2x |
"\n#' @param argument <template: Argument description.>", |
| 43 | 2x |
"\n#' @examples\n#' \\dontrun{",
|
| 44 | 2x |
"\n#' <template: a working example for illustration; add outside of \\dontrun{} when possible>\n#' }",
|
| 45 | 2x |
"\n#' @return <template: Description of what is returned.>", |
| 46 | 2x |
"\n#' @export", |
| 47 | 2x |
"\n\n", |
| 48 | 2x |
name, |
| 49 | 2x |
" <- function(argument){\n\n}"
|
| 50 |
), |
|
| 51 | 2x |
paths[1] |
| 52 |
) |
|
| 53 | 2x |
writeLines( |
| 54 | 2x |
paste0( |
| 55 | 2x |
"test_that('a test has been written for ",
|
| 56 | 2x |
name, |
| 57 | 2x |
"', {\n expect_true(FALSE)\n})"
|
| 58 |
), |
|
| 59 | 2x |
paths[2] |
| 60 |
) |
|
| 61 | 2x |
msg <- c("created files for function {name}:", paste0("{.file ", paths, "}"))
|
| 62 | 2x |
names(msg) <- c("v", rep("*", length(paths)))
|
| 63 | 2x |
if (interactive()) {
|
| 64 | ! |
cli_bullets(msg) |
| 65 | ! |
navigateToFile(paths[1]) |
| 66 |
} |
|
| 67 | 2x |
invisible(paths) |
| 68 |
} |
| 1 |
#' Add a text input to a website |
|
| 2 |
#' |
|
| 3 |
#' Adds an direct text input element to a website. |
|
| 4 |
#' |
|
| 5 |
#' @param label Label of the input for the user. |
|
| 6 |
#' @param id Unique ID of the element to be created. |
|
| 7 |
#' @param ... Other attributes to add to the input. |
|
| 8 |
#' @param default Default value of the input, which will appear as a placeholder. |
|
| 9 |
#' @param multiline Logical; if \code{TRUE}, create a \code{textarea} element, instead of an \code{input} element
|
|
| 10 |
#' to accept multiple lines of text. |
|
| 11 |
#' @param class Class names to add to the input's list. |
|
| 12 |
#' @param note Text to display as a tooltip for the input or textarea. |
|
| 13 |
#' @param floating_label Logical; if \code{FALSE}, labels are separate from their input elements.
|
|
| 14 |
#' @examples |
|
| 15 |
#' \dontrun{
|
|
| 16 |
#' input_text("Enter Text:", "entered_text")
|
|
| 17 |
#' } |
|
| 18 |
#' @return A character vector of the contents to be added. |
|
| 19 |
#' @export |
|
| 20 | ||
| 21 |
input_text <- function( |
|
| 22 |
label, |
|
| 23 |
id = label, |
|
| 24 |
..., |
|
| 25 |
default = NULL, |
|
| 26 |
note = NULL, |
|
| 27 |
multiline = FALSE, |
|
| 28 |
class = NULL, |
|
| 29 |
floating_label = TRUE |
|
| 30 |
) {
|
|
| 31 | 3x |
id <- gsub("\\s", "", id)
|
| 32 | 3x |
a <- list(...) |
| 33 | 3x |
r <- c( |
| 34 | 3x |
paste0( |
| 35 | 3x |
'<div class="wrapper text-wrapper', |
| 36 | 3x |
if (floating_label) " form-floating", |
| 37 |
'">' |
|
| 38 |
), |
|
| 39 | 3x |
if (!floating_label) paste0('<label for="', id, '">', label, "</label>"),
|
| 40 | 3x |
paste0( |
| 41 | 3x |
c( |
| 42 |
"<", |
|
| 43 | 3x |
if (multiline) "textarea" else 'input type="text"', |
| 44 | 3x |
' id="', |
| 45 | 3x |
id, |
| 46 |
'"', |
|
| 47 | 3x |
if (!is.null(default)) {
|
| 48 | ! |
c( |
| 49 | ! |
' placeholder="', |
| 50 | ! |
default, |
| 51 |
'"', |
|
| 52 | ! |
' value="', |
| 53 | ! |
default, |
| 54 |
'"' |
|
| 55 |
) |
|
| 56 |
}, |
|
| 57 | 3x |
if (length(a)) {
|
| 58 | ! |
unlist(lapply( |
| 59 | ! |
seq_along(a), |
| 60 | ! |
function(i) paste0(" ", names(a)[i], '="', a[[i]], '"')
|
| 61 |
)) |
|
| 62 |
}, |
|
| 63 | 3x |
if (!is.null(note)) c(' aria-description="', note, '"'),
|
| 64 | 3x |
' class="form-control auto-input', |
| 65 | 3x |
if (!is.null(class)) paste("", class),
|
| 66 | 3x |
'" data-autoType="intext">', |
| 67 | 3x |
if (multiline) "</textarea>", |
| 68 | 3x |
if (floating_label) paste0('<label for="', id, '">', label, "</label>")
|
| 69 |
), |
|
| 70 | 3x |
collapse = "" |
| 71 |
), |
|
| 72 | 3x |
"</div>" |
| 73 |
) |
|
| 74 | 3x |
caller <- parent.frame() |
| 75 |
if ( |
|
| 76 | 3x |
!is.null(attr(caller, "name")) && |
| 77 | 3x |
attr(caller, "name") == "community_site_parts" |
| 78 |
) {
|
|
| 79 | 1x |
caller$content <- c(caller$content, r) |
| 80 |
} |
|
| 81 | 3x |
r |
| 82 |
} |
| 1 |
#' Makes a measurement metadata file |
|
| 2 |
#' |
|
| 3 |
#' Make a \code{measure_info.json} file, or add measure entries to an existing one.
|
|
| 4 |
#' |
|
| 5 |
#' @param path Path to the \code{measure_info.json} file, existing or to be created.
|
|
| 6 |
#' @param ... Lists containing individual measure items. See the Measure Entries section. |
|
| 7 |
#' @param info A list containing measurement information to be added. |
|
| 8 |
#' @param references A list containing citation entries. See the Reference Entries section. |
|
| 9 |
#' @param strict Logical; if \code{TRUE}, will only allow recognized entries and values.
|
|
| 10 |
#' @param include_empty Logical; if \code{FALSE}, will omit entries that have not been provided.
|
|
| 11 |
#' @param overwrite_entry Logical; if \code{TRUE}, will replace rather than add to an existing entry.
|
|
| 12 |
#' @param render Path to save a version of \code{path} to, with dynamic entries expanded. See the
|
|
| 13 |
#' Dynamic Entries section. |
|
| 14 |
#' @param overwrite Logical; if \code{TRUE}, will overwrite rather than add to an existing \code{path}.
|
|
| 15 |
#' @param write Logical; if \code{FALSE}, will not write the build or rendered measure info.
|
|
| 16 |
#' @param verbose Logical; if \code{FALSE}, will not display status messages.
|
|
| 17 |
#' @param open_after Logical; if \code{FALSE}, will not open the measure file after writing/updating.
|
|
| 18 |
#' @section Measure Entries: |
|
| 19 |
#' Measure entries are named by the full variable name with any of these entries (if \code{strict}):
|
|
| 20 |
#' \itemize{
|
|
| 21 |
#' \item \strong{\code{measure}}: Name of the measure.
|
|
| 22 |
#' \item \strong{\code{full_name}}: Full name of the measure, which is also the name of the entry.
|
|
| 23 |
#' \item \strong{\code{short_name}}: Shortest possible display name.
|
|
| 24 |
#' \item \strong{\code{long_name}}: Longer display name.
|
|
| 25 |
#' \item \strong{\code{category}}: Arbitrary category for the measure.
|
|
| 26 |
#' \item \strong{\code{short_description}}: Shortest possible description.
|
|
| 27 |
#' \item \strong{\code{long_description}}: Complete description. Either description can include
|
|
| 28 |
#' TeX-style equations, enclosed in escaped square brackets (e.g., |
|
| 29 |
#' \code{"The equation \\\\[a_{i} = b^\\\\frac{c}{d}\\\\] was used."}; or \code{$...$},
|
|
| 30 |
#' \code{\\\\(...\\\\)}, or \code{\\\\begin{math}...\\\\end{math}}). The final enclosing symbol must be
|
|
| 31 |
#' followed by a space or the end of the string. These are pre-render to MathML with |
|
| 32 |
#' \code{\link[katex]{katex_mathml}}.
|
|
| 33 |
#' \item \strong{\code{statement}}: String with dynamic references to entity features
|
|
| 34 |
#' (e.g., \code{"measure value = {value}"}). References can include:
|
|
| 35 |
#' \itemize{
|
|
| 36 |
#' \item \code{value}: Value of a currently displaying variable at a current time.
|
|
| 37 |
#' \item \code{region_name}: Alias of \code{features.name}.
|
|
| 38 |
#' \item \code{features.<entry>}: An entity feature, coming from \code{entity_info.json} or GeoJSON properties.
|
|
| 39 |
#' All entities have at least \code{name} and \code{id} entries (e.g., \code{"{features.id}"}).
|
|
| 40 |
#' \item \code{variables.<entry>}: A variable feature such as \code{name} which is the same as
|
|
| 41 |
#' \code{full_name} (e.g., \code{"{variables.name}"}).
|
|
| 42 |
#' \item \code{data.<variable>}: The value of another variable at a current time (e.g., \code{"{data.variable_a}"}).
|
|
| 43 |
#' } |
|
| 44 |
#' \item \strong{\code{measure_type}}: Type of the measure's value. Recognized types are displayed in a special way:
|
|
| 45 |
#' \itemize{
|
|
| 46 |
#' \item \code{year} or \code{integer} show as entered (usually as whole numbers). Other numeric
|
|
| 47 |
#' types are rounded to show a set number of digits. |
|
| 48 |
#' \item \code{percent} shows as \code{{value}\%}.
|
|
| 49 |
#' \item \code{minutes} shows as \code{{value} minutes}.
|
|
| 50 |
#' \item \code{dollar} shows as \code{${value}}.
|
|
| 51 |
#' \item \code{internet speed} shows as \code{{value} Mbps}.
|
|
| 52 |
#' } |
|
| 53 |
#' \item \strong{\code{unit}}: Prefix or suffix associated with the measure's type, such as \code{\%} for \code{percent},
|
|
| 54 |
#' or \code{Mbps} for \code{rate}.
|
|
| 55 |
#' \item \strong{\code{time_resolution}}: Temporal resolution of the variable, such as \code{year} or \code{week},
|
|
| 56 |
#' \item \strong{\code{sources}}: A list or list of list containing source information, including any of these entries:
|
|
| 57 |
#' \itemize{
|
|
| 58 |
#' \item \code{name}: Name of the source (such as an organization name).
|
|
| 59 |
#' \item \code{url}: General URL of the source (such as an organization's website).
|
|
| 60 |
#' \item \code{location}: More specific description of the source (such as a the name of a particular data product).
|
|
| 61 |
#' \item \code{location_url}: More direct URL to the resource (such as a page listing data products).
|
|
| 62 |
#' \item \code{date_accessed}: Date of retrieval (arbitrary format).
|
|
| 63 |
#' } |
|
| 64 |
#' \item \strong{\code{citations}}: A vector of reference ids (the names of \code{reference} entries; e.g., \code{c("ref1", "ref3")}).
|
|
| 65 |
#' \item \strong{\code{layer}}: A list specifying an \code{\link{output_map}} overlay:
|
|
| 66 |
#' \itemize{
|
|
| 67 |
#' \item \code{source} (required): A URL to a GeoJSON file, or a list with a \code{url} and \code{time} entry, where
|
|
| 68 |
#' \code{time} conditions the display of the layer on the current selected time. Alternative to a list that specifies time,
|
|
| 69 |
#' the URL can include a dynamic reference to time, if the time values correspond to a component of the URL |
|
| 70 |
#' (e.g., \code{"https://example.com/{time}/points.geojson"}).
|
|
| 71 |
#' \item \code{filter}: A list or list of lists specifying how the elements of the layer should be filtered for this variable:
|
|
| 72 |
#' \itemize{
|
|
| 73 |
#' \item \code{feature}: Name of the layer's property to filter on.
|
|
| 74 |
#' \item \code{operator}: Operator to filter by (e.g., \code{"="} or \code{"!="}).
|
|
| 75 |
#' \item \code{value}: Value to filter by.
|
|
| 76 |
#' } |
|
| 77 |
#' } |
|
| 78 |
#' \item \strong{\code{categories}}: A named list of categories, with any of the other measure entries, or a
|
|
| 79 |
#' \code{default} entry giving a default category name. See the Dynamic Entries section.
|
|
| 80 |
#' \item \strong{\code{variants}}: A named list of variants, with any of the other measure entries, or a
|
|
| 81 |
#' \code{default} entry giving a default variant name. See the Dynamic Entries section.
|
|
| 82 |
#' } |
|
| 83 |
#' @section Dynamic Entries: |
|
| 84 |
#' You may have several closely related variables in a dataset, which share sections of metadata, |
|
| 85 |
#' or have formulaic differences. In cases like this, the \code{categories} and/or \code{variants} entries
|
|
| 86 |
#' can be used along with dynamic notation to construct multiple entries from a single template. |
|
| 87 |
#' |
|
| 88 |
#' Though functionally the same, \code{categories} might include broken-out subsets of some total
|
|
| 89 |
#' (such as race groups, as categories of a total population), whereas \code{variants} may be different
|
|
| 90 |
#' transformations of the same variable (such as raw counts versus percentages). |
|
| 91 |
#' |
|
| 92 |
#' In dynamic entries, \code{{category}} or \code{{variant}} refers to entries in the \code{categories}
|
|
| 93 |
#' or \code{variants} lists. By default, these are replaced with the name of each entries in those lists
|
|
| 94 |
#' (e.g., \code{"variable_{category}"} where \code{categories = "a"} would become \code{"variable_a"}).
|
|
| 95 |
#' A \code{default} entry would change this behavior (e.g., with \code{categories = list(a = list(default = "b")}
|
|
| 96 |
#' that would become \code{"variable_b"}). Adding \code{.name} would force the original behavior (e.g.,
|
|
| 97 |
#' \code{"variable_{category.name}"} would be \code{"variable_a"}). A name of \code{"blank"} is treated as
|
|
| 98 |
#' an empty string. |
|
| 99 |
#' |
|
| 100 |
#' When notation appears in a measure info entry, they will first default to a matching name in the \code{categories}
|
|
| 101 |
#' or \code{variants} list; for example, \code{short_name} in \code{list(short_name = "variable {category}")} with
|
|
| 102 |
#' \code{categories = list(a = list(short_name = "(category a)"))} would become \code{"variable (category a)"}.
|
|
| 103 |
#' To force this behavior, the entry name can be included in the notation (e.g., |
|
| 104 |
#' \code{"{category.short_name}"} would be \code{"variable (category a)"} in any entry).
|
|
| 105 |
#' |
|
| 106 |
#' Only string entries are processed dynamically -- any list-like entries (such as |
|
| 107 |
#' \code{source}, \code{citations}, or \code{layer}) appearing in
|
|
| 108 |
#' \code{categories} or \code{variants} entries will fully replace the base entry.
|
|
| 109 |
#' |
|
| 110 |
#' Dynamic entries can be kept dynamic when passed to a data site, but can be rendered for other uses, |
|
| 111 |
#' where the rendered version will have each dynamic entry replaced with all unique combinations of |
|
| 112 |
#' \code{categories} and \code{variants} entries, assuming both are used in the dynamic entry's name
|
|
| 113 |
#' (e.g., \code{"variable_{category}_{variant}"}). See Examples.
|
|
| 114 |
#' @section Reference Entries: |
|
| 115 |
#' Reference entries can be included in a \code{_references} entry, and should have names corresponding to
|
|
| 116 |
#' those included in any of the measures' \code{citation} entries. These can include any of these entries:
|
|
| 117 |
#' \itemize{
|
|
| 118 |
#' \item \strong{\code{id}}: The reference id, same as the entry name.
|
|
| 119 |
#' \item \strong{\code{author}}: A list or list of lists specifying one or more authors. These can include
|
|
| 120 |
#' entries for \code{given} and \code{family} names.
|
|
| 121 |
#' \item \strong{\code{year}}: Year of the publication.
|
|
| 122 |
#' \item \strong{\code{title}}: Title of the publication.
|
|
| 123 |
#' \item \strong{\code{journal}}: Journal in which the publication appears.
|
|
| 124 |
#' \item \strong{\code{volume}}: Volume number of the journal.
|
|
| 125 |
#' \item \strong{\code{page}}: Page number of the journal.
|
|
| 126 |
#' \item \strong{\code{doi}}: Digital Object Identifier, from which a link is made (\code{https://doi.org/{doi}}).
|
|
| 127 |
#' \item \strong{\code{version}}: Version number of software.
|
|
| 128 |
#' \item \strong{\code{url}}: Link to the publication, alternative to a DOI.
|
|
| 129 |
#' } |
|
| 130 |
#' @examples |
|
| 131 |
#' path <- tempfile() |
|
| 132 |
#' |
|
| 133 |
#' # make an initial file |
|
| 134 |
#' data_measure_info(path, "measure name" = list( |
|
| 135 |
#' measure = "measure name", |
|
| 136 |
#' full_name = "prefix:measure name", |
|
| 137 |
#' short_description = "A measure.", |
|
| 138 |
#' statement = "This entity has {value} measure units."
|
|
| 139 |
#' ), verbose = FALSE) |
|
| 140 |
#' |
|
| 141 |
#' # add another measure to that |
|
| 142 |
#' measure_info <- data_measure_info(path, "measure two" = list( |
|
| 143 |
#' measure = "measure two", |
|
| 144 |
#' full_name = "prefix:measure two", |
|
| 145 |
#' short_description = "Another measure.", |
|
| 146 |
#' statement = "This entity has {value} measure units."
|
|
| 147 |
#' ), verbose = FALSE) |
|
| 148 |
#' names(measure_info) |
|
| 149 |
#' |
|
| 150 |
#' # add a dynamic measure, and make a rendered version |
|
| 151 |
#' measure_info_rendered <- data_measure_info( |
|
| 152 |
#' path, |
|
| 153 |
#' "measure {category} {variant.name}" = list(
|
|
| 154 |
#' measure = "measure {category}",
|
|
| 155 |
#' full_name = "{variant}:measure {category}",
|
|
| 156 |
#' short_description = "Another measure ({category}; {variant}).",
|
|
| 157 |
#' statement = "This entity has {value} {category} {variant}s.",
|
|
| 158 |
#' categories = c("a", "b"),
|
|
| 159 |
#' variants = list(u1 = list(default = "U1"), u2 = list(default = "U2")) |
|
| 160 |
#' ), |
|
| 161 |
#' render = TRUE, verbose = FALSE |
|
| 162 |
#' ) |
|
| 163 |
#' names(measure_info_rendered) |
|
| 164 |
#' measure_info_rendered[["measure a u1"]]$statement |
|
| 165 |
#' @return An invisible list containing measurement metadata (the rendered version if made). |
|
| 166 |
#' @export |
|
| 167 | ||
| 168 |
data_measure_info <- function( |
|
| 169 |
path, |
|
| 170 |
..., |
|
| 171 |
info = list(), |
|
| 172 |
references = list(), |
|
| 173 |
strict = FALSE, |
|
| 174 |
include_empty = TRUE, |
|
| 175 |
overwrite_entry = FALSE, |
|
| 176 |
render = NULL, |
|
| 177 |
overwrite = FALSE, |
|
| 178 |
write = TRUE, |
|
| 179 |
verbose = TRUE, |
|
| 180 |
open_after = interactive() |
|
| 181 |
) {
|
|
| 182 | 19x |
if (write) {
|
| 183 | 7x |
if (missing(path) || !is.character(path)) {
|
| 184 | ! |
cli_abort("enter a path to the measure_info.json file as {.arg path}")
|
| 185 |
} |
|
| 186 | 7x |
dir.create(dirname(path), FALSE, TRUE) |
| 187 |
} |
|
| 188 | 19x |
built <- list() |
| 189 | 19x |
if (!overwrite && is.character(path) && file.exists(path)) {
|
| 190 | 18x |
if (verbose) {
|
| 191 | 5x |
cli_bullets(c(i = "updating existing file: {.path {basename(path)}}"))
|
| 192 |
} |
|
| 193 | 18x |
built <- jsonlite::read_json(path) |
| 194 | 18x |
if (all(c("measure", "measure_type") %in% names(built))) {
|
| 195 | ! |
built <- list(built) |
| 196 | ! |
names(built) <- built[[1]]$measure |
| 197 |
} |
|
| 198 |
} |
|
| 199 | 19x |
if (length(references)) {
|
| 200 | 2x |
references <- c(references, built$`_references`) |
| 201 | 2x |
references <- references[!duplicated(names(references))] |
| 202 | 2x |
built$`_references` <- references |
| 203 |
} else {
|
|
| 204 | 17x |
references <- built$`_references` |
| 205 |
} |
|
| 206 | 19x |
defaults <- list( |
| 207 | 19x |
measure = "", |
| 208 | 19x |
full_name = "", |
| 209 | 19x |
short_name = "", |
| 210 | 19x |
long_name = "", |
| 211 | 19x |
category = "", |
| 212 | 19x |
short_description = "", |
| 213 | 19x |
long_description = "", |
| 214 | 19x |
statement = "", |
| 215 | 19x |
measure_type = "", |
| 216 | 19x |
unit = "", |
| 217 | 19x |
time_resolution = "", |
| 218 | 19x |
sources = list(), |
| 219 | 19x |
citations = list(), |
| 220 | 19x |
layer = list() |
| 221 |
) |
|
| 222 | 19x |
if (!is.list(info)) {
|
| 223 | ! |
info <- sapply(info, function(name) list()) |
| 224 |
} |
|
| 225 | 19x |
info <- c(list(...), info) |
| 226 | 19x |
if (length(info) && is.null(names(info))) {
|
| 227 | ! |
cli_abort("supplied measure entries must be named")
|
| 228 |
} |
|
| 229 | 19x |
for (n in names(info)) {
|
| 230 | 10x |
if (overwrite_entry || is.null(built[[n]])) {
|
| 231 | 7x |
l <- info[[n]] |
| 232 |
} else {
|
|
| 233 | 3x |
l <- c(info[[n]], built[[n]]) |
| 234 | 3x |
l <- l[!duplicated(names(l))] |
| 235 |
} |
|
| 236 | 10x |
if (is.null(l$full_name)) {
|
| 237 | 3x |
l$full_name <- n |
| 238 |
} |
|
| 239 | 10x |
if (strict) {
|
| 240 | 1x |
su <- names(l) %in% names(defaults) |
| 241 | 1x |
if (verbose && any(!su)) {
|
| 242 | 1x |
cli_warn(paste0( |
| 243 | 1x |
"unrecognized {?entry/entries} in ",
|
| 244 | 1x |
n, |
| 245 | 1x |
": {names(l)[!su]}"
|
| 246 |
)) |
|
| 247 |
} |
|
| 248 | 1x |
if (include_empty) {
|
| 249 | ! |
for (e in names(l)) {
|
| 250 | ! |
if (!is.null(defaults[[e]])) {
|
| 251 | ! |
defaults[[e]] <- l[[e]] |
| 252 |
} |
|
| 253 |
} |
|
| 254 | ! |
l <- defaults |
| 255 |
} else {
|
|
| 256 | 1x |
l <- l[su] |
| 257 |
} |
|
| 258 | 9x |
} else if (include_empty) {
|
| 259 | 8x |
su <- !names(defaults) %in% names(l) |
| 260 | 6x |
if (any(su)) l <- c(l, defaults[su]) |
| 261 |
} |
|
| 262 | 10x |
if (!is.null(l$categories) && !is.list(l$categories)) {
|
| 263 | 1x |
l$categories <- structure( |
| 264 | 1x |
lapply(l$categories, function(e) list(default = e)), |
| 265 | 1x |
names = l$categories |
| 266 |
) |
|
| 267 |
} |
|
| 268 | 10x |
if (!is.null(l$variants) && !is.list(l$variants)) {
|
| 269 | ! |
l$variants <- structure( |
| 270 | ! |
lapply(l$variants, function(e) list(default = e)), |
| 271 | ! |
names = l$categories |
| 272 |
) |
|
| 273 |
} |
|
| 274 | 10x |
if (verbose && !is.null(l$citations)) {
|
| 275 | 8x |
su <- !l$citations %in% names(references) |
| 276 | 8x |
if (any(su)) {
|
| 277 | 2x |
cli_warn( |
| 278 | 2x |
"no matching reference entry for {.val {l$citations[su]}} in {.val {n}}"
|
| 279 |
) |
|
| 280 |
} |
|
| 281 |
} |
|
| 282 | 10x |
built[[n]] <- l |
| 283 |
} |
|
| 284 | 19x |
built <- built[order(grepl("^_", names(built)))]
|
| 285 | 19x |
if (write) {
|
| 286 | 7x |
if (verbose) {
|
| 287 | 6x |
cli_bullets(c(i = "writing info to {.path {path}}"))
|
| 288 |
} |
|
| 289 | 7x |
jsonlite::write_json(built, path, auto_unbox = TRUE, pretty = TRUE) |
| 290 |
} |
|
| 291 | 19x |
if (!is.null(render)) {
|
| 292 | 13x |
expanded <- list() |
| 293 | 13x |
for (name in names(built)) {
|
| 294 | 37x |
expanded <- c( |
| 295 | 37x |
expanded, |
| 296 | 37x |
if (grepl("{", name, fixed = TRUE)) {
|
| 297 | 11x |
render_info(built[name]) |
| 298 |
} else {
|
|
| 299 | 26x |
structure(list(built[[name]]), names = name) |
| 300 |
} |
|
| 301 |
) |
|
| 302 |
} |
|
| 303 | 13x |
changed <- !identical(built, expanded) |
| 304 | 13x |
built <- expanded |
| 305 | 13x |
if (write && changed) {
|
| 306 | 1x |
path <- if (is.character(render)) {
|
| 307 | ! |
render |
| 308 |
} else {
|
|
| 309 | 1x |
sub("\\.json", "_rendered.json", path, TRUE)
|
| 310 |
} |
|
| 311 | 1x |
if (verbose) {
|
| 312 | 1x |
cli_bullets(c(i = "writing rendered info to {.path {path}}"))
|
| 313 |
} |
|
| 314 | 1x |
jsonlite::write_json(built, path, auto_unbox = TRUE, pretty = TRUE) |
| 315 |
} |
|
| 316 |
} |
|
| 317 | 19x |
if (open_after) {
|
| 318 | ! |
navigateToFile(path) |
| 319 |
} |
|
| 320 | 19x |
invisible(built) |
| 321 |
} |
| 1 |
#' Add an input rule to a website |
|
| 2 |
#' |
|
| 3 |
#' Specifies if-then conditions for inputs; that is, when one input is changed, change another based on |
|
| 4 |
#' the entered conditions. |
|
| 5 |
#' |
|
| 6 |
#' @param condition A string representing the condition (e.g., "input_a == 'a'"), with multiple conditions |
|
| 7 |
#' separated by \code{"&"}. If all conditions are \code{TRUE}, all \code{effects} will be set.
|
|
| 8 |
#' @param effects A list with names corresponding to input IDs, and values of what they should be set to. |
|
| 9 |
#' @examples |
|
| 10 |
#' \dontrun{
|
|
| 11 |
#' input_select("input_a", c("a", "b", "c"))
|
|
| 12 |
#' input_slider("input_b", c(0, 10))
|
|
| 13 |
#' input_rule("input_a == 'a' && input_b != 0", list(input_b = 10))
|
|
| 14 |
#' } |
|
| 15 |
#' @return The entered condition and effects. |
|
| 16 |
#' @export |
|
| 17 | ||
| 18 |
input_rule <- function(condition, effects) {
|
|
| 19 | 3x |
conditions <- substitute(condition) |
| 20 | 3x |
if (!is.character(condition)) {
|
| 21 | ! |
condition <- deparse(condition) |
| 22 |
} |
|
| 23 | 3x |
r <- list(condition = parse_rule(condition), effects = as.list(effects)) |
| 24 | 3x |
caller <- parent.frame() |
| 25 |
if ( |
|
| 26 | 3x |
!is.null(attr(caller, "name")) && |
| 27 | 3x |
attr(caller, "name") == "community_site_parts" |
| 28 |
) {
|
|
| 29 | 1x |
caller$rules <- c(caller$rules, list(r)) |
| 30 |
} |
|
| 31 | 3x |
r |
| 32 |
} |
| 1 |
#' Adds credits to a website |
|
| 2 |
#' |
|
| 3 |
#' Adds a credits section, which is automatically filled with the libraries used. |
|
| 4 |
#' These can be added to or edited. |
|
| 5 |
#' |
|
| 6 |
#' @param add A list of credits to add. Each credit should be a list with at least entries for \code{"name"}
|
|
| 7 |
#' and \code{"url"}, and optionally a \code{"version"} and/or \code{"description"}. These can be named,
|
|
| 8 |
#' which will overwrite other credits with the same name. |
|
| 9 |
#' @param exclude Names of automatic credits to exclude. The automatic credits are \code{"bootstrap"},
|
|
| 10 |
#' \code{"leaflet"} (from \code{\link{output_map}}), \code{"plotly"} (from \code{\link{output_plot}}), and
|
|
| 11 |
#' \code{"datatables"} (from \code{\link{output_table}}).
|
|
| 12 |
#' @examples |
|
| 13 |
#' \dontrun{
|
|
| 14 |
#' # adds an institution credit, and excludes the default colorbrewer credit |
|
| 15 |
#' output_credits( |
|
| 16 |
#' list(name = "Institution", url = "https://example.com", description = "The institution."), |
|
| 17 |
#' "colorbrewer" |
|
| 18 |
#' ) |
|
| 19 |
#' } |
|
| 20 |
#' @return A character vector of the contents to be added. |
|
| 21 |
#' @export |
|
| 22 | ||
| 23 |
output_credits <- function(add = NULL, exclude = NULL) {
|
|
| 24 | 3x |
caller <- parent.frame() |
| 25 | 3x |
building <- !is.null(attr(caller, "name")) && |
| 26 | 3x |
attr(caller, "name") == "community_site_parts" |
| 27 | 3x |
id <- paste0("credits", caller$uid)
|
| 28 | 3x |
r <- paste0( |
| 29 | 3x |
'<div id="', |
| 30 | 3x |
id, |
| 31 | 3x |
'" class="auto-output credits" data-autoType="credits"></div>' |
| 32 |
) |
|
| 33 | 3x |
if (building) {
|
| 34 | 1x |
caller$content <- c(caller$content, r) |
| 35 | 1x |
if (!is.null(add) || !is.null(exclude)) {
|
| 36 | 1x |
if (!is.null(names(add)) && "name" %in% names(add)) {
|
| 37 | 1x |
add <- list(add) |
| 38 |
} |
|
| 39 | 1x |
caller$credit_output[[id]] <- list(add = add, exclude = exclude) |
| 40 |
} |
|
| 41 | 1x |
caller$uid <- caller$uid + 1 |
| 42 |
} |
|
| 43 | 3x |
r |
| 44 |
} |
| 1 |
#' Add a guided tour to a website |
|
| 2 |
#' |
|
| 3 |
#' Adds a set of instructions that will guide a user through a process. |
|
| 4 |
#' |
|
| 5 |
#' Tutorials take over control of the interface to walk the user through some process. |
|
| 6 |
#' |
|
| 7 |
#' @param ... Lists specifying each tutorial, or a single list of such lists: |
|
| 8 |
#' \itemize{
|
|
| 9 |
#' \item \strong{\code{name}}: Short name used for reference (as in links). Taken from
|
|
| 10 |
#' the name of entries if omitted. |
|
| 11 |
#' \item \strong{\code{title}}: Display title of the tutorial.
|
|
| 12 |
#' \item \strong{\code{description}}: A description of what the tutorial will do.
|
|
| 13 |
#' \item \strong{\code{steps}} (required): A list containing step specifications:
|
|
| 14 |
#' \itemize{
|
|
| 15 |
#' \item \strong{\code{description}}: Text description to accompany the step.
|
|
| 16 |
#' \item \strong{\code{focus}}: Query selector for the element to focus on (e.g., \code{"#input_a"}).
|
|
| 17 |
#' \item \strong{\code{option}}: Name (value) of an option in a dropdown menu to highlight.
|
|
| 18 |
#' \item \strong{\code{before}}: A vector of actions to perform before showing the step, where
|
|
| 19 |
#' names are of input elements, and values are either \code{"click"} to click on the element
|
|
| 20 |
#' (mainly for elements with toggleable menus -- elements that do not accept a value will always |
|
| 21 |
#' be clicked), \code{"reset"} to reset the input, or a value to set the input to
|
|
| 22 |
#' (e.g., \code{c("input_a" = "a"}). An unnamed actions or values will apply to the \code{focus} element.
|
|
| 23 |
#' If \code{focus} or the named element has options and \code{option} is included, \code{"set"},
|
|
| 24 |
#' will set those options. If a dialogue-like element is open, \code{"close"} will close it.
|
|
| 25 |
#' \item \strong{\code{after}}: A vector of actions to perform after the step has advanced,
|
|
| 26 |
#' before the next step starts (if any). |
|
| 27 |
#' \item \strong{\code{wait}}: Number of milliseconds to wait before starting the step. Useful
|
|
| 28 |
#' to add time to allow loads or animations to finish; defaults to \code{400}.
|
|
| 29 |
#' \item \strong{\code{time}}: Number of seconds to wait before auto-advancing. If omitted,
|
|
| 30 |
#' will not auto-advance. |
|
| 31 |
#' \item \strong{\code{disable_continue}}: Logical; if \code{TRUE}, will disable the continue button.
|
|
| 32 |
#' } |
|
| 33 |
#' \item \strong{\code{reset}}: Logical; if \code{TRUE}, will reset the interface
|
|
| 34 |
#' before starting the tutorial. |
|
| 35 |
#' } |
|
| 36 |
#' @param button Text to show a button to show the tutorials listing menu, or \code{FALSE}
|
|
| 37 |
#' to not create a button. |
|
| 38 |
#' @param id Unique ID of the button element to be created. |
|
| 39 |
#' @param class Additional class names to add to the button element. |
|
| 40 |
#' @param note Text to display as a tooltip for the button. |
|
| 41 |
#' @examples |
|
| 42 |
#' page_tutorials( |
|
| 43 |
#' use_menu = list( |
|
| 44 |
#' title = "Use Settings Menu", |
|
| 45 |
#' steps = list( |
|
| 46 |
#' list( |
|
| 47 |
#' description = "Click on the settings button.", |
|
| 48 |
#' focus = "#navbar_menu .nav-item:nth-child(3)", |
|
| 49 |
#' after = "click" |
|
| 50 |
#' ), |
|
| 51 |
#' list( |
|
| 52 |
#' description = "Locate setting A.", |
|
| 53 |
#' focus = "setting.a" |
|
| 54 |
#' ) |
|
| 55 |
#' ) |
|
| 56 |
#' ) |
|
| 57 |
#' ) |
|
| 58 |
#' @return A character vector of the content to be added. |
|
| 59 |
#' @export |
|
| 60 | ||
| 61 |
page_tutorials <- function( |
|
| 62 |
..., |
|
| 63 |
button = "Tutorials", |
|
| 64 |
id = NULL, |
|
| 65 |
class = NULL, |
|
| 66 |
note = NULL |
|
| 67 |
) {
|
|
| 68 | 3x |
caller <- parent.frame() |
| 69 | 3x |
building <- !is.null(attr(caller, "name")) && |
| 70 | 3x |
attr(caller, "name") == "community_site_parts" |
| 71 | 3x |
parts <- new.env() |
| 72 | 3x |
attr(parts, "name") <- "community_site_parts" |
| 73 | 3x |
tutorials <- list(...) |
| 74 | 3x |
if (length(tutorials) == 1) {
|
| 75 | 2x |
if ("name" %in% names(tutorials)) {
|
| 76 | ! |
tutorials <- list(tutorials) |
| 77 | 2x |
} else if (is.null(names(tutorials))) {
|
| 78 | ! |
tutorials <- unlist(tutorials, recursive = FALSE) |
| 79 |
} |
|
| 80 |
} |
|
| 81 | 3x |
for (i in seq_along(tutorials)) {
|
| 82 | 2x |
if (is.null(tutorials[[i]]$name)) {
|
| 83 | 2x |
tutorials[[i]]$name <- if (is.null(names(tutorials)[i])) {
|
| 84 | ! |
paste("tutorial", i)
|
| 85 |
} else {
|
|
| 86 | 2x |
names(tutorials)[i] |
| 87 |
} |
|
| 88 |
} |
|
| 89 | 2x |
tutorials[[i]]$steps <- lapply(tutorials[[i]]$steps, function(s) {
|
| 90 | 4x |
if (!is.null(s$before)) {
|
| 91 | ! |
s$before <- as.list(s$before) |
| 92 |
} |
|
| 93 | 4x |
if (!is.null(s$after)) {
|
| 94 | ! |
s$after <- as.list(s$after) |
| 95 |
} |
|
| 96 | 4x |
s |
| 97 |
}) |
|
| 98 |
} |
|
| 99 | 3x |
names(tutorials) <- vapply(tutorials, "[[", "", "name") |
| 100 | 3x |
r <- c( |
| 101 | 3x |
'<div class="wrapper button-wrapper">', |
| 102 | 3x |
paste0( |
| 103 | 3x |
'<button type="button" data-bs-toggle="modal" data-bs-target="#community_tutorials_menu" class="btn', |
| 104 | 3x |
if (!is.null(class)) paste("", class),
|
| 105 |
'"', |
|
| 106 | 3x |
if (!is.null(id)) paste0(' id="', id, '"'),
|
| 107 | 3x |
if (!is.null(note)) paste0(' aria-description="', note, '"'),
|
| 108 |
">", |
|
| 109 | 3x |
button, |
| 110 | 3x |
"</button>" |
| 111 |
), |
|
| 112 | 3x |
"</div>" |
| 113 |
) |
|
| 114 | 3x |
if (building) {
|
| 115 | 1x |
if (is.character(button)) {
|
| 116 | 1x |
caller$content <- c(caller$content, r) |
| 117 |
} |
|
| 118 | 1x |
caller$tutorials <- c(caller$tutorials, tutorials) |
| 119 |
} |
|
| 120 | 3x |
r |
| 121 |
} |
| 1 |
#' Create a datapackage.json template |
|
| 2 |
#' |
|
| 3 |
#' Initialize dataset documentation with a \code{datapackage.json} template, based on a
|
|
| 4 |
#' \href{https://specs.frictionlessdata.io/data-package}{Data Package} standard.
|
|
| 5 |
#' |
|
| 6 |
#' @param name A unique name for the dataset; allowed characters are \code{[a-z._/-]}.
|
|
| 7 |
#' @param title A display name for the dataset; if not specified, will be a formatted version of \code{name}.
|
|
| 8 |
#' @param dir Directory in which to save the \code{datapackage.json} file.
|
|
| 9 |
#' @param ... passes arguments to \code{\link{data_add}}.
|
|
| 10 |
#' @param write Logical; if \code{FALSE}, the package object will not be written to a file.
|
|
| 11 |
#' @param overwrite Logical; if \code{TRUE} and \code{write} is \code{TRUE}, an existing
|
|
| 12 |
#' \code{datapackage.json} file will be overwritten.
|
|
| 13 |
#' @param quiet Logical; if \code{TRUE}, will not print messages or navigate to files.
|
|
| 14 |
#' @examples |
|
| 15 |
#' \dontrun{
|
|
| 16 |
#' # make a template datapackage.json file in the current working directory |
|
| 17 |
#' init_data("mtcars", "Motor Trend Car Road Tests")
|
|
| 18 |
#' } |
|
| 19 |
#' @return An invisible list with the content written to the \code{datapackage.json} file.
|
|
| 20 |
#' @seealso Add basic information about a dataset with \code{\link{data_add}}.
|
|
| 21 |
#' @export |
|
| 22 | ||
| 23 |
init_data <- function( |
|
| 24 |
name, |
|
| 25 |
title = name, |
|
| 26 |
dir = ".", |
|
| 27 |
..., |
|
| 28 |
write = TRUE, |
|
| 29 |
overwrite = FALSE, |
|
| 30 |
quiet = !interactive() |
|
| 31 |
) {
|
|
| 32 | 5x |
if (missing(name)) {
|
| 33 | ! |
cli_abort("{.arg name} must be specified")
|
| 34 |
} |
|
| 35 | 5x |
package <- list( |
| 36 | 5x |
name = name, |
| 37 | 5x |
title = if (title == name) {
|
| 38 | 4x |
gsub("\\b(\\w)", "\\U\\1", gsub("[._/-]", " ", name), perl = TRUE)
|
| 39 |
} else {
|
|
| 40 | 1x |
title |
| 41 |
}, |
|
| 42 | 5x |
licence = list( |
| 43 | 5x |
url = "http://opendatacommons.org/licenses/pddl", |
| 44 | 5x |
name = "Open Data Commons Public Domain", |
| 45 | 5x |
version = "1.0", |
| 46 | 5x |
id = "odc-pddl" |
| 47 |
), |
|
| 48 | 5x |
resources = list() |
| 49 |
) |
|
| 50 | 5x |
package_path <- normalizePath(paste0(dir, "/datapackage.json"), "/", FALSE) |
| 51 | 5x |
if (write && !overwrite && file.exists(package_path)) {
|
| 52 | ! |
cli_abort(c( |
| 53 | ! |
"datapackage ({.path {package_path}}) already exists",
|
| 54 | ! |
i = "add {.code overwrite = TRUE} to overwrite it"
|
| 55 |
)) |
|
| 56 |
} |
|
| 57 | 5x |
if (length(list(...))) {
|
| 58 | 1x |
package$resources <- data_add(..., dir = dir, write = FALSE) |
| 59 |
} |
|
| 60 | 5x |
if (write) {
|
| 61 | 4x |
if (!dir.exists(dir)) {
|
| 62 | ! |
dir.create(dir, recursive = TRUE) |
| 63 |
} |
|
| 64 | 4x |
jsonlite::write_json( |
| 65 | 4x |
package, |
| 66 | 4x |
package_path, |
| 67 | 4x |
auto_unbox = TRUE, |
| 68 | 4x |
digits = 6, |
| 69 | 4x |
pretty = TRUE |
| 70 |
) |
|
| 71 | 4x |
if (!quiet) {
|
| 72 | ! |
cli_bullets(c( |
| 73 | ! |
v = "created metadata template for {name}:",
|
| 74 | ! |
"*" = paste0("{.path ", package_path, "}")
|
| 75 |
)) |
|
| 76 | ! |
navigateToFile(package_path) |
| 77 |
} |
|
| 78 |
} |
|
| 79 | 5x |
invisible(package) |
| 80 |
} |
| 1 |
#' Add a number input to a website |
|
| 2 |
#' |
|
| 3 |
#' Adds an direct number input element to a website. |
|
| 4 |
#' |
|
| 5 |
#' @param label Label of the input for the user. |
|
| 6 |
#' @param id Unique ID of the element to be created. |
|
| 7 |
#' @param ... Other attributes to add to the input. |
|
| 8 |
#' @param default Default value of the input, the ID of an input to use as the default, or \code{"min"} or \code{"max"}
|
|
| 9 |
#' to default to the current minimum or maximum value. |
|
| 10 |
#' @param variable The name of a variable or ID of a variable selector to get a range from. |
|
| 11 |
#' @param min The smallest allowed value. |
|
| 12 |
#' @param max The largest allowed value. |
|
| 13 |
#' @param step Amount to increase or decrease the value by when changed with arrows. |
|
| 14 |
#' @param type Name of the input's type -- other number-based types like \code{date} might make sense.
|
|
| 15 |
#' @param class Class names to add to the input's list. |
|
| 16 |
#' @param note Text to display as a tooltip for the input. |
|
| 17 |
#' @param dataview ID of a \code{\link{input_dataview}}, to use as a source of variables.
|
|
| 18 |
#' @param floating_label Logical; if \code{FALSE}, labels are separate from their input elements.
|
|
| 19 |
#' @param buttons Logical; if \code{TRUE}, adds increment and decrement buttons to the sides of the input.
|
|
| 20 |
#' @param show_range Logical; if \code{TRUE}, adds min and max indicators around the input field.
|
|
| 21 |
#' @examples |
|
| 22 |
#' \dontrun{
|
|
| 23 |
#' input_text("entered_text", "Enter Text:")
|
|
| 24 |
#' } |
|
| 25 |
#' @return A character vector of the contents to be added. |
|
| 26 |
#' @export |
|
| 27 | ||
| 28 |
input_number <- function( |
|
| 29 |
label, |
|
| 30 |
id = label, |
|
| 31 |
..., |
|
| 32 |
default = NULL, |
|
| 33 |
variable = NULL, |
|
| 34 |
min = NULL, |
|
| 35 |
max = NULL, |
|
| 36 |
step = NULL, |
|
| 37 |
type = "number", |
|
| 38 |
class = NULL, |
|
| 39 |
note = NULL, |
|
| 40 |
dataview = NULL, |
|
| 41 |
floating_label = TRUE, |
|
| 42 |
buttons = FALSE, |
|
| 43 |
show_range = FALSE |
|
| 44 |
) {
|
|
| 45 | 3x |
id <- gsub("\\s", "", id)
|
| 46 | 3x |
r <- c( |
| 47 | 3x |
if (buttons || show_range) '<div class="wrapper number-input-row">', |
| 48 | 3x |
if (show_range) {
|
| 49 | ! |
paste0( |
| 50 | ! |
'<div><button role="button" label="set value to min" class="text-muted indicator-min"><span>', |
| 51 | ! |
min, |
| 52 | ! |
"</span></button></div>" |
| 53 |
) |
|
| 54 |
}, |
|
| 55 | 3x |
if (buttons) {
|
| 56 | ! |
'<button role="button" label="decrease value" class="btn number-down"><</button>' |
| 57 |
}, |
|
| 58 | 3x |
paste0( |
| 59 | 3x |
'<div class="wrapper text-wrapper', |
| 60 | 3x |
if (floating_label) " form-floating", |
| 61 |
'">' |
|
| 62 |
), |
|
| 63 | 3x |
if (!floating_label) paste0('<label for="', id, '">', label, "</label>"),
|
| 64 | 3x |
paste0( |
| 65 | 3x |
c( |
| 66 | 3x |
'<input type="', |
| 67 | 3x |
type, |
| 68 |
'"', |
|
| 69 | 3x |
' id="', |
| 70 | 3x |
id, |
| 71 |
'"', |
|
| 72 | 3x |
if (!is.null(default)) paste0(' data-default="', default, '"'),
|
| 73 | 3x |
if (!is.null(note)) paste0(' aria-description="', note, '"'),
|
| 74 | 3x |
if (!is.null(variable)) paste0(' data-variable="', variable, '"'),
|
| 75 | 3x |
if (!is.null(min)) paste0(' min="', min, '"'),
|
| 76 | 3x |
if (!is.null(max)) paste0(' max="', max, '"'),
|
| 77 | 3x |
if (!is.null(step)) paste0(' step="', step, '"'),
|
| 78 | 3x |
if (!is.null(dataview)) paste0(' data-view="', dataview, '"'),
|
| 79 | 3x |
unlist(list(...)), |
| 80 | 3x |
' class="form-control auto-input', |
| 81 | 3x |
if (!is.null(class)) paste("", class),
|
| 82 | 3x |
'" data-autoType="number">', |
| 83 | 3x |
if (floating_label) paste0('<label for="', id, '">', label, "</label>")
|
| 84 |
), |
|
| 85 | 3x |
collapse = "" |
| 86 |
), |
|
| 87 | 3x |
"</div>", |
| 88 | 3x |
if (buttons) {
|
| 89 | ! |
'<button role="button" label="increase value" class="btn number-up">></button>' |
| 90 |
}, |
|
| 91 | 3x |
if (show_range) {
|
| 92 | ! |
paste0( |
| 93 | ! |
'<div><button role="button" label="set value to max" class="text-muted indicator-max"><span>', |
| 94 | ! |
max, |
| 95 | ! |
"</span></button></div>" |
| 96 |
) |
|
| 97 |
}, |
|
| 98 | 3x |
if (buttons || show_range) "</div>" |
| 99 |
) |
|
| 100 | 3x |
caller <- parent.frame() |
| 101 |
if ( |
|
| 102 | 3x |
!is.null(attr(caller, "name")) && |
| 103 | 3x |
attr(caller, "name") == "community_site_parts" |
| 104 |
) {
|
|
| 105 | 1x |
caller$content <- c(caller$content, r) |
| 106 |
} |
|
| 107 | 3x |
r |
| 108 |
} |
| 1 |
#' Add static text elements to a website. |
|
| 2 |
#' |
|
| 3 |
#' Adds regular text elements to a website. |
|
| 4 |
#' |
|
| 5 |
#' @param text A character vector of text to add. Each entry will be a separate element. Text can include |
|
| 6 |
#' links to be embedded, in the form \code{"[text](url)"}.
|
|
| 7 |
#' @param ... Attributes to add to each element. |
|
| 8 |
#' @param tag The tag name of each element. |
|
| 9 |
#' @examples |
|
| 10 |
#' \dontrun{
|
|
| 11 |
#' # regular text |
|
| 12 |
#' page_text("text to be added")
|
|
| 13 |
#' } |
|
| 14 |
#' @return A character vector of the content to be added. |
|
| 15 |
#' @export |
|
| 16 | ||
| 17 |
page_text <- function(text, ..., tag = "p") {
|
|
| 18 | 4x |
n <- length(text) |
| 19 | 4x |
tag <- rep_len(tag, n) |
| 20 | 4x |
atr <- lapply(list(...), function(a) rep_len(as.character(a), n)) |
| 21 | 4x |
r <- unlist( |
| 22 | 4x |
lapply(seq_len(n), function(i) {
|
| 23 | 4x |
txt <- text[i] |
| 24 | 4x |
m <- regmatches(txt, gregexpr("\\[.*?\\]\\(.*?\\)", txt))[[1]]
|
| 25 | 4x |
for (l in m) {
|
| 26 | ! |
parts <- strsplit(substr(l, 2, nchar(l) - 1), "](", fixed = TRUE)[[1]]
|
| 27 | ! |
txt <- sub( |
| 28 | ! |
l, |
| 29 | ! |
paste0( |
| 30 | ! |
'<a target="_blank" rel="noreferrer" href="', |
| 31 | ! |
parts[2], |
| 32 |
'">', |
|
| 33 | ! |
parts[1], |
| 34 | ! |
"</a>" |
| 35 |
), |
|
| 36 | ! |
txt, |
| 37 | ! |
fixed = TRUE |
| 38 |
) |
|
| 39 |
} |
|
| 40 | 4x |
paste0( |
| 41 |
"<", |
|
| 42 | 4x |
tag[i], |
| 43 | 4x |
if (length(atr)) {
|
| 44 | 1x |
paste( |
| 45 |
"", |
|
| 46 | 1x |
paste( |
| 47 | 1x |
paste0(names(atr), '="', vapply(atr, "[[", "", i), '"'), |
| 48 | 1x |
collapse = " " |
| 49 |
) |
|
| 50 |
) |
|
| 51 |
} else {
|
|
| 52 |
"" |
|
| 53 |
}, |
|
| 54 |
">", |
|
| 55 | 4x |
txt, |
| 56 |
"</", |
|
| 57 | 4x |
tag[i], |
| 58 |
">" |
|
| 59 |
) |
|
| 60 |
}), |
|
| 61 | 4x |
use.names = FALSE |
| 62 |
) |
|
| 63 | 4x |
caller <- parent.frame() |
| 64 |
if ( |
|
| 65 | 4x |
!is.null(attr(caller, "name")) && |
| 66 | 4x |
attr(caller, "name") == "community_site_parts" |
| 67 |
) {
|
|
| 68 | 2x |
caller$content <- c(caller$content, r) |
| 69 |
} |
|
| 70 | 4x |
r |
| 71 |
} |
| 1 |
#' Write content to the head of a website |
|
| 2 |
#' |
|
| 3 |
#' Adds to the \code{<head>} tag of a page being build with \code{\link{site_build}}.
|
|
| 4 |
#' |
|
| 5 |
#' @param ... Content to be added to the \code{<head>} tag, such as a \code{<meta>} or \code{<link>} tag.
|
|
| 6 |
#' @param title Text to appear as the site's name (as in a browser tab); added to a \code{<title>} tag.
|
|
| 7 |
#' @param description Text describing the site; added to a \code{<meta>} tag.
|
|
| 8 |
#' @param icon Path to an image for the site's icon. |
|
| 9 |
#' @examples |
|
| 10 |
#' \dontrun{
|
|
| 11 |
#' page_head(title = "Site Name") |
|
| 12 |
#' } |
|
| 13 |
#' @return A character vector of the content to be added. |
|
| 14 |
#' @export |
|
| 15 | ||
| 16 |
page_head <- function(..., title = "", description = "", icon = "") {
|
|
| 17 | 3x |
r <- lapply(list(...), as.character) |
| 18 | 3x |
caller <- parent.frame() |
| 19 | 3x |
building <- !is.null(attr(caller, "name")) && |
| 20 | 3x |
attr(caller, "name") == "community_site_parts" |
| 21 | 3x |
if (building) {
|
| 22 | 1x |
for (e in names(caller$head)) {
|
| 23 | ! |
if (!e %in% names(r)) r[[e]] <- caller$head[[e]] |
| 24 |
} |
|
| 25 |
} |
|
| 26 | 3x |
if (!missing(title)) {
|
| 27 | 3x |
r$title <- c( |
| 28 | 3x |
paste0("<title>", title, "</title>"),
|
| 29 | 3x |
paste0('<meta name="title" content="', title, '">')
|
| 30 |
) |
|
| 31 |
} |
|
| 32 | 3x |
if (!missing(description)) {
|
| 33 | 3x |
r$description <- paste0( |
| 34 | 3x |
'<meta name="description" content="', |
| 35 | 3x |
description, |
| 36 |
'">' |
|
| 37 |
) |
|
| 38 |
} |
|
| 39 | 3x |
if (!missing(icon)) {
|
| 40 | 3x |
r$icon <- paste0('<link rel="icon" href="', icon, '">')
|
| 41 |
} |
|
| 42 | 3x |
if (building) {
|
| 43 | 1x |
caller$head <- r |
| 44 |
} |
|
| 45 | 3x |
r |
| 46 |
} |
| 1 |
#' Add a range slider to a website |
|
| 2 |
#' |
|
| 3 |
#' Adds an input to select within the entered range. |
|
| 4 |
#' |
|
| 5 |
#' @param label Label of the input for the user. |
|
| 6 |
#' @param id Unique id of the element to be created. |
|
| 7 |
#' @param ... Additional attributes to set on the element. |
|
| 8 |
#' @param min The smallest value in the range. |
|
| 9 |
#' @param max The largest value in the range. |
|
| 10 |
#' @param step How much moving the handle adjusts the selected value. |
|
| 11 |
#' @param default Starting value of the slider handle. |
|
| 12 |
#' @param note Text to display as a tooltip for the input. |
|
| 13 |
#' @param dataset The name of an included dataset, where \code{variable} should be looked for; only applies when
|
|
| 14 |
#' there are multiple datasets with the same variable name. |
|
| 15 |
#' @param depends The id of another input on which the options depend; this will take president over \code{dataset}
|
|
| 16 |
#' and \code{variable}, depending on this type of input \code{depends} points to.
|
|
| 17 |
#' @examples |
|
| 18 |
#' \dontrun{
|
|
| 19 |
#' input_slider() |
|
| 20 |
#' } |
|
| 21 |
#' @return A character vector of the contents to be added. |
|
| 22 |
#' @export |
|
| 23 | ||
| 24 |
input_slider <- function( |
|
| 25 |
label, |
|
| 26 |
id = label, |
|
| 27 |
..., |
|
| 28 |
min = 0, |
|
| 29 |
max = 1, |
|
| 30 |
step = 1, |
|
| 31 |
default = max, |
|
| 32 |
note = NULL, |
|
| 33 |
dataset = NULL, |
|
| 34 |
depends = NULL |
|
| 35 |
) {
|
|
| 36 | 3x |
id <- gsub("\\s", "", id)
|
| 37 | 3x |
a <- list(...) |
| 38 | 3x |
r <- c( |
| 39 | 3x |
'<div class="wrapper slider-wrapper">', |
| 40 | 3x |
paste0('<label class="form-label" for="', id, '">', label, "</label>"),
|
| 41 | 3x |
paste0( |
| 42 | 3x |
'<input role="slider" type="range" class="auto-input form-range" data-autoType="number" id="', |
| 43 | 3x |
id, |
| 44 | 3x |
'" data-default=', |
| 45 | 3x |
default, |
| 46 | 3x |
" step=", |
| 47 | 3x |
step, |
| 48 | 3x |
" min=", |
| 49 | 3x |
min, |
| 50 | 3x |
" max=", |
| 51 | 3x |
max, |
| 52 | 3x |
if (!is.null(depends)) {
|
| 53 | ! |
paste0(' data-depends="', depends, '"')
|
| 54 | 3x |
} else if (!is.null(dataset)) {
|
| 55 | ! |
paste0('data-dataset="', dataset, '"')
|
| 56 |
}, |
|
| 57 | 3x |
if (length(a)) {
|
| 58 | ! |
unlist(lapply( |
| 59 | ! |
seq_along(a), |
| 60 | ! |
function(i) paste0(" ", names(a)[i], '="', a[[i]], '"')
|
| 61 |
)) |
|
| 62 |
}, |
|
| 63 | 3x |
if (!is.null(note)) paste0(' aria-description="', note, '"'),
|
| 64 |
">" |
|
| 65 |
), |
|
| 66 | 3x |
paste0('<div class="slider-display"><span>', default, "</span></div>"),
|
| 67 | 3x |
"</div>" |
| 68 |
) |
|
| 69 | 3x |
caller <- parent.frame() |
| 70 |
if ( |
|
| 71 | 3x |
!is.null(attr(caller, "name")) && |
| 72 | 3x |
attr(caller, "name") == "community_site_parts" |
| 73 |
) {
|
|
| 74 | 1x |
caller$content <- c(caller$content, r) |
| 75 |
} |
|
| 76 | 3x |
r |
| 77 |
} |
| 1 |
#' Add an internal variable to a website |
|
| 2 |
#' |
|
| 3 |
#' Creates an internal variable (virtual input), which can be used for display logic. |
|
| 4 |
#' |
|
| 5 |
#' @param id Name of the variable, which can be referred to by other inputs of outputs. |
|
| 6 |
#' @param cases A list of conditions with names specifying conditions, and values to set the variable to |
|
| 7 |
#' in that condition (e.g., \code{list("input_a == 1" = 1)}).
|
|
| 8 |
#' These can also be specified separately with an \code{\link{input_rule}}.
|
|
| 9 |
#' @param default The value to set if no condition is \code{TRUE}.
|
|
| 10 |
#' @param display A list mapping cases names to display names (e.g., \code{list(value = "Value")}).
|
|
| 11 |
#' @examples |
|
| 12 |
#' \dontrun{
|
|
| 13 |
#' input_select("input_a", c("a", "b", "c"))
|
|
| 14 |
#' input_variable("vinput_a", list("input_a == c" = "b"), "a")
|
|
| 15 |
#' |
|
| 16 |
#' # vinput_a will be "a" unless input_a is "c" |
|
| 17 |
#' } |
|
| 18 |
#' @return A version of the resulting variable object. |
|
| 19 |
#' @export |
|
| 20 | ||
| 21 |
input_variable <- function(id, cases, default = "", display = list()) {
|
|
| 22 | 3x |
r <- Filter( |
| 23 | 3x |
length, |
| 24 | 3x |
list( |
| 25 | 3x |
id = id, |
| 26 | 3x |
states = lapply(seq_along(cases), function(i) {
|
| 27 | 6x |
list( |
| 28 | 6x |
condition = parse_rule(names(cases[i])), |
| 29 | 6x |
value = cases[[i]] |
| 30 |
) |
|
| 31 |
}), |
|
| 32 | 3x |
default = default, |
| 33 | 3x |
display = display |
| 34 |
) |
|
| 35 |
) |
|
| 36 | 3x |
caller <- parent.frame() |
| 37 |
if ( |
|
| 38 | 3x |
!is.null(attr(caller, "name")) && |
| 39 | 3x |
attr(caller, "name") == "community_site_parts" |
| 40 |
) {
|
|
| 41 | 1x |
caller$variables <- c(caller$variables, list(r)) |
| 42 |
} |
|
| 43 | 3x |
r |
| 44 |
} |