Skip to content

Could gridtext implement xDetails / yDetails methods? #33

Description

@teunbrand

The larger context of this feature request is that I was playing around trying to implement slowkow/ggrepel#169, but ran into the following barrier.

The grobX() and grobY() functions can be used to find locations on the boundary of a grob.
These rely on the xDetails() and yDetails() methods of a grob to report metrics.

An example to find such points for a textGrob() is given below:

library(grid)
library(gridtext)

theta <- seq(0, 315, by = 15)

label <- "Lorem ipsum dolor sit amet, consectetur 
adipiscing elit. In hendrerit metus quam, 
sed rutrum nulla feugiat consequat. Sed eu 
pulvinar purus, vel placerat metus."

txt <- textGrob(label, rot = 45)

grid.newpage()
grid.draw(txt)

x <- grobX(txt, theta)
y <- grobY(txt, theta)

grid.points(x = x, y = y, gp = gpar(col = 'red'))

Attempting to do the same for richtext_grob(), we find that the results are all in the center of the plot.
This is the outcome of the default methods of xDetails() and yDetails().

# With <br> instead of newlines
label <- "Lorem ipsum dolor sit amet, consectetur<br> 
adipiscing elit. In hendrerit metus quam,<br> 
sed rutrum nulla feugiat consequat. Sed eu<br>
pulvinar purus, vel placerat metus."

richtxt <- richtext_grob(label, rot = 45)

grid.newpage()
grid.draw(richtxt)

x <- grobX(richtxt, theta)
y <- grobY(richtxt, theta)

grid.points(x = x, y = y, gp = gpar(col = 'red'))

We can compute the vertices of the enclosing box of a rich text grob quite easily, due to the xext and yext variables the grobs have. If we trick grid into treating these vertices as points and implement the xDetails() and yDetails() methods, we can get grobX() and grobY() methods to work.

as_points <- function(x) {
  grobs <- x$children
  
  if (isTRUE(x$debug)) {
    grobs <- grobs[c(-1, -length(grobs))]
  }
  
  x <- do.call(unit.c, lapply(grobs, function(x) x$x))
  y <- do.call(unit.c, lapply(grobs, function(x) x$y))
  
  xext <- lapply(grobs, function(x) x$xext)
  yext <- lapply(grobs, function(x) x$yext)
  
  x <- rep(x, lengths(xext))
  y <- rep(y, lengths(yext))
  
  structure(
    list(
      x = x + unit(unlist(xext), "pt"), 
      y = y + unit(unlist(yext), "pt")
    ), 
    class = "points"
  )
}

xDetails.richtext_grob <- function(x, theta) {
  xDetails(as_points(x), theta)
}

yDetails.richtext_grob <- function(x, theta) {
  yDetails(as_points(x), theta)
}

grid.newpage()
grid.draw(richtxt)

x <- grobX(richtxt, theta)
y <- grobY(richtxt, theta)

grid.points(x = x, y = y, gp = gpar(col = 'red'))

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

Please note that the image above is not identical to the textGrob(), which I think is due to the treatment of the lineheight parameter.

So the ask here is if it is possible to implement these methods in {gridtext}? I can prepare a PR with the approach above if we're all in agreement.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions