【问题标题】:Efficient way to modify selected points in onRender() of htmlWidgets在 htmlWidgets 的 onRender() 中修改选定点的有效方法
【发布时间】:2017-04-16 23:24:41
【问题描述】:

我正在制作一个交互式图形,它为 2 个定量变量(A 和 B)绘制黑点。用户可以选择阈值。这样做之后,仅绘制高于该阈值幅度的点,并且还绘制表示未通过阈值的区域的灰色矩形。这一切似乎都运行良好。

但是,我现在想要实现的是允许用户选择某些点。这样做时,选定的点应从黑色变为红色。当用户选择新的点子集时,先前选择的点会从红色变为黑色。换句话说,图中唯一显示为红色的点应该是最后选择的点。

不幸的是,这部分似乎不起作用。现在的问题是:

1) 所选点永久保持红色。 2)如果选择了已经是红色的点,有时未选择的黑色点会变成红色。您可以在帖子底部看到此错误的示例(在图片中)。

我相信这种情况正在发生,因为我只是绘制了与所选 x 和 y 值相对应的全新红点轨迹(这在下面的代码中注释为“// 以红色添加用户选择的点”)。因此,红点成为自己的实体。

我的问题是:什么是解决此问题的有效方法(使选定的点变为红色,直到选择另一个子集?)我使用术语高效,因为我正在处理大型数据集(100-1,000 点) ,所以我正在寻找节省计算速度的方法。简单地将选定的黑点修改为红色而不是完全重绘叠加的红点可能更有效?但是,我不确定如何使用 Plotly 跟踪语法来实现这一点。

任何帮助将不胜感激。

以下是我的脚本的简化版本。

library(plotly)
library(GGally)
library(htmlwidgets)

ui <- shinyUI(fluidPage(
  sliderInput("thresh", "Threshold:", min = 0, max = 3, value=1, step=1),
  plotlyOutput("myPlot"),
  textOutput("selectedValues")
))

server <- shinyServer(function(input, output) {
  thresh <- reactive(input$thresh)

  set.seed(1)
  dat <- data.frame(Row = paste0("Row",sample(c(1:20),20)), A=4*rnorm(20), B=4*rnorm(20))
  dat$Row <- as.character(dat$Row)

  minVal = min(dat[,-1])
  maxVal = max(dat[,-1])

  gg <- ggplot(data = dat, aes(x=A, y=B)) + coord_cartesian(xlim = c(minVal, maxVal), ylim = c(minVal, maxVal))

  ggY <- ggplotly(gg)

  output$myPlot <- renderPlotly(ggY %>% onRender("
    function(el, x, data) {
    var Points = [];
    var Traces = [];
    var selRows = [];

    data.dat.forEach(function(row){
    if(Math.abs(row['B']) > data.thresh) selRows.push(row);});

    var xArr = [];
    var yArr = [];
    var keepIndex = [];
    for (a=0; a<selRows.length; a++){
      xArr.push(selRows[a]['A'])
      yArr.push(selRows[a]['B'])
      keepIndex.push(selRows[a]['Row'])
    }
    Points.push(keepIndex);

    // Add points above the threshold in black
    var tracePoints = {
      x: xArr,
      y: yArr,
      hoverinfo: 'none',
      mode: 'markers',
      marker: {
        color: 'black',
        size: 4
      }
    };

    // Add upper horizontal line of gray box
    var hiLine = {
      x: [-15,15],
      y: [data.thresh,data.thresh],
      mode: 'lines',
      line: {
        color: 'gray',
        width: 1
      },
      opacity: 0.25,
      hoverinfo: 'none'
    };

    // Add lower horizontal line of gray box
    var lowLine = {
      x: [-15,15],
      y: [-1*data.thresh,-1*data.thresh],
      mode: 'lines',
      fill: 'tonexty',
      line: {
        color: 'gray',
        width: 1
      },
      opacity: 0.25,
      hoverinfo: 'none'
    };

    Traces.push(tracePoints);
    Traces.push(hiLine);
    Traces.push(lowLine);
    Plotly.addTraces(el.id, Traces);

    var idRows = []
    for (a=0; a<data.dat.length; a++){
      idRows.push(data.dat[a]['Row'])
    }

    el.on('plotly_selected', function(e) {
      numSel = e.points.length
      cN = e.points[0].curveNumber;

      var pointNumbers = [];
      var selData = [];
      for (a=0; a<numSel; a++){
        pointNumbers.push(e.points[a].pointNumber)
        selData.push(data.dat[idRows.indexOf(Points[0][pointNumbers[a]])])
      }
      Shiny.onInputChange('selData', selData);
      var Traces = [];
      var xArr = [];
      var yArr = [];
      for (a=0; a<selData.length; a++){
        xArr.push(selData[a]['A'])
        yArr.push(selData[a]['B'])
      }

      // Add user-selected points in red
      var traceRed = {
        x: xArr,
        y: yArr,
        mode: 'markers',
        marker: {
          color: 'red',
          size: 4
        },
        hoverinfo: 'none'
      };
      Traces.push(traceRed);

      Plotly.addTraces(el.id, Traces);
    })

    }", data = list(dat=dat, thresh=thresh())))

  selData <- reactive(input$selData)
  output$selectedValues <- renderPrint({selData()})

})

shinyApp(ui, server)

以下是错误示例:

假设,我以所有默认设置打开应用程序,我只是简单地选择右上角的四个点。结果看起来不错,因为只有这四个点是红色的,如下所示:

现在,假设我重新选择了前四点。结果看起来不再好,因为即使这四个点被涂成红色,另外两个点也被错误地涂成红色:

【问题讨论】:

    标签: javascript r shiny plotly htmlwidgets


    【解决方案1】:

    处理此问题的最简单方法是简单地跟踪您是否选择了某些内容,如果之前有选择,则删除包含所选点的先前跟踪。为了实现这一点,进行了以下更改:

    • 添加了一个计数器 nseltrace 以跟踪我们选择事物的频率
    • 添加了Plotly.deleteTraces 语句来删除最后一个跟踪(可以用索引-1 指定。
    • 删除操作由if (nseltrace&gt;0) 语句保护。
    • 重新格式化您的选择点以将它们放入数据框中(它们作为字符向量组合在一起)
    • 使用verbatumTextOutput 将它们打印出来,因为它是一种更简洁的格式。

    更新:

    • 添加了一个守卫,使其仅选择属于curveNumber 1 的点,即原始数据,而不是随后的选定轨迹。这会导致已选择的点在新选择的列表中输入两次。

    至于这是否是最有效的方式,显然取决于。您正在将选定的点绘制在先前未选定的表示上,最坏的情况是两倍,但点绘制速度非常快,并且一小部分点的典型减速可能甚至无法测量。除非后来证明是个问题,否则我不会对此进行优化,然后我会使用最新的 plotly,因为 plotly 代码库中目前正在进行大量优化(和错误删除)。

    代码如下:

    library(plotly)
    library(GGally)
    library(htmlwidgets)
    library(shiny)
    
    ui <- shinyUI(fluidPage(
      sliderInput("thresh", "Threshold:", min = 0, max = 3, value=1, step=1),
      plotlyOutput("myPlot"),
      verbatimTextOutput("selectedValues")
    ))
    
    server <- shinyServer(function(input, output) {
      thresh <- reactive(input$thresh)
    
      set.seed(1)
      dat <- data.frame(Row = paste0("Row",sample(c(1:20),20)), A=4*rnorm(20), B=4*rnorm(20))
      dat$Row <- as.character(dat$Row)
    
      minVal = min(dat[,-1])
      maxVal = max(dat[,-1])
    
      gg <- ggplot(data = dat, aes(x=A, y=B)) + coord_cartesian(xlim = c(minVal, maxVal), ylim = c(minVal, maxVal))
    
      ggY <- ggplotly(gg)
    
      output$myPlot <- renderPlotly(ggY %>% onRender("
                                                     function(el, x, data) {
                                                     var Points = [];
                                                     var Traces = [];
                                                     var selRows = [];
    
                                                     data.dat.forEach(function(row){
                                                     if(Math.abs(row['B']) > data.thresh) selRows.push(row);});
    
                                                     var xArr = [];
                                                     var yArr = [];
                                                     var keepIndex = [];
                                                     for (a=0; a<selRows.length; a++){
                                                     xArr.push(selRows[a]['A'])
                                                     yArr.push(selRows[a]['B'])
                                                     keepIndex.push(selRows[a]['Row'])
                                                     }
                                                     Points.push(keepIndex);
    
                                                     // Add points above the threshold in black
                                                     var tracePoints = {
                                                     x: xArr,
                                                     y: yArr,
                                                     hoverinfo: 'none',
                                                     mode: 'markers',
                                                     marker: {
                                                     color: 'black',
                                                     size: 4
                                                     }
                                                     };
    
                                                     // Add upper horizontal line of gray box
                                                     var hiLine = {
                                                     x: [-15,15],
                                                     y: [data.thresh,data.thresh],
                                                     mode: 'lines',
                                                     line: {
                                                     color: 'gray',
                                                     width: 1
                                                     },
                                                     opacity: 0.25,
                                                     hoverinfo: 'none'
                                                     };
    
                                                     // Add lower horizontal line of gray box
                                                     var lowLine = {
                                                     x: [-15,15],
                                                     y: [-1*data.thresh,-1*data.thresh],
                                                     mode: 'lines',
                                                     fill: 'tonexty',
                                                     line: {
                                                     color: 'gray',
                                                     width: 1
                                                     },
                                                     opacity: 0.25,
                                                     hoverinfo: 'none'
                                                     };
    
                                                     Traces.push(tracePoints);
                                                     Traces.push(hiLine);
                                                     Traces.push(lowLine);
                                                     Plotly.addTraces(el.id, Traces);
    
                                                     var idRows = []
                                                     for (a=0; a<data.dat.length; a++){
                                                     idRows.push(data.dat[a]['Row'])
                                                     }
    
                                                     var nseltrace = 0;
                                                     el.on('plotly_selected', function(e) {
                                                     console.log(e.points)
                                                     numSel = e.points.length
    
                                                     var pointNumbers = [];
                                                     var selData = [];
                                                     for (a=0; a<numSel; a++){
                                                       if (e.points[a].curveNumber==1){  // restrict to points in the original curve
                                                         pointNumbers.push(e.points[a].pointNumber)
                                                         selData.push(data.dat[idRows.indexOf(Points[0][pointNumbers[a]])])
                                                       }
                                                     }
                                                     Shiny.onInputChange('selData', selData);
                                                     var Traces = [];
                                                     var xArr = [];
                                                     var yArr = [];
                                                     for (a=0; a<selData.length; a++){
                                                     xArr.push(selData[a]['A'])
                                                     yArr.push(selData[a]['B'])
                                                     }
    
                                                     // Add user-selected points in red
                                                     var traceRed = {
                                                     x: xArr,
                                                     y: yArr,
                                                     mode: 'markers',
                                                     marker: {
                                                     color: 'red',
                                                     size: 4
                                                     },
                                                     hoverinfo: 'none'
                                                     };
    
                                                     if (nseltrace>0){
                                                     Plotly.deleteTraces(el.id,-1)
                                                     }
                                                     Traces.push(traceRed);
                                                     nseltrace = nseltrace+1
    
                                                     Plotly.addTraces(el.id, Traces);
                                                     })
    
                                                     }", data = list(dat=dat, thresh=thresh())))
    
      selData <- reactive({
        req(input$selData)
        rawc <- input$selData
        df <- data.frame(t(matrix(rawc,nrow=3)))
        names(df) <- names(rawc)[1:3]
        df
      })
      output$selectedValues <- renderPrint({selData()})
    
    })
    
    shinyApp(ui, server)
    

    这是一个屏幕截图:

    【讨论】:

    • 我非常感谢您提供的有用想法。不幸的是,我认为当用户的选择包含任何已经红点的子集时发生的错误仍然存​​在。我在帖子底部添加了一个示例(带有图像)。再次感谢。
    • 当我使用我在这里粘贴的代码时,我看到的不是。每次我选择以前的点都会消失。您是否使用了我发布的代码?
    • 哦,好的,我现在看到了这个问题。和以前不同了,重选有点不同。让我看看。
    • 谢谢。这个错误非常狡猾,它在你的方法和我的方法中都有。
    • 是的,您发布的代码确实解决了我遇到的第一个问题!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多