{"spec_id":"recurrence-basic","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nrecurrence-basic: Recurrence Plot for Nonlinear Time Series\nLibrary: plotly 6.8.0 | Python 3.13.13\nQuality: 85/100 | Updated: 2026-06-10\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove this script's directory from sys.path so \"plotly.py\" doesn't shadow the installed package\n_here = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p) != _here]\n\nimport numpy as np\nimport plotly.graph_objects as go\nfrom plotly.subplots import make_subplots\nfrom scipy.integrate import solve_ivp\nfrom scipy.spatial.distance import cdist\n\n\n# Theme tokens (Imprint palette — see prompts/default-style-guide.md)\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\"\n\n# Imprint categorical palette — position 1 (time series), position 3 (recurrent points)\nBRAND = \"#009E73\"  # Imprint position 1 — ALWAYS first series\n# Lightened in dark theme to maintain contrast on near-black background (~1.5:1 → ~3:1)\nBLUE = \"#4467A3\" if THEME == \"light\" else \"#6688CC\"\n\n# Data — Lorenz attractor x-component, transient removed (integrate from t=0, sample from t=10)\nsol = solve_ivp(\n    lambda t, s: [10 * (s[1] - s[0]), s[0] * (28 - s[2]) - s[1], s[0] * s[1] - (8 / 3) * s[2]],\n    [0, 50],\n    [1.0, 1.0, 1.0],\n    dense_output=True,\n    max_step=0.01,\n)\nt_eval = np.linspace(10, 50, 500)  # skip initial transient — attractor well-established by t=10\nx_series = sol.sol(t_eval)[0]\n\n# Time-delay embedding (Takens' theorem): embed scalar series into phase space\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\n# Binary recurrence matrix: 1 where distance ≤ threshold, 0 elsewhere\ndistance_matrix = cdist(embedded, embedded, metric=\"euclidean\")\nthreshold = np.percentile(distance_matrix, 15)\nrecurrence_matrix = (distance_matrix <= threshold).astype(float)\ntime_indices = np.arange(n_embedded)\n\n# Two-stop binary colorscale: non-recurrent → background, recurrent → Imprint blue\ncolorscale = [[0.0, PAGE_BG], [1.0, BLUE]]\n\n# Figure with marginal time series panel for context\nfig = make_subplots(rows=2, cols=1, row_heights=[0.15, 0.85], vertical_spacing=0.03, shared_xaxes=True)\n\n# Top panel: Lorenz x(t) time series (provides temporal context)\nfig.add_trace(\n    go.Scatter(\n        x=time_indices,\n        y=x_series[:n_embedded],\n        mode=\"lines\",\n        line={\"color\": BRAND, \"width\": 1.5},\n        fill=\"tozeroy\",\n        fillcolor=\"rgba(0,158,115,0.12)\",\n        hovertemplate=\"t: %{x}<br>x(t): %{y:.2f}<extra></extra>\",\n        showlegend=False,\n    ),\n    row=1,\n    col=1,\n)\n\n# Bottom panel: recurrence matrix heatmap\nfig.add_trace(\n    go.Heatmap(\n        z=recurrence_matrix,\n        x=time_indices,\n        y=time_indices,\n        colorscale=colorscale,\n        zmin=0,\n        zmax=1,\n        showscale=False,\n        hovertemplate=\"i: %{x}<br>j: %{y}<br>Recurrent: %{z}<extra></extra>\",\n        xgap=0,\n        ygap=0,\n    ),\n    row=2,\n    col=1,\n)\n\ntitle_text = \"recurrence-basic · python · plotly · anyplot.ai\"\nfig.update_layout(\n    autosize=False,\n    width=600,\n    height=600,\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK, \"family\": \"Palatino, Georgia, serif\"},\n    title={\n        \"text\": (\n            title_text\n            + \"<br><sup style='color:\"\n            + INK_SOFT\n            + \"; font-size:12px;'>\"\n            + \"Lorenz attractor recurrence plot — diagonal lines reveal deterministic chaotic dynamics\"\n            + \"</sup>\"\n        ),\n        \"font\": {\"size\": 16, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n        \"y\": 0.975,\n        \"yanchor\": \"top\",\n    },\n    margin={\"l\": 80, \"r\": 40, \"t\": 130, \"b\": 60},\n)\n\n# Top subplot (time series) axis styling\nfig.update_yaxes(\n    title={\"text\": \"x(t)\", \"font\": {\"size\": 10, \"color\": INK}},\n    tickfont={\"size\": 10, \"color\": INK_SOFT},\n    showgrid=False,\n    zeroline=False,\n    row=1,\n    col=1,\n)\nfig.update_xaxes(showticklabels=False, showgrid=False, zeroline=False, row=1, col=1)\n\n# Bottom subplot (recurrence matrix) axis styling — constrained square aspect ratio\nfig.update_xaxes(\n    title={\"text\": \"Time Index (i)\", \"font\": {\"size\": 12, \"color\": INK}},\n    tickfont={\"size\": 10, \"color\": INK_SOFT},\n    showgrid=False,\n    zeroline=False,\n    linecolor=INK_SOFT,\n    row=2,\n    col=1,\n)\nfig.update_yaxes(\n    title={\"text\": \"Time Index (j)\", \"font\": {\"size\": 12, \"color\": INK}},\n    tickfont={\"size\": 10, \"color\": INK_SOFT},\n    scaleanchor=\"x2\",\n    scaleratio=1,\n    constrain=\"domain\",\n    autorange=\"reversed\",\n    showgrid=False,\n    zeroline=False,\n    linecolor=INK_SOFT,\n    row=2,\n    col=1,\n)\n\n# Save — theme-suffixed PNG + HTML (interactive)\nfig.write_image(f\"plot-{THEME}.png\", width=600, height=600, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}