|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | map-tile-background: Map with Tile Background |
3 | | -Library: plotnine 0.15.2 | Python 3.13.11 |
4 | | -Quality: 90/100 | Created: 2026-01-20 |
| 3 | +Library: plotnine 0.15.4 | Python 3.13.13 |
| 4 | +Quality: 82/100 | Updated: 2026-05-27 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
| 8 | +import sys |
| 9 | + |
7 | 10 | import numpy as np |
8 | 11 | import pandas as pd |
9 | | -from plotnine import ( |
| 12 | + |
| 13 | + |
| 14 | +# Work around naming conflict with plotnine.py script and plotnine package |
| 15 | +script_dir = os.path.dirname(os.path.abspath(__file__)) |
| 16 | +if script_dir in sys.path: |
| 17 | + sys.path.remove(script_dir) |
| 18 | +if "" in sys.path: |
| 19 | + sys.path.remove("") |
| 20 | +if "." in sys.path: |
| 21 | + sys.path.remove(".") |
| 22 | + |
| 23 | +from plotnine import ( # noqa: E402 |
10 | 24 | aes, |
11 | 25 | annotate, |
12 | 26 | coord_fixed, |
|
27 | 41 | ) |
28 | 42 |
|
29 | 43 |
|
30 | | -# Seed for reproducibility |
| 44 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 45 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 46 | +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
| 47 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 48 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 49 | +INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" |
| 50 | + |
| 51 | +ANYPLOT_PALETTE = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477", "#99B314"] |
| 52 | + |
31 | 53 | np.random.seed(42) |
32 | 54 |
|
33 | 55 | # San Francisco Bay Area landmarks with visitor counts (thousands per year) |
|
106 | 128 | df = pd.DataFrame(landmarks_data) |
107 | 129 |
|
108 | 130 | # Simulated tile-style background using grid rectangles |
109 | | -# This creates a visual effect similar to map tiles |
110 | 131 | lon_min, lon_max = -122.52, -122.36 |
111 | 132 | lat_min, lat_max = 37.755, 37.84 |
112 | 133 |
|
113 | | -# Create grid cells that simulate map tiles (10x8 grid) |
114 | 134 | n_tiles_x = 10 |
115 | 135 | n_tiles_y = 8 |
116 | 136 | tile_width = (lon_max - lon_min) / n_tiles_x |
117 | 137 | tile_height = (lat_max - lat_min) / n_tiles_y |
118 | 138 |
|
119 | | -# Generate tile background with terrain-like coloring |
| 139 | +# Theme-adaptive terrain colors |
| 140 | +water_color = "#B8D4E8" if THEME == "light" else "#1D2E3A" |
| 141 | +land_color = "#E8E4D8" if THEME == "light" else "#2B2820" |
| 142 | +coast_color = "#8A7A6B" if THEME == "light" else "#7A7268" |
| 143 | + |
120 | 144 | tiles = [] |
121 | | -tile_id = 0 |
122 | 145 | for i in range(n_tiles_x): |
123 | 146 | for j in range(n_tiles_y): |
124 | 147 | x_center = lon_min + tile_width * (i + 0.5) |
125 | 148 | y_center = lat_min + tile_height * (j + 0.5) |
126 | 149 |
|
127 | | - # Determine terrain type based on position (simulating land/water) |
128 | | - # Water on east side (bay) and portions of north (golden gate strait) |
129 | 150 | is_water = ( |
130 | 151 | (x_center > -122.39 and y_center < 37.79) |
131 | 152 | or (x_center > -122.44 and y_center > 37.825) |
|
139 | 160 | "ymin": lat_min + tile_height * j, |
140 | 161 | "ymax": lat_min + tile_height * (j + 1), |
141 | 162 | "terrain": "water" if is_water else "land", |
142 | | - "tile_id": tile_id, |
143 | 163 | } |
144 | 164 | ) |
145 | | - tile_id += 1 |
146 | 165 |
|
147 | 166 | df_tiles = pd.DataFrame(tiles) |
148 | 167 |
|
149 | | -# Coastline polygon (San Francisco peninsula outline for visible area) |
| 168 | +# Coastline polygon (San Francisco peninsula outline) |
150 | 169 | coast_coords = [ |
151 | 170 | (-122.52, 37.755), |
152 | 171 | (-122.48, 37.755), |
|
165 | 184 | coastline = [{"region": "sf", "order": i, "lon": c[0], "lat": c[1]} for i, c in enumerate(coast_coords)] |
166 | 185 | df_coast = pd.DataFrame(coastline) |
167 | 186 |
|
168 | | -# Category color palette (colorblind-safe) |
169 | | -category_colors = { |
170 | | - "Cultural": "#9467BD", |
171 | | - "Historic": "#8C564B", |
172 | | - "Landmark": "#306998", |
173 | | - "Museum": "#2CA02C", |
174 | | - "Shopping": "#FFD43B", |
175 | | - "Sports": "#D62728", |
176 | | - "Tourism": "#17BECF", |
177 | | - "Transport": "#FF7F0E", |
178 | | -} |
179 | | - |
180 | | -# Prepare label data with individual position adjustments to avoid overlap |
181 | | -# Select top attractions by visitor count |
182 | | -top_attractions = df[df["visitors"] >= 10000].copy() |
| 187 | +# anyplot palette assigned alphabetically by category |
| 188 | +categories_sorted = sorted(df["category"].unique()) |
| 189 | +category_colors = {cat: ANYPLOT_PALETTE[i] for i, cat in enumerate(categories_sorted)} |
183 | 190 |
|
184 | | -# Create label positions with nudge offsets to prevent overlap |
185 | | -# Labels positioned with significant separation to avoid any touching |
| 191 | +# Labels for top 3 most-visited landmarks (well-separated geographically) |
| 192 | +top3 = df.nlargest(3, "visitors") # Union Square, Fisherman's Wharf, Golden Gate Bridge |
186 | 193 | label_positions = { |
187 | | - "Golden Gate Bridge": {"nudge_x": 0.01, "nudge_y": 0.016}, |
188 | | - "Fisherman's Wharf": {"nudge_x": -0.055, "nudge_y": 0.02}, |
189 | | - "Pier 39": {"nudge_x": 0.045, "nudge_y": -0.025}, |
190 | | - "Union Square": {"nudge_x": 0.025, "nudge_y": 0.015}, |
| 194 | + "Union Square": {"nudge_x": 0.025, "nudge_y": 0.012}, |
| 195 | + "Fisherman's Wharf": {"nudge_x": -0.035, "nudge_y": 0.014}, |
| 196 | + "Golden Gate Bridge": {"nudge_x": 0.015, "nudge_y": 0.014}, |
191 | 197 | } |
192 | 198 |
|
193 | | -# Build label dataframe with adjusted positions |
194 | 199 | label_records = [] |
195 | | -for _, row in top_attractions.iterrows(): |
| 200 | +for _, row in top3.iterrows(): |
196 | 201 | pos = label_positions.get(row["name"], {"nudge_x": 0, "nudge_y": 0.012}) |
197 | 202 | label_records.append({"name": row["name"], "lon": row["lon"] + pos["nudge_x"], "lat": row["lat"] + pos["nudge_y"]}) |
198 | 203 | label_df = pd.DataFrame(label_records) |
199 | 204 |
|
200 | | -# Create the map visualization |
| 205 | +title = "map-tile-background · python · plotnine · anyplot.ai" |
| 206 | + |
201 | 207 | plot = ( |
202 | 208 | ggplot() |
203 | | - # Layer 1: Tile background rectangles |
204 | 209 | + geom_rect( |
205 | 210 | aes(xmin="xmin", xmax="xmax", ymin="ymin", ymax="ymax", fill="terrain"), |
206 | 211 | data=df_tiles, |
207 | | - color="#AAAAAA", |
208 | | - size=0.15, |
209 | | - alpha=0.8, |
| 212 | + color=INK_MUTED, |
| 213 | + size=0.05, |
| 214 | + alpha=0.85, |
210 | 215 | ) |
211 | | - + scale_fill_manual(values={"water": "#B8D4E8", "land": "#E8E4D8"}, guide=None) |
212 | | - # Layer 2: Coastline outline for geographic context |
213 | | - + geom_polygon( |
214 | | - aes(x="lon", y="lat", group="region"), data=df_coast, fill="none", color="#444444", size=1.2, alpha=1.0 |
215 | | - ) |
216 | | - # Layer 3: Data points with size encoding for visitor counts |
217 | | - + geom_point(aes(x="lon", y="lat", color="category", size="visitors"), data=df, alpha=0.85, stroke=1.2) |
218 | | - + scale_size_continuous(range=(5, 20), name="Visitors\n(thousands/yr)") |
| 216 | + + scale_fill_manual(values={"water": water_color, "land": land_color}, guide=None) |
| 217 | + + geom_polygon(aes(x="lon", y="lat", group="region"), data=df_coast, fill="none", color=coast_color, size=0.8) |
| 218 | + + geom_point(aes(x="lon", y="lat", color="category", size="visitors"), data=df, alpha=0.88, stroke=0.4) |
| 219 | + + scale_size_continuous(range=(2, 10), name="Visitors\n(K/yr)") |
219 | 220 | + scale_color_manual(values=category_colors, name="Category") |
220 | | - # Layer 4: Labels for top landmarks (positions pre-adjusted in label_df) |
221 | 221 | + geom_label( |
222 | | - aes(x="lon", y="lat", label="name"), data=label_df, size=9, alpha=0.9, fill="white", label_padding=0.25 |
| 222 | + aes(x="lon", y="lat", label="name"), |
| 223 | + data=label_df, |
| 224 | + size=3.5, |
| 225 | + alpha=0.92, |
| 226 | + fill=ELEVATED_BG, |
| 227 | + color=INK, |
| 228 | + label_padding=0.2, |
223 | 229 | ) |
224 | | - # Attribution for simulated tile background (spec requirement) |
225 | 230 | + annotate( |
226 | 231 | "text", |
227 | | - x=lon_max - 0.01, |
228 | | - y=lat_min + 0.005, |
229 | | - label="Simulated tiles | Data: SF landmarks", |
230 | | - size=7, |
| 232 | + x=lon_max - 0.003, |
| 233 | + y=lat_min + 0.003, |
| 234 | + label="Simulated tiles · SF landmarks", |
| 235 | + size=2.5, |
231 | 236 | ha="right", |
232 | 237 | va="bottom", |
233 | | - color="#666666", |
234 | | - alpha=0.8, |
| 238 | + color=INK_MUTED, |
235 | 239 | ) |
236 | | - # Coordinate system with proper aspect ratio for geographic accuracy |
237 | | - + coord_fixed(ratio=1.3, xlim=(lon_min, lon_max), ylim=(lat_min, lat_max)) |
238 | | - + labs(title="SF Landmarks · map-tile-background · plotnine · pyplots.ai", x="Longitude (°)", y="Latitude (°)") |
| 240 | + + coord_fixed(ratio=1.06, xlim=(lon_min, lon_max), ylim=(lat_min, lat_max)) |
| 241 | + + labs(title=title, x="Longitude (°)", y="Latitude (°)") |
239 | 242 | + theme_minimal() |
240 | 243 | + theme( |
241 | | - figure_size=(16, 9), |
242 | | - plot_title=element_text(size=20, weight="bold", ha="center"), |
243 | | - axis_title=element_text(size=18), |
244 | | - axis_text=element_text(size=14), |
245 | | - legend_title=element_text(size=14), |
246 | | - legend_text=element_text(size=11), |
247 | | - legend_position="right", |
248 | | - legend_box_spacing=0.1, |
249 | | - legend_key_size=16, |
| 244 | + figure_size=(8, 4.5), |
| 245 | + plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), |
| 246 | + panel_background=element_rect(fill=PAGE_BG), |
250 | 247 | panel_grid_major=element_blank(), |
251 | 248 | panel_grid_minor=element_blank(), |
252 | | - panel_background=element_rect(fill="none"), |
253 | | - plot_margin=0.02, |
| 249 | + panel_border=element_rect(color=INK_SOFT, fill=None), |
| 250 | + plot_title=element_text(size=12, weight="bold", ha="center", color=INK), |
| 251 | + axis_title=element_text(size=10, color=INK), |
| 252 | + axis_text=element_text(size=8, color=INK_SOFT), |
| 253 | + legend_title=element_text(size=9, color=INK), |
| 254 | + legend_text=element_text(size=8, color=INK_SOFT), |
| 255 | + legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), |
| 256 | + legend_position="right", |
| 257 | + legend_key_size=10, |
| 258 | + plot_margin=0.01, |
254 | 259 | ) |
255 | 260 | ) |
256 | 261 |
|
257 | | -# Save at 300 DPI for 4800x2700 px output |
258 | | -plot.save("plot.png", dpi=300, verbose=False) |
| 262 | +plot.save(f"plot-{THEME}.png", dpi=400, width=8, height=4.5, units="in", verbose=False) |
0 commit comments