diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml
index a3ac618..21efb73 100644
--- a/.github/workflows/R-CMD-check.yaml
+++ b/.github/workflows/R-CMD-check.yaml
@@ -29,7 +29,7 @@ jobs:
R_KEEP_PKG_SOURCE: yes
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v5
- uses: r-lib/actions/setup-pandoc@v2
diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml
index ea362c8..1194d5e 100644
--- a/.github/workflows/pkgdown.yaml
+++ b/.github/workflows/pkgdown.yaml
@@ -1,47 +1,41 @@
+# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
on:
push:
branches: [main, master]
+ workflow_dispatch:
name: pkgdown
jobs:
pkgdown:
- runs-on: macOS-latest
+ runs-on: ubuntu-latest
env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
- steps:
- - uses: actions/checkout@v2
+ permissions:
+ contents: write
- - uses: r-lib/actions/setup-r@v1
+ steps:
+ - uses: actions/checkout@v5
- - uses: r-lib/actions/setup-pandoc@v1
+ - uses: r-lib/actions/setup-pandoc@v2
- - name: Query dependencies
- run: |
- install.packages('remotes')
- saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2)
- writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version")
- shell: Rscript {0}
+ - uses: r-lib/actions/setup-r@v2
+ with:
+ use-public-rspm: true
- - name: Cache R packages
- uses: actions/cache@v2
+ - uses: r-lib/actions/setup-r-dependencies@v2
with:
- path: ${{ env.R_LIBS_USER }}
- key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }}
- restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-
-
- - name: Install dependencies
- run: |
- remotes::install_deps(dependencies = TRUE)
- remotes::install_github("ThinkR-open/thinkrtemplate")
- install.packages("pkgdown", type = "binary")
- shell: Rscript {0}
+ extra-packages: any::pkgdown, ThinkR-open/thinkrtemplate
+ needs: website
- - name: Install package
- run: R CMD INSTALL .
+ - name: Build site
+ run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE)
+ shell: Rscript {0}
- - name: Deploy package
- run: |
- git config --local user.email "actions@github.com"
- git config --local user.name "GitHub Actions"
- Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)'
+ - name: Deploy to GitHub pages
+ if: github.event_name != 'pull_request'
+ uses: JamesIves/github-pages-deploy-action@v4.5.0
+ with:
+ clean: false
+ branch: gh-pages
+ folder: docs
diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml
index 0182b4e..731cc6c 100644
--- a/.github/workflows/test-coverage.yaml
+++ b/.github/workflows/test-coverage.yaml
@@ -1,46 +1,30 @@
+# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
on:
push:
- branches:
- - master
+ branches: [main, master]
pull_request:
- branches:
- - master
+ branches: [main, master]
name: test-coverage
jobs:
test-coverage:
- runs-on: macOS-latest
+ runs-on: ubuntu-latest
env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
- steps:
- - uses: actions/checkout@v2
-
- - uses: r-lib/actions/setup-r@v1
- - uses: r-lib/actions/setup-pandoc@v1
-
- - name: Query dependencies
- run: |
- install.packages('remotes')
- saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2)
- writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version")
- shell: Rscript {0}
+ steps:
+ - uses: actions/checkout@v5
- - name: Cache R packages
- uses: actions/cache@v2
+ - uses: r-lib/actions/setup-r@v2
with:
- path: ${{ env.R_LIBS_USER }}
- key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }}
- restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-
+ use-public-rspm: true
- - name: Install dependencies
- run: |
- install.packages(c("remotes"))
- remotes::install_deps(dependencies = TRUE)
- remotes::install_cran("covr")
- shell: Rscript {0}
+ - uses: r-lib/actions/setup-r-dependencies@v2
+ with:
+ extra-packages: any::covr
+ needs: coverage
- name: Test coverage
- run: covr::codecov()
+ run: covr::codecov(quiet = FALSE)
shell: Rscript {0}
diff --git a/DESCRIPTION b/DESCRIPTION
index af6527b..b1faa67 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -36,4 +36,4 @@ Suggests:
Encoding: UTF-8
LazyData: true
Roxygen: list(markdown = TRUE)
-RoxygenNote: 7.1.1
+RoxygenNote: 7.3.3
diff --git a/R/Image.R b/R/Image.R
index a3fbadd..d7a8d96 100644
--- a/R/Image.R
+++ b/R/Image.R
@@ -2,14 +2,25 @@
#'
#' This function returns a random image that can be passed into `renderImage` and `plotOutput`.
#'
-#' @return an image
+#' @param width image width passed through to the rendered `
` tag.
+#' `NULL` (default) leaves the attribute unset (#9).
+#' @param height image height, same semantics as `width` (#9).
+#' @param alt `alt` attribute for accessibility. `NULL` (default) leaves
+#' the attribute unset (#9).
+#'
+#' @return a list compatible with `shiny::renderImage()`: `src`, plus the
+#' `width` / `height` / `alt` attributes when provided.
#'
#' @export
-random_image <- function(){
+random_image <- function(width = NULL, height = NULL, alt = NULL){
l <- list.files(system.file("img", package = "shinipsum"), full.names = TRUE)
img <- normalizePath(sample(l, 1))
tmpimg <- paste(tempfile(), basename(img), sep = "-")
file.copy(img, tmpimg)
- list(src = tmpimg)
+ out <- list(src = tmpimg)
+ if (!is.null(width)) out$width <- width
+ if (!is.null(height)) out$height <- height
+ if (!is.null(alt)) out$alt <- alt
+ out
}
diff --git a/R/Plot.R b/R/Plot.R
index 217f78e..0fdeabe 100644
--- a/R/Plot.R
+++ b/R/Plot.R
@@ -1,8 +1,21 @@
+#' Build the categorical labels used by random_ggplot('bar', n_bars=).
+#' @noRd
+make_bar_labels <- function(n) {
+ if (n <= 26L) {
+ LETTERS[seq_len(n)]
+ } else {
+ sprintf("Cat%02d", seq_len(n))
+ }
+}
+
#' A Random ggplot
#'
#' This function returns a ggplot object, which can be passed to `renderPlot` and `plotOutput`
#'
#' @param type type of the geom. Can be any of "random", "point", "bar", "boxplot","col", "tile", "line", "bin2d", "contour", "density", "density_2d", "dotplot", "hex", "freqpoly", "histogram", "ribbon", "raster", "tile", "violin" and defines the geom of the ggplot. Default is "random", and chooses a random geom for you.
+#' @param n_bars integer, number of bars to draw when `type == "bar"`. When
+#' `NULL` (default), one of the built-in datasets is sampled; otherwise
+#' a synthetic data frame with `n_bars` categories is used (#5).
#'
#' @importFrom ggplot2 ggplot aes geom_point geom_bar scale_color_viridis_d theme_minimal geom_boxplot labs coord_flip geom_tile geom_line facet_grid geom_col scale_fill_viridis_c
#' @importFrom ggplot2 xlim ylim geom_bin2d geom_contour geom_density geom_density_2d geom_dotplot
@@ -18,7 +31,8 @@ random_ggplot <- function(type = c("random", "point", "bar",
"density", "density_2d", "dotplot",
"hex", "freqpoly", "histogram",
"ribbon", "raster", "tile",
- "violin")) {
+ "violin"),
+ n_bars = NULL) {
type_matched <- match.arg(type)
if (type_matched == "random") {
@@ -28,6 +42,27 @@ random_ggplot <- function(type = c("random", "point", "bar",
type_matched <- sample( form, 1 )
}
+ # User asked for a specific number of bars -> short-circuit the
+ # builtin-dataset switch with synthetic data so we hit exactly n_bars (#5).
+ if (type_matched == "bar" && !is.null(n_bars)) {
+ stopifnot(is.numeric(n_bars), length(n_bars) == 1L,
+ !is.na(n_bars), n_bars >= 1L)
+ n_bars <- as.integer(n_bars)
+ df <- data.frame(
+ category = factor(make_bar_labels(n_bars),
+ levels = make_bar_labels(n_bars)),
+ value = sample.int(100L, n_bars, replace = TRUE)
+ )
+ return(
+ ggplot2::ggplot(df) +
+ ggplot2::aes(category, value, fill = category) +
+ ggplot2::geom_col() +
+ ggplot2::scale_fill_viridis_d() +
+ ggplot2::theme_minimal() +
+ ggplot2::theme(legend.position = "none")
+ )
+ }
+
r <- switch(as.character(type_matched),
"point" = sample(0:5, 1),
"bar" = sample(10:11, 1),
@@ -144,7 +179,7 @@ random_ggplot <- function(type = c("random", "point", "bar",
"50" = list(
ggplot(datasets::women) +
aes(height, weight) +
- geom_line(size = 2) +
+ geom_line(linewidth = 2) +
theme_minimal()
),
"51" = list(
diff --git a/R/globals.R b/R/globals.R
index 9560def..5a79f87 100644
--- a/R/globals.R
+++ b/R/globals.R
@@ -13,7 +13,9 @@ utils::globalVariables(unique(c(
"x", "y",
"depth", "cut", "carat", "price", "color",
"year", "level",
- "z", "w"
+ "z", "w",
+ # random_ggplot('bar', n_bars=)
+ "category", "value"
# random dygraphs
# @importFrom datasets mdeaths fdeaths ldeaths nhtemp AirPassengers discoveries presidents austres
diff --git a/dev/SUIVI_ISSUES.md b/dev/SUIVI_ISSUES.md
new file mode 100644
index 0000000..492b58d
--- /dev/null
+++ b/dev/SUIVI_ISSUES.md
@@ -0,0 +1,17 @@
+# Suivi — passe `fix/multiple-issues`
+
+| # | Type | Résumé | Fix | Test |
+|---|---|---|---|---|
+| #13 | bug | `random_ggplot("line")` triggered ggplot2's `size→linewidth` deprecation warning | `geom_line(size = 2)` -> `geom_line(linewidth = 2)` | `tests/testthat/test-no_deprecated_args.R` (build no warning + static sweep) |
+| #9 | feat | `random_image()` couldn't propagate `width` / `height` / `alt` | new params `width = NULL, height = NULL, alt = NULL` (kept default `list(src = ...)` shape) | `tests/testthat/test-image_args.R` |
+| #5 | feat | no way to control the number of bars in `random_ggplot("bar")` | new `n_bars = NULL` parameter — when set, generates a synthetic categorical data frame so the rendered plot has *exactly* that many bars | `tests/testthat/test-bar_n.R` (1 / 3 / 7 / 27 bars + invalid input) |
+
+## Issues envisagées mais non traitées
+
+| # | Pourquoi pas |
+|---|---|
+| #4 | "time series option" — design choice (date axis, density, faceting) à arbitrer avec mainteneur. |
+| #3 | "other lorem ipsum options" — choix de corpora alternatifs (cat ipsum, hipster ipsum…), demande d'inclure de nouveaux assets. |
+| #2 | "Release 0.1.0" — meta. |
+| #1 | `mock_frame()` — feature, demande design. |
+| #10 | `_R_CHECK_USE_CODETOOLS_=false` — dépend du contexte de test downstream. |
diff --git a/man/random_ggplot.Rd b/man/random_ggplot.Rd
index 07a25c6..bf2257f 100644
--- a/man/random_ggplot.Rd
+++ b/man/random_ggplot.Rd
@@ -7,11 +7,16 @@
random_ggplot(
type = c("random", "point", "bar", "boxplot", "col", "tile", "line", "bin2d",
"contour", "density", "density_2d", "dotplot", "hex", "freqpoly", "histogram",
- "ribbon", "raster", "tile", "violin")
+ "ribbon", "raster", "tile", "violin"),
+ n_bars = NULL
)
}
\arguments{
\item{type}{type of the geom. Can be any of "random", "point", "bar", "boxplot","col", "tile", "line", "bin2d", "contour", "density", "density_2d", "dotplot", "hex", "freqpoly", "histogram", "ribbon", "raster", "tile", "violin" and defines the geom of the ggplot. Default is "random", and chooses a random geom for you.}
+
+\item{n_bars}{integer, number of bars to draw when \code{type == "bar"}. When
+\code{NULL} (default), one of the built-in datasets is sampled; otherwise
+a synthetic data frame with \code{n_bars} categories is used (#5).}
}
\value{
a ggplot
diff --git a/man/random_image.Rd b/man/random_image.Rd
index c699a5e..d150da0 100644
--- a/man/random_image.Rd
+++ b/man/random_image.Rd
@@ -4,10 +4,20 @@
\alias{random_image}
\title{A Random Image}
\usage{
-random_image()
+random_image(width = NULL, height = NULL, alt = NULL)
+}
+\arguments{
+\item{width}{image width passed through to the rendered \verb{
} tag.
+\code{NULL} (default) leaves the attribute unset (#9).}
+
+\item{height}{image height, same semantics as \code{width} (#9).}
+
+\item{alt}{\code{alt} attribute for accessibility. \code{NULL} (default) leaves
+the attribute unset (#9).}
}
\value{
-an image
+a list compatible with \code{shiny::renderImage()}: \code{src}, plus the
+\code{width} / \code{height} / \code{alt} attributes when provided.
}
\description{
This function returns a random image that can be passed into \code{renderImage} and \code{plotOutput}.
diff --git a/man/shinipsum-package.Rd b/man/shinipsum-package.Rd
index b0b0ca3..0fa84ce 100644
--- a/man/shinipsum-package.Rd
+++ b/man/shinipsum-package.Rd
@@ -6,8 +6,7 @@
\alias{shinipsum-package}
\title{shinipsum: Lorem-Ipsum-like Helpers for fast Shiny Prototyping}
\description{
-Prototype your shiny apps quickly with these
- Lorem-Ipsum-like Helpers.
+Prototype your shiny apps quickly with these Lorem-Ipsum-like Helpers.
}
\seealso{
Useful links:
diff --git a/tests/testthat/test-bar_n.R b/tests/testthat/test-bar_n.R
new file mode 100644
index 0000000..0682d5c
--- /dev/null
+++ b/tests/testthat/test-bar_n.R
@@ -0,0 +1,33 @@
+test_that("random_ggplot('bar', n_bars=) produces exactly n bars (#5)", {
+ for (n in c(1L, 3L, 7L, 27L)) {
+ p <- random_ggplot("bar", n_bars = n)
+ expect_s3_class(p, "ggplot")
+ # Build the plot and count distinct bars in the rendered data layer.
+ built <- ggplot2::ggplot_build(p)
+ bars <- nrow(built$data[[1]])
+ expect_equal(bars, n,
+ info = paste0("requested ", n, " bars, got ", bars))
+ }
+})
+
+test_that("random_ggplot('bar') without n_bars keeps the legacy datasets behaviour", {
+ old_seed <- if (exists(".Random.seed", envir = .GlobalEnv, inherits = FALSE)) {
+ get(".Random.seed", envir = .GlobalEnv)
+ } else NULL
+ on.exit(
+ if (!is.null(old_seed)) assign(".Random.seed", old_seed, envir = .GlobalEnv)
+ else if (exists(".Random.seed", envir = .GlobalEnv, inherits = FALSE)) {
+ rm(list = ".Random.seed", envir = .GlobalEnv)
+ },
+ add = TRUE
+ )
+ set.seed(1)
+ p <- random_ggplot("bar")
+ expect_s3_class(p, "ggplot")
+})
+
+test_that("random_ggplot rejects nonsense n_bars (#5)", {
+ expect_error(random_ggplot("bar", n_bars = -1))
+ expect_error(random_ggplot("bar", n_bars = "five"))
+ expect_error(random_ggplot("bar", n_bars = c(3, 4)))
+})
diff --git a/tests/testthat/test-image_args.R b/tests/testthat/test-image_args.R
new file mode 100644
index 0000000..3dfc011
--- /dev/null
+++ b/tests/testthat/test-image_args.R
@@ -0,0 +1,20 @@
+test_that("random_image() returns just `src` by default (regression)", {
+ res <- random_image()
+ expect_type(res, "list")
+ expect_named(res, "src")
+ expect_true(file.exists(res$src))
+})
+
+test_that("random_image(width=, height=, alt=) propagates the arguments (#9)", {
+ res <- random_image(width = "100px", height = "80px", alt = "Yo")
+ expect_named(res, c("src", "width", "height", "alt"))
+ expect_equal(res$width, "100px")
+ expect_equal(res$height, "80px")
+ expect_equal(res$alt, "Yo")
+})
+
+test_that("random_image() drops NULL extras (#9)", {
+ res <- random_image(width = "200px")
+ expect_named(res, c("src", "width"))
+ expect_equal(res$width, "200px")
+})
diff --git a/tests/testthat/test-no_deprecated_args.R b/tests/testthat/test-no_deprecated_args.R
new file mode 100644
index 0000000..18a238e
--- /dev/null
+++ b/tests/testthat/test-no_deprecated_args.R
@@ -0,0 +1,26 @@
+test_that("random_ggplot('line') does not emit ggplot2 'size' deprecation (#13)", {
+ # ggplot2 >= 3.4 deprecated size= for line geoms in favour of linewidth=.
+ # Force the line geom by passing type = 'line'; either of the two line
+ # variants must be deprecation-clean.
+ old_seed <- if (exists(".Random.seed", envir = .GlobalEnv, inherits = FALSE)) {
+ get(".Random.seed", envir = .GlobalEnv)
+ } else NULL
+ on.exit(
+ if (!is.null(old_seed)) assign(".Random.seed", old_seed, envir = .GlobalEnv)
+ else if (exists(".Random.seed", envir = .GlobalEnv, inherits = FALSE)) {
+ rm(list = ".Random.seed", envir = .GlobalEnv)
+ },
+ add = TRUE
+ )
+ set.seed(42)
+ p <- random_ggplot("line")
+ # Building the plot is what triggers the lifecycle warning, not the
+ # construction. expect_no_warning() requires testthat 3.1.5+.
+ if (utils::packageVersion("testthat") >= "3.1.5") {
+ expect_no_warning(invisible(ggplot2::ggplot_build(p)))
+ } else {
+ msgs <- capture_warnings(invisible(ggplot2::ggplot_build(p)))
+ expect_false(any(grepl("size.*deprecated", msgs)))
+ }
+})
+