In the last section we saw how the mouseover listener can be used to extract the marker id – in this case the FIPS code. Being able to extract this id from a leaflet map and mouseaction is ideal for using the functions (make_graph, and make_table) we developed in section 02,03, and 04.
Lets start with adding the dygraph
object. Remember that to add an object we need to define it both in the UI (where it will be seen) and in the server (how it will be adjusted, and rendered)
In the UI we want to display the chart below the covidMap
in the mainPanel.
We know we are generating a dygraph object so we add dyGraphOutput
and expect to display a rendered output object called covidGraph.
Below is our UI interface with some slight modifications:
ui <- fluidPage(
titlePanel('Mike Johnson: COVID-19 Tracker'),
# Sidebar layout output definitions ----
sidebarPanel(
textOutput("covidMessage", container = h3),
),
# Main panel for displaying outputs ----
mainPanel(
# Output: Map ----
leafletOutput('covidMap'),
# Output: Chart ----
dygraphOutput('covidGraph')
)
)
Here you’ll notice that we added a dyGraphOutput
that expects to find a covidGraph
object in the output list. This means the server must return a dygraph
object in the covidGraph
slot of the output
list.
By placing this line of code underneath the leafletOutput
we ensure the UI
will render the graph underneath the map.
In our server function we will want to generate a dygraph using our make_graph
function. Remember that make_graph
requires a FIP code as input.
In general this FIP will be informed by user input. However when the application first starts we need a default value so the chart will still execute.
It might also be clear to you at this point that all of our functions require a FIP code as input. Because of this, we will create a global FIP variable that persists through the global scope of the server function. As a default, lets make a global FIP value containing the county with the most cases and add a line to save a rendered dygraph
object, made with our make_graph
function, to the output list.
server <- function(input, output, session) {
# Global variables initialized ----
FIP <- today$fips[which.max(today$cases)]
v <- reactiveValues(msg = "First Shiny!!!")
# Leaflet Map ----
# ---- must be rendered as leaflet ----
output$covidMap <- renderLeaflet({ basemap })
# dyGraph Chart ----
# ---- must be rendered as dyGraph ----
output$covidGraph <- renderDygraph({ make_chart(covid19, FIP) })
# Events ----
# removed here for clarity keep them in your server function
# Message to Display ----
# removed here for clarity keep them in your server function
}
When we run our complete shiny application we’ll see something like this:
Great! 😄 we have a map
that listens to mouseover
and mouseout
events and a graph
that displays the COVID trends for the USA county with the most cases.
While setting a default FIP value is needed for the application to initialize, it would be better if the chart adapted to input from the map – much like our reactive message!
But, changing the graph with the mouseover
would be impractical (think how fast it would adapt!!). Instead, lets change the chart only when a map_marker
is clicked.
This requires observing the event of a mouseclick. Following our pattern from before, a covidMap_marker_click
will do the trick!
Lets look at what to add to the server function:
observeEvent(input$covidMap_marker_click, {
FIP <<- input$covidMap_marker_click$id
output$covidGraph <- renderDygraph({ make_graph(covid_data, FIP) })
})
IMPORTANT: There is one very important thing to note in this entry. Here, the <<-
sets the global value of FIP to the id/FIP of the clicked on marker. Had we just used <-
, the assignment to FIP would have only occurred in the scope of this observeEvent
. By using the <<-
assignment operator, we are setting the value of FIP in the global scope of the server. Ultimately, this will make the identified FIP accessible to other listeners and methods in the application.
Wouldn’t it also be nice if we could modify the map based on our click? How about we apply our zoom_to_county
function within the mouseclick listener:
observeEvent(input$covidMap_marker_click, {
FIP <<- input$covidMap_marker_click$id
output$covidGraph <- renderDygraph({ make_graph(covid_data, FIP) })
leafletProxy('covidMap') %>% zoom_to_county(counties, FIP)
})
Doing this highlights another very important shiny/leaflet functionality.
Notice that with the dyGraph
option we simply re-rendered the plot and overrode the existing output$covidGraph
object. This is because once a new FIP is selected, there was nothing from the previous graph that is useful.
The same isn’t true for maps, in fact, our changes are often related to zooming, adding, or removing features of an existing rendered map. Therefore there is no need to re-render the map - just adjust it!
The Shiny and leaflet developers recognize this and offer the leafletProxy
function to create a map-like object that can customize and control an existing map . Therefore instead of passing the direct basemap object to zoom_to_county, we pass a proxy of that object and Shiny knows to modify the existing render.
With all those changes lets run our app once again!
Now when a marker is clicked, the map zooms to the county, displays the border, and changes the chart – all based on user input!!
A full app.R
file up to this point can be found here
With that, you are ready to extend your shiny app even more. In the next section we’ll see add our DT
table to our sidePanel
and syncing it with our map and chart.