2015-09-08 3 views
5

Meine Shiny App verfügt über mehrere Eingänge, die zur Definition mehrerer Parameter eines generierten Plots verwendet werden. Es ist sehr wahrscheinlich, dass der Benutzer einige Minuten damit verbringen wird, alle möglichen Optionen durchzugehen, bis er mit der Ausgabe zufrieden ist. Offensichtlich kann das Plot in verschiedenen Formaten exportiert werden, aber es ist möglich, dass der Benutzer später dasselbe Plot mit anderen Daten neu erstellen oder vielleicht nur ein kleines Detail ändern möchte.Alle Benutzereingaben in einer Shiny App in Datei exportieren und später laden

Aus diesem Grund muss ich dem Benutzer eine Möglichkeit bieten, alle seine Einstellungen zu exportieren und diese Datei für die spätere Verwendung zu behalten. Ich habe einen Ansatz entwickelt, aber es funktioniert nicht gut. Ich verwende reactiveValuesToList, um die Namen aller Eingabeelemente zu erhalten und als einfache Textdatei im Format inputname=inputvalue zu speichern. Dies ist die downloadHandler auf server.R:

output$bt_export <- downloadHandler(
    filename = function() { 
    "export.txt" 
    }, 
    content = function(file) { 
    inputsList <- names(reactiveValuesToList(input)) 
    exportVars <- paste0(inputsList, "=", sapply(inputsList, function(inpt) input[[inpt]])) 
    write(exportVars, file) 
    }) 

Dies funktioniert gut, aber das Laden nicht sehr glatt gehen. Da ich den Eingabetyp nicht speichern möchte (und nicht herausfinden konnte, wie), muss ich die Werte blind aktualisieren. Dies ist, wie ich es tun:

importFile <- reactive({  
    inFile <- input$fileImport  
    if (is.null(inFile)) 
    return(NULL)  
    lines <- readLines(inFile$datapath) 
    out <- lapply(lines, function(l) unlist(strsplit(l, "=")))  
    return(out) 
}) 

observe({ 
    imp <- importFile()    
    for (inpt in imp) { 
     if (substr(inpt[2], 0, 1) == "#") { 
     shinyjs::updateColourInput(session, inputId = inpt[1], value = inpt[2]) 
     } else { 
     try({ 
      updateTextInput(session, inputId = inpt[1], value = inpt[2]) 
      updateNumericInput(session, inputId = inpt[1], value = inpt[2]) 
      updateSelectInput(session, inputId = inpt[1], selected = inpt[2])    
     }) 
     } 
    }  
    }) 

Neben den shinyjs::colorInput, die von dem # Start erkannt werden kann, ich habe try() für die andere zu verwenden. Dies funktioniert teilweise, aber einige Eingaben werden nicht aktualisiert. Wenn Sie die exportierte Datei manuell überprüfen, werden die Eingaben angezeigt, die nicht aktualisiert wurden. Daher ist es wahrscheinlich keine gute Idee, 100 + Eingänge gleichzeitig zu aktualisieren. Auch der Teil sieht nicht gut aus und ist wahrscheinlich keine gute Idee.

Die App ist fast fertig, wird aber wahrscheinlich in der Zukunft aktualisiert werden, einige Eingaben hinzugefügt/geändert haben. Es ist akzeptabel, wenn dies sogar einige "alte" exportierte Eingaben ungültig macht, da ich versuchen werde, die Rückwärtskompatibilität beizubehalten. Aber ich suche nach einem Ansatz, der nicht nur Hunderte von Zeilen schreibt, um die Eingaben einzeln zu aktualisieren.

Ich habe über die Verwendung von save.image() gedacht, aber einfach load() verwendet nicht die App-Eingaben. Ich habe auch überlegt, irgendwie alle Eingaben auf einmal zu aktualisieren, anstatt nacheinander, aber nichts zu finden. Gibt es eine bessere Möglichkeit, alle Benutzereingaben in eine Datei zu exportieren und dann alle zu laden? Es spielt keine Rolle, ob es ein Tweak zu diesem ist, der besser funktioniert oder ein völlig anderer Ansatz.

+0

Hat Ihre App stark abhängige Eingaben? Ist es eine Menge von 'renderUI' Blöcken oder ist es eine Menge von 'numericInput' -schennen Eingaben? – Mark

+0

@Mark Sie sind alle "statische" Eingaben, keiner davon wird mit 'renderUI' erstellt. – Molx

+0

