Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,15 @@ Theme fixes:
- Polygon density hatching lines now correctly use the group colour instead of
black. Affects `type_polygon`, `type_chull`, and `type_ellipse` when
`density` is set. (#610 @grantmcdermott)
- Fixed inconsistent decimal places in the `tinylabel()` currency and comma
formatters (e.g., `"$"`, `"€"`, `"£"`, `","`). These now use a consistent
number of decimal places across the whole vector, matching the existing
behaviour of the percent formatter. The currency formatters additionally show
at least two decimal places whenever a fractional component is present (e.g.
`"$0.50"` rather than `"$0.5"`), while still keeping clean integers
integer-valued (e.g. `"$1,000"`), and place the negative sign in front of the
currency symbol (e.g. `"-$1.50"` rather than `"$-1.50"`).
(#618 @grantmcdermott)

## v0.6.1

Expand Down
49 changes: 32 additions & 17 deletions R/tinylabel.R
Original file line number Diff line number Diff line change
Expand Up @@ -133,36 +133,51 @@ labeller_fun = function(label = "percent") {

## actual formatting functions

format_percent = function(x) {
max_decimals = 5L
pct = as.numeric(x) * 100
upct = unique(pct)
d = Find(
# Find the smallest number of decimal places (from 0:max_decimals) that keeps
# the unique values distinct, so a single consistent format can be applied to
# the whole vector. Falls back to max_decimals.
consistent_decimals = function(x, max_decimals = 5L) {
ux = unique(as.numeric(x))
Find(
function(d) {
length(unique(sprintf(paste0('%.', d, 'f%%'), upct))) == length(upct)
length(unique(sprintf(paste0("%.", d, "f"), ux))) == length(ux)
},
0:max_decimals
) %||%
max_decimals
pct = sprintf(paste0('%.', d, 'f%%'), pct)
return(pct)
}

format_comma = function(x) {
prettyNum(x, big.mark = ",", scientific = FALSE)
format_percent = function(x) {
pct = as.numeric(x) * 100
d = consistent_decimals(pct)
sprintf(paste0("%.", d, "f%%"), pct)
}

format_dollar = function(x) {
paste0("$", prettyNum(x, big.mark = ",", scientific = FALSE))
# Currency convention: keep clean integers integer-valued, but show at least
# two decimal places whenever any fractional component is present (e.g.
# "$0.50" rather than "$0.5"). Negative values place the sign in front of the
# currency symbol (e.g. "-$1.50" rather than "$-1.50"). The comma formatter is
# not currency, so it just uses the smallest consistent number of decimals.
format_currency = function(x, symbol) {
xn = as.numeric(x)
d = consistent_decimals(xn)
has_decimal = any(xn[!is.na(xn)] %% 1 != 0)
if (has_decimal) d = max(d, 2L)
fmt = formatC(abs(xn), format = "f", digits = d, big.mark = ",")
sign = ifelse(!is.na(xn) & xn < 0, "-", "")
paste0(sign, symbol, fmt)
}

format_euro = function(x) {
paste0("\u20ac", prettyNum(x, big.mark = ",", scientific = FALSE))
format_comma = function(x) {
d = consistent_decimals(x)
formatC(as.numeric(x), format = "f", digits = d, big.mark = ",")
}

format_sterling = function(x) {
paste0("\u00a3", prettyNum(x, big.mark = ",", scientific = FALSE))
}
format_dollar = function(x) format_currency(x, "$")

format_euro = function(x) format_currency(x, "\u20ac")

format_sterling = function(x) format_currency(x, "\u00a3")

format_log = function(x) {
x = as.numeric(x)
Expand Down
42 changes: 27 additions & 15 deletions R/type_barplot.R
Original file line number Diff line number Diff line change
Expand Up @@ -57,37 +57,49 @@
#' # wouldn't work for `width`, since it would conflict with the top-level
#' # `tinyplot(..., width = <width>)` argument. It's safer to pass these args
#' # through the `type_barplot()` functional equivalent.
#' tinyplot(~ cyl | vs, data = mtcars, fill = 0.2,
#' type = type_barplot(beside = TRUE, drop.zeros = TRUE, width = 0.65))
#'
#' tinytheme("clean2")
#' tinyplot(
#' ~ cyl | vs, data = mtcars, fill = 0.2,
#' type = type_barplot(beside = TRUE, drop.zeros = TRUE, width = 0.65)
#' )
#'
#' # Example for numeric y aggregated by x (default: FUN = mean) + facets
#' tinyplot(extra ~ ID | group, facet = "by", data = sleep,
#' type = "barplot", fill = 0.6)
#' tinyplot(
#' extra ~ ID | group, facet = "by", data = sleep,
#' type = "barplot", fill = 0.6,
#' theme = "clean2"
#' )
#'
#' # Fancy frequency table:
#' tinyplot(Freq ~ Sex | Survived, facet = ~ Class, data = as.data.frame(Titanic),
#' type = "barplot", facet.args = list(nrow = 1), flip = TRUE, fill = 0.6)
#' tinyplot(
#' Freq ~ Sex | Survived, data = as.data.frame(Titanic),
#' facet = ~ Class, facet.args = list(nrow = 1),
#' type = "barplot", flip = TRUE, fill = 0.6,
#' theme = "clean2"
#' )
#'
#' # Centered barplot for conditional proportions of hair color (black/brown vs.
#' # red/blond) given eye color and sex
#' tinytheme("clean2", palette.qualitative = c("black", "sienna", "indianred", "goldenrod"))
#' hec = as.data.frame(proportions(HairEyeColor, 2:3))
#' tinyplot(Freq ~ Eye | Hair, facet = ~ Sex, data = hec, type = "barplot",
#' center = TRUE, flip = TRUE, facet.args = list(ncol = 1), yaxl = "percent")
#' hcols = c("black", "sienna", "indianred", "goldenrod")
#' tinyplot(
#' Freq ~ Eye | Hair, data = hec,
#' facet = ~ Sex, facet.args = list(ncol = 1),
#' type = "barplot", center = TRUE,
#' flip = TRUE, yaxl = "percent",
#' theme = list("clean2", palette.qualitative = hcols)
#' )
#'
#' tinytheme()
#'
#' # Use cases for the `offset` argument
#'
#' # 1. Waterfall plot
#' d = data.frame(item = c("Sales", "Services", "Costs", "Returns", "TOTAL"),
#' value = c(100, 40, -80, -10, 50))
#' d$item = factor(d$item, levels = d$item)
#' d$offset = c(0, cumsum(d$value[1:3]), 0)
#' tinyplot(value ~ item | I(value < 0), data = d,
#' type = type_barplot(offset = d$offset), legend = FALSE)
#' tinyplot(
#' value ~ item | I(value < 0), data = d,
#' type = type_barplot(offset = d$offset), legend = FALSE
#' )
#' tinyplot_add(type = type_vline(4.5), lty = 2)
#'
#' # 2. Diverging/Likert layout: a character (or named numeric) offset "sets
Expand Down
38 changes: 38 additions & 0 deletions inst/tinytest/test-tinylabel.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,41 @@ f = function() tinyplot(
theme = "clean"
)
expect_snapshot_plot(f, label = "tinylabel")

# Currency/comma formatters should use a consistent number of decimal places
# across the whole vector (#618). Currency formatters additionally follow the
# convention of showing at least two decimal places when any fractional
# component is present, while still keeping clean integers integer-valued.
revenue = seq(0, 2.5, length.out = 6)
expect_equal(
tinylabel(revenue, "$"),
c("$0.00", "$0.50", "$1.00", "$1.50", "$2.00", "$2.50")
)
# comma is not currency, so it uses the minimal consistent decimals
expect_equal(
tinylabel(revenue, ","),
c("0.0", "0.5", "1.0", "1.5", "2.0", "2.5")
)
# clean integers stay integer-valued
expect_equal(
tinylabel(c(1000, 2000, 3000), "$"),
c("$1,000", "$2,000", "$3,000")
)
expect_equal(
tinylabel(c(1000, 2000, 3000), ","),
c("1,000", "2,000", "3,000")
)
# NA values are left as-is by default (na.ignore = TRUE)
expect_equal(
tinylabel(c(0, 0.5, NA, 1.5), "$"),
c("$0.00", "$0.50", NA, "$1.50")
)
# negative currency values place the sign in front of the symbol
expect_equal(
tinylabel(c(-1.5, 0, 2), "$"),
c("-$1.50", "$0.00", "$2.00")
)
expect_equal(
tinylabel(c(-1000, 2000), "$"),
c("-$1,000", "$2,000")
)
49 changes: 32 additions & 17 deletions man/type_barplot.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading