# Module UI
#' @title mod_leaflet_ui and mod_leaflet_server
#' @description A shiny Module.
#'
#' @param id shiny id
#' @param input internal
#' @param output internal
#' @param session internal
#'
#' @rdname mod_leaflet
#'
#' @keywords internal
#' @export
#' @importFrom shiny NS tagList
mod_leaflet_ui <- function(id){
ns <- NS(id)
# Choices for drop-downs
vars <- c(
"Is SuperZIP?" = "superzip",
"Centile score" = "centile",
"College education" = "college",
"Median income" = "income",
"Population" = "adultpop"
)
navbarPage("Superzip", id="nav",
tabPanel("Interactive map",
div(class="outer",
tags$head(
# Include our custom CSS
includeCSS("styles.css"),
includeScript("gomap.js")
),
# If not using custom CSS, set height of leafletOutput to a number instead of percent
leafletOutput(ns("map"), width="100%", height="100%"),
# Shiny versions prior to 0.11 should use class = "modal" instead.
absolutePanel(id = "controls", class = "panel panel-default", fixed = TRUE,
draggable = TRUE, top = 60, left = "auto", right = 20, bottom = "auto",
width = 330, height = "auto",
h2("ZIP explorer"),
selectInput(ns("color"), "Color", vars),
selectInput(ns("size"), "Size", vars, selected = "adultpop"),
conditionalPanel("input.color == 'superzip' || input.size == 'superzip'", ns = ns,
# Only prompt for threshold when coloring or sizing by superzip
numericInput("threshold", "SuperZIP threshold (top n percentile)", 5)
),
plotOutput(ns("histCentile"), height = 200),
plotOutput(ns("scatterCollegeIncome"), height = 250)
),
tags$div(id="cite",
'Data compiled for ', tags$em('Coming Apart: The State of White America, 1960–2010'), ' by Charles Murray (Crown Forum, 2012).'
)
)
),
tabPanel("Data explorer",
fluidRow(
column(3,
selectInput(ns("states"), "States", c("All states"="", structure(state.abb, names=state.name), "Washington, DC"="DC"), multiple=TRUE)
),
column(3,
conditionalPanel("input.states",
selectInput(ns("cities"), "Cities", c("All cities"=""), multiple=TRUE)
)
),
column(3,
conditionalPanel("input.states",
selectInput(ns("zipcodes"), "Zipcodes", c("All zipcodes"=""), multiple=TRUE)
)
)
),
fluidRow(
column(1,
numericInput(ns("minScore"), "Min score", min=0, max=100, value=0)
),
column(1,
numericInput(ns("maxScore"), "Max score", min=0, max=100, value=100)
)
),
hr(),
DT::dataTableOutput(ns("ziptable")
)
),
conditionalPanel(ns("false"), icon("crosshair"))
)
}
# Module Server
#' @rdname mod_leaflet
#' @export
#' @keywords internal
mod_leaflet_server <- function(input, output, session, datasets){
ns <- session$ns
allzips <- readRDS("superzip.rds")
allzips$latitude <- jitter(allzips$latitude)
allzips$longitude <- jitter(allzips$longitude)
allzips$college <- allzips$college * 100
allzips$zipcode <- formatC(allzips$zipcode, width=5, format="d", flag="0")
row.names(allzips) <- allzips$zipcode
cleantable <- allzips %>%
select(
City = city.x,
State = state.x,
Zipcode = zipcode,
Rank = rank,
Score = centile,
Superzip = superzip,
Population = adultpop,
College = college,
Income = income,
Lat = latitude,
Long = longitude
)
zipdata <- allzips[sample.int(nrow(allzips), 10000),]
# By ordering by centile, we ensure that the (comparatively rare) SuperZIPs
# will be drawn last and thus be easier to see
zipdata <- zipdata[order(zipdata$centile),]
# Create the map
output$map <- renderLeaflet({
leaflet() %>%
addTiles(
urlTemplate = "//{s}.tiles.mapbox.com/v3/jcheng.map-5ebohr46/{z}/{x}/{y}.png",
attribution = 'Maps by <a href="http://www.mapbox.com/">Mapbox</a>'
) %>%
setView(lng = -93.85, lat = 37.45, zoom = 4)
})
# A reactive expression that returns the set of zips that are
# in bounds right now
zipsInBounds <- reactive({
if (is.null(input$map_bounds))
return(zipdata[FALSE,])
bounds <- input$map_bounds
latRng <- range(bounds$north, bounds$south)
lngRng <- range(bounds$east, bounds$west)
subset(zipdata,
latitude >= latRng[1] & latitude <= latRng[2] &
longitude >= lngRng[1] & longitude <= lngRng[2])
})
# Precalculate the breaks we'll need for the two histograms
centileBreaks <- hist(plot = FALSE, allzips$centile, breaks = 20)$breaks
output$histCentile <- renderPlot({
# If no zipcodes are in view, don't plot
if (nrow(zipsInBounds()) == 0)
return(NULL)
hist(zipsInBounds()$centile,
breaks = centileBreaks,
main = "SuperZIP score (visible zips)",
xlab = "Percentile",
xlim = range(allzips$centile),
col = '#00DD00',
border = 'white')
})
output$scatterCollegeIncome <- renderPlot({
# If no zipcodes are in view, don't plot
if (nrow(zipsInBounds()) == 0)
return(NULL)
print(xyplot(income ~ college, data = zipsInBounds(), xlim = range(allzips$college), ylim = range(allzips$income)))
})
# This observer is responsible for maintaining the circles and legend,
# according to the variables the user has chosen to map to color and size.
observe({
colorBy <- input$color
sizeBy <- input$size
if (colorBy == "superzip") {
# Color and palette are treated specially in the "superzip" case, because
# the values are categorical instead of continuous.
colorData <- ifelse(zipdata$centile >= (100 - input$threshold), "yes", "no")
pal <- colorFactor("viridis", colorData)
} else {
colorData <- zipdata[[colorBy]]
pal <- colorBin("viridis", colorData, 7, pretty = FALSE)
}
if (sizeBy == "superzip") {
# Radius is treated specially in the "superzip" case.
radius <- ifelse(zipdata$centile >= (100 - input$threshold), 30000, 3000)
} else {
radius <- zipdata[[sizeBy]] / max(zipdata[[sizeBy]]) * 30000
}
leafletProxy("map", data = zipdata) %>%
clearShapes() %>%
addCircles(~longitude, ~latitude, radius=radius, layerId=~zipcode,
stroke=FALSE, fillOpacity=0.4, fillColor=pal(colorData)) %>%
addLegend("bottomleft", pal=pal, values=colorData, title=colorBy,
layerId="colorLegend")
})
# Show a popup at the given location
showZipcodePopup <- function(zipcode, lat, lng) {
selectedZip <- allzips[allzips$zipcode == zipcode,]
content <- as.character(tagList(
tags$h4("Score:", as.integer(selectedZip$centile)),
tags$strong(HTML(sprintf("%s, %s %s",
selectedZip$city.x, selectedZip$state.x, selectedZip$zipcode
))), tags$br(),
sprintf("Median household income: %s", dollar(selectedZip$income * 1000)), tags$br(),
sprintf("Percent of adults with BA: %s%%", as.integer(selectedZip$college)), tags$br(),
sprintf("Adult population: %s", selectedZip$adultpop)
))
leafletProxy("map") %>% addPopups(lng, lat, content, layerId = zipcode)
}
# When map is clicked, show a popup with city info
observe({
leafletProxy("map") %>% clearPopups()
event <- input$map_shape_click
if (is.null(event))
return()
isolate({
showZipcodePopup(event$id, event$lat, event$lng)
})
})
## Data Explorer ###########################################
observe({
cities <- if (is.null(input$states)) character(0) else {
filter(cleantable, State %in% input$states) %>%
`$`('City') %>%
unique() %>%
sort()
}
stillSelected <- isolate(input$cities[input$cities %in% cities])
updateSelectizeInput(session, "cities", choices = cities,
selected = stillSelected, server = TRUE)
})
observe({
zipcodes <- if (is.null(input$states)) character(0) else {
cleantable %>%
filter(State %in% input$states,
is.null(input$cities) | City %in% input$cities) %>%
`$`('Zipcode') %>%
unique() %>%
sort()
}
stillSelected <- isolate(input$zipcodes[input$zipcodes %in% zipcodes])
updateSelectizeInput(session, "zipcodes", choices = zipcodes,
selected = stillSelected, server = TRUE)
})
observe({
if (is.null(input$goto))
return()
isolate({
map <- leafletProxy("map")
map %>% clearPopups()
dist <- 0.5
zip <- input$goto$zip
lat <- input$goto$lat
lng <- input$goto$lng
showZipcodePopup(zip, lat, lng)
map %>% fitBounds(lng - dist, lat - dist, lng + dist, lat + dist)
})
})
output$ziptable <- DT::renderDataTable({
df <- cleantable %>%
filter(
Score >= input$minScore,
Score <= input$maxScore,
is.null(input$states) | State %in% input$states,
is.null(input$cities) | City %in% input$cities,
is.null(input$zipcodes) | Zipcode %in% input$zipcodes
) %>%
mutate(Action = paste('<a class="go-map" href="" data-lat="', Lat, '" data-long="', Long, '" data-zip="', Zipcode, '"><i class="fa fa-crosshairs"></i></a>', sep=""))
action <- DT::dataTableAjax(session, df, outputId = "ziptable")
DT::datatable(df, options = list(ajax = list(url = action)), escape = FALSE)
})
}
## To be copied in the UI
# mod_leaflet_ui("leaflet_ui_1")
## To be copied in the server
# callModule(mod_leaflet_server, "leaflet_ui_1")
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.