Skip to content

Commit bf3ff2b

Browse files
feat(letsplot): implement bifurcation-basic (#5119)
## Implementation: `bifurcation-basic` - letsplot Implements the **letsplot** version of `bifurcation-basic`. **File:** `plots/bifurcation-basic/implementations/letsplot.py` **Parent Issue:** #4415 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/23361206404)* --------- 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 97d0c25 commit bf3ff2b

2 files changed

Lines changed: 350 additions & 0 deletions

File tree

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
""" pyplots.ai
2+
bifurcation-basic: Bifurcation Diagram for Dynamical Systems
3+
Library: letsplot 4.9.0 | Python 3.14.3
4+
Quality: 93/100 | Created: 2026-03-20
5+
"""
6+
7+
import numpy as np
8+
import pandas as pd
9+
from lets_plot import * # noqa: F403
10+
from lets_plot.export import ggsave as export_ggsave
11+
12+
13+
LetsPlot.setup_html() # noqa: F405
14+
15+
# Data - Logistic map: x(n+1) = r * x(n) * (1 - x(n))
16+
# Higher resolution in chaotic regime for denser visualization
17+
r_stable = np.linspace(2.5, 3.45, 600)
18+
r_chaotic = np.linspace(3.45, 4.0, 1600)
19+
r_values = np.concatenate([r_stable, r_chaotic])
20+
transient = 250
21+
iterations = 100
22+
23+
r_all = []
24+
x_all = []
25+
26+
for r in r_values:
27+
x = 0.5
28+
for _ in range(transient):
29+
x = r * x * (1.0 - x)
30+
for _ in range(iterations):
31+
x = r * x * (1.0 - x)
32+
r_all.append(r)
33+
x_all.append(x)
34+
35+
df = pd.DataFrame({"r": np.array(r_all), "x": np.array(x_all)})
36+
37+
# Key bifurcation points
38+
bif_r = [3.0, 3.449, 3.544, 3.5699]
39+
segments_df = pd.DataFrame({"r": bif_r, "ymin": [0.0] * 4, "ymax": [1.0] * 4})
40+
41+
# Stagger labels at different y positions to avoid overlap
42+
labels_df = pd.DataFrame(
43+
{"r": bif_r, "x": [0.93, 0.83, 0.73, 0.63], "label": ["Period-2", "Period-4", "Period-8", "Chaos"]}
44+
)
45+
46+
# Feigenbaum point annotation
47+
feigen_df = pd.DataFrame({"r": [3.5699], "x": [0.05], "label": ["δ ≈ 4.669 (Feigenbaum)"]})
48+
49+
# Plot with perceptually uniform viridis-based gradient
50+
plot = (
51+
ggplot(df, aes(x="r", y="x", color="r")) # noqa: F405
52+
+ geom_point( # noqa: F405
53+
size=0.4,
54+
alpha=0.35,
55+
tooltips="none",
56+
show_legend=False,
57+
sampling=sampling_pick(n=220000), # noqa: F405
58+
)
59+
+ scale_color_gradientn( # noqa: F405
60+
colors=["#440154", "#414487", "#2A788E", "#22A884", "#7AD151"], name="r"
61+
)
62+
+ geom_segment( # noqa: F405
63+
aes(x="r", y="ymin", xend="r", yend="ymax"), # noqa: F405
64+
data=segments_df,
65+
color="#AAAAAA",
66+
size=0.3,
67+
linetype="dashed",
68+
inherit_aes=False,
69+
tooltips="none",
70+
)
71+
+ geom_text( # noqa: F405
72+
aes(x="r", y="x", label="label"), # noqa: F405
73+
data=labels_df,
74+
size=13,
75+
color="#555555",
76+
hjust=0.5,
77+
vjust=0,
78+
inherit_aes=False,
79+
)
80+
+ geom_text( # noqa: F405
81+
aes(x="r", y="x", label="label"), # noqa: F405
82+
data=feigen_df,
83+
size=11,
84+
color="#777777",
85+
hjust=0,
86+
vjust=1,
87+
fontface="italic",
88+
nudge_x=0.02,
89+
inherit_aes=False,
90+
)
91+
+ guides(color="none") # noqa: F405
92+
+ labs( # noqa: F405
93+
x="Growth Rate (r)",
94+
y="Population (x)",
95+
title="bifurcation-basic · letsplot · pyplots.ai",
96+
caption="Logistic map: x(n+1) = r · x(n) · (1 − x(n))",
97+
)
98+
+ scale_x_continuous( # noqa: F405
99+
breaks=[2.5, 2.75, 3.0, 3.25, 3.5, 3.75, 4.0], expand=[0.02, 0], format=".2f"
100+
)
101+
+ scale_y_continuous( # noqa: F405
102+
breaks=[0.0, 0.2, 0.4, 0.6, 0.8, 1.0], expand=[0.02, 0], format=".1f"
103+
)
104+
+ coord_cartesian(ylim=[0, 1]) # noqa: F405
105+
+ ggsize(1600, 900) # noqa: F405
106+
+ theme_minimal() # noqa: F405
107+
+ theme( # noqa: F405
108+
axis_text=element_text(size=16, color="#555555"), # noqa: F405
109+
axis_title=element_text(size=20, color="#333333"), # noqa: F405
110+
plot_title=element_text(size=24, color="#222222", face="bold"), # noqa: F405
111+
plot_caption=element_text(size=13, color="#888888", face="italic"), # noqa: F405
112+
panel_grid_major_x=element_line(color="#E8E8E8", size=0.3), # noqa: F405
113+
panel_grid_major_y=element_blank(), # noqa: F405
114+
panel_grid_minor=element_blank(), # noqa: F405
115+
plot_background=element_rect(fill="#FAFAFA", color="#FAFAFA"), # noqa: F405
116+
panel_background=element_rect(fill="#FAFAFA", color="#FAFAFA"), # noqa: F405
117+
axis_ticks=element_line(color="#CCCCCC", size=0.3), # noqa: F405
118+
axis_line=element_line(color="#CCCCCC", size=0.4), # noqa: F405
119+
plot_margin=[30, 40, 20, 20],
120+
)
121+
)
122+
123+
# Save
124+
export_ggsave(plot, filename="plot.png", path=".", scale=3)
125+
export_ggsave(plot, filename="plot.html", path=".")
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
library: letsplot
2+
specification_id: bifurcation-basic
3+
created: '2026-03-20T20:31:53Z'
4+
updated: '2026-03-20T20:57:00Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 23361206404
7+
issue: 4415
8+
python_version: 3.14.3
9+
library_version: 4.9.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/bifurcation-basic/letsplot/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/bifurcation-basic/letsplot/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/bifurcation-basic/letsplot/plot.html
13+
quality_score: 93
14+
review:
15+
strengths:
16+
- Excellent density visualization with appropriate point size and alpha for 220k+
17+
data points
18+
- Strong data storytelling through color gradient, staggered bifurcation labels,
19+
and Feigenbaum annotation
20+
- Polished visual refinement with custom background, selective grid, and styled
21+
axis elements
22+
- Higher resolution sampling in chaotic regime (1600 vs 600 points) shows attention
23+
to data quality
24+
- Clean, deterministic code with appropriate use of letsplot-specific features
25+
weaknesses:
26+
- DE-01 could reach 7-8 with more distinctive typography or additional design polish
27+
- LM-01 could leverage more advanced letsplot grammar patterns
28+
image_description: 'The plot shows a bifurcation diagram of the logistic map with
29+
"Growth Rate (r)" on the x-axis (2.50–4.00) and "Population (x)" on the y-axis
30+
(0.0–1.0). Points are colored using a viridis-based gradient transitioning from
31+
dark purple (low r) through teal to bright green (high r). The diagram clearly
32+
shows the period-doubling cascade: a single stable fixed point for r < 3.0, period-2
33+
bifurcation at r ≈ 3.0, period-4 at r ≈ 3.449, period-8 at r ≈ 3.544, and then
34+
chaos. Dashed vertical gray lines mark each bifurcation point. Text labels "Period-2",
35+
"Period-4", "Period-8", and "Chaos" are staggered at different y positions to
36+
avoid overlap. An italic annotation "δ ≈ 4.669 (Feigenbaum)" appears near the
37+
bottom right. The title reads "bifurcation-basic · letsplot · pyplots.ai" in bold.
38+
A caption provides the logistic map equation. The background is a light off-white
39+
(#FAFAFA), with subtle vertical-only grid lines. Overall the plot is clean, professional,
40+
and clearly communicates the route to chaos.'
41+
criteria_checklist:
42+
visual_quality:
43+
score: 30
44+
max: 30
45+
items:
46+
- id: VQ-01
47+
name: Text Legibility
48+
score: 8
49+
max: 8
50+
passed: true
51+
comment: 'All font sizes explicitly set: title=24, axis_title=20, axis_text=16,
52+
caption=13, labels=13/11'
53+
- id: VQ-02
54+
name: No Overlap
55+
score: 6
56+
max: 6
57+
passed: true
58+
comment: Labels staggered at different y positions, no text collisions
59+
- id: VQ-03
60+
name: Element Visibility
61+
score: 6
62+
max: 6
63+
passed: true
64+
comment: Point size=0.4 with alpha=0.35 optimal for 220k-point density visualization
65+
- id: VQ-04
66+
name: Color Accessibility
67+
score: 4
68+
max: 4
69+
passed: true
70+
comment: Viridis-based gradient is colorblind-safe with excellent contrast
71+
- id: VQ-05
72+
name: Layout & Canvas
73+
score: 4
74+
max: 4
75+
passed: true
76+
comment: Plot fills canvas well with balanced margins, nothing cut off
77+
- id: VQ-06
78+
name: Axis Labels & Title
79+
score: 2
80+
max: 2
81+
passed: true
82+
comment: 'Descriptive labels with parameter notation: Growth Rate (r), Population
83+
(x)'
84+
design_excellence:
85+
score: 16
86+
max: 20
87+
items:
88+
- id: DE-01
89+
name: Aesthetic Sophistication
90+
score: 6
91+
max: 8
92+
passed: true
93+
comment: Custom viridis gradient, custom background, intentional typography
94+
hierarchy, clearly above defaults
95+
- id: DE-02
96+
name: Visual Refinement
97+
score: 5
98+
max: 6
99+
passed: true
100+
comment: Y-grid removed, subtle x-only grid, custom background, styled axis
101+
elements, generous margins
102+
- id: DE-03
103+
name: Data Storytelling
104+
score: 5
105+
max: 6
106+
passed: true
107+
comment: Color gradient reinforces parameter progression, staggered labels
108+
guide viewer, Feigenbaum annotation adds depth
109+
spec_compliance:
110+
score: 15
111+
max: 15
112+
items:
113+
- id: SC-01
114+
name: Plot Type
115+
score: 5
116+
max: 5
117+
passed: true
118+
comment: Correct bifurcation diagram
119+
- id: SC-02
120+
name: Required Features
121+
score: 4
122+
max: 4
123+
passed: true
124+
comment: 'All spec features present: small points, alpha, full cascade, labeled
125+
bifurcation points, transients discarded'
126+
- id: SC-03
127+
name: Data Mapping
128+
score: 3
129+
max: 3
130+
passed: true
131+
comment: X=r (parameter), Y=x (state), correct mapping
132+
- id: SC-04
133+
name: Title & Legend
134+
score: 3
135+
max: 3
136+
passed: true
137+
comment: Title format correct, no legend needed for continuous color
138+
data_quality:
139+
score: 15
140+
max: 15
141+
items:
142+
- id: DQ-01
143+
name: Feature Coverage
144+
score: 6
145+
max: 6
146+
passed: true
147+
comment: Shows stable fixed point, period-2/4/8, chaos, windows of stability,
148+
higher resolution in chaotic regime
149+
- id: DQ-02
150+
name: Realistic Context
151+
score: 5
152+
max: 5
153+
passed: true
154+
comment: Logistic map is canonical example with real ecology applications
155+
- id: DQ-03
156+
name: Appropriate Scale
157+
score: 4
158+
max: 4
159+
passed: true
160+
comment: Standard parameter range r=[2.5, 4.0], x=[0, 1]
161+
code_quality:
162+
score: 10
163+
max: 10
164+
items:
165+
- id: CQ-01
166+
name: KISS Structure
167+
score: 3
168+
max: 3
169+
passed: true
170+
comment: 'Clean linear flow: imports, data generation, plot, save'
171+
- id: CQ-02
172+
name: Reproducibility
173+
score: 2
174+
max: 2
175+
passed: true
176+
comment: Fully deterministic with fixed initial condition, no randomness
177+
- id: CQ-03
178+
name: Clean Imports
179+
score: 2
180+
max: 2
181+
passed: true
182+
comment: All imports used
183+
- id: CQ-04
184+
name: Code Elegance
185+
score: 2
186+
max: 2
187+
passed: true
188+
comment: Appropriate complexity, no over-engineering
189+
- id: CQ-05
190+
name: Output & API
191+
score: 1
192+
max: 1
193+
passed: true
194+
comment: Saves as plot.png via export_ggsave with scale=3
195+
library_mastery:
196+
score: 7
197+
max: 10
198+
items:
199+
- id: LM-01
200+
name: Idiomatic Usage
201+
score: 4
202+
max: 5
203+
passed: true
204+
comment: Proper ggplot grammar with aes, layered geoms, scales, themes, sampling_pick
205+
- id: LM-02
206+
name: Distinctive Features
207+
score: 3
208+
max: 5
209+
passed: true
210+
comment: Uses sampling_pick, tooltips control, export_ggsave with scale, ggsize,
211+
HTML export
212+
verdict: APPROVED
213+
impl_tags:
214+
dependencies: []
215+
techniques:
216+
- annotations
217+
- layer-composition
218+
patterns:
219+
- data-generation
220+
- iteration-over-groups
221+
dataprep: []
222+
styling:
223+
- custom-colormap
224+
- alpha-blending
225+
- grid-styling

0 commit comments

Comments
 (0)