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

ragg device functions not working with ggsave #4347

Closed
PaulC91 opened this issue Feb 17, 2021 · 13 comments · Fixed by #4388
Closed

ragg device functions not working with ggsave #4347

PaulC91 opened this issue Feb 17, 2021 · 13 comments · Fixed by #4388
Labels
bug an unexpected problem or unintended behavior
Milestone

Comments

@PaulC91
Copy link

PaulC91 commented Feb 17, 2021

In the recent tidyverse blogpost "Modern Text Features in R" it states you can use the ragg device functions with ggsave:

You can use ragg with ggsave() by passing the device function to the device argument (e.g. ggsave(device = agg_tiff)).

It's not working for me however, the following code results in an empty image being saved.

Related to r-lib/ragg#69

library(ggplot2)
library(ragg)

p <- ggplot(iris, aes(Sepal.Length, Sepal.Width, col = Species)) + geom_point() 

ggsave("gg_test.png", p, device = agg_png) 
#> Saving 7 x 5 in image

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

Session info
sessioninfo::session_info()
#> ─ Session info ───────────────────────────────────────────────────────────────
#>  setting  value                       
#>  version  R version 4.0.3 (2020-10-10)
#>  os       macOS Mojave 10.14.6        
#>  system   x86_64, darwin17.0          
#>  ui       X11                         
#>  language (EN)                        
#>  collate  en_GB.UTF-8                 
#>  ctype    en_GB.UTF-8                 
#>  tz       Europe/Paris                
#>  date     2021-02-17                  
#> 
#> ─ Packages ───────────────────────────────────────────────────────────────────
#>  package     * version date       lib source             
#>  assertthat    0.2.1   2019-03-21 [1] standard (@0.2.1)  
#>  backports     1.2.0   2020-11-02 [1] standard (@1.2.0)  
#>  cli           2.2.0   2020-11-20 [1] standard (@2.2.0)  
#>  colorspace    2.0-0   2020-11-11 [1] standard (@2.0-0)  
#>  crayon        1.3.4   2017-09-16 [1] standard (@1.3.4)  
#>  digest        0.6.27  2020-10-24 [1] standard (@0.6.27) 
#>  dplyr         1.0.2   2020-08-18 [1] standard (@1.0.2)  
#>  ellipsis      0.3.1   2020-05-15 [1] standard (@0.3.1)  
#>  evaluate      0.14    2019-05-28 [1] standard (@0.14)   
#>  fansi         0.4.1   2020-01-08 [1] standard (@0.4.1)  
#>  farver        2.0.3   2020-01-16 [1] standard (@2.0.3)  
#>  fs            1.5.0   2020-07-31 [1] standard (@1.5.0)  
#>  generics      0.1.0   2020-10-31 [1] standard (@0.1.0)  
#>  ggplot2     * 3.3.3   2020-12-30 [1] standard (@3.3.3)  
#>  glue          1.4.2   2020-08-27 [1] standard (@1.4.2)  
#>  gtable        0.3.0   2019-03-25 [1] standard (@0.3.0)  
#>  highr         0.8     2019-03-20 [1] standard (@0.8)    
#>  htmltools     0.5.1.1 2021-01-22 [1] standard (@0.5.1.1)
#>  knitr         1.30    2020-09-22 [1] standard (@1.30)   
#>  labeling      0.4.2   2020-10-20 [1] standard (@0.4.2)  
#>  lifecycle     0.2.0   2020-03-06 [1] standard (@0.2.0)  
#>  magrittr      2.0.1   2020-11-17 [1] standard (@2.0.1)  
#>  munsell       0.5.0   2018-06-12 [1] standard (@0.5.0)  
#>  pillar        1.4.7   2020-11-20 [1] standard (@1.4.7)  
#>  pkgconfig     2.0.3   2019-09-22 [1] standard (@2.0.3)  
#>  purrr         0.3.4   2020-04-17 [1] standard (@0.3.4)  
#>  R6            2.5.0   2020-10-28 [1] standard (@2.5.0)  
#>  ragg        * 1.1.0   2021-02-15 [1] standard (@1.1.0)  
#>  reprex        1.0.0   2021-01-27 [1] standard (@1.0.0)  
#>  rlang         0.4.10  2020-12-30 [1] standard (@0.4.10) 
#>  rmarkdown     2.5     2020-10-21 [1] standard (@2.5)    
#>  rstudioapi    0.13    2020-11-12 [1] standard (@0.13)   
#>  scales        1.1.1   2020-05-11 [1] standard (@1.1.1)  
#>  sessioninfo   1.1.1   2018-11-05 [1] standard (@1.1.1)  
#>  stringi       1.5.3   2020-09-09 [1] standard (@1.5.3)  
#>  stringr       1.4.0   2019-02-10 [1] standard (@1.4.0)  
#>  styler        1.3.2   2020-02-23 [1] standard (@1.3.2)  
#>  systemfonts   1.0.1   2021-02-09 [1] standard (@1.0.1)  
#>  textshaping   0.3.0   2021-02-10 [1] standard (@0.3.0)  
#>  tibble        3.0.4   2020-10-12 [1] standard (@3.0.4)  
#>  tidyselect    1.1.0   2020-05-11 [1] standard (@1.1.0)  
#>  vctrs         0.3.5   2020-11-17 [1] standard (@0.3.5)  
#>  withr         2.3.0   2020-09-22 [1] standard (@2.3.0)  
#>  xfun          0.19    2020-10-30 [1] standard (@0.19)   
#>  yaml          2.2.1   2020-02-01 [1] standard (@2.2.1)  
#> 
#> [1] /Library/Frameworks/R.framework/Versions/4.0/Resources/library

