Van der Pol phase portrait
Nonlinear Dynamics
Example from the compendium of canonical charts
Python Code
"""Nonlinear Dynamics — Van der Pol phase portrait."""
from pathlib import Path
# ── Palette + theme (matching the Plotly Studio gallery these charts ship in) ──
VIOLET, TEAL, GREEN, PINK, ORANGE = "#845EEE", "#52B3D0", "#55B685", "#DA5597", "#E9A23B"
PRIMARY, SECONDARY = VIOLET, TEAL
COLORWAY = [VIOLET, TEAL, GREEN, PINK, ORANGE]
BG, TEXT, GRID, MUTED = "#ffffff", "#1c2024", "#d9d9e0", "#60646c"
FONT = "Inter, -apple-system, BlinkMacSystemFont, sans-serif"
COLORSCALE = [[0, "rgba(132, 94, 238, 0.05)"], [1, "rgba(132, 94, 238, 0.9)"]]
def apply_theme(fig):
"""Light gallery theme: white background, Inter font, soft gridlines."""
fig.update_layout(
paper_bgcolor=BG, plot_bgcolor=BG, colorway=COLORWAY,
font=dict(family=FONT, color=TEXT, size=12),
legend=dict(font=dict(color=TEXT)),
hoverlabel=dict(bgcolor="#f0f0f3", font=dict(color=TEXT, family=FONT), bordercolor=GRID),
)
fig.update_xaxes(gridcolor=GRID, linecolor=GRID, zerolinecolor=GRID)
fig.update_yaxes(gridcolor=GRID, linecolor=GRID, zerolinecolor=GRID)
def fetch_csv(url, **kwargs):
import io
import pandas as pd
import requests
r = requests.get(url, timeout=60)
r.raise_for_status()
return pd.read_csv(io.StringIO(r.text), **kwargs)
def fetch_json(url):
import requests
r = requests.get(url, timeout=60)
r.raise_for_status()
return r.json()
import numpy as np
import plotly.graph_objects as go
import plotly.figure_factory as ff
from scipy.integrate import solve_ivp
MU = 1.0
def vdp(t, y):
x, v = y
return [v, MU * (1 - x**2) * v - x]
def generate():
ICs = [(0.1, 0), (3, 0), (-3, 0), (0.5, 2), (-2, -2)]
colors = [VIOLET, GREEN, TEAL, PINK, TEAL]
fig = go.Figure()
# Background quiver field on a coarse grid
gx = np.linspace(-4, 4, 16)
gy = np.linspace(-4, 4, 16)
GX, GY = np.meshgrid(gx, gy)
GU = GY
GV = MU * (1 - GX**2) * GY - GX
# Normalize arrow length for display
mag = np.sqrt(GU**2 + GV**2) + 1e-10
scale_factor = 0.25
GU_n = GU / mag * scale_factor
GV_n = GV / mag * scale_factor
quiver_fig = ff.create_quiver(
GX, GY, GU_n, GV_n,
scale=1.0, arrow_scale=0.3,
)
for trace in quiver_fig.data:
trace.line.color = MUTED
trace.opacity = 0.35
trace.showlegend = False
fig.add_trace(trace)
# Trajectory for each IC
t_span = (0, 30)
t_eval = np.linspace(0, 30, 3000)
for (x0, v0), color in zip(ICs, colors):
sol = solve_ivp(vdp, t_span, [x0, v0], t_eval=t_eval, dense_output=True, rtol=1e-8, atol=1e-10)
x_traj = sol.y[0]
v_traj = sol.y[1]
fig.add_trace(go.Scatter(
x=x_traj, y=v_traj,
mode="lines",
name=f"IC=({x0},{v0})",
line=dict(color=color, width=1.8),
hovertemplate="x=%{x:.2f}, ẋ=%{y:.2f}<extra></extra>",
))
fig.update_layout(
xaxis=dict(
title="x",
range=[-4.5, 4.5],
zeroline=True,
zerolinecolor=GRID,
zerolinewidth=1,
),
yaxis=dict(
title="dx/dt",
range=[-4.5, 4.5],
zeroline=True,
zerolinecolor=GRID,
zerolinewidth=1,
),
legend=dict(orientation="h", y=1.08),
margin=dict(t=40, b=60, l=70, r=40),
height=580,
)
apply_theme(fig)
return fig
fig = generate()
fig.show()
Made with Plotly