{"spec_id":"streamline-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nstreamline-basic: Basic Streamline Plot\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 95/100 | Updated: 2026-05-14\n\"\"\"\n\nimport os\nimport time\nfrom importlib import import_module\nfrom pathlib import Path\n\nimport numpy as np\nfrom scipy.interpolate import RegularGridInterpolator\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\nbokeh_io = import_module(\"bokeh.io\")\nbokeh_models = import_module(\"bokeh.models\")\nbokeh_palettes = import_module(\"bokeh.palettes\")\nbokeh_plotting = import_module(\"bokeh.plotting\")\n\noutput_file = bokeh_io.output_file\nsave = bokeh_io.save\nHoverTool = bokeh_models.HoverTool\nViridis256 = bokeh_palettes.Viridis256\nfigure = bokeh_plotting.figure\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Seed for reproducibility\nnp.random.seed(42)\n\n# Grid setup\nx = np.linspace(-3, 3, 40)\ny = np.linspace(-3, 3, 40)\nX, Y = np.meshgrid(x, y)\n\n# Vortex flow field: u = -y, v = x (creates circular streamlines)\nU = -Y\nV = X\n\n# Compute velocity magnitude for coloring\nmagnitude = np.sqrt(U**2 + V**2)\n\n# Create interpolators for the vector field\nu_interp = RegularGridInterpolator((y, x), U, bounds_error=False, fill_value=None)\nv_interp = RegularGridInterpolator((y, x), V, bounds_error=False, fill_value=None)\nmag_interp = RegularGridInterpolator((y, x), magnitude, bounds_error=False, fill_value=None)\n\n# Seed points for streamlines in a grid pattern\nseed_x = np.linspace(-2.5, 2.5, 8)\nseed_y = np.linspace(-2.5, 2.5, 8)\n\n# Storage for streamline data\nstreamlines_data = []\n\n# Compute streamlines from seed points\nfor sx in seed_x:\n    for sy in seed_y:\n        # Trace streamline using Euler integration\n        xs, ys, mags = [sx], [sy], []\n        px, py = sx, sy\n        dt, max_steps = 0.05, 300\n\n        # Get initial magnitude\n        m = mag_interp([[py, px]])[0]\n        if m is None or np.isnan(m):\n            continue\n        mags.append(m)\n\n        for _ in range(max_steps):\n            u_val = u_interp([[py, px]])[0]\n            v_val = v_interp([[py, px]])[0]\n\n            if u_val is None or v_val is None or np.isnan(u_val) or np.isnan(v_val):\n                break\n\n            speed = np.sqrt(u_val**2 + v_val**2)\n            if speed < 1e-6:\n                break\n\n            # Normalize and step\n            px += u_val / speed * dt\n            py += v_val / speed * dt\n\n            # Check bounds\n            if px < x.min() or px > x.max() or py < y.min() or py > y.max():\n                break\n\n            xs.append(px)\n            ys.append(py)\n            m = mag_interp([[py, px]])[0]\n            if m is None or np.isnan(m):\n                break\n            mags.append(m)\n\n        # Store if streamline is long enough\n        if len(xs) >= 5:\n            avg_mag = np.mean(mags)\n            streamlines_data.append(\n                {\"xs\": np.array(xs), \"ys\": np.array(ys), \"mags\": np.array(mags), \"avg_mag\": avg_mag}\n            )\n\n# Create figure\np = figure(\n    width=4800,\n    height=2700,\n    title=\"streamline-basic · bokeh · anyplot.ai\",\n    x_axis_label=\"X Position (arbitrary units)\",\n    y_axis_label=\"Y Position (arbitrary units)\",\n    x_range=(-3.5, 3.5),\n    y_range=(-3.5, 3.5),\n)\n\n# Style title and axes for large canvas\np.title.text_font_size = \"28pt\"\np.title.text_color = INK\np.xaxis.axis_label_text_font_size = \"22pt\"\np.yaxis.axis_label_text_font_size = \"22pt\"\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\np.xaxis.major_label_text_font_size = \"18pt\"\np.yaxis.major_label_text_font_size = \"18pt\"\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\n\n# Grid styling\np.xgrid.grid_line_color = INK\np.ygrid.grid_line_color = INK\np.xgrid.grid_line_alpha = 0.10\np.ygrid.grid_line_alpha = 0.10\n\n# Spine colors\np.outline_line_color = INK_SOFT\np.xaxis.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.xaxis.major_tick_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\n\n# Background\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\n\n# Normalize magnitude for color mapping (0-1 scale for Viridis256)\nmax_mag = max(s[\"avg_mag\"] for s in streamlines_data)\nif max_mag > 0:\n    norm_mags = [s[\"avg_mag\"] / max_mag for s in streamlines_data]\nelse:\n    norm_mags = [0.5 for _ in streamlines_data]\n\n# Draw streamlines with direction arrows\nfor sl_data, norm_mag in zip(streamlines_data, norm_mags, strict=False):\n    xs = sl_data[\"xs\"]\n    ys = sl_data[\"ys\"]\n\n    # Map normalized magnitude to Viridis256 color\n    color_idx = min(int(norm_mag * 255), 255)\n    color = Viridis256[color_idx]\n\n    # Draw streamline\n    p.line(xs, ys, line_width=4, line_color=color, line_alpha=0.85)\n\n    # Add arrowhead at the end to show flow direction\n    if len(xs) >= 2:\n        dx = xs[-1] - xs[-2]\n        dy = ys[-1] - ys[-2]\n        length = np.sqrt(dx**2 + dy**2)\n        if length > 0:\n            dx /= length\n            dy /= length\n            arrow_size = 0.18\n            tip_x, tip_y = xs[-1], ys[-1]\n            wing1_x = tip_x - arrow_size * (dx + 0.5 * dy)\n            wing1_y = tip_y - arrow_size * (dy - 0.5 * dx)\n            wing2_x = tip_x - arrow_size * (dx - 0.5 * dy)\n            wing2_y = tip_y - arrow_size * (dy + 0.5 * dx)\n            p.patch(\n                [tip_x, wing1_x, wing2_x], [tip_y, wing1_y, wing2_y], fill_color=color, line_color=color, fill_alpha=0.9\n            )\n\n# Add hover tool for interactivity\nhover = HoverTool(tooltips=[(\"Position\", \"($x, $y)\")])\np.add_tools(hover)\n\n# Save HTML (required artifact)\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome for PNG\nW, H = 4800, 2700\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{H}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}