Skip to content

Convert guide system to ggproto #3329

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

Closed
clauswilke opened this issue May 15, 2019 · 8 comments
Closed

Convert guide system to ggproto #3329

clauswilke opened this issue May 15, 2019 · 8 comments
Labels
feature a feature request or enhancement guides 📏 internals 🔎

Comments

@clauswilke
Copy link
Member

The guide system is the last remaining major ggplot2 component that has not been converted to ggproto. As a consequence, it doesn't match well with the rest of the ggplot2 code base, and it is difficult to extend by external package maintainers. We should consider converting it over, possibly before building additional components that would use the current system, as in #3322.

@paleolimbot
Copy link
Member

Yes! I was surprised that guides were S3 when I opened up that code. I think it will be less work to do this after #3322, which is to say, after all the guides use the same system. I'd be happy to take this on as well, but would prefer to do #3322 first.

@teunbrand
Copy link
Collaborator

teunbrand commented Jun 9, 2022

The ggplot2 book softly discourages extending ggplot's guide system. However, I'm pretty keen on making some guide extensions, so I'd be willing to spend some time experimenting. What are the current thoughts on moving the guide system to ggproto? I've heard on the grapevines that the entire secondary axis mechanism might move to guides, for example. Other than simply wrapping guide code into ggproto methods and making appropriate classes, what would need to change?

@thomasp85
Copy link
Member

@teunbrand I can expand a bit on why I've yet to build up the stamina to do this. If it were a simple rewrite of the current guide methods into a ggproto system it would be quite quick and we would have done it years ago. However, I want this to be an opportunity to rethink the guide implementation in a better way. Currently there is so much code duplication between the different legend guides in terms of how to lay out the grobs, calculate ticks etc. One could even go so far as to say that the keys in a colorbar guide is actually an instance of an axis guide and should use that implementation - but can that be generalised to e.g. the legend guide? There are quite a number of questions about how to do this properly in a manner that doesn't constraint future guide development but allows maximum code reuse.

This is not to deter you from taking on the task. I would be happy if you had the stamina for it, and I would love to set off some time to discuss and guide you through it so you end up with a PR that we agree with the direction on.

@teunbrand
Copy link
Collaborator

Thanks for your thoughts Thomas, they make a lot of sense to me. From an extension POV, it would be useful too if (in particular the drawing) code was more modular in some ways, which would align with the perspective of reducing code duplication if these modules can be re-used in different guides. For example, if you'd like to make ticks for the minor breaks, you wouldn't want to touch the axis lines or labels parts. I'll have a walk through the code at some point and take stock of what is the same/very similar or very different between various guides.

@thomasp85
Copy link
Member

yes, exactly. If we are going to do it we are going to do it with thought and intent. If you want to take it on I will be happy to have one or more design meetings to discuss your ideas and give you feedback

@teunbrand
Copy link
Collaborator

I'd be willing to take a stab at this, but it may take quite some time before we find appropriate solutions. Worst case scenario is we can't figure out an elegant solution, and we're back where we started with some lessons learned along the way. I'd definitely be happy to get some guidance along the way, I don't consider this a small task. Below, I've made some notes going through the guide code trying to understand what some guides share and what is specific to a guide. I think that the shared bits might be modularised in some way.

Notes

General ideas

  • (Thomas) tick drawing code could be universal
  • Bidirectional element_grob() might simplify code
  • guide_colourbar()/guide_legend()/guide_bins() might share the same
    layout code, if we see the colourbar or bins as a 1-key legend.
  • Some code might be cleaner if arguments like frame.colour, frame.linewidth
    etc., are captured as elements in the constructor. Likewise for ticks.*,
    axis.*, label.* and title.*. Can later be combined with parent theme
    elements using ggplot2:::combine_elements() (might be worth exporting).

General notes

  • guide_grid() doesn't seem to follow conventions of other guides. I think it might be worth exposing, but might get hairy implementation-wise.
  • guide_none() generally does nothing and returns zeroGrob() in the end.
  • guide_coloursteps is, with the exception of training, identical to
    guide_colourbar().

Training tasks

Common tasks

  • Extract key from scale
    • non-axis guides: reverse key
  • Set guide name
  • Set hash

Specific tasks

  • guide_bins()/guide_coloursteps():
    • Format breaks/labels from (n, n] notation
  • guide_colourbar()/guide_coloursteps():
    • Create key for bar

Merging tasks

Common tasks

  • Discard new_guide

Specific tasks

  • guide_legend()/guide_bins()
    • Merge keys
    • Merge override.aes
  • guide_none()
    • Return new_guide instead

Transform tasks

Common tasks

  • Skip doing anything

Specific tasks

  • guide_axis()
    • Do coord transformation

guide_geom() tasks

Common tasks

  • Skip doing anything

Specific tasks

  • guide_legend()/guide_bins()
    • Walk through all layers
    • Decide which layers to include
    • Stage components for drawing keys

Grob generation, i.e. drawing tasks

Common tasks

  • Set parameters
  • Extract relevant elements from theme
  • Override specific theme settings depending on direction/position
  • Build title
  • Build labels
  • Build tickmarks

The follow tasks are common, but implementation is specific

  • Measure components
  • Setup a layout for components
  • Assemble components

Specific tasks

  • guide_axis()
    • Title handled by facet instead. Might be useful to provide framework for titles anyway and have title build code just return zeroGrob().
    • Build axis line.
    • Overrule axis.label.{x/y}.{position} angle
    • Dodge labels
  • guide_legend()
    • No tickmarks needed
    • Legend text are individual grobs
    • Overrule keywidth/keyheight
  • guide_colourbar()
    • Build colour bar
    • Overrule barwidth/barheight
  • guide_legend()/guide_bins()
    • Build keys
  • guide_legend()/guide_colourbar()/guide_bins()
    • Overrule title's hjust/vjust
    • Overrule legend text's hjust/vjust

@teunbrand
Copy link
Collaborator

I do want to spend some time trying to get this to work. How about I just make a Guide parent + implementation of 1 guide (axis probably being the simplest), put it in a WIP PR and we could course-correct from there?

@thomasp85
Copy link
Member

That may be a good idea to get the ball rolling

teunbrand added a commit that referenced this issue Apr 24, 2023
The guide system, as the last remaining chunk of ggplot2, has been rewritten 
in ggproto. The axes and legends now inherit from a <Guide> class, which makes
them extensible in the same manner as geoms, stats, facets and coords 
(#3329, @teunbrand). In addition, the following changes were made:
    * Styling theme parts of the guide now inherit from the plot's theme 
      (#2728). 
    * Styling non-theme parts of the guides accept <element> objects, so that
      the following is possible: `guide_colourbar(frame = element_rect(...))`.
    * Primary axis titles are now placed at the primary guide, so that
      `guides(x = guide_axis(position = "top"))` will display the title at the
      top by default (#4650).
    * Unknown secondary axis guide positions are now inferred as the opposite 
      of the primary axis guide when the latter has a known `position` (#4650).
    * `guide_colourbar()`, `guide_coloursteps()` and `guide_bins()` gain a
      `ticks.length` argument.
    * In `guide_bins()`, the title no longer arbitrarily becomes offset from
      the guide when it has long labels.
    * The `order` argument of guides now strictly needs to be a length-1 
      integer (#4958).
    * More informative error for mismatched 
     `direction`/`theme(legend.direction = ...)` arguments (#4364, #4930).
    * `guide_coloursteps()` and `guide_bins()` sort breaks (#5152).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature a feature request or enhancement guides 📏 internals 🔎
Projects
None yet
Development

No branches or pull requests

5 participants