Creating a simple game in R Shiny using ggplot

Hello everyone,

I'm trying to create a very simple game in R Shiny, but I don't know how to get started. I'm looking for a bit of skeleton code to figure out how the game loop will work, so I can modify that for my own needs. Specifically, what I'm looking for is the code for a Shiny app with a single PlotOutput ggplot object, with a plot title that is a number; it starts at 0 and goes up by 1 every time the user clicks on the plot. I think if I have this basic structure, I can expand it to fit my own needs.

Thank you!

There you go (added a little theming):

library(shiny)
library(bslib)
library(bsicons)
library(thematic)
library(datasets)
library(ggplot2)

ui <- page_navbar(
  theme = bs_theme(version = 5, bootswatch = bootswatch_themes()[1L]),
  nav_spacer(),
  nav_panel(
    title = "Home",
    icon = bs_icon("house"),
    plotOutput("plot", click = "plot_click")
  ),
  nav_panel(
    title = "Empty page",
    icon = bs_icon("database"),
    tags$p('Empty page')
  ),
  title = "Test"
)

server <- function(input, output, session) {
  thematic_shiny()
  output$plot <- renderPlot({
    ggplot(mtcars, aes(wt, mpg)) + geom_point() + ggtitle(nclicks()) +
      theme(plot.title = element_text(hjust = 0.5))
  }, res = 96L)
  
  nclicks <- reactiveVal(0L)
  observeEvent(input$plot_click, {
    nclicks(nclicks()+1L)
  })
}

shinyApp(ui = ui, server = server)

Not sure if this is an option for you, but I'd suggest to use {plotly} instead of {ggplot2} because plot_ly() or ggplot objects can be modified via plotlyProxy. The ggplot output is re-rendered after every click, which is slow.

Thank you! What would the equivalent code in plotly look like? I'm not familiar with that package. Performance is a big concern for me, since it's a game.

Do you need the click event everywhere on the plot element or only on specified datapoints?

Everywhere on the plot, if possible!

This is an example based on my answer here:

library(plotly)
library(shiny)
library(htmlwidgets)

initDF <- data.frame(x = 1:10, y = 1:10)

ui <- fluidPage(
  plotlyOutput("myPlot"),
  verbatimTextOutput("click")
)

server <- function(input, output, session) {
  
  js <- "
    function(el, x, inputName){
      var id = el.getAttribute('id');
      var gd = document.getElementById(id);
      var d3 = Plotly.d3;
      Plotly.update(id).then(attach);
        function attach() {
          gd.addEventListener('click', function(evt) {
            var xaxis = gd._fullLayout.xaxis;
            var yaxis = gd._fullLayout.yaxis;
            var bb = evt.target.getBoundingClientRect();
            var x = xaxis.p2d(evt.clientX - bb.left);
            var y = yaxis.p2d(evt.clientY - bb.top);
            var coordinates = [x, y];
            Shiny.setInputValue(inputName, coordinates, {priority: 'event'});
          });
        };
  }
  "
  
  clickposition_history <- reactiveVal(initDF)
  
  observeEvent(input$clickposition, {
    clickposition_history(rbind(clickposition_history(), input$clickposition))
  })
  
  output$myPlot <- renderPlotly({
    plot_ly(initDF, x = ~x, y = ~y, type = "scatter", mode = "markers") |> layout(title = "nclicks: 0")|>
      onRender(js, data = "clickposition")
  })
  
  myPlotProxy <- plotlyProxy("myPlot", session)
  
  observe({
    plotlyProxyInvoke(myPlotProxy, "restyle", list(x = list(clickposition_history()$x), y = list(clickposition_history()$y)))
  })
  
  output$click <- renderPrint({
    clickposition_history()
  })
  
  # modify title ------------------------------------------------------------
  nclicks <- reactiveVal(0L)
  observeEvent(input$clickposition, {
    nclicks(nclicks()+1L)
  })
  
  observe({
    plotlyProxyInvoke(myPlotProxy, "relayout", list(title = paste0("nclicks: ", nclicks())))
  })
}

shinyApp(ui, server)

For clicks on the datapoints we could have used plotly::event_data() instead of custom JS.

Also check this: