{"spec_id":"recurrence-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nrecurrence-basic: Recurrence Plot for Nonlinear Time Series\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 86/100 | Updated: 2026-06-10\n\"\"\"\n\nimport os\nimport sys\nfrom pathlib import Path\n\nimport numpy as np\nfrom scipy.integrate import solve_ivp\nfrom scipy.spatial.distance import cdist\n\n\n# File is named pygal.py — remove local dir so Python finds the installed package\n_local = str(Path(__file__).parent)\nif _local in sys.path:\n    sys.path.remove(_local)\n\nimport pygal\nfrom pygal.style import Style\n\n\n# Theme tokens (Imprint palette + adaptive chrome)\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint sequential colormap: brand green (#009E73) → blue (#4467A3), 5 stops\n# Bin 0 = nearest recurrent states (dark green), Bin 4 = farthest (blue)\nN_BINS = 5\nSEQ_COLORS = tuple(\n    \"#{:02X}{:02X}{:02X}\".format(\n        round(0x00 + (0x44 - 0x00) * i / (N_BINS - 1)),\n        round(0x9E + (0x67 - 0x9E) * i / (N_BINS - 1)),\n        round(0x73 + (0xA3 - 0x73) * i / (N_BINS - 1)),\n    )\n    for i in range(N_BINS)\n)\n\n# Data: Lorenz attractor x-component with time-delay embedding\nnp.random.seed(42)\nsol = solve_ivp(\n    lambda t, s: [10.0 * (s[1] - s[0]), s[0] * (28.0 - s[2]) - s[1], s[0] * s[1] - (8.0 / 3.0) * s[2]],\n    [0, 40],\n    [1.0, 1.0, 1.0],\n    t_eval=np.linspace(0, 40, 4000),\n    method=\"RK45\",\n)\nx_raw = sol.y[0, 1000:]\nstride = max(1, len(x_raw) // 220)\nx_series = x_raw[::stride][:220]\n\nemb_dim, emb_delay = 3, 5\nn_pts = len(x_series) - (emb_dim - 1) * emb_delay\nembedded = np.array([[x_series[k + d * emb_delay] for d in range(emb_dim)] for k in range(n_pts)])\ndist_matrix = cdist(embedded, embedded, metric=\"euclidean\")\n\n# Threshold giving ~15% recurrence rate\nsorted_dists = np.sort(dist_matrix.ravel())\nepsilon = sorted_dists[int(0.15 * len(sorted_dists))]\n\n# Group recurrent pairs into distance bins; flip y so time-0 appears at top-left\nbin_data = [[] for _ in range(N_BINS)]\nfor i in range(n_pts):\n    for j in range(n_pts):\n        d = dist_matrix[i, j]\n        if d <= epsilon:\n            b = min(int(d / epsilon * N_BINS), N_BINS - 1)\n            bin_data[b].append({\"value\": (j, n_pts - 1 - i), \"label\": f\"t{j}–t{i}\"})\n\n# Plot\ntitle = \"recurrence-basic · python · pygal · anyplot.ai\"\n\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=SEQ_COLORS,\n    title_font_size=66,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n    stroke_width=2.5,\n    font_family=\"sans-serif\",\n)\n\nchart = pygal.XY(\n    width=2400,\n    height=2400,\n    style=custom_style,\n    title=title,\n    x_title=\"Time Index\",\n    y_title=\"Time Index\",\n    stroke=False,\n    dots_size=4,\n    show_legend=True,\n    show_x_guides=True,\n    show_y_guides=True,\n)\n\nfor b in range(N_BINS):\n    chart.add(f\"Distance bin {b + 1}\", bin_data[b])\n\n# Save\nchart.render_to_png(f\"plot-{THEME}.png\")\n\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}