{"spec_id":"streamline-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nstreamline-basic: Basic Streamline Plot\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-05-14\n\"\"\"\n\nimport os\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\n\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# Disable data row limit\nalt.data_transformers.disable_max_rows()\n\n# Data - Create a vector field for a vortex flow (u = -y, v = x)\nnp.random.seed(42)\n\n# Generate streamlines using Euler integration\nstreamlines_data = []\nstreamline_id = 0\n\n# Starting points at different radii for vortex visualization\nradii = [0.4, 0.7, 1.0, 1.4, 1.8, 2.2, 2.6, 3.0]\nn_per_radius = 6\ndt = 0.03\nmax_steps = 250\n\nfor r in radii:\n    for i in range(n_per_radius):\n        angle = 2 * np.pi * i / n_per_radius + (r * 0.1)\n        x = r * np.cos(angle)\n        y = r * np.sin(angle)\n        points = [(x, y)]\n\n        # Trace streamline using Euler integration\n        for _ in range(max_steps):\n            # Vector field: circular vortex (u = -y, v = x)\n            u = -y\n            v = x\n            mag = np.sqrt(u**2 + v**2)\n            if mag < 1e-6:\n                break\n            # Normalize and step\n            x_new = x + dt * u / mag\n            y_new = y + dt * v / mag\n            # Stop if out of bounds\n            if abs(x_new) > 3.2 or abs(y_new) > 3.2:\n                break\n            x, y = x_new, y_new\n            points.append((x, y))\n\n        # Only include streamlines with enough points\n        if len(points) > 5:\n            for j, (px, py) in enumerate(points):\n                # Velocity magnitude equals distance from center in this vortex\n                vel = np.sqrt(px**2 + py**2)\n                streamlines_data.append(\n                    {\"x\": float(px), \"y\": float(py), \"streamline_id\": streamline_id, \"order\": j, \"velocity\": float(vel)}\n                )\n            streamline_id += 1\n\ndf = pd.DataFrame(streamlines_data)\n\n# Compute average velocity per streamline for color encoding\navg_velocity = df.groupby(\"streamline_id\")[\"velocity\"].mean().reset_index()\navg_velocity.columns = [\"streamline_id\", \"avg_velocity\"]\ndf = df.merge(avg_velocity, on=\"streamline_id\")\n\n# Create the streamline chart using line marks\nchart = (\n    alt.Chart(df)\n    .mark_line(strokeWidth=2.5, opacity=0.85)\n    .encode(\n        x=alt.X(\"x:Q\", title=\"X Position (units)\", scale=alt.Scale(domain=[-3.5, 3.5])),\n        y=alt.Y(\"y:Q\", title=\"Y Position (units)\", scale=alt.Scale(domain=[-3.5, 3.5])),\n        color=alt.Color(\n            \"avg_velocity:Q\",\n            scale=alt.Scale(scheme=\"viridis\"),\n            title=\"Flow Speed\",\n            legend=alt.Legend(titleFontSize=18, labelFontSize=16, gradientLength=200),\n        ),\n        detail=\"streamline_id:N\",\n        order=\"order:O\",\n    )\n    .properties(\n        width=1600,\n        height=900,\n        title=alt.Title(\"streamline-basic · altair · anyplot.ai\", fontSize=28, anchor=\"middle\"),\n        background=PAGE_BG,\n    )\n    .configure_view(fill=PAGE_BG, stroke=INK_SOFT)\n    .configure_axis(\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK,\n        gridOpacity=0.10,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        labelFontSize=18,\n        titleFontSize=22,\n    )\n    .configure_title(color=INK, fontSize=28)\n    .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)\n)\n\n# Save as PNG and HTML\nchart.save(f\"plot-{THEME}.png\", scale_factor=3.0)\nchart.save(f\"plot-{THEME}.html\")\n"}