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

Plots in for loop #237

Open
cderv opened this issue Feb 6, 2025 · 6 comments
Open

Plots in for loop #237

cderv opened this issue Feb 6, 2025 · 6 comments
Assignees

Comments

@cderv
Copy link
Collaborator

cderv commented Feb 6, 2025

Opening this issue to follow up on knitr report:

and as a way to keep track of this example. However, possibly related to

Plots in for loop are not treated the same as plots outside of it.

  • Only last is in output
res <- evaluate::evaluate(function(){ 
    library(tinyplot)
    for (thm in c("dark", "minimal")) {
        tinytheme(thm)
        tinyplot(I(Sepal.Length * 1e4) ~ Petal.Length | Species, data = iris)
    }
})
str(res, 1)
#> List of 3
#>  $ :List of 1
#>   ..- attr(*, "class")= chr "source"
#>  $ :List of 1
#>   ..- attr(*, "class")= chr "source"
#>  $ :List of 2
#>   ..- attr(*, "engineVersion")= int 16
#>   ..- attr(*, "pid")= int 49608
#>   ..- attr(*, "Rversion")=Classes 'R_system_version', 'package_version', 'numeric_version'  hidden list of 1
#>   ..- attr(*, "load")= chr(0) 
#>   ..- attr(*, "attach")= chr(0) 
#>   ..- attr(*, "class")= chr "recordedplot"
#>  - attr(*, "class")= chr [1:2] "evaluate_evaluation" "list"
  • Both plots are recorded
res <- evaluate::evaluate(function(){ 
    library(tinyplot)
    tinytheme("dark")
    tinyplot(I(Sepal.Length * 1e4) ~ Petal.Length | Species, data = iris)
    tinytheme("minimal")
    tinyplot(I(Sepal.Length * 1e4) ~ Petal.Length | Species, data = iris)
})
str(res, 1)
#> List of 7
#>  $ :List of 1
#>   ..- attr(*, "class")= chr "source"
#>  $ :List of 1
#>   ..- attr(*, "class")= chr "source"
#>  $ :List of 1
#>   ..- attr(*, "class")= chr "source"
#>  $ :List of 2
#>   ..- attr(*, "engineVersion")= int 16
#>   ..- attr(*, "pid")= int 71848
#>   ..- attr(*, "Rversion")=Classes 'R_system_version', 'package_version', 'numeric_version'  hidden list of 1
#>   ..- attr(*, "load")= chr(0) 
#>   ..- attr(*, "attach")= chr(0) 
#>   ..- attr(*, "class")= chr "recordedplot"
#>  $ :List of 1
#>   ..- attr(*, "class")= chr "source"
#>  $ :List of 1
#>   ..- attr(*, "class")= chr "source"
#>  $ :List of 2
#>   ..- attr(*, "engineVersion")= int 16
#>   ..- attr(*, "pid")= int 71848
#>   ..- attr(*, "Rversion")=Classes 'R_system_version', 'package_version', 'numeric_version'  hidden list of 1
#>   ..- attr(*, "load")= chr(0) 
#>   ..- attr(*, "attach")= chr(0) 
#>   ..- attr(*, "class")= chr "recordedplot"
#>  - attr(*, "class")= chr [1:2] "evaluate_evaluation" "list"

Not sure if something can be done around that, but this is now definitely a differentiating approach with how litedown::fuse() handles plot. So I would like to dig into this.

@cderv cderv self-assigned this Feb 6, 2025
@hadley
Copy link
Member

hadley commented Feb 6, 2025

Simpler reprex?

res <- evaluate::evaluate(function(){
  for (i in 1:2) {
    plot(1)
  }
})
sapply(res, class)
#> [1] "source"       "recordedplot"

Created on 2025-02-06 with reprex v2.1.0

@hadley
Copy link
Member

hadley commented Feb 6, 2025

Although if I run that directly in the console, I see:

[1] "source"       "recordedplot" "recordedplot"

But I don't see that with tinyplot, which suggests that it's doing something different to plot().

@etiennebacher
Copy link

etiennebacher commented Feb 10, 2025

But I don't see that with tinyplot, which suggests that it's doing something different to plot().

It's the opposite for me:

  • in reprex() and in the console, using plot() returns [1] "source" "recordedplot"
  • in reprex() and in the console, using tinyplot() returns [1] "source" "recordedplot" "recordedplot"

@etiennebacher
Copy link

etiennebacher commented Feb 10, 2025

Looks like tinytheme() might be the culprit here:

  • without it:
reprex::reprex({
  library(tinyplot)
  for (thm in 1:2) {
    tinyplot(Sepal.Length ~ Petal.Length, data = iris)
  }
})

prints 2 plots.

  • with it:
reprex::reprex({
  library(tinyplot)
  tinytheme("default")
  for (thm in 1:2) {
    tinyplot(Sepal.Length ~ Petal.Length, data = iris)
  }
})

prints 1 plot.

What I don't understand is that

reprex::reprex({
  for (thm in 1:2) {
    plot(Sepal.Length ~ Petal.Length, data = iris)
  }
})

also prints a single plot. I can't help further but hopefully that's useful to you.

@cderv
Copy link
Collaborator Author

cderv commented Feb 10, 2025

If the exact same plot is drawn, then it won't be recorded.
plot capture logic is here

evaluate/R/watchout.R

Lines 46 to 74 in 9b41223

capture_plot <- function(incomplete = FALSE) {
# no plots open; par("page") will open a device
if (is.null(dev.list())) {
return()
}
# only record plots for our graphics device
if (!identical(dev.cur(), dev)) {
return()
}
# current page is incomplete
if (!par("page") && !incomplete) {
return()
}
plot <- recordPlot()
if (!makes_visual_change(plot[[1]])) {
return()
}
if (!looks_different(last_plot[[1]], plot[[1]])) {
return()
}
last_plot <<- plot
push(plot)
invisible()
}

So what does tinytheme do exactly that would make the plot different ? Maybe plot change are not correctly detected. If you can give details on this, it would help undertand this better

I tried changing par() in the loop, and got two outputs

res <- evaluate::evaluate(function() {
    for (bg in c("red", "blue")) {
        par(bg = bg)
        plot(Sepal.Length ~ Petal.Length, data = iris)
    }
})
sapply(res, class)
#> [1] "source"       "recordedplot" "recordedplot"

While changing theme for tinyplot does return one

res <- evaluate::evaluate(function(){ 
    library(tinyplot)
    for (thm in c("dark", "minimal")) {
        tinytheme(thm)
        tinyplot(I(Sepal.Length * 1e4) ~ Petal.Length | Species, data = iris)
    }
})
sapply(res, class)
#> [1] "source"       "source"       "recordedplot"

@etiennebacher
Copy link

etiennebacher commented Feb 11, 2025

So what does tinytheme do exactly that would make the plot different ? Maybe plot change are not correctly detected. If you can give details on this, it would help undertand this better

I don't know enough about graphics in base R (I'm not the author of tinyplot) but I narrowed it down to the init_tpar() and tpar() calls in tinytheme(). If those two calls are commented out then the two plots are printed.

Also, the tinyplot() call isn't even needed in the reprex.

This gives two plots:

reprex::reprex({
  for (i in 1:2) {
      plot(i)
  }
})

This gives one plot:

reprex::reprex({
  tinyplot::tinytheme("basic")
  for (i in 1:2) {
      plot(i)
  }
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants