{"spec_id":"phase-diagram","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nphase-diagram: Phase Diagram (State Space Plot)\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 97/100 | Updated: 2026-05-14\n\"\"\"\n\nimport os\nimport sys\n\n\nsys.path = [p for p in sys.path if not p.endswith(\"python\")]\nimport altair as alt\nimport numpy as np\nimport pandas as pd\n\n\n# Theme tokens\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# Okabe-Ito palette (first series is #009E73)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data: Damped pendulum simulation\nnp.random.seed(42)\n\ndt = 0.02\ngamma = 0.15\n\ntrajectories = []\ninitial_conditions = [(2.5, 0.0), (-2.0, 1.5), (0.5, 2.0), (1.5, -1.5)]\n\nfor idx, (x0, v0) in enumerate(initial_conditions):\n    x, v = x0, v0\n    trajectory_x = [x]\n    trajectory_v = [v]\n\n    for _ in range(500):\n        a = -np.sin(x) - gamma * v\n        v = v + a * dt\n        x = x + v * dt\n        trajectory_x.append(x)\n        trajectory_v.append(v)\n\n    for i, (px, pv) in enumerate(zip(trajectory_x, trajectory_v, strict=True)):\n        trajectories.append({\"x\": px, \"dx_dt\": pv, \"trajectory\": f\"IC {idx + 1}: ({x0:.1f}, {v0:.1f})\", \"order\": i})\n\ndf = pd.DataFrame(trajectories)\n\n# Create phase diagram\nbase = (\n    alt.Chart(df)\n    .mark_line(strokeWidth=2.5, opacity=0.85)\n    .encode(\n        x=alt.X(\"x:Q\", title=\"Position (x)\", axis=alt.Axis(titleFontSize=22, labelFontSize=18)),\n        y=alt.Y(\"dx_dt:Q\", title=\"Velocity (dx/dt)\", axis=alt.Axis(titleFontSize=22, labelFontSize=18)),\n        color=alt.Color(\n            \"trajectory:N\",\n            title=\"Initial Condition\",\n            scale=alt.Scale(range=IMPRINT),\n            legend=alt.Legend(titleFontSize=18, labelFontSize=16, symbolStrokeWidth=3),\n        ),\n        order=alt.Order(\"order:Q\"),\n        detail=\"trajectory:N\",\n    )\n    .properties(\n        width=1600,\n        height=900,\n        title=alt.Title(text=\"phase-diagram · altair · anyplot.ai\", fontSize=28, anchor=\"middle\"),\n    )\n)\n\n# Add starting points as markers\nstart_points = df[df[\"order\"] == 0]\npoints = (\n    alt.Chart(start_points)\n    .mark_point(size=400, filled=True, opacity=1.0)\n    .encode(x=\"x:Q\", y=\"dx_dt:Q\", color=alt.Color(\"trajectory:N\", scale=alt.Scale(range=IMPRINT), legend=None))\n)\n\n# Add equilibrium point marker at origin\nequilibrium = pd.DataFrame([{\"x\": 0, \"y\": 0}])\neq_point = alt.Chart(equilibrium).mark_point(shape=\"cross\", size=600, strokeWidth=4, color=INK).encode(x=\"x:Q\", y=\"y:Q\")\n\n# Combine layers with theme-aware styling\nchart = (\n    (base + points + eq_point)\n    .properties(background=PAGE_BG)\n    .configure_view(fill=PAGE_BG, stroke=INK_SOFT, strokeWidth=0)\n    .configure_axis(\n        domainColor=INK_SOFT, tickColor=INK_SOFT, gridColor=INK, gridOpacity=0.10, labelColor=INK_SOFT, titleColor=INK\n    )\n    .configure_title(color=INK)\n    .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)\n)\n\n# Save with theme suffix\nchart.save(f\"plot-{THEME}.png\", scale_factor=3.0)\nchart.interactive().save(f\"plot-{THEME}.html\")\n"}