From 0bfaac698cd341a6c223efe225a4c7e3b596c007 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 19 Jul 2025 13:49:45 -0700 Subject: [PATCH 1/9] text.width = NA --- R/draw_legend.R | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/R/draw_legend.R b/R/draw_legend.R index ce960e6b..5aabce45 100644 --- a/R/draw_legend.R +++ b/R/draw_legend.R @@ -331,6 +331,14 @@ draw_legend = function( legend_args[["horiz"]] = TRUE + # tighter labeling + # See: https://github.com/grantmcdermott/tinyplot/issues/434 + if (!gradient) { + legend_args[["text.width"]] = NA + nlabs = length(legend_args[["legend"]]) + legend_args[["legend"]][-nlabs] = paste(legend_args[["legend"]][-nlabs], " ") + } + # Catch for horizontal ribbon legend spacing if (type=="ribbon" && isTRUE(legend_args[["horiz"]])) { if (legend_args[["pt.lwd"]] == 1) { From 5863ca22eddae422a49cf189b9c799e0bfb963d5 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 19 Jul 2025 14:23:06 -0700 Subject: [PATCH 2/9] catch for user-specified ncol --- R/draw_legend.R | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/R/draw_legend.R b/R/draw_legend.R index 5aabce45..3f627aa8 100644 --- a/R/draw_legend.R +++ b/R/draw_legend.R @@ -329,7 +329,9 @@ 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 # tighter labeling # See: https://github.com/grantmcdermott/tinyplot/issues/434 @@ -340,7 +342,8 @@ draw_legend = function( } # Catch for horizontal ribbon legend spacing - if (type=="ribbon" && isTRUE(legend_args[["horiz"]])) { + # if (type=="ribbon" && isTRUE(legend_args[["horiz"]])) { + if (type=="ribbon") { if (legend_args[["pt.lwd"]] == 1) { legend_args[["x.intersp"]] = 1 } else { From 31b6673a8390edf16eda17d61b275b2ff639d8a0 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 19 Jul 2025 14:44:15 -0700 Subject: [PATCH 3/9] move horiz spacing logic to catch right!/left! ncol > 1 cases too --- R/draw_legend.R | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/R/draw_legend.R b/R/draw_legend.R index 3f627aa8..5435e88d 100644 --- a/R/draw_legend.R +++ b/R/draw_legend.R @@ -333,31 +333,32 @@ draw_legend = function( # (exception: gradient legends at bottom/top are always horizontal) if (is.null(legend_args[["ncol"]]) || gradient) legend_args[["horiz"]] = TRUE - # tighter labeling + } 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 nlabs = length(legend_args[["legend"]]) legend_args[["legend"]][-nlabs] = paste(legend_args[["legend"]][-nlabs], " ") } - - # Catch for horizontal ribbon legend spacing - # if (type=="ribbon" && isTRUE(legend_args[["horiz"]])) { + # 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() - } # From fc89596ef0653db6e77fec17e67504bb67445f9b Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 19 Jul 2025 14:48:42 -0700 Subject: [PATCH 4/9] update tests --- .../_tinysnapshot/density_legend_bottom.svg | 8 ++++---- .../_tinysnapshot/legend_default_legend.svg | 20 +++++++++---------- .../_tinysnapshot/legend_formula_legend.svg | 20 +++++++++---------- .../_tinysnapshot/legend_keyword_default.svg | 18 ++++++++--------- .../_tinysnapshot/legend_keyword_formula.svg | 18 ++++++++--------- .../legend_keyword_outerbottom.svg | 18 ++++++++--------- .../_tinysnapshot/legend_keyword_outertop.svg | 18 ++++++++--------- .../_tinysnapshot/legend_lmar_bottom.svg | 20 +++++++++---------- .../_tinysnapshot/legend_lmar_top.svg | 20 +++++++++---------- .../_tinysnapshot/legend_multiline_bottom.svg | 20 +++++++++---------- .../_tinysnapshot/legend_multiline_top.svg | 20 +++++++++---------- .../_tinysnapshot/readme_legend_bottom.svg | 20 +++++++++---------- .../_tinysnapshot/restore_par_bottom.svg | 14 ++++++------- .../_tinysnapshot/tinytheme_legend_bottom.svg | 8 ++++---- 14 files changed, 121 insertions(+), 121 deletions(-) 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/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!" From 6586cb5614c049bba4aa7237555caefc743fa922 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 19 Jul 2025 16:10:31 -0700 Subject: [PATCH 5/9] better catch for spacing of ncol outer column --- R/draw_legend.R | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/R/draw_legend.R b/R/draw_legend.R index 5435e88d..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 ---- @@ -346,8 +350,11 @@ draw_legend = function( # 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"]]) - legend_args[["legend"]][-nlabs] = paste(legend_args[["legend"]][-nlabs], " ") + 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") { From fb5c2de3d376772857646bafc27541103a1cdba9 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 19 Jul 2025 16:10:38 -0700 Subject: [PATCH 6/9] extra tests --- .../legend_spacing_horiz_label_bottom.svg | 119 +++++++ .../legend_spacing_ncol_bottom.svg | 224 ++++++++++++ .../legend_spacing_ncol_right.svg | 327 ++++++++++++++++++ inst/tinytest/test-legend.R | 22 ++ 4 files changed, 692 insertions(+) create mode 100644 inst/tinytest/_tinysnapshot/legend_spacing_horiz_label_bottom.svg create mode 100644 inst/tinytest/_tinysnapshot/legend_spacing_ncol_bottom.svg create mode 100644 inst/tinytest/_tinysnapshot/legend_spacing_ncol_right.svg 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 @@ + + + + + + + + + + + + + + + + + + +Month2 +May +June +July +August +September + + + + + + + +Day +Temp + + + + + + + + + + +0 +5 +10 +15 +20 +25 +30 + + + + + + + +65 +70 +75 +80 +85 +90 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Chick +18 +16 +15 +13 +9 +20 +10 +8 +17 +19 +4 +6 +11 +3 +1 +12 +2 +5 +14 +7 +24 +30 +22 +23 +27 +28 +26 +25 +29 +21 +33 +37 +36 +31 +39 +38 +32 +40 +34 +35 +44 +45 +43 +41 +47 +49 +46 +50 +42 +48 + + + + + + + +Time +weight + + + + + + + + +0 +5 +10 +15 +20 + + + + + + + + +50 +100 +200 +300 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Chick +18 +16 +15 +13 +9 +20 +10 +8 +17 +19 +4 +6 +11 +3 +1 +12 +2 +5 +14 +7 +24 +30 +22 +23 +27 +28 +26 +25 +29 +21 +33 +37 +36 +31 +39 +38 +32 +40 +34 +35 +44 +45 +43 +41 +47 +49 +46 +50 +42 +48 + + + + + + + +Time +weight + + + + + + + + +0 +5 +10 +15 +20 + + + + + + + + +50 +100 +150 +200 +250 +300 +350 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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( From 336c9623b465355d1aa711b377b9074d02ed2e35 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 19 Jul 2025 17:20:00 -0700 Subject: [PATCH 7/9] example for tips vignette --- vignettes/tips.qmd | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/vignettes/tips.qmd b/vignettes/tips.qmd index 63400d52..d477e92a 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) ``` +### Mulitcolumn 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 From 23dc42718ed523b5e908f1f12cef057ff91fd59e Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 19 Jul 2025 17:52:31 -0700 Subject: [PATCH 8/9] typo --- vignettes/tips.qmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/tips.qmd b/vignettes/tips.qmd index d477e92a..4f8cc026 100644 --- a/vignettes/tips.qmd +++ b/vignettes/tips.qmd @@ -38,7 +38,7 @@ tinyplot(y ~ x | z, data = dat, pch = 19, empty = TRUE) tinyplot_add(empty = FALSE, alpha = .1) ``` -### Mulitcolumn legends +### Multicolumn legends For cases where we have many discrete groups, the default single column legend can overrun the plot limits. For example: From bfffaa954a7ca96955914f97483332caa1af18cc Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Sat, 19 Jul 2025 17:55:29 -0700 Subject: [PATCH 9/9] news --- NEWS.md | 6 ++++++ 1 file changed, 6 insertions(+) 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`.