@Molx Wenn Sie keine Lösung finden, mit der Sie zufrieden sind, kontaktieren Sie mich bitte. Wenn eine der Lösungen hier funktioniert, dann toll :) (schön zu sehen, dass mein colorInput verwendet wird!) –

Antwort

9

Wenn Sie den Code der glänzenden Eingabeaktualisierungsfunktionen betrachten, enden sie mit session$sendInputMessage(inputId, message). message ist eine Liste von Attributen, die für die Ex in der Eingabe, die geändert werden müssen, für eine Checkbox Eingabe: message <- dropNulls(list(label = label, value = value))

Da die meisten der Eingabe des value Attribut haben, können Sie einfach direkt auf alle von ihnen session$sendInputMessage Funktion ohne die try.

Hier ist ein Beispiel, ich dummy_data erstellt alle Eingänge zu aktualisieren, wenn Sie auf die Schaltfläche klicken, sollte die Struktur ähnlich zu dem, was Sie exportieren:

ui.R

library(shiny) 
shinyUI(fluidPage(
    textInput("control_label", 
      "This controls some of the labels:", 
      "LABEL TEXT"), 
    numericInput("inNumber", "Number input:", 
       min = 1, max = 20, value = 5, step = 0.5), 
    radioButtons("inRadio", "Radio buttons:", 
       c("label 1" = "option1", 
       "label 2" = "option2", 
       "label 3" = "option3")), 
    actionButton("update_data", "Update") 

)) 

server.R

library(shiny) 

dummy_data <- c("inRadio=option2","inNumber=10","control_label=Updated TEXT") 

shinyServer(function(input, output,session) { 
    observeEvent(input$update_data,{  
    out <- lapply(dummy_data, function(l) unlist(strsplit(l, "="))) 
    for (inpt in out) { 
    session$sendInputMessage(inpt[1], list(value=inpt[2])) 
    } 
    }) 

}) 

Alle update Funktionen formatieren auch den Wert vor dem Aufruf session$sendInputMessage. Ich habe nicht alle möglichen Eingaben ausprobiert, aber zumindest für diese 3 können Sie eine Zeichenfolge an die Funktion übergeben, um die numericInput zu ändern, und es funktioniert immer noch gut.

Wenn dies ein Problem für einige Ihrer Eingaben ist, können Sie reactiveValuesToList(input) mit save speichern, und wenn Sie Ihre Eingaben aktualisieren möchten, verwenden Sie load und die Liste in der for Schleife ausgeführt (Sie werden anpassen müssen es zu einer benannten Liste).

+0

Obwohl dies eine ziemlich einfache Änderung war, war es sehr nützlich, das hässliche 'try()' und gute Kenntnisse zu entfernen. Letztendlich half es mir, das größte Problem des Ansatzes zu finden, nämlich dass einige unerwünschte und unerwartete reaktive Werte exportiert wurden und so etwas wie ein Injektionsangriff auf die Ladefunktion ausgeführt wurde. – Molx

1

Es sei denn, Sie eine Menge von hochflexiblen Typ Eingänge (renderUI Blöcke, die jede Art von Eingabe sein könnte) tun, dann können Sie eine Liste erstellen, um alle aktuellen Werte zu speichern, verwenden dput sie in eine Datei zu speichern, mit einem entsprechenden dget um es einzulesen.

In einer App, die ich habe, erlaube ich Benutzern, eine Datei herunterzuladen, die alle ihre hochgeladenen Daten plus all ihre Optionen speichert.

output$saveData <- downloadHandler(
    filename = function() { 
    paste0('Export_',Sys.Date(),'.sprout') 
    }, 
    content = function(file) { 
    dataToExport = list() 
    #User specified options 
    dataToExport$sproutData$transformations=sproutData$transformations #user specified transformations 
    dataToExport$sproutData$processing=sproutData$processing #user specified text processing rules 
    dataToExport$sproutData$sc=sproutData$sc #user specified option to spell check 
    dataToExport$sproutData$scOptions=sproutData$scOptions #user specified spell check options (only used if spell check is turned on) 
    dataToExport$sproutData$scLength=sproutData$scLength #user specified min word lenght for spell check (only used if spell check is turned on) 
    dataToExport$sproutData$stopwords=sproutData$stopwords #user specified stopwords 
    dataToExport$sproutData$stopwordsLastChoice=sproutData$stopwordsLastChoice #last pre-built list selected 
    dput(dataToExport,file=file) 
    } 
) 

