Basic Streamline Plot — Pygal

A streamline plot visualizes vector fields using smooth curves that are tangent to the field at every point. Unlike quiver plots that show discrete arrows, streamlines trace continuous paths through the field, revealing flow patterns, circulation, and field topology. This visualization is ideal for understanding fluid dynamics, electromagnetic fields, or gradient fields where the continuous nature of the flow is important.

Basic Streamline Plot rendered with Pygal

Python source (Pygal)

""" anyplot.ai
streamline-basic: Basic Streamline Plot
Library: pygal 3.1.0 | Python 3.13.13
Quality: 87/100 | Updated: 2026-05-14
"""

import os
import sys

import numpy as np


# Prevent this script's directory from shadowing the pygal package
script_dir = os.path.dirname(os.path.abspath(__file__))
sys.path = [p for p in sys.path if not (p == script_dir or p == "")]

import pygal
from pygal.style import Style


# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"

# Okabe-Ito palette
IMPRINT = (
    "#009E73",  # Brand green - position 1
    "#C475FD",  # Vermillion - position 2
    "#4467A3",  # Blue - position 3
    "#BD8233",  # Reddish purple - position 4
)

# Data - Multiple streamline patterns to showcase varied flow
# Vortex pattern: u = -y, v = x (counterclockwise circulation)
# We'll create streamlines at different starting points

streamlines = []

# Vortex streamlines from 4 orbital radii
radii = [0.7, 1.4, 2.1, 2.8]
points_per_radius = 5

for radius_idx, radius in enumerate(radii):
    for angle_idx in range(points_per_radius):
        angle = 2 * np.pi * angle_idx / points_per_radius
        x0 = radius * np.cos(angle)
        y0 = radius * np.sin(angle)

        # Trace streamline from this starting point
        points = [(x0, y0)]
        x, y = x0, y0
        dt = 0.02
        max_steps = 400
        bounds = 3.5

        for _ in range(max_steps):
            # Velocity field: vortex with radial decay
            r = np.sqrt(x**2 + y**2)
            factor = 1.0 / (1.0 + 0.1 * r)
            u = -y * factor
            v = x * factor
            speed = np.sqrt(u**2 + v**2)

            if speed < 0.001:
                break

            # Normalize and step
            x_new = x + dt * u / speed
            y_new = y + dt * v / speed

            # Check bounds
            if abs(x_new) > bounds or abs(y_new) > bounds:
                break

            x, y = x_new, y_new
            points.append((x, y))

        if len(points) > 10:
            streamlines.append((points, radius_idx))

# Group streamlines by orbital distance
binned_streamlines = {i: [] for i in range(4)}
bin_labels = ["Inner Orbit (r=0.7)", "Mid-Inner Orbit (r=1.4)", "Mid-Outer Orbit (r=2.1)", "Outer Orbit (r=2.8)"]

for points, radius_idx in streamlines:
    binned_streamlines[radius_idx].append(points)

# Create custom style with theme-adaptive colors and proper sizing
custom_style = Style(
    background=PAGE_BG,
    plot_background=PAGE_BG,
    foreground=INK,
    foreground_strong=INK,
    foreground_subtle=INK_MUTED,
    colors=IMPRINT,
    title_font_size=28,
    label_font_size=22,
    major_label_font_size=18,
    legend_font_size=16,
    value_font_size=14,
    stroke_width=3,
)

# Create chart
chart = pygal.XY(
    style=custom_style,
    width=4800,
    height=2700,
    stroke=True,
    stroke_style={"width": 3},
    show_dots=False,
    show_legend=True,
    legend_at_bottom=True,
    legend_at_bottom_columns=2,
    title="streamline-basic · pygal · anyplot.ai",
    x_title="X Position",
    y_title="Y Position",
    show_x_guides=False,
    show_y_guides=False,
    range=(-4, 4),
    xrange=(-4, 4),
)

# Add each orbital group as a series with Okabe-Ito colors
for bin_idx in range(4):
    series_data = []
    for streamline_points in binned_streamlines[bin_idx]:
        # Add streamline points
        for point in streamline_points:
            series_data.append(point)
        # Add None to separate streamlines
        series_data.append(None)

    if series_data:
        chart.add(bin_labels[bin_idx], series_data)

# Save outputs - theme-suffixed filenames
chart.render_to_png(f"plot-{THEME}.png")
with open(f"plot-{THEME}.html", "wb") as f:
    f.write(chart.render())

Part of Basic Streamline Plot on anyplot.ai.

Other implementations