wilkelab / gridtext

Improved text rendering support for grid graphics in R
https://wilkelab.org/gridtext
Other
96 stars 17 forks source link

Incorrect height for the richtext_grob object #6

Closed jokergoo closed 4 years ago

jokergoo commented 4 years ago

Hi, thanks for developing this package for better configuring text under grid system! I am developing a package called ComplexHeatmap and I want to support gridtext with it. I found when rot is set to e.g. 30 in richtext_grob() function, the height calculated by grobHeight() is not identical to the height (or the width if the rotation is 90 degrees) of the longest text.

I attached an example here. In the following plot, the height of the rectangles should be the same as the height of September, regardless of the rotation of the text. As a comparison, I also attached plots if using textGrob() function and you can see the height of rectangles is exactly the same as September.

I haven't tested grobWidth() on richtext_grob object, but I guess it should be similar.

Thanks!

grid.newpage()
rot = seq(0, 360, by = 30); rot = rot[rot != 360]
pushViewport(viewport(layout = grid.layout(nrow = 4, ncol = 3)))
for(i in seq_along(rot)) {
    gb = richtext_grob(month.name, x = 1:12, default.units = "native", box_gp = gpar(col = "red"), rot = rot[i])
    # gb = textGrob(month.name, x = 1:12, default.units = "native", rot = rot[i])
    pushViewport(viewport(layout.pos.col = (i-1) %% 3 + 1, layout.pos.row = ceiling(i/3)))
    pushViewport(viewport(xscale = c(0, 13), height = grobHeight(gb), width = unit(0.8, "npc")))
    grid.rect()
    grid.text(paste0("rot = ", rot[i]), x = unit(0.5, "npc"), y = unit(1, "npc"), just = c(0.5, 0))
    grid.draw(gb)
    popViewport()
    popViewport()
}
popViewport()

image

image

clauswilke commented 4 years ago

