Skip to content

Commit 38305e7

Browse files
feat(plotnine): implement bubble-map-geographic (#3635)
## Implementation: `bubble-map-geographic` - plotnine Implements the **plotnine** version of `bubble-map-geographic`. **File:** `plots/bubble-map-geographic/implementations/plotnine.py` **Parent Issue:** #3625 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20873959857)* --------- 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 0620c36 commit 38305e7

2 files changed

Lines changed: 517 additions & 0 deletions

File tree

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
""" pyplots.ai
2+
bubble-map-geographic: Bubble Map with Sized Geographic Markers
3+
Library: plotnine 0.15.2 | Python 3.13.11
4+
Quality: 91/100 | Created: 2026-01-10
5+
"""
6+
7+
import numpy as np
8+
import pandas as pd
9+
from plotnine import (
10+
aes,
11+
coord_fixed,
12+
element_blank,
13+
element_line,
14+
element_rect,
15+
element_text,
16+
geom_point,
17+
geom_polygon,
18+
ggplot,
19+
labs,
20+
scale_color_manual,
21+
scale_size_area,
22+
theme,
23+
theme_minimal,
24+
)
25+
26+
27+
# Seed for reproducibility
28+
np.random.seed(42)
29+
30+
# Major world cities with population data (in millions)
31+
cities_data = {
32+
"city": [
33+
"Tokyo",
34+
"Delhi",
35+
"Shanghai",
36+
"Sao Paulo",
37+
"Mexico City",
38+
"Cairo",
39+
"Mumbai",
40+
"Beijing",
41+
"Dhaka",
42+
"Osaka",
43+
"New York",
44+
"Karachi",
45+
"Buenos Aires",
46+
"Istanbul",
47+
"Kolkata",
48+
"Lagos",
49+
"Rio de Janeiro",
50+
"Los Angeles",
51+
"Moscow",
52+
"Paris",
53+
"Bangkok",
54+
"Seoul",
55+
"London",
56+
"Lima",
57+
"Chicago",
58+
"Santiago",
59+
"Sydney",
60+
"Toronto",
61+
"Singapore",
62+
"Dubai",
63+
],
64+
"latitude": [
65+
35.68,
66+
28.61,
67+
31.23,
68+
-23.55,
69+
19.43,
70+
30.04,
71+
19.08,
72+
39.90,
73+
23.81,
74+
34.69,
75+
40.71,
76+
24.86,
77+
-34.60,
78+
41.01,
79+
22.57,
80+
6.52,
81+
-22.91,
82+
34.05,
83+
55.76,
84+
48.86,
85+
13.76,
86+
37.57,
87+
51.51,
88+
-12.05,
89+
41.88,
90+
-33.45,
91+
-33.87,
92+
43.65,
93+
1.35,
94+
25.20,
95+
],
96+
"longitude": [
97+
139.69,
98+
77.21,
99+
121.47,
100+
-46.63,
101+
-99.13,
102+
31.24,
103+
72.88,
104+
116.41,
105+
90.41,
106+
135.50,
107+
-74.01,
108+
67.01,
109+
-58.38,
110+
28.98,
111+
88.36,
112+
3.38,
113+
-43.17,
114+
-118.24,
115+
37.62,
116+
2.35,
117+
100.50,
118+
127.00,
119+
-0.13,
120+
-77.04,
121+
-87.63,
122+
-70.67,
123+
151.21,
124+
-79.38,
125+
103.82,
126+
55.27,
127+
],
128+
"population": [
129+
37.4,
130+
32.9,
131+
29.2,
132+
22.4,
133+
21.8,
134+
21.3,
135+
21.0,
136+
20.9,
137+
22.5,
138+
19.1,
139+
18.8,
140+
16.8,
141+
15.4,
142+
15.6,
143+
15.1,
144+
15.3,
145+
13.5,
146+
12.5,
147+
12.5,
148+
11.0,
149+
10.7,
150+
9.9,
151+
9.5,
152+
11.0,
153+
8.9,
154+
6.8,
155+
5.3,
156+
6.3,
157+
5.9,
158+
3.4,
159+
],
160+
"region": [
161+
"Asia",
162+
"Asia",
163+
"Asia",
164+
"S. America",
165+
"N. America",
166+
"Africa",
167+
"Asia",
168+
"Asia",
169+
"Asia",
170+
"Asia",
171+
"N. America",
172+
"Asia",
173+
"S. America",
174+
"Europe",
175+
"Asia",
176+
"Africa",
177+
"S. America",
178+
"N. America",
179+
"Europe",
180+
"Europe",
181+
"Asia",
182+
"Asia",
183+
"Europe",
184+
"S. America",
185+
"N. America",
186+
"S. America",
187+
"Oceania",
188+
"N. America",
189+
"Asia",
190+
"Asia",
191+
],
192+
}
193+
194+
df = pd.DataFrame(cities_data)
195+
196+
# Simplified continent outlines for basemap
197+
continents = []
198+
199+
# North America
200+
na_lon = [
201+
-170,
202+
-168,
203+
-140,
204+
-125,
205+
-124,
206+
-117,
207+
-105,
208+
-97,
209+
-82,
210+
-77,
211+
-68,
212+
-55,
213+
-52,
214+
-80,
215+
-87,
216+
-97,
217+
-105,
218+
-125,
219+
-145,
220+
-165,
221+
-170,
222+
]
223+
na_lat = [60, 65, 70, 55, 48, 33, 25, 26, 25, 35, 45, 48, 45, 27, 30, 20, 22, 50, 60, 55, 60]
224+
for i in range(len(na_lon)):
225+
continents.append({"continent": "N. America", "order": i, "lon": na_lon[i], "lat": na_lat[i]})
226+
227+
# South America
228+
sa_lon = [-80, -68, -60, -50, -35, -40, -50, -55, -68, -72, -75, -80, -82, -80]
229+
sa_lat = [10, 12, 5, 0, -5, -22, -35, -52, -55, -18, -5, 0, 8, 10]
230+
for i in range(len(sa_lon)):
231+
continents.append({"continent": "S. America", "order": i, "lon": sa_lon[i], "lat": sa_lat[i]})
232+
233+
# Europe
234+
eu_lon = [-10, 0, 10, 20, 30, 40, 50, 60, 50, 35, 25, 20, 10, 0, -10, -10]
235+
eu_lat = [35, 37, 36, 35, 35, 40, 45, 55, 70, 70, 70, 65, 60, 50, 40, 35]
236+
for i in range(len(eu_lon)):
237+
continents.append({"continent": "Europe", "order": i, "lon": eu_lon[i], "lat": eu_lat[i]})
238+
239+
# Africa
240+
af_lon = [-17, -5, 10, 35, 50, 52, 43, 35, 30, 15, 0, -17, -17]
241+
af_lat = [15, 37, 37, 32, 12, 0, -25, -35, -35, -25, 5, 20, 15]
242+
for i in range(len(af_lon)):
243+
continents.append({"continent": "Africa", "order": i, "lon": af_lon[i], "lat": af_lat[i]})
244+
245+
# Asia
246+
as_lon = [60, 80, 100, 120, 140, 145, 140, 130, 105, 100, 80, 60, 45, 30, 25, 30, 35, 50, 60]
247+
as_lat = [55, 70, 75, 70, 55, 45, 35, 30, 0, 5, 10, 25, 30, 35, 42, 55, 70, 70, 55]
248+
for i in range(len(as_lon)):
249+
continents.append({"continent": "Asia", "order": i, "lon": as_lon[i], "lat": as_lat[i]})
250+
251+
# Australia
252+
au_lon = [113, 125, 135, 145, 152, 150, 140, 130, 115, 113]
253+
au_lat = [-22, -15, -12, -15, -25, -38, -38, -33, -35, -22]
254+
for i in range(len(au_lon)):
255+
continents.append({"continent": "Australia", "order": i, "lon": au_lon[i], "lat": au_lat[i]})
256+
257+
df_continents = pd.DataFrame(continents)
258+
259+
# Region color palette (colorblind-safe, alphabetically ordered for plotnine)
260+
region_colors = [
261+
"#9467BD", # Africa - Purple
262+
"#306998", # Asia - Python Blue
263+
"#FFD43B", # Europe - Python Yellow
264+
"#2CA02C", # N. America - Green
265+
"#17BECF", # Oceania - Cyan
266+
"#D62728", # S. America - Red
267+
]
268+
269+
# Create the bubble map
270+
plot = (
271+
ggplot()
272+
# Draw continent polygons as basemap
273+
+ geom_polygon(
274+
aes(x="lon", y="lat", group="continent"),
275+
data=df_continents,
276+
fill="#E0E0E0",
277+
color="#A0A0A0",
278+
size=0.5,
279+
alpha=0.8,
280+
)
281+
# Draw bubble markers sized by population
282+
+ geom_point(aes(x="longitude", y="latitude", color="region", size="population"), data=df, alpha=0.7, stroke=0.5)
283+
# Scale size by area for accurate perception (bubble area proportional to value)
284+
+ scale_size_area(max_size=20, name="Population (M)")
285+
+ scale_color_manual(values=region_colors, name="Region")
286+
+ coord_fixed(ratio=1.0, xlim=(-180, 180), ylim=(-60, 80))
287+
+ labs(
288+
title="World City Populations · bubble-map-geographic · plotnine · pyplots.ai",
289+
x="Longitude (°)",
290+
y="Latitude (°)",
291+
)
292+
+ theme_minimal()
293+
+ theme(
294+
figure_size=(16, 9),
295+
plot_title=element_text(size=24, weight="bold"),
296+
axis_title=element_text(size=20),
297+
axis_text=element_text(size=16),
298+
legend_title=element_text(size=18),
299+
legend_text=element_text(size=14),
300+
legend_position="right",
301+
panel_grid_major=element_line(color="#CCCCCC", size=0.3, alpha=0.5),
302+
panel_grid_minor=element_blank(),
303+
panel_background=element_rect(fill="#D4E8F7", alpha=0.5), # Ocean color
304+
)
305+
)
306+
307+
# Save at 300 DPI for 4800x2700 px output
308+
plot.save("plot.png", dpi=300, verbose=False)

0 commit comments

Comments
 (0)