Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Legend and facet spacing #94

Merged
merged 38 commits into from
Jan 21, 2024
Merged

Legend and facet spacing #94

merged 38 commits into from
Jan 21, 2024

Conversation

grantmcdermott
Copy link
Owner

@grantmcdermott grantmcdermott commented Jan 20, 2024

This is a major PR—albeit with few changes on the UI side—that provides a principled approach for fixing the margin spacing for both legends and facets. Specifically:

  • For legends that are placed outside the plotting region (e.g., "right!", "bottom!", etc.), margins are now controlled by the lmar = c(inner, outer) parameter, with default values of c(1.0, 0.1). The first number represents the "inner" margin in lines between the legend and the plot region. The second number represents the "outer" margin in lines between the legend and the edge of the graphics device. (For "top!" legends, the outer margin represents the extra gap between the legend and the title, since the legend sits underneath the title.)
  • Similarly, facet margins are now controlled by the fmar = c(b,l,t,r) parameter, where the default values are c(1,1,1,1). In other words, each individual facet has a single line's worth of space around each of its sides. For faceted plots where "frame.plot = FALSE", these gaps are slightly reduced (by 0.5 lines) to reduce the excess whitespace that arises from the missing axes lines and labels.

Both lmar and fmar can be can accessed and controlled as part of the par2() global parameters.

Final thoughts before some examples: This PR took me significantly longer than expected and I ended up squashing quite a few additional hidden bugs during the process. Less pleasantly, I had to grapple with some idiosyncrasies of the base graphics internals that I had so far managed to avoid (example). But having come through the other end, I'm pretty chuffed with the end result. It's big improvement over the existing system, which was very ad hoc even if the "usual" cases looked okay. (Non-standard cases would not look okay.)

Legend margin (lmar)

library(plot2)
palette("classic")

aq = airquality
aq$Month = factor(month.abb[aq$Month], levels = month.abb[5:9])


plot2(Temp ~ Day | Month, data = aq, legend = list("right!", bty = "o"))
box("figure", col = "blue", lty = 2)

plot2(Temp ~ Day | Month, data = aq, legend = list("left!", bty = "o"))
box("figure", col = "blue", lty = 2)

plot2(Temp ~ Day | Month, data = aq, legend = list("bottom!", bty = "o"))
box("figure", col = "blue", lty = 2)

plot2(Temp ~ Day | Month, data = aq, legend = list("right!", bty = "o"))
box("figure", col = "blue", lty = 2)

Long (and multi-line) legend titles are fine too...

plot2(
  Temp ~ Day | Month, data = aq, main = "Long title",
  legend = list("right!", title = "What month of the year is it?", bty = "o")
)
box("figure", col = "blue", lty = 2)

plot2(
  Temp ~ Day | Month, data = aq, main = "Multiline title",
  legend = list("bottom!", title = "Month\nof\nthe\nyear", bty = "o")
)
box("figure", col = "blue", lty = 2)

And here's an adjusted lmar example.

par2(lmar = c(2, 1))
plot2(Temp ~ Day | Month, data = aq, legend = list("right!", bty = "o"))
box("figure", col = "blue", lty = 2)

par2(lmar = c(1.0, 0.1))  ## reset

Facet margins (fmar)

with(
  aq,
  plot2(
    x = Day, y = Temp,
    facet = Month,
    grid = TRUE
  )
)

Note that non framed plots get an automatic fmar reduction (-0.5 lines) to reduce excess whitespace arising from the missing axes lines and labels.

with(
  aq,
  plot2(
    x = Day, y = Temp,
    facet = Month,
    grid = TRUE, frame = FALSE
  )
)

And here is an adjusted fmar example or two.

par2(fmar = c(1,1,0.5,2))
with(
  aq,
  plot2(
    x = Day, y = Temp,
    facet = Month,
    grid = TRUE
  )
)

Side note: We can also set fmar temporarily via facet.args.

par2(fmar = rep(1, 4)) # reset
with(
  aq,
  plot2(
    x = Day, y = Temp,
    facet = Month,
    facet.args = list(fmar = c(2,2,2,2)),
    grid = TRUE
  )
)

Created on 2024-01-20 with reprex v2.0.2

@grantmcdermott
Copy link
Owner Author

grantmcdermott commented Jan 20, 2024

@vincentarelbundock @zeileis I plan to merge this PR later today. I've stressed tested it a ton and I'm confident that I've caught all of the major edge cases.

If either of you have time, however, I'd be grateful for feedback on my default choices of lmar = c(1.0, 0.1) and fmar = c(1,1,1,1) . Please say if you don't think that these defaults lead to nice spacing around the legend and facets, respectively.

@vincentarelbundock
Copy link
Collaborator

Oooh, this looks suuper nice! I don't have anything to say about the spacing. Looks about right to me. I really like this a lot. Feels like with plot2 and tinytable, we've got a nice combo ;)

@grantmcdermott
Copy link
Owner Author

Feels like with plot2 and tinytable, we've got a nice combo ;)

💯

@grantmcdermott grantmcdermott merged commit 2dcae14 into main Jan 21, 2024
3 checks passed
@grantmcdermott grantmcdermott deleted the legend_spacing branch January 21, 2024 00:39
@grantmcdermott grantmcdermott mentioned this pull request Jan 21, 2024
11 tasks
@zeileis
Copy link
Collaborator

zeileis commented Jan 21, 2024

Indeed. Thank you both!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants