Skip to content

Commit 367df67

Browse files
feat(bokeh): implement choropleth-basic (#3137)
## Implementation: `choropleth-basic` - bokeh Implements the **bokeh** version of `choropleth-basic`. **File:** `plots/choropleth-basic/implementations/bokeh.py` **Parent Issue:** #3069 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20627498185)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent f5f9a66 commit 367df67

2 files changed

Lines changed: 278 additions & 0 deletions

File tree

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
""" pyplots.ai
2+
choropleth-basic: Choropleth Map with Regional Coloring
3+
Library: bokeh 3.8.1 | Python 3.13.11
4+
Quality: 91/100 | Created: 2025-12-31
5+
"""
6+
7+
import math
8+
9+
from bokeh.io import export_png
10+
from bokeh.models import ColorBar, ColumnDataSource, LabelSet, LogColorMapper
11+
from bokeh.palettes import Blues9
12+
from bokeh.plotting import figure
13+
14+
15+
# Data: US states arranged in a tile grid map layout (all 50 states + DC)
16+
# Grid positions (col, row) - approximating geographic positions
17+
regions = {
18+
"AK": (0, 5),
19+
"HI": (0, 1),
20+
"WA": (1, 5),
21+
"OR": (1, 4),
22+
"CA": (0, 3),
23+
"NV": (1, 3),
24+
"ID": (2, 5),
25+
"MT": (3, 5),
26+
"WY": (3, 4),
27+
"UT": (2, 3),
28+
"AZ": (2, 2),
29+
"CO": (3, 3),
30+
"NM": (3, 2),
31+
"ND": (4, 5),
32+
"SD": (4, 4),
33+
"NE": (4, 3),
34+
"KS": (4, 2),
35+
"OK": (4, 1),
36+
"TX": (3, 1),
37+
"MN": (5, 5),
38+
"IA": (5, 4),
39+
"MO": (5, 3),
40+
"AR": (5, 2),
41+
"LA": (5, 1),
42+
"WI": (6, 5),
43+
"IL": (6, 4),
44+
"IN": (7, 4),
45+
"MI": (7, 5),
46+
"OH": (8, 4),
47+
"KY": (7, 3),
48+
"TN": (6, 3),
49+
"MS": (6, 2),
50+
"AL": (7, 2),
51+
"GA": (8, 2),
52+
"FL": (8, 1),
53+
"SC": (9, 2),
54+
"NC": (9, 3),
55+
"VA": (9, 4),
56+
"WV": (8, 3),
57+
"PA": (10, 4),
58+
"NY": (10, 5),
59+
"VT": (11, 5),
60+
"NH": (11, 4),
61+
"ME": (12, 5),
62+
"MA": (11, 3),
63+
"RI": (12, 3),
64+
"CT": (11, 2),
65+
"NJ": (10, 3),
66+
"DE": (10, 2),
67+
"MD": (9, 1),
68+
"DC": (10, 1),
69+
}
70+
71+
# Population density values (people per sq mile) - realistic ranges
72+
density_values = {
73+
"AK": 1,
74+
"HI": 226,
75+
"CA": 253,
76+
"TX": 112,
77+
"FL": 411,
78+
"NY": 408,
79+
"PA": 286,
80+
"IL": 227,
81+
"OH": 289,
82+
"GA": 185,
83+
"NC": 218,
84+
"MI": 177,
85+
"NJ": 1263,
86+
"VA": 218,
87+
"WA": 117,
88+
"AZ": 64,
89+
"MA": 901,
90+
"TN": 167,
91+
"IN": 189,
92+
"MO": 89,
93+
"MD": 636,
94+
"WI": 108,
95+
"CO": 57,
96+
"MN": 71,
97+
"SC": 173,
98+
"AL": 99,
99+
"LA": 107,
100+
"KY": 114,
101+
"OR": 44,
102+
"OK": 58,
103+
"CT": 733,
104+
"IA": 57,
105+
"MS": 63,
106+
"AR": 58,
107+
"UT": 40,
108+
"NV": 28,
109+
"KS": 36,
110+
"NM": 17,
111+
"NE": 25,
112+
"WV": 74,
113+
"ID": 23,
114+
"ME": 44,
115+
"NH": 154,
116+
"RI": 1061,
117+
"MT": 8,
118+
"DE": 508,
119+
"SD": 12,
120+
"ND": 11,
121+
"VT": 68,
122+
"WY": 6,
123+
# DC intentionally missing to demonstrate missing data handling
124+
}
125+
126+
# Prepare data for rectangles
127+
xs = []
128+
ys = []
129+
widths = []
130+
heights = []
131+
colors = []
132+
abbrevs = []
133+
abbrev_x = []
134+
abbrev_y = []
135+
136+
# Color mapping - use log scale to better differentiate low-density states
137+
min_val = max(1, min(density_values.values())) # Log scale needs min > 0
138+
max_val = max(density_values.values())
139+
palette = list(reversed(Blues9))
140+
color_mapper = LogColorMapper(palette=palette, low=min_val, high=max_val, nan_color="#d0d0d0")
141+
142+
# Process each region - rect is centered, so adjust coords to center of tile
143+
for abbrev, (col, row) in regions.items():
144+
center_x = col * 1.1 + 0.5
145+
center_y = row * 1.1 + 0.5
146+
xs.append(center_x)
147+
ys.append(center_y)
148+
widths.append(1.0)
149+
heights.append(1.0)
150+
abbrev_x.append(center_x)
151+
abbrev_y.append(center_y)
152+
abbrevs.append(abbrev)
153+
154+
if abbrev in density_values:
155+
density = density_values[abbrev]
156+
# Map to color index using log scale for better low-value differentiation
157+
log_min = math.log10(min_val)
158+
log_max = math.log10(max_val)
159+
log_val = math.log10(max(density, 1)) # Ensure positive for log
160+
norm = (log_val - log_min) / (log_max - log_min)
161+
idx = min(int(norm * (len(palette) - 1)), len(palette) - 1)
162+
colors.append(palette[idx])
163+
else:
164+
# Missing data - gray with pattern indication
165+
colors.append("#d0d0d0")
166+
167+
# Create data sources
168+
rect_source = ColumnDataSource(data={"x": xs, "y": ys, "width": widths, "height": heights, "fill_color": colors})
169+
170+
# Determine text colors based on background (using log scale)
171+
text_colors = []
172+
log_min = math.log10(min_val)
173+
log_max = math.log10(max_val)
174+
for abbrev in abbrevs:
175+
if abbrev not in density_values:
176+
text_colors.append("#333333")
177+
else:
178+
density = density_values[abbrev]
179+
log_val = math.log10(max(density, 1))
180+
norm = (log_val - log_min) / (log_max - log_min)
181+
text_colors.append("white" if norm > 0.5 else "#306998")
182+
183+
label_source = ColumnDataSource(data={"x": abbrev_x, "y": abbrev_y, "text": abbrevs, "text_color": text_colors})
184+
185+
# Create figure (4800x2700 for landscape)
186+
p = figure(
187+
width=4800,
188+
height=2700,
189+
title="choropleth-basic · bokeh · pyplots.ai",
190+
x_axis_location=None,
191+
y_axis_location=None,
192+
tools="",
193+
toolbar_location=None,
194+
x_range=(-1, 14),
195+
y_range=(0, 7),
196+
)
197+
198+
# Remove grid and axes
199+
p.grid.grid_line_color = None
200+
p.outline_line_color = None
201+
p.xaxis.visible = False
202+
p.yaxis.visible = False
203+
204+
# Draw rectangles for each state
205+
p.rect(
206+
x="x",
207+
y="y",
208+
width="width",
209+
height="height",
210+
source=rect_source,
211+
fill_color="fill_color",
212+
fill_alpha=0.9,
213+
line_color="white",
214+
line_width=2,
215+
)
216+
217+
# Add state abbreviation labels
218+
labels = LabelSet(
219+
x="x",
220+
y="y",
221+
text="text",
222+
text_color="text_color",
223+
source=label_source,
224+
text_align="center",
225+
text_baseline="middle",
226+
text_font_size="18pt",
227+
text_font_style="bold",
228+
)
229+
p.add_layout(labels)
230+
231+
# Title styling
232+
p.title.text_font_size = "32pt"
233+
p.title.align = "center"
234+
235+
# Color bar for legend - made larger for prominence
236+
color_bar = ColorBar(
237+
color_mapper=color_mapper,
238+
width=60,
239+
height=1200,
240+
location=(0, 0),
241+
title="Population Density (per sq mile)",
242+
title_text_font_size="24pt",
243+
major_label_text_font_size="20pt",
244+
title_standoff=20,
245+
padding=30,
246+
margin=40,
247+
)
248+
p.add_layout(color_bar, "right")
249+
250+
# Save output
251+
export_png(p, filename="plot.png")
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
library: bokeh
2+
specification_id: choropleth-basic
3+
created: '2025-12-31T21:33:44Z'
4+
updated: '2025-12-31T21:41:36Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20627498185
7+
issue: 3069
8+
python_version: 3.13.11
9+
library_version: 3.8.1
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/choropleth-basic/bokeh/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/choropleth-basic/bokeh/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/choropleth-basic/bokeh/plot.html
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent use of Bokeh-specific features including LogColorMapper for better differentiation
17+
of low-density states
18+
- Smart contrast handling with dynamic text colors (white on dark, blue on light
19+
backgrounds)
20+
- Good missing data handling demonstrated with DC shown in gray
21+
- Clean tile grid approach that fits all 50 states + DC in a readable layout
22+
- Well-implemented color bar with logarithmic scale and clear labeling
23+
weaknesses:
24+
- Title format should be exactly "choropleth-basic · bokeh · pyplots.ai" without
25+
the "US Population Density" prefix
26+
- Tile grid approach, while creative and readable, differs from traditional geographic
27+
projection that spec mentions (Robinson for world, Albers for US)

0 commit comments

Comments
 (0)