Skip to content

Instantly share code, notes, and snippets.

@jcheng5
Created March 10, 2014 23:03
Show Gist options
  • Save jcheng5/9476282 to your computer and use it in GitHub Desktop.
Save jcheng5/9476282 to your computer and use it in GitHub Desktop.
SuperZip example License: MIT
library(dplyr)
allzips <- readRDS("data/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
)
library(shiny)
library(RColorBrewer)
library(scales)
library(lattice)
library(dplyr)
# Leaflet bindings are a bit slow; for now we'll just sample to compensate
set.seed(100)
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),]
shinyServer(function(input, output, session) {
## Interactive Map ###########################################
# Create the map
map <- createLeafletMap(session, "map")
# 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)))
})
# session$onFlushed is necessary to work around a bug in the Shiny/Leaflet
# integration; without it, the addCircle commands arrive in the browser
# before the map is created.
session$onFlushed(once=TRUE, function() {
paintObs <- observe({
colorBy <- input$color
sizeBy <- input$size
colorData <- if (colorBy == "superzip") {
as.numeric(allzips$centile > (100 - input$threshold))
} else {
allzips[[colorBy]]
}
colors <- brewer.pal(7, "Spectral")[cut(colorData, 7, labels = FALSE)]
colors <- colors[match(zipdata$zipcode, allzips$zipcode)]
# Clear existing circles before drawing
map$clearShapes()
# Draw in batches of 1000; makes the app feel a bit more responsive
chunksize <- 1000
for (from in seq.int(1, nrow(zipdata), chunksize)) {
to <- min(nrow(zipdata), from + chunksize)
zipchunk <- zipdata[from:to,]
# Bug in Shiny causes this to error out when user closes browser
# before we get here
try(
map$addCircle(
zipchunk$latitude, zipchunk$longitude,
(zipchunk[[sizeBy]] / max(allzips[[sizeBy]])) * 30000,
zipchunk$zipcode,
list(stroke=FALSE, fill=TRUE, fillOpacity=0.4),
list(color = colors[from:to])
)
)
}
})
# TIL this is necessary in order to prevent the observer from
# attempting to write to the websocket after the session is gone.
session$onSessionEnded(paintObs$suspend)
})
# 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)
))
map$showPopup(lat, lng, content, zipcode)
}
# When map is clicked, show a popup with city info
clickObs <- observe({
map$clearPopups()
event <- input$map_shape_click
if (is.null(event))
return()
isolate({
showZipcodePopup(event$id, event$lat, event$lng)
})
})
session$onSessionEnded(clickObs$suspend)
## 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])
updateSelectInput(session, "cities", choices = cities,
selected = stillSelected)
})
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])
updateSelectInput(session, "zipcodes", choices = zipcodes,
selected = stillSelected)
})
observe({
if (is.null(input$goto))
return()
isolate({
map$clearPopups()
dist <- 0.5
zip <- input$goto$zip
lat <- input$goto$lat
lng <- input$goto$lng
showZipcodePopup(zip, lat, lng)
map$fitBounds(lat - dist, lng - dist,
lat + dist, lng + dist)
})
})
output$ziptable <- renderDataTable({
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=""))
})
})
library(shiny)
library(leaflet)
# Choices for drop-downs
vars <- c(
"Is SuperZIP?" = "superzip",
"Centile score" = "centile",
"College education" = "college",
"Median income" = "income",
"Population" = "adultpop"
)
shinyUI(navbarPage("Superzip", id="nav",
tabPanel("Interactive map",
div(class="outer",
tags$head(
# Include our custom CSS
includeCSS("styles.css"),
includeScript("gomap.js")
),
leafletMap("map", width="100%", height="100%",
initialTileLayer = "//{s}.tiles.mapbox.com/v3/jcheng.map-5ebohr46/{z}/{x}/{y}.png",
initialTileLayerAttribution = HTML('Maps by <a href="http://www.mapbox.com/">Mapbox</a>'),
options=list(
center = c(37.45, -93.85),
zoom = 4,
maxBounds = list(list(15.961329,-129.92981), list(52.908902,-56.80481)) # Show US only
)
),
absolutePanel(id = "controls", class = "modal", fixed = TRUE, draggable = TRUE,
top = 60, left = "auto", right = 20, bottom = "auto",
width = 330, height = "auto",
h2("ZIP explorer"),
selectInput("color", "Color", vars),
selectInput("size", "Size", vars, selected = "adultpop"),
conditionalPanel("input.color == 'superzip' || input.size == 'superzip'",
# Only prompt for threshold when coloring or sizing by superzip
numericInput("threshold", "SuperZIP threshold (top n percentile)", 5)
),
plotOutput("histCentile", height = 200),
plotOutput("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("states", "States", c("All states"="", structure(state.abb, names=state.name), "Washington, DC"="DC"), multiple=TRUE)
),
column(3,
conditionalPanel("input.states",
selectInput("cities", "Cities", c("All cities"=""), multiple=TRUE)
)
),
column(3,
conditionalPanel("input.states",
selectInput("zipcodes", "Zipcodes", c("All zipcodes"=""), multiple=TRUE)
)
)
),
fluidRow(
column(1,
numericInput("minScore", "Min score", min=0, max=100, value=0)
),
column(1,
numericInput("maxScore", "Max score", min=0, max=100, value=100)
)
),
hr(),
dataTableOutput("ziptable")
),
conditionalPanel("false", icon("crosshair"))
))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment