Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions plots/scatter-lag/implementations/pygal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
""" pyplots.ai
scatter-lag: Lag Plot for Time Series Autocorrelation Diagnosis
Library: pygal 3.1.0 | Python 3.14.3
Quality: 87/100 | Created: 2026-04-12
"""

import numpy as np
import pygal
from pygal.style import Style


# Data — synthetic AR(1) process with moderate positive autocorrelation
np.random.seed(42)
n = 400
phi = 0.78
noise = np.random.normal(0, 1.0, n)
temperature = np.zeros(n)
temperature[0] = 20.0
for i in range(1, n):
temperature[i] = 20.0 + phi * (temperature[i - 1] - 20.0) + noise[i]

lag = 1
y_t = temperature[:-lag]
y_t_lag = temperature[lag:]

# Temporal quartile masks
time_idx = np.arange(len(y_t))
q_bounds = np.percentile(time_idx, [25, 50, 75])

# Build scatter series with interactive tooltips (pygal dict format)
early = [
{"value": (float(y_t[i]), float(y_t_lag[i])), "label": f"Day {i + 1}"}
for i in range(len(y_t))
if time_idx[i] < q_bounds[0]
]
mid_early = [
{"value": (float(y_t[i]), float(y_t_lag[i])), "label": f"Day {i + 1}"}
for i in range(len(y_t))
if q_bounds[0] <= time_idx[i] < q_bounds[1]
]
mid_late = [
{"value": (float(y_t[i]), float(y_t_lag[i])), "label": f"Day {i + 1}"}
for i in range(len(y_t))
if q_bounds[1] <= time_idx[i] < q_bounds[2]
]
late = [
{"value": (float(y_t[i]), float(y_t_lag[i])), "label": f"Day {i + 1}"}
for i in range(len(y_t))
if time_idx[i] >= q_bounds[2]
]

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

# Reference geometry
data_min = float(min(y_t.min(), y_t_lag.min()))
data_max = float(max(y_t.max(), y_t_lag.max()))
pad = (data_max - data_min) * 0.05
ref_start = data_min - pad
ref_end = data_max + pad
ref_line = [(ref_start, ref_start), (ref_end, ref_end)]

# ±1σ envelope around y=x to visualise autocorrelation spread
sigma = float(np.std(y_t_lag - y_t))
upper_env = [(ref_start, ref_start + sigma), (ref_end, ref_end + sigma)]
lower_env = [(ref_start, ref_start - sigma), (ref_end, ref_end - sigma)]

# Warm-to-cool temporal palette: terracotta → amber → teal → navy
font = "DejaVu Sans, Helvetica, Arial, sans-serif"
custom_style = Style(
background="white",
plot_background="#f8f7f5",
foreground="#2a2a2a",
foreground_strong="#1a1a1a",
foreground_subtle="#d5d5d3",
guide_stroke_color="#e0dfdd",
colors=(
"#c25a3c", # Q1 — terracotta
"#d4a028", # Q2 — warm amber
"#2a9d8f", # Q3 — teal
"#264653", # Q4 — deep navy
"#c0bebb", # +1σ envelope
"#c0bebb", # −1σ envelope
"#888886", # y = x reference
),
font_family=font,
title_font_family=font,
title_font_size=56,
label_font_size=42,
major_label_font_size=38,
legend_font_size=32,
legend_font_family=font,
value_font_size=28,
tooltip_font_size=28,
tooltip_font_family=font,
opacity=0.60,
opacity_hover=0.95,
stroke_opacity=0.7,
stroke_opacity_hover=1,
)

# Chart — interactivity enabled for SVG hover tooltips
chart = pygal.XY(
width=4800,
height=2700,
style=custom_style,
title=f"Lag Plot (k={lag}, r={r:.2f}) \u00b7 scatter-lag \u00b7 pygal \u00b7 pyplots.ai",
x_title="y(t)",
y_title=f"y(t+{lag})",
show_legend=True,
legend_at_bottom=True,
legend_at_bottom_columns=5,
legend_box_size=24,
stroke=False,
dots_size=8,
show_x_guides=True,
show_y_guides=True,
x_value_formatter=lambda x: f"{x:.1f}",
value_formatter=lambda y: f"{y:.1f}",
margin_bottom=100,
margin_left=80,
margin_right=30,
margin_top=40,
range=(ref_start, ref_end),
xrange=(ref_start, ref_end),
x_labels_major_count=8,
y_labels_major_count=8,
print_values=False,
print_zeroes=False,
truncate_legend=40,
)

