{"spec_id":"bifurcation-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nbifurcation-basic: Bifurcation Diagram for Dynamical Systems\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 82/100 | Updated: 2026-06-17\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\n\n\n# Theme tokens — Imprint palette 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 — 8 hues, hybrid-v3 sort\nIMPRINT_PALETTE = (\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\")\n\n# Logistic map data — x(n+1) = r * x(n) * (1 - x(n))\nnp.random.seed(42)\nTRANSIENT = 200\nITERATIONS = 100\nx0 = 0.1 + np.random.uniform(-0.01, 0.01)\n\n# Key bifurcation thresholds\nR_PERIOD2 = 3.0\nR_PERIOD4 = 3.449\nR_PERIOD8 = 3.544\nR_CHAOS = 3.57\n\n# Variable-density sampling: more points where structure is richer\nr_stable = np.linspace(2.5, R_PERIOD2, 250)\nr_periodic = np.linspace(R_PERIOD2, R_CHAOS, 500)\nr_chaotic = np.linspace(R_CHAOS, 4.0, 700)\nr_values = np.concatenate([r_stable, r_periodic, r_chaotic])\n\n# Three dynamical regions mapped to first three Imprint palette positions\nregions = {\n    \"Stable Fixed Point\": (2.5, R_PERIOD2, IMPRINT_PALETTE[0]),\n    \"Period-Doubling Cascade\": (R_PERIOD2, R_CHAOS, IMPRINT_PALETTE[1]),\n    \"Chaotic Regime\": (R_CHAOS, 4.0, IMPRINT_PALETTE[2]),\n}\n\nregion_data = {name: [] for name in regions}\n\nfor r in r_values:\n    x = x0\n    for _ in range(TRANSIENT):\n        x = r * x * (1.0 - x)\n    for _ in range(ITERATIONS):\n        x = r * x * (1.0 - x)\n        for name, (lo, hi, _) in regions.items():\n            if lo <= r < hi or (name == \"Chaotic Regime\" and r == 4.0):\n                region_data[name].append(\n                    {\"value\": (round(float(r), 5), round(float(x), 5)), \"label\": f\"r={r:.4f}, x={x:.4f}\"}\n                )\n                break\n\n# Downsample each region to balance visual density\nmax_per_region = {\"Stable Fixed Point\": 6000, \"Period-Doubling Cascade\": 18000, \"Chaotic Regime\": 28000}\nfor name in region_data:\n    pts = region_data[name]\n    cap = max_per_region[name]\n    if len(pts) > cap:\n        idx = np.random.choice(len(pts), cap, replace=False)\n        idx.sort()\n        region_data[name] = [pts[i] for i in idx]\n\n# Color tuple: 3 Imprint data series + INK_MUTED for dashed annotation lines\nregion_colors = tuple(color for _, (_, _, color) in regions.items())\nall_colors = region_colors + (INK_MUTED,)\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    guide_stroke_color=INK_MUTED,\n    guide_stroke_dasharray=\"3, 8\",\n    colors=all_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    tooltip_font_size=32,\n    stroke_width=2.5,\n    opacity=0.55,\n    opacity_hover=1.0,\n)\n\nchart = pygal.XY(\n    width=3200,\n    height=1800,\n    style=custom_style,\n    title=\"bifurcation-basic · python · pygal · anyplot.ai\",\n    x_title=\"Growth Rate Parameter (r)\",\n    y_title=\"Steady-State Population (xₙ)\",\n    show_legend=True,\n    legend_at_bottom=True,\n    legend_at_bottom_columns=2,\n    legend_box_size=22,\n    stroke=False,\n    dots_size=1.2,\n    show_x_guides=True,\n    show_y_guides=True,\n    x_value_formatter=lambda v: f\"{v:.3f}\",\n    value_formatter=lambda v: f\"{v:.4f}\",\n    margin_bottom=160,\n    margin_left=70,\n    margin_right=50,\n    margin_top=55,\n    xrange=(2.5, 4.0),\n    range=(0.0, 1.0),\n    print_values=False,\n    print_zeroes=False,\n    js=[],\n    x_labels=[2.5, R_PERIOD2, 3.2, R_PERIOD4, R_PERIOD8, 3.7, 3.8, 4.0],\n    x_labels_major=[R_PERIOD2, R_PERIOD4, R_PERIOD8],\n    y_labels=[0.0, 0.2, 0.4, 0.6, 0.8, 1.0],\n    truncate_legend=-1,\n    no_data_text=\"\",\n    show_x_labels=True,\n    show_y_labels=True,\n    allow_interruptions=True,\n    show_minor_x_labels=True,\n    spacing=25,\n    include_x_axis=True,\n)\n\n# Add each dynamical region as a separate series with per-point tooltip metadata\nfor name in regions:\n    lo, hi, _ = regions[name]\n    chart.add(\n        f\"{name} (r≈{lo:.1f}–{hi:.2f})\", region_data[name], stroke=False, show_dots=True, allow_interruptions=True\n    )\n\n# Dashed vertical lines at key bifurcation thresholds — no secondary axis\nannotation_points = [\n    (R_PERIOD2, \"r≈3.0: Period-2 onset\"),\n    (R_PERIOD4, \"r≈3.449: Period-4 onset\"),\n    (R_PERIOD8, \"r≈3.544: Period-8 onset\"),\n]\n\nannotation_data = []\nfor r_val, label in annotation_points:\n    annotation_data.append({\"value\": (r_val, 0.0), \"label\": label})\n    annotation_data.append({\"value\": (r_val, 1.0), \"label\": label})\n    annotation_data.append(None)\n\nchart.add(\n    \"Bifurcation Points\",\n    annotation_data,\n    stroke=True,\n    stroke_style={\"width\": 2.5, \"dasharray\": \"10, 5\"},\n    show_dots=False,\n    dots_size=0,\n)\n\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}