Skip to content

Commit be29bda

Browse files
feat(pygal): implement streamline-basic (#2913)
## Implementation: `streamline-basic` - pygal Implements the **pygal** version of `streamline-basic`. **File:** `plots/streamline-basic/implementations/pygal.py` **Parent Issue:** #2861 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20608714729)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 9427107 commit be29bda

2 files changed

Lines changed: 156 additions & 0 deletions

File tree

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
""" pyplots.ai
2+
streamline-basic: Basic Streamline Plot
3+
Library: pygal 3.1.0 | Python 3.13.11
4+
Quality: 77/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 - Vortex flow field (circular streamlines)
13+
# u = -y, v = x creates counterclockwise circular flow
14+
np.random.seed(42)
15+
16+
# Trace streamlines inline - KISS structure (no helper functions)
17+
# Starting points distributed at different radii to show flow structure
18+
streamlines = []
19+
20+
# Use 4 radii to show different orbital distances
21+
radii = [0.8, 1.3, 1.8, 2.3]
22+
points_per_radius = 5
23+
24+
for radius in radii:
25+
for angle_idx in range(points_per_radius):
26+
angle = 2 * np.pi * angle_idx / points_per_radius
27+
x0 = radius * np.cos(angle)
28+
y0 = radius * np.sin(angle)
29+
30+
# Trace streamline from this starting point
31+
points = [(x0, y0)]
32+
x, y = x0, y0
33+
dt = 0.02 # Smaller step for smoother curves
34+
max_steps = 400 # More steps for longer, smoother curves
35+
bounds = 3.0
36+
37+
for _ in range(max_steps):
38+
# Velocity field: vortex with radial decay
39+
r = np.sqrt(x**2 + y**2)
40+
factor = 1.0 / (1.0 + 0.1 * r)
41+
u = -y * factor
42+
v = x * factor
43+
speed = np.sqrt(u**2 + v**2)
44+
45+
if speed < 0.001: # Stagnation point
46+
break
47+
48+
# Normalize and step
49+
x_new = x + dt * u / speed
50+
y_new = y + dt * v / speed
51+
52+
# Check bounds
53+
if abs(x_new) > bounds or abs(y_new) > bounds:
54+
break
55+
56+
x, y = x_new, y_new
57+
points.append((x, y))
58+
59+
if len(points) > 10: # Only keep meaningful streamlines
60+
streamlines.append((points, radius))
61+
62+
# Group streamlines by radial distance for coloring
63+
# 4 distinct high-contrast colors for different radii
64+
# Labels describe orbit radius, not "speed" - for vortex flow, all orbits have same
65+
# angular velocity so inner orbits have lower linear speed
66+
bin_colors = ["#d62728", "#2ca02c", "#1f77b4", "#9467bd"]
67+
bin_labels = ["Inner Orbit (r=0.8)", "Mid-Inner Orbit (r=1.3)", "Mid-Outer Orbit (r=1.8)", "Outer Orbit (r=2.3)"]
68+
69+
binned_streamlines = {i: [] for i in range(4)}
70+
for points, radius in streamlines:
71+
bin_idx = radii.index(radius)
72+
binned_streamlines[bin_idx].append(points)
73+
74+
# Custom style with larger fonts for readability
75+
custom_style = Style(
76+
background="white",
77+
plot_background="white",
78+
foreground="#333333",
79+
foreground_strong="#333333",
80+
foreground_subtle="#555555",
81+
colors=tuple(bin_colors),
82+
title_font_size=72,
83+
label_font_size=48,
84+
major_label_font_size=40,
85+
legend_font_size=48,
86+
value_font_size=32,
87+
guide_stroke_color="#cccccc",
88+
)
89+
90+
# Create chart
91+
chart = pygal.XY(
92+
style=custom_style,
93+
width=4800,
94+
height=2700,
95+
stroke=True,
96+
stroke_style={"width": 10},
97+
show_dots=False,
98+
show_legend=True,
99+
legend_at_bottom=True,
100+
legend_at_bottom_columns=4,
101+
title="streamline-basic · pygal · pyplots.ai",
102+
x_title="X Position",
103+
y_title="Y Position",
104+
show_x_guides=True,
105+
show_y_guides=True,
106+
range=(-3.5, 3.5),
107+
xrange=(-3.5, 3.5),
108+
dots_size=8, # Larger dots for legend visibility
109+
)
110+
111+
# Add each bin as a series
112+
for bin_idx in range(4):
113+
series_data = []
114+
for sl in binned_streamlines[bin_idx]:
115+
# Add streamline points
116+
for point in sl:
117+
series_data.append(point)
118+
# Add None to separate streamlines
119+
series_data.append(None)
120+
121+
if series_data:
122+
chart.add(bin_labels[bin_idx], series_data)
123+
124+
# Save outputs
125+
chart.render_to_png("plot.png")
126+
chart.render_to_file("plot.html")
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
library: pygal
2+
specification_id: streamline-basic
3+
created: '2025-12-31T00:16:07Z'
4+
updated: '2025-12-31T00:35:04Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20608714729
7+
issue: 2861
8+
python_version: 3.13.11
9+
library_version: 3.1.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/streamline-basic/pygal/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/streamline-basic/pygal/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/streamline-basic/pygal/plot.html
13+
quality_score: 77
14+
review:
15+
strengths:
16+
- Creative workaround for pygal lack of native streamline support using XY connected
17+
lines
18+
- Good visual representation of vortex flow with distinct colored orbital groups
19+
- Clean code structure following KISS principles
20+
- Appropriate use of custom Style for font sizing and colors
21+
- Good canvas utilization with balanced layout
22+
- Correct title format
23+
weaknesses:
24+
- Legend labels in rendered image appear truncated compared to code-defined labels
25+
- The blue color tones (Near-Inner and Middle) are somewhat similar and could be
26+
more distinct
27+
- Streamlines only show closed circular orbits - more varied flow features would
28+
better demonstrate streamline capabilities
29+
- Random seed is set but not actually needed since the vortex field is mathematically
30+
deterministic

0 commit comments

Comments
 (0)