Как отобразить сюжетный график со скрытыми предустановленными трассами, т.е. «только для легенд» на основе списка - PullRequest
0 голосов
/ 23 февраля 2019

Благодаря помощи по предыдущему вопросу здесь Теперь я могу записать list, которые traces скрыты на графике plotly, считав список легенд TRUE/legendonly скусок javascript, который я использую, чтобы изменить записи списка, и цвет связанных кнопок.

То, что я сейчас тоже хочу сделать, - это сохранение этого TRUE/legendonly статуса при повторной визуализации графика.В приведенном ниже фиктивном приложении plot может быть перерисован с помощью переключателя actionbutton, что вызывает повторное render из-за смены цветов.

Другими словами: как отобразить график с определенными трассами, уже имеющими «легендарный статус» на основе values$tracesPlot1, который был изменен / записан в последний раз, когда пользователь просматривал этот конкретный график.

Я подозреваю, что это потребует некоторого document.getElementById("") подхода для получения значений $ tracesPlot1, а затем выполните противоположный сценарий, который уже существует, чтобы получить статус легенды из этого графика, и отправить его всюжет, с использованием того же onRender(js, data = "tracesPlot1")

ЗДЕСЬ: video вы можете увидеть, что когда пользователь возвращается к первой цветовой схеме, некоторыекнопки все еще выключены, но на графике, конечно, снова отображаются все следы, вместо того, чтобы отражать состояние кнопки.

ps: в моем приложении пользователь может переключать график между сгруппированными по 1 из 3 столбцам, вызывая повторный рендеринг, и я хотел бы загрузить его обратно с теми же элементами легенды, которые не были выбраны при рендеринге

library(plotly)
library(shiny)
library(htmlwidgets)

js <- c(
  "function(el, x, inputName){",
  "  var id = el.getAttribute('id');",
  "  var d3 = Plotly.d3;",
  "  el.on('plotly_restyle', function(evtData) {",
  "    var out = {};",
  "    d3.select('#' + id + ' g.legend').selectAll('.traces').each(function(){",
  "      var trace = d3.select(this)[0][0].__data__[0].trace;",
  "      out[trace.name] = trace.visible;",
  "    });",
  "    Shiny.setInputValue(inputName, out);",
  "  });",
  "}")

YNElement <-    function(idx){sprintf("YesNo_button-%d", idx)}

