diff --git a/NEWS.md b/NEWS.md
index 5a33eeb4..16ebcbe2 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -6,6 +6,12 @@ where the formatting is also better._
## Development
+### New features
+
+- Improved horizontal legend spacing, as well as multicolumn legend support. A
+ new example in the "Tips & tricks" vignettes demonstrates the latter.
+ (#446 @grantmcdermott)
+
### Internals
- Move `altdoc` from `Suggests` to `Config/Needs/website`.
diff --git a/R/draw_legend.R b/R/draw_legend.R
index ce960e6b..84aeb5f1 100644
--- a/R/draw_legend.R
+++ b/R/draw_legend.R
@@ -214,6 +214,10 @@ draw_legend = function(
)
legend_args[["legend"]] = lgnd_labs
}
+
+ if (isTRUE(gradient)) {
+ legend_args[["ncol"]] = NULL
+ }
#
## legend placement ----
@@ -329,24 +333,39 @@ draw_legend = function(
}
}
- legend_args[["horiz"]] = TRUE
+ # enforce horizontal legend if user hasn't specified ncol arg
+ # (exception: gradient legends at bottom/top are always horizontal)
+ if (is.null(legend_args[["ncol"]]) || gradient) legend_args[["horiz"]] = TRUE
- # Catch for horizontal ribbon legend spacing
- if (type=="ribbon" && isTRUE(legend_args[["horiz"]])) {
+ } else {
+
+ legend_args[["inset"]] = 0
+ if (new_plot && draw) plot.new()
+
+ }
+
+ # Additional tweaks for horiz and/or multi-column legends
+ if (isTRUE(legend_args[["horiz"]]) || !is.null(legend_args[["ncol"]])) {
+ # tighter horizontal labelling
+ # See: https://github.com/grantmcdermott/tinyplot/issues/434
+ if (!gradient) {
+ legend_args[["text.width"]] = NA
+ # Add a space to all labs except the outer most right ones
+ nlabs = length(legend_args[["legend"]])
+ nidx = nlabs
+ if (!is.null(legend_args[["ncol"]])) nidx = tail(1:nlabs, (nlabs %/% legend_args[["ncol"]]))
+ legend_args[["legend"]][-nidx] = paste(legend_args[["legend"]][-nidx], " ")
+ }
+ # catch for horizontal ribbon legend spacing
+ if (type=="ribbon") {
if (legend_args[["pt.lwd"]] == 1) {
legend_args[["x.intersp"]] = 1
} else {
legend_args[["x.intersp"]] = 0.5
}
- } else if (gradient && isTRUE(legend_args[["horiz"]])) {
+ } else if (gradient) {
legend_args[["x.intersp"]] = 0.5
}
-
- } else {
-
- legend_args[["inset"]] = 0
- if (new_plot && draw) plot.new()
-
}
#
diff --git a/inst/tinytest/_tinysnapshot/density_legend_bottom.svg b/inst/tinytest/_tinysnapshot/density_legend_bottom.svg
index dacb7d94..c9088eb3 100644
--- a/inst/tinytest/_tinysnapshot/density_legend_bottom.svg
+++ b/inst/tinytest/_tinysnapshot/density_legend_bottom.svg
@@ -26,11 +26,11 @@
-
-
+
+
am
-0
-1
+0
+1
diff --git a/inst/tinytest/_tinysnapshot/legend_default_legend.svg b/inst/tinytest/_tinysnapshot/legend_default_legend.svg
index fed1ab42..82de1245 100644
--- a/inst/tinytest/_tinysnapshot/legend_default_legend.svg
+++ b/inst/tinytest/_tinysnapshot/legend_default_legend.svg
@@ -26,18 +26,18 @@
-
-
-
+
+
+
-
-
+
+
Month of the year
-5
-6
-7
-8
-9
+5
+6
+7
+8
+9
diff --git a/inst/tinytest/_tinysnapshot/legend_formula_legend.svg b/inst/tinytest/_tinysnapshot/legend_formula_legend.svg
index fed1ab42..82de1245 100644
--- a/inst/tinytest/_tinysnapshot/legend_formula_legend.svg
+++ b/inst/tinytest/_tinysnapshot/legend_formula_legend.svg
@@ -26,18 +26,18 @@
-
-
-
+
+
+
-
-
+
+
Month of the year
-5
-6
-7
-8
-9
+5
+6
+7
+8
+9
diff --git a/inst/tinytest/_tinysnapshot/legend_keyword_default.svg b/inst/tinytest/_tinysnapshot/legend_keyword_default.svg
index 7807b4fc..39bb588a 100644
--- a/inst/tinytest/_tinysnapshot/legend_keyword_default.svg
+++ b/inst/tinytest/_tinysnapshot/legend_keyword_default.svg
@@ -26,17 +26,17 @@
-
-
+
+
-
-
+
+
Month
-5
-6
-7
-8
-9
+5
+6
+7
+8
+9
diff --git a/inst/tinytest/_tinysnapshot/legend_keyword_formula.svg b/inst/tinytest/_tinysnapshot/legend_keyword_formula.svg
index 7807b4fc..39bb588a 100644
--- a/inst/tinytest/_tinysnapshot/legend_keyword_formula.svg
+++ b/inst/tinytest/_tinysnapshot/legend_keyword_formula.svg
@@ -26,17 +26,17 @@
-
-
+
+
-
-
+
+
Month
-5
-6
-7
-8
-9
+5
+6
+7
+8
+9
diff --git a/inst/tinytest/_tinysnapshot/legend_keyword_outerbottom.svg b/inst/tinytest/_tinysnapshot/legend_keyword_outerbottom.svg
index 7807b4fc..39bb588a 100644
--- a/inst/tinytest/_tinysnapshot/legend_keyword_outerbottom.svg
+++ b/inst/tinytest/_tinysnapshot/legend_keyword_outerbottom.svg
@@ -26,17 +26,17 @@
-
-
+
+
-
-
+
+
Month
-5
-6
-7
-8
-9
+5
+6
+7
+8
+9
diff --git a/inst/tinytest/_tinysnapshot/legend_keyword_outertop.svg b/inst/tinytest/_tinysnapshot/legend_keyword_outertop.svg
index de95ed57..4f514de5 100644
--- a/inst/tinytest/_tinysnapshot/legend_keyword_outertop.svg
+++ b/inst/tinytest/_tinysnapshot/legend_keyword_outertop.svg
@@ -26,17 +26,17 @@
-
-
+
+
-
-
+
+
Month
-5
-6
-7
-8
-9
+5
+6
+7
+8
+9
diff --git a/inst/tinytest/_tinysnapshot/legend_lmar_bottom.svg b/inst/tinytest/_tinysnapshot/legend_lmar_bottom.svg
index 092d655e..348e5bc7 100644
--- a/inst/tinytest/_tinysnapshot/legend_lmar_bottom.svg
+++ b/inst/tinytest/_tinysnapshot/legend_lmar_bottom.svg
@@ -26,18 +26,18 @@
-
-
-
+
+
+
-
-
+
+
Month
-5
-6
-7
-8
-9
+5
+6
+7
+8
+9
diff --git a/inst/tinytest/_tinysnapshot/legend_lmar_top.svg b/inst/tinytest/_tinysnapshot/legend_lmar_top.svg
index 4d7ce50e..2c1943a7 100644
--- a/inst/tinytest/_tinysnapshot/legend_lmar_top.svg
+++ b/inst/tinytest/_tinysnapshot/legend_lmar_top.svg
@@ -26,18 +26,18 @@
-
-
-
+
+
+
-
-
+
+
Month
-5
-6
-7
-8
-9
+5
+6
+7
+8
+9
diff --git a/inst/tinytest/_tinysnapshot/legend_multiline_bottom.svg b/inst/tinytest/_tinysnapshot/legend_multiline_bottom.svg
index 62c9f124..21efd3b5 100644
--- a/inst/tinytest/_tinysnapshot/legend_multiline_bottom.svg
+++ b/inst/tinytest/_tinysnapshot/legend_multiline_bottom.svg
@@ -26,21 +26,21 @@
-
-
-
+
+
+
-
-
+
+
Month
of
the
year
-5
-6
-7
-8
-9
+5
+6
+7
+8
+9
diff --git a/inst/tinytest/_tinysnapshot/legend_multiline_top.svg b/inst/tinytest/_tinysnapshot/legend_multiline_top.svg
index e3e1542f..dadfc476 100644
--- a/inst/tinytest/_tinysnapshot/legend_multiline_top.svg
+++ b/inst/tinytest/_tinysnapshot/legend_multiline_top.svg
@@ -26,21 +26,21 @@
-
-
-
+
+
+
-
-
+
+
Month
of
the
year
-5
-6
-7
-8
-9
+5
+6
+7
+8
+9
diff --git a/inst/tinytest/_tinysnapshot/legend_spacing_horiz_label_bottom.svg b/inst/tinytest/_tinysnapshot/legend_spacing_horiz_label_bottom.svg
new file mode 100644
index 00000000..14f28b3d
--- /dev/null
+++ b/inst/tinytest/_tinysnapshot/legend_spacing_horiz_label_bottom.svg
@@ -0,0 +1,119 @@
+
+
diff --git a/inst/tinytest/_tinysnapshot/legend_spacing_ncol_bottom.svg b/inst/tinytest/_tinysnapshot/legend_spacing_ncol_bottom.svg
new file mode 100644
index 00000000..8c2463a9
--- /dev/null
+++ b/inst/tinytest/_tinysnapshot/legend_spacing_ncol_bottom.svg
@@ -0,0 +1,224 @@
+
+
diff --git a/inst/tinytest/_tinysnapshot/legend_spacing_ncol_right.svg b/inst/tinytest/_tinysnapshot/legend_spacing_ncol_right.svg
new file mode 100644
index 00000000..c3d77239
--- /dev/null
+++ b/inst/tinytest/_tinysnapshot/legend_spacing_ncol_right.svg
@@ -0,0 +1,327 @@
+
+
diff --git a/inst/tinytest/_tinysnapshot/readme_legend_bottom.svg b/inst/tinytest/_tinysnapshot/readme_legend_bottom.svg
index 0d73f9c2..80898069 100644
--- a/inst/tinytest/_tinysnapshot/readme_legend_bottom.svg
+++ b/inst/tinytest/_tinysnapshot/readme_legend_bottom.svg
@@ -26,18 +26,18 @@
-
-
-
+
+
+
-
-
+
+
Month of the year
-5
-6
-7
-8
-9
+5
+6
+7
+8
+9
diff --git a/inst/tinytest/_tinysnapshot/restore_par_bottom.svg b/inst/tinytest/_tinysnapshot/restore_par_bottom.svg
index f56a9ab6..98b6a146 100644
--- a/inst/tinytest/_tinysnapshot/restore_par_bottom.svg
+++ b/inst/tinytest/_tinysnapshot/restore_par_bottom.svg
@@ -26,14 +26,14 @@
-
-
-
-
+
+
+
+
Species
-setosa
-versicolor
-virginica
+setosa
+versicolor
+virginica
diff --git a/inst/tinytest/_tinysnapshot/tinytheme_legend_bottom.svg b/inst/tinytest/_tinysnapshot/tinytheme_legend_bottom.svg
index bf753938..3e2ce5de 100644
--- a/inst/tinytest/_tinysnapshot/tinytheme_legend_bottom.svg
+++ b/inst/tinytest/_tinysnapshot/tinytheme_legend_bottom.svg
@@ -26,11 +26,11 @@
-
-
+
+
factor(am)
-0
-1
+0
+1
tinytheme("clean") + legend = "bottom!"
diff --git a/inst/tinytest/test-legend.R b/inst/tinytest/test-legend.R
index e2d15f44..cf72ece3 100644
--- a/inst/tinytest/test-legend.R
+++ b/inst/tinytest/test-legend.R
@@ -40,6 +40,28 @@ expect_snapshot_plot(f, label = "legend_keyword_outertopright")
f = function() tinyplot(Temp ~ Day | Month, data = aq, legend = "bottomleft!")
expect_snapshot_plot(f, label = "legend_keyword_outerbottomleft")
+# Horizontal and/or multicolumn legend spacing
+
+aq$Month2 = factor(month.name[aq$Month], levels = month.name[5:9])
+f = function() tinyplot(Temp ~ Day | Month2, data = aq, legend = "bottom!")
+expect_snapshot_plot(f, label = "legend_spacing_horiz_label_bottom")
+
+f = function() tinyplot(
+ weight ~ Time | Chick,
+ data = ChickWeight,
+ type = "ribbon", # not necessary for plot but helps to check some internal logic
+ legend = list("right!", ncol = 3)
+)
+expect_snapshot_plot(f, label = "legend_spacing_ncol_right")
+
+f = function() tinyplot(
+ weight ~ Time | Chick,
+ data = ChickWeight,
+ type = "l",
+ legend = list("bottom!", ncol = 5)
+)
+expect_snapshot_plot(f, label = "legend_spacing_ncol_bottom")
+
# Long legend titles
f = function() tinyplot(
diff --git a/vignettes/tips.qmd b/vignettes/tips.qmd
index 63400d52..4f8cc026 100644
--- a/vignettes/tips.qmd
+++ b/vignettes/tips.qmd
@@ -13,11 +13,11 @@ package. Please feel free to suggest or add more tips via our [GitHub repo](http
## Legend
-### Opacity (`alpha`)
+### Legend transparency
-By default, the legend inherits the opacity of the plotted elements. In some
-cases, this may be undesirable. For example, in the following plot, the legend
-is too light:
+By default, the legend inherits the transparency (`alpha`) of the plotted
+elements. In some cases, this may be undesirable. For example, in the following
+plot, the legend is too light:
```{r}
library(tinyplot)
@@ -38,6 +38,37 @@ tinyplot(y ~ x | z, data = dat, pch = 19, empty = TRUE)
tinyplot_add(empty = FALSE, alpha = .1)
```
+### Multicolumn legends
+
+For cases where we have many discrete groups, the default single column legend
+can overrun the plot limits. For example:
+
+```{r}
+library(tinyplot)
+
+plt(weight ~ Time | Chick, data = ChickWeight, type = "l")
+```
+
+To solve this undesirable behaviour, simply pass an approporiate `ncol`
+adjustment as part of your legend (list) argument:
+
+```{r}
+plt(weight ~ Time | Chick, data = ChickWeight, type = "l",
+ legend = list(ncol = 3))
+```
+
+The same trick works for horizontal legends and/or legends in other positions,
+as well for other plot types and themes. For example:
+
+```{r}
+plt(weight ~ Time | Chick, data = ChickWeight, type = "l",
+ legend = list("bottom!", ncol = 5))
+```
+
+(Admittedly, the end result is a bit compressed here because of the default
+aspect ratio that we use for figures on our website. But for regular interactive
+plots, or plots saved to disk, the aesthetic effect should be quite pleasing.)
+
## Labels
### Direct labels