1

I am new to the world of combining Javascript with R and my Javascript knowledge is quite limited. I am trying to make a plotly plot of chess openings and when you click on the opening it shows a board with the initial moves of the opening.

library(plotly)
library(htmlwidgets)
library(data.table)
library(rchess)

data("chessopenings")
setDT(chessopenings)
mychess = chessopenings[1:10]
mychess[, Pop := sample(c(1,2,4), nrow(mychess), replace = T)]
mychess[, fens := sapply(pgn, function(x) {chsspgn <- Chess$new(); chsspgn$load_pgn(x); chsspgn$fen()})]

The code above just creates a sample dataset of 10 openings. I then plot those using plotly and based on the guidance I found here https://plotly-r.com/supplying-custom-data.html#fig:hover-annotate I tried to create a new function that shows the chessboard when you click on the points. The chessboard function is the one specified here https://chessboardjs.com/examples#1002 and if I understand it correctly (please correct me if I am wrong) it returns a 'div' element which is the one I have to show.

q = plot_ly(mychess, x = ~eco, y = ~Pop) %>% 
  add_markers(text = ~name,
              customdata = ~fens)  
    
onRender(
  q, "
     function(el) {
      el.on('plotly_click', function(d) {
      var pt = d.points[0]; 
      var fen =  d.points[0].customdata
      var newboard = Chessboard('myBoard', fen);
      newboard.style.visibility = 'visible';
      newboard.style.display = 'block';
      })}")

Unfortunately nothing shows. I used the information provided here Show/hide 'div' using JavaScript but it seems I am missing something.

Update: I tried the suggestion by @Bas to convert the images by using the following code:

png(pic1 <- tempfile(fileext = ".png"));chessboardjs(fen = mychess$fens[1]); dev.off() 
text = knitr::image_uri(pic1) 
text = sub(".*?,", "", text) 
html <- sprintf('<html><body><img src="data:image/png;base64,%s"></body></html>', text) 
cat(html, file = tf2 <- tempfile(fileext = ".html")) 
browseURL(tf2)

But this doesn't work either.

SeGa
  • 9,454
  • 3
  • 31
  • 70
User2321
  • 2,952
  • 23
  • 46

2 Answers2

2

It works if you apply the Plotly.relayout(...) function like below. Note that I replaced the image with some custom data since I don't have the Chessboard(...) function.

htmlwidgets::onRender(
  q, "
  function(el) {
      el.on('plotly_click', function(d) {
      var pt = d.points[0]; 
      var fen =  d.points[0].customdata
      var img = {
            // location of image
            source: \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==\",
            // top-left corner
            x: 0,
            y: 1,
            sizex: 0.2,
            sizey: 0.2,
            xref: 'paper',
            yref: 'paper'
          };
      Plotly.relayout(el.id, {
               images: [img] 
      });
      })}
      ")
Bas
  • 4,628
  • 1
  • 14
  • 16
  • Thank you! If my understanding is correct, if you have the `rchess` package then you should also have the Chessboard() function. But again I am not really sure and I don't know exactly how to check (any hints would be very helpful). Making an image appear is not the problem I am dealing with. – User2321 Sep 24 '20 at 08:28
  • I see – I thought that was your issue. I just installed the `rchess` package, but JavaScript still misses the `Chessboard()` function. You can see this if you open your plotly output in a browser and right click, go to 'Inspect Element' and select 'Console'. There you can see the JavaScript errors. By adding `console.log()` statements in your JavaScript code, you can debug it. – Bas Sep 24 '20 at 11:10
  • That was helpful thank you. Hmm is there a possibility to use an R function in the javascript part? I think that the function I need is the one here: `https://github.com/jbkunst/rchess/blob/master/R/chessboardjs.R` but I thought that this was using the `Chessboard()` function somehow... If you think I should raise a new question just let me know. – User2321 Sep 24 '20 at 16:03
  • 1
    No, as far as I know that's not possible. You could store the image in base64 form for every point with https://stackoverflow.com/questions/33409363/convert-r-image-to-base-64, store this as `img` in the data frame and then display the image using the code I gave above, with `source = d.points[0].img`. – Bas Sep 24 '20 at 21:13
  • Thank you again!. I tried `png(pic1 <- tempfile(fileext = ".png")) ; chessboardjs(fen = mychess$fens[1]); dev.off()` `text = knitr::image_uri(pic1)` `text = sub(".*?,", "", text)` `html <- sprintf('', text)` `cat(html, file = tf2 <- tempfile(fileext = ".html"))` `browseURL(tf2)` but unfortunately nothing shows... :( – User2321 Sep 30 '20 at 15:44
2

You have to include the Chessboard JavaScript and CSS-Files in order to use the Chessboard function. The function will not create a div but append the chessboard-visualization to a given CSS-selector. So the div has to be created beforehand.


Shiny version:

library(plotly)
library(htmlwidgets)
library(data.table)
library(rchess)
library(shiny)

data("chessopenings")
setDT(chessopenings)
mychess = chessopenings[1:10]
mychess[, Pop := sample(c(1,2,4), nrow(mychess), replace = T)]
mychess[, fens := sapply(pgn, function(x) {chsspgn <- Chess$new();
chsspgn$load_pgn(x); chsspgn$fen()})]

ui <- fluidPage(
  tags$head(tags$link(href = "https://unpkg.com/@chrisoakman/chessboardjs@1.0.0/dist/chessboard-1.0.0.min.css", rel = "stylesheet", type = "text/css")),
  tags$head(tags$script(src = "https://unpkg.com/@chrisoakman/chessboardjs@1.0.0/dist/chessboard-1.0.0.min.js")),
  plotlyOutput("plt"),
  div(id = "myBoard", style="width: 400px")
)

server <- function(input, output, session) {
  output$plt <- renderPlotly({
    q = plot_ly(mychess, x = ~eco, y = ~Pop) %>% 
      add_markers(text = ~name,
                  customdata = ~fens)  
    onRender(
      q, "
     function(el) {
      el.on('plotly_click', function(d) {
      var pt = d.points[0];
      var fen =  d.points[0].customdata;
      var newboard = Chessboard('myBoard', fen);
      })}")
  })
}

shinyApp(ui, server)

To see the images of chess pieces you have the include them in the www/img/chesspieces/wikipedia/ folder of your Shiny App. Otherwise you will see some errors in the browser console.

GET http://127.0.0.1:5247/img/chesspieces/wikipedia/wK.png HTTP/1.1 404 Not Found 11ms


A Markdown version:

To see the chess positions you have to download the png files and put them in this folder structure /img/chesspieces/wikipedia/ based on where the .Rmd document is.

---
  title: "Chess Openings"
  output: html_document
---

<style type="text/css">
@import url("https://unpkg.com/@chrisoakman/chessboardjs@1.0.0/dist/chessboard-1.0.0.min.css");
</style>

  <script src="https://unpkg.com/@chrisoakman/chessboardjs@1.0.0/dist/chessboard-1.0.0.min.js"></script>

```{r setup, include=FALSE}
library(plotly)
library(htmlwidgets)
library(data.table)
library(rchess)
library(shiny)
data("chessopenings")
setDT(chessopenings)
mychess = chessopenings[1:10]
mychess[, Pop := sample(c(1,2,4), nrow(mychess), replace = T)]
mychess[, fens := sapply(pgn, function(x) {chsspgn <- Chess$new();
chsspgn$load_pgn(x); chsspgn$fen()})]
```

```{r out.width='100%', echo=FALSE, warning = FALSE}
q = plot_ly(mychess, x = ~eco, y = ~Pop) %>%
  add_markers(text = ~name,
              customdata = ~fens)
onRender(
  q, "
 function(el) {
  el.on('plotly_click', function(d) {
  var pt = d.points[0];
  var fen =  d.points[0].customdata;
  var newboard = Chessboard('myBoard', fen);
  })}")
```

  <div id="myBoard" style="width: 400px"></div>
SeGa
  • 9,454
  • 3
  • 31
  • 70
  • Thank you! I have a very fundamental question: Is it possible to have this without using `shiny`? Could this work by just using `htmlwidgets`? I try to avoid needing a server... – User2321 Oct 13 '20 at 16:49
  • Do you want is a Markdown or just an interactive R-script? – SeGa Oct 13 '20 at 16:51
  • As an html Markdown. (or other Markdown if more appropriate but I think html is the way to go) – User2321 Oct 13 '20 at 16:52
  • 1
    I updated my answer with a markdown-version. Would that solve it for you? – SeGa Oct 14 '20 at 06:44
  • This is brilliant! Yes this is perfect! May I ask for some clarifications as to how you knew the location you needed to store the files and in general the whole process? Just for my curiosity, if it isn't much trouble. Thank you very very much! – User2321 Oct 14 '20 at 08:24
  • I started with the Shiny version, which easily lets you create/debug interactive documents and then you just go from one error to the next, look for their reason and fix them. And by fixing I am mean a lot of trial and error, setting print statements, using `browser()` in R and `debugger` in JS. – SeGa Oct 14 '20 at 08:37