Skip to content

Commit cf9c0f6

Browse files
committed
add span parameter to geom_dots
1 parent 5048854 commit cf9c0f6

19 files changed

+659
-270
lines changed

NEWS.md

Lines changed: 74 additions & 63 deletions
Large diffs are not rendered by default.

R/bin_dots.R

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ NULL
2020
#' between dot centers
2121
#' @eval rd_param_dots_layout()
2222
#' @eval rd_param_dots_overlaps()
23+
#' @eval rd_param_dots_span()
2324
#' @eval rd_param_side("dots")
2425
#' @param orientation <[string][character]> Whether the dots are laid out horizontally
2526
#' or vertically. Follows the naming scheme of [geom_slabinterval()]:
@@ -70,7 +71,8 @@ bin_dots = function(x, y, binwidth,
7071
layout = c("bin", "weave", "hex", "swarm", "swarm2", "bar"),
7172
side = c("topright", "top", "right", "bottomleft", "bottom", "left", "topleft", "bottomright", "both"),
7273
orientation = c("horizontal", "vertical", "y", "x"),
73-
overlaps = "nudge"
74+
overlaps = "nudge",
75+
span = waiver()
7476
) {
7577
side = match.arg(side)
7678
orientation = match.arg(orientation)
@@ -97,7 +99,8 @@ bin_dots = function(x, y, binwidth,
9799
stackratio = stackratio,
98100
side = side,
99101
orientation = orientation,
100-
overlaps = overlaps
102+
overlaps = overlaps,
103+
span = span
101104
)
102105
binning = arrange_bins(binner, d[[x]], binwidth = binwidth)
103106
d = place_dots(binner, d, binning)

R/binner.R

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,7 @@ binner_bin = new_binner_class(
124124
class_logical,
125125
getter = function(self) FALSE
126126
)
127-
),
128-
constructor = binner@constructor
127+
)
129128
)
130129

131130
#' Weave binner

