|
library(shiny) |
|
library(mapgl) |
|
library(tidycensus) |
|
library(bslib) |
|
options(tigris_use_cache = TRUE) |
|
|
|
# Get LA income data |
|
la_income <- get_acs( |
|
geography = "tract", |
|
variables = "B19013_001", |
|
state = "CA", |
|
county = "Los Angeles", |
|
geometry = TRUE |
|
) |
|
|
|
# Define reusable expressions |
|
income_colors <- interpolate( |
|
column = "estimate", |
|
values = c(20000, 50000, 80000, 120000, 200000), |
|
stops = c("#feedde", "#fdbe85", "#fd8d3c", "#d94701", "#7f2704"), |
|
na_color = "lightgray" |
|
) |
|
|
|
income_height <- interpolate( |
|
column = "estimate", |
|
values = c(20000, 50000, 80000, 120000, 200000), |
|
stops = c(0, 500, 2000, 5000, 12000) |
|
) |
|
|
|
ui <- page_fillable( |
|
tags$style(HTML(" |
|
.tilt-btn { |
|
position: fixed; |
|
top: 20px; |
|
right: 20px; |
|
z-index: 1000; |
|
padding: 12px 24px; |
|
background: #0891b2; |
|
border: none; |
|
border-radius: 8px; |
|
color: white; |
|
font-size: 14px; |
|
font-weight: 600; |
|
cursor: pointer; |
|
box-shadow: 0 4px 12px rgba(0,0,0,0.2); |
|
} |
|
.tilt-btn:hover { |
|
background: #0e7490; |
|
} |
|
")), |
|
mapboxglOutput("map", height = "100%"), |
|
actionButton("toggle_3d", "Toggle 3D", class = "tilt-btn") |
|
) |
|
|
|
server <- function(input, output, session) { |
|
is_3d <- reactiveVal(FALSE) |
|
|
|
output$map <- renderMapboxgl({ |
|
mapboxgl( |
|
style = mapbox_style("light"), |
|
center = c(-118.25, 34.05), |
|
zoom = 9, |
|
pitch = 0, |
|
bearing = 0 |
|
) |> |
|
add_source( |
|
id = "income-source", |
|
data = la_income, |
|
tolerance = 0 |
|
) |> |
|
add_fill_extrusion_layer( |
|
id = "income", |
|
source = "income-source", |
|
fill_extrusion_color = income_colors, |
|
fill_extrusion_height = 0, |
|
fill_extrusion_opacity = 0.8, |
|
tooltip = "estimate" |
|
) |> |
|
add_legend( |
|
legend_title = "Median Household Income", |
|
values = c("$20k", "$50k", "$80k", "$120k", "$200k+"), |
|
colors = c("#feedde", "#fdbe85", "#fd8d3c", "#d94701", "#7f2704"), |
|
position = "bottom-left", |
|
width = "300px" |
|
) |
|
}) |
|
|
|
observeEvent(input$toggle_3d, { |
|
current <- is_3d() |
|
is_3d(!current) |
|
|
|
center <- input$map_center |
|
zoom <- input$map_zoom |
|
|
|
if (!current) { |
|
# Switch to 3D |
|
mapboxgl_proxy("map") |> |
|
set_paint_property("income", "fill-extrusion-height", income_height) |> |
|
ease_to( |
|
center = c(center$lng, center$lat), |
|
zoom = zoom, |
|
pitch = 60, |
|
bearing = -20, |
|
duration = 1000 |
|
) |
|
} else { |
|
# Switch to 2D |
|
mapboxgl_proxy("map") |> |
|
set_paint_property("income", "fill-extrusion-height", 0) |> |
|
ease_to( |
|
center = c(center$lng, center$lat), |
|
zoom = zoom, |
|
pitch = 0, |
|
bearing = 0, |
|
duration = 1000 |
|
) |
|
} |
|
}) |
|
} |
|
|
|
shinyApp(ui, server) |