rstudio / reticulate

R Interface to Python
https://rstudio.github.io/reticulate
Apache License 2.0
1.67k stars 327 forks source link

Make plots available in IRkernel #555

Open flying-sheep opened 5 years ago

flying-sheep commented 5 years ago

As a hack, it would be sufficient to do this:

.onLoad <- function(...) {
    if (getOption('jupyter.in_kernel')) {
        plt <- import('matplotlib.pyplot')
        plt$show <- function(...) {
            io <- import('io')
            builtins <- import_builtins()
            buf <- io$BytesIO()
            plt$savefig(buf, format = 'png')
            buf$seek(0L)
            png_data <- as.raw(builtins$list(buf$getvalue()))
            IRdisplay::display_png(png_data)
        }
    }
}

As a nicer solution, there could be a matplotlib backend that basically does that.

kevinushey commented 5 years ago

RStudio basically does this as well to ensure matplotlib plots can show up in the appropriate context (e.g. the RStudio Plots pane or in R Notebooks). I would recommend Jupyter / the IRkernel did the same.

We could consider some sort of official mechanism for doing this but I would still prefer the coupling to be 'loose'; that is, I believe it's the responsibility of the frontend to inject the code necessary to get plots working.

Let me know if you disagree or feel that there's some reason why this code wouldn't be able to fit in the IRkernel itself. (Also worth noting that we wouldn't want to eagerly load matplotlib or pyplot; the hook would need to be injected at module load time)

flying-sheep commented 5 years ago

I see a few reasons why I think this is the place for that code:

  1. Also worth noting that we wouldn't want to eagerly load matplotlib or pyplot; the hook would need to be injected at module load time

    Exactly. I don’t think you provide a hook mechanism triggering when loading modules, right?

  2. You also have code in here making matplotlib work in R-Markdown.

  3. I would still prefer the coupling to be 'loose'; that is, I believe it's the responsibility of the frontend to inject the code necessary to get plots working.

    R has a plotting mechanism in place out of the box. You are the compatibility layer to Python, so I’d argue it’s your job to make matplotlib draw to R plotting devices. Ideally you’d create a matplotlib backend that does this. Then matplotlib would automatically work everywhere, including IRkernel, and you can get rid of the R-Markdown special case.

kevinushey commented 5 years ago

Exactly. I don’t think you provide a hook mechanism triggering when loading modules, right?

We do now in the development version of reticulate:

https://github.com/rstudio/reticulate/blob/59367b5265c94c393e0faf921f049518ec5ad9a6/R/python.R#L64-L74

You should be able to do something like:

setHook("reticulate::matplotlib.pyplot::load", function(...) { ... })

R has a plotting mechanism in place out of the box. You are the compatibility layer to Python, so I’d argue it’s your job to make matplotlib draw to R plotting devices. Ideally you’d create a matplotlib backend that does this.

This is a good point and I think you're right, but I haven't yet investigated what it would take to implement a matplotlib backend for this. My gut instinct says that this could be challenging to get right, but I could be wrong.

flying-sheep commented 5 years ago

I think it should be possible. Here’s the docs for implementing a backend (=Renderer):

The following methods must be implemented in the backend for full functionality (though just implementing draw_path alone would give a highly capable backend):

  • draw_path
  • draw_image
  • draw_gouraud_triangle

The following methods should be implemented in the backend for optimization reasons:

  • draw_text
  • draw_markers
  • draw_path_collection
  • draw_quad_mesh