Evenly-spaced streamlines for 2-D, 3-D, and N-D vector fields in Julia, using the Jobard–Lefer algorithm. Works with function-defined or grid-defined velocity fields, with built-in support for Plots.jl and Makie.jl.
using Pkg
Pkg.add("UniformStreamlines")using UniformStreamlines
xs = LinRange(-2, 2, 200)
ys = LinRange(-2, 2, 200)
# From functions
u(x,y) = -y
v(x,y) = 1 + x - y^2
str = evenstream(xs, ys, u, v)
# From matrices
U = [-y for x in xs, y in ys]
V = [ x for x in xs, y in ys]
str = evenstream(xs, ys, U, V)Plot with Plots.jl:
using Plots
streamlines(str)Or with Makie:
using CairoMakie
streamlines(str)u(x,y) = -1 - x^2 + y
v(x,y) = 1 + x - y^2
# Sparse streamlines
str = evenstream(xs, ys, u, v ;
min_density=2, max_density=4)
# Dense streamlines
str = evenstream(xs, ys, u, v;
min_density=5, max_density=15)str = evenstream(xs, ys, (x, y) -> sin(π*x) * cos(π*y), (x, y) -> 0.2y)
c = colorize(str, :norm)
# Makie
streamlines(str; color=colors, color=:viridis)Built-in color symbols: :norm (or :speed), :vx (or :u), :vy (or :v), :vz (or :w), :x, :y, :z, or pass any (pos, vel) -> scalar function.
Add directional arrows with with_arrows=true. Arrows are placed uniformly along the arc length of each streamline by default:
# Uniform arrows (default)
streamlines(str; with_arrows=true)
# Control spacing explicitly
streamlines(str; with_arrows=true, arrows_spacing=0.15)
# Vertex-based placement (non-uniform, but faster)
streamlines(str; with_arrows=true, arrows_every=20)Return NaN to mask regions — streamlines stop at boundaries:
u(x, y) = (x+1)^2 + y^2 < 1 ? NaN : x + y
v(x, y) = (x+1)^2 + y^2 < 1 ? NaN : x - y
str = evenstream(xs, ys, u, v)
c = colorize(str,:norm)
streamlines(str; color=c, colormap=:coolwarm, linewidth=2)
xs = -3:3
ys = -3:3
seed_x = rand(10) .* 6 .- 3 # 10 random x-coordinates in [-3, 3]
seed_y = rand(10) .* 6 .- 3 # 10 random y-coordinates in [-3, 3]
# create an Ntuple of seed 2D vectors [x,y] from the separate x and y vectors
seeds = ntuple(i -> [seed_x[i], seed_y[i]], length(seed_x))
str = evenstream(xs, ys, (x, y) -> x + y, (x, y) -> x - y;
seeds=seeds)
c_seed = colorize(str, (p, v) -> atan(v[2], v[1]))
streamlines(str; color=c_seed, colormap=:hsv, linewidth=2)
scatter!(seed_x, seed_y; markersize=12, color=:black, label="seeds")
str_normal = evenstream(xs6, ys6,u_vdp, v_vdp)
str_unbroken = evenstream(xs6, ys6,u_vdp, v_vdp;
allow_collisions=true)
fig = Figure(size=(1000, 450));
c_normal = colorize(str_normal, :norm)
c_unbroken = colorize(str_unbroken, :norm)
ax7a = Axis(fig[1, 1]; aspect=DataAspect(), title="Default (truncated)")
streamlines!(ax7a, str_normal; color=c_normal, colormap=:turbo, linewidth=2)
tightlimits!(ax7a)
ax7b = Axis(fig[1, 2]; aspect=DataAspect(), title="allow_collisions = true")
streamlines!(ax7b, str_unbroken; color=c_unbroken, colormap=:turbo, linewidth=2)
tightlimits!(ax7b)xs = LinRange(-2, 2, 50); ys = LinRange(-2, 2, 50); zs = LinRange(-2, 2, 50)
str3 = evenstream(xs, ys, zs, (x,y,z) -> -y, (x,y,z) -> x, (x,y,z) -> 0.3z)ABC flow with arrows and speed coloring:
A, B, C = 1.0, √2, √3
str3 = evenstream(xs, ys, zs,
(x,y,z) -> A*sin(z) + C*cos(y),
(x,y,z) -> B*sin(x) + A*cos(z),
(x,y,z) -> C*sin(y) + B*cos(x);
min_density=2, max_density=4)
c3 = colorize(str3, :norm)
streamlines(str3; color=c3, colormap=:magma,
with_arrows=true, markersize=0.12)axs = ntuple(_ -> LinRange(-2, 2, 50), 4)
fns = ((x,y,z,t) -> -y, (x,y,z,t) -> x, (x,y,z,t) -> z, (x,y,z,t) -> -t)
str4 = evenstream(axs, fns)| Function | Description |
|---|---|
evenstream |
Compute evenly-spaced streamlines |
colorize |
Compute per-point color values |
streamarrows |
Extract arrow glyphs for visualization |
streamlines / streamlines! |
Plot recipe (Plots.jl or Makie) |
See the documentation for full details.







