0

In an attempt to make my multiplot app as 'free' as possible, I have so far build a nice way to build 1:n ggplots

  • auto scale them
  • auto arrange them
  • make them zoomable
  • make them clickable
  • give smartly positioned hover messages

Each plot also gets a few buttons above it in it's div to allow the user to: - save them - make plot n full screen - remove plot from the page

and functionality for the user to build their own new plots to add to the auto layout page

The next step I was trying out, was to incorporate shinyjqui functionality but there I ran into some (expected?) obstacles

To start with: the jqui_sortable wrapper in the app successfully allows the user to pick up sub plots with the mouse, but does not actually change the layout when the user drops it somewhere. The plots simply return to the normal order when the user drops the plot object. I followed the examples on the jqui page

The second problem, which may not be possible to overcome, is that click-hold of the mouse in the div, inside the plot OR outside of it has the same effect, it triggers the lifting up by jqui_sortable , thereby 'overruling the click-drag to zoom of the ggplot object. In an ideal scenario I would like to trigger the jqui_sortable when clicking outside the plot (but inside the div), and ggplot2's brush

partially working

require('shiny')
require('ggplot2')
require('shinyjqui')

ui <- pageWithSidebar(

  headerPanel("reorganize page"),
  sidebarPanel(width = 2,
               sliderInput(inputId = 'NrOfPlots', label = 'Nr of Plots', min = 1, max = 20, value = 1)
  ),
  mainPanel(

    uiOutput('FP1PlotMultiplot'),

    style = 'width:1250px'
      )
    )

server <- function(input, output, session) {
  ranges <- reactiveValues()

  observe({
  lapply(1:input$NrOfPlots, function(i) {
  output[[paste0('FP1Plot_', i)]] <- renderPlot({
    p <- ggplot(mtcars, aes(wt, mpg, color = as.factor(cyl))) + geom_point() +
      theme(legend.position = "none") +
      ggtitle(paste('plot', i))

    if(!is.null(ranges[[paste('FP1Plot', i, 'x', sep = '_')]]) & !is.null(ranges[[paste('FP1Plot', i, 'y', sep = '_')]])) {
      p <- p + coord_cartesian(xlim = ranges[[paste('FP1Plot', i, 'x', sep = '_')]], ylim = ranges[[paste('FP1Plot', i, 'y', sep = '_')]] )
    }

    p

})
  })
  })

  output$FP1PlotMultiplot<- renderUI({

    n <- input$NrOfPlots

    n_cols <- if(n == 1) {
      1
    } else if (n %in% c(2,4)) {
      2
    } else if (n %in% c(3,5,6,9)) {
      3
    } else {
      4
    }

    Pwidth <- 1000/n_cols
    Pheight <- 450/ceiling(n/n_cols) # calculate number of rows
    Pwidth2 <- Pwidth+40
    Pheight2 <- Pheight+80

    plot_output_list <- list()

    for(i in 1:input$NrOfPlots) {
      plot_output_list <- append(plot_output_list,list(
        div(id = paste0('div', 'FP1Plot_', i),
            wellPanel(
              plotOutput(paste0('FP1Plot_', i),
                         width = Pwidth,
                         height = Pheight,
                         dblclick =  paste('FP1Plot' , i, 'dblclick', sep = '_'),
                         brush = brushOpts(
                           id =  paste('FP1Plot', i, 'brush', sep = '_'),
                           resetOnNew = TRUE
                         )
              ),
              style = paste('border-color:#339fff; border-width:2px; background-color: #fff; width:',  Pwidth2, 'px; height:', Pheight2, 'px', sep = '')),
            style = paste('display: inline-block; margin: 2px; width:', Pwidth2, 'px; height:', Pheight2, 'px', sep = ''))

      ))
    }
    jqui_sortable(do.call(tagList, plot_output_list))

  })

    lapply(1:20, function(i) {

  observeEvent(input[[paste('FP1Plot', i, 'brush', sep = '_')]], {
    brush <- input[[paste('FP1Plot', i, 'brush', sep = '_')]]

    if (!is.null(brush)) {
      ranges[[paste('FP1Plot', i, 'x', sep = '_')]] <- c(brush$xmin, brush$xmax)
      ranges[[paste('FP1Plot', i, 'y', sep = '_')]] <- c(brush$ymin, brush$ymax)
    }
  })

  observeEvent(input[[paste('FP1Plot', i, 'dblclick', sep = '_')]], {

    ranges[[paste('FP1Plot', i, 'x', sep = '_')]] <- NULL
    ranges[[paste('FP1Plot', i, 'y', sep = '_')]] <- NULL

  })
    })

}

shinyApp(ui, server)
Mark
  • 2,789
  • 1
  • 26
  • 66

1 Answers1

1

The sortable elements must be in a div. Do:

jqui_sortable(do.call(function(...) div(id="allplots", ...), 
                      plot_output_list))
Stéphane Laurent
  • 75,186
  • 15
  • 119
  • 225
  • Ah, I thought I had that covered already with the div's wrapped in the `for` loop. Any idea if it's even possible to combine the sortable with ggplot's brush? – Mark Jul 22 '19 at 16:02