Skip to content

Commit 3b55023

Browse files
feat(pygal): implement phase-diagram (#3061)
## Implementation: `phase-diagram` - pygal Implements the **pygal** version of `phase-diagram`. **File:** `plots/phase-diagram/implementations/pygal.py` **Parent Issue:** #3004 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20617709332)* --------- 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 dd8e543 commit 3b55023

2 files changed

Lines changed: 132 additions & 0 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
""" pyplots.ai
2+
phase-diagram: Phase Diagram (State Space Plot)
3+
Library: pygal 3.1.0 | Python 3.13.11
4+
Quality: 91/100 | Created: 2025-12-31
5+
"""
6+
7+
import numpy as np
8+
import pygal
9+
from pygal.style import Style
10+
11+
12+
# Data: Damped pendulum phase space
13+
# The damped pendulum follows: d²x/dt² = -ω²x - γ(dx/dt)
14+
# This creates a spiral trajectory converging to equilibrium
15+
16+
np.random.seed(42)
17+
18+
# Parameters for damped harmonic oscillator
19+
omega = 2.0 # Natural frequency
20+
gamma = 0.3 # Damping coefficient
21+
dt = 0.02 # Time step
22+
t_max = 15 # Total time
23+
24+
# Generate trajectory using numerical integration (Euler method)
25+
t = np.arange(0, t_max, dt)
26+
n_points = len(t)
27+
28+
# Initial conditions: displaced position, zero velocity
29+
x = np.zeros(n_points)
30+
v = np.zeros(n_points) # v = dx/dt
31+
x[0] = 2.0 # Initial displacement
32+
v[0] = 0.0 # Initial velocity
33+
34+
# Integrate the equations of motion
35+
for i in range(1, n_points):
36+
# Acceleration: a = -ω²x - γv
37+
a = -(omega**2) * x[i - 1] - gamma * v[i - 1]
38+
# Update velocity and position
39+
v[i] = v[i - 1] + a * dt
40+
x[i] = x[i - 1] + v[i] * dt
41+
42+
# Also generate an undamped oscillator for comparison (limit cycle)
43+
x_undamped = np.zeros(n_points)
44+
v_undamped = np.zeros(n_points)
45+
x_undamped[0] = 1.5
46+
v_undamped[0] = 0.0
47+
48+
for i in range(1, n_points):
49+
a = -(omega**2) * x_undamped[i - 1]
50+
v_undamped[i] = v_undamped[i - 1] + a * dt
51+
x_undamped[i] = x_undamped[i - 1] + v_undamped[i] * dt
52+
53+
# Custom style for large canvas
54+
custom_style = Style(
55+
background="white",
56+
plot_background="white",
57+
foreground="#333333",
58+
foreground_strong="#333333",
59+
foreground_subtle="#666666",
60+
colors=("#306998", "#FFD43B", "#FF6B6B"),
61+
title_font_size=60,
62+
label_font_size=40,
63+
major_label_font_size=36,
64+
legend_font_size=36,
65+
value_font_size=28,
66+
stroke_width=4,
67+
opacity=0.85,
68+
opacity_hover=1.0,
69+
)
70+
71+
# Create XY chart (scatter plot with lines)
72+
chart = pygal.XY(
73+
width=4800,
74+
height=2700,
75+
style=custom_style,
76+
title="phase-diagram · pygal · pyplots.ai",
77+
x_title="Position x",
78+
y_title="Velocity dx/dt",
79+
show_dots=False, # Lines only for smooth trajectory
80+
stroke=True,
81+
fill=False,
82+
show_x_guides=True,
83+
show_y_guides=True,
84+
dots_size=3,
85+
stroke_style={"width": 4},
86+
legend_at_bottom=True,
87+
legend_box_size=24,
88+
truncate_legend=-1,
89+
show_legend=True,
90+
)
91+
92+
# Downsample for visualization (pygal works better with fewer points)
93+
step = 3
94+
damped_points = [(float(x[i]), float(v[i])) for i in range(0, n_points, step)]
95+
undamped_points = [(float(x_undamped[i]), float(v_undamped[i])) for i in range(0, n_points, step)]
96+
97+
# Add trajectories
98+
chart.add("Damped Oscillator (γ=0.3)", damped_points)
99+
chart.add("Undamped Oscillator (Limit Cycle)", undamped_points)
100+
101+
# Add fixed point marker at origin
102+
chart.add("Equilibrium (Fixed Point)", [(0, 0)], dots_size=12, show_dots=True, stroke=False)
103+
104+
# Save outputs
105+
chart.render_to_file("plot.html")
106+
chart.render_to_png("plot.png")
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
library: pygal
2+
specification_id: phase-diagram
3+
created: '2025-12-31T11:12:39Z'
4+
updated: '2025-12-31T11:20:54Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20617709332
7+
issue: 3004
8+
python_version: 3.13.11
9+
library_version: 3.1.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/phase-diagram/pygal/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/phase-diagram/pygal/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/phase-diagram/pygal/plot.html
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent demonstration of phase space concepts with both damped and undamped
17+
trajectories
18+
- Clean visual distinction between the spiral (damped) and closed loop (limit cycle)
19+
behaviors
20+
- Well-chosen colorblind-safe color scheme (blue and yellow)
21+
- Proper mathematical implementation of damped harmonic oscillator using Euler integration
22+
- Good use of pygal custom styling for large canvas rendering
23+
weaknesses:
24+
- The equilibrium point marker at origin is quite small and could be more prominent
25+
- The undamped oscillator trajectory shows some numerical drift (not a perfect ellipse
26+
due to Euler method)

0 commit comments

Comments
 (0)