Thanks.

@yutannihilation
Copy link
Member

Thanks, I too faced this problem a while ago. As the default value of ragg::agg_png()'s unit is px, which is the same as grDevices::png(), I guess ggplot2 needs some wrappers like this:

ggplot2/R/save.r

Lines 181 to 185 in dbd7d79

png = function(...) grDevices::png(..., res = dpi, units = "in"),
jpg = function(...) grDevices::jpeg(..., res = dpi, units = "in"),
jpeg = function(...) grDevices::jpeg(..., res = dpi, units = "in"),
bmp = function(...) grDevices::bmp(..., res = dpi, units = "in"),
tiff = function(...) grDevices::tiff(..., res = dpi, units = "in")

@clauswilke
Copy link
Member

This is the lookup table that converts file extensions into graphic devices, so it wouldn't have an effect if users specify the graphics device as a function. Instead, if they do so, they will have to set res and units manually.

However, we should change the lookup table so it uses ragg devices by default if available.

@clauswilke
Copy link
Member

See also: #4086

@thomasp85
Copy link
Member

Yeah — the plan is to use ragg if available for all file types where it makes sense in the next release. If someone is eager to implement the logic they should feel free but otherwise I'm on it the next time I look at ggplot2

@yutannihilation
Copy link
Member

However, we should change the lookup table so it uses ragg devices by default if available.

Yes, what I meant was the users need to define some wrapper function like the one below. I guess a lot of people will use AGG as RStudio graphic device (as it's great) and they'll want to use the same rendering on ggsave() as well. Probably the next release will not be very soon, so I think it's meaningful to show a workaround for this here.

library(ggplot2)

p <- ggplot(iris, aes(Sepal.Length, Sepal.Width, col = Species)) + geom_point() 

# 300 is the same dpi as ggsave()'s default
png <- function(...) ragg::agg_png(..., res = 300, units = "in")
ggsave("gg_test.png", p, device = png)

@thomasp85
Copy link
Member

The behaviour of ggsave() is quite unfortunate... ragg uses the exact same default values as grDevices::png() but still does not behave the same way

@clauswilke
Copy link
Member

The following works just fine, though. If people want to use custom graphics devices they need to be prepared to set a few parameters correctly, I think. Maybe the real issue is to document ggsave() better, and to explain that when using a device function, some settings such as dpi or units may not work and/or may have to be set explicitly.

library(ggplot2)

p <- ggplot(iris, aes(Sepal.Length, Sepal.Width, col = Species)) + geom_point() 

ggsave("~/Desktop/gg_test.png", p, device = ragg::agg_png, res = 300, units = "in")
#> Saving 7 x 5 in image

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

gg_test

@taraskaduk
Copy link

taraskaduk commented Mar 5, 2021

@clauswilke, when I run your example, my gg_test.png file is 7x7 pixels (not inches) big and 116 bytes in size.
Screen Shot 2021-03-04 at 21 07 11

FWIW, p prints to Viewer just fine, it's ggsave with agg_png that saves a tiny 7x7 pixels file.

My session info:

R version 4.0.3 (2020-10-10)
Platform: x86_64-apple-darwin17.0 (64-bit)
Running under: macOS Big Sur 10.16

Matrix products: default
LAPACK: /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] ggplot2_3.3.3