Not sure what exactly is going on in your code. I'll have to ask you to try to figure out the specific problem in more detail. The moment you start pushing viewports inside viewports and then try to calculate the width or height of a grob, things can go wrong, in particular when you're rotating grobs and using different types of units along x and y. (For a possibly somewhat related problem, see here: https://github.com/r-lib/gtable/issues/83)

In my testing code, the width and height are calculated correctly.

library(grid)
library(gridtext)

f <- function(rot) {
  gb = richtext_grob(
    month.name, x = ((3:14)+.5)/18,
    box_gp = gpar(col = "red"),
    rot = rot
  )

  grid.newpage()
  grid.draw(
    rectGrob(height = grobHeight(gb), width = grobWidth(gb))
  )
  grid.draw(gb)
}

f(30)

f(45)

f(90)

f(-45)

Created on 2020-02-04 by the reprex package (v0.3.0)

jokergoo commented 4 years ago

The reason is I want to create a viewport with the same height as the richtext_grob. Let's say, when I implement the heatmap package, I want to create a viewport to put the column names in, thus, I need the height of this viewport to have the same height of the column names, also when they are rotated.

Since the texts have absolute width and height, does it matter to calculate the size inside or outside a certain viewport?

A strange thing is when I run your example code in a completely new R session, there is still empty space above and below the text, no matter I directly plot it in an interactive window or save into a PDF file.

f(45)

image

But, it seems it only happens on my laptop (Macbook). If I run the same code in other PC, it is fine:

image

jokergoo commented 4 years ago

Also, I think the height of the grob is calculated from yext from gb$childrens. This value is different from machines with the same grob.

E.g. for following grob where text is rotated with 45 degrees:

gb = richtext_grob(
    month.name, x = ((3:14)+.5)/18,
    box_gp = gpar(col = "red"),
    rot = 45
)

and I check yext for the first children of gb (I think it is for January):

gb$children[[1]]$yext

On my laptop:

[1] 10.89174 41.19233 19.40884 49.70943

On another PC:

[1] -11.15314  19.14218 -19.14218  11.15314

On Rstudio server:

[1] -19.42964  10.91254 -10.91254  19.42964
clauswilke commented 4 years ago

I'm not sure why you get this wide range of different numbers for yext, but in any case, only the difference between the largest and the smallest numbers matters, and the difference is approximately the same in your three examples, at least within the expected accuracy of font metrics reported by different graphics devices.

library(gridtext)
library(grid)

diff(range(c(10.89174, 41.19233, 19.40884, 49.70943)))
#> [1] 38.81769
diff(range(c(-11.15314, 19.14218, -19.14218, 11.15314)))
#> [1] 38.28436
diff(range(c(-19.42964, 10.91254, -10.91254, 19.42964)))
#> [1] 38.85928

png()
gb <- richtext_grob(
  month.name, x = ((3:14)+.5)/18,
  box_gp = gpar(col = "red"),
  rot = 45
)

gb$children[[1]]$yext
#> [1] -19.09486  11.20573 -11.20573  19.09486
diff(range(gb$children[[1]]$yext))
#> [1] 38.18972
dev.off()
#> quartz_off_screen 
#>                 2

pdf()
gb <- richtext_grob(
  month.name, x = ((3:14)+.5)/18,
  box_gp = gpar(col = "red"),
  rot = 45
)

gb$children[[1]]$yext
#> [1] -19.14218  11.15314 -11.15314  19.14218
diff(range(gb$children[[1]]$yext))
#> [1] 38.28437
dev.off()
#> quartz_off_screen 
#>                 2

Created on 2020-02-06 by the reprex package (v0.3.0)

A couple of additional comments:

  1. In general, grob width and height are only properly defined when a grob is placed into its final viewport. This creates an identifiability problem when you want to create a viewport the size of a grob. I would recommend against this approach, even if it seems that it should be possible.

  2. I'm not discounting the possibility that there is something wrong with my code, but I'll need a very clear and minimal reprex that shows what exactly the problem is. As far as I can tell, grob heights are calculated correctly when gridtext is used inside ggplot, on different platforms.

  3. Please use the reprex package to create examples, to make sure we know exactly what the state of the graphics device is when specific calls are made, and in what order things are executed.

Thanks!

jokergoo commented 4 years ago

Thanks for your reply. Now I tried to set align_widths argument to TRUE and FALSE separately and I convert the height to mm unit for easy reading. In theory, the three numbers in the following code should be all the same, but still on my Macbook, setting align_widths or not gives different height for the text.

On other machines, the three numbers are identical.

library(gridtext)
library(grid)
gb = richtext_grob(month.name, rot = 90, align_widths = FALSE)
convertHeight(grobHeight(gb), "mm")
#> [1] 27.29755859375mm
gb = richtext_grob(month.name, rot = 90, align_widths = TRUE)
convertHeight(grobHeight(gb), "mm")
#> [1] 20.7077799479167mm

# if only September
gb = richtext_grob(month.name[9], rot = 90, align_widths = FALSE)
convertHeight(grobHeight(gb), "mm")
#> [1] 20.7077799479167mm

Created on 2020-02-06 by the reprex package (v0.3.0)

clauswilke commented 4 years ago

This is what I see (on OS X). Could you run the reprex with si = TRUE, so we can compare the session info?

library(gridtext)
library(grid)
gb = richtext_grob(month.name, rot = 90, align_widths = FALSE)
convertHeight(grobHeight(gb), "mm")
#> [1] 20.7077799479167mm

gb = richtext_grob(month.name, rot = 90, align_widths = TRUE)
convertHeight(grobHeight(gb), "mm")
#> [1] 20.7077799479167mm

# if only September
gb = richtext_grob(month.name[9], rot = 90, align_widths = FALSE)
convertHeight(grobHeight(gb), "mm")
#> [1] 20.7077799479167mm

Created on 2020-02-06 by the reprex package (v0.3.0)

Session info ``` r devtools::session_info() #> ─ Session info ─────────────────────────────────────────────────────────────── #> setting value #> version R version 3.6.0 (2019-04-26) #> os macOS Mojave 10.14.6 #> system x86_64, darwin15.6.0 #> ui X11 #> language (EN) #> collate en_US.UTF-8 #> ctype en_US.UTF-8 #> tz America/Chicago #> date 2020-02-06 #> #> ─ Packages ─────────────────────────────────────────────────────────────────── #> package * version date lib source #> assertthat 0.2.1 2019-03-21 [1] CRAN (R 3.6.0) #> backports 1.1.5 2019-10-02 [1] CRAN (R 3.6.0) #> callr 3.4.1 2020-01-24 [1] CRAN (R 3.6.0) #> cli 2.0.1 2020-01-08 [1] CRAN (R 3.6.0) #> crayon 1.3.4 2017-09-16 [1] CRAN (R 3.6.0) #> desc 1.2.0 2018-05-01 [1] CRAN (R 3.6.0) #> devtools 2.2.1 2019-09-24 [1] CRAN (R 3.6.0) #> digest 0.6.23 2019-11-23 [1] CRAN (R 3.6.0) #> ellipsis 0.3.0 2019-09-20 [1] CRAN (R 3.6.0) #> evaluate 0.14 2019-05-28 [1] CRAN (R 3.6.0) #> fansi 0.4.1 2020-01-08 [1] CRAN (R 3.6.0) #> fs 1.3.1 2019-05-06 [1] CRAN (R 3.6.0) #> glue 1.3.1 2019-03-12 [1] CRAN (R 3.6.0) #> gridtext * 0.1.0.9000 2020-02-03 [1] local #> highr 0.8 2019-03-20 [1] CRAN (R 3.6.0) #> htmltools 0.3.6 2017-04-28 [1] CRAN (R 3.6.0) #> knitr 1.26 2019-11-12 [1] CRAN (R 3.6.0) #> magrittr 1.5 2014-11-22 [1] CRAN (R 3.6.0) #> markdown 1.1 2019-08-07 [1] CRAN (R 3.6.0) #> memoise 1.1.0 2017-04-21 [1] CRAN (R 3.6.0) #> pkgbuild 1.0.6 2019-10-09 [1] CRAN (R 3.6.0) #> pkgload 1.0.2 2018-10-29 [1] CRAN (R 3.6.0) #> prettyunits 1.1.1 2020-01-24 [1] CRAN (R 3.6.0) #> processx 3.4.1 2019-07-18 [1] CRAN (R 3.6.0) #> ps 1.3.0 2018-12-21 [1] CRAN (R 3.6.0) #> R6 2.4.1 2019-11-12 [1] CRAN (R 3.6.0) #> Rcpp 1.0.3 2019-11-08 [1] CRAN (R 3.6.0) #> remotes 2.1.0 2019-06-24 [1] CRAN (R 3.6.0) #> rlang 0.4.3 2020-01-24 [1] CRAN (R 3.6.0) #> rmarkdown 1.15 2019-08-21 [1] CRAN (R 3.6.0) #> rprojroot 1.3-2 2018-01-03 [1] CRAN (R 3.6.0) #> sessioninfo 1.1.1 2018-11-05 [1] CRAN (R 3.6.0) #> stringi 1.4.5 2020-01-11 [1] CRAN (R 3.6.0) #> stringr 1.4.0 2019-02-10 [1] CRAN (R 3.6.0) #> testthat 2.3.1 2019-12-01 [1] CRAN (R 3.6.0) #> usethis 1.5.1 2019-07-04 [1] CRAN (R 3.6.0) #> withr 2.1.2 2018-03-15 [1] CRAN (R 3.6.0) #> xfun 0.12 2020-01-13 [1] CRAN (R 3.6.0) #> xml2 1.2.2 2019-08-09 [1] CRAN (R 3.6.0) #> yaml 2.2.0 2018-07-25 [1] CRAN (R 3.6.0) #> #> [1] /Library/Frameworks/R.framework/Versions/3.6/Resources/library ```
jokergoo commented 4 years ago

Hi, I updated the version of gridtext to 0.1.0.9000 (the github version. The previous one installed on my laptop was 0.1.0, from CRAN). Now everything is fine (also the in the plots).

library(gridtext)
library(grid)
gb = richtext_grob(month.name, rot = 90, align_widths = FALSE)
convertHeight(grobHeight(gb), "mm")
#> [1] 20.7077799479167mm

gb = richtext_grob(month.name, rot = 90, align_widths = TRUE)
convertHeight(grobHeight(gb), "mm")
#> [1] 20.7077799479167mm

# if only September
gb = richtext_grob(month.name[9], rot = 90, align_widths = FALSE)
convertHeight(grobHeight(gb), "mm")
#> [1] 20.7077799479167mm

Created on 2020-02-07 by the reprex package (v0.3.0)

Session info ``` r devtools::session_info() #> ─ Session info ─────────────────────────────────────────────────────────────── #> setting value #> version R version 3.6.2 (2019-12-12) #> os macOS Mojave 10.14.2 #> system x86_64, darwin15.6.0 #> ui X11 #> language (EN) #> collate en_GB.UTF-8 #> ctype en_GB.UTF-8 #> tz Europe/Berlin #> date 2020-02-07 #> #> ─ Packages ─────────────────────────────────────────────────────────────────── #> package * version date lib source #> assertthat 0.2.1 2019-03-21 [1] CRAN (R 3.6.0) #> backports 1.1.5 2019-10-02 [1] CRAN (R 3.6.0) #> callr 3.4.0 2019-12-09 [1] CRAN (R 3.6.0) #> cli 2.0.1 2020-01-08 [1] CRAN (R 3.6.0) #> crayon 1.3.4 2017-09-16 [1] CRAN (R 3.6.0) #> desc 1.2.0 2018-05-01 [1] CRAN (R 3.6.0) #> devtools 2.2.1 2019-09-24 [1] CRAN (R 3.6.1) #> digest 0.6.23 2019-11-23 [1] CRAN (R 3.6.0) #> ellipsis 0.3.0 2019-09-20 [1] CRAN (R 3.6.0) #> evaluate 0.14 2019-05-28 [1] CRAN (R 3.6.0) #> fansi 0.4.1 2020-01-08 [1] CRAN (R 3.6.0) #> fs 1.3.1 2019-05-06 [1] CRAN (R 3.6.0) #> glue 1.3.1 2019-03-12 [1] CRAN (R 3.6.0) #> gridtext * 0.1.0.9000 2020-02-07 [1] Github (wilkelab/gridtext@5cc4b40) #> highr 0.8 2019-03-20 [1] CRAN (R 3.6.0) #> htmltools 0.4.0 2019-10-04 [1] CRAN (R 3.6.0) #> knitr 1.26 2019-11-12 [1] CRAN (R 3.6.0) #> magrittr 1.5 2014-11-22 [1] CRAN (R 3.6.0) #> markdown 1.1 2019-08-07 [1] CRAN (R 3.6.0) #> memoise 1.1.0 2017-04-21 [1] CRAN (R 3.6.0) #> pkgbuild 1.0.6 2019-10-09 [1] CRAN (R 3.6.0) #> pkgload 1.0.2 2018-10-29 [1] CRAN (R 3.6.0) #> prettyunits 1.0.2 2015-07-13 [1] CRAN (R 3.6.0) #> processx 3.4.1 2019-07-18 [1] CRAN (R 3.6.0) #> ps 1.3.0 2018-12-21 [1] CRAN (R 3.6.0) #> R6 2.4.1 2019-11-12 [1] CRAN (R 3.6.0) #> Rcpp 1.0.3 2019-11-08 [1] CRAN (R 3.6.0) #> remotes 2.1.0 2019-06-24 [1] CRAN (R 3.6.0) #> rlang 0.4.2 2019-11-23 [1] CRAN (R 3.6.0) #> rmarkdown 2.0 2019-12-12 [1] CRAN (R 3.6.0) #> rprojroot 1.3-2 2018-01-03 [1] CRAN (R 3.6.0) #> sessioninfo 1.1.1 2018-11-05 [1] CRAN (R 3.6.0) #> stringi 1.4.5 2020-01-11 [1] CRAN (R 3.6.0) #> stringr 1.4.0 2019-02-10 [1] CRAN (R 3.6.0) #> testthat 2.3.1 2019-12-01 [1] CRAN (R 3.6.1) #> usethis 1.5.1 2019-07-04 [1] CRAN (R 3.6.0) #> withr 2.1.2 2018-03-15 [1] CRAN (R 3.6.0) #> xfun 0.11 2019-11-12 [1] CRAN (R 3.6.0) #> xml2 1.2.2 2019-08-09 [1] CRAN (R 3.6.1) #> yaml 2.2.0 2018-07-25 [1] CRAN (R 3.6.0) #> #> [1] /Library/Frameworks/R.framework/Versions/3.6/Resources/library ```

The f() function defined before:

f <- function(rot) {
  gb = richtext_grob(
    month.name, x = ((3:14)+.5)/18,
    box_gp = gpar(col = "red"),
    rot = rot
  )

  grid.newpage()
  grid.draw(
    rectGrob(height = grobHeight(gb), width = grobWidth(gb))
  )
  grid.draw(gb)
}
f(45)
Screenshot 2020-02-07 at 15 41 35
jokergoo commented 4 years ago

Now I re-installed the version 0.1.0 from CRAN, everything is also working. I don't know why, but it works.

Thank you for your help!

clauswilke commented 4 years ago

I'm glad things work now. 0.1.0 and 0.1.0.9000 are essentially the same. It would have been strange if one worked and the other didn't.