Search code examples
rshinydt

How can I display a dataframe in a Shiny app as a grid, and not a table?


I have some data in a dataframe. I can display the dataframe as a table with DataTables.

However, I would like to display the data as a grid with N columns, so that every N rows from the data frame are shown in the same row.

Show data as a grid:

Show data as grid

As shown in the image above, I have gotten a grid to show up by using HTML to render the data frame directly. But the next step is where I am stuck, which is I want to be able to show a modal dialog when a cell in the grid is clicked. I have that working in the data table, but I haven't been able to figure out how to make a div clickable, such that when handling the event I know which cell was clicked?

library("shiny")
library("tidyr")
library("tidyverse")
library("dplyr")
library("shinydashboard")


# generate html grid from data frame
getHTML <- function (frames) {
  innerhtml = '<div class="grid-container">'
  for (row in 1:(nrow(frames))) {
      id <- frames[row, "id"]
      name  <- frames[row, "names"]
        row_html = '<div class="grid-item">'
        row_html = paste(row_html, '<span>Name: ' , name, "id ", row , '</span>')
        row_html = paste(row_html, '</div>')
        
         innerhtml = paste(innerhtml, row_html)
  }
  paste(innerhtml, "</div>")
  return (innerhtml)
}

#show modal dialog for player id and name
plotModal <- function(id, name) {
  modalDialog(
         p(paste("Player  # ", id, ", " , name,", was clicked")),
         title = paste("Player " , id),
        
        easyClose = TRUE
      )
}

ui <- dashboardPage(
  # Application title
  dashboardHeader(title = "Dashboard"),

  dashboardSidebar(
      h3("Filters")
    ),

    dashboardBody(
        tags$head(tags$style(HTML('
      .grid-container {
        display: grid;
        grid-template-columns: auto auto auto auto;
 
        }
        .grid-item {
        background-color: rgba(255, 255, 255, 0.8);
        border: 1px solid rgba(0, 0, 0, 0.8);
          padding: 20px;


        }'))),

      fluidRow(
        box(title="Render as table", column(width=12, DT::dataTableOutput("player_table"))),
       box(title="Render as Grid", column(width=12,   uiOutput("player_grid")))
      )
    )
)


server <- function(input, output, session) {
# data to be rendered
    frames = data.frame(names= c("james","kyle", "sally","hannah","jeff","kurt"), ids=c(1:6))

  output$player_table <- DT::renderDataTable({
    DT::datatable(frames, rownames=FALSE,  selection = 'single')
  })

        
#when a row in the table is clicked, show popup
  observeEvent(input$player_table_cell_clicked, {
    info = input$player_table_cell_clicked
    # do nothing if not clicked yet, or the clicked cell is not in the 1st column
    if (is.null(info$value)) {
      return()
    }
  
    row = frames[info$row, ]
    showModal(plotModal(row$id, row$names))
  })

output$player_grid <- renderUI ({
      HTML(getHTML(frames))
  })
  
}

# Create Shiny app ----
shinyApp(ui, server,options=list(host="0.0.0.0", port=8015))

Solution

  • Here is a way:

    library(shiny)
    library(shinydashboard)
    
    js <- "
    $(document).ready(function(){
      $('body').on('click', '.grid-item span', function(){
        var name = $(this).data('name'),
            id = $(this).data('id');
        Shiny.setInputValue('cell', {name: name, id: id});
      });
    });
    "
    
    # generate html grid from data frame
    getHTML <- function (frames) {
      innerhtml = '<div class="grid-container">'
      for (row in 1:(nrow(frames))) {
        id <- frames[row, "ids"]
        name  <- frames[row, "names"]
        row_html = '<div class="grid-item">'
        cell <- sprintf("<span data-name='%s' data-id='%s'>Name: %s - id: %s</span>", 
                        name, id, name, id)
        row_html = paste(row_html, cell)
        row_html = paste(row_html, '</div>')
        innerhtml = paste(innerhtml, row_html)
      }
      paste(innerhtml, "</div>")
      return (innerhtml)
    }
    
    #show modal dialog for player id and name
    plotModal <- function(id, name) {
      modalDialog(
        p(paste("Player  # ", id, ", " , name,", was clicked")),
        title = paste("Player " , id),
        
        easyClose = TRUE
      )
    }
    
    ui <- dashboardPage(
      # Application title
      dashboardHeader(title = "Dashboard"),
      
      dashboardSidebar(
        h3("Filters")
      ),
      
      dashboardBody(
        tags$head(tags$style(HTML('
                                  .grid-container {
                                  display: grid;
                                  grid-template-columns: auto auto auto auto;
                                  }
                                  .grid-item {
                                  background-color: rgba(255, 255, 255, 0.8);
                                  border: 1px solid rgba(0, 0, 0, 0.8);
                                  padding: 20px;
                                  }')),
                  tags$script(HTML(js))),
        
        fluidRow(
          box(title="Render as Grid", column(width=12, uiOutput("player_grid")))
        )
      )
    )
    
    
    server <- function(input, output, session) {
      # data to be rendered
      frames = data.frame(
        names= c("james","kyle", "sally","hannah","jeff","kurt"), 
        ids=c(1:6)
      )
      
      #when a row in the table is clicked, show popup
      observeEvent(input$cell, {
        showModal(plotModal(input$cell$id, input$cell$name))
      })
      
      output$player_grid <- renderUI ({
        HTML(getHTML(frames))
      })
      
    }
    
    # Create Shiny app ----
    shinyApp(ui, server)