5

I am currently trying to create a "wall" of cards from a dataframe for a hobby Shiny app. Adapting from this SO answer, here is a minimal complete example of my code:

library(shiny)

df <- read.csv("https://github.com/intelligence-refinery/shiny_app/raw/master/flyer.csv")

card <- function(img, item, store) {
    HTML(
        paste0(
            '<div class="card">
              <div class="container">
              <h4><b>', item, '</b></h4>
              </div>
              <img src="', img, '" style="width:100%">
              <div class="container">
              <h5><i>', store, '</i></h5>
              </div>
              </div>'
        )
    )
}


ui <- fluidPage(
    tags$head(tags$style('.card {
                         width: 250px;
                       clear: both;
                       /* Add shadows to create the "card" effect */
                       box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
                       transition: 0.3s;
                       }
                       /* On mouse-over, add a deeper shadow */
                       .card:hover {
                       box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
                       }
                       /* Add some padding inside the card container */
                       .container {
                       width: 250px;
                       padding: 2px 16px;
                       }')),
    uiOutput("cards")
)

server <- function(input, output, session) {
    output$cards <- renderUI({
        # First make the cards
        args <-
            lapply(1:dim(df)[1], function(x)
                card(df[x, "clean_image_url"],
                     store = df[x, "store_name"],
                     item = df[x, "name"]))

        # Make sure to add other arguments to the list:
        args$cellArgs <- list(style = "
            width: 300px;
            height: auto;
            margin: 5px;
            ")

        # basically the same as flowLayout(cards[[1]], cards[[2]],...)
        do.call(shiny::flowLayout, args)

    })
}


# Preview the UI in the console
shinyApp(ui = ui, server = server)


However, this results in unsightly gaps between the cards, as they are different size:

gaps between cards

A masonry grid layout would be ideal in this situation:

masonry grid example

Since I'm very new to web work of any kind, I'm unsure as to how to apply the pure CSS/Javascript solutions to my Shiny app. I have tried to adapt the example from the Bootstrap docs, but when I pasted the HTML and CSS to CodePen, I am not getting a complete example...

The closest that I have found to an example of creating a masonry grid in a Shiny app is the (quite complex) conference Twitter dashboard. I found the relevant section in the code. However, as I am quite new to any web work, I can't adapt it to my own code.

Is there a way to create a masonry layout for a panel of cards generated this way? Any help would be greatly appreciated! :)

Mihai Chelaru
  • 5,844
  • 14
  • 34
  • 43
DMNO
  • 53
  • 4

1 Answers1

5

If you can divide your images into some meaninful number of columns, then before laying out your horizontal layout, you can wrap sets of images in column. Then arrange the columns with fluidRow.

Here is an example using just the first 20 rows from your data. Pasting the server only. The other change was to add df <- df[1:20,] after loading the data:

 server <- function(input, output, session) {
    output$cards <- renderUI({
        # First make the cards
        args <-
            lapply(1:dim(df)[1], function(x)
                card(df[x, "clean_image_url"],
                     store = df[x, "store_name"],
                     item = df[x, "name"]))

        # Make sure to add other arguments to the list:
        args$cellArgs <- list(style = "
            width: 300px;
            height: auto;
            margin: 5px;
            ")

        # make five columns with four images each
        cols <-
          lapply(seq(4, dim(df)[1], 4), function(x) {
              column(width = 2, verticalLayout(args[(x - 3):x], fluid = TRUE))
          })


        # then use fluidRow to arrange the columns 
        do.call(shiny::fluidRow, cols)

    })
}

You should get something like this:

enter image description here

teofil
  • 2,039
  • 1
  • 5
  • 15