Compare commits

...
Sign in to create a new pull request.

9 commits
revamp ... main

Author SHA1 Message Date
6b508150a7 Fix R CMD checks and update README 2025-07-03 11:52:39 +02:00
0aae59491e Add roxygen2 documentation for custom infix operators 2025-07-02 13:45:17 +02:00
186fce48da Fix typo in scale_color_visualizer_continuous guide argument 2025-07-02 13:41:22 +02:00
1584bdca30 Replace mockery with withr in Suggests and tests 2025-07-02 13:34:11 +02:00
201fe39973 Add code coverage reporting and update README badges
- Integrate codecov for test coverage reporting - Add codecov and
R-CMD-check badges to README - Update roadmap and documentation to
reflect coverage goals - Add codecov.yml configuration file - Enable
tests in tests/testthat.R - Update example images in man/figures
2025-07-02 13:20:39 +02:00
8de44120ec Update upload-artifact action to v4 in test coverage workflow 2025-07-02 13:15:09 +02:00
a791074dde Add GitHub Actions workflows and PR template 2025-07-02 13:12:39 +02:00
fa2b174898 Update WORDLIST with "CMD" 2025-07-02 13:12:20 +02:00
d7b3052d83
Merge pull request #27 from gnoblet/revamp
Revamp
2025-07-02 13:10:39 +02:00
42 changed files with 839 additions and 389 deletions

View file

@ -5,6 +5,7 @@
^\.github$ ^\.github$
^\.pre-commit-config\.yaml$ ^\.pre-commit-config\.yaml$
^_pkgdown\.yml$ ^_pkgdown\.yml$
^codecov\.yml$
^data-raw$ ^data-raw$
^docs ^docs
^docs$ ^docs$
@ -15,3 +16,4 @@
^renv\.lock$ ^renv\.lock$
^renv\.lock$ ^renv\.lock$
^test-example.R ^test-example.R
^test\.R$

1
.github/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.html

32
.github/pull_request_template.md vendored Normal file
View file

@ -0,0 +1,32 @@
## PR Description
<!-- Please include a summary of the changes and which issue is fixed or what feature is added -->
## Type of change
<!-- Please mark relevant options with [x] -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update
- [ ] Code refactoring or style updates
## Checklist:
<!-- Please mark relevant options with [x] -->
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
## Notes for reviewers
<!-- Optional: Any notes or context that would be useful for the reviewer -->
## Automated Checks
The following checks will run automatically on this PR:
- R CMD check
- Documentation updates
- Test coverage
- Linting and style checks

56
.github/workflows/R-CMD-check.yml vendored Normal file
View file

@ -0,0 +1,56 @@
# For help debugging build failures open an issue on the RStudio community with the 'github-actions' tag.
# https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
name: R-CMD-check
jobs:
R-CMD-check:
runs-on: ${{ matrix.config.os }}
name: ${{ matrix.config.os }} (${{ matrix.config.r }})
strategy:
fail-fast: false
matrix:
config:
- {os: windows-latest, r: 'release'}
- {os: macOS-latest, r: 'release'}
- {os: ubuntu-latest, r: 'release'}
- {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'}
env:
R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
RSPM: ${{ matrix.config.rspm }}
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v3
- uses: r-lib/actions/setup-pandoc@v2
- uses: r-lib/actions/setup-r@v2
with:
r-version: ${{ matrix.config.r }}
http-user-agent: ${{ matrix.config.http-user-agent }}
use-public-rspm: true
- uses: r-lib/actions/setup-r-dependencies@v2
with:
extra-packages: any::rcmdcheck
needs: check
- name: Document
run: |
install.packages("devtools")
devtools::document()
shell: Rscript {0}
if: github.event_name == 'pull_request'
- uses: r-lib/actions/check-r-package@v2
with:
upload-snapshots: true

40
.github/workflows/lint.yml vendored Normal file
View file

@ -0,0 +1,40 @@
# Workflow for linting and style checks
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
name: lint
jobs:
lint:
runs-on: ubuntu-latest
env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v3
- uses: r-lib/actions/setup-r@v2
with:
use-public-rspm: true
- uses: r-lib/actions/setup-r-dependencies@v2
with:
extra-packages: |
any::lintr
any::styler
needs: lint
- name: Lint
run: |
lintr::lint_package()
shell: Rscript {0}
- name: Style check
run: |
if (!styler::style_pkg(dry = TRUE)) {
message("Some files are not properly styled!")
quit(status = 1)
}
shell: Rscript {0}

49
.github/workflows/pkgdown.yaml vendored Normal file
View file

@ -0,0 +1,49 @@
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
on:
push:
branches: [main, master]
pull_request:
release:
types: [published]
workflow_dispatch:
name: pkgdown.yaml
permissions: read-all
jobs:
pkgdown:
runs-on: ubuntu-latest
# Only restrict concurrency for non-PR jobs
concurrency:
group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }}
env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: r-lib/actions/setup-pandoc@v2
- uses: r-lib/actions/setup-r@v2
with:
use-public-rspm: true
- uses: r-lib/actions/setup-r-dependencies@v2
with:
extra-packages: any::pkgdown, local::.
needs: website
- name: Build site
run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE)
shell: Rscript {0}
- 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

55
.github/workflows/test-coverage.yml vendored Normal file
View file

@ -0,0 +1,55 @@
# Run test-coverage for visualizeR package
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
name: test-coverage
jobs:
test-coverage:
runs-on: ubuntu-latest
env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v3
- uses: r-lib/actions/setup-r@v2
with:
use-public-rspm: true
- uses: r-lib/actions/setup-r-dependencies@v2
with:
extra-packages: |
any::covr
any::remotes
needs: coverage
- name: Install package
run: |
R CMD build .
R CMD INSTALL *.tar.gz
- name: Test coverage
run: |
covr::codecov(
quiet = FALSE,
clean = FALSE,
install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package")
)
shell: Rscript {0}
- name: Show testthat output
if: always()
run: |
## Print out test results details
find . -name 'testthat.Rout*' -exec cat '{}' \; || true
shell: bash
- name: Upload test results
if: failure()
uses: actions/upload-artifact@v4
with:
name: coverage-test-failures
path: ${{ runner.temp }}/package

View file

@ -16,7 +16,6 @@ Imports:
checkmate, checkmate,
dplyr, dplyr,
forcats, forcats,
ggalluvial,
ggplot2, ggplot2,
ggrepel, ggrepel,
ggtext, ggtext,
@ -24,18 +23,16 @@ Imports:
grDevices, grDevices,
rlang (>= 0.4.11), rlang (>= 0.4.11),
scales, scales,
stringr, tidyr
tidyr,
viridisLite,
waffle
Suggests: Suggests:
covr,
knitr, knitr,
rio, rio,
rmarkdown, rmarkdown,
roxygen2, roxygen2,
testthat (>= 3.0.0), testthat (>= 3.0.0),
vdiffr, vdiffr,
mockery withr
VignetteBuilder: VignetteBuilder:
knitr knitr
Config/testthat/edition: 3 Config/testthat/edition: 3

116
R/bar.R
View file

