jrowen / rhandsontable

A htmlwidgets implementation of Handsontable.js
http://jrowen.github.io/rhandsontable/
Other
380 stars 147 forks source link

updating values in rhandsontable from dragged points on plotly chart - or more generally from values on server #410

Closed nevilamos closed 1 year ago

nevilamos commented 2 years ago

I am creating an shinyapp to allow users to change the shape of a curve by either editing xy values in a table, which are then plotted in plotly line chart, or alternatively, by dragging markers on the chart - in this case I would like the change in xy coordinates of the markers on the chart to be updated in the table. So the rhandsontable changes the chart and the chart changes the rhadsontable

The code for getting the xy markervalues and redrawing the spline come from https://github.com/plotly/plotly.R/blob/master/inst/examples/shiny/drag_markers/app.R

here is the example - only working from table to chart not chart to table:

library(shiny)
library(dplyr)
library(rhandsontable)
library(purrr)
library(plotly)
maxX = 100
n = maxX + 1
startDF <- data.frame(X = c(0, 20, 30, maxX), Y = c(1, 2, 3, 4))

ui <- fluidPage(
  column(
    3,
    h4("Editable handsontable"),
    rHandsontableOutput('table'),
    br(),
    h4("Values to update in handsontable from moving blue dots on figure"),
    textOutput("newPoint"),
    h4("so the values in table above become:"),
    tableOutput("outTab"),
    tableOutput('table1')
  ),
  column(
    6,
    h4("Click and drag blue markers to change the curve"),
    plotlyOutput("p")
  )
)

server <- function(input, output, session) {
  rv <- reactiveValues()
  rv$tab <- startDF

  #editable handsontable  with QC only updates with correct values
  df <- eventReactive(input$table ,  {
    if (is.null(input$table))  {
      df <- rv$tab
      dfOld <<- df
    } else {
      df <- hot_to_r(input$table)
      #  Quality control
      # Rule 1: maintain X values in correct order with extreme values as 0 and maxX
      ifelse(
        df$X[1] != 0 |
        df$X[1] >= df$X[2] |
        df$X[2] >= df$X[3] |
        df$X[3] >= df$X[4] |
        df$X[4] != maxX,
        df$X <- dfOld$X,
        df$X <- df$X
      )
    }
    dfOld <- df
    df
  },
  ignoreNULL = F)

  output$table <- renderRHandsontable({
    rhandsontable(df()) %>%
      #hot_col("Parameter", readOnly = TRUE)%>%
      hot_validate_numeric(
        col = 'X',
        min = 0,
        max = maxX,
        allowInvalid = FALSE
      ) %>%
      hot_validate_numeric(col = 'Y',
                           min = 0,
                           allowInvalid = FALSE)
  })

  observe(rv$tab <- df())

  observeEvent(rv$tab$X |
                 rv$tab$Y , {
                   rv$mySpline <- as.data.frame(spline(
                     rv$tab$X,
                     rv$tab$Y,
                     xmin = 0,
                     xmax = maxX,
                     n = n
                   )) %>%
                     mutate(y = ifelse(y < 0, 0, y))

                   maxY <- 1.5 * (max(c(rv$mySpline$y)))
                   mySpline <- rv$mySpline
                   circles <- map2(
                     rv$tab$X,
                     rv$tab$Y,
                     ~ list(
                       type = "circle",
                       xanchor = .x,
                       yanchor = .y,
                       # give each circle a 2 pixel diameter
                       x0 = -4,
                       x1 = 4,
                       y0 = -4,
                       y1 = 4,
                       xsizemode = "pixel",
                       ysizemode = "pixel",
                       # other visual properties
                       fillcolor = "blue",
                       line = list(color = "blue")
                     )
                   )

                   # # # plot the shapes and fitted line
                   p = plot_ly() %>%
                     add_lines(
                       x = ~ rv$mySpline$x,
                       y = ~ rv$mySpline$y,
                       name = "Response curve",
                       line = list(color = "black")
                     ) %>%
                     layout(
                       shapes = c(circles),
                       xaxis = list(range = c(0, maxX), fixedrange = TRUE),
                       yaxis = list(range = c(0, maxY))
                     ) %>%
                     config(edits = list(shapePosition = TRUE))

                   output$p <- renderPlotly(p)

                 })

  observe({
    ed <- event_data("plotly_relayout")
    shape_anchors <- ed[grepl("^shapes.*anchor$", names(ed))]
    if (length(shape_anchors) != 2) {
      return()
    }
    row_index <-
      unique(readr::parse_number(names(shape_anchors)) + 1)
    pts <- as.numeric(shape_anchors)

    rv$tab[row_index, 1:2] <- pts
    print(rv$tab)
    output$outTab <- renderTable(rv$tab)
    output$newPoint <-
      renderText(paste("Altered point: row =", row_index, "X =", pts[1], "Y=", pts[2]))
  })
}

