ggplot2 icon indicating copy to clipboard operation
ggplot2 copied to clipboard

Axes at interior panels #4064

Open teunbrand opened this issue 3 years ago • 8 comments

This PR aims to implement drawing axes at interior facets proposed in #4064. It implements this for facet_wrap() as well as for facet_grid(). While the changes for facet_wrap() are quite minimal, for facet_grid() some additional code was borrowed from facet_wrap() to draw additional axes.

A few discussion points:

  • With regards to the main challenge (the name of the argument), I went with draw.axes because it felt somewhat more intuitive than just axes to me. I'm not particularly attached to the name though.
  • I also think the unit tests might still be improved, they now require drawing four plots which doesn't make the tests particularly lightweight.
  • Lastly, https://github.com/tidyverse/ggplot2/issues/4064#issuecomment-646564182 mentions an option for omitting the labels at interior axes. I could add this, but wanted to ask if the maintainers approve of this first.

Two small demonstrations:

library(ggplot2)

df <- data.frame(
  x = 1, y = 1, facet = LETTERS[1:4]
)

ggplot(df, aes(x, y)) +
  geom_point() +
  facet_wrap(vars(facet), draw.axes = "all")

df <- data.frame(
  x = 1:4, y = 1:4, 
  fx = c("A", "A", "B", "B"),
  fy = c("c", "d", "c", "d")
)

ggplot(df, aes(x, y)) +
  geom_point() +
  facet_grid(vars(fy), vars(fx), draw.axes = "all")

Created on 2021-05-05 by the reprex package (v1.0.0)

teunbrand avatar May 05 '21 19:05 teunbrand

When I need this functionality I rely on: https://cran.r-project.org/web/packages/lemon/vignettes/facet-rep-labels.html It offers different ways to specify how axes are repeated

smouksassi avatar May 05 '21 19:05 smouksassi

I have also some version of this in an extension package of mine: https://teunbrand.github.io/ggh4x/articles/Facets.html#extended-facets-1 But I'll gladly make it unnecessary through incorporation in ggplot2.

teunbrand avatar May 05 '21 19:05 teunbrand

Agree having this in ggplot2 will make it more mainstream I use ggh4x a lot especially the facet_nested ! Thank you !

smouksassi avatar May 05 '21 19:05 smouksassi

@clauswilke any thoughts on this - I'm in two minds

thomasp85 avatar Nov 09 '21 08:11 thomasp85

I like this feature. My workaround, which I use all the time, is to set scales to free and then impose scale limits. This has the same effect visually but is a bit confusing, as it requires setting two contradictory options. I'd also say that being able to omit labels for interior plots would be useful.

I haven't reviewed the code, though.

clauswilke avatar Nov 09 '21 14:11 clauswilke

The current interface seems like a reasonable compromise to me; it meaningfully improves the display options available, without getting bogged down into too many details. However, I do think axes fits better with the current argument names than draw.axes.

@teunbrand do you mind changing to axes and then merging/rebasing against main?

@clauswilke do you want to review this? or should I?

hadley avatar Mar 23 '22 14:03 hadley

Thanks Hadley! I'll take a look in a few days time once I've got some to spare myself.

teunbrand avatar Mar 23 '22 15:03 teunbrand

option for omitting the labels at interior axes

Revisiting this topic now a while later, I don't think there currently exists a clean path towards omitting labels. We can edit the axis grob in the draw_panels() method, but it feels a bit messy. While I think it is useful to be able to omit labels, the messy approach might be more difficult to maintain in the future, and the faceting code is already quite complicated.

What I think is a neater solution would be to create an argument in the render_axes() function that propagates all the way down to ggplot2:::guide_gengrob.axis(), that controls the drawing of the label parts. I could give this a go, but it might be a good idea to postpone this and consider implementing #3329 first.

teunbrand avatar Mar 24 '22 19:03 teunbrand

So this PR has been barren for a while, but with ggproto guides, we can now also implement the censoring of labels. I've called the argument that controls this axis_labels but perhaps there is a more intuitive name. Quick demo:

devtools::load_all("~/packages/ggplot2/")
#> ℹ Loading ggplot2

base <- ggplot(mtcars, aes(mpg, disp)) +
  geom_point()

base + facet_grid(vars(cyl), vars(vs), axes = "all", axis_labels = "margins")

base + facet_wrap(vars(cyl, vs), axes = "all", axis_labels = "margins")

Created on 2023-05-21 with reprex v2.0.2

As you can see for the facet_wrap() case, it is mildly annoying that the labels in the x-axis of the rightmost panel still contributes height for spacing panels. I could try to make this slightly better, but I don't know if that is needed.

teunbrand avatar May 21 '23 14:05 teunbrand

Is the additional height only seen if all axes are shown (i.e. turning off interior axes will revert completely to the old look/spacing)?

thomasp85 avatar May 22 '23 08:05 thomasp85

Yeah it looks like it should if no interior axes are drawn or the interior axes are drawn with labels. The issue is that the space reserved for axes is calculated as the maximum size for all the axes in a particular row or column. We could try to calculate the size of only the relevant axes that are in between panels, but this would get pretty gnarly and the drawing code already isn't the easiest to work with.

teunbrand avatar May 22 '23 17:05 teunbrand

Now with much better spacing for hidden labels:

devtools::load_all("~/packages/ggplot2/")
#> ℹ Loading ggplot2

ggplot(mtcars, aes(mpg, disp)) +
  geom_point() +
  guides(x.sec = "axis", y.sec = "axis") +
  facet_wrap(vars(cyl, vs), axes = "all", axis_labels = "margins")

Created on 2023-10-31 with reprex v2.0.2

teunbrand avatar Oct 31 '23 10:10 teunbrand

A small update on how this PR now interacts with newer features.

When using stacked axes, only the first axis in the stack is rendered at interior panels without labels if so directed.

devtools::load_all("~/packages/ggplot2/")
#> ℹ Loading ggplot2

p <- ggplot(mtcars, aes(mpg, disp)) +
  geom_point() +
  facet_wrap(vars(cyl, vs), axes = "all", axis_labels = "margins")

p + guides(x = guide_axis_stack("axis", "axis"),
           y = guide_axis_stack("axis", "axis"))

An exterior r-axis in coord_radial() is treated as typical axes, i.e. labels are censored.

p + coord_radial()

Whereas interior r-axes are rendered in full.

p + coord_radial(end = 1.5 * pi)

Created on 2023-12-12 with reprex v2.0.2

teunbrand avatar Dec 12 '23 09:12 teunbrand

Thanks for the review Thomas!

teunbrand avatar Dec 14 '23 21:12 teunbrand