{"spec_id":"recurrence-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nrecurrence-basic: Recurrence Plot for Nonlinear Time Series\nLibrary: altair 6.2.1 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-06-10\n\"\"\"\n\nimport importlib\nimport os\nimport sys\n\nfrom PIL import Image\n\n\n# Drop script directory from sys.path so the `altair` package resolves, not this file\nsys.path[:] = [p for p in sys.path if os.path.abspath(p or \".\") != os.path.dirname(os.path.abspath(__file__))]\nalt = importlib.import_module(\"altair\")\nnp = importlib.import_module(\"numpy\")\npd = importlib.import_module(\"pandas\")\n\n# Theme tokens — Imprint palette, theme-adaptive chrome\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 sequential colormap for continuous single-polarity distances\nSEQ_LOW = \"#009E73\"  # brand green — near-identical states (distance ≈ 0)\nSEQ_HIGH = \"#4467A3\"  # blue — close to threshold\n\n# Data — logistic map near type-I intermittency (r = 3.8284)\n# 100 steps (reduced from 152) for larger cells; ε=0.17 (raised) for more V/H structure\nnp.random.seed(42)\nn_steps = 100\nr = 3.8284\nx = np.zeros(n_steps)\nx[0] = 0.1\nfor i in range(1, n_steps):\n    x[i] = r * x[i - 1] * (1 - x[i - 1])\n\n# Time-delay embedding (dimension=3, delay=1) per Takens' theorem\nembedding_dim = 3\ndelay = 1\nn_embedded = n_steps - (embedding_dim - 1) * delay\nembedded = np.column_stack([x[d * delay : d * delay + n_embedded] for d in range(embedding_dim)])\n\n# Pairwise Euclidean distance matrix + threshold\ndiff = embedded[:, np.newaxis, :] - embedded[np.newaxis, :, :]\ndistance_matrix = np.sqrt(np.sum(diff**2, axis=2))\nthreshold = 0.17\nrecurrence_matrix = distance_matrix < threshold\n\n# Sparse encoding: recurrent points only (absent cells render as PAGE_BG background)\nrows, cols = np.where(recurrence_matrix)\ndistances = distance_matrix[rows, cols]\ndf = pd.DataFrame({\"time_i\": rows, \"time_j\": cols, \"distance\": distances})\n\n# Interactive hover selection for cross-highlighting\nhover = alt.selection_point(on=\"pointerover\", fields=[\"time_i\"], nearest=True, empty=False)\n\n# Imprint sequential scale: distance=0 (self-recurrence) → green; near-threshold → blue\ncolor_scale = alt.Scale(domain=[0, threshold], range=[SEQ_LOW, SEQ_HIGH])\n\n# Recurrence heatmap\nheatmap = (\n    alt.Chart(df)\n    .mark_rect(stroke=None)\n    .encode(\n        x=alt.X(\n            \"time_i:O\",\n            title=\"Time Index (step)\",\n            axis=alt.Axis(\n                labelFontSize=10,\n                titleFontSize=12,\n                titlePadding=10,\n                values=list(range(0, n_embedded, 20)),\n                grid=False,\n                domainColor=INK_SOFT,\n                tickColor=INK_SOFT,\n                labelColor=INK_SOFT,\n                titleColor=INK,\n            ),\n            scale=alt.Scale(paddingInner=0, paddingOuter=0),\n        ),\n        y=alt.Y(\n            \"time_j:O\",\n            title=\"Time Index (step)\",\n            axis=alt.Axis(\n                labelFontSize=10,\n                titleFontSize=12,\n                titlePadding=10,\n                values=list(range(0, n_embedded, 20)),\n                grid=False,\n                domainColor=INK_SOFT,\n                tickColor=INK_SOFT,\n                labelColor=INK_SOFT,\n                titleColor=INK,\n            ),\n            scale=alt.Scale(paddingInner=0, paddingOuter=0),\n        ),\n        color=alt.Color(\n            \"distance:Q\",\n            title=\"Distance\",\n            scale=color_scale,\n            legend=alt.Legend(\n                titleFontSize=10,\n                labelFontSize=10,\n                orient=\"right\",\n                direction=\"vertical\",\n                gradientLength=200,\n                gradientThickness=12,\n                titlePadding=6,\n                offset=8,\n                labelColor=INK_SOFT,\n                titleColor=INK,\n            ),\n        ),\n        opacity=alt.condition(hover, alt.value(1.0), alt.value(0.88)),\n        tooltip=[\n            alt.Tooltip(\"time_i:Q\", title=\"Time i\"),\n            alt.Tooltip(\"time_j:Q\", title=\"Time j\"),\n            alt.Tooltip(\"distance:Q\", title=\"Distance\", format=\".4f\"),\n        ],\n    )\n    .add_params(hover)\n)\n\n# Annotation markers for key structural features\nannotations_data = pd.DataFrame(\n    {\n        \"label\": [\"Main diagonal (self-recurrence)\", \"Laminar phase\", \"Diagonal lines (determinism)\"],\n        \"x\": [20, 68, 42],\n        \"y\": [14, 60, 30],\n    }\n)\n\nannotation_shadow = (\n    alt.Chart(annotations_data)\n    .mark_text(fontSize=11, fontWeight=\"bold\", color=PAGE_BG, align=\"left\", dx=9, dy=-5, strokeWidth=3)\n    .encode(x=alt.X(\"x:O\"), y=alt.Y(\"y:O\"), text=\"label:N\")\n)\n\nannotation_marks = (\n    alt.Chart(annotations_data)\n    .mark_text(fontSize=11, fontWeight=\"bold\", color=\"#AE3030\", align=\"left\", dx=9, dy=-5)\n    .encode(x=alt.X(\"x:O\"), y=alt.Y(\"y:O\"), text=\"label:N\")\n)\n\nannotation_dots = (\n    alt.Chart(annotations_data)\n    .mark_point(size=70, color=\"#AE3030\", filled=True, opacity=0.9)\n    .encode(x=alt.X(\"x:O\"), y=alt.Y(\"y:O\"))\n)\n\n# Chart assembly — square target 2400 × 2400; inner view 450 × 450 + scale 4.0\ntitle_str = \"recurrence-basic · python · altair · anyplot.ai\"\nchart = (\n    alt.layer(heatmap, annotation_dots, annotation_shadow, annotation_marks)\n    .properties(\n        width=450,\n        height=450,\n        background=PAGE_BG,\n        title=alt.Title(\n            title_str,\n            subtitle=[\n                \"Logistic map r=3.8284 (type-I intermittency) · ε=0.17 · Takens d=3, τ=1\",\n                \"Green = near-identical states · Diagonal lines = determinism · Blocks = laminar phases\",\n            ],\n            fontSize=16,\n            subtitleFontSize=10,\n            subtitleColor=INK_MUTED,\n            anchor=\"start\",\n            offset=12,\n            color=INK,\n        ),\n        padding={\"left\": 10, \"right\": 10, \"top\": 10, \"bottom\": 10},\n    )\n    .configure_axis(grid=False)\n    .configure_title(color=INK)\n    .configure_view(strokeWidth=0, fill=PAGE_BG)\n    .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)\n)\n\n# Save PNG then pad to exact 2400 × 2400 (square target)\nTW, TH = 2400, 2400\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TW or _h > TH:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TW or _h < TH:\n    _canvas = Image.new(\"RGB\", (TW, TH), PAGE_BG)\n    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n\n# Save HTML (interactive view)\nchart.save(f\"plot-{THEME}.html\")\n"}