Skip to content

Commit b7c767c

Browse files
fix(letsplot): address review feedback for stereonet-equal-area
Attempt 2/3 - fixes based on AI review
1 parent 808a1ef commit b7c767c

1 file changed

Lines changed: 36 additions & 8 deletions

File tree

plots/stereonet-equal-area/implementations/letsplot.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
""" pyplots.ai
1+
"""pyplots.ai
22
stereonet-equal-area: Structural Geology Stereonet (Equal-Area Projection)
33
Library: letsplot 4.9.0 | Python 3.14.3
44
Quality: 84/100 | Created: 2026-03-15
@@ -16,13 +16,15 @@
1616
geom_density2d,
1717
geom_path,
1818
geom_point,
19+
geom_polygon,
1920
geom_segment,
2021
geom_text,
2122
ggplot,
2223
ggsize,
2324
labs,
2425
layer_tooltips,
2526
scale_color_manual,
27+
scale_fill_manual,
2628
scale_x_continuous,
2729
scale_y_continuous,
2830
theme,
@@ -104,8 +106,13 @@
104106
sc = 1.0 / np.sqrt(1.0 - pz)
105107
gx = px * sc
106108
gy = py * sc
107-
for j in range(len(alphas)):
108-
gc_rows.append({"x": gx[j], "y": gy[j], "group": i, "feature_type": feature_types[i]})
109+
# Clip to unit circle boundary
110+
r2 = gx**2 + gy**2
111+
mask_inside = r2 <= 1.0
112+
gx_clip = gx[mask_inside]
113+
gy_clip = gy[mask_inside]
114+
for j in range(len(gx_clip)):
115+
gc_rows.append({"x": gx_clip[j], "y": gy_clip[j], "group": i, "feature_type": feature_types[i]})
109116

110117
gc_df = pd.DataFrame(gc_rows)
111118

@@ -129,9 +136,10 @@
129136
# Cardinal direction labels
130137
label_df = pd.DataFrame({"x": [0, 1.09, 0, -1.09], "y": [1.09, 0, -1.09, 0], "label": ["N", "E", "S", "W"]})
131138

132-
# Mean pole annotations per feature type (circular mean for strike)
139+
# Mean pole annotations and confidence ellipses per feature type
133140
mean_annotations = []
134-
for ft in ["Bedding", "Joint", "Fault", "Foliation"]:
141+
ellipse_rows = []
142+
for idx, ft in enumerate(["Bedding", "Joint", "Fault", "Foliation"]):
135143
mask = np.array(feature_types) == ft
136144
mx = pole_x[mask].mean()
137145
my = pole_y[mask].mean()
@@ -140,7 +148,22 @@
140148
ms = np.degrees(np.arctan2(np.sin(s_rad).mean(), np.cos(s_rad).mean())) % 360
141149
md = dips[mask].mean()
142150
mean_annotations.append({"x": mx, "y": my, "label": f"{ft}\n{ms:.0f}/{md:.0f}"})
151+
# Confidence ellipse (1-sigma) around each cluster
152+
px_ft = pole_x[mask]
153+
py_ft = pole_y[mask]
154+
cov = np.cov(px_ft, py_ft)
155+
eigvals, eigvecs = np.linalg.eigh(cov)
156+
angle = np.arctan2(eigvecs[1, 1], eigvecs[0, 1])
157+
t = np.linspace(0, 2 * np.pi, 40)
158+
ex = np.sqrt(eigvals[1]) * np.cos(t)
159+
ey = np.sqrt(eigvals[0]) * np.sin(t)
160+
rx = mx + ex * np.cos(angle) - ey * np.sin(angle)
161+
ry = my + ex * np.sin(angle) + ey * np.cos(angle)
162+
for j in range(len(t)):
163+
ellipse_rows.append({"x": rx[j], "y": ry[j], "group": idx, "feature_type": ft})
164+
143165
mean_df = pd.DataFrame(mean_annotations)
166+
ellipse_df = pd.DataFrame(ellipse_rows)
144167

145168
# Colorblind-safe palette (4 feature types)
146169
color_values = ["#306998", "#CC79A7", "#E69F00", "#009E73"]
@@ -169,6 +192,10 @@
169192
)
170193
# Great circles
171194
+ geom_path(aes(x="x", y="y", group="group", color="feature_type"), data=gc_df, size=0.4, alpha=0.3)
195+
# Confidence ellipses around each cluster
196+
+ geom_polygon(
197+
aes(x="x", y="y", group="group", fill="feature_type"), data=ellipse_df, alpha=0.12, size=0, show_legend=False
198+
)
172199
# Poles to planes with tooltips
173200
+ geom_point(aes(x="x", y="y", color="feature_type"), data=poles_df, size=5, alpha=0.85, tooltips=pole_tooltips)
174201
# Mean pole markers (larger, outlined)
@@ -177,9 +204,9 @@
177204
+ geom_text(
178205
aes(x="x", y="y", label="label"),
179206
data=mean_df,
180-
size=11,
181-
color="#333333",
182-
nudge_y=-0.08,
207+
size=14,
208+
color="#222222",
209+
nudge_y=-0.12,
183210
fontface="italic",
184211
show_legend=False,
185212
)
@@ -191,6 +218,7 @@
191218
+ geom_text(aes(x="x", y="y", label="label"), data=label_df, size=18, color="#333333", fontface="bold")
192219
# Color scale
193220
+ scale_color_manual(values=color_values, name="Feature Type")
221+
+ scale_fill_manual(values=color_values)
194222
+ coord_fixed()
195223
+ scale_x_continuous(limits=[-1.25, 1.25])
196224
+ scale_y_continuous(limits=[-1.25, 1.25])

0 commit comments

Comments
 (0)