@ -2,13 +2,14 @@
#' #'
#' @inheritParams bar #' @inheritParams bar
#' #'
#' @param ... Additional arguments passed to `bar()`
#'
#' @export #' @export
hbar <- function( hbar <- function(
..., ...,
flip = TRUE, flip = TRUE,
add_text = FALSE, add_text = FALSE,
theme_fun = theme_bar(flip = flip, add_text = add_text) theme_fun = theme_bar(flip = flip, add_text = add_text)) {
) {
bar(flip = flip, add_text = add_text, theme_fun = theme_fun, ...) bar(flip = flip, add_text = add_text, theme_fun = theme_fun, ...)
} }
@ -48,6 +49,8 @@ hbar <- function(
#' @param add_text_expand_limit Default to adding 10\% on top of the bar. #' @param add_text_expand_limit Default to adding 10\% on top of the bar.
#' @param add_text_round Round the text label. #' @param add_text_round Round the text label.
#' @param theme_fun Whatever theme function. For no custom theme, use theme_fun = NULL. #' @param theme_fun Whatever theme function. For no custom theme, use theme_fun = NULL.
#' @param scale_fill_fun Scale fill function. Default to scale_fill_visualizer_discrete().
#' @param scale_color_fun Scale color function. Default to scale_color_visualizer_discrete().
#' #'
#' @inheritParams reorder_by #' @inheritParams reorder_by
#' #'
@ -55,48 +58,47 @@ hbar <- function(
#' #'
#' @export #' @export
bar <- function( bar <- function(
df, df,
x, x,
y, y,
group = "", group = "",
facet = "", facet = "",
order = "none", order = "none",
x_rm_na = TRUE, x_rm_na = TRUE,
y_rm_na = TRUE, y_rm_na = TRUE,
group_rm_na = TRUE, group_rm_na = TRUE,
facet_rm_na = TRUE, facet_rm_na = TRUE,
y_expand = 0.1, y_expand = 0.1,
add_color = color("cat_5_main_1"), add_color = color("cat_5_main_1"),
add_color_guide = TRUE, add_color_guide = TRUE,
flip = FALSE, flip = FALSE,
wrap = NULL, wrap = NULL,
position = "dodge", position = "dodge",
alpha = 1, alpha = 1,
x_title = NULL, x_title = NULL,
y_title = NULL, y_title = NULL,
group_title = NULL, group_title = NULL,
title = NULL, title = NULL,
subtitle = NULL, subtitle = NULL,
caption = NULL, caption = NULL,
width = 0.8, width = 0.8,
add_text = FALSE, add_text = FALSE,
add_text_size = 4.5, add_text_size = 4.5,
add_text_color = color("dark_grey"), add_text_color = color("dark_grey"),
add_text_font_face = "bold", add_text_font_face = "bold",
add_text_threshold_display = 0.05, add_text_threshold_display = 0.05,
add_text_suffix = "%", add_text_suffix = "%",
add_text_expand_limit = 1.2, add_text_expand_limit = 1.2,
add_text_round = 1, add_text_round = 1,
theme_fun = theme_bar( theme_fun = theme_bar(
flip = flip, flip = flip,
add_text = add_text, add_text = add_text,
axis_text_x_angle = 0, axis_text_x_angle = 0,
axis_text_x_vjust = 0.5, axis_text_x_vjust = 0.5,
axis_text_x_hjust = 0.5 axis_text_x_hjust = 0.5
), ),
scale_fill_fun = scale_fill_visualizer_discrete(), scale_fill_fun = scale_fill_visualizer_discrete(),
scale_color_fun = scale_color_visualizer_discrete() scale_color_fun = scale_color_visualizer_discrete()) {
) {
#------ Checks #------ Checks
# df is a data frame # df is a data frame
@ -349,7 +351,7 @@ bar <- function(
vjust_flip <- -0.5 vjust_flip <- -0.5
} }
# function for interaction # function for interactio
interaction_f <- function(group, facet, data) { interaction_f <- function(group, facet, data) {
if (group == "" && facet == "") { if (group == "" && facet == "") {
return(NULL) return(NULL)
@ -366,14 +368,7 @@ bar <- function(
# add text labels # add text labels
if (add_text & position == "dodge") { if (add_text & position == "dodge") {
df <- dplyr::mutate( df$y_threshold <- ifelse(df[[y]] >= add_text_threshold_display, df[[y]], NA)
df,
"y_threshold" := ifelse(
!!rlang::sym(y) >= add_text_threshold_display,
!!rlang::sym(y),
NA
)
)
# expand limits # expand limits
g <- g + g <- g +
@ -407,14 +402,7 @@ bar <- function(
position = ggplot2::position_dodge2(width = dodge_width) position = ggplot2::position_dodge2(width = dodge_width)
) )
} else if (add_text & position == "stack") { } else if (add_text & position == "stack") {
df <- dplyr::mutate( df$y_threshold <- ifelse(df[[y]] >= add_text_threshold_display, df[[y]], NA)
df,
"y_threshold" := ifelse(
!!rlang::sym(y) >= add_text_threshold_display,
!!rlang::sym(y),
NA
)
)
g <- g + g <- g +
ggplot2::geom_text( ggplot2::geom_text(

View file

@ -1,15 +1,25 @@
# not in # not in
#' Not In Operator
#'
#' A negation of the `%in%` operator that tests if elements of `a` are not in `b`.
#'
#' @param a Vector or value to test
#' @param b Vector to test against
#'
#' @return Logical vector with TRUE for elements of `a` that are not in `b`
`%notin%` <- function(a, b) { `%notin%` <- function(a, b) {
!(a %in% b) !(a %in% b)
} }
# not all in # not all in
#' Not All In Operator
#'
#' Tests if not all elements of `a` are contained in `b`.
#'
#' @param a Vector to test
#' @param b Vector to test against
#'
#' @return TRUE if at least one element of `a` is not in `b`, otherwise FALSE
`%notallin%` <- function(a, b) { `%notallin%` <- function(a, b) {
!(all(a %in% b)) !(all(a %in% b))
} }
# infix for null replacement
#' @importFrom rlang `%||%`
`%ifnullrep%` <- function(a, b) {
a %||% b
}

View file

@ -1,6 +1,7 @@
#' @rdname lollipop #' @rdname lollipop
#' #'
#' @inheritParams lollipop #' @inheritParams lollipop
#' @param ... Additional arguments passed to `lollipop()`
#' #'
#' @export #' @export
hlollipop <- function( hlollipop <- function(
@ -12,6 +13,7 @@ hlollipop <- function(
#' Simple lollipop chart #' Simple lollipop chart
#' #'
#' @description
#' `lollipop()` is a simple lollipop chart (dots connected to the baseline by a segment) with some customization allowed. #' `lollipop()` is a simple lollipop chart (dots connected to the baseline by a segment) with some customization allowed.
#' `hlollipop()` uses `lollipop()` with sane defaults for a horizontal lollipop chart. #' `hlollipop()` uses `lollipop()` with sane defaults for a horizontal lollipop chart.
#' #'
@ -41,14 +43,24 @@ hlollipop <- function(
#' @param line_color The color of the line connecting dots to the baseline. #' @param line_color The color of the line connecting dots to the baseline.
#' @param dodge_width Width for position dodge when using groups (controls space between grouped lollipops). #' @param dodge_width Width for position dodge when using groups (controls space between grouped lollipops).
#' @param theme_fun Whatever theme function. For no custom theme, use theme_fun = NULL. #' @param theme_fun Whatever theme function. For no custom theme, use theme_fun = NULL.
#' @param scale_fill_fun Scale fill function. #' @param scale_fill_fun Scale fill function. Default to scale_fill_visualizer_discrete().
#' @param scale_color_fun Scale color function. #' @param scale_color_fun Scale color function. Default to scale_color_visualizer_discrete().
#'
#' #'
#' @inheritParams reorder_by #' @inheritParams reorder_by
#' #'
#' @importFrom rlang `:=` #' @importFrom rlang `:=`
#' #'
#' @return A ggplot object
#' @export #' @export
#' @examples
#' \dontrun{
#' df <- data.frame(x = letters[1:5], y = c(10, 5, 7, 12, 8))
#' # Vertical lollipop
#' lollipop(df, "x", "y")
#' # Horizontal lollipop
#' hlollipop(df, "x", "y")
#' }
lollipop <- function( lollipop <- function(
df, df,
x, x,

View file

@ -22,36 +22,35 @@
#' @param subtitle Plot subtitle. Default to NULL. #' @param subtitle Plot subtitle. Default to NULL.
#' @param caption Plot caption. Default to NULL. #' @param caption Plot caption. Default to NULL.
#' @param theme_fun Whatever theme. Default to theme_point(). NULL if no theming needed. #' @param theme_fun Whatever theme. Default to theme_point(). NULL if no theming needed.
#' #' @param scale_fill_fun Scale fill function. Default to scale_fill_visualizer_discrete().
#' @inheritParams scale_color_visualizer_discrete #' @param scale_color_fun Scale color function. Default to scale_color_visualizer_discrete().
#' #'
#' @export #' @export
point <- function( point <- function(
df, df,
x, x,
y, y,
group = "", group = "",
facet = "", facet = "",
facet_scales = "free", facet_scales = "free",
x_rm_na = TRUE, x_rm_na = TRUE,
y_rm_na = TRUE, y_rm_na = TRUE,
group_rm_na = TRUE, group_rm_na = TRUE,
facet_rm_na = TRUE, facet_rm_na = TRUE,
add_color = color("cat_5_main_1"), add_color = color("cat_5_main_1"),
add_color_guide = TRUE, add_color_guide = TRUE,
flip = TRUE, flip = TRUE,
alpha = 1, alpha = 1,
size = 2, size = 2,
x_title = NULL, x_title = NULL,
y_title = NULL, y_title = NULL,
group_title = NULL, group_title = NULL,
title = NULL, title = NULL,
subtitle = NULL, subtitle = NULL,
caption = NULL, caption = NULL,
theme_fun = theme_point(), theme_fun = theme_point(),
scale_fill_fun = scale_fill_visualizer_discrete(), scale_fill_fun = scale_fill_visualizer_discrete(),
scale_color_fun = scale_color_visualizer_discrete() scale_color_fun = scale_color_visualizer_discrete()) {
) {
#------ Checks #------ Checks
# df is a data frame # df is a data frame

View file

@ -5,16 +5,16 @@
#' @inheritParams palette_gen #' @inheritParams palette_gen
#' #'
#' @param reverse_guide Boolean indicating whether the guide should be reversed. #' @param reverse_guide Boolean indicating whether the guide should be reversed.
#' @param title_position Position of the title. See [ggplot2::guide_legend()]'s title.position argument.
#' @param ... Additional arguments passed to [ggplot2::discrete_scale()] if discrete or [ggplot2::scale_fill_gradient()] if continuous. #' @param ... Additional arguments passed to [ggplot2::discrete_scale()] if discrete or [ggplot2::scale_fill_gradient()] if continuous.
#' #'
#' @export #' @export
scale_color_visualizer_discrete <- function( scale_color_visualizer_discrete <- function(
palette = "cat_5_main", palette = "cat_5_main",
direction = 1, direction = 1,
reverse_guide = TRUE, reverse_guide = TRUE,
title_position = NULL, title_position = NULL,
... ...) {
) {
if (!(is.null(palette))) { if (!(is.null(palette))) {
ggplot2::discrete_scale( ggplot2::discrete_scale(
"color", "color",
@ -47,12 +47,11 @@ scale_color_visualizer_discrete <- function(
#' #'
#' @export #' @export
scale_fill_visualizer_discrete <- function( scale_fill_visualizer_discrete <- function(
palette = "cat_5_main", palette = "cat_5_main",
direction = 1, direction = 1,
reverse_guide = TRUE, reverse_guide = TRUE,
title_position = NULL, title_position = NULL,
... ...) {
) {
if (!(is.null(palette))) { if (!(is.null(palette))) {
ggplot2::discrete_scale( ggplot2::discrete_scale(
"fill", "fill",
@ -85,12 +84,11 @@ scale_fill_visualizer_discrete <- function(
#' #'
#' @export #' @export
scale_fill_visualizer_continuous <- function( scale_fill_visualizer_continuous <- function(
palette = "seq_5_main", palette = "seq_5_main",
direction = 1, direction = 1,
reverse_guide = TRUE, reverse_guide = TRUE,
title_position = NULL, title_position = NULL,
... ...) {
) {
if (!(is.null(palette))) { if (!(is.null(palette))) {
pal <- palette_gen(palette, "continuous", direction) pal <- palette_gen(palette, "continuous", direction)
@ -124,12 +122,11 @@ scale_fill_visualizer_continuous <- function(
#' #'
#' @export #' @export
scale_color_visualizer_continuous <- function( scale_color_visualizer_continuous <- function(
palette = "seq_5_main", palette = "seq_5_main",
direction = 1, direction = 1,
reverse_guide = TRUE, reverse_guide = TRUE,
title_position = NULL, title_position = NULL,
... ...) {
) {
if (!(is.null(palette))) { if (!(is.null(palette))) {
pal <- palette_gen(palette, "continuous", direction) pal <- palette_gen(palette, "continuous", direction)
@ -154,7 +151,7 @@ scale_color_visualizer_continuous <- function(
# ticks.colour = "#F1F3F5", # ticks.colour = "#F1F3F5",
reverse = reverse_guide reverse = reverse_guide
), ),
.... ...
) )
} }
} }

View file

@ -2,16 +2,18 @@
#' #'
#' @return A custom theme object. #' @return A custom theme object.
#' #'
#'
#' @rdname theme_default #' @rdname theme_default
#' #'
#' @inheritParams bar
#'
#' @export #' @export
theme_bar <- function( theme_bar <- function(
flip = TRUE, flip = TRUE,
add_text = FALSE, add_text = FALSE,
axis_text_x_angle = 0, axis_text_x_angle = 0,
axis_text_x_vjust = 0.5, axis_text_x_vjust = 0.5,
axis_text_x_hjust = 0.5 axis_text_x_hjust = 0.5) {
) {
# If add_text is TRUE, flip is FALSE # If add_text is TRUE, flip is FALSE
if (!flip && !add_text) { if (!flip && !add_text) {
par_axis_text_font_face <- "plain" par_axis_text_font_face <- "plain"

View file

@ -1,11 +1,17 @@
#' ggplot2 theme wrapper with fonts and colors #' ggplot2 theme wrapper with fonts and colors
#' #'
#' @param font_family The font family for all plot's texts. Default to "Segoe UI".
#' @param title_size The size of the title. Defaults to 12. #' @param title_size The size of the title. Defaults to 12.
#' @param title_color Title color. #' @param title_color Title color.
#' @param title_font_face Title font face. Default to "bold". Font face ("plain", "italic", "bold", "bold.italic"). #' @param title_font_face Title font face. Default to "bold". Font face ("plain", "italic", "bold", "bold.italic").
#' @param title_hjust Title horizontal justification. Default to NULL. Use 0.5 to center the title. #' @param title_hjust Title horizontal justification. Default to NULL. Use 0.5 to center the title.
#' @param title_font_family Title font family. Default to "Roboto Condensed". #' @param title_font_family Title font family. Default to "Carlito".
#' @param title_position_to_plot TRUE or FALSE. Positioning to plot or to panel?
#' @param subtitle_font_family Subtitle font family. Default to "Carlito".
#' @param subtitle_size The size of the subtitle. Defaults to 10.
#' @param subtitle_color Subtitle color.
#' @param subtitle_font_face Subtitle font face. Default to "plain". Font face ("plain", "italic", "bold", "bold.italic").
#' @param subtitle_hjust Subtitle horizontal justification. Default to NULL. Use 0.5 to center the subtitle.
#' @param text_font_family Text font family. Default to "Carlito".
#' @param text_size The size of all text other than the title, subtitle and caption. Defaults to 10. #' @param text_size The size of all text other than the title, subtitle and caption. Defaults to 10.
#' @param text_color Text color. #' @param text_color Text color.
#' @param text_font_face Text font face. Default to "bold". Font face ("plain", "italic", "bold", "bold.italic"). #' @param text_font_face Text font face. Default to "bold". Font face ("plain", "italic", "bold", "bold.italic").
@ -18,16 +24,21 @@
#' @param legend_title_size Legend title size. #' @param legend_title_size Legend title size.
#' @param legend_title_color Legend title color. #' @param legend_title_color Legend title color.
#' @param legend_title_font_face Legend title font face. Default to "plain". Font face ("plain", "italic", "bold", "bold.italic"). #' @param legend_title_font_face Legend title font face. Default to "plain". Font face ("plain", "italic", "bold", "bold.italic").
#' @param legend_title_font_family Legend title font family. Default to "Carlito".
#' @param legend_text_size Legend text size. #' @param legend_text_size Legend text size.
#' @param legend_text_color Legend text color. #' @param legend_text_color Legend text color.
#' @param legend_text_font_face Legend text font face. Default to "plain". Font face ("plain", "italic", "bold", "bold.italic"). #' @param legend_text_font_face Legend text font face. Default to "plain". Font face ("plain", "italic", "bold", "bold.italic").
#' @param legend_text_font_family Legend text font family. Default to "Carlito".
#'
#' @param legend_reverse Reverse the color in the guide? Default to TRUE. #' @param legend_reverse Reverse the color in the guide? Default to TRUE.
#' @param title_size The size of the legend title. Defaults to 11. #' @param facet_size Facet font size.
#' @param title_color Legend title color. #' @param facet_color Facet font color.
#' @param title_font_face Legend title font face. Default to "plain". Font face ("plain", "italic", "bold", "bold.italic"). #' @param facet_font_face Facet font face. Default to "plain". Font face ("plain", "italic", "bold", "bold.italic").
#' @param title_position_to_plot TRUE or FALSE. Positioning to plot or to panel? #' @param facet_font_family Facet font family. Default to "Carlito".
#' @param facet_bg_color Facet background color.
#' @param axis_x Boolean. Do you need x-axis? #' @param axis_x Boolean. Do you need x-axis?
#' @param axis_y Boolean. Do you need y-axis? #' @param axis_y Boolean. Do you need y-axis?
#' @param axis_text_font_family Axis text font family. Default to "Carlito".
#' @param axis_text_size Axis text size. #' @param axis_text_size Axis text size.
#' @param axis_text_color Axis text color. #' @param axis_text_color Axis text color.
#' @param axis_text_font_face Axis text font face. Default to "plain". Font face ("plain", "italic", "bold", "bold.italic"). #' @param axis_text_font_face Axis text font face. Default to "plain". Font face ("plain", "italic", "bold", "bold.italic").
@ -53,7 +64,11 @@
#' @param grid_minor_x_size Minor X line size. #' @param grid_minor_x_size Minor X line size.
#' @param grid_minor_y_size Minor Y line size. #' @param grid_minor_y_size Minor Y line size.
#' @param grid_minor_color Minor grid lines color. #' @param grid_minor_color Minor grid lines color.
#' @param caption_font_family Caption font family. Default to "Carlito".
#' @param caption_font_face Caption font face. Default to "plain". Font face ("plain", "italic", "bold", "bold.italic").
#' @param caption_position_to_plot TRUE or FALSE. Positioning to plot or to panel? #' @param caption_position_to_plot TRUE or FALSE. Positioning to plot or to panel?
#' @param caption_size The size of the caption. Defaults to 10.
#' @param caption_color Caption color.
#' @param ... Additional arguments passed to [ggplot2::theme()]. #' @param ... Additional arguments passed to [ggplot2::theme()].
#' #'
#' #'
@ -61,76 +76,75 @@
#' #'
#' @export #' @export
theme_default <- function( theme_default <- function(
title_font_family = "Carlito", title_font_family = "Carlito",
title_size = 20, title_size = 20,
title_color = color("dark_grey"), title_color = color("dark_grey"),
title_font_face = "bold", title_font_face = "bold",
title_hjust = NULL, title_hjust = NULL,
title_position_to_plot = TRUE, title_position_to_plot = TRUE,
subtitle_font_family = "Carlito", subtitle_font_family = "Carlito",
subtitle_size = 16, subtitle_size = 16,
subtitle_color = color("dark_grey"), subtitle_color = color("dark_grey"),
subtitle_font_face = "plain", subtitle_font_face = "plain",
subtitle_hjust = NULL, subtitle_hjust = NULL,
text_font_family = "Carlito", text_font_family = "Carlito",
text_size = 14, text_size = 14,
text_color = color("dark_grey"), text_color = color("dark_grey"),
text_font_face = "plain", text_font_face = "plain",
panel_background_color = "#FFFFFF", panel_background_color = "#FFFFFF",
panel_border = FALSE, panel_border = FALSE,
panel_border_color = color("dark_grey"), panel_border_color = color("dark_grey"),
legend_position = "top", legend_position = "top",
legend_direction = "horizontal", legend_direction = "horizontal",
legend_justification = "center", legend_justification = "center",
legend_reverse = TRUE, legend_reverse = TRUE,
legend_title_size = 14, legend_title_size = 14,
legend_title_color = color("dark_grey"), legend_title_color = color("dark_grey"),
legend_title_font_face = "plain", legend_title_font_face = "plain",
legend_title_font_family = "Carlito", legend_title_font_family = "Carlito",
legend_text_size = 14, legend_text_size = 14,
legend_text_color = color("dark_grey"), legend_text_color = color("dark_grey"),
legend_text_font_face = "plain", legend_text_font_face = "plain",
legend_text_font_family = "Carlito", legend_text_font_family = "Carlito",
facet_size = 15, facet_size = 15,
facet_color = color("dark_grey"), facet_color = color("dark_grey"),
facet_font_face = "bold", facet_font_face = "bold",
facet_font_family = "Carlito", facet_font_family = "Carlito",
facet_bg_color = color("lighter_grey"), facet_bg_color = color("lighter_grey"),
axis_x = TRUE, axis_x = TRUE,
axis_y = TRUE, axis_y = TRUE,
axis_text_x = TRUE, axis_text_x = TRUE,
axis_line_x = FALSE, axis_line_x = FALSE,
axis_ticks_x = FALSE, axis_ticks_x = FALSE,
axis_text_y = TRUE, axis_text_y = TRUE,
axis_line_y = TRUE, axis_line_y = TRUE,
axis_ticks_y = TRUE, axis_ticks_y = TRUE,
axis_text_font_family = "Carlito", axis_text_font_family = "Carlito",
axis_text_size = 14, axis_text_size = 14,
axis_text_color = color("dark_grey"), axis_text_color = color("dark_grey"),
axis_text_font_face = "plain", axis_text_font_face = "plain",
axis_title_size = 15, axis_title_size = 15,
axis_title_color = color("dark_grey"), axis_title_color = color("dark_grey"),
axis_title_font_face = "plain", axis_title_font_face = "plain",
axis_text_x_angle = 0, axis_text_x_angle = 0,
axis_text_x_vjust = 0.5, axis_text_x_vjust = 0.5,
axis_text_x_hjust = 0.5, axis_text_x_hjust = 0.5,
grid_major_x = TRUE, grid_major_x = TRUE,
grid_major_y = FALSE, grid_major_y = FALSE,
grid_major_color = color("dark_grey"), grid_major_color = color("dark_grey"),
grid_major_x_size = 0.1, grid_major_x_size = 0.1,
grid_major_y_size = 0.1, grid_major_y_size = 0.1,
grid_minor_x = TRUE, grid_minor_x = TRUE,
grid_minor_y = FALSE, grid_minor_y = FALSE,
grid_minor_color = color("dark_grey"), grid_minor_color = color("dark_grey"),
grid_minor_x_size = 0.05, grid_minor_x_size = 0.05,
grid_minor_y_size = 0.05, grid_minor_y_size = 0.05,
caption_font_family = "Carlito", caption_font_family = "Carlito",
caption_font_face = "plain", caption_font_face = "plain",
caption_position_to_plot = TRUE, caption_position_to_plot = TRUE,
caption_size = 12, caption_size = 12,
caption_color = color("dark_grey"), caption_color = color("dark_grey"),
... ...) {
) {
# Basic simple theme # Basic simple theme
theme <- ggplot2::theme_minimal() theme <- ggplot2::theme_minimal()

View file

@ -6,8 +6,5 @@
#' #'
#' @export #' @export
theme_dumbbell <- function() { theme_dumbbell <- function() {
theme_default( theme_default()
axis_line_x = TRUE,
grid_
)
} }

View file

@ -1,10 +1,27 @@
#' Custom Theme for Lollipop Charts #' Custom Theme for Lollipop Charts
#' #'
#' @return A custom theme object. #' @description
#' A custom theme specifically designed for lollipop charts with appropriate grid lines and axis styling
#' based on whether the chart is flipped (horizontal) or not.
#'
#' @param flip Logical indicating whether the lollipop chart is flipped (horizontal). Default is TRUE.
#' @param axis_text_x_angle Angle for x-axis text labels. Default is 0.
#' @param axis_text_x_vjust Vertical justification for x-axis text labels. Default is 0.5.
#' @param axis_text_x_hjust Horizontal justification for x-axis text labels. Default is 0.5.
#'
#' @return A ggplot2 theme object
#' #'
#' @rdname theme_default #' @rdname theme_default
#'
#' @export #' @export
#'
#' @examples
#' \dontrun{
#' library(ggplot2)
#' df <- data.frame(x = letters[1:5], y = c(10, 5, 7, 12, 8))
#' ggplot(df, aes(x, y)) +
#' geom_point() +
#' theme_lollipop()
#' }
theme_lollipop <- function( theme_lollipop <- function(
flip = TRUE, flip = TRUE,
axis_text_x_angle = 0, axis_text_x_angle = 0,

View file

@ -22,9 +22,14 @@ desc <- setNames(as.list(desc), colnames(desc))
# `r desc$Package` <img src="man/figures/logo.png" align="right" width="120"/> # `r desc$Package` <img src="man/figures/logo.png" align="right" width="120"/>
<!-- badges: start -->
[![R-CMD-check](https://github.com/gnoblet/visualizeR/actions/workflows/R-CMD-check.yml/badge.svg)](https://github.com/gnoblet/visualizeR/actions/workflows/R-CMD-check.yml)
[![Codecov test coverage](https://codecov.io/gh/gnoblet/visualizeR/branch/main/graph/badge.svg)](https://app.codecov.io/gh/gnoblet/visualizeR?branch=main)
<!-- badges: end -->
> `r desc$Title` > `r desc$Title`
`visualizeR` proposes some utils to sane colors, ready-to-go color palettes, and a few visualization functions. `visualizeR` proposes some utils to sane colors, ready-to-go color palettes, and a few visualization functions. The package is thoroughly tested with comprehensive code coverage.
## Installation ## Installation
@ -40,16 +45,23 @@ devtools::install_github("gnoblet/visualizeR", build_vignettes = TRUE)
Roadmap is as follows: Roadmap is as follows:
- [ ] Full revamp of core functions (colors, pattern, incl. adding test and pre-commit structures) - [ ] Full revamp of core functions (colors, pattern, incl. adding test and pre-commit structures)
- [x] Add test coverage reporting via codecov
- [ ] Maintain >80% test coverage across all functions
- [ ] Add other types of plots: - [ ] Add other types of plots:
- [ ] Dumbell - [ ] Dumbell
- [ ] Waffle - [ ] Waffle
- [ ] Donut - [ ] Donut
- [ ] Alluvial - [ ] Alluvial
- [ ] Option for tag with css code + for titles/subtitles/captions
## Request ## Request
Please, do not hesitate to pull request any new viz or colors or color palettes, or to email request any change ([gnoblet\@zaclys.net](mailto:gnoblet@zaclys.net){.email}). Please, do not hesitate to pull request any new viz or colors or color palettes, or to email request any change ([gnoblet\@zaclys.net](mailto:gnoblet@zaclys.net){.email}).
## Code Coverage
`visualizeR` uses [codecov](https://codecov.io/) for test coverage reporting. You can see the current coverage status by clicking on the codecov badge at the top of this README. We aim to maintain high test coverage to ensure code reliability and stability.
## Colors ## Colors
Functions to access colors and palettes are `color()` or `palette()`. Feel free to pull request new colors. Functions to access colors and palettes are `color()` or `palette()`. Feel free to pull request new colors.
@ -255,7 +267,6 @@ lollipop(
y = "stat", y = "stat",
group = "group", group = "group",
order = "grouped_y", order = "grouped_y",
dodge_width = 0.8, # Control spacing between grouped lollipops
dot_size = 3.5, dot_size = 3.5,
line_size = 0.8, line_size = 0.8,
y_title = "Value", y_title = "Value",
@ -269,7 +280,6 @@ hlollipop(
x = "admin1", x = "admin1",
y = "stat", y = "stat",
group = "group", group = "group",
dodge_width = 0.7, # Narrower spacing for horizontal orientation
dot_size = 3.5, dot_size = 3.5,
line_size = 0.8, line_size = 0.8,
y_title = "Category", y_title = "Category",
@ -277,15 +287,3 @@ hlollipop(
title = "Horizontal side-by-side grouped lollipop chart" title = "Horizontal side-by-side grouped lollipop chart"
) )
``` ```
## Lollipop Chart Features
Lollipop charts offer several advantages:
- Clean visualization of point data with connecting lines to a baseline
- True side-by-side grouped display for easy comparison between categories
- Each lollipop maintains its position from dot to baseline
- Customizable appearance with parameters for dot size, line width, and colors
- The `dodge_width` parameter controls spacing between grouped lollipops
The side-by-side positioning for grouped lollipops makes them visually distinct from dumbbell plots, which typically connect related points on the same line.

View file

@ -3,10 +3,18 @@
# visualizeR <img src="man/figures/logo.png" align="right" width="120"/> # visualizeR <img src="man/figures/logo.png" align="right" width="120"/>
<!-- badges: start -->
[![R-CMD-check](https://github.com/gnoblet/visualizeR/actions/workflows/R-CMD-check.yml/badge.svg)](https://github.com/gnoblet/visualizeR/actions/workflows/R-CMD-check.yml)
[![Codecov test
coverage](https://codecov.io/gh/gnoblet/visualizeR/branch/main/graph/badge.svg)](https://app.codecov.io/gh/gnoblet/visualizeR?branch=main)
<!-- badges: end -->
> What a color! What a viz! > What a color! What a viz!
`visualizeR` proposes some utils to sane colors, ready-to-go color `visualizeR` proposes some utils to sane colors, ready-to-go color
palettes, and a few visualization functions. palettes, and a few visualization functions. The package is thoroughly
tested with comprehensive code coverage.
## Installation ## Installation
@ -15,7 +23,7 @@ You can install the last version of visualizeR from
``` r ``` r
# install.packages("devtools") # install.packages("devtools")
devtools::install_github('gnoblet/visualizeR', build_vignettes = TRUE) devtools::install_github("gnoblet/visualizeR", build_vignettes = TRUE)
``` ```
## Roadmap ## Roadmap
@ -24,17 +32,27 @@ Roadmap is as follows:
- [ ] Full revamp of core functions (colors, pattern, incl. adding test - [ ] Full revamp of core functions (colors, pattern, incl. adding test
and pre-commit structures) and pre-commit structures)
- [x] Add test coverage reporting via codecov
- [ ] Maintain \>80% test coverage across all functions
- [ ] Add other types of plots: - [ ] Add other types of plots:
- [ ] Dumbell - [ ] Dumbell
- [ ] Waffle - [ ] Waffle
- [ ] Donut - [ ] Donut
- [ ] Alluvial - [ ] Alluvial
- [ ] Option for tag with css code + for titles/subtitles/captions
## Request ## Request
Please, do not hesitate to pull request any new viz or colors or color Please, do not hesitate to pull request any new viz or colors or color
palettes, or to email request any change (<gnoblet@zaclys.net>). palettes, or to email request any change (<gnoblet@zaclys.net>).
## Code Coverage
`visualizeR` uses [codecov](https://codecov.io/) for test coverage
reporting. You can see the current coverage status by clicking on the
codecov badge at the top of this README. We aim to maintain high test
coverage to ensure code reliability and stability.
## Colors ## Colors
Functions to access colors and palettes are `color()` or `palette()`. Functions to access colors and palettes are `color()` or `palette()`.
@ -51,7 +69,7 @@ color(unname = F)[1:10]
#> "#71716F" "#000000" "#ffc20a" "#0c7bdc" "#fefe62" #> "#71716F" "#000000" "#ffc20a" "#0c7bdc" "#fefe62"
# Extract a color palette as hexadecimal codes and reversed # Extract a color palette as hexadecimal codes and reversed
palette(palette = 'cat_5_main', reversed = TRUE, color_ramp_palette = FALSE) palette(palette = "cat_5_main", reversed = TRUE, color_ramp_palette = FALSE)
#> [1] "#083d77" "#4ecdc4" "#f4c095" "#b47eb3" "#ffd5ff" #> [1] "#083d77" "#4ecdc4" "#f4c095" "#b47eb3" "#ffd5ff"
# Get all color palettes names # Get all color palettes names
@ -91,7 +109,7 @@ df_island <- penguins |>
ungroup() ungroup()
# Simple bar chart by group with some alpha transparency # Simple bar chart by group with some alpha transparency
bar(df, 'island', 'mean_bl', 'species', x_title = 'Mean of bill length', title = 'Mean of bill length by island and species') bar(df, "island", "mean_bl", "species", x_title = "Mean of bill length", title = "Mean of bill length by island and species")
``` ```
<img src="man/figures/README-example-bar-chart-1.png" width="65%" /> <img src="man/figures/README-example-bar-chart-1.png" width="65%" />
@ -99,7 +117,7 @@ bar(df, 'island', 'mean_bl', 'species', x_title = 'Mean of bill length', title =
``` r ``` r
# Flipped / Horizontal # Flipped / Horizontal
hbar(df, 'island', 'mean_bl', 'species', x_title = 'Mean of bill length', title = 'Mean of bill length by island and species') hbar(df, "island", "mean_bl", "species", x_title = "Mean of bill length", title = "Mean of bill length by island and species")
``` ```
<img src="man/figures/README-example-bar-chart-2.png" width="65%" /> <img src="man/figures/README-example-bar-chart-2.png" width="65%" />
@ -107,7 +125,7 @@ hbar(df, 'island', 'mean_bl', 'species', x_title = 'Mean of bill length', title
``` r ``` r
# Facetted # Facetted
bar(df, 'island', 'mean_bl', facet = 'species', x_title = 'Mean of bill length', title = 'Mean of bill length by island and species', add_color_guide = FALSE) bar(df, "island", "mean_bl", facet = "species", x_title = "Mean of bill length", title = "Mean of bill length by island and species", add_color_guide = FALSE)
``` ```
<img src="man/figures/README-example-bar-chart-3.png" width="65%" /> <img src="man/figures/README-example-bar-chart-3.png" width="65%" />
@ -115,7 +133,7 @@ bar(df, 'island', 'mean_bl', facet = 'species', x_title = 'Mean of bill length',
``` r ``` r
# Flipped, with text, smaller width, and caption # Flipped, with text, smaller width, and caption
hbar(df = df_island, x = 'island', y = 'mean_bl', title = 'Mean of bill length by island', add_text = T, width = 0.6, add_text_suffix = 'mm', add_text_expand_limit = 1.3, add_color_guide = FALSE, caption = "Data: palmerpenguins package.") hbar(df = df_island, x = "island", y = "mean_bl", title = "Mean of bill length by island", add_text = T, width = 0.6, add_text_suffix = "mm", add_text_expand_limit = 1.3, add_color_guide = FALSE, caption = "Data: palmerpenguins package.")
``` ```
<img src="man/figures/README-example-bar-chart-4.png" width="65%" /> <img src="man/figures/README-example-bar-chart-4.png" width="65%" />
@ -124,7 +142,7 @@ hbar(df = df_island, x = 'island', y = 'mean_bl', title = 'Mean of bill length b
``` r ``` r
# Simple scatterplot # Simple scatterplot
point(penguins, 'bill_length_mm', 'flipper_length_mm') point(penguins, "bill_length_mm", "flipper_length_mm")
``` ```
<img src="man/figures/README-example-point-chart-1.png" width="65%" /> <img src="man/figures/README-example-point-chart-1.png" width="65%" />
@ -132,7 +150,7 @@ point(penguins, 'bill_length_mm', 'flipper_length_mm')
``` r ``` r
# Scatterplot with grouping colors, greater dot size, some transparency # Scatterplot with grouping colors, greater dot size, some transparency
point(penguins, 'bill_length_mm', 'flipper_length_mm', 'island', group_title = 'Island', alpha = 0.6, size = 3, title = 'Bill vs. flipper length', , add_color_guide = FALSE) point(penguins, "bill_length_mm", "flipper_length_mm", "island", group_title = "Island", alpha = 0.6, size = 3, title = "Bill vs. flipper length", , add_color_guide = FALSE)
``` ```
<img src="man/figures/README-example-point-chart-2.png" width="65%" /> <img src="man/figures/README-example-point-chart-2.png" width="65%" />
@ -140,7 +158,7 @@ point(penguins, 'bill_length_mm', 'flipper_length_mm', 'island', group_title = '
``` r ``` r
# Facetted scatterplot by island # Facetted scatterplot by island
point(penguins, 'bill_length_mm', 'flipper_length_mm', 'species', 'island', 'fixed', group_title = 'Species', title = 'Bill vs. flipper length by species and island', add_color_guide = FALSE) point(penguins, "bill_length_mm", "flipper_length_mm", "species", "island", "fixed", group_title = "Species", title = "Bill vs. flipper length by species and island", add_color_guide = FALSE)
``` ```
<img src="man/figures/README-example-point-chart-3.png" width="65%" /> <img src="man/figures/README-example-point-chart-3.png" width="65%" />
@ -155,7 +173,7 @@ values.
# Prepare long data # Prepare long data
df <- tibble::tibble( df <- tibble::tibble(
admin1 = rep(letters[1:8], 2), admin1 = rep(letters[1:8], 2),
setting = c(rep(c('Rural', 'Urban'), 4), rep(c('Urban', 'Rural'), 4)), setting = c(rep(c("Rural", "Urban"), 4), rep(c("Urban", "Rural"), 4)),
stat = rnorm(16, mean = 50, sd = 18) stat = rnorm(16, mean = 50, sd = 18)
) |> ) |>
dplyr::mutate(stat = round(stat, 0)) dplyr::mutate(stat = round(stat, 0))
@ -179,7 +197,7 @@ df <- tibble::tibble(
``` r ``` r
# Some summarized data: % of HHs by displacement status # Some summarized data: % of HHs by displacement status
df <- tibble::tibble( df <- tibble::tibble(
status = c('Displaced', 'Non displaced', 'Returnee', 'Don\'t know/Prefer not to say'), status = c("Displaced", "Non displaced", "Returnee", "Don't know/Prefer not to say"),
percentage = c(18, 65, 12, 3) percentage = c(18, 65, 12, 3)
) )
@ -209,12 +227,12 @@ df <- tibble::tibble(
# Some summarized data: % of HHs by self-reported status of displacement in 2021 and in 2022 # Some summarized data: % of HHs by self-reported status of displacement in 2021 and in 2022
df <- tibble::tibble( df <- tibble::tibble(
status_from = c( status_from = c(
rep('Displaced', 4), rep("Displaced", 4),
rep('Non displaced', 4), rep("Non displaced", 4),
rep('Returnee', 4), rep("Returnee", 4),
rep('Dnk/Pnts', 4) rep("Dnk/Pnts", 4)
), ),
status_to = c('Displaced', 'Non displaced', 'Returnee', 'Dnk/Pnts', 'Displaced', 'Non displaced', 'Returnee', 'Dnk/Pnts', 'Displaced', 'Non displaced', 'Returnee', 'Dnk/Pnts', 'Displaced', 'Non displaced', 'Returnee', 'Dnk/Pnts'), status_to = c("Displaced", "Non displaced", "Returnee", "Dnk/Pnts", "Displaced", "Non displaced", "Returnee", "Dnk/Pnts", "Displaced", "Non displaced", "Returnee", "Dnk/Pnts", "Displaced", "Non displaced", "Returnee", "Dnk/Pnts"),
percentage = c(20, 8, 18, 1, 12, 21, 0, 2, 0, 3, 12, 1, 0, 0, 1, 1) percentage = c(20, 8, 18, 1, 12, 21, 0, 2, 0, 3, 12, 1, 0, 0, 1, 1)
) )
@ -294,7 +312,7 @@ lollipop(
x = "admin1", x = "admin1",
y = "stat", y = "stat",
group = "group", group = "group",
dodge_width = 0.8, # Control spacing between grouped lollipops order = "grouped_y",
dot_size = 3.5, dot_size = 3.5,
line_size = 0.8, line_size = 0.8,
y_title = "Value", y_title = "Value",
@ -313,7 +331,6 @@ hlollipop(
x = "admin1", x = "admin1",
y = "stat", y = "stat",
group = "group", group = "group",
dodge_width = 0.7, # Narrower spacing for horizontal orientation
dot_size = 3.5, dot_size = 3.5,
line_size = 0.8, line_size = 0.8,
y_title = "Category", y_title = "Category",
@ -323,19 +340,3 @@ hlollipop(
``` ```
<img src="man/figures/README-example-lollipop-chart-4.png" width="65%" /> <img src="man/figures/README-example-lollipop-chart-4.png" width="65%" />
## Lollipop Chart Features
Lollipop charts offer several advantages:
- Clean visualization of point data with connecting lines to a baseline
- True side-by-side grouped display for easy comparison between
categories
- Each lollipop maintains its position from dot to baseline
- Customizable appearance with parameters for dot size, line width, and
colors
- The `dodge_width` parameter controls spacing between grouped lollipops
The side-by-side positioning for grouped lollipops makes them visually
distinct from dumbbell plots, which typically connect related points on
the same line.

14
codecov.yml Normal file
View file

@ -0,0 +1,14 @@
comment: false
coverage:
status:
project:
default:
target: auto
threshold: 1%
informational: true
patch:
default:
target: auto
threshold: 1%
informational: true

View file

@ -1,7 +1,13 @@
aut aut
Carlito
CMD
codecov
Codecov
coercible coercible
Config Config
covr
cre cre
css
Customizable Customizable
donut donut
Donut Donut
@ -14,9 +20,11 @@ ggrepel
ggtext ggtext
github github
gnoblet gnoblet
gpplot
grDevices grDevices
grey grey
Guillaume Guillaume
hbar
hlollipop hlollipop
horizonal horizonal
https https
@ -26,6 +34,7 @@ knitr
LazyData LazyData
Noblet Noblet
pre pre
README
rio rio
rlang rlang
rmarkdown rmarkdown
@ -36,10 +45,13 @@ RoxygenNote
Segoe Segoe
stringr stringr
testthat testthat
theming
tidyr tidyr
UI UI
vdiffr vdiffr
VignetteBuilder VignetteBuilder
viridis
viridisLite viridisLite
visualizeR visualizeR
withr
zaclys zaclys

View file

@ -52,6 +52,8 @@ bar(
) )
} }
\arguments{ \arguments{
\item{...}{Additional arguments passed to `bar()`}
\item{flip}{TRUE or FALSE (default). Default to TRUE or horizontal bar plot.} \item{flip}{TRUE or FALSE (default). Default to TRUE or horizontal bar plot.}
\item{add_text}{TRUE or FALSE. Add values as text.} \item{add_text}{TRUE or FALSE. Add values as text.}
@ -117,6 +119,10 @@ bar(
\item{add_text_expand_limit}{Default to adding 10\% on top of the bar.} \item{add_text_expand_limit}{Default to adding 10\% on top of the bar.}
\item{add_text_round}{Round the text label.} \item{add_text_round}{Round the text label.}
\item{scale_fill_fun}{Scale fill function. Default to scale_fill_visualizer_discrete().}
\item{scale_color_fun}{Scale color function. Default to scale_color_visualizer_discrete().}
} }
\description{ \description{
`bar()` is a simple bar chart with some customization allowed, in particular the `theme_fun` argument for theming. `hbar()` uses `bar()` with sane defaults for a horizontal bar chart. `bar()` is a simple bar chart with some customization allowed, in particular the `theme_fun` argument for theming. `hbar()` uses `bar()` with sane defaults for a horizontal bar chart.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

After

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 KiB

After

Width:  |  Height:  |  Size: 856 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 KiB

After

Width:  |  Height:  |  Size: 447 KiB

View file

@ -0,0 +1,19 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/internals.R
\name{\%notallin\%}
\alias{\%notallin\%}
\title{Not All In Operator}
\usage{
a \%notallin\% b
}
\arguments{
\item{a}{Vector to test}
\item{b}{Vector to test against}
}
\value{
TRUE if at least one element of `a` is not in `b`, otherwise FALSE
}
\description{
Tests if not all elements of `a` are contained in `b`.
}

View file

@ -0,0 +1,19 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/internals.R
\name{\%notin\%}
\alias{\%notin\%}
\title{Not In Operator}
\usage{
a \%notin\% b
}
\arguments{
\item{a}{Vector or value to test}
\item{b}{Vector to test against}
}
\value{
Logical vector with TRUE for elements of `a` that are not in `b`
}
\description{
A negation of the `%in%` operator that tests if elements of `a` are not in `b`.
}

119
man/lollipop.Rd Normal file
View file

@ -0,0 +1,119 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lollipop.R
\name{hlollipop}
\alias{hlollipop}
\alias{lollipop}
\title{Simple lollipop chart}
\usage{
hlollipop(..., flip = TRUE, theme_fun = theme_lollipop(flip = flip))
lollipop(
df,
x,
y,
group = "",
facet = "",
order = "y",
x_rm_na = TRUE,
y_rm_na = TRUE,
group_rm_na = TRUE,
facet_rm_na = TRUE,
y_expand = 0.1,
add_color = color("cat_5_main_1"),
add_color_guide = TRUE,
flip = FALSE,
wrap = NULL,
alpha = 1,
x_title = NULL,
y_title = NULL,
group_title = NULL,
title = NULL,
subtitle = NULL,
caption = NULL,
dot_size = 4,
line_size = 0.8,
line_color = color("dark_grey"),
dodge_width = 0.9,
theme_fun = theme_lollipop(flip = flip, axis_text_x_angle = 0, axis_text_x_vjust = 0.5,
axis_text_x_hjust = 0.5),
scale_fill_fun = scale_fill_visualizer_discrete(),
scale_color_fun = scale_color_visualizer_discrete()
)
}
\arguments{
\item{...}{Additional arguments passed to `lollipop()`}
\item{flip}{TRUE or FALSE (default). Default to TRUE or horizontal lollipop plot.}
\item{theme_fun}{Whatever theme function. For no custom theme, use theme_fun = NULL.}
\item{df}{A data frame.}
\item{x}{A quoted character column or coercible as a character column.}
\item{y}{A quoted numeric column.}
\item{group}{Some quoted grouping categorical column, e.g. administrative areas or population groups.}
\item{facet}{Some quoted grouping categorical column, e.g. administrative areas or population groups.}
\item{order}{A character scalar specifying the order type (one of "none", "y", "grouped"). See details.}
\item{x_rm_na}{Remove NAs in x?}
\item{y_rm_na}{Remove NAs in y?}
\item{group_rm_na}{Remove NAs in group?}
\item{facet_rm_na}{Remove NAs in facet?}
\item{y_expand}{Multiplier to expand the y axis.}
\item{add_color}{Add a color to dots (if no grouping).}
\item{add_color_guide}{Should a legend be added?}
\item{wrap}{Should x-labels be wrapped? Number of characters.}
\item{alpha}{Fill transparency for dots.}
\item{x_title}{The x scale title. Default to NULL.}
\item{y_title}{The y scale title. Default to NULL.}
\item{group_title}{The group legend title. Default to NULL.}
\item{title}{Plot title. Default to NULL.}
\item{subtitle}{Plot subtitle. Default to NULL.}
\item{caption}{Plot caption. Default to NULL.}
\item{dot_size}{The size of the dots.}
\item{line_size}{The size/width of the line connecting dots to the baseline.}
\item{line_color}{The color of the line connecting dots to the baseline.}
\item{dodge_width}{Width for position dodge when using groups (controls space between grouped lollipops).}
\item{scale_fill_fun}{Scale fill function. Default to scale_fill_visualizer_discrete().}
\item{scale_color_fun}{Scale color function. Default to scale_color_visualizer_discrete().}
}
\value{
A ggplot object
}
\description{
`lollipop()` is a simple lollipop chart (dots connected to the baseline by a segment) with some customization allowed.
`hlollipop()` uses `lollipop()` with sane defaults for a horizontal lollipop chart.
}
\examples{
\dontrun{
df <- data.frame(x = letters[1:5], y = c(10, 5, 7, 12, 8))
# Vertical lollipop
lollipop(df, "x", "y")
# Horizontal lollipop
hlollipop(df, "x", "y")
}
}

View file

@ -75,6 +75,10 @@ point(
\item{caption}{Plot caption. Default to NULL.} \item{caption}{Plot caption. Default to NULL.}
\item{theme_fun}{Whatever theme. Default to theme_point(). NULL if no theming needed.} \item{theme_fun}{Whatever theme. Default to theme_point(). NULL if no theming needed.}
\item{scale_fill_fun}{Scale fill function. Default to scale_fill_visualizer_discrete().}
\item{scale_color_fun}{Scale color function. Default to scale_color_visualizer_discrete().}
} }
\description{ \description{
Simple scatterplot Simple scatterplot

View file

@ -46,6 +46,8 @@ scale_color_visualizer_continuous(
\item{reverse_guide}{Boolean indicating whether the guide should be reversed.} \item{reverse_guide}{Boolean indicating whether the guide should be reversed.}
\item{title_position}{Position of the title. See [ggplot2::guide_legend()]'s title.position argument.}
\item{...}{Additional arguments passed to [ggplot2::discrete_scale()] if discrete or [ggplot2::scale_fill_gradient()] if continuous.} \item{...}{Additional arguments passed to [ggplot2::discrete_scale()] if discrete or [ggplot2::scale_fill_gradient()] if continuous.}
} }
\description{ \description{

View file

@ -1,10 +1,11 @@
% Generated by roxygen2: do not edit by hand % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/theme_bar.R, R/theme_default.R, % Please edit documentation in R/theme_bar.R, R/theme_default.R,
% R/theme_dumbbell.R, R/theme_point.R % R/theme_dumbbell.R, R/theme_lollipop.R, R/theme_point.R
\name{theme_bar} \name{theme_bar}
\alias{theme_bar} \alias{theme_bar}
\alias{theme_default} \alias{theme_default}
\alias{theme_dumbbell} \alias{theme_dumbbell}
\alias{theme_lollipop}
\alias{theme_point} \alias{theme_point}
\title{Custom Theme for Bar Charts} \title{Custom Theme for Bar Charts}
\usage{ \usage{
@ -18,18 +19,18 @@ theme_bar(
theme_default( theme_default(
title_font_family = "Carlito", title_font_family = "Carlito",
title_size = 16, title_size = 20,
title_color = color("dark_grey"), title_color = color("dark_grey"),
title_font_face = "bold", title_font_face = "bold",
title_hjust = NULL, title_hjust = NULL,
title_position_to_plot = TRUE, title_position_to_plot = TRUE,
subtitle_font_family = "Carlito", subtitle_font_family = "Carlito",
subtitle_size = 15, subtitle_size = 16,
subtitle_color = color("dark_grey"), subtitle_color = color("dark_grey"),
subtitle_font_face = "plain", subtitle_font_face = "plain",
subtitle_hjust = NULL, subtitle_hjust = NULL,
text_font_family = "Carlito", text_font_family = "Carlito",
text_size = 13, text_size = 14,
text_color = color("dark_grey"), text_color = color("dark_grey"),
text_font_face = "plain", text_font_face = "plain",
panel_background_color = "#FFFFFF", panel_background_color = "#FFFFFF",
@ -39,15 +40,15 @@ theme_default(
legend_direction = "horizontal", legend_direction = "horizontal",
legend_justification = "center", legend_justification = "center",
legend_reverse = TRUE, legend_reverse = TRUE,
legend_title_size = 13, legend_title_size = 14,
legend_title_color = color("dark_grey"), legend_title_color = color("dark_grey"),
legend_title_font_face = "plain", legend_title_font_face = "plain",
legend_title_font_family = "Carlito", legend_title_font_family = "Carlito",
legend_text_size = 13, legend_text_size = 14,
legend_text_color = color("dark_grey"), legend_text_color = color("dark_grey"),
legend_text_font_face = "plain", legend_text_font_face = "plain",
legend_text_font_family = "Carlito", legend_text_font_family = "Carlito",
facet_size = 14, facet_size = 15,
facet_color = color("dark_grey"), facet_color = color("dark_grey"),
facet_font_face = "bold", facet_font_face = "bold",
facet_font_family = "Carlito", facet_font_family = "Carlito",
@ -61,7 +62,7 @@ theme_default(
axis_line_y = TRUE, axis_line_y = TRUE,
axis_ticks_y = TRUE, axis_ticks_y = TRUE,
axis_text_font_family = "Carlito", axis_text_font_family = "Carlito",
axis_text_size = 13, axis_text_size = 14,
axis_text_color = color("dark_grey"), axis_text_color = color("dark_grey"),
axis_text_font_face = "plain", axis_text_font_face = "plain",
axis_title_size = 15, axis_title_size = 15,
@ -83,17 +84,26 @@ theme_default(
caption_font_family = "Carlito", caption_font_family = "Carlito",
caption_font_face = "plain", caption_font_face = "plain",
caption_position_to_plot = TRUE, caption_position_to_plot = TRUE,
caption_size = 11, caption_size = 12,
caption_color = color("dark_grey"), caption_color = color("dark_grey"),
... ...
) )
theme_dumbbell() theme_dumbbell()
theme_lollipop(
flip = TRUE,
axis_text_x_angle = 0,
axis_text_x_vjust = 0.5,
axis_text_x_hjust = 0.5
)
theme_point() theme_point()
} }
\arguments{ \arguments{
\item{flip}{Logical. Whether the plot is flipped (horizonal).} \item{flip}{Logical. Whether the plot is flipped (horizontal).}
\item{add_text}{TRUE or FALSE. Add values as text.}
\item{axis_text_x_angle}{Angle for x-axis text.} \item{axis_text_x_angle}{Angle for x-axis text.}
@ -101,18 +111,30 @@ theme_point()
\item{axis_text_x_hjust}{Horizontal justification for x-axis text.} \item{axis_text_x_hjust}{Horizontal justification for x-axis text.}
\item{title_font_family}{Title font family. Default to "Roboto Condensed".} \item{title_font_family}{Title font family. Default to "Carlito".}
\item{title_size}{The size of the legend title. Defaults to 11.} \item{title_size}{The size of the title. Defaults to 12.}
\item{title_color}{Legend title color.} \item{title_color}{Title color.}
\item{title_font_face}{Legend title font face. Default to "plain". Font face ("plain", "italic", "bold", "bold.italic").} \item{title_font_face}{Title font face. Default to "bold". Font face ("plain", "italic", "bold", "bold.italic").}
\item{title_hjust}{Title horizontal justification. Default to NULL. Use 0.5 to center the title.} \item{title_hjust}{Title horizontal justification. Default to NULL. Use 0.5 to center the title.}
\item{title_position_to_plot}{TRUE or FALSE. Positioning to plot or to panel?} \item{title_position_to_plot}{TRUE or FALSE. Positioning to plot or to panel?}
\item{subtitle_font_family}{Subtitle font family. Default to "Carlito".}
\item{subtitle_size}{The size of the subtitle. Defaults to 10.}
\item{subtitle_color}{Subtitle color.}
\item{subtitle_font_face}{Subtitle font face. Default to "plain". Font face ("plain", "italic", "bold", "bold.italic").}
\item{subtitle_hjust}{Subtitle horizontal justification. Default to NULL. Use 0.5 to center the subtitle.}
\item{text_font_family}{Text font family. Default to "Carlito".}
\item{text_size}{The size of all text other than the title, subtitle and caption. Defaults to 10.} \item{text_size}{The size of all text other than the title, subtitle and caption. Defaults to 10.}
\item{text_color}{Text color.} \item{text_color}{Text color.}
@ -139,12 +161,26 @@ theme_point()
\item{legend_title_font_face}{Legend title font face. Default to "plain". Font face ("plain", "italic", "bold", "bold.italic").} \item{legend_title_font_face}{Legend title font face. Default to "plain". Font face ("plain", "italic", "bold", "bold.italic").}
\item{legend_title_font_family}{Legend title font family. Default to "Carlito".}
\item{legend_text_size}{Legend text size.} \item{legend_text_size}{Legend text size.}
\item{legend_text_color}{Legend text color.} \item{legend_text_color}{Legend text color.}
\item{legend_text_font_face}{Legend text font face. Default to "plain". Font face ("plain", "italic", "bold", "bold.italic").} \item{legend_text_font_face}{Legend text font face. Default to "plain". Font face ("plain", "italic", "bold", "bold.italic").}
\item{legend_text_font_family}{Legend text font family. Default to "Carlito".}
\item{facet_size}{Facet font size.}
\item{facet_color}{Facet font color.}
\item{facet_font_face}{Facet font face. Default to "plain". Font face ("plain", "italic", "bold", "bold.italic").}
\item{facet_font_family}{Facet font family. Default to "Carlito".}
\item{facet_bg_color}{Facet background color.}
\item{axis_x}{Boolean. Do you need x-axis?} \item{axis_x}{Boolean. Do you need x-axis?}
\item{axis_y}{Boolean. Do you need y-axis?} \item{axis_y}{Boolean. Do you need y-axis?}
@ -161,6 +197,8 @@ theme_point()
\item{axis_ticks_y}{Boolean. Do you need the line for the y-axis?} \item{axis_ticks_y}{Boolean. Do you need the line for the y-axis?}
\item{axis_text_font_family}{Axis text font family. Default to "Carlito".}
\item{axis_text_size}{Axis text size.} \item{axis_text_size}{Axis text size.}
\item{axis_text_color}{Axis text color.} \item{axis_text_color}{Axis text color.}
@ -193,19 +231,39 @@ theme_point()
\item{grid_minor_y_size}{Minor Y line size.} \item{grid_minor_y_size}{Minor Y line size.}
\item{caption_font_family}{Caption font family. Default to "Carlito".}
\item{caption_font_face}{Caption font face. Default to "plain". Font face ("plain", "italic", "bold", "bold.italic").}
\item{caption_position_to_plot}{TRUE or FALSE. Positioning to plot or to panel?} \item{caption_position_to_plot}{TRUE or FALSE. Positioning to plot or to panel?}
\item{...}{Additional arguments passed to [ggplot2::theme()].} \item{caption_size}{The size of the caption. Defaults to 10.}
\item{font_family}{The font family for all plot's texts. Default to "Segoe UI".} \item{caption_color}{Caption color.}
\item{...}{Additional arguments passed to [ggplot2::theme()].}
} }
\value{ \value{
A custom theme object. A custom theme object.
A ggplot2 theme object
A custom theme object. A custom theme object.
} }
\description{ \description{
Give some reach colors and fonts to a ggplot. Give some reach colors and fonts to a ggplot.
Theme for dumbbell charts based on theme_default. Theme for dumbbell charts based on theme_default.
A custom theme specifically designed for lollipop charts with appropriate grid lines and axis styling
based on whether the chart is flipped (horizontal) or not.
}
\examples{
\dontrun{
library(ggplot2)
df <- data.frame(x = letters[1:5], y = c(10, 5, 7, 12, 8))
ggplot(df, aes(x, y)) +
geom_point() +
theme_lollipop()
}
} }

View file

@ -1,12 +1,12 @@
# # This file is part of the standard setup for testthat. # This file is part of the standard setup for testthat.
# # It is recommended that you do not modify it. # It is recommended that you do not modify it.
# # #
# # Where should you do additional test configuration? # Where should you do additional test configuration?
# # Learn more about the roles of various files in: # Learn more about the roles of various files in:
# # * https://r-pkgs.org/testing-design.html#sec-tests-files-overview # * https://r-pkgs.org/testing-design.html#sec-tests-files-overview
# # * https://testthat.r-lib.org/articles/special-files.html # * https://testthat.r-lib.org/articles/special-files.html
# library(testthat) library(testthat)
# library(visualizeR) library(visualizeR)
# test_check("visualizeR") test_check("visualizeR")

View file

@ -47,41 +47,28 @@ test_that("palette_gen returns appropriate function types", {
expect_true(is.function(div_fn)) expect_true(is.function(div_fn))
}) })
test_that("palette_gen forwards arguments to appropriate function", { test_that("palette_gen dispatches to correct function types", {
# Skip the test if mockery is not available # Test for categorical type
skip_if_not_installed("mockery") cat_result <- palette_gen("cat_5_main", "categorical", direction = -1)
skip_if_not(exists("with_mocked_bindings")) expect_true(is.function(cat_result))
# Create a mock for palette_gen_categorical # Verify it behaves like a categorical palette function
mockery::with_mocked_bindings( expect_equal(length(cat_result(3)), 3)
palette_gen_categorical = function(palette, direction) { expect_type(cat_result(3), "character")
return(list(
palette = palette,
direction = direction,
type = "categorical"
))
},
palette_gen_sequential = function(palette, direction, ...) {
return(list(
palette = palette,
direction = direction,
type = "sequential"
))
},
code = {
# Test categorical forwarding
result <- palette_gen("cat_palette", "categorical", direction = -1)
expect_equal(result$palette, "cat_palette")
expect_equal(result$direction, -1)
expect_equal(result$type, "categorical")
# Test sequential forwarding # Test for sequential type
result <- palette_gen("seq_palette", "sequential", direction = -1) seq_result <- palette_gen("cat_5_main", "sequential", direction = -1)
expect_equal(result$palette, "seq_palette") expect_true(is.function(seq_result))
expect_equal(result$direction, -1)
expect_equal(result$type, "sequential") # Verify it behaves like a sequential palette function
} expect_equal(length(seq_result(5)), 5)
) expect_type(seq_result(5), "character")
# Test for divergent type - should work like sequential
div_result <- palette_gen("div_5_orange_blue", "divergent", direction = -1)
expect_true(is.function(div_result))
expect_equal(length(div_result(7)), 7)
expect_type(div_result(7), "character")
}) })
test_that("palette_gen_categorical validates parameters", { test_that("palette_gen_categorical validates parameters", {

View file

@ -1,57 +0,0 @@
---
title: "Bar charts"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Bar charts}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>"
)
```
Let's start by importing some data and running some data wrangling:
```{r data-import}
library(rio)
library(data.table)
dat <- import("https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/11_SevCatOneNumNestedOneObsPerGroup.csv", data.table = TRUE)
setDT(dat)
# in all character columns, tranform empty string to NA
vars_chr <- colnames(dat)[sapply(dat, is.character)]
dat[, (vars_chr) := lapply(.SD, function(x) fifelse(x == "", NA_character_, x)), .SDcols = vars_chr]
# in value, if -1 replace with NA
dat[, value := fifelse(value == -1, NA_real_, value)]
# remove lines where value is NA (in place)
dat <- dat[!is.na(value), ]
# kepp only top 20 values and divide data to get million units
df <- dat[
!is.na(value), ][
order(value, decreasing = TRUE), ][
1:20, ][
, value := value/1000000, ][
, key := ifelse(key == "Democratic Republic of the Congo", "DRC", key)]
```
Now, let's see the defaults for a horizontal bar diagram without any grouping and ordering values from highest to smallest:
```{r hbar}
library(visualizeR)
hbar(
df,
x = "key",
y = "value",
facet = "region",
order = "y",
title = "Top 20 countries by population (in Million)"
)
```
Moving on to a vertical bar chart, with country facets and groups