ui <- fluidPage(
  fluidRow(
    column(2,
           h5("Keep/Drop choices linked to colorscheme 1"),
           uiOutput('YNbuttons')

           ),
    column(8,
  plotlyOutput("plot1")
    ),
  column(2,
         h5('Switch grouping'),
         actionButton(inputId = 'Switch', label = icon('refresh'), style = "color: #f7ad6e;   background-color: white;  border-color: #f7ad6e;
                        height: 40px; width: 40px; border-radius: 6px;  border-width: 2px; text-align: center;  line-height: 50%; padding: 0px; display:block; margin: 2px")
         ), style = "margin-top:150px"
  ),
  verbatimTextOutput("tracesPlot1")
)

server <- function(input, output, session) {
  values <- reactiveValues(colors = T, NrOfTraces = length(unique(mtcars$cyl)))


  output$plot1 <- renderPlotly({
    if(values$colors) { colors <- c('red', 'blue', 'green') } else {colors <- c('black', 'orange', 'gray')}
    p1 <- plot_ly()
    p1 <-  add_trace(p1, data = mtcars, x = ~disp, y = ~mpg, type = 'scatter', mode = 'markers', color = ~as.factor(cyl), colors = colors)
    p1 <- layout(p1, title = 'mtcars group by cyl with switching colors')
    p1 %>% onRender(js, data = "tracesPlot1")   

  })


  observeEvent(input$Switch, { values$colors <- !values$colors    })


  observeEvent(values$NrOfTraces, { 
    values$dYNbs_cyl_el <- rep(T,values$NrOfTraces)
    names(values$dYNbs_cyl_el) <- sapply(1:values$NrOfTraces, function(x) {YNElement(x)})
  })

  output$YNbuttons <- renderUI({
    req(values$NrOfTraces)
    lapply(1:values$NrOfTraces, function(el) {
      YNb <- YNElement(el)
       if(values$dYNbs_cyl_el[[YNb]] == T ) {
        div(actionButton(inputId = YNb, label = icon("check"), style = "color: #339FFF;   background-color: white;  border-color: #339FFF;height: 34px; width: 34px; border-radius: 6px;  border-width: 2px; text-align: center;  line-height: 50%; padding: 0px; display:block; margin: 2px"))
      } else {
        div(actionButton(inputId = YNb, label = icon("times"), style = "color: #ff4d4d;   background-color: white;  border-color: #ff4d4d;height: 34px; width: 34px; border-radius: 6px;  border-width: 2px; text-align: center;  line-height: 50%; padding: 0px; display:block; margin: 2px"))
      }
     })
    })  

  observeEvent(input$tracesPlot1, {
    listTraces <- input$tracesPlot1
    #values$tracesPlot1 <- input$tracesPlot1
    listTracesTF <- gsub('legendonly', FALSE, listTraces)
    lapply(1:values$NrOfTraces, function(el) {
      if(el <= length(listTracesTF)) {
        YNb <- YNElement(el)
        if(values$dYNbs_cyl_el[[YNb]] != listTracesTF[el]) {
          values$dYNbs_cyl_el[[YNb]] <- listTracesTF[el]
        }
      }
    })
  })

  output$tracesPlot1 <- renderPrint({ unlist(input$tracesPlot1)  })
}
shinyApp(ui, server)

Ответы [ 2 ]

0 голосов
/ 24 февраля 2019

@ Stephane,

Я разобрался, как заставить это работать.Важно, чтобы код из вашего ответа был помещен над p1 %>% onRender(js, data = "tracesPlot1"), иначе мы потеряем javascript.

В приведенном ниже примере я сделал несколько дополнений, так что нажатие на три кнопки теперь активирует скрытие ... К сожалению, это означает, что график должен будет полностью повторно визуализироваться, что в моих трехмерных точечных диаграммах с 5000 точками данных и1-50 следов займут несколько секунд.Единственный способ обойти это, если мы можем выполнить манипуляции с p1$x$data[[i]]$visible по javascript, которые изменяют виджет и не запускают блестящий сервер ....... Есть мысли?Я мог бы открыть новый элемент для этого преобразования из текущего решения в более быстрый подход javascript

В приложении ниже: при нажатии на легенду изменяется input$tracePlot1, который я манипулирую в список T / F вместо «ИСТИНА»/ "legendonly" и обновите values$dYNbs_cyl_el, используя его там, где это необходимо

нажатие на сами кнопки также изменяет values$dYNbs_cyl_el элементы

и observeEvent при просмотре values$dYNbs_cyl_el клонирует этот список, изменил T/ F снова в «TRUE» / «legenonly», чтобы он соответствовал вводу статуса легенды, и назвал список с помощью sort(unique(mtcars$cyl)), а затем преобразовал этот объект в values$legenditems

, если график показывает цветверсия 1 ', то есть суррогат для моего приложения, где я изменяю столбец, по которому я группирую данные в кривые, затем график использует values$legenditems для изменения статуса элементов легенды.

Это дает хорошее трехэлементное взаимодействие двумя способами.Легенда изменяет сюжет, а кнопки изменяют сюжет и легенду, а сюжет " запоминает ", кто был показан, а кто нет.

library(plotly)
library(shiny)
library(htmlwidgets)

js <- c(
  "function(el, x, inputName){",
  "  var id = el.getAttribute('id');",
  "  var d3 = Plotly.d3;",
  "  el.on('plotly_restyle', function(evtData) {",
  "    var out = {};",
  "    d3.select('#' + id + ' g.legend').selectAll('.traces').each(function(){",
  "      var trace = d3.select(this)[0][0].__data__[0].trace;",
  "      out[trace.name] = trace.visible;",
  "    });",
  "    Shiny.setInputValue(inputName, out);",
  "  });",
  "}")

YNElement <-    function(idx){sprintf("YesNo_button-%d", idx)}

ui <- fluidPage(
  fluidRow(
    column(2,
           h5("Keep/Drop choices linked to colorscheme 1"),
           uiOutput('YNbuttons')
    ),
    column(8,
           plotlyOutput("plot1")
    ),
    column(2,
           h5('Switch grouping'),
           actionButton(inputId = 'Switch', label = icon('refresh'), style = "color: #f7ad6e;   background-color: white;  border-color: #f7ad6e;
                        height: 40px; width: 40px; border-radius: 6px;  border-width: 2px; text-align: center;  line-height: 50%; padding: 0px; display:block; margin: 2px")
           ), style = "margin-top:150px"
    ),
  verbatimTextOutput("tracesPlot1"),
  verbatimTextOutput("tracesPlot2")

  )

server <- function(input, output, session) {
  values <- reactiveValues(colors = T, NrOfTraces = length(unique(mtcars$cyl)))


  output$plot1 <- renderPlotly({
    if(values$colors) { colors <- c('red', 'blue', 'green') } else {colors <- c('black', 'orange', 'gray')}
    p1 <- plot_ly()
    p1 <-  add_trace(p1, data = mtcars, x = ~disp, y = ~mpg, type = 'scatter', mode = 'markers', color = ~as.factor(cyl), colors = colors)
    p1 <- layout(p1, title = 'mtcars group by cyl with switching colors')
    p1 <- plotly_build(p1)

    if(values$colors) { for(i in seq_along(p1$x$data)){
      p1$x$data[[i]]$visible <- values$legenditems[[p1$x$data[[i]]$name]]}
    }
     p1 %>% onRender(js, data = "tracesPlot1")
  })


  observeEvent(input$Switch, { values$colors <- !values$colors    })

    observeEvent(values$dYNbs_cyl_el, {
      legenditems <- values$dYNbs_cyl_el
      legenditems[which(legenditems == FALSE)] <- 'legendonly'
      legenditems[which(legenditems == TRUE )] <- 'TRUE'
      names(legenditems) <- sort(unique(mtcars$cyl))
      values$legenditems <- as.list(legenditems)
    })


  observeEvent(values$NrOfTraces, { 
    values$dYNbs_cyl_el <- rep(T,values$NrOfTraces)
    names(values$dYNbs_cyl_el) <- sapply(1:values$NrOfTraces, function(x) {YNElement(x)})
  })

  output$YNbuttons <- renderUI({
    req(values$NrOfTraces)
    lapply(1:values$NrOfTraces, function(el) {
      YNb <- YNElement(el)
      if(values$dYNbs_cyl_el[[YNb]] == T ) {
        div(actionButton(inputId = YNb, label = icon("check"), style = "color: #339FFF;   background-color: white;  border-color: #339FFF;height: 34px; width: 34px; border-radius: 6px;  border-width: 2px; text-align: center;  line-height: 50%; padding: 0px; display:block; margin: 2px"))
      } else {
        div(actionButton(inputId = YNb, label = icon("times"), style = "color: #ff4d4d;   background-color: white;  border-color: #ff4d4d;height: 34px; width: 34px; border-radius: 6px;  border-width: 2px; text-align: center;  line-height: 50%; padding: 0px; display:block; margin: 2px"))
      }
    })
  })  

  flipYNb_FP1 <- function(idx){
    YNb <- YNElement(idx)
    values$dYNbs_cyl_el[[YNb]] <- !values$dYNbs_cyl_el[[YNb]]
  }

  observe({
    lapply(1:values$NrOfTraces, function(ob) {
      YNElement <- YNElement(ob)
      observeEvent(input[[YNElement]], {
        flipYNb_FP1(ob)
      }, ignoreInit = T)
    })
  })

  observeEvent(input$tracesPlot1, {
    listTraces <- input$tracesPlot1
    listTracesTF <- gsub('legendonly', FALSE, listTraces)
    listTracesTF <- as.logical(listTracesTF)
    lapply(1:values$NrOfTraces, function(el) {
      if(el <= length(listTracesTF)) {
        YNb <- YNElement(el)
        if(values$dYNbs_cyl_el[[YNb]] != listTracesTF[el]) {
          values$dYNbs_cyl_el[[YNb]] <- listTracesTF[el]
        }
      }
    })
  })

  output$tracesPlot1 <- renderPrint({ unlist(input$tracesPlot1)  })
  output$tracesPlot2 <- renderPrint({ unlist(values$legenditems)  })


}
shinyApp(ui, server)
0 голосов
/ 23 февраля 2019

Вы можете установить свойство visible трасс:

library(plotly)

legendItems <- list("4" = TRUE, "6" = "legendonly", "8" = TRUE)

p <- plot_ly() %>%
  add_trace(p1, data = mtcars, x = ~disp, y = ~mpg, type = 'scatter', mode = 'markers', color = ~as.factor(cyl))
p <- plotly_build(p)

for(i in seq_along(p$x$data)){
  p$x$data[[i]]$visible <- legendItems[[p$x$data[[i]]$name]]
}

p

enter image description here

...