# Temporal quartile scatter series
chart.add("Days 1\u2013100", early, stroke=False, dots_size=9)
chart.add("Days 101\u2013200", mid_early, stroke=False)
chart.add("Days 201\u2013300", mid_late, stroke=False)
chart.add("Days 301\u2013399", late, stroke=False, dots_size=9)

# ±1σ envelope (no legend entry)
env_style = {"width": 3, "dasharray": "6, 8", "linecap": "round"}
chart.add(None, upper_env, stroke=True, show_dots=False, stroke_style=env_style)
chart.add(None, lower_env, stroke=True, show_dots=False, stroke_style=env_style)

# Diagonal reference line y = x
chart.add(
"y = x (\u00b11\u03c3)",
ref_line,
stroke=True,
show_dots=False,
stroke_style={"width": 6, "dasharray": "24, 12", "linecap": "round"},
)

# Save
chart.render_to_png("plot.png")
chart.render_to_file("plot.html")
227 changes: 227 additions & 0 deletions plots/scatter-lag/metadata/pygal.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
library: pygal
specification_id: scatter-lag
created: '2026-04-12T18:12:36Z'
updated: '2026-04-12T18:32:40Z'
generated_by: claude-opus-4-5-20251101
workflow_run: 24313009875
issue: 5251
python_version: 3.14.3
library_version: 3.1.0
preview_url: https://storage.googleapis.com/pyplots-images/plots/scatter-lag/pygal/plot.png
preview_html: https://storage.googleapis.com/pyplots-images/plots/scatter-lag/pygal/plot.html
quality_score: 87
review:
strengths:
- Intentional warm-to-cool temporal palette (terracotta → amber → teal → navy) creates
a compelling visual narrative of time progression within the autocorrelation structure
- All required spec features fully implemented plus value-adding ±1σ envelope around
the diagonal reference line
- Comprehensive explicit font sizing throughout; perfect spec compliance (15/15)
and code quality (10/10)
weaknesses:
- dots_size=8–9 causes overplotting for 400 data points; smaller dots (5–6) with
lower opacity (~0.45) would reduce central cluster overlap
- Terracotta (#c25a3c) and amber (#d4a028) risk confusion for deuteranopic (red-green
colorblind) viewers; palette shift needed for full accessibility
image_description: 'The plot shows a lag scatter plot on a light beige (#f8f7f5)
background. The title reads "Lag Plot (k=1, r=0.75) · scatter-lag · pygal · pyplots.ai"
centered at the top. The X-axis is labeled "y(t)" and the Y-axis "y(t+1)". Four
temporal quartile series are plotted: Days 1–100 in terracotta (brick-red), Days
101–200 in amber/golden-yellow, Days 201–300 in teal/seafoam, and Days 301–399
in deep navy. A dashed gray diagonal reference line (y = x ±1σ) runs from lower-left
to upper-right. Subtle dotted grid lines are visible across the plot. The legend
sits at the bottom with five labeled entries. A clear positive linear autocorrelation
pattern is evident — data points cluster diagonally, confirming the AR(1) process
(r=0.75).'
criteria_checklist:
visual_quality:
score: 25
max: 30
items:
- id: VQ-01
name: Text Legibility
score: 8
max: 8
passed: true
comment: 'All font sizes explicitly set: title=56, labels=42, major=38, legend=32,
value=28; fully readable at 4800x2700 px'
- id: VQ-02
name: No Overlap
score: 5
max: 6
passed: true
comment: Text elements clear; minor dot overlap in dense central cluster expected
for 400 points
- id: VQ-03
name: Element Visibility
score: 4
max: 6
passed: false
comment: dots_size=8-9 for 400 points causes noticeable overplotting; guidelines
suggest size 20-50 with alpha 0.3-0.5 for 300+ points
- id: VQ-04
name: Color Accessibility
score: 3
max: 4
passed: false
comment: Terracotta (#c25a3c) and amber (#d4a028) may be confused by deuteranopic
users; rest of palette is good
- id: VQ-05
name: Layout & Canvas
score: 3
max: 4
passed: true
comment: Good proportions; margins tuned; legend well-anchored at bottom
- id: VQ-06
name: Axis Labels & Title
score: 2
max: 2
passed: true
comment: y(t) / y(t+1) are standard lag-plot notation; title is descriptive
and complete
design_excellence:
score: 15
max: 20
items:
- id: DE-01
name: Aesthetic Sophistication
score: 6
max: 8
passed: true
comment: Intentional warm-to-cool temporal palette with custom beige background;
clearly above defaults, but not quite publication-ready
- id: DE-02
name: Visual Refinement
score: 4
max: 6
passed: true
comment: Subtle guide_stroke_color, beige plot background, legend columns,
margin tuning; limited further by pygal API (no spine removal)
- id: DE-03
name: Data Storytelling
score: 5
max: 6
passed: true
comment: Temporal color gradient is an effective storytelling device; correlation
coefficient in title; ±1σ envelope contextualises autocorrelation spread
spec_compliance:
score: 15
max: 15
items:
- id: SC-01
name: Plot Type
score: 5
max: 5
passed: true
comment: Correct XY scatter for lag plot
- id: SC-02
name: Required Features
score: 4
max: 4
passed: true
comment: Diagonal y=x reference line, temporal point coloring, r-value annotation
in title, configurable lag; bonus ±1σ envelope
- id: SC-03
name: Data Mapping
score: 3
max: 3
passed: true
comment: y(t) on X, y(t+lag) on Y, all data visible
- id: SC-04
name: Title & Legend
score: 3
max: 3
passed: true
comment: Title includes spec-id, library, pyplots.ai, plus informative k and
r values; legend labels match temporal quartiles
data_quality:
score: 14
max: 15
items:
- id: DQ-01
name: Feature Coverage
score: 5
max: 6
passed: true
comment: Clear positive autocorrelation pattern demonstrated; temporal coloring
reveals consistent structure; could include stronger/weaker autocorrelation
variation
- id: DQ-02
name: Realistic Context
score: 5
max: 5
passed: true
comment: Daily temperature readings with AR(1) mean reversion (mu=20C) is
natural and neutral
- id: DQ-03
name: Appropriate Scale
score: 4
max: 4
passed: true
comment: Values 16-25C and phi=0.78 are realistic for daily temperatures
code_quality:
score: 10
max: 10
items:
- id: CQ-01
name: KISS Structure
score: 3
max: 3
passed: true
comment: Clean imports -> data -> plot -> save; no functions or classes
- id: CQ-02
name: Reproducibility
score: 2
max: 2
passed: true
comment: np.random.seed(42) set
- id: CQ-03
name: Clean Imports
score: 2
max: 2
passed: true
comment: numpy, pygal, Style; all used
- id: CQ-04
name: Code Elegance
score: 2
max: 2
passed: true
comment: Four separate list comprehensions for quartiles are verbose but clear;
no fake functionality
- id: CQ-05
name: Output & API
score: 1
max: 1
passed: true
comment: render_to_png('plot.png') correct; HTML export is a pygal bonus
library_mastery:
score: 8
max: 10
items:
- id: LM-01
name: Idiomatic Usage
score: 5
max: 5
passed: true
comment: Dict-format data points with label keys for SVG tooltips, stroke_style
for line customisation, legend_at_bottom_columns — all idiomatic pygal
- id: LM-02
name: Distinctive Features
score: 3
max: 5
passed: true
comment: Pygal-specific dict tooltip data and stroke_style dasharray are library-distinctive;
could leverage pygal interactive SVG more explicitly
verdict: REJECTED
impl_tags:
dependencies: []
techniques:
- hover-tooltips
- html-export
patterns:
- data-generation
- iteration-over-groups
dataprep:
- time-series
styling:
- alpha-blending
Loading