Skip to content

Commit 176c794

Browse files
feat(seaborn): implement streamline-basic (#2920)
## Implementation: `streamline-basic` - seaborn Implements the **seaborn** version of `streamline-basic`. **File:** `plots/streamline-basic/implementations/seaborn.py` **Parent Issue:** #2861 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20609206863)* --------- 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 3d0be06 commit 176c794

2 files changed

Lines changed: 188 additions & 0 deletions

File tree

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
""" pyplots.ai
2+
streamline-basic: Basic Streamline Plot
3+
Library: seaborn 0.13.2 | Python 3.13.11
4+
Quality: 91/100 | Created: 2025-12-31
5+
"""
6+
7+
import matplotlib.pyplot as plt
8+
import numpy as np
9+
import pandas as pd
10+
import seaborn as sns
11+
from matplotlib.patches import FancyArrowPatch
12+
13+
14+
# Set seed for reproducibility
15+
np.random.seed(42)
16+
17+
# Vortex flow field: u = -y, v = x (creates circular streamlines)
18+
# Velocity magnitude = distance from center
19+
20+
# Generate streamlines using Euler integration
21+
streamlines_data = []
22+
arrow_data = [] # Store arrow positions for flow direction indicators
23+
streamline_id = 0
24+
25+
# Starting points at different radii - removed innermost radius to eliminate overlap artifacts
26+
radii = [0.8, 1.2, 1.6, 2.0, 2.4, 2.8]
27+
# Use fewer streamlines at inner radii to prevent crowding
28+
n_per_radius_map = {0.8: 3, 1.2: 4, 1.6: 5, 2.0: 5, 2.4: 6, 2.8: 6}
29+
dt = 0.03
30+
max_steps = 250
31+
32+
for r in radii:
33+
n_per_radius = n_per_radius_map[r]
34+
for i in range(n_per_radius):
35+
angle = 2 * np.pi * i / n_per_radius + (r * 0.15)
36+
x = r * np.cos(angle)
37+
y = r * np.sin(angle)
38+
streamline_points = []
39+
40+
# Trace streamline using Euler integration
41+
for step in range(max_steps):
42+
# Check bounds first
43+
if abs(x) > 3.2 or abs(y) > 3.2:
44+
break
45+
46+
# Vector field: circular vortex (u = -y, v = x)
47+
u = -y
48+
v = x
49+
speed = np.sqrt(u**2 + v**2)
50+
51+
if speed < 1e-6:
52+
break
53+
54+
# Store point with velocity magnitude (= radius in vortex)
55+
vel_mag = np.sqrt(x**2 + y**2)
56+
streamlines_data.append(
57+
{
58+
"x": float(x),
59+
"y": float(y),
60+
"streamline_id": streamline_id,
61+
"order": step,
62+
"velocity": float(vel_mag),
63+
}
64+
)
65+
streamline_points.append((x, y, u, v, vel_mag))
66+
67+
# Normalize and step
68+
x = x + dt * u / speed
69+
y = y + dt * v / speed
70+
71+
# Store arrow position at midpoint of each streamline
72+
if len(streamline_points) > 20:
73+
mid_idx = len(streamline_points) // 2
74+
px, py, pu, pv, pvel = streamline_points[mid_idx]
75+
arrow_data.append({"x": px, "y": py, "u": pu, "v": pv, "velocity": pvel, "radius": r})
76+
77+
streamline_id += 1
78+
79+
# Create DataFrame
80+
df = pd.DataFrame(streamlines_data)
81+
arrows_df = pd.DataFrame(arrow_data)
82+
83+
# Compute average velocity per streamline for color encoding
84+
avg_velocity = df.groupby("streamline_id")["velocity"].mean().reset_index()
85+
avg_velocity.columns = ["streamline_id", "avg_velocity"]
86+
df = df.merge(avg_velocity, on="streamline_id")
87+
88+
# Create velocity bins for categorical legend - seaborn-centric approach
89+
velocity_bins = pd.qcut(df["avg_velocity"], q=6, duplicates="drop")
90+
df["Speed Range"] = velocity_bins.apply(lambda x: f"{x.left:.1f}-{x.right:.1f} m/s")
91+
92+
# Set seaborn style with custom aesthetics
93+
sns.set_theme(
94+
style="whitegrid", rc={"axes.labelsize": 20, "axes.titlesize": 24, "xtick.labelsize": 16, "ytick.labelsize": 16}
95+
)
96+
sns.set_context("talk", font_scale=1.2)
97+
98+
# Create square figure to better utilize canvas for equal aspect ratio plot
99+
fig, ax = plt.subplots(figsize=(12, 12))
100+
101+
# Use seaborn color_palette to create viridis colors for continuous mapping
102+
palette = sns.color_palette("viridis", as_cmap=True)
103+
norm = plt.Normalize(df["avg_velocity"].min(), df["avg_velocity"].max())
104+
105+
# Plot streamlines using seaborn's lineplot with hue for velocity
106+
# Each streamline is a separate unit, colored by average velocity
107+
sns.lineplot(
108+
data=df,
109+
x="x",
110+
y="y",
111+
hue="avg_velocity",
112+
units="streamline_id",
113+
estimator=None,
114+
sort=False,
115+
linewidth=2.5,
116+
alpha=0.85,
117+
palette="viridis",
118+
legend=False,
119+
ax=ax,
120+
)
121+
122+
# Add arrowheads to show flow direction
123+
cmap = plt.cm.viridis
124+
for _, arrow in arrows_df.iterrows():
125+
px, py = arrow["x"], arrow["y"]
126+
pu, pv = arrow["u"], arrow["v"]
127+
speed = np.sqrt(pu**2 + pv**2)
128+
# Normalize direction
129+
dx = 0.15 * pu / speed
130+
dy = 0.15 * pv / speed
131+
color = cmap(norm(arrow["velocity"]))
132+
arrow_patch = FancyArrowPatch(
133+
(px - dx / 2, py - dy / 2),
134+
(px + dx / 2, py + dy / 2),
135+
arrowstyle="->,head_width=4,head_length=4",
136+
color=color,
137+
linewidth=2,
138+
mutation_scale=1,
139+
zorder=10,
140+
)
141+
ax.add_patch(arrow_patch)
142+
143+
# Add colorbar manually (seaborn lineplot doesn't auto-create one for continuous hue)
144+
sm = plt.cm.ScalarMappable(cmap="viridis", norm=norm)
145+
sm.set_array([])
146+
cbar = fig.colorbar(sm, ax=ax, shrink=0.8, aspect=20)
147+
cbar.set_label("Flow Speed (m/s)", fontsize=20)
148+
cbar.ax.tick_params(labelsize=16)
149+
150+
# Use seaborn despine for cleaner appearance
151+
sns.despine(ax=ax, left=False, bottom=False)
152+
153+
# Styling with units explicitly in axis labels
154+
ax.set(xlabel="X Position (m)", ylabel="Y Position (m)")
155+
ax.set_title("streamline-basic · seaborn · pyplots.ai", fontsize=24, fontweight="bold")
156+
ax.tick_params(axis="both", labelsize=16)
157+
ax.set_aspect("equal")
158+
ax.set_xlim(-3.5, 3.5)
159+
ax.set_ylim(-3.5, 3.5)
160+
ax.grid(True, alpha=0.3, linestyle="--")
161+
162+
plt.tight_layout()
163+
plt.savefig("plot.png", dpi=300, bbox_inches="tight")
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
library: seaborn
2+
specification_id: streamline-basic
3+
created: '2025-12-31T00:48:49Z'
4+
updated: '2025-12-31T01:07:48Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20609206863
7+
issue: 2861
8+
python_version: 3.13.11
9+
library_version: 0.13.2
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/streamline-basic/seaborn/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/streamline-basic/seaborn/plot_thumb.png
12+
preview_html: null
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent visual clarity with well-sized streamlines and viridis color encoding
17+
- Creative solution using seaborn lineplot with units parameter to render individual
18+
streamlines
19+
- Proper Euler integration for tracing streamlines through the vector field
20+
- Clean vortex pattern demonstrates the circular flow field effectively
21+
- Arrowheads correctly indicate flow direction
22+
- Good use of seaborn theming (sns.set_theme, sns.set_context, sns.despine)
23+
weaknesses:
24+
- Colorbar label missing units in rendered output
25+
- Axis labels in rendered image missing units despite being in code

0 commit comments

Comments
 (0)