This document describes the codebase used to create the GenEst Graphic User Interface (GUI). The Genest GUI is coded in HTML via external R packages (DT, htmltools, shiny, shinyjs, as well as a number of internal functions to facilitate a simple, human-readable codebase underlying the app. The goal being to allow GenEst to evolve as fluidly as possible at the user interface.
The GUI is executed locally or as a deployed app following the basic approach of shiny applications. For ease of implementation, we have created an overall function to intialize the app, runGenEst()
, which calls both the server and UI codebases. Like most vintage Shiny apps, we employee the two-file system, including a ui.R
and server.R
script, although each script is Spartan. The ui.R
script includes a single call to the GenEstUI(appType)
function, which starts the cascade of HTML-generating functions outlined in UI Function Hierarchy. The server.R
script includes a single reference (not a call) to the function GenEstServer
, which is detailed in Server Function Hierarchy.
The GenEst User Interface is constructed in HTML using pages, panels, tabs, and widgets. The code is parsed into a series of hierarchical functions to facilitate readability and mobility of specific UI components.
GenEstUI(appType)
dataInputPanel()
dataInputSidebar()
dataInputWidget("SE")
dataInputWidget("CP")
dataInputWidget("SS")
dataInputWidget("DWP")
dataInputWidget("CO")
loadedDataPanel()
dataTabPanel("SE")
dataTabPanel("CP")
dataTabPanel("SS")
dataTabPanel("DWP")
dataTabPanel("CO")
analysisPanel()
GeneralInputsPanel()
GeneralInputsSidebar()
modelInputWidget("nsim")
modelInputWidget("CL")
modelInputWidget("sizeclassCol")
SEPanel()
SESidebar()
modelInputWidget("obsSE")
modelInputWidget("predsSE")
modelInputWidget("kFixed")
modelRunWidget("SE")
modelOutputWidget("SE")
SEMainPanel()
selectedDataPanel("SE")
modelOutputPanel("SEFigures")
modelOutputPanel("SEEstimates")
modelOutputPanel("SEModComparison")
modelOutputPanel("SEModSelection")
CPPanel()
CPSidebar()
modelInputWidget("ltp")
modelInputWidget("fta")
modelInputWidget("predsCP")
modelInputWidget("dists")
modelRunWidget("CP")
modelOutputWidget("CP")
CPMainPanel()
selectedDataPanel("CP")
modelOutputPanel("CPFigures")
modelOutputPanel("CPEstimates")
modelOutputPanel("CPModComparison")
modelOutputPanel("CPModSelection")
MPanel()
MSidebar()
modelInputWidget("frac")
modelInputWidget("DWPCol")
modelInputWidget("dateFoundCol")
modelRunWidget("M")
modelOutputWidget("M")
MMainPanel()
gPanel()
gSidebar()
modelInputWidget("gSearchInterval")
modelInputWidget("gSearchMax")
modelInputWidget("useSSinputs")
modelInputWidget("useSSdata")
modelRunWidget("g")
modelOutputWidget("g")
gMainPanel()
selectedDataPanel("g")
modelOutputPanel("gFigures")
modelOutputPanel("gSummary")
helpPanel(type)
gettingStartedPanel()
gettingStartedContent()
downloadsPanel()
dataDownloadsWidget("RP")
dataDownloadsWidget("RPbat")
dataDownloadsWidget("cleared")
dataDownloadsWidget("powerTower")
dataDownloadsWidget("PV")
dataDownloadsWidget("trough")
dataDownloadsWidget("mock")
aboutPanel()
aboutContent()
GenEstAuthors()
GenEstGUIauthors()
GenEstLicense()
GenEstAcknowledgements()
GenEstLogos()
disclaimersPanel(appType)
disclaimerContent(appType)
disclaimerUSGS()
disclaimerWEST(appType)
We have coded up a number of widget functions, some of which are simple wrappers on shiny functions that help reduce code clutter, and others of which are custom HTML (e.g., for model selection), but which are still nonetheless wrapped over shiny widgets:
dataInputWidget(dataType)
modelInputWidget(inType)
modelRunWidget(modType)
modelOutputWidget(modType)
dataDownloadsWidget(set)
modelSelectionWidget(modType)
kFixedWidget()
A major need for widgets is having a simple condition wrapped on it, such that the widget is within a conditional panel, defined by some other input or output variable. To facilitate coding of these widgets, we have made a function widgetMaker
, which is a generalized constructor function.
Similarly to the input widgets, we have coded up a number of output panel functions that help direct traffic in the building on the HTML document. These functions are generalized to the suite of possible options for panels currently needed by leveraging the basic template approach with limited variation.
dataTabPanel(dataType)
selectedDataPanel(modType)
modelOutputPanel(outType)
There is a fair amount of (mostly) static content, which we have contained wihtin functions to reduce overall code clutter. These functions primarily dictate content within the “Help” tab’s subtabs.
gettingStartedContent()
aboutContent()
GenEstAuthors()
GenEstGUIAuthors()
GenEstLicense()
GenEstAcknowledgements()
GenEstLogos()
disclaimersContent(appType)
disclaimerUSGS()
disclaimerWEST(appType)
The server-side functionality operates using Shiny’s reactive programming framework. In particular, we include a main reactiveValues
list called rv
, that, in addition to the standard input
and output
lists is passed among functions throughout the app. The rv
list holds all of the reactive values currently being used by the application.
The GenEst server code is a relatively flat hierarchy, especially in comparison to the UI code. In particular, after a brief set of preamble functions for preparing objects and options, GenEstServer
makes many calls to observeEvent
, the reactive observation function, one call for each of the possible events in the application (data load, model run or clear, column selection, clear of contents). Each call also includes an evaluation of the held-back “handler” code for the event returned by the function reaction
, with the handler being the code that is to be evaluated when the event is observed. The expression is held-back in reaction
to minimize scoping issues associated with message-related functions.
GenEstServer(input, output, session)
rv <- initialReactiveValues()
output <- initialOutput(rv, output)
msgs <- msgList()
options(htmlwidgets.TOJSON_ARGS = list(na = 'string'))
observeEvent(DT.options = list(pageLength = 25))
observeEvent(input$clear_all, eval(reaction("clear_all")))
observeEvent(input$file_SE, eval(reaction("file_SE")))
observeEvent(input$file_SE_clear, eval(reaction("file_SE_clear")))
observeEvent(input$file_CP, eval(reaction("file_CP")))
observeEvent(input$file_CP_clear, eval(reaction("file_CP_clear")))
observeEvent(input$file_SS, eval(reaction("file_SS")))
observeEvent(input$file_SS_clear, eval(reaction("file_SS_clear")))
observeEvent(input$file_DWP, eval(reaction("file_DWP")))
observeEvent(input$file_DWP_clear, eval(reaction("file_DWP_clear")))
observeEvent(input$file_CO, eval(reaction("file_CO")))
observeEvent(input$file_CO_clear, eval(reaction("file_CO_clear")))
observeEvent(input$class, eval(reaction("class")), ignoreNULL = FALSE)
observeEvent(input$obsSE, eval(reaction("obsSE")), ignoreNULL = FALSE)
observeEvent(input$predsSE, eval(reaction("predsSE")), ignoreNULL = FALSE)
observeEvent(input$run_SE, eval(reaction("run_SE")))
observeEvent(input$run_SE_clear, eval(reaction("run_SE_clear")))
observeEvent(input$outSEclass, eval(reaction("outSEclass")))
observeEvent(input$outSEp, eval(reaction("outSEp")))
observeEvent(input$outSEk, eval(reaction("outSEk")))
observeEvent(input$ltp, eval(reaction("ltp")), ignoreNULL = FALSE)
observeEvent(input$fta, eval(reaction("fta")), ignoreNULL = FALSE)
observeEvent(input$predsCP, eval(reaction("predsCP")), ignoreNULL = FALSE)
observeEvent(input$run_CP, eval(reaction("run_CP")))
observeEvent(input$run_CP_clear, eval(reaction("run_CP_clear")))
observeEvent(input$outCPclass, eval(reaction("outCPclass")))
observeEvent(input$outCPdist, eval(reaction("outCPdist")))
observeEvent(input$outCPl, eval(reaction("outCPl")))
observeEvent(input$outCPs, eval(reaction("outCPs")))
observeEvent(input$run_M, eval(reaction("run_M")))
observeEvent(input$run_M_clear, eval(reaction("run_M_clear")))
observeEvent(input$split_M, eval(reaction("split_M")))
observeEvent(input$split_M_clear, eval(reaction("split_M_clear")))
observeEvent(input$transpose_split, eval(reaction("transpose_split")))
observeEvent(input$useSSdata, eval(reaction("useSSdata")))
observeEvent(input$useSSinputs, eval(reaction("useSSinputs")))
observeEvent(input$run_g, eval(reaction("run_g")))
observeEvent(input$run_g_clear, eval(reaction("run_g_clear")))
observeEvent(input$outgclass, eval(reaction("outgclass")))
The reaction
function is, essentially, a parsed-text-generating function. Depending upon the specific event (eventName
, the only input), the necessary set of function calls (messages for running, running the code, messages for when done) is prepared for evaluation. The main function used to do things within the handler code (when the event occurs) is eventReaction
, which calls three functions: update_rv
, update_output
, and update_input
, in that order (updating the output depends on the rv being updated already and updating the input requires both the rv and output to be up-to-date):
eventReaction(eventName, rv, input, output, session)
update_rv(eventName, rv, input)
update_output(eventName, rv, output)
update_input(eventName, rv, input, session)
Because of the scoping set-up for Shiny apps, there is no need to assign the returned elements for update_rv
and update_output
to anything, and update_input
works through session
to direct its updates to the application.
Each of the three functions takes eventName
as the first argument, which is used to toggle amongst the possible actions to be taken with respect to each of the lists. That is, each of the three update_
functions contains a large internal set of routines, and only the relevant ones are called for a given function. This occurs via simple conditional code blocks (“if the eventType is this, do this”) for each of the possible events, thereby reducing the number of specific functions, but increasing the size of the key functions. Within each of the three functions, some of the events trigger a substantial amount of code while others only trigger a few (or no) lines. Similarly, some of the handler expressions take virutally no time to run, while others take a few minutes.