Hier mache ich eine leere Liste, dann halte ich die Werte fest, die ich in meiner App verwende. Der Grund für die dTE$sD$name Struktur ist, dass ich eine reactiveValues namens sproutData habe, die alle vom Benutzer ausgewählten Optionen und Daten speichert. Also, ich behalte die Struktur in der Ausgabe.

Dann habe ich eine Last Datenseite die Folgendes bewirkt:

output$loadStatusIndicator = renderUI({ 
    worked = T 
    a = tryCatch(dget(input$loadSavedData$datapath),error=function(x){worked<<-F}) 
    if(worked){ 
    #User specified options 
    a$sproutData$transformations->sproutData$transformations #user specified transformations 
    a$sproutData$processing->sproutData$processing #user specified text processing rules 
    updateCheckboxGroupInput(session,"processingOptions",selected=sproutData$processing) 
    a$sproutData$sc->sproutData$sc #user specified option to spell check 
    updateCheckboxInput(session,"spellCheck",value = sproutData$sc) 
    a$sproutData$scOptions->sproutData$scOptions #user specified spell check options (only used if spell check is turned on) 
    updateCheckboxGroupInput(session,"spellCheckOptions",selected=sproutData$scOptions) 
    a$sproutData$scLength->sproutData$scLength #user specified min word lenght for spell check (only used if spell check is turned on) 
    updateNumericInput(session,"spellCheckMinLength",value=sproutData$scLength) 
    a$sproutData$stopwords->sproutData$stopwords #user specified stopwords 
    a$sproutData$stopwordsLastChoice->sproutData$stopwordsLastChoice 
    if(sproutData$stopwordsLastChoice[1] == ""){ 
     updateSelectInput(session,"stopwordsChoice",selected="none") 
    } else if(all(sproutData$stopwordsLastChoice == stopwords('en'))){ 
     updateSelectInput(session,"stopwordsChoice",selected="en") 
    } else if(all(sproutData$stopwordsLastChoice == stopwords('SMART'))){ 
     updateSelectInput(session,"stopwordsChoice",selected="SMART") 
    } 
    HTML("<strong>Loaded data!</strong>") 
    } else if (!is.null(input$loadSavedData$datapath)) { 
    HTML(paste("<strong>Not a valid save file</strong>")) 
    } 
}) 

Die tatsächliche Ausgabe ist eine Tabelle, welche Details, was es gefunden und was es eingestellt.Da ich aber alle Eingaben kenne und sie sich nicht ändern, kann ich sie explizit speichern (Standardwert oder geänderter Wert) und sie dann explizit aktualisieren, wenn die Sicherungsdatei hochgeladen wird.

+0

Ich habe auch darüber nachgedacht, jeden einzelnen von ihnen "manuell" zu aktualisieren, aber das würde zu viele Zeilen hinzufügen (derzeit 117), was sehr schwer zu verwalten wäre. Die App ist fast fertig, aber ich würde sagen, sie ist in Alpha und wird wahrscheinlich modifiziert werden, möglicherweise mehr Eingaben erhalten, vielleicht einige davon ändern. Ich werde der Frage etwas hinzufügen. – Molx

3

Dies ist ein bisschen alt, aber ich denke, ist nützlich, um ein komplettes Beispiel zu veröffentlichen, Speichern und Laden von Benutzereingaben.

library(shiny) 

ui <- shinyUI(fluidPage(
    textInput("control_label", 
      "This controls some of the labels:", 
      "LABEL TEXT"), 
    numericInput("inNumber", "Number input:", 
       min = 1, max = 20, value = 5, step = 0.5), 
    radioButtons("inRadio", "Radio buttons:", 
       c("label 1" = "option1", 
       "label 2" = "option2", 
       "label 3" = "option3")), 

    actionButton("load_inputs", "Load inputs"), 
    actionButton('save_inputs', 'Save inputs') 

)) 

server <- shinyServer(function(input, output,session) { 

    observeEvent(input$load_inputs,{ 

    if(!file.exists('inputs.RDS')) {return(NULL)} 

    savedInputs <- readRDS('inputs.RDS') 

    inputIDs  <- names(savedInputs) 
    inputvalues <- unlist(savedInputs) 
    for (i in 1:length(savedInputs)) { 
     session$sendInputMessage(inputIDs[i], list(value=inputvalues[[i]])) 
    } 
    }) 

    observeEvent(input$save_inputs,{ 
    saveRDS(reactiveValuesToList(input) , file = 'inputs.RDS') 
    }) 
}) 
Verwandte Themen