5

I would like click-select points and group them based on color.

I can save selected points with color information into a new data frame and plot it, however I would like to keep track and see what was already selected on the interactive plot.

How can I show/label already selected points or make it permanent after "Add selection"?

library(shiny)
library(tidyverse)
library(DT)
library(colourpicker)

ui = fluidPage(
    colourInput("col", "Select colour", "purple"),
    actionButton("addToDT", "Add selection", icon = icon("plus")), hr(),
    plotOutput("plot", click = "plot_click", dblclick = "plot_reset"),
    DT::dataTableOutput('plot_DT'), hr(),
    textOutput("clickcoord"),
    DT::dataTableOutput('final_DT'),
    plotOutput("plotSelected")
)

server = function(input, output, session) {
    
    selectedPoint = reactiveVal(rep(FALSE, nrow(mtcars)))
    
    output$clickcoord <- renderPrint({
        print(input$plot_click)
    })
    
    observeEvent(input$plot_click, {
        clicked = nearPoints(mtcars, input$plot_click, allRows = TRUE)$selected_
        selectedPoint(clicked | selectedPoint())
    })
    
    observeEvent(input$plot_reset, {
        selectedPoint(rep(FALSE, nrow(mtcars)))
    })
    
    output$plot_DT = DT::renderDataTable({
        mtcars$sel = selectedPoint()
        mtcars = dplyr::filter(mtcars, sel == TRUE) %>% mutate(group_color = input$col)
    })
    
    final_DT = reactiveValues()
    final_DT$df = data.frame()
    
    FinalData = eventReactive(input$addToDT, {
        mtcars$sel = selectedPoint()
        mtcars = dplyr::filter(mtcars, sel == TRUE) %>% mutate(group_color = input$col)
        final_DT$df = bind_rows(final_DT$df, mtcars)
    })
    
    output$final_DT = renderDataTable({FinalData()})
    
    output$plot = renderPlot({
        mtcars$sel = selectedPoint()
        ggplot(mtcars, aes(wt, mpg, color =  mtcars$sel, fill=mpg)) +
            geom_point(shape = 21, size = 6, stroke = 2) + 
            scale_color_manual(values = c("#ffffff00", input$col)) + 
            scale_fill_viridis_c() + 
            theme_bw()
    })
    
    output$plotSelected = renderPlot({
        sel_df = FinalData()
        ggplot(sel_df, aes(wt, mpg, fill = group_color, colour = group_color)) +
            geom_point(shape = 21, size = 6, stroke = 2) + 
            scale_color_manual(values = unique(sel_df$group_color)) + 
            scale_fill_manual(values = unique(sel_df$group_color)) + 
            theme_bw()
    })
    
    observeEvent(input$addToDT, {
        selectedPoint(rep(FALSE, nrow(mtcars)))
    })
}

shinyApp(ui, server)
Hixon
  • 77
  • 7
  • 1
    Your app seems to work nicely, I am not sure what you are looking for. Are you looking for the user to select a color and then the app would find the observations close to that color? or something else? – DashdotdotDashdotdot Dec 16 '22 at 15:22
  • I would like to enable user to classify points based on selected color; then use this classified data frame for further analysis. For example: 1) choose red, select few points, then "Add to selection" (which adds to table and deselect points); then 2) Blue and do the same thing etc. Also, I would like to indicate on the first plot, what was already added and selected, while being able to select new points. – Hixon Dec 16 '22 at 15:37
  • 1
    I think you could do this by creating a reactive matrix with maybe 20 columns and the number of rows equal to the number of observations in the scatter plot. Initialize the matrix with all cells equal to all false whenever "Add selection" is clicked, or more generally populate the first column that is not all False, with whatever has been selected this round, and then use the matrix to graph what has been selected according to a chosen color scheme, with a different color for each column. Would this work? Would 20 columns be enough? – DashdotdotDashdotdot Dec 16 '22 at 16:53
  • I think that can work; 20 columns should be enough. So, load that reactive matrix too in the renderPlot part and make a new layer on top of the original? – Hixon Dec 16 '22 at 17:57

1 Answers1

1

I think this is the "crux" of what your are looking for. I used a very similar example that I found in the help for entitled:

A demonstration of clicking, hovering, and brushing

(https://shiny.rstudio.com/reference/shiny/0.13.1/plotoutput)

It is very similar to your example.

I create a matrix of T/F elements where the rows are the observations and the columns are in which batch the observation is selected. So when you launch the whole matrix is False, but as you click on observations the switch to positive in the first column. Then if you click addSelection and continue you start switching the observations in the next column. Could you confirm that this what you are looking for? Below is the code.

shinyApp(
  ui = basicPage(
    fluidRow(
      column(
        width = 4,
        plotOutput("plot",
                   height = 300,
                   click = "plot_click", # Equiv, to click=clickOpts(id='plot_click')
        ),
        actionButton("addToDT", "Add selection", icon = icon("plus")), hr(),
        h4("Clicked points"),
        tableOutput("plot_clickedpoints"),
      ),
      column(
        width = 4,
        verbatimTextOutput("counter"),
      ),
    )
  ),
  server = function(input, output, session) {
    data <- reactive({
      input$newplot
      # Add a little noise to the cars data so the points move
      cars + rnorm(nrow(cars))
    })
    output$plot <- renderPlot({
      d <- data()
      plot(d$speed, d$dist, main = paste("No of Sets Chosen", input$addToDT))
    })
    output$plot_clickinfo <- renderPrint({
      cat("Click:
")
      str(input$plot_click)
    })
    
    selectedPoints <- reactiveVal(rep(FALSE, nrow(cars)))
    selectionMatrix <- reactiveVal(matrix(data = F, nrow = nrow(cars), ncol = 7))
    
    observeEvent(input$plot_click, {
      clicked <- nearPoints(data(), input$plot_click, "speed", "dist", allRows = TRUE)$selected
      selectedPoints(clicked | selectedPoints())
      tmp <- unlist(selectionMatrix())
      tmp[, (input$addToDT + 1)] <- selectedPoints()
      selectionMatrix(tmp)
    })
    observeEvent(input$addToDT, {
      selectedPoints(rep(FALSE, nrow(cars)))
    })
    output$plot_clickedpoints <- renderTable({
      #  if (input$addToDT==0) {
      res <- selectionMatrix()
      return(res)
    })
  }
)
  • Thank you! Yes this should work, i'll need to read and learn more. I need a few days then try to update and apply to the app. – Hixon Dec 18 '22 at 02:46