Skip to content

Commit 1c34c9a

Browse files
feat(letsplot): implement phase-diagram (#3064)
## Implementation: `phase-diagram` - letsplot Implements the **letsplot** version of `phase-diagram`. **File:** `plots/phase-diagram/implementations/letsplot.py` **Parent Issue:** #3004 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20617713571)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 062b245 commit 1c34c9a

2 files changed

Lines changed: 125 additions & 0 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
""" pyplots.ai
2+
phase-diagram: Phase Diagram (State Space Plot)
3+
Library: letsplot 4.8.2 | Python 3.13.11
4+
Quality: 93/100 | Created: 2025-12-31
5+
"""
6+
7+
import numpy as np
8+
import pandas as pd
9+
from lets_plot import * # noqa: F403
10+
11+
12+
LetsPlot.setup_html() # noqa: F405
13+
14+
# Data - Simple damped harmonic oscillator (damped pendulum)
15+
# dx/dt = v, dv/dt = -omega^2 * x - gamma * v
16+
np.random.seed(42)
17+
18+
omega = 2.0 # Natural frequency
19+
gamma = 0.3 # Damping coefficient
20+
21+
# Multiple trajectories from different initial conditions
22+
trajectories = []
23+
24+
initial_conditions = [
25+
(3.0, 0.0), # Start displaced right, no velocity
26+
(-2.5, 2.0), # Start left with upward velocity
27+
(0.5, -3.0), # Start near center with downward velocity
28+
(2.0, 2.5), # Start with both positive
29+
]
30+
31+
for x0, v0 in initial_conditions:
32+
t = np.linspace(0, 15, 500)
33+
dt = t[1] - t[0]
34+
35+
x = np.zeros_like(t)
36+
v = np.zeros_like(t)
37+
x[0], v[0] = x0, v0
38+
39+
# Euler integration for damped harmonic oscillator
40+
for i in range(1, len(t)):
41+
x[i] = x[i - 1] + v[i - 1] * dt
42+
v[i] = v[i - 1] + (-(omega**2) * x[i - 1] - gamma * v[i - 1]) * dt
43+
44+
traj_df = pd.DataFrame({"x": x, "dx_dt": v, "time": t, "trajectory": f"Initial ({x0}, {v0})"})
45+
trajectories.append(traj_df)
46+
47+
df = pd.concat(trajectories, ignore_index=True)
48+
49+
# Create phase diagram # noqa: F405
50+
plot = (
51+
ggplot(df, aes(x="x", y="dx_dt", color="trajectory")) # noqa: F405
52+
+ geom_path(size=1.2, alpha=0.8) # noqa: F405
53+
+ geom_point( # noqa: F405
54+
mapping=aes(x="x", y="dx_dt"), # noqa: F405
55+
data=df.groupby("trajectory").head(1),
56+
size=6,
57+
shape=21,
58+
fill="white",
59+
stroke=2,
60+
)
61+
# Mark the fixed point (equilibrium at origin)
62+
+ geom_point( # noqa: F405
63+
mapping=aes(x="x", y="dx_dt"), # noqa: F405
64+
data=pd.DataFrame({"x": [0], "dx_dt": [0]}),
65+
color="#DC2626",
66+
size=8,
67+
shape=4,
68+
stroke=3,
69+
inherit_aes=False,
70+
)
71+
+ scale_color_manual(values=["#306998", "#FFD43B", "#16A34A", "#9333EA"]) # noqa: F405
72+
+ labs( # noqa: F405
73+
x="Position (x)",
74+
y="Velocity (dx/dt)",
75+
title="phase-diagram · letsplot · pyplots.ai",
76+
color="Starting Condition",
77+
)
78+
+ theme_minimal() # noqa: F405
79+
+ theme( # noqa: F405
80+
axis_title=element_text(size=20), # noqa: F405
81+
axis_text=element_text(size=16), # noqa: F405
82+
plot_title=element_text(size=24), # noqa: F405
83+
legend_text=element_text(size=14), # noqa: F405
84+
legend_title=element_text(size=16), # noqa: F405
85+
legend_position="right",
86+
panel_grid=element_line(color="#E5E7EB", size=0.5), # noqa: F405
87+
)
88+
+ ggsize(1600, 900) # noqa: F405
89+
)
90+
91+
# Save PNG (scale 3x for 4800 x 2700)
92+
ggsave(plot, "plot.png", scale=3) # noqa: F405
93+
94+
# Save interactive HTML
95+
ggsave(plot, "plot.html") # noqa: F405
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
library: letsplot
2+
specification_id: phase-diagram
3+
created: '2025-12-31T11:15:33Z'
4+
updated: '2025-12-31T11:22:00Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20617713571
7+
issue: 3004
8+
python_version: 3.13.11
9+
library_version: 4.8.2
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/phase-diagram/letsplot/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/phase-diagram/letsplot/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/phase-diagram/letsplot/plot.html
13+
quality_score: 93
14+
review:
15+
strengths:
16+
- Excellent visualization of damped harmonic oscillator phase space with clear spiral
17+
convergence to equilibrium
18+
- Multiple trajectories from different initial conditions effectively demonstrate
19+
basin of attraction concept
20+
- Fixed point clearly marked with distinctive red X marker
21+
- Starting points clearly marked with hollow circles with stroke, making trajectory
22+
origins easy to identify
23+
- Clean, professional styling with appropriate color palette and readable text at
24+
all sizes
25+
- Proper use of lets-plot grammar of graphics (ggplot, geom_path, aes mappings)
26+
weaknesses:
27+
- Legend entries show raw initial condition tuples; more descriptive labels would
28+
improve readability
29+
- Inner spiral loops become slightly dense near equilibrium; direction arrows could
30+
show time evolution more clearly

0 commit comments

Comments
 (0)