loaded via a namespace (and not attached):
 [1] magrittr_2.0.1    tidyselect_1.1.0  munsell_0.5.0     colorspace_2.0-0  R6_2.5.0         
 [6] ragg_1.1.1.9000   rlang_0.4.10      fansi_0.4.2       dplyr_1.0.5       tools_4.0.3      
[11] grid_4.0.3        gtable_0.3.0      utf8_1.1.4        DBI_1.1.1         withr_2.4.1      
[16] systemfonts_1.0.1 ellipsis_0.3.1    digest_0.6.27     assertthat_0.2.1  tibble_3.0.6     
[21] lifecycle_1.0.0   crayon_1.4.1      textshaping_0.3.1 farver_2.0.3      purrr_0.3.4      
[26] vctrs_0.3.6       glue_1.4.2        labeling_0.4.2    compiler_4.0.3    pillar_1.5.0     
[31] generics_0.1.0    scales_1.1.1      pkgconfig_2.0.3  

@collioud
Copy link

Hello,

In addition, it seems that ggsave() does not close the device when using agg_png() as device (it does when specifying 'png' as device). The following reprex ends with to the Too many open devices error.

library(ggplot2)
library(ragg)
library(glue)

data(iris)
for (i in 1:200){
  p <- ggplot(iris) + 
    geom_point(aes(Sepal.Length,Sepal.Width,color=Species))

  ggsave(glue("Plot_{i}.png"), plot=p, device=agg_png(), path="./tmp/")
}

(This is solved by explicitly calling dev.off() after ggsave())

@yutannihilation
Copy link
Member

yutannihilation commented Mar 16, 2021

@collioud
What device argument accepts is a function (e.g. agg_png, function(...) agg_png(...)), not a function call (e.g. agg_png()).

@jthomasmock
Copy link

jthomasmock commented Mar 24, 2021

I can confirm w/ @taraskaduk that using Claus' exact code with agg_png + friends as the device returns small images, 116 bytes, 7x7 pixels.

Screen Shot 2021-03-24 at 6 29 02 PM

ragg version 1.1.2

However, providing additional arguments to agg_png() appears to work fine.

ggsave(
  "gg_test.png",
  p,
  device = ragg::agg_png( 
    width = 12,
    height = 8, 
    units = "cm",
    scaling = 1,
    res = 500)
)

gg_test

@clauswilke
Copy link
Member

Ah, this makes sense. ggsave() has a units argument so it doesn't get captured by ... and thus doesn't get handed off to agg_png(). Workaround is to specify the resolution directly in px, not in inch.

@jthomasmock
Copy link

jthomasmock commented Mar 24, 2021

Yup!

I can confirm you can get the "expected" behavior as so, without specifying arguments inside agg_png:

ggsave(
  "gg_test.png",
  p,
  device = ragg::agg_png,
  res = 300,
  units = "in", # this is ignored by `agg_png` and it returns default of `"px"`
  height = 1200, # this is ACTUALLY pixels
  width = 1400,  # also pixels
  limitsize = FALSE # because ggplot2 thinks it's inches
)

This is still a bit tricky to read as ggsave doesn't allow for units = "px" as the argument though.

@thomasp85 thomasp85 added the bug an unexpected problem or unintended behavior label Mar 25, 2021
@thomasp85 thomasp85 added this to the ggplot2 3.3.4 milestone Mar 25, 2021
thomasp85 added a commit to thomasp85/ggplot2 that referenced this issue Mar 25, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug an unexpected problem or unintended behavior
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants