Skip to content

Commit 6b25d07

Browse files
fix(pygal): address review feedback for scatter-lag
Attempt 1/3 - fixes based on AI review
1 parent 3b5e8cc commit 6b25d07

1 file changed

Lines changed: 60 additions & 34 deletions

File tree

  • plots/scatter-lag/implementations

plots/scatter-lag/implementations/pygal.py

Lines changed: 60 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
""" pyplots.ai
1+
"""pyplots.ai
22
scatter-lag: Lag Plot for Time Series Autocorrelation Diagnosis
33
Library: pygal 3.1.0 | Python 3.14.3
44
Quality: 82/100 | Created: 2026-04-12
@@ -23,45 +23,66 @@
2323
y_t = temperature[:-lag]
2424
y_t_lag = temperature[lag:]
2525

26-
# Color points by time index for temporal structure
26+
# Temporal quartile masks
2727
time_idx = np.arange(len(y_t))
28-
quartile_bounds = np.percentile(time_idx, [25, 50, 75])
29-
early = [(float(y_t[i]), float(y_t_lag[i])) for i in range(len(y_t)) if time_idx[i] < quartile_bounds[0]]
28+
q_bounds = np.percentile(time_idx, [25, 50, 75])
29+
30+
# Build scatter series with interactive tooltips (pygal dict format)
31+
early = [
32+
{"value": (float(y_t[i]), float(y_t_lag[i])), "label": f"Day {i + 1}"}
33+
for i in range(len(y_t))
34+
if time_idx[i] < q_bounds[0]
35+
]
3036
mid_early = [
31-
(float(y_t[i]), float(y_t_lag[i]))
37+
{"value": (float(y_t[i]), float(y_t_lag[i])), "label": f"Day {i + 1}"}
3238
for i in range(len(y_t))
33-
if quartile_bounds[0] <= time_idx[i] < quartile_bounds[1]
39+
if q_bounds[0] <= time_idx[i] < q_bounds[1]
3440
]
3541
mid_late = [
36-
(float(y_t[i]), float(y_t_lag[i]))
42+
{"value": (float(y_t[i]), float(y_t_lag[i])), "label": f"Day {i + 1}"}
43+
for i in range(len(y_t))
44+
if q_bounds[1] <= time_idx[i] < q_bounds[2]
45+
]
46+
late = [
47+
{"value": (float(y_t[i]), float(y_t_lag[i])), "label": f"Day {i + 1}"}
3748
for i in range(len(y_t))
38-
if quartile_bounds[1] <= time_idx[i] < quartile_bounds[2]
49+
if time_idx[i] >= q_bounds[2]
3950
]
40-
late = [(float(y_t[i]), float(y_t_lag[i])) for i in range(len(y_t)) if time_idx[i] >= quartile_bounds[2]]
4151

4252
# Correlation coefficient
4353
r = np.corrcoef(y_t, y_t_lag)[0, 1]
4454

45-
# Diagonal reference line (y = x)
55+
# Reference geometry
4656
data_min = float(min(y_t.min(), y_t_lag.min()))
4757
data_max = float(max(y_t.max(), y_t_lag.max()))
48-
margin = (data_max - data_min) * 0.05
49-
ref_start = data_min - margin
50-
ref_end = data_max + margin
58+
pad = (data_max - data_min) * 0.05
59+
ref_start = data_min - pad
60+
ref_end = data_max + pad
5161
ref_line = [(ref_start, ref_start), (ref_end, ref_end)]
5262

53-
# Shared font
54-
font = "DejaVu Sans, Helvetica, Arial, sans-serif"
63+
# ±1σ envelope around y=x to visualise autocorrelation spread
64+
sigma = float(np.std(y_t_lag - y_t))
65+
upper_env = [(ref_start, ref_start + sigma), (ref_end, ref_end + sigma)]
66+
lower_env = [(ref_start, ref_start - sigma), (ref_end, ref_end - sigma)]
5567

56-
# Style
68+
# Warm-to-cool temporal palette: terracotta → amber → teal → navy
69+
font = "DejaVu Sans, Helvetica, Arial, sans-serif"
5770
custom_style = Style(
5871
background="white",
59-
plot_background="#fafafa",
72+
plot_background="#f8f7f5",
6073
foreground="#2a2a2a",
61-
foreground_strong="#2a2a2a",
62-
foreground_subtle="#e0e0e0",
63-
guide_stroke_color="#e0e0e0",
64-
colors=("#1a3a5c", "#306998", "#5a9bd5", "#a3ceed", "#888888"),
74+
foreground_strong="#1a1a1a",
75+
foreground_subtle="#d5d5d3",
76+
guide_stroke_color="#e0dfdd",
77+
colors=(
78+
"#c25a3c", # Q1 — terracotta
79+
"#d4a028", # Q2 — warm amber
80+
"#2a9d8f", # Q3 — teal
81+
"#264653", # Q4 — deep navy
82+
"#c0bebb", # +1σ envelope
83+
"#c0bebb", # −1σ envelope
84+
"#888886", # y = x reference
85+
),
6586
font_family=font,
6687
title_font_family=font,
6788
title_font_size=56,
@@ -72,13 +93,13 @@
7293
value_font_size=28,
7394
tooltip_font_size=28,
7495
tooltip_font_family=font,
75-
opacity=0.55,
76-
opacity_hover=0.90,
77-
stroke_opacity=1,
96+
opacity=0.60,
97+
opacity_hover=0.95,
98+
stroke_opacity=0.7,
7899
stroke_opacity_hover=1,
79100
)
80101

81-
# Chart
102+
# Chart — interactivity enabled for SVG hover tooltips
82103
chart = pygal.XY(
83104
width=4800,
84105
height=2700,
@@ -97,27 +118,32 @@
97118
x_value_formatter=lambda x: f"{x:.1f}",
98119
value_formatter=lambda y: f"{y:.1f}",
99120
margin_bottom=100,
100-
margin_left=60,
101-
margin_right=40,
102-
margin_top=50,
121+
margin_left=80,
122+
margin_right=30,
123+
margin_top=40,
103124
range=(ref_start, ref_end),
104125
xrange=(ref_start, ref_end),
105126
x_labels_major_count=8,
106127
y_labels_major_count=8,
107128
print_values=False,
108129
print_zeroes=False,
109-
js=[],
130+
truncate_legend=40,
110131
)
111132

112-
# Add temporal quartile series
113-
chart.add("Days 1\u2013100", early, stroke=False)
133+
# Temporal quartile scatter series
134+
chart.add("Days 1\u2013100", early, stroke=False, dots_size=9)
114135
chart.add("Days 101\u2013200", mid_early, stroke=False)
115136
chart.add("Days 201\u2013300", mid_late, stroke=False)
116-
chart.add("Days 301\u2013399", late, stroke=False)
137+
chart.add("Days 301\u2013399", late, stroke=False, dots_size=9)
138+
139+
# ±1σ envelope (no legend entry)
140+
env_style = {"width": 3, "dasharray": "6, 8", "linecap": "round"}
141+
chart.add(None, upper_env, stroke=True, show_dots=False, stroke_style=env_style)
142+
chart.add(None, lower_env, stroke=True, show_dots=False, stroke_style=env_style)
117143

118-
# Diagonal reference line
144+
# Diagonal reference line y = x
119145
chart.add(
120-
"y = x",
146+
"y = x (\u00b11\u03c3)",
121147
ref_line,
122148
stroke=True,
123149
show_dots=False,

0 commit comments

Comments
 (0)