Skip to content

Instantly share code, notes, and snippets.

@walkerke
Created June 5, 2025 15:28
Show Gist options
  • Save walkerke/2b62551c5fecfd88575a1b5de8793bda to your computer and use it in GitHub Desktop.
Save walkerke/2b62551c5fecfd88575a1b5de8793bda to your computer and use it in GitHub Desktop.
library(mapgl) # pak::pak("walkerke/mapgl")
library(tidycensus)
library(dplyr)
library(viridisLite)
# Get viridis colors
viridis_colors <- viridis(5)
# Get median household income by county in Texas
tx_income <- get_acs(
geography = "county",
state = "TX",
variables = "B19013_001",
year = 2023,
geometry = TRUE
) %>%
mutate(
# Format income with comma separator
income_formatted = scales::dollar(estimate, accuracy = 1),
# Create income categories for styling
income_category = case_when(
estimate < 50000 ~ "low",
estimate < 75000 ~ "medium",
estimate < 100000 ~ "high",
TRUE ~ "very high"
),
# Calculate margin of error percentage
moe_percent = round((moe / estimate) * 100, 1),
# Calculate percentile for progress bar (0-100)
income_percentile = round(percent_rank(estimate) * 100)
)
# Create the map with an awesome styled popup
mapboxgl(style = mapbox_style("dark"), bounds = tx_income) %>%
add_fill_layer(
id = "income",
source = tx_income,
fill_color = interpolate(
column = "estimate",
values = c(25000, 50000, 75000, 100000, 125000),
stops = viridis_colors,
na_color = "#808080"
),
popup = concat(
'<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); ',
'padding: 20px; border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,0.4); ',
'color: white; font-family: -apple-system, BlinkMacSystemFont, sans-serif; ',
'max-width: 300px; position: relative; overflow: hidden;">',
# CSS animations
'<style>',
'@keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% {
transform: scale(1); } }',
'@keyframes slideIn { from { width: 0%; } to { width: ',
get_column("income_percentile"),
'%; } }',
'@keyframes shimmer { 0% { background-position: -200% center; } 100% {
background-position: 200% center; } }',
'.income-badge { animation: pulse 2s ease-in-out infinite; }',
'.progress-fill { animation: slideIn 1s ease-out forwards; }',
'.shimmer { background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3),
transparent); ',
'background-size: 200% 100%; animation: shimmer 2s linear infinite; }',
'</style>',
# Decorative background element with rotation animation
'<div style="position: absolute; top: -50px; right: -50px; width: 150px; height: 150px;
',
'background: rgba(255,255,255,0.1); border-radius: 50%; ',
'animation: pulse 4s ease-in-out infinite;"></div>',
# County name with icon
'<h2 style="margin: 0 0 15px 0; font-size: 24px; font-weight: 700; ',
'text-shadow: 2px 2px 4px rgba(0,0,0,0.3); display: flex; align-items: center; gap:
10px;">',
'<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2">',
'<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>',
'<circle cx="12" cy="10" r="3"></circle></svg>',
get_column("NAME"),
'</h2>',
# Income display with animated badge
'<div class="income-badge" style="background: rgba(255,255,255,0.2); padding: 15px;
border-radius: 8px; ',
'margin-bottom: 15px; backdrop-filter: blur(10px); position: relative;">',
'<div class="shimmer" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0;
',
'border-radius: 8px; pointer-events: none;"></div>',
'<div style="font-size: 14px; opacity: 0.9; margin-bottom: 5px;">Median Household
Income</div>',
'<div style="font-size: 32px; font-weight: 700; text-shadow: 2px 2px 4px
rgba(0,0,0,0.3);">',
get_column("income_formatted"),
'</div>',
'</div>',
# Stats grid with conditional coloring
'<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom:
15px;">',
# MOE card
'<div style="background: rgba(255,255,255,0.15); padding: 10px; border-radius: 6px;
text-align: center;">',
'<div style="font-size: 12px; opacity: 0.8; margin-bottom: 3px;">Margin of Error</div>',
'<div style="font-size: 18px; font-weight: 600;">±',
get_column("moe_percent"),
'%</div>',
'</div>',
# Category card with conditional background (using viridis-inspired colors)
'<div style="background: ',
'rgba(',
match_expr(
column = "income_category",
values = c("low", "medium", "high", "very high"),
stops = c("68,1,84", "59,82,139", "33,145,140", "253,231,37")
),
',0.3); padding: 10px; border-radius: 6px; text-align: center;">',
'<div style="font-size: 12px; opacity: 0.8; margin-bottom: 3px;">Income Level</div>',
'<div style="font-size: 16px; font-weight: 600; text-transform: uppercase;">',
get_column("income_category"),
'</div>',
'</div>',
'</div>',
# Animated progress bar with cleaner percentile display
'<div style="margin-bottom: 10px;">',
'<div style="font-size: 12px; opacity: 0.8; margin-bottom: 5px;">',
'Percentile: ',
get_column("income_percentile"),
'</div>',
'<div style="background: rgba(0,0,0,0.3); height: 12px; border-radius: 6px; overflow:
hidden; position: relative;">',
'<div class="progress-fill" style="background: linear-gradient(90deg, ',
viridis_colors[1],
' 0%, ',
viridis_colors[2],
' 25%, ',
viridis_colors[3],
' 50%, ',
viridis_colors[4],
' 75%, ',
viridis_colors[5],
' 100%); ',
'height: 100%; width: ',
get_column("income_percentile"),
'%; ',
'box-shadow: 0 0 10px rgba(255,255,255,0.5); border-radius: 6px;"></div>',
'<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; ',
'background: repeating-linear-gradient(45deg, transparent, transparent 10px,
rgba(255,255,255,0.1) 10px, rgba(255,255,255,0.1) 20px); ',
'animation: slideIn 1s ease-out forwards;"></div>',
'</div>',
'</div>',
# Data source footer
'<div style="font-size: 11px; opacity: 0.7; text-align: center; ',
'padding-top: 10px; border-top: 1px solid rgba(255,255,255,0.2);">',
'<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"
style="vertical-align: middle; margin-right: 3px;">',
'<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1
17.93c-3.94-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2
2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1
0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/>',
'</svg>',
'American Community Survey 2023 (5-year estimates)',
'</div>',
'</div>'
)
) %>%
add_continuous_legend(
colors = viridis_colors,
values = c("$25k", "$50k", "$75k", "$100k", "$125k"),
legend_title = "Median Household Income",
position = "bottom-left",
margin_bottom = 30
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment