{"spec_id":"recurrence-basic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nrecurrence-basic: Recurrence Plot for Nonlinear Time Series\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 87/100 | Updated: 2026-06-10\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib.colors import LinearSegmentedColormap, ListedColormap\nfrom scipy.integrate import solve_ivp\nfrom scipy.spatial.distance import cdist\n\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\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 palette — first series always #009E73\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nBRAND = IMPRINT_PALETTE[0]\n\n# Imprint continuous cmap — sequential for single-polarity distance data\nimprint_seq = LinearSegmentedColormap.from_list(\"imprint_seq\", [\"#009E73\", \"#4467A3\"])\n\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.15,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Data — Lorenz attractor x-component with 3D time-delay embedding\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, 50],\n    [1.0, 1.0, 1.0],\n    max_step=0.05,\n    dense_output=True,\n)\nt_eval = np.linspace(5, 50, 500)\nx_series = sol.sol(t_eval)[0]\n\nembedding_dim = 3\ndelay = 5\nn_embedded = len(x_series) - (embedding_dim - 1) * delay\nembedded = np.column_stack([x_series[i * delay : i * delay + n_embedded] for i in range(embedding_dim)])\n\ndistance_matrix = cdist(embedded, embedded, metric=\"euclidean\")\nthreshold = 0.15 * np.max(distance_matrix)\nrecurrence_matrix = (distance_matrix <= threshold).astype(int)\nnorm_distances = distance_matrix / np.max(distance_matrix)\n\n# Square canvas for the symmetric recurrence matrix\nfig, ax = plt.subplots(figsize=(6, 6), dpi=400)\nfig.patch.set_facecolor(PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Layer 1: Distance background — imprint_seq (sequential, single-polarity)\nsns.heatmap(\n    norm_distances, cmap=imprint_seq, cbar=False, square=True, xticklabels=False, yticklabels=False, linewidths=0, ax=ax\n)\nax.collections[-1].set_alpha(0.18)\n\n# Layer 2: Binary recurrence overlay — seaborn heatmap with mask (seaborn-native)\nrec_cmap = ListedColormap([BRAND])\nrec_cmap.set_bad(color=(0, 0, 0, 0))  # transparent for non-recurrent cells\nsns.heatmap(\n    recurrence_matrix.astype(float),\n    mask=(recurrence_matrix == 0),\n    cmap=rec_cmap,\n    cbar=False,\n    square=True,\n    xticklabels=False,\n    yticklabels=False,\n    linewidths=0,\n    ax=ax,\n    vmin=0,\n    vmax=1,\n)\n\n# Spines\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nax.spines[\"left\"].set_color(INK_SOFT)\nax.spines[\"bottom\"].set_color(INK_SOFT)\n\n# Tick labels at 6 evenly-spaced positions\nn_ticks = 6\ntick_pos = np.linspace(0, n_embedded, n_ticks)\ntick_lab = [f\"{int(v)}\" for v in np.linspace(0, n_embedded - 1, n_ticks)]\nax.set_xticks(tick_pos)\nax.set_xticklabels(tick_lab, fontsize=8, color=INK_SOFT)\nax.set_yticks(tick_pos)\nax.set_yticklabels(tick_lab[::-1], fontsize=8, color=INK_SOFT, rotation=0)\n\nax.set_xlabel(\"Time Index (embedding steps)\", fontsize=10, color=INK, labelpad=10)\nax.set_ylabel(\"Time Index (embedding steps)\", fontsize=10, color=INK, labelpad=10)\ntitle = \"Lorenz Attractor · recurrence-basic · python · seaborn · anyplot.ai\"\nax.set_title(title, fontsize=10, fontweight=\"medium\", color=INK, pad=14)\n\n# Story annotations — guide viewer to key structural features\n_ann = {\n    \"fontsize\": 7,\n    \"color\": INK_SOFT,\n    \"style\": \"italic\",\n    \"bbox\": {\"facecolor\": PAGE_BG, \"edgecolor\": \"none\", \"alpha\": 0.80, \"pad\": 1.5},\n}\nax.text(n_embedded * 0.62, n_embedded * 0.56, \"← diagonal:\\ndeterminism\", ha=\"left\", va=\"center\", **_ann)\nax.text(n_embedded * 0.43, n_embedded * 0.50, \"chaotic\\ntransition\", ha=\"center\", va=\"center\", **_ann)\n\n# Inset: recurrence rate over time — seaborn lineplot (repositioned upper-right)\nrecurrence_rate = recurrence_matrix.sum(axis=1) / n_embedded\nax_inset = fig.add_axes([0.67, 0.77, 0.22, 0.13])\nax_inset.set_facecolor(ELEVATED_BG)\nax_inset.patch.set_alpha(0.93)\nrate_df = pd.DataFrame({\"Time\": np.arange(n_embedded), \"Rate\": recurrence_rate})\nsns.lineplot(data=rate_df, x=\"Time\", y=\"Rate\", color=BRAND, linewidth=1.5, ax=ax_inset)\nax_inset.fill_between(rate_df[\"Time\"], rate_df[\"Rate\"], alpha=0.25, color=BRAND)\nax_inset.set_title(\"Recurrence Rate\", fontsize=8, color=INK_SOFT)\nax_inset.set_xlabel(\"\")\nax_inset.set_ylabel(\"\")\nax_inset.tick_params(labelsize=7, colors=INK_SOFT)\nsns.despine(ax=ax_inset)\nax_inset.spines[\"left\"].set_color(INK_SOFT)\nax_inset.spines[\"bottom\"].set_color(INK_SOFT)\n\n# Footnote\nfig.text(\n    0.5,\n    0.015,\n    \"3D time-delay embedding (τ=5) · Euclidean distance · ε = 15% of max distance\",\n    ha=\"center\",\n    fontsize=8,\n    color=INK_MUTED,\n    style=\"italic\",\n)\n\nfig.subplots_adjust(bottom=0.09, left=0.13, right=0.96, top=0.94)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}