EnviroScreen Shiny App overview
This document is meant to provide some description context to support active development and troubleshooting of the code base.
Error and Troubleshooting
Troubleshooting a shinyapp can be a bit challenging at time. While developing this application the most common source of error came from miss matched variable names between the input dataset and parameters within the application. I speak about this quite directly throughout the document. Aside from the naming issues the three primary reactive elements; map, histogram, tables do interface with each other but not at the start of the application. This means that specific errors within those features will likely appear on deployment. Usually the error message will point you to the corrent element. If the app runs but fails once a specific request is provided, that’s where to look.
If things are really bad, like they were when we tried the translation for the first time follow the steps below.
- Comment out all the content in the server function and make sure the UI looks like you expect it should.
- Incrementally introduce a single server side functionally at a time making sure it works as expected before moving on.
- this is a great way to understand the dependencies and input to the various reactive actions in the application.
- once things work locally, test deployment to server.
Deployment
The main
branch in the github repository reflects the currently deployed version of the application.
Any addational braches are there for specific development reasons and will be resolved into the main or deleted when appropriate.
Before deploying to the public URL
- test application locally.
- take your time here. Run through tables, geographies, update the map, test map layers.
- errors with indicator names will only show when a specific indicator is selected.
- Push version to test site on end point server.
- This ensure your application will run on the shinyapps.io server
- If it worked on the test site, push to main server.
- You be only slightly worried about crashing things at thing point because of the previous two steps.
Inputs
-
envoData : primary data generated by the data processing code base. Column names from this dataset are what defines the display of all information within the application. Change a column name without adjusting features in the application and something, probably multiple reactive elements will break.
- oil : Map visualization feature generated in the data processing code base.
- coal : Map visualization feature generated in the data processing code base.
- rural : Map visualization feature generated in the data processing code base.
- descriptors : CSV containing indicator names, short descriptions, and all content displayed on the indicator descriptions table. Indicator names in this file must match those in the envoData dataset.
- justice40 : Map visualization feature generated in the data processing code base.
- di : Map visualization feature generated in the data processing code base.
- sm : Map visualization feature generated in the data processing code base.
Reactive Objects
These features are what the users actively engage with to make changes to the application. Some are very obvious, things like buttons, others are more subtle, map clicks. Most of these features are defined in the UI but some are actions defined within the widget (map and tables).
Defined in the UI
updateMap
: Button that allows changes to geographic scale, indicator, and measure/percentile to occur. This button is here to prevent a lag of request. Specifically with the census block level data, it takes a few seconds to update the map visualization.Geom
: Used to define the geographic scale displayed on the map, tables, and histograms. This object is used a lot for filtering dataset. Tables and histograms look to this object for changes.Indicator
: Alters the layer displayed on the map.percentile
: Alters the layer displayed on the map.removeHighlight
: Clears the user defined map element that can be added based on table selections.mymap
: The map obect. This is the most engaged object within the application. There are two functions that influence it and well as proxy elements. It also ingest all input dataset in some way. The application was built around the map so it tends to be a bit more stable then the histograms/tables. That said because it engages with all the data and most the reactive element it is the best place to start the troubleshooting method.indicatorDesc
: Block quote of short indicator descriptionshistEnviroScreen
: Histogram of the Enviroscreen Score. This features is larger then other histograms. All histograms are defined with the same function, so issues should be evaluated from the function rather then the app.r file.histExposure
: Historgram of environmental exposurehistEffect
: Histogram of environmental effectshistClimate
: Histogram of ClimatehistSocial
: Histogram of Social EconomichistDemo
: Histogram of DemographicstableAll
: Contructs the data table from which specific tables are displayed.tableSelect
: Filters the table data to visualize the information that the user has selected. Note that all but the Indicator Descriptions table are parsed from the same input dataset. Having two different input dataset under this call has created some real challenges in maintaining similar performance between the two features. Currently the table work well for the enviroscreen data, and not well for the reference material in the indicator description table. This is a compromise.button_table
: a poorly named variable that creates a map object based on features selected in the table. Currently selections are held across all tables. Selecting a features on the map also highlights the object on a table. The result of this is that there aredownloadData
: Download the data held on all be the Indicator Description table. Data is limited to the currently selected geography.downloadData2
: Download the data held in the Indicator Description table. This does not change with geography.
inputs from base functionality
mymap_shape_click
: Leaflet object that collects information about the user selected map feature. This only applies to Indicator Layer. Selecting a DI community area does not effect the reactively of the map elements. THe select does two things automatically. One; histograms are redrawn to show the bin in which the selected feature was identified. Two; the table data is sorted so that the select object is at the top of the table. The table is sorted by the GEOID at that point. Same as aphabetically, except that some census tracts/block groups can appear above the selected GEOID. This measure that selecting a census tract from adams county does not garuntee that ll census blocks from adams county will be visable near the start of the table.tableAll_rows_selected
: Datatable object that collects the rowid from the selected table. This is then used to gather the GEOID from the table. The GEOID also the app to subset the envoData dataset and produce a new spatial object that is rendered on the map.mymap_shape_click$id
: The id parameter from the leaflet object also the quick indexing of the GEOID. This allows the sorting of the datatable object.
Reactive Elements
tabset panels Not really a reactive element, nothing about these table changes besides which feature is shown, but from the user prespective they seem like on. These tabset panels play an important role in the application because that provide all the contentual information people need to understand what the applicaiton is talking about. That said, I don’t like them. They are oddly finick in weird ways. They also don’t see as adaptable as most other shiny elements. For example, I can create a in applicaiton link to the map by defining a CSS class object for the fluid row that it rests in. I can’t do this for the tabset panel element. It doesn’t seem to respect the CSS structure in the same why the fluidrows do. I don’t have a valid explanation. This rant is here to point caution to this useful but not very robest element of the application.
Map Selection
This series of buttons defines the behavior or the map, histograms, and table elements of the applicaiton. Changes to the map require use of the updata map
button. Where as changes to the histogram and tables are reactive to changes in the geo scale
selection. Odd behavior, from the users persepetive, can result when the geography displayed on the map does not match the geography current selected on under the geo scale
drop down. Basically the GEOID from map selections will never match the content in the histogram and tables so there is a lost of functionality. The remove highlighted features from the map works indepently of other features. It’s kinda out of place from a functional perspective, but works well for user interaction.
Map
This beauty is map possible because of the rmapshaper::ms_simplify()
function which greatly reduces the complexity of input spatial datasets (up to and order of magniture in overall file size) while maining topological relationships. We are apply the function within the data processing code.
The map is effect by four specific actions
- The map is initial created with a predefined subset of the data. This allows for quick loading and allows the handling of all addational map layers.
- As the user interacts with the
map selection
buttons the map is updated using a proxy call on the existing object. This allows user to change what is presented as the Indicator map layer. - Adding highlighted feature on the table to the map all relies on a proxy call.
- Removing highlighted features from the map utilizes a proxy call.
Histograms The histograms are all rendered about a single function. No proxy elements are used, meaning everything a new map feature is selected or a new geo scale is select all 6 histograms are regenerated. Labeling is handled with clauses within the function. The visualization effect for highlight the bin required some duplication of code with the if/else statement. Should be obvious within the function, I just found that I ofter only edited one element rather then both when troubleshooting. Last note is than running locally the histograms seem to be effected by the size of the plot display within your rstudio environment. This is very odd, but if you end up with a ‘figure margin error’ then it’s worth increasing the display size of the plot viewer pane before looking to the code. I never encountered this error with deployment to shinyapps.io
Data Tables There is nothing to special about the data table themselves. They are indexed from a single datatable that is a subset of the current selected geography. There are a lot of changes to make here if you end up changing indicator names. As noted above the Indicator Description table breaks the mold of the other tables and does not react the same way as others. It be great to improve this at some point but for not I think it is a reasonable defect to maintain.
Functions
Functions are used within the application for one of two reasons.
- They represent a reusable code chunk that is deployed multiple times
- They represent a container for an isolated code chuck so that lines of code can be removed from the app.r file
My interest in utilizing function for contains has a cost when it comes to indicator name changes. You have to replace features names within the the app.r and functions. Still the endless scrolling of hunderd of line of code is bothersome so we’ve got container functions.
appPolyLine
description: Adds a polyline feature to an existing map object inputs :
- map : leaflet map object this
- sf1 : spatial data representing the polyline note: polyline rather then polygon due to the visualization technique apply to these features.
- group : layerId, used to turn feature on and off in the map
- popup : Text description to include in the popup outputs : returns a visualed polyline object that can appear on the map. considerations: Created to add the three community layers (oil, coal, rural). Mostly a container function.
createMap
description: This defines the inital map object. Lots of leaflet functions coming in to make the map. inputs
- mapData : sf object used to define the indicator layer
- pal : vector of hex codes used for the legend of the indicator layer
- palMap : leaflet pallette object used to visualize the indicator layer
- diPal : leaflet pallette object used to visualize the DI layer
- oil : sf object for the oil community layer
- rural : sf object for the rural community layer
- coal : sf object for the coal community layer
- di : sf object for the di community layer
- justice40 : sf object for the justice40 layer
- storyMaps : sf object for the story maps layer outputs : leaflet map object considerations: Mostly a container function. Calling on other functions within. Keep care to ensure the group names remain consistent as they occur in multiple places within the function.
defineLegend
description: Assigns label values to legend based on indicator name. inputs
- indicator : indicator name.
outputs : vector of character values to be used to label indicator layers within the legend
considerations: If you change indicator names in the input datasets you have to change them within this function as well.
genPlots
description: Comprehensive function for generating the histograms. inputs
- dataframe : envoData filtered to geography of interest
- parameter : Component score to be visualized
- geometry : Character from geo scale drop down, used to determine axis labels.
- geoid = NULL : value from user map click. Set to null to default. If statement built around this parameter which determines if a single color are two colored histogram is generated.
outputs : plotly object
considerations: There are a lot of conditional statements in here as this is applied to 6 different indicators at three different geographies. Be aware of the if statement around the geiod parameter as there is a lot of duplicated code present there.
genTable
description: Largely a multi part select funtion to produce the table the user wants . inputs
- tableData : envoData filtered to geography of interest
- geoid : Value from the selection of a map feature. Used to order the table data
- colselected : user input selection of what table to view.
outputs : datatable object
considerations: This function does not include the Indicator Description table. Any changes to names in the input dataset will require alterations here.
getDI
description: Container function to contrust the DI community map layer .
inputs : NA
outputs : SF object
considerations: All this can be done within the data processing code base, consider moving it out of the application in the future.
getStoryMaps
description: Generate the story map sf object.
inputs : NA
outputs : SF object
considerations: We only use this story may data within the shiny app. Will need to update the function once we get more links.
initialMapData
description: Simply container function to simplify input dataset for intial map construction.
inputs : SF object
outputs : SF object
considerations: NA
SCSS and inline HTML
Styling within the shiny application occurs in three different ways.
- Through the
bslib::bs_theme
function. - Through class definations and scss script
- Through inline html/css
The base theme is set by the bslib
fuction. Changing these parameters will directly alter the look of the application. That said this library is mostly responsabile for the look of the buttons.
Within the bslib
funciton a scss file is reference as a source of addational rules. The style.scss
file was generated incrementally to address specific visualization requests. Rules within in the scss file are ment to override generic theme set by the bs_theme
. Rules were attempted to be made as specifcally as possible, thought there are some generic applications such as styling all p()
calls.
Inline html/css was used as sparingly as possible and over time it would be ideal to phase it out entirely. For example we utilize inline html to set the in application relative link and maintain the proportion of the map element.
# display map -------------------------------------------------------------
fluidRow(tags$style(type = "text/css", "#mymap {height: calc(100vh - 250px) !important;}"), #style = {"background-color:#4d3a7d;"},
column(1),
column(7, leafletOutput("mymap")),
column(3, br(),br(),br(),br(),
plotlyOutput("histEnviroScreen" ,height = "80%", width = "100%")),
column(1),
),
Once you get the hand of utilizing a code editor within your browser, this can be pretty fun stuff to experiment with.
Adaptations for Spanish
Ay Yai Yai, multilingual applications.
There is tremedous room for growth here. The intital development of the spanish language version of enviroscreen took place in the last month of the project. The implimention of application at release gets negitive points for elegance and parsimony. Why all the trouble.
Special Characters, sentence structure, and variable prepositions
special characters
Special characters have cause issues. Saving the files with encoding, “utf-8”, has resolved the majority of these issues. The persistent problem is that a character string with special characters can not be passed through a function. The work around for this was to dissolve all functions into the app.r file. As most functions were simply containers for saving space this did not effect the functionality of the application. That said it does mean there are numerous redundent code chunks.
Special characters have also cause problems in the input RDS file. They were not being save connectly. The work around here was utilize the dplyr::select
function to rename all columns in the input dataset to ensure utf-8 formating.
The benifit of both these alterations is that all occurences of input parameter names are present in the app.r script. This means adapting the code to account for changes in indicator names is significantly more straighforward as compared to the english implimentation.
Sentence structure Being a different language, Spanish utilizes a different sentence structure then English. This effected the position of intext formating and hyperlinks. Once dealt with this is not a major concern but it is noted here because any addational translation efforts due require addational time for implementation.
variable prepositions “Puntaje de Colorado EnviroScreen” “Percentil del puntaje de Colorado EnviroScreen” “Contaminación y carga climática” “Percentil de contaminación y carga climática”
Sometimes it’s del sometimes it’s de. Nothing wrong with either. The challenge came from that fact that the application contrusts the percentil indicator names based on the input variable name alone. That is we don’t store the percentile del puntaje de Coloroado Enviroscreen in a drop down list. Instead we store “Puntaje de Colorado EnviroScreen” and measure or percentile. Based on the selection the indicator name is selected. This resulted in an addational clause statement. Not that big of a deal but this is a potential problem others will need to address in the future.
# indicator
in1 <- input$Indicator
# need to lower the first letter in the string but nothing else
# grab first letter and set to lower
t1 <- tolower(str_sub(in1, 1, 1))
# subset out first letter
t2 <- str_sub(in1, 2, nchar(in1))
# construct new string
indicator1 <- in1
# sometimes it's del sometimes it de
if(in1 %in% c("Puntaje de Colorado EnviroScreen"
,"Indicador de descargas de aguas residuales"
,"Indicador de salud mental"
,"Porcentaje de discapacidades"
,'Porcentaje que no completaron los estudios de secundaria'
,"Porcentaje de aislamiento lingüístico"
,"Porcentaje de bajos ingresos"
,"Porcentaje de personas de color"
)){
indicator2 <- paste0("Percentil del ", t1,t2)
}else{
indicator2 <- paste0("Percentil de ", t1,t2)
}
if(input$Percentile == "Valor medido"){
indicator <- indicator1
}
if(input$Percentile == "Rango percentil"){
indicator <- indicator2
}
Spanish/English Overall
Translation work presents a unique set of challenges. The benefit of the process is that it forces you to reevaluate the structure and function of your application. The result in the case of EnviroScreen two applications that take very different routes to get to the same place.
Questions
carverd@colostate.edu