# these could be function parameters, but I'm not going to pretend that this code is that reusable.
gdf = us_states_data
variable = 'smoking_rate'
colormap = 'YlOrBr'
color_bar_label = f'cigarette smoking rate,\n% of adults'
# main plot title
plot_title = 'Adult Cigarette Smoking Rates by US State (2022)'
subtitle = '% of adults >= 18 who smoked >= 1 cigarette in last 30 days & >= 100 cigarettes in lifetime'
subtitle_font_size = 16
title_font_size = 30
source_notes = 'Source: American Lung Association,\n CDC Behavioral Risk Factor Surveillance System'
note_font_size = 16
# color of state borders and arrows between small state labels and their state centroids.
edgecolor = '0.2'
# properties for state & state-data labels
label_alpha = 0.9
label_size = 14
# function to get label text for state & state-data labels (centralize config)
def get_label_text(row):
usps = row['usps']
value = row[variable]
return f"{usps}\n{value:.1%}"
# Apply binned colors
gdf = makeColorColumn(gdf, variable, colormap)
# Join USPS abbreviations with gdf using state_to_abbr
gdf = gdf.merge(state_to_abbr, left_on='state', right_on='state', how='left')
# Separate Alaska, Hawaii, and mainland states
mainland = gdf[~gdf['state'].isin(['Alaska', 'Hawaii'])].copy()
# Reproject mainland states to Albers Equal Area projection
mainland = mainland.to_crs('EPSG:2163')
# create figure and axes for with Matplotlib for main map
fig, ax = plt.subplots(1, figsize=(18, 14))
# remove the axis box from the main map
ax.axis('off')
# TITLE CONFIGURATION (this is always going to require some manual tweaking)
text = ax.text(0.01, 0.98, subtitle, fontsize=subtitle_font_size, transform=ax.transAxes)
ex = text.get_window_extent()
x, y = text.get_position()
t = transforms.offset_copy(text._transform, y=ex.height + 10, units='dots')
ax.text(x, y, plot_title, fontsize=title_font_size, fontweight='bold', transform=t)
# add colorbar axes to the figure
# This will take some iterating to get it where you want it [l,b,w,h] right
# l:left, b:bottom, w:width, h:height; in normalized unit (0-1)
cbax = fig.add_axes([0.92, 0.24, 0.03, 0.31])
# place color bar label on bottom
cbax.text(0.5, -0.15, color_bar_label, fontdict={'fontsize': '15', 'fontweight' : 'bold'}, transform=cbax.transAxes, ha='center')
# Create a continuous colormap
base_cmap = mpl.colormaps.get_cmap(colormap)
vmin = gdf[variable].min()
vmax = gdf[variable].max()
norm = mpl.colors.Normalize(vmin=vmin, vmax=vmax)
sm = plt.cm.ScalarMappable(cmap=base_cmap, norm=norm)
sm._A = []
# Create colorbar with custom formatting for percentages
cbar_fmt = FuncFormatter(lambda x, p: format(x, '.0%'))
cbar = fig.colorbar(sm, cax=cbax, format=cbar_fmt, spacing='proportional')
tick_font_size = 15
cbax.tick_params(labelsize=tick_font_size)
# plot mainland states
mainland.plot(color=mainland['c_hex'], linewidth=0.8, ax=ax, edgecolor=edgecolor)
# Define small states that need external labels with arrows
# Offsets are in projected coordinate units (meters for EPSG:2163)
# NOTE: all position adjustments are highly style-dependent.
# Any changes to font/size will require calibration.
small_states = {
'Vermont': {'offset': (-250000, 200000), 'ha': 'right'},
'New Hampshire': {'offset': (-70000, 270000), 'ha': 'right'},
'Massachusetts': {'offset': (200000, 180000), 'ha': 'left'},
'Rhode Island': {'offset': (390000, 30000), 'ha': 'left'},
'Connecticut': {'offset': (200000, -60000), 'ha': 'left'},
'New Jersey': {'offset': (120000, -70000), 'ha': 'left'},
'Delaware': {'offset': (180000, -140000), 'ha': 'left'},
'Maryland': {'offset': (280000, -330000), 'ha': 'left'},
'District of Columbia': {'offset': (590000, 15000), 'ha': 'left'},
}
# States that need custom positioning without arrows
custom_positioned_states = {
'Florida': {'offset': (90000, 0), 'ha': 'center'}, # Move north to center in peninsula
'Louisiana': {'offset': (-50000, 0), 'ha': 'center'},
'Michigan': {'offset': (50000, -50000), 'ha': 'center'},
'West Virginia': {'offset': (-10000, 0), 'ha': 'center'},
}
# Add labels to mainland states
for idx, row in mainland.iterrows():
state_name = row['state']
usps = row['usps']
value = row[variable]
x, y = row['geometry'].centroid.x, row['geometry'].centroid.y
bg_color = row['c_hex']
text_color = get_text_color(bg_color)
label = get_label_text(row)
if state_name in small_states:
# Use annotate with arrow for small states
# Calculate absolute position for text
config = small_states[state_name]
text_x = x + config['offset'][0]
text_y = y + config['offset'][1]
ax.annotate(label, xy=(x, y), xytext=(text_x, text_y),
xycoords='data', textcoords='data',
fontsize=label_size, ha=config['ha'], va='center', color=text_color, weight='bold',
bbox=dict(boxstyle='round,pad=0.4', facecolor=bg_color, edgecolor='gray', alpha=label_alpha), arrowprops=dict(arrowstyle='-', color=edgecolor, lw=1.2))
elif state_name in custom_positioned_states:
# Custom positioned labels without arrows or bounding boxes
config = custom_positioned_states[state_name]
label_x = x + config['offset'][0]
label_y = y + config['offset'][1]
ax.text(label_x, label_y, label, fontsize=label_size, ha=config['ha'], va='center',
color=text_color, weight='bold')
else:
# Place label at centroid for larger states (no arrow or bounding box)
ax.text(x, y, label, fontsize=label_size, ha='center', va='center', color=text_color, weight='bold',)
# Add Alaska Axis: (x, y, width, height) inside the main map
akax = fig.add_axes([0.1, 0.17, 0.2, 0.19])
akax.axis('off')
# Need to clip before projecting, then project
alaska = gdf[gdf['state'] == 'Alaska'].copy()
# polygon to clip western islands (in WGS84 coordinates)
ak_polygon = Polygon([(-170,50),(-170,72),(-140, 72),(-140,50)])
alaska = alaska.clip(ak_polygon)
# Now project the clipped version for display
alaska = alaska.to_crs('EPSG:3338')
alaska.plot(color=alaska['c_hex'].iloc[0], linewidth=0.8, ax=akax, edgecolor=edgecolor)
# Add Alaska label using centroid from projected clipped geometry
ak_centroid = alaska.geometry.centroid.iloc[0]
ak_bg_color = alaska['c_hex'].iloc[0]
ak_text_color = get_text_color(ak_bg_color)
ak_label = get_label_text(alaska.iloc[0])
# no need for bounding box around label, AK is large enough to be visible
akax.text(ak_centroid.x, ak_centroid.y, ak_label, fontsize=label_size, ha='center', va='center',
color=ak_text_color, weight='bold')
# Add Hawaii Axis(x, y, width, height)
hiax = fig.add_axes([.28, 0.20, 0.1, 0.1])
hiax.axis('off')
# Need to clip before projecting, then project
hawaii = gdf[gdf['state'] == 'Hawaii'].copy()
# clipping polygon for Hawaii (in WGS84 coordinates)
hi_polygon = Polygon([(-160,0),(-160,90),(-120,90),(-120,0)])
hawaii = hawaii.clip(hi_polygon)
# Now project the clipped version for display
hawaii = hawaii.to_crs('EPSG:32604')
_ = hawaii.plot(color=hawaii['c_hex'].iloc[0], linewidth=0.8, ax=hiax, edgecolor=edgecolor)
# Add Hawaii label - position to the right of the Big Island
# Get bounds to position label to the right
hi_bounds = hawaii.geometry.bounds.iloc[0]
hi_label_x = hi_bounds['maxx'] + 50000 # 50km to the right of the Big Island
hi_label_y = (hi_bounds['miny'] + hi_bounds['maxy']) / 2 - 75000
hi_bg_color = hawaii['c_hex'].iloc[0]
hi_text_color = get_text_color(hi_bg_color)
hi_label = get_label_text(hawaii.iloc[0])
_ = hiax.text(hi_label_x, hi_label_y, hi_label, fontsize=label_size, ha='left', va='center',
color=hi_text_color, weight='bold',
bbox=dict(boxstyle='round,pad=0.4', facecolor=hi_bg_color, edgecolor='gray', alpha=label_alpha))
# SOURCE NOTES (will always require some manual tweaking)
text = ax.text(0.01, -0.07, source_notes, fontsize=note_font_size, transform=ax.transAxes)
# website link
ax.text(0.85, -0.05, 'aaronjbecker.com', fontweight='bold', fontsize=title_font_size, transform=ax.transAxes)
plt.show()