|
| 1 | +""" pyplots.ai |
| 2 | +streamline-basic: Basic Streamline Plot |
| 3 | +Library: bokeh 3.8.1 | Python 3.13.11 |
| 4 | +Quality: 91/100 | Created: 2025-12-31 |
| 5 | +""" |
| 6 | + |
| 7 | +import numpy as np |
| 8 | +from bokeh.io import export_png, save |
| 9 | +from bokeh.plotting import figure |
| 10 | +from bokeh.resources import CDN |
| 11 | +from scipy.interpolate import RegularGridInterpolator |
| 12 | + |
| 13 | + |
| 14 | +# Seed for reproducibility |
| 15 | +np.random.seed(42) |
| 16 | + |
| 17 | +# Grid setup |
| 18 | +x = np.linspace(-3, 3, 40) |
| 19 | +y = np.linspace(-3, 3, 40) |
| 20 | +X, Y = np.meshgrid(x, y) |
| 21 | + |
| 22 | +# Vortex flow field: u = -y, v = x (creates circular streamlines) |
| 23 | +U = -Y |
| 24 | +V = X |
| 25 | + |
| 26 | +# Compute velocity magnitude for coloring |
| 27 | +magnitude = np.sqrt(U**2 + V**2) |
| 28 | + |
| 29 | +# Create interpolators for the vector field |
| 30 | +u_interp = RegularGridInterpolator((y, x), U, bounds_error=False, fill_value=None) |
| 31 | +v_interp = RegularGridInterpolator((y, x), V, bounds_error=False, fill_value=None) |
| 32 | +mag_interp = RegularGridInterpolator((y, x), magnitude, bounds_error=False, fill_value=None) |
| 33 | + |
| 34 | +# Seed points for streamlines in a grid pattern |
| 35 | +seed_x = np.linspace(-2.5, 2.5, 8) |
| 36 | +seed_y = np.linspace(-2.5, 2.5, 8) |
| 37 | + |
| 38 | +# Storage for streamline data |
| 39 | +all_xs = [] |
| 40 | +all_ys = [] |
| 41 | +all_colors = [] |
| 42 | + |
| 43 | +# Compute streamlines from seed points |
| 44 | +for sx in seed_x: |
| 45 | + for sy in seed_y: |
| 46 | + # Trace streamline using Euler integration |
| 47 | + xs, ys, mags = [sx], [sy], [] |
| 48 | + px, py = sx, sy |
| 49 | + dt, max_steps = 0.05, 300 |
| 50 | + |
| 51 | + # Get initial magnitude |
| 52 | + m = mag_interp([[py, px]])[0] |
| 53 | + if m is None or np.isnan(m): |
| 54 | + continue |
| 55 | + mags.append(m) |
| 56 | + |
| 57 | + for _ in range(max_steps): |
| 58 | + u_val = u_interp([[py, px]])[0] |
| 59 | + v_val = v_interp([[py, px]])[0] |
| 60 | + |
| 61 | + if u_val is None or v_val is None or np.isnan(u_val) or np.isnan(v_val): |
| 62 | + break |
| 63 | + |
| 64 | + speed = np.sqrt(u_val**2 + v_val**2) |
| 65 | + if speed < 1e-6: |
| 66 | + break |
| 67 | + |
| 68 | + # Normalize and step |
| 69 | + px += u_val / speed * dt |
| 70 | + py += v_val / speed * dt |
| 71 | + |
| 72 | + # Check bounds |
| 73 | + if px < x.min() or px > x.max() or py < y.min() or py > y.max(): |
| 74 | + break |
| 75 | + |
| 76 | + xs.append(px) |
| 77 | + ys.append(py) |
| 78 | + m = mag_interp([[py, px]])[0] |
| 79 | + if m is None or np.isnan(m): |
| 80 | + break |
| 81 | + mags.append(m) |
| 82 | + |
| 83 | + # Store if streamline is long enough |
| 84 | + if len(xs) >= 5: |
| 85 | + all_xs.append(np.array(xs)) |
| 86 | + all_ys.append(np.array(ys)) |
| 87 | + # Color based on average magnitude (blue to yellow gradient) |
| 88 | + avg_mag = np.mean(mags) |
| 89 | + t = np.clip(avg_mag / 3.0, 0, 1) |
| 90 | + r = int(48 + t * (255 - 48)) |
| 91 | + g = int(105 + t * (212 - 105)) |
| 92 | + b = int(152 + t * (59 - 152)) |
| 93 | + all_colors.append(f"#{r:02x}{g:02x}{b:02x}") |
| 94 | + |
| 95 | +# Create figure |
| 96 | +p = figure( |
| 97 | + width=4800, |
| 98 | + height=2700, |
| 99 | + title="streamline-basic · bokeh · pyplots.ai", |
| 100 | + x_axis_label="X Position", |
| 101 | + y_axis_label="Y Position", |
| 102 | + x_range=(-3.5, 3.5), |
| 103 | + y_range=(-3.5, 3.5), |
| 104 | +) |
| 105 | + |
| 106 | +# Style title and axes for large canvas |
| 107 | +p.title.text_font_size = "32pt" |
| 108 | +p.xaxis.axis_label_text_font_size = "26pt" |
| 109 | +p.yaxis.axis_label_text_font_size = "26pt" |
| 110 | +p.xaxis.major_label_text_font_size = "20pt" |
| 111 | +p.yaxis.major_label_text_font_size = "20pt" |
| 112 | + |
| 113 | +# Grid styling |
| 114 | +p.grid.grid_line_alpha = 0.3 |
| 115 | +p.grid.grid_line_dash = [6, 4] |
| 116 | + |
| 117 | +# Draw streamlines with direction arrows |
| 118 | +for xs, ys, color in zip(all_xs, all_ys, all_colors, strict=True): |
| 119 | + p.line(xs, ys, line_width=4, line_color=color, line_alpha=0.85) |
| 120 | + |
| 121 | + # Add arrowhead at the end to show flow direction |
| 122 | + if len(xs) >= 2: |
| 123 | + dx = xs[-1] - xs[-2] |
| 124 | + dy = ys[-1] - ys[-2] |
| 125 | + length = np.sqrt(dx**2 + dy**2) |
| 126 | + if length > 0: |
| 127 | + dx /= length |
| 128 | + dy /= length |
| 129 | + arrow_size = 0.18 |
| 130 | + tip_x, tip_y = xs[-1], ys[-1] |
| 131 | + wing1_x = tip_x - arrow_size * (dx + 0.5 * dy) |
| 132 | + wing1_y = tip_y - arrow_size * (dy - 0.5 * dx) |
| 133 | + wing2_x = tip_x - arrow_size * (dx - 0.5 * dy) |
| 134 | + wing2_y = tip_y - arrow_size * (dy + 0.5 * dx) |
| 135 | + p.patch( |
| 136 | + [tip_x, wing1_x, wing2_x], [tip_y, wing1_y, wing2_y], fill_color=color, line_color=color, fill_alpha=0.9 |
| 137 | + ) |
| 138 | + |
| 139 | +# Background |
| 140 | +p.background_fill_color = "#fafafa" |
| 141 | + |
| 142 | +# Save outputs |
| 143 | +export_png(p, filename="plot.png") |
| 144 | +save(p, filename="plot.html", resources=CDN, title="Streamline Plot") |
0 commit comments