{"spec_id":"scatter-lag","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nscatter-lag: Lag Plot for Time Series Autocorrelation Diagnosis\nLibrary: pygal 3.1.3 | Python 3.13.14\nQuality: 89/100 | Updated: 2026-06-24\n\"\"\"\n\nimport os\nimport sys\n\nimport numpy as np\n\n\n# Avoid name collision: pygal.py filename shadows the pygal package\n_cwd = sys.path[0] if sys.path[0] else \".\"\nif _cwd in sys.path:\n    sys.path.remove(_cwd)\n\nimport pygal\nfrom pygal.style import Style\n\n\n# Theme tokens — Imprint palette, theme-adaptive chrome\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint categorical palette for temporal quartiles + structural reference layers\nPALETTE = (\n    \"#009E73\",  # Q1 — brand green (ALWAYS first)\n    \"#C475FD\",  # Q2 — lavender\n    \"#4467A3\",  # Q3 — blue\n    \"#BD8233\",  # Q4 — ochre\n    INK_MUTED,  # +1σ envelope (structural, muted)\n    INK_MUTED,  # −1σ envelope (structural, muted)\n    INK_SOFT,  # y = x reference diagonal\n)\n\n# Data — synthetic AR(1) hourly temperature process, moderate positive autocorrelation\nnp.random.seed(42)\nn = 400\nphi = 0.78\nnoise = np.random.normal(0, 1.0, n)\ntemperature = np.zeros(n)\ntemperature[0] = 20.0\nfor i in range(1, n):\n    temperature[i] = 20.0 + phi * (temperature[i - 1] - 20.0) + noise[i]\n\nlag = 1\ny_t = temperature[:-lag]\ny_t_lag = temperature[lag:]\n\n# Temporal quartile masks — color by time to reveal temporal structure\ntime_idx = np.arange(len(y_t))\nq_bounds = np.percentile(time_idx, [25, 50, 75])\n\nearly = [\n    {\"value\": (float(y_t[i]), float(y_t_lag[i])), \"label\": f\"Hour {i + 1}\"}\n    for i in range(len(y_t))\n    if time_idx[i] < q_bounds[0]\n]\nmid_early = [\n    {\"value\": (float(y_t[i]), float(y_t_lag[i])), \"label\": f\"Hour {i + 1}\"}\n    for i in range(len(y_t))\n    if q_bounds[0] <= time_idx[i] < q_bounds[1]\n]\nmid_late = [\n    {\"value\": (float(y_t[i]), float(y_t_lag[i])), \"label\": f\"Hour {i + 1}\"}\n    for i in range(len(y_t))\n    if q_bounds[1] <= time_idx[i] < q_bounds[2]\n]\nlate = [\n    {\"value\": (float(y_t[i]), float(y_t_lag[i])), \"label\": f\"Hour {i + 1}\"}\n    for i in range(len(y_t))\n    if time_idx[i] >= q_bounds[2]\n]\n\n# Correlation coefficient for title annotation\nr = np.corrcoef(y_t, y_t_lag)[0, 1]\n\n# Reference geometry — diagonal y = x and ±1σ spread envelope\ndata_min = float(min(y_t.min(), y_t_lag.min()))\ndata_max = float(max(y_t.max(), y_t_lag.max()))\npad = (data_max - data_min) * 0.05\nref_start = data_min - pad\nref_end = data_max + pad\nref_line = [(ref_start, ref_start), (ref_end, ref_end)]\n\nsigma = float(np.std(y_t_lag - y_t))\nupper_env = [(ref_start, ref_start + sigma), (ref_end, ref_end + sigma)]\nlower_env = [(ref_start, ref_start - sigma), (ref_end, ref_end - sigma)]\n\n# Title — length-aware font scaling (baseline 66px for ~67 chars)\ntitle = f\"Lag Plot (k={lag}, r={r:.2f}) · scatter-lag · python · pygal · anyplot.ai\"\ntitle_len = len(title)\ntitle_font_size = round(66 * 67 / title_len) if title_len > 67 else 66\ntitle_font_size = max(title_font_size, 44)\n\nfont = \"DejaVu Sans, Helvetica, Arial, sans-serif\"\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=PALETTE,\n    font_family=font,\n    title_font_family=font,\n    title_font_size=title_font_size,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    legend_font_family=font,\n    value_font_size=36,\n    tooltip_font_size=32,\n    tooltip_font_family=font,\n    opacity=0.45,\n    opacity_hover=0.95,\n    stroke_width=2.5,\n)\n\n# Chart — canvas at 3200×1800 (landscape 16:9, hard contract)\nchart = pygal.XY(\n    width=3200,\n    height=1800,\n    style=custom_style,\n    title=title,\n    x_title=\"y(t)  — Temperature (°C)\",\n    y_title=f\"y(t+{lag})  — Temp. (°C)\",\n    show_legend=True,\n    legend_at_bottom=True,\n    legend_at_bottom_columns=4,\n    legend_box_size=28,\n    stroke=False,\n    dots_size=8,\n    show_x_guides=True,\n    show_y_guides=True,\n    x_value_formatter=lambda x: f\"{x:.1f}\",\n    value_formatter=lambda y: f\"{y:.1f}\",\n    margin_bottom=65,\n    margin_left=80,\n    margin_right=30,\n    margin_top=40,\n    range=(ref_start, ref_end),\n    xrange=(ref_start, ref_end),\n    x_labels_major_count=8,\n    y_labels_major_count=8,\n    print_values=False,\n    print_zeroes=False,\n    truncate_legend=40,\n)\n\n# Temporal quartile scatter series — size encodes temporal recency (7→10)\nchart.add(\"Hours 1–100\", early, stroke=False, dots_size=7)\nchart.add(\"Hours 101–200\", mid_early, stroke=False, dots_size=8)\nchart.add(\"Hours 201–300\", mid_late, stroke=False, dots_size=9)\nchart.add(\"Hours 301–399\", late, stroke=False, dots_size=10)\n\n# ±1σ spread envelope (structural layer — no legend entry)\nenv_style = {\"width\": 3, \"dasharray\": \"6, 8\", \"linecap\": \"round\"}\nchart.add(None, upper_env, stroke=True, show_dots=False, stroke_style=env_style)\nchart.add(None, lower_env, stroke=True, show_dots=False, stroke_style=env_style)\n\n# Diagonal reference line y = x (strong autocorrelation aligns points to this)\nchart.add(\n    \"y = x (±1σ)\",\n    ref_line,\n    stroke=True,\n    show_dots=False,\n    stroke_style={\"width\": 6, \"dasharray\": \"24, 12\", \"linecap\": \"round\"},\n)\n\n# Save — PNG + interactive HTML (pygal is interactive)\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}