{"spec_id":"phase-diagram","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nphase-diagram: Phase Diagram (State Space Plot)\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-14\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\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\"\nGRID = \"rgba(26,26,23,0.20)\" if THEME == \"light\" else \"rgba(240,239,232,0.20)\"\nBRAND = \"#009E73\"  # Okabe-Ito position 1\nACCENT_1 = \"#C475FD\"  # Okabe-Ito position 2\n\n# Data - Damped pendulum showing spiral convergence to equilibrium\nnp.random.seed(42)\n\n# Parameters for damped harmonic oscillator: d²x/dt² + 2ζω₀(dx/dt) + ω₀²x = 0\nomega_0 = 2.0\nzeta = 0.15\n\n# Time array\nt = np.linspace(0, 15, 1500)\ndt = t[1] - t[0]\n\n# Damped frequency\nomega_d = omega_0 * np.sqrt(1 - zeta**2)\n\n# Trajectory 1: x(0) = 2.0, dx/dt(0) = 0\nx0 = 2.0\nv0 = 0.0\nA = np.sqrt(x0**2 + ((v0 + zeta * omega_0 * x0) / omega_d) ** 2)\nphi = np.arctan2((v0 + zeta * omega_0 * x0) / omega_d, x0)\n\nx = A * np.exp(-zeta * omega_0 * t) * np.cos(omega_d * t - phi)\ndx_dt = -zeta * omega_0 * A * np.exp(-zeta * omega_0 * t) * np.cos(omega_d * t - phi) - omega_d * A * np.exp(\n    -zeta * omega_0 * t\n) * np.sin(omega_d * t - phi)\n\n# Trajectory 2: x(0) = -1.5, dx/dt(0) = 3.0\nx0_2 = -1.5\nv0_2 = 3.0\nA2 = np.sqrt(x0_2**2 + ((v0_2 + zeta * omega_0 * x0_2) / omega_d) ** 2)\nphi2 = np.arctan2((v0_2 + zeta * omega_0 * x0_2) / omega_d, x0_2)\n\nx2 = A2 * np.exp(-zeta * omega_0 * t) * np.cos(omega_d * t - phi2)\ndx_dt_2 = -zeta * omega_0 * A2 * np.exp(-zeta * omega_0 * t) * np.cos(omega_d * t - phi2) - omega_d * A2 * np.exp(\n    -zeta * omega_0 * t\n) * np.sin(omega_d * t - phi2)\n\n# Create figure\nfig = go.Figure()\n\n# Main trajectory\nfig.add_trace(\n    go.Scatter(\n        x=x,\n        y=dx_dt,\n        mode=\"lines+markers\",\n        name=\"Trajectory 1 (x₀=2.0, v₀=0)\",\n        line=dict(color=BRAND, width=3),\n        marker=dict(size=7, color=BRAND, opacity=0.8),\n        hovertemplate=\"x: %{x:.2f}<br>dx/dt: %{y:.2f}<extra></extra>\",\n    )\n)\n\n# Second trajectory\nfig.add_trace(\n    go.Scatter(\n        x=x2,\n        y=dx_dt_2,\n        mode=\"lines+markers\",\n        name=\"Trajectory 2 (x₀=-1.5, v₀=3.0)\",\n        line=dict(color=ACCENT_1, width=3),\n        marker=dict(size=7, color=ACCENT_1, opacity=0.8),\n        hovertemplate=\"x: %{x:.2f}<br>dx/dt: %{y:.2f}<extra></extra>\",\n    )\n)\n\n# Fixed point (equilibrium at origin)\nfig.add_trace(\n    go.Scatter(\n        x=[0],\n        y=[0],\n        mode=\"markers\",\n        name=\"Fixed Point (Stable)\",\n        marker=dict(size=18, color=INK, symbol=\"x\", line=dict(width=3)),\n        hovertemplate=\"Equilibrium<br>x=0, dx/dt=0<extra></extra>\",\n    )\n)\n\n# Initial conditions\nfig.add_trace(\n    go.Scatter(\n        x=[x[0], x2[0]],\n        y=[dx_dt[0], dx_dt_2[0]],\n        mode=\"markers\",\n        name=\"Initial Conditions\",\n        marker=dict(size=14, color=INK_SOFT, symbol=\"circle\"),\n        hovertemplate=\"Initial: x=%{x:.2f}, dx/dt=%{y:.2f}<extra></extra>\",\n    )\n)\n\n# Direction arrows\narrow_indices = [200, 500, 900]\nfor idx in arrow_indices:\n    # Arrow for trajectory 1\n    fig.add_annotation(\n        x=x[idx],\n        y=dx_dt[idx],\n        ax=x[idx - 30],\n        ay=dx_dt[idx - 30],\n        xref=\"x\",\n        yref=\"y\",\n        axref=\"x\",\n        ayref=\"y\",\n        showarrow=True,\n        arrowhead=2,\n        arrowsize=2,\n        arrowwidth=2,\n        arrowcolor=BRAND,\n    )\n    # Arrow for trajectory 2\n    fig.add_annotation(\n        x=x2[idx],\n        y=dx_dt_2[idx],\n        ax=x2[idx - 30],\n        ay=dx_dt_2[idx - 30],\n        xref=\"x\",\n        yref=\"y\",\n        axref=\"x\",\n        ayref=\"y\",\n        showarrow=True,\n        arrowhead=2,\n        arrowsize=2,\n        arrowwidth=2,\n        arrowcolor=ACCENT_1,\n    )\n\n# Update layout\nfig.update_layout(\n    title=dict(text=\"phase-diagram · plotly · anyplot.ai\", font=dict(size=28, color=INK), x=0.5, xanchor=\"center\"),\n    xaxis=dict(\n        title=dict(text=\"Position x\", font=dict(size=22, color=INK)),\n        tickfont=dict(size=18, color=INK_SOFT),\n        gridcolor=GRID,\n        gridwidth=1,\n        zeroline=True,\n        zerolinecolor=INK_SOFT,\n        zerolinewidth=2,\n        linecolor=INK_SOFT,\n    ),\n    yaxis=dict(\n        title=dict(text=\"Velocity dx/dt\", font=dict(size=22, color=INK)),\n        tickfont=dict(size=18, color=INK_SOFT),\n        gridcolor=GRID,\n        gridwidth=1,\n        zeroline=True,\n        zerolinecolor=INK_SOFT,\n        zerolinewidth=2,\n        linecolor=INK_SOFT,\n    ),\n    legend=dict(\n        font=dict(size=16, color=INK_SOFT), x=0.02, y=0.98, bgcolor=ELEVATED_BG, bordercolor=INK_SOFT, borderwidth=1\n    ),\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font=dict(color=INK),\n    margin=dict(l=80, r=40, t=100, b=80),\n)\n\n# Save outputs\nfig.write_image(f\"plot-{THEME}.png\", width=1600, height=900, scale=3)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}