shinyApp(ui, server)
Estateira commented 1 year ago

I made a few changes, now it works that when the points on the charts are dragged, the rhandsontable also gets updated with the new values (the same values which were previously only shown in output$outTab)

library(shiny)
library(dplyr)
library(rhandsontable)
library(purrr)
library(plotly)
maxX = 100
n = maxX + 1
startDF <- data.frame(X = c(0, 20, 30, maxX), Y = c(1, 2, 3, 4))

ui <- fluidPage(
  column(
    3,
    h4("Editable handsontable"),
    rHandsontableOutput('table'),
    br(),
    h4("Values to update in handsontable from moving blue dots on figure"),
    textOutput("newPoint"),
    h4("Now we can see that the tables are equal"),
    tableOutput("outTab")
  ),
  column(
    6,
    h4("Click and drag blue markers to change the curve"),
    plotlyOutput("p")
  )
)

server <- function(input, output, session) {
  rv <- reactiveValues()
  rv$tab <- startDF

  #editable handsontable  with QC only updates with correct values
  df <- eventReactive(input$table ,  {
    if (is.null(input$table))  {
      df <- rv$tab
      dfOld <<- df
    } else {
      df <- hot_to_r(input$table)
      #  Quality control
      # Rule 1: maintain X values in correct order with extreme values as 0 and maxX
      ifelse(
        df$X[1] != 0 |
          df$X[1] >= df$X[2] |
          df$X[2] >= df$X[3] |
          df$X[3] >= df$X[4] |
          df$X[4] != maxX,
        df$X <- dfOld$X,
        df$X <- df$X
      )
    }
    dfOld <- df
    df
  },
  ignoreNULL = F)

  output$table <- renderRHandsontable({
    rhandsontable(df()) %>%
      #hot_col("Parameter", readOnly = TRUE)%>%
      hot_validate_numeric(
        col = 'X',
        min = 0,
        max = maxX,
        allowInvalid = FALSE
      ) %>%
      hot_validate_numeric(col = 'Y',
                           min = 0,
                           allowInvalid = FALSE)
  })

  observe(rv$tab <- df())

  observeEvent(rv$tab$X |
                 rv$tab$Y , {
                   rv$mySpline <- as.data.frame(spline(
                     rv$tab$X,
                     rv$tab$Y,
                     xmin = 0,
                     xmax = maxX,
                     n = n
                   )) %>%
                     mutate(y = ifelse(y < 0, 0, y))

                   maxY <- 1.5 * (max(c(rv$mySpline$y)))
                   mySpline <- rv$mySpline
                   circles <- map2(
                     rv$tab$X,
                     rv$tab$Y,
                     ~ list(
                       type = "circle",
                       xanchor = .x,
                       yanchor = .y,
                       # give each circle a 2 pixel diameter
                       x0 = -4,
                       x1 = 4,
                       y0 = -4,
                       y1 = 4,
                       xsizemode = "pixel",
                       ysizemode = "pixel",
                       # other visual properties
                       fillcolor = "blue",
                       line = list(color = "blue")
                     )
                   )

                   # # # plot the shapes and fitted line
                   p = plot_ly() %>%
                     add_lines(
                       x = ~ rv$mySpline$x,
                       y = ~ rv$mySpline$y,
                       name = "Response curve",
                       line = list(color = "black")
                     ) %>%
                     layout(
                       shapes = c(circles),
                       xaxis = list(range = c(0, maxX), fixedrange = TRUE),
                       yaxis = list(range = c(0, maxY))
                     ) %>%
                     config(edits = list(shapePosition = TRUE))

                   output$p <- renderPlotly(p)

                 })

  observe({
    ed <- event_data("plotly_relayout")
    shape_anchors <- ed[grepl("^shapes.*anchor$", names(ed))]
    if (length(shape_anchors) != 2) {
      return()
    }
    row_index <-
      unique(readr::parse_number(names(shape_anchors)) + 1)
    pts <- as.numeric(shape_anchors)

    rv$tab[row_index, 1:2] <- pts
    print(rv$tab)
    output$table <-  renderRHandsontable( rhandsontable((rv$tab)))
    output$outTab <- renderTable(rv$tab)
    output$newPoint <-
      renderText(paste("Altered point: row =", row_index, "X =", pts[1], "Y=", pts[2]))
  })
}

shinyApp(ui, server)
nevilamos commented 1 year ago

Thanks for the suggestions working well now.