feat: add position argument to civilytics_logo() for corner placement
R-CMD-check / R CMD check (push) Successful in 3m37s
R-CMD-check / R CMD check (push) Successful in 3m37s
Supports "bottom-right" (default), "bottom-left", "top-right", and "top-left". The position controls both horizontal alignment of the logo grob and whether it is placed above or below the plot. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,12 +50,20 @@ get_png <- function(filename) {
|
|||||||
#' sizes before composing the plot with the logo. Default `1.1` inflates
|
#' sizes before composing the plot with the logo. Default `1.1` inflates
|
||||||
#' text by ~10 \% to compensate for the viewport shrinkage caused by
|
#' text by ~10 \% to compensate for the viewport shrinkage caused by
|
||||||
#' [gridExtra::arrangeGrob()]. Set to `1` to disable.
|
#' [gridExtra::arrangeGrob()]. Set to `1` to disable.
|
||||||
|
#' @param position Character. Corner placement for the logo: `"bottom-right"`
|
||||||
|
#' (default), `"bottom-left"`, `"top-right"`, or `"top-left"`. Controls
|
||||||
|
#' whether the logo is placed above or below the plot.
|
||||||
#'
|
#'
|
||||||
#' @return a grob with a logo attached to it ready to plot
|
#' @return a grob with a logo attached to it ready to plot
|
||||||
#' @importFrom ggplot2 theme
|
#' @importFrom ggplot2 theme
|
||||||
#' @importFrom gridExtra arrangeGrob
|
#' @importFrom gridExtra arrangeGrob
|
||||||
#' @export
|
#' @export
|
||||||
add_logo <- function(plot, logo, margin_param = NULL, font_scale = 1.1) {
|
add_logo <- function(plot, logo, margin_param = NULL, font_scale = 1.1,
|
||||||
|
position = c("bottom-right", "bottom-left",
|
||||||
|
"top-right", "top-left")) {
|
||||||
|
position <- match.arg(position)
|
||||||
|
at_top <- grepl("top", position)
|
||||||
|
|
||||||
# Inflate text sizes to compensate for arrangeGrob viewport shrinkage.
|
# Inflate text sizes to compensate for arrangeGrob viewport shrinkage.
|
||||||
# All theme text elements use rel() sizing, so scaling the root 'text'
|
# All theme text elements use rel() sizing, so scaling the root 'text'
|
||||||
# element cascades to titles, axis labels, legends, captions, and strips.
|
# element cascades to titles, axis labels, legends, captions, and strips.
|
||||||
@@ -66,19 +74,30 @@ add_logo <- function(plot, logo, margin_param = NULL, font_scale = 1.1) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(has_caption(plot)) {
|
if (at_top) {
|
||||||
cap_lines <- measure_caption(plot)
|
# For top placement, tighten the top margin of the plot
|
||||||
if (!is.null(margin_param)) {
|
if (!is.null(margin_param)) {
|
||||||
plot <- plot + theme(plot.margin = unit(c(7, 7, margin_param, 7), "pt"))
|
plot <- plot + theme(plot.margin = unit(c(margin_param, 7, 7, 7), "pt"))
|
||||||
} else {
|
} else {
|
||||||
plot <- plot + theme(plot.margin = unit(c(7, 7, cap_lines * -52, 7), "pt"))
|
plot <- plot + theme(plot.margin = unit(c(-14, 7, 7, 7), "pt"))
|
||||||
}
|
}
|
||||||
|
arrangeGrob(logo, plot, heights = c(0.1, 0.93),
|
||||||
|
padding = unit(0.1, "line"))
|
||||||
} else {
|
} else {
|
||||||
plot <- plot + theme(plot.margin = unit(c(7, 7, -14, 7), "pt"))
|
# Bottom placement (original behaviour)
|
||||||
|
if(has_caption(plot)) {
|
||||||
|
cap_lines <- measure_caption(plot)
|
||||||
|
if (!is.null(margin_param)) {
|
||||||
|
plot <- plot + theme(plot.margin = unit(c(7, 7, margin_param, 7), "pt"))
|
||||||
|
} else {
|
||||||
|
plot <- plot + theme(plot.margin = unit(c(7, 7, cap_lines * -52, 7), "pt"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
plot <- plot + theme(plot.margin = unit(c(7, 7, -14, 7), "pt"))
|
||||||
|
}
|
||||||
|
arrangeGrob(plot, logo, heights = c(0.93, 0.1),
|
||||||
|
padding = unit(0.1, "line"))
|
||||||
}
|
}
|
||||||
|
|
||||||
arrangeGrob(plot, logo, heights = c(0.93, 0.1),
|
|
||||||
padding = unit(0.1, "line"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#' Measure a ggplot2 object caption
|
#' Measure a ggplot2 object caption
|
||||||
@@ -193,6 +212,9 @@ add_logo_ga <- function(plot_list, logo, nrow = 1, widths = NULL,
|
|||||||
#' @param variant Character. `"light"` (default) uses the dark logo for light
|
#' @param variant Character. `"light"` (default) uses the dark logo for light
|
||||||
#' backgrounds. `"dark"` uses the reverse (light) logo for dark backgrounds
|
#' backgrounds. `"dark"` uses the reverse (light) logo for dark backgrounds
|
||||||
#' (pairs with [theme_civilytics_dark()]).
|
#' (pairs with [theme_civilytics_dark()]).
|
||||||
|
#' @param position Character. Corner placement for the logo: `"bottom-right"`
|
||||||
|
#' (default), `"bottom-left"`, `"top-right"`, or `"top-left"`. Controls
|
||||||
|
#' horizontal alignment of the logo grob.
|
||||||
#'
|
#'
|
||||||
#' @return A ggplot object (class `"gg"`) containing the logo grob.
|
#' @return A ggplot object (class `"gg"`) containing the logo grob.
|
||||||
#' @export
|
#' @export
|
||||||
@@ -200,11 +222,15 @@ add_logo_ga <- function(plot_list, logo, nrow = 1, widths = NULL,
|
|||||||
#' @examples
|
#' @examples
|
||||||
#' logo <- make_logo_grob() # wordmark, light
|
#' logo <- make_logo_grob() # wordmark, light
|
||||||
#' logo <- make_logo_grob("mark", "dark") # mark, dark
|
#' logo <- make_logo_grob("mark", "dark") # mark, dark
|
||||||
|
#' logo <- make_logo_grob(position = "bottom-left") # left-aligned
|
||||||
#' class(logo) # "gg" "ggplot"
|
#' class(logo) # "gg" "ggplot"
|
||||||
make_logo_grob <- function(type = c("wordmark", "mark"),
|
make_logo_grob <- function(type = c("wordmark", "mark"),
|
||||||
variant = c("light", "dark")) {
|
variant = c("light", "dark"),
|
||||||
type <- match.arg(type)
|
position = c("bottom-right", "bottom-left",
|
||||||
variant <- match.arg(variant)
|
"top-right", "top-left")) {
|
||||||
|
type <- match.arg(type)
|
||||||
|
variant <- match.arg(variant)
|
||||||
|
position <- match.arg(position)
|
||||||
|
|
||||||
img_file <- switch(
|
img_file <- switch(
|
||||||
paste(type, variant, sep = "_"),
|
paste(type, variant, sep = "_"),
|
||||||
@@ -214,21 +240,28 @@ make_logo_grob <- function(type = c("wordmark", "mark"),
|
|||||||
mark_dark = "civilytics-mark-reverse.png"
|
mark_dark = "civilytics-mark-reverse.png"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Right-align the logo; the mark is compact so it gets a narrower slice.
|
align_left <- grepl("left", position)
|
||||||
xmin <- if (type == "mark") 0.93 else 0.65
|
|
||||||
|
if (align_left) {
|
||||||
|
xmin <- 0
|
||||||
|
xmax <- if (type == "mark") 0.07 else 0.35
|
||||||
|
} else {
|
||||||
|
xmin <- if (type == "mark") 0.93 else 0.65
|
||||||
|
xmax <- 1
|
||||||
|
}
|
||||||
|
|
||||||
ggplot2::ggplot() +
|
ggplot2::ggplot() +
|
||||||
ggplot2::theme_void() +
|
ggplot2::theme_void() +
|
||||||
ggplot2::annotation_custom(
|
ggplot2::annotation_custom(
|
||||||
get_png(system.file("img", img_file, package = "civilytics")),
|
get_png(system.file("img", img_file, package = "civilytics")),
|
||||||
xmin = xmin, xmax = 1
|
xmin = xmin, xmax = xmax
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#' Add a Civilytics logo to a ggplot (pipe-friendly)
|
#' Add a Civilytics logo to a ggplot (pipe-friendly)
|
||||||
#'
|
#'
|
||||||
#' A convenience wrapper that creates the logo grob and attaches it below the
|
#' A convenience wrapper that creates the logo grob and attaches it to a
|
||||||
#' plot in one call. Designed for use with the base pipe `|>`.
|
#' corner of the plot in one call. Designed for use with the base pipe `|>`.
|
||||||
#'
|
#'
|
||||||
#' **Important:** R's `|>` has *higher* precedence than `+`, so you must
|
#' **Important:** R's `|>` has *higher* precedence than `+`, so you must
|
||||||
#' wrap the ggplot chain in parentheses before piping:
|
#' wrap the ggplot chain in parentheses before piping:
|
||||||
@@ -245,6 +278,8 @@ make_logo_grob <- function(type = c("wordmark", "mark"),
|
|||||||
#' [make_logo_grob()].
|
#' [make_logo_grob()].
|
||||||
#' @param variant Character. `"light"` (default) or `"dark"`. Passed to
|
#' @param variant Character. `"light"` (default) or `"dark"`. Passed to
|
||||||
#' [make_logo_grob()].
|
#' [make_logo_grob()].
|
||||||
|
#' @param position Character. Corner placement for the logo: `"bottom-right"`
|
||||||
|
#' (default), `"bottom-left"`, `"top-right"`, or `"top-left"`.
|
||||||
#' @param margin_param Numeric or `NULL`. Manual margin adjustment passed to
|
#' @param margin_param Numeric or `NULL`. Manual margin adjustment passed to
|
||||||
#' [add_logo()].
|
#' [add_logo()].
|
||||||
#' @param font_scale Numeric. Inflate text sizes by this factor to compensate
|
#' @param font_scale Numeric. Inflate text sizes by this factor to compensate
|
||||||
@@ -266,26 +301,38 @@ make_logo_grob <- function(type = c("wordmark", "mark"),
|
|||||||
#' civilytics_logo() |>
|
#' civilytics_logo() |>
|
||||||
#' grid.draw()
|
#' grid.draw()
|
||||||
#'
|
#'
|
||||||
#' # Dark theme with mark
|
#' # Top-right placement
|
||||||
#' (ggplot(mpg, aes(displ, hwy)) +
|
|
||||||
#' geom_point() +
|
|
||||||
#' theme_civilytics_dark()) |>
|
|
||||||
#' civilytics_logo(variant = "dark", type = "mark") |>
|
|
||||||
#' grid.draw()
|
|
||||||
#'
|
|
||||||
#' # Disable font inflation
|
|
||||||
#' (ggplot(mpg, aes(displ, hwy)) +
|
#' (ggplot(mpg, aes(displ, hwy)) +
|
||||||
#' geom_point() +
|
#' geom_point() +
|
||||||
#' theme_civilytics()) |>
|
#' theme_civilytics()) |>
|
||||||
#' civilytics_logo(font_scale = 1) |>
|
#' civilytics_logo(position = "top-right") |>
|
||||||
|
#' grid.draw()
|
||||||
|
#'
|
||||||
|
#' # Bottom-left with mark
|
||||||
|
#' (ggplot(mpg, aes(displ, hwy)) +
|
||||||
|
#' geom_point() +
|
||||||
|
#' theme_civilytics()) |>
|
||||||
|
#' civilytics_logo(type = "mark", position = "bottom-left") |>
|
||||||
|
#' grid.draw()
|
||||||
|
#'
|
||||||
|
#' # Dark theme with mark in top-left
|
||||||
|
#' (ggplot(mpg, aes(displ, hwy)) +
|
||||||
|
#' geom_point() +
|
||||||
|
#' theme_civilytics_dark()) |>
|
||||||
|
#' civilytics_logo(variant = "dark", type = "mark",
|
||||||
|
#' position = "top-left") |>
|
||||||
#' grid.draw()
|
#' grid.draw()
|
||||||
#' }
|
#' }
|
||||||
civilytics_logo <- function(plot,
|
civilytics_logo <- function(plot,
|
||||||
type = c("wordmark", "mark"),
|
type = c("wordmark", "mark"),
|
||||||
variant = c("light", "dark"),
|
variant = c("light", "dark"),
|
||||||
|
position = c("bottom-right", "bottom-left",
|
||||||
|
"top-right", "top-left"),
|
||||||
margin_param = NULL,
|
margin_param = NULL,
|
||||||
font_scale = 1.1) {
|
font_scale = 1.1) {
|
||||||
logo <- make_logo_grob(type = type, variant = variant)
|
position <- match.arg(position)
|
||||||
add_logo(plot, logo, margin_param = margin_param, font_scale = font_scale)
|
logo <- make_logo_grob(type = type, variant = variant, position = position)
|
||||||
|
add_logo(plot, logo, margin_param = margin_param, font_scale = font_scale,
|
||||||
|
position = position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -4,7 +4,13 @@
|
|||||||
\alias{add_logo}
|
\alias{add_logo}
|
||||||
\title{Add a logo to a ggplot2 object}
|
\title{Add a logo to a ggplot2 object}
|
||||||
\usage{
|
\usage{
|
||||||
add_logo(plot, logo, margin_param = NULL, font_scale = 1.1)
|
add_logo(
|
||||||
|
plot,
|
||||||
|
logo,
|
||||||
|
margin_param = NULL,
|
||||||
|
font_scale = 1.1,
|
||||||
|
position = c("bottom-right", "bottom-left", "top-right", "top-left")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
\arguments{
|
\arguments{
|
||||||
\item{plot}{a ggplot2 grob}
|
\item{plot}{a ggplot2 grob}
|
||||||
@@ -17,6 +23,10 @@ add_logo(plot, logo, margin_param = NULL, font_scale = 1.1)
|
|||||||
sizes before composing the plot with the logo. Default `1.1` inflates
|
sizes before composing the plot with the logo. Default `1.1` inflates
|
||||||
text by ~10 \% to compensate for the viewport shrinkage caused by
|
text by ~10 \% to compensate for the viewport shrinkage caused by
|
||||||
[gridExtra::arrangeGrob()]. Set to `1` to disable.}
|
[gridExtra::arrangeGrob()]. Set to `1` to disable.}
|
||||||
|
|
||||||
|
\item{position}{Character. Corner placement for the logo: `"bottom-right"`
|
||||||
|
(default), `"bottom-left"`, `"top-right"`, or `"top-left"`. Controls
|
||||||
|
whether the logo is placed above or below the plot.}
|
||||||
}
|
}
|
||||||
\value{
|
\value{
|
||||||
a grob with a logo attached to it ready to plot
|
a grob with a logo attached to it ready to plot
|
||||||
|
|||||||
+23
-11
@@ -8,6 +8,7 @@ civilytics_logo(
|
|||||||
plot,
|
plot,
|
||||||
type = c("wordmark", "mark"),
|
type = c("wordmark", "mark"),
|
||||||
variant = c("light", "dark"),
|
variant = c("light", "dark"),
|
||||||
|
position = c("bottom-right", "bottom-left", "top-right", "top-left"),
|
||||||
margin_param = NULL,
|
margin_param = NULL,
|
||||||
font_scale = 1.1
|
font_scale = 1.1
|
||||||
)
|
)
|
||||||
@@ -21,6 +22,9 @@ civilytics_logo(
|
|||||||
\item{variant}{Character. `"light"` (default) or `"dark"`. Passed to
|
\item{variant}{Character. `"light"` (default) or `"dark"`. Passed to
|
||||||
[make_logo_grob()].}
|
[make_logo_grob()].}
|
||||||
|
|
||||||
|
\item{position}{Character. Corner placement for the logo: `"bottom-right"`
|
||||||
|
(default), `"bottom-left"`, `"top-right"`, or `"top-left"`.}
|
||||||
|
|
||||||
\item{margin_param}{Numeric or `NULL`. Manual margin adjustment passed to
|
\item{margin_param}{Numeric or `NULL`. Manual margin adjustment passed to
|
||||||
[add_logo()].}
|
[add_logo()].}
|
||||||
|
|
||||||
@@ -33,8 +37,8 @@ A grob (from [gridExtra::arrangeGrob()]) ready to draw with
|
|||||||
[grid::grid.draw()].
|
[grid::grid.draw()].
|
||||||
}
|
}
|
||||||
\description{
|
\description{
|
||||||
A convenience wrapper that creates the logo grob and attaches it below the
|
A convenience wrapper that creates the logo grob and attaches it to a
|
||||||
plot in one call. Designed for use with the base pipe `|>`.
|
corner of the plot in one call. Designed for use with the base pipe `|>`.
|
||||||
}
|
}
|
||||||
\details{
|
\details{
|
||||||
**Important:** R's `|>` has *higher* precedence than `+`, so you must
|
**Important:** R's `|>` has *higher* precedence than `+`, so you must
|
||||||
@@ -58,18 +62,26 @@ library(ggplot2); library(grid)
|
|||||||
civilytics_logo() |>
|
civilytics_logo() |>
|
||||||
grid.draw()
|
grid.draw()
|
||||||
|
|
||||||
# Dark theme with mark
|
# Top-right placement
|
||||||
(ggplot(mpg, aes(displ, hwy)) +
|
|
||||||
geom_point() +
|
|
||||||
theme_civilytics_dark()) |>
|
|
||||||
civilytics_logo(variant = "dark", type = "mark") |>
|
|
||||||
grid.draw()
|
|
||||||
|
|
||||||
# Disable font inflation
|
|
||||||
(ggplot(mpg, aes(displ, hwy)) +
|
(ggplot(mpg, aes(displ, hwy)) +
|
||||||
geom_point() +
|
geom_point() +
|
||||||
theme_civilytics()) |>
|
theme_civilytics()) |>
|
||||||
civilytics_logo(font_scale = 1) |>
|
civilytics_logo(position = "top-right") |>
|
||||||
|
grid.draw()
|
||||||
|
|
||||||
|
# Bottom-left with mark
|
||||||
|
(ggplot(mpg, aes(displ, hwy)) +
|
||||||
|
geom_point() +
|
||||||
|
theme_civilytics()) |>
|
||||||
|
civilytics_logo(type = "mark", position = "bottom-left") |>
|
||||||
|
grid.draw()
|
||||||
|
|
||||||
|
# Dark theme with mark in top-left
|
||||||
|
(ggplot(mpg, aes(displ, hwy)) +
|
||||||
|
geom_point() +
|
||||||
|
theme_civilytics_dark()) |>
|
||||||
|
civilytics_logo(variant = "dark", type = "mark",
|
||||||
|
position = "top-left") |>
|
||||||
grid.draw()
|
grid.draw()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-1
@@ -4,7 +4,11 @@
|
|||||||
\alias{make_logo_grob}
|
\alias{make_logo_grob}
|
||||||
\title{Get a Civilytics Logo grob}
|
\title{Get a Civilytics Logo grob}
|
||||||
\usage{
|
\usage{
|
||||||
make_logo_grob(type = c("wordmark", "mark"), variant = c("light", "dark"))
|
make_logo_grob(
|
||||||
|
type = c("wordmark", "mark"),
|
||||||
|
variant = c("light", "dark"),
|
||||||
|
position = c("bottom-right", "bottom-left", "top-right", "top-left")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
\arguments{
|
\arguments{
|
||||||
\item{type}{Character. `"wordmark"` (default) uses the full wordmark.
|
\item{type}{Character. `"wordmark"` (default) uses the full wordmark.
|
||||||
@@ -13,6 +17,10 @@ make_logo_grob(type = c("wordmark", "mark"), variant = c("light", "dark"))
|
|||||||
\item{variant}{Character. `"light"` (default) uses the dark logo for light
|
\item{variant}{Character. `"light"` (default) uses the dark logo for light
|
||||||
backgrounds. `"dark"` uses the reverse (light) logo for dark backgrounds
|
backgrounds. `"dark"` uses the reverse (light) logo for dark backgrounds
|
||||||
(pairs with [theme_civilytics_dark()]).}
|
(pairs with [theme_civilytics_dark()]).}
|
||||||
|
|
||||||
|
\item{position}{Character. Corner placement for the logo: `"bottom-right"`
|
||||||
|
(default), `"bottom-left"`, `"top-right"`, or `"top-left"`. Controls
|
||||||
|
horizontal alignment of the logo grob.}
|
||||||
}
|
}
|
||||||
\value{
|
\value{
|
||||||
A ggplot object (class `"gg"`) containing the logo grob.
|
A ggplot object (class `"gg"`) containing the logo grob.
|
||||||
@@ -25,5 +33,6 @@ pipe-friendly [civilytics_logo()].
|
|||||||
\examples{
|
\examples{
|
||||||
logo <- make_logo_grob() # wordmark, light
|
logo <- make_logo_grob() # wordmark, light
|
||||||
logo <- make_logo_grob("mark", "dark") # mark, dark
|
logo <- make_logo_grob("mark", "dark") # mark, dark
|
||||||
|
logo <- make_logo_grob(position = "bottom-left") # left-aligned
|
||||||
class(logo) # "gg" "ggplot"
|
class(logo) # "gg" "ggplot"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -298,3 +298,36 @@ test_that("civilytics_logo accepts type and variant params", {
|
|||||||
result <- suppressWarnings(civilytics_logo(p, type = "mark", variant = "dark"))
|
result <- suppressWarnings(civilytics_logo(p, type = "mark", variant = "dark"))
|
||||||
expect_s3_class(result, "grob")
|
expect_s3_class(result, "grob")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# --- position argument -------------------------------------------------------
|
||||||
|
|
||||||
|
test_that("civilytics_logo accepts all four position values", {
|
||||||
|
p <- ggplot(mpg, aes(displ, hwy)) + geom_point() + theme_civilytics()
|
||||||
|
for (pos in c("bottom-right", "bottom-left", "top-right", "top-left")) {
|
||||||
|
result <- suppressWarnings(civilytics_logo(p, position = pos))
|
||||||
|
expect_s3_class(result, "grob")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test_that("civilytics_logo errors on invalid position", {
|
||||||
|
p <- ggplot(mpg, aes(displ, hwy)) + geom_point() + theme_civilytics()
|
||||||
|
expect_error(civilytics_logo(p, position = "center"))
|
||||||
|
})
|
||||||
|
|
||||||
|
test_that("make_logo_grob accepts position argument", {
|
||||||
|
for (pos in c("bottom-right", "bottom-left", "top-right", "top-left")) {
|
||||||
|
logo <- make_logo_grob(position = pos)
|
||||||
|
expect_s3_class(logo, "gg")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test_that("top positions place logo before plot in arrangeGrob", {
|
||||||
|
p <- ggplot(mpg, aes(displ, hwy)) + geom_point() + theme_civilytics()
|
||||||
|
result_top <- suppressWarnings(civilytics_logo(p, position = "top-right"))
|
||||||
|
result_bot <- suppressWarnings(civilytics_logo(p, position = "bottom-right"))
|
||||||
|
# Top: logo is first grob; bottom: plot is first grob.
|
||||||
|
|
||||||
|
# The arrangeGrob heights differ in order
|
||||||
|
expect_s3_class(result_top, "grob")
|
||||||
|
expect_s3_class(result_bot, "grob")
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user