R/find_dotplot_binwidth.R

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ find_dotplot_binwidth = function(
6060
heightratio = 1,
6161
stackratio = 1,
6262
layout = c("bin", "weave", "hex", "swarm", "swarm2", "bar"),
63-
side = c("topright", "top", "right", "bottomleft", "bottom", "left", "topleft", "bottomright", "both")
63+
side = c("topright", "top", "right", "bottomleft", "bottom", "left", "topleft", "bottomright", "both"),
64+
span = waiver()
6465
) {
6566
side = match.arg(side)
6667

@@ -78,7 +79,8 @@ find_dotplot_binwidth = function(
7879
maxheight = maxheight,
7980
heightratio = heightratio,
8081
stackratio = stackratio,
81-
side = side
82+
side = side,
83+
span = span
8284
)
8385
min_binning = arrange_bins(binner, x, nbins = min_nbins)
8486

R/geom_dotsinterval.R

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ NULL
1212
dots_grob = function(data, x, y, xscale = 1,
1313
name = NULL, gp = gpar(), vp = NULL,
1414
dotsize = 1.07, stackratio = 1, binwidth = NA, layout = "bin",
15-
overlaps = "nudge", overflow = "warn",
15+
overlaps = "nudge", overflow = "warn", span = waiver(),
1616
subguide = "dots",
1717
verbose = FALSE,
1818
orientation = "vertical",
@@ -29,7 +29,7 @@ dots_grob = function(data, x, y, xscale = 1,
2929
datas = datas,
3030
xscale = xscale,
3131
dotsize = dotsize, stackratio = stackratio, binwidth = binwidth, layout = layout,
32-
overlaps = overlaps, overflow = overflow,
32+
overlaps = overlaps, overflow = overflow, span = span,
3333
subguide = subguide,
3434
verbose = verbose,
3535
orientation = orientation,
@@ -60,6 +60,7 @@ makeContent.dots_grob = function(x) {
6060
binwidth = grob_$binwidth
6161
layout = grob_$layout
6262
overlaps = grob_$overlaps
63+
span = grob_$span
6364
overflow = grob_$overflow
6465
subguide = grob_$subguide
6566
stackratio = grob_$stackratio
@@ -100,7 +101,15 @@ makeContent.dots_grob = function(x) {
100101
# find the best bin widths across all the dotplots we are going to draw
101102
binwidths = map_dbl_(datas, function(d) {
102103
maxheight = max(d[[ymax]] - d[[ymin]])
103-
find_dotplot_binwidth(d[[x]], maxheight, heightratio, stackratio, layout = layout, side = d$side[[1]])
104+
find_dotplot_binwidth(
105+
d[[x]],
106+
maxheight,
107+
heightratio,
108+
stackratio,
109+
layout = layout,
110+
side = d$side[[1]],
111+
span = span
112+
)
104113
})
105114

106115
binwidth = min(binwidths, user_max_binwidth)
@@ -151,7 +160,7 @@ makeContent.dots_grob = function(x) {
151160
dot_positions = bin_dots(
152161
d$x, d$y,
153162
binwidth = binwidth, heightratio = heightratio, stackratio = stackratio,
154-
overlaps = overlaps,
163+
overlaps = overlaps, span = span,
155164
layout = layout, side = d$side[[1]], orientation = orientation
156165
)
157166

@@ -274,7 +283,7 @@ draw_slabs_dots = function(
274283
...,
275284
fill_type, na.rm,
276285
dotsize, stackratio, binwidth, layout,
277-
overlaps, overflow,
286+
overlaps, overflow, span,
278287
subguide,
279288
verbose
280289
) {
@@ -337,6 +346,7 @@ draw_slabs_dots = function(
337346
binwidth = binwidth,
338347
layout = layout,
339348
overlaps = overlaps,
349+
span = span,
340350
overflow = overflow,
341351
subguide = subguide,
342352
verbose = verbose,
@@ -505,6 +515,12 @@ GeomDotsinterval = ggproto("GeomDotsinterval", GeomSlabinterval,
505515
'),
506516
smooth = glue_doc('
507517
<[function] | [string][character]> Smoother to apply to dot positions.
518+
519+
**Note:** in most cases, more reliable forms of smoothing can be achieved via
520+
changing layouts (e.g. using `layout = "bar"` instead of `smooth = "bar"`)
521+
or via other parameters (e.g. using `span = 1.25` for `"bin"`, `"hex"`,
522+
or `"weave"` layouts).
523+
508524
One of:
509525
- A function that takes a numeric vector of dot positions and returns a
510526
smoothed version of that vector, such as [smooth_bounded()],
@@ -536,27 +552,31 @@ GeomDotsinterval = ggproto("GeomDotsinterval", GeomSlabinterval,
536552
layout = glue_doc('
537553
<[string][character]> The layout method used for the dots. One of: \\itemize{
538554
\\item `"bin"` (default): places dots on the off-axis at the midpoint of
539-
their bins as in the classic Wilkinson dotplot. This maintains the
555+
their bins as in the classic Wilkinson (1999) dotplot. This maintains the
540556
alignment of rows and columns in the dotplot. This layout is slightly
541-
different from the classic Wilkinson algorithm in that: (1) it nudges
542-
bins slightly to avoid overlapping bins and (2) if the input data are
543-
symmetrical it will return a symmetrical layout.
557+
different from the Wilkinson algorithm: (1) it nudges bin positions
558+
slightly to avoid overlaps (see the `overlaps` parameter); (2) by default
559+
it does not apply a smoothing pass (but Wilkinson-style smoothing can be
560+
applied via passing `span = 1.25`); (3) it uses a backwards sweep to
561+
reduce right-edge binning effects; (4) if the input data are symmetrical,
562+
it bins out from the center to return a symmetrical layout.
544563
\\item `"weave"`: uses the same basic binning approach of `"bin"`, but
545564
places dots in the off-axis at their actual positions (unless
546-
`overlaps = "nudge"`, in which case overlaps may be nudged out of the
547-
way). This maintains the alignment of rows but does not align dots
565+
`overlaps = "nudge"`, in which case overlaps within rows are nudged out
566+
of the way). This maintains the alignment of rows but does not align dots
548567
within columns.
549568
\\item `"hex"`: uses the same basic binning approach of `"bin"`, but
550-
alternates placing dots `+ binwidth/4` or `- binwidth/4` in the
551-
off-axis from the bin center. This allows hexagonal packing by setting
552-
a `stackratio` less than 1 (something like `0.9` tends to work).
569+
alternates placing dots at \\eqn{\\pm} `binwidth/4` in the
570+
off-axis from the bin center, giving a hexagonal layout. For
571+
an equilateral hexagonal packing, set `dotsize = k` and
572+
`stackratio = sqrt(3/4) / k` for some `k` (e.g. `0.9`).
553573
\\item `"swarm"`: uses a version of the `"compactswarm"` layout from
554-
[beeswarm::beeswarm()] (with minor modifications to improve visual
555-
symmetry when `side = "both"`). Does not maintain alignment of rows or
556-
columns, but can be more compact and neat-looking, especially for
557-
sample data (as opposed to quantile dotplots of theoretical
558-
distributions, which may look better with `"bin"`, `"weave"`, or
559-
`"hex"`).
574+
[beeswarm][beeswarm::beeswarm()], with minor modifications to improve visual
575+
symmetry when `side = "both"`. Ensures dot are positioned exacty at their
576+
underlying data values. Does not maintain alignment of rows or
577+
columns, but can be more compact, especially for sample data (as opposed
578+
to quantile dotplots of theoretical distributions, which may look better
579+
with `"bin"`, `"weave"`, or `"hex"`).
560580
\\item `"bar"`: for discrete distributions, lays out duplicate values in
561581
rectangular bars.
562582
}'),
@@ -574,6 +594,29 @@ GeomDotsinterval = ggproto("GeomDotsinterval", GeomSlabinterval,
574594
dots to their desired positions, subject to the constraint that adjacent
575595
dots do not overlap.
576596
}'),
597+
span = glue_doc('
598+
<scalar [numeric]> Non-negative smoothing/spacing parameter expressed as a
599+
fraction. The specific use and default value (`waiver()`) depend on the
600+
`layout`:
601+
\\itemize{
602+
\\item{For `layout = "bin"`, `"hex"`, or `"weave"`: A smoothing parameter
603+
expressed as a proportion of the `binwidth` (default: `0`, no smoothing).
604+
Use `span = 1.25` to apply moderate smoothing.
605+
A positive `span` applies a moving average to adjacent
606+
bins whose midpoints are within `span * binwidth` of each other,
607+
exchanging dots between bins using the method described
608+
by Wilkinson (1999). Specifically, for every bin \\eqn{i} with midpoint
609+
\\eqn{m_i} containing \\eqn{f(i)} dots, the algorithm moves
610+
\\eqn{\\left\\lfloor\\frac{f(i + 1) - f(i)}{2}\\right\\rfloor} dots from bin
611+
\\eqn{i + 1} to bin \\eqn{i} if \\eqn{m_{i + 1} - m_i \\le} `span`.
612+
Good values for `span` are typically between `1` and `2`. `span = 1.25` is
613+
equivalent to Wilkinson\'s recommendation, which smooths bins that are at most
614+
`binwidth/4` apart.
615+
}
616+
\\item For `layout = "bar"`: Width of bars expressed as a proportion of the data resolution
617+
(default: 0.9). Values are typically between `0` and `1`. Use smaller values to
618+
increase the spacing between bars.
619+
}'),
577620
verbose = glue_doc('
578621
<scalar [logical]> If `TRUE`, print out the bin width of the dotplot. Can be useful
579622
if you want to start from an automatically-selected bin width and then adjust it
@@ -594,6 +637,7 @@ GeomDotsinterval = ggproto("GeomDotsinterval", GeomSlabinterval,
594637
stackratio = 1,
595638
layout = "bin",
596639
overlaps = "nudge",
640+
span = waiver(),
597641
smooth = "none",
598642
overflow = "warn",
599643
verbose = FALSE

R/rd_dotsinterval.R

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ rd_dotsinterval_shortcut_geom = function(
3535
'),
3636
'@template details-dotsinterval-family',
3737
'@template references-quantile-dotplots',
38+
'@template references-dotplots',
3839
rd_dotsinterval_aesthetics(geom_name),
3940
if (exists(paste0("stat_", geom_name))) glue_doc('
4041
@seealso See [stat_<<geom_name>>()] for the stat version, intended for
@@ -104,6 +105,7 @@ rd_dotsinterval_shortcut_stat = function(
104105
the default connection between [stat_<<stat_name>>()] and [geom_<<geom_name>>()]'),
105106
'@template details-dotsinterval-family',
106107
'@template references-quantile-dotplots',
108+
'@template references-dotplots',
107109
'@template details-x-y-xdist-ydist',
108110
glue_doc('
109111
@return A [ggplot2::Stat] representing a <<chart_type>> geometry which can
@@ -180,3 +182,7 @@ rd_param_dots_layout = function() {
180182
rd_param_dots_overlaps = function() {
181183
paste0("@param overlaps ", GeomDotsinterval$get_param_docs()$overlaps)
182184
}
185+
186+
rd_param_dots_span = function() {
187+
paste0("@param span ", GeomDotsinterval$get_param_docs()$span)
188+
}

R/rd_slabinterval.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ rd_aes_side = function(slab = "slab") {
205205
glue_doc('
206206
Which side to place the <<slab>> on. One of:
207207
\\itemize{
208-
\\item `"topright"`, `"top"`, and `"right"` are synonyms which draw the <<slab>> on the top or the
208+
\\item `"topright"`, `"top"`, and `"right"` are synonyms which draw the <<slab>> on the top or the
209209
right depending on if `orientation` is `"horizontal"` or `"vertical"`.
210210
\\item `"bottomleft"`, `"bottom"`, and `"left"` are synonyms which draw the <<slab>> on the bottom
211211
or the left depending on if `orientation` is `"horizontal"` or `"vertical"`.
@@ -220,5 +220,5 @@ rd_aes_side = function(slab = "slab") {
220220
# shared parameter docs ---------------------------------------------------
221221

222222
rd_param_side = function(slab = "slab") {
223-
paste0("@param side ", rd_aes_side(slab))
223+
paste0("@param side <[string][character]> ", rd_aes_side(slab))
224224
}

R/validate.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ validate_positive_scalar = function(value) {
1616
}
1717

1818
validate_positive_scalar_integerish = function(value) {
19-
validate_positive_scalar(value) %||% if (as.integer(value) != value) {
19+
validate_positive_scalar(value) %||% if (!is.infinite(value) && as.integer(value) != value) {
2020
"must be an integer."
2121
}
2222
}

man-roxygen/references-dotplots.R

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#' @references
2+
#' Wilkinson, L. (1999). Dot plots. *The American Statistician*, 53(3), 276--281.
3+
#' \doi{10.1080/00031305.1999.10474474}
4+
#'

man/bin_dots.Rd

Lines changed: 45 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)