Update README with grouped lollipop chart examples and roadmap

This commit is contained in:
gnoblet 2025-07-02 12:23:09 +02:00
parent 767ad2f064
commit 7ccaa74d17
2 changed files with 240 additions and 73 deletions

View file

@ -32,12 +32,21 @@ You can install the last version of visualizeR from [GitHub](https://github.com/
```{r, eval = FALSE} ```{r, eval = FALSE}
# install.packages("devtools") # install.packages("devtools")
devtools::install_github('gnoblet/visualizeR', build_vignettes = TRUE) devtools::install_github("gnoblet/visualizeR", build_vignettes = TRUE)
``` ```
## Roadmap ## Roadmap
Roadmap is as follows: - [ ] Full revamp \## Request Roadmap is as follows:
- [ ] Full revamp of core functions (colors, pattern, incl. adding test and pre-commit structures)
- [ ] Add other types of plots:
- [ ] Dumbell
- [ ] Waffle
- [ ] Donut
- [ ] Alluvial
## 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}).
@ -52,7 +61,7 @@ library(visualizeR)
color(unname = F)[1:10] color(unname = F)[1:10]
# 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)
# Get all color palettes names # Get all color palettes names
palette(show_palettes = TRUE) palette(show_palettes = TRUE)
@ -83,29 +92,29 @@ 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")
# 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")
# 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)
# 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.")
``` ```
### Example 2: Scatterplot ### Example 2: Scatterplot
```{r example-point-chart, out.width = '65%', eval = TRUE} ```{r example-point-chart, out.width = '65%', eval = TRUE}
# Simple scatterplot # Simple scatterplot
point(penguins, 'bill_length_mm', 'flipper_length_mm') point(penguins, "bill_length_mm", "flipper_length_mm")
# 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)
# 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)
``` ```
### Example 3: Dumbbell plot ### Example 3: Dumbbell plot
@ -116,7 +125,7 @@ Remember to ensure that your data are in the long format and you only have two g
# 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))
@ -140,7 +149,7 @@ df <- tibble::tibble(
```{r example-donut-plot, out.width = '65%', warning = FALSE} ```{r example-donut-plot, out.width = '65%', warning = FALSE}
# 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)
) )
@ -170,12 +179,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)
) )
@ -195,7 +204,7 @@ df <- tibble::tibble(
### Example 7: Lollipop chart ### Example 7: Lollipop chart
```{r example-lollipop-chart, out.width = "65%", warning = FALSE} ```{r example-lollipop-chart, out.width = "65%", warning = FALSE, eval = TRUE}
library(tidyr) library(tidyr)
# Prepare long data # Prepare long data
df <- tibble::tibble( df <- tibble::tibble(
@ -204,15 +213,79 @@ df <- tibble::tibble(
) |> ) |>
dplyr::mutate(stat = round(stat, 0)) dplyr::mutate(stat = round(stat, 0))
# Make lollipop plot, vertical with 45 degrees angle X-labels # Simple vertical lollipop chart
# lollipop(df, lollipop(
# admin1, df = df,
# stat, x = "admin1",
# arrange = FALSE, y = "stat",
# add_text = FALSE, flip = FALSE,
# flip = FALSE, dot_size = 3,
# y_title = "% of HHs", y_title = "% of HHs",
# x_title = "Admin 1", x_title = "Admin 1",
# title = "% of HHs that reported having received a humanitarian assistance" title = "% of HHs that received humanitarian assistance"
# ) )
# Horizontal lollipop chart with custom colors
hlollipop(
df = df,
x = "admin1",
y = "stat",
dot_size = 4,
line_size = 1,
add_color = color("cat_5_main_2"),
line_color = color("cat_5_main_4"),
y_title = "% of HHs",
x_title = "Admin 1",
title = "% of HHs that received humanitarian assistance"
)
# Create data for grouped lollipop - using set.seed for reproducibility
set.seed(123)
df_grouped <- tibble::tibble(
admin1 = rep(c("A", "B", "C", "D", "E", "F"), 2),
group = rep(c("Group A", "Group B"), each = 6),
stat = c(rnorm(6, mean = 40, sd = 10), rnorm(6, mean = 60, sd = 10))
) |>
dplyr::mutate(stat = round(stat, 0))
# Grouped lollipop chart with proper side-by-side positioning
lollipop(
df = df_grouped,
x = "admin1",
y = "stat",
group = "group",
order = "grouped_y",
dodge_width = 0.8, # Control spacing between grouped lollipops
dot_size = 3.5,
line_size = 0.8,
y_title = "Value",
x_title = "Category",
title = "True side-by-side grouped lollipop chart"
)
# Horizontal grouped lollipop chart
hlollipop(
df = df_grouped,
x = "admin1",
y = "stat",
group = "group",
dodge_width = 0.7, # Narrower spacing for horizontal orientation
dot_size = 3.5,
line_size = 0.8,
y_title = "Category",
x_title = "Value",
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.

182
README.md
View file

@ -15,12 +15,22 @@ 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
Roadmap is as follows: - \[ \] Full revamp \## Request Roadmap is as follows:
- [ ] Full revamp of core functions (colors, pattern, incl. adding test
and pre-commit structures)
- [ ] Add other types of plots:
- [ ] Dumbell
- [ ] Waffle
- [ ] Donut
- [ ] Alluvial
## 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>).
@ -35,13 +45,13 @@ library(visualizeR)
# Get all saved colors, named # Get all saved colors, named
color(unname = F)[1:10] color(unname = F)[1:10]
#> white lighter_grey light_grey dark_grey black #> white lighter_grey light_grey dark_grey light_blue_grey
#> "#FFFFFF" "#F5F5F5" "#E3E3E3" "#464647" "#000000" #> "#FFFFFF" "#F5F5F5" "#E3E3E3" "#464647" "#B3C6D1"
#> cat_2_yellow_1 cat_2_yellow_2 cat_2_light_1 cat_2_light_2 cat_2_green_1 #> grey black cat_2_yellow_1 cat_2_yellow_2 cat_2_light_1
#> "#ffc20a" "#0c7bdc" "#fefe62" "#d35fb7" "#1aff1a" #> "#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
@ -81,7 +91,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%" />
@ -89,7 +99,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%" />
@ -97,15 +107,15 @@ hbar(df, "island", "mean_bl", "species", x_title = "Mean of bill length", title
``` r ``` r
# Facetted # Facetted
bar(df, "island", "mean_bl", "species", 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%" />
``` r ``` r
# Flipped, with text, smaller width # Flipped, with text, smaller width, and caption
hbar(df = df_island, x = "island", y = "mean_bl", group = "island", 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) 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%" />
@ -114,7 +124,7 @@ hbar(df = df_island, x = "island", y = "mean_bl", group = "island", title = "Mea
``` 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%" />
@ -122,7 +132,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%" />
@ -130,7 +140,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%" />
@ -145,7 +155,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))
@ -155,12 +165,12 @@ df <- tibble::tibble(
# dumbbell( # dumbbell(
# df, # df,
# "stat", # 'stat',
# "setting", # 'setting',
# "admin1", # 'admin1',
# title = "% of HHs that reported open defecation as sanitation facility", # title = '% of HHs that reported open defecation as sanitation facility',
# group_y_title = "Admin 1", # group_y_title = 'Admin 1',
# group_x_title = "Setting" # group_x_title = 'Setting'
# ) # )
``` ```
@ -169,7 +179,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)
) )
@ -178,11 +188,11 @@ df <- tibble::tibble(
# status, # status,
# percentage, # percentage,
# hole_size = 3, # hole_size = 3,
# add_text_suffix = "%", # add_text_suffix = '%',
# add_text_color = color("dark_grey"), # add_text_color = color('dark_grey'),
# add_text_treshold_display = 5, # add_text_treshold_display = 5,
# x_title = "Displacement status", # x_title = 'Displacement status',
# title = "% of HHs by displacement status" # title = '% of HHs by displacement status'
# ) # )
``` ```
@ -190,7 +200,7 @@ df <- tibble::tibble(
``` r ``` r
# #
# waffle(df, status, percentage, x_title = "A caption", title = "A title", subtitle = "A subtitle") # waffle(df, status, percentage, x_title = 'A caption', title = 'A title', subtitle = 'A subtitle')
``` ```
### Example 6: Alluvial chart ### Example 6: Alluvial chart
@ -199,12 +209,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)
) )
@ -233,15 +243,99 @@ df <- tibble::tibble(
) |> ) |>
dplyr::mutate(stat = round(stat, 0)) dplyr::mutate(stat = round(stat, 0))
# Make lollipop plot, vertical with 45 degrees angle X-labels # Simple vertical lollipop chart
# lollipop(df, lollipop(
# admin1, df = df,
# stat, x = "admin1",
# arrange = FALSE, y = "stat",
# add_text = FALSE, flip = FALSE,
# flip = FALSE, dot_size = 3,
# y_title = "% of HHs", y_title = "% of HHs",
# x_title = "Admin 1", x_title = "Admin 1",
# title = "% of HHs that reported having received a humanitarian assistance" title = "% of HHs that received humanitarian assistance"
# ) )
``` ```
<img src="man/figures/README-example-lollipop-chart-1.png" width="65%" />
``` r
# Horizontal lollipop chart with custom colors
hlollipop(
df = df,
x = "admin1",
y = "stat",
dot_size = 4,
line_size = 1,
add_color = color("cat_5_main_2"),
line_color = color("cat_5_main_4"),
y_title = "% of HHs",
x_title = "Admin 1",
title = "% of HHs that received humanitarian assistance"
)
```
<img src="man/figures/README-example-lollipop-chart-2.png" width="65%" />
``` r
# Create data for grouped lollipop - using set.seed for reproducibility
set.seed(123)
df_grouped <- tibble::tibble(
admin1 = rep(c("A", "B", "C", "D", "E", "F"), 2),
group = rep(c("Group A", "Group B"), each = 6),
stat = c(rnorm(6, mean = 40, sd = 10), rnorm(6, mean = 60, sd = 10))
) |>
dplyr::mutate(stat = round(stat, 0))
# Grouped lollipop chart with proper side-by-side positioning
lollipop(
df = df_grouped,
x = "admin1",
y = "stat",
group = "group",
dodge_width = 0.8, # Control spacing between grouped lollipops
dot_size = 3.5,
line_size = 0.8,
y_title = "Value",
x_title = "Category",
title = "True side-by-side grouped lollipop chart"
)
```
<img src="man/figures/README-example-lollipop-chart-3.png" width="65%" />
``` r
# Horizontal grouped lollipop chart
hlollipop(
df = df_grouped,
x = "admin1",
y = "stat",
group = "group",
dodge_width = 0.7, # Narrower spacing for horizontal orientation
dot_size = 3.5,
line_size = 0.8,
y_title = "Category",
x_title = "Value",
title = "Horizontal side-by-side grouped lollipop chart"
)
```
<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.