Skip to content

Commit 9d7fee4

Browse files
update(hexbin-basic): altair — comprehensive quality review (#4317)
## Summary Updated **altair** implementation for **hexbin-basic**. **Changes:** Comprehensive quality review ### Changes - Enhanced hexbin transform approach - Better Vega-Lite idioms - Improved color encoding and sizing ## Test Plan - [x] Preview images uploaded to GCS staging - [x] Implementation file passes ruff format/check - [x] Metadata YAML updated with current versions - [ ] Automated review triggered --- Generated with [Claude Code](https://claude.com/claude-code) `/update` command --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent bd2fb1c commit 9d7fee4

2 files changed

Lines changed: 337 additions & 45 deletions

File tree

plots/hexbin-basic/implementations/altair.py

Lines changed: 120 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
""" pyplots.ai
22
hexbin-basic: Basic Hexbin Plot
3-
Library: altair 6.0.0 | Python 3.13.11
4-
Quality: 72/100 | Created: 2025-12-23
3+
Library: altair 6.0.0 | Python 3.14.3
4+
Quality: 92/100 | Updated: 2026-02-21
55
"""
66

77
import altair as alt
88
import numpy as np
99
import pandas as pd
1010

1111

12-
# Data - GPS coordinates showing traffic density in a metropolitan area
13-
# Simulating vehicle GPS pings across different urban zones
12+
# Data - GPS coordinates showing traffic density in Seattle
1413
np.random.seed(42)
1514

1615
n_points = 5000
1716

18-
# Downtown core - highest density (longitude/latitude offsets from city center)
19-
downtown_lon = np.random.randn(n_points // 2) * 0.008 + (-122.335)
20-
downtown_lat = np.random.randn(n_points // 2) * 0.006 + 47.608
17+
# Downtown core - highest density (tight cluster for strong density peak)
18+
downtown_lon = np.random.randn(n_points // 2) * 0.006 + (-122.335)
19+
downtown_lat = np.random.randn(n_points // 2) * 0.005 + 47.608
2120

2221
# Shopping district - secondary hotspot
2322
shopping_lon = np.random.randn(n_points // 3) * 0.005 + (-122.315)
@@ -30,43 +29,135 @@
3029
longitude = np.concatenate([downtown_lon, shopping_lon, industrial_lon])
3130
latitude = np.concatenate([downtown_lat, shopping_lat, industrial_lat])
3231

33-
df = pd.DataFrame({"longitude": longitude, "latitude": latitude})
32+
# Hexagonal binning - compute hex grid positions and counts
33+
hex_radius = 0.002
34+
dx = hex_radius * np.sqrt(3)
35+
dy = hex_radius * 1.5
3436

35-
# Plot - 2D density binning using mark_rect with binning transform
36-
# Altair doesn't have native hexbin, so we use rectangular binning heatmap
37-
chart = (
38-
alt.Chart(df)
39-
.mark_rect(stroke="white", strokeWidth=0.5)
37+
row_idx = np.round(latitude / dy).astype(int)
38+
shift = (row_idx % 2) * 0.5
39+
col_adj = np.round((longitude / dx) - shift).astype(int)
40+
41+
hex_cx = (col_adj + shift) * dx
42+
hex_cy = row_idx * dy
43+
44+
hexbins = pd.DataFrame({"lon": hex_cx, "lat": hex_cy}).groupby(["lon", "lat"]).size().reset_index(name="count")
45+
46+
# Compute pixel size for hexagons to tile cleanly
47+
chart_width, chart_height = 1600, 900
48+
lon_range = hexbins["lon"].max() - hexbins["lon"].min()
49+
lat_range = hexbins["lat"].max() - hexbins["lat"].min()
50+
hex_px_w = dx * (chart_width / lon_range) if lon_range > 0 else 1
51+
hex_px_h = 2 * hex_radius * (chart_height / lat_range) if lat_range > 0 else 1
52+
hex_area = hex_px_w * hex_px_h
53+
54+
# Custom hexagon SVG path (pointy-top)
55+
hex_path = "M0,-1L0.866,-0.5L0.866,0.5L0,1L-0.866,0.5L-0.866,-0.5Z"
56+
57+
# Interactive hover selection — distinctive Altair/Vega-Lite feature
58+
hover = alt.selection_point(on="pointerover", nearest=True, empty=False)
59+
60+
# Hexbin layer with hover-responsive encoding and computed density level
61+
hexbin_layer = (
62+
alt.Chart(hexbins)
63+
.transform_calculate(density="datum.count > 60 ? 'High' : datum.count > 25 ? 'Medium' : 'Low'")
64+
.mark_point(shape=hex_path, filled=True, stroke="white")
4065
.encode(
4166
x=alt.X(
42-
"longitude:Q",
43-
bin=alt.Bin(maxbins=35),
44-
title="Longitude (°W)",
45-
axis=alt.Axis(labelFontSize=18, titleFontSize=22, format=".2f", grid=False),
67+
"lon:Q",
68+
title="Longitude (\u00b0W)",
69+
scale=alt.Scale(zero=False),
70+
axis=alt.Axis(
71+
labelFontSize=18,
72+
titleFontSize=22,
73+
format=".2f",
74+
values=[-122.36, -122.34, -122.32, -122.30],
75+
grid=True,
76+
gridOpacity=0.08,
77+
gridColor="#ccc",
78+
),
4679
),
4780
y=alt.Y(
48-
"latitude:Q",
49-
bin=alt.Bin(maxbins=25),
50-
title="Latitude (°N)",
51-
axis=alt.Axis(labelFontSize=18, titleFontSize=22, format=".3f", grid=False),
81+
"lat:Q",
82+
title="Latitude (\u00b0N)",
83+
scale=alt.Scale(zero=False),
84+
axis=alt.Axis(
85+
labelFontSize=18,
86+
titleFontSize=22,
87+
format=".2f",
88+
values=[47.59, 47.60, 47.61, 47.62, 47.63, 47.64],
89+
grid=True,
90+
gridOpacity=0.08,
91+
gridColor="#ccc",
92+
),
5293
),
5394
color=alt.Color(
54-
"count():Q",
55-
scale=alt.Scale(scheme="viridis"),
95+
"count:Q",
96+
scale=alt.Scale(scheme="viridis", type="symlog"),
5697
legend=alt.Legend(
57-
title="Vehicle Count", titleFontSize=20, labelFontSize=16, gradientLength=350, gradientThickness=25
98+
title="Vehicle Count",
99+
titleFontSize=20,
100+
labelFontSize=16,
101+
gradientLength=350,
102+
gradientThickness=25,
103+
orient="right",
104+
offset=20,
105+
titlePadding=10,
58106
),
59107
),
108+
size=alt.value(hex_area),
109+
strokeWidth=alt.condition(hover, alt.value(2.5), alt.value(0.4)),
60110
tooltip=[
61-
alt.Tooltip("longitude:Q", title="Longitude", bin=True),
62-
alt.Tooltip("latitude:Q", title="Latitude", bin=True),
63-
alt.Tooltip("count():Q", title="Vehicles"),
111+
alt.Tooltip("lon:Q", title="Longitude", format=".4f"),
112+
alt.Tooltip("lat:Q", title="Latitude", format=".4f"),
113+
alt.Tooltip("count:Q", title="Vehicles"),
114+
alt.Tooltip("density:N", title="Density Level"),
64115
],
65116
)
117+
.add_params(hover)
118+
)
119+
120+
# Cluster annotation labels for data storytelling
121+
annotations = pd.DataFrame(
122+
{
123+
"lon": [-122.335, -122.303, -122.360],
124+
"lat": [47.587, 47.633, 47.648],
125+
"label": ["Downtown Core", "Shopping District", "Industrial Zone"],
126+
}
127+
)
128+
129+
text_bg = (
130+
alt.Chart(annotations)
131+
.mark_text(fontSize=16, fontWeight="bold", color="#f9f9fb", strokeWidth=4, stroke="#f9f9fb")
132+
.encode(x="lon:Q", y="lat:Q", text="label:N")
133+
)
134+
135+
text_fg = (
136+
alt.Chart(annotations)
137+
.mark_text(fontSize=16, fontWeight="bold", color="#2a2a2a")
138+
.encode(x="lon:Q", y="lat:Q", text="label:N")
139+
)
140+
141+
# Compose layers with title, subtitle, and refined styling
142+
chart = (
143+
alt.layer(hexbin_layer, text_bg, text_fg)
66144
.properties(
67-
width=1600, height=900, title=alt.Title("hexbin-basic · altair · pyplots.ai", fontSize=28, anchor="middle")
145+
width=chart_width,
146+
height=chart_height,
147+
title=alt.Title(
148+
"hexbin-basic \u00b7 altair \u00b7 pyplots.ai",
149+
fontSize=28,
150+
anchor="middle",
151+
color="#222",
152+
subtitle="Seattle metropolitan traffic density \u2014 5,000 GPS vehicle observations",
153+
subtitleFontSize=18,
154+
subtitleColor="#666",
155+
subtitlePadding=8,
156+
),
157+
padding={"left": 20, "right": 20, "top": 10, "bottom": 10},
68158
)
69-
.configure_view(strokeWidth=0)
159+
.configure_view(strokeWidth=0, fill="#f9f9fb")
160+
.configure_axis(domainColor="#aaa", tickColor="#aaa", labelColor="#555", titleColor="#333")
70161
)
71162

72163
# Save

0 commit comments

Comments
 (0)