Skip to content

Commit f59bdb1

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

2 files changed

Lines changed: 174 additions & 0 deletions

File tree

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
""" pyplots.ai
2+
phase-diagram: Phase Diagram (State Space Plot)
3+
Library: bokeh 3.8.1 | Python 3.13.11
4+
Quality: 91/100 | Created: 2025-12-31
5+
"""
6+
7+
# Fix module shadowing when script is named bokeh.py
8+
import sys
9+
10+
11+
sys.path = [p for p in sys.path if p and not p.endswith("implementations")]
12+
13+
import numpy as np # noqa: E402
14+
from bokeh.io import export_png, output_file, save # noqa: E402
15+
from bokeh.models import ColorBar, ColumnDataSource, LinearColorMapper # noqa: E402
16+
from bokeh.palettes import Viridis256 # noqa: E402
17+
from bokeh.plotting import figure # noqa: E402
18+
19+
20+
# Data: Damped harmonic oscillator (simple pendulum with friction)
21+
# dx/dt = v, dv/dt = -omega^2 * x - gamma * v
22+
np.random.seed(42)
23+
24+
omega = 2.0 # Natural frequency
25+
gamma = 0.3 # Damping coefficient
26+
dt = 0.02
27+
n_steps = 800
28+
29+
# Multiple trajectories from different initial conditions
30+
trajectories = []
31+
initial_conditions = [
32+
(2.0, 0.0), # Start from displacement, no velocity
33+
(-1.5, 2.0), # Start with both displacement and velocity
34+
(0.5, -2.5), # Different quadrant
35+
(2.5, 1.5), # Another starting point
36+
]
37+
38+
for x0, v0 in initial_conditions:
39+
x_traj = [x0]
40+
v_traj = [v0]
41+
t_traj = [0]
42+
x, v = x0, v0
43+
44+
for i in range(n_steps):
45+
# Euler integration of damped harmonic oscillator
46+
ax = -(omega**2) * x - gamma * v
47+
x_new = x + v * dt
48+
v_new = v + ax * dt
49+
x, v = x_new, v_new
50+
x_traj.append(x)
51+
v_traj.append(v)
52+
t_traj.append((i + 1) * dt)
53+
54+
trajectories.append((x_traj, v_traj, t_traj))
55+
56+
# Create figure
57+
p = figure(
58+
width=4800,
59+
height=2700,
60+
title="Damped Pendulum · phase-diagram · bokeh · pyplots.ai",
61+
x_axis_label="Position x (displacement)",
62+
y_axis_label="Velocity dx/dt (m/s)",
63+
tools="pan,wheel_zoom,box_zoom,reset",
64+
)
65+
66+
# Color palette for distinct trajectories
67+
traj_colors = ["#306998", "#FFD43B", "#E34A33", "#31A354"]
68+
69+
# Single color mapper for time evolution (shared across all trajectories)
70+
color_mapper = LinearColorMapper(palette=Viridis256, low=0, high=1)
71+
72+
# Plot each trajectory as connected line with time-based color markers
73+
for idx, (x_traj, v_traj, t_traj) in enumerate(trajectories):
74+
# Normalize time for color mapping
75+
t_norm = np.array(t_traj)
76+
t_norm = (t_norm - t_norm.min()) / (t_norm.max() - t_norm.min())
77+
78+
source = ColumnDataSource(data={"x": x_traj, "v": v_traj, "t_norm": t_norm.tolist()})
79+
80+
# Draw trajectory as scatter points with time-based coloring
81+
p.scatter(x="x", y="v", source=source, size=12, color={"field": "t_norm", "transform": color_mapper}, alpha=0.85)
82+
83+
# Add starting point marker (larger, colored by trajectory)
84+
p.scatter(
85+
x=[x_traj[0]],
86+
y=[v_traj[0]],
87+
size=30,
88+
color=traj_colors[idx],
89+
marker="circle",
90+
line_color="white",
91+
line_width=3,
92+
legend_label=f"Start: ({initial_conditions[idx][0]}, {initial_conditions[idx][1]})",
93+
)
94+
95+
# Mark the fixed point (equilibrium at origin)
96+
p.scatter(x=[0], y=[0], size=40, color="#D62728", marker="x", line_width=5, legend_label="Equilibrium (stable)")
97+
98+
# Add zero velocity line (where dx/dt = 0)
99+
p.line(
100+
x=[-3.5, 3.5],
101+
y=[0, 0],
102+
line_width=3,
103+
line_dash="dashed",
104+
line_color="#7F7F7F",
105+
alpha=0.7,
106+
legend_label="Zero velocity (dx/dt = 0)",
107+
)
108+
109+
# Add color bar to show time evolution
110+
color_bar = ColorBar(
111+
color_mapper=LinearColorMapper(palette=Viridis256, low=0, high=16),
112+
title="Time (s)",
113+
title_text_font_size="24pt",
114+
major_label_text_font_size="20pt",
115+
label_standoff=15,
116+
width=40,
117+
location=(0, 0),
118+
)
119+
p.add_layout(color_bar, "right")
120+
121+
# Styling for large canvas (4800x2700)
122+
p.title.text_font_size = "36pt"
123+
p.title.text_font_style = "bold"
124+
p.xaxis.axis_label_text_font_size = "28pt"
125+
p.yaxis.axis_label_text_font_size = "28pt"
126+
p.xaxis.major_label_text_font_size = "22pt"
127+
p.yaxis.major_label_text_font_size = "22pt"
128+
129+
# Grid styling
130+
p.grid.grid_line_alpha = 0.3
131+
p.grid.grid_line_dash = [6, 4]
132+
133+
# Legend styling
134+
p.legend.label_text_font_size = "20pt"
135+
p.legend.location = "top_right"
136+
p.legend.background_fill_alpha = 0.9
137+
p.legend.border_line_width = 2
138+
p.legend.padding = 15
139+
p.legend.spacing = 10
140+
141+
# Background
142+
p.background_fill_color = "#fafafa"
143+
144+
# Save as PNG and HTML
145+
export_png(p, filename="plot.png")
146+
output_file("plot.html")
147+
save(p)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
library: bokeh
2+
specification_id: phase-diagram
3+
created: '2025-12-31T11:10:12Z'
4+
updated: '2025-12-31T11:20:48Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20617651777
7+
issue: 3004
8+
python_version: 3.13.11
9+
library_version: 3.8.1
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/phase-diagram/bokeh/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/phase-diagram/bokeh/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/phase-diagram/bokeh/plot.html
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent physics simulation showing damped harmonic oscillator behavior
17+
- Beautiful Viridis color gradient effectively shows time evolution
18+
- Multiple trajectories from different initial conditions demonstrate basin of attraction
19+
- Clear marking of equilibrium point and zero velocity line
20+
- Good use of Bokeh ColumnDataSource and ColorBar features
21+
- Appropriate text sizing for the 4800x2700 canvas
22+
- Proper legend identifying all plot elements
23+
weaknesses:
24+
- Title format includes extra Damped Pendulum prefix instead of just {spec-id} ·
25+
{library} · pyplots.ai
26+
- Legend text appears small in the rendered output despite font size settings
27+
- Could add HoverTool for interactivity to show time/position on hover

0 commit comments

Comments
 (0)