
Chris Parmer
November 9, 2026
Vector Field Plots - A Complete Guide
TL;DR
- A vector field plot draws a quantity that has both a size and a direction, such as wind, an ocean current or a magnetic field, at many places at once: an arrow at each point shows which way it goes, and the arrow's length or color shows how strong it is.
- Vector field plots are also known as quiver plots, vector plots and arrow plots, and when they trace the flow instead of drawing arrows, as streamline plots. They are similar to contour plots, which show the size of a field but not its direction, and to wind rose charts, which summarize the wind at one place over time. Wind barbs are a different, meteorological symbol for the same data.
- The four forms Plotly draws are quiver plots (arrows at points), streamlines (curves that follow the flow), cone plots (3D arrows) and streamtubes (3D tubes that follow the flow from starting points you choose).
- You can make a vector field plot with Plotly in Python from plotly.figure_factory.create_quiver and create_streamline in 2D, and the go.Cone and go.Streamtube traces in 3D.
What is a vector field plot?
Vector field plots look like this:

Data: US National Oceanic and Atmospheric Administration (NOAA), Global Forecast System (GFS) analysis, 0.25 degree grid.
How do you read a vector field plot? In the example above, notice these things:
- Each of the 510 arrows is the wind 10 m above the surface at one grid point, every 1.5 degrees. It points the way the wind blows, and its length is proportional to the wind speed, so the 10 m/s arrow in the corner is the ruler for all the others.
- The winds turn counterclockwise around the low of 983 hectopascals (hPa): at 57.5°N, the wind at 30°W, on the west side of the low, blows mostly south (v = −10.1 m/s), and at 15°W, on the east side, mostly north (v = 16.2 m/s).
- The gray lines are isobars, lines of equal air pressure, every 4 hPa. Over the open ocean the arrows don't run exactly along them but cross them at about 15° toward the low, because friction with the sea surface slows the wind.
- The longest arrows are south-east of the low, where the isobars bunch up. The fastest wind on the full grid is 21.8 m/s, at 48.75°N 17.5°W.
It's often used for charts like these:

Ocean currents. Streamlines follow the Gulf Stream from Florida past Cape Hatteras into the open Atlantic, where it meanders at up to 1.9 m/s and sheds eddies on both sides. Data: NOAA Atlantic Oceanographic and Meteorological Laboratory (AOML), near-real-time geostrophic currents from satellite altimetry, via NOAA CoastWatch.

Winds in 3D. A cone plot shows the wind at three heights, and the fastest cones, up to 85 m/s, are in the jet stream 12 km above Nevada, where the average wind is five times the average near the ground. Data: NOAA GFS analysis at 11 pressure levels.

The gradient of a surface, the classic textbook example. The arrows of a gradient point straight uphill and cross every contour at a right angle, longest where the slope is steepest, 0.94 m of rise per meter on this volcano. Data: R's volcano data set, Maunga Whau (Mt Eden), digitized from a topographic map by Ross Ihaka.
How to read a vector field: arrows, scale and streamlines
A vector field plot assumes a few ideas that are second nature to meteorologists and engineers but not to everyone. This section goes through them with the wind data from the first chart.
A vector has a direction and a size
Temperature or elevation is one number at each place, a scalar, and a heatmap or a contour plot can show it. Wind needs two numbers at each place, because it has a direction as well as a speed. Data files store it as two components: u, the speed toward the east (negative means toward the west), and v, the speed toward the north. This is the wind at 55°N 15°W from the first chart:

Data: NOAA GFS analysis, September 29, 2026, 00:00 UTC (Coordinated Universal Time).
The arrow is the two components added together: 8.43 m/s toward the west and 15.72 m/s toward the north make a wind of √(8.43² + 15.72²) = 17.8 m/s toward the north-northwest. A quiver plot draws that arrow at the place it was measured, scaled down to fit the map, and repeats it for every grid point. Meteorologists name a wind by where it comes from, so this one is a wind from 152°, a south-south-easterly. Keep that difference in mind when you turn a speed and a direction into u and v (the FAQ has the formula), because getting it backwards flips every arrow.
Arrow scaling
Arrow lengths have to be scaled from the data's units (m/s) into the plot's units (degrees of latitude here), and the right factor depends on how far apart the arrows are. create_quiver multiplies u and v by scale, and these are the same winds with three values of it:

Data: NOAA GFS analysis, the first chart's winds between 44°N and 60°N.
A good rule is to make the longest arrow a little shorter than the gap between two arrows, and to thin the grid (here every sixth point) rather than shrink the arrows until they're unreadable. On a map there is one more step. A degree of longitude is shorter than a degree of latitude, by a factor of cos(latitude), 0.63 at 51°N, so set the map's aspect with scaleratio = 1/cos(latitude) and pass the same scaleratio to create_quiver. Without it, the map is stretched east to west and every arrow points in a slightly wrong direction.
Streamlines and arrows
Arrows show the field at separate points. Streamlines are curves that are everywhere parallel to the field, so they show the paths it describes, like the swirls of iron filings around a magnet. A streamline plot is the better picture of flow (the Gulf Stream chart above), and a quiver plot is better when the value at each point matters or the data are scattered (example 2 below uses GPS stations). Streamlines don't show speed by their length, so add color, or read speed from how close together they are, which is what example 6 does.
Streamlines describe the field at one moment. In a flow that changes over time they are not the paths that real particles take (pathlines), and they coincide only when the flow is steady, as Wikipedia's article on streamlines, streaklines and pathlines explains.
3D: cones, sizemode and seeding streamtubes
In 3D, go.Cone is the arrow and go.Streamtube is the streamline. A cone's size is set by sizemode and sizeref. With "scaled" (the default), sizes are relative to the largest vector in the trace, so the biggest cone is about as long as the gap between points. With "absolute", sizeref is used as given, so two cone traces or animation frames with the same sizeref share one scale. With "raw", a cone is drawn exactly as long as its vector in the axis units.
A streamtube needs starting points, which you set with starts. Without them, plotly.js starts tubes on the face of the grid with the smallest y value, which for these winds is the southern edge at 20°N:

Data: NOAA GFS analysis, November 1, 2026, 00:00 UTC, 11 pressure levels.
The result is a tangle of tubes from the subtropics. Example 1 below passes 20 starting points on the west coast at four heights instead, which gives one readable tube per path. Choose starting points where the flow enters the region you care about, and use fewer than you think.
A short history
Edmond Halley drew what is generally considered the first meteorological map in 1686, a chart of the trade winds published with a paper on the trade winds and monsoons in the Philosophical Transactions of the Royal Society. Instead of arrows he drew rows of short strokes, "the sharp end of each little stroak pointing out that part of the Horizon, from whence the Wind continually comes", the same from-direction convention as today's wind barbs. Michael Faraday's lines of force, which iron filings make visible around a magnet, gave physics the streamline picture (Wikipedia). The name quiver plot comes from MATLAB's quiver function, a "Quiver or vector plot" in its documentation, and Matplotlib and Plotly use it too. plotly.js added 3D cone traces in version 1.38 (May 2018) and streamtubes in 1.39 (July 2018), according to its changelog. This is Halley's chart redrawn with eight years of satellite winds, in his style:

Data: Advanced Scatterometer (ASCAT) winds from the European Metop satellites, processed by NOAA, via NOAA CoastWatch, monthly means averaged over 2014 to 2021.
The satellite averages show the same pattern Halley drew: between 10°N and 25°N in the Atlantic the average wind blows from 65° (the north-east trades) at 6.4 m/s, and between 10°S and 25°S from 113° (the south-east trades) at 6.1 m/s.
Alternatives to a vector field plot
Here's the same North Atlantic low drawn four ways. Some are better for the question, some worse, some just different:

- Quiver plot (the default). Arrows show direction and speed at every point and work for scattered data, but they get crowded, and the eye has to join them up to see the circulation.
- Streamlines (better for flow). The spiral into the low is obvious at once, but speed is gone unless you add color, and create_streamline needs a regular grid.
- Heatmap or contour plot of speed (worse for direction, better for magnitude). It shows exactly where the wind is strongest, which matters most for wind energy (example 4), but not which way it blows. Combine it with arrows or streamlines.
- Wind barbs (different). The meteorological convention: the staff points to where the wind comes from, and each long feather is 10 knots, a short one 5 and a flag 50, as the US National Weather Service explains. Barbs give speed to the nearest 5 knots, which forecasters read easily and most other audiences don't. You can draw them with Plotly as line segments, as this figure does.
- Wind rose (different). It shows how often the wind blows from each direction at one place over months, instead of everywhere at one time.
- 3D cones or streamtubes (for 3D fields). Use them when height matters, as in the cone plot above and example 1 below; for a single layer a 2D plot is easier to read. A 3D surface plot shows a scalar over a plane in 3D, not a vector.
How to make a vector field plot
With AI
ChatGPT, Claude Code, GitHub Copilot, Cursor and other AI tools are excellent at writing Plotly code. Name Plotly in the prompt, and say how the components are stored and how to scale the arrows:
Make a plotly quiver plot from winds.csv (columns: lat, lon, u, v; u is the eastward and v the northwardwind in m/s, on a 0.25 degree grid).- Use plotly.figure_factory.create_quiver with an arrow every 1.5 degrees, scale so the longest arrow is alittle shorter than the 1.5 degree spacing, and scaleratio = 1/cos(mean latitude) so the map isn't stretched.- Add a 10 m/s key arrow in a corner and light gray land outlines from Natural Earth.- Put sea-level pressure (column mslp_hpa) underneath as gray contour lines every 4 hPa with labels.- A title that states the takeaway.- Check: print the maximum wind speed and where it is.
With Plotly Studio
Plotly Studio is a desktop app where you point an AI at a dataset and it builds charts and a Dash app around your questions. Ask for a vector field plot the same way:
Show the surface winds as a quiver plot over a heatmap of wind speed, with a slider for the time step and a dropdown to switch between arrows and streamlines.
Make a vector field plot in Python or JavaScript
This is the code AI tools write under the hood. In Python, create_quiver takes the arrow positions and components and returns a figure with one line trace for all the arrows:
import numpy as npimport pandas as pdimport plotly.figure_factory as ffdf = pd.read_csv("winds.csv") # lat, lon, u, v (m/s), one row per grid pointdf = df[(df.lat % 1.5 == 0) & (df.lon % 1.5 == 0)] # an arrow every 1.5 degreesr = 1 / np.cos(np.radians(df.lat.mean())) # a degree of longitude is shorter than a degree of latitudefig = ff.create_quiver(df.lon, df.lat, df.u, df.v, scale=0.07, arrow_scale=0.3, scaleratio=r, line_width=1.2)fig.update_layout(xaxis_title="Longitude", yaxis_title="Latitude") # scaleratio also sets the axes' aspectfig.show()
create_quiver loops in pure Python, which is fine for the few hundred arrows a readable map needs but takes about a minute for 10,000 (see the FAQ). For more, build the same arrows with numpy and draw them as one go.Scattergl line trace:
import plotly.graph_objects as godef quiver_lines(x, y, u, v, scale=0.1, arrow_scale=0.3, angle=np.pi / 9):"""The shafts and heads create_quiver draws, as x and y arrays with NaN gaps, for any number of arrows."""x, y, u, v = (np.asarray(a, float).ravel() for a in (x, y, u, v))x1, y1 = x + u * scale, y + v * scaleang, head = np.arctan2(v, u), arrow_scale * np.hypot(u, v) * scalegap = np.full_like(x, np.nan)xs = np.column_stack([x, x1, gap, x1 - head * np.cos(ang + angle), x1, x1 - head * np.cos(ang - angle), gap])ys = np.column_stack([y, y1, gap, y1 - head * np.sin(ang + angle), y1, y1 - head * np.sin(ang - angle), gap])return xs.ravel(), ys.ravel()xs, ys = quiver_lines(df.lon, df.lat, df.u, df.v, scale=0.07)fig = go.Figure(go.Scattergl(x=xs, y=ys, mode="lines", line_width=1))
On a map, multiply u by the same r as above before calling it and set scaleanchor="x", scaleratio=r on the y axis.
create_streamline takes 1D x and y and 2D u and v. Its x and y must be evenly spaced and of the same length, and the speed can't be zero anywhere, so fill land or empty cells with a tiny value:
grid = pd.read_csv("currents.csv") # lat, lon, u, v on an evenly spaced grid with as many lons as latsx = np.sort(grid.lon.unique()); y = np.sort(grid.lat.unique())U = grid.pivot(index="lat", columns="lon", values="u").fillna(1e-6).valuesV = grid.pivot(index="lat", columns="lon", values="v").fillna(0).valuesfig = ff.create_streamline(x, y, U, V, density=1.5, arrow_scale=0.1)
In 3D, go.Cone and go.Streamtube take flat columns of positions and components. The streamtube's points must form a full regular grid (the spacing can vary along an axis, as the heights do here):
import plotly.graph_objects as gow3 = pd.read_csv("winds_3d.csv") # lon, lat, z_km, u, v: every combination of the grid's lon, lat and z_kmfig = go.Figure(go.Cone(x=w3.lon, y=w3.lat, z=w3.z_km, u=w3.u, v=w3.v, w=0 * w3.u,sizemode="absolute", sizeref=22, anchor="tail", colorscale="Blues"))fig.add_trace(go.Streamtube(x=w3.lon, y=w3.lat, z=w3.z_km, u=w3.u, v=w3.v, w=0 * w3.u,starts=dict(x=[-128] * 4, y=[36, 40, 44, 48], z=[9] * 4), sizeref=0.6))fig.update_layout(scene=dict(aspectmode="manual", aspectratio=dict(x=2, y=1.2, z=0.8)))fig.show()
In JavaScript, plotly.js draws a quiver plot as one line trace once you compute each arrow's shaft and head:
<div id="chart"></div><script src="https://cdn.plot.ly/plotly-4.1.1.min.js"></script><script>// NOAA GFS 10 m winds (m/s) over the North Atlantic, September 29, 2026, 00:00 UTCconst lat = [45, 45, 45, 45, 45, 50, 50, 50, 50, 50, 55, 55, 55, 55, 55, 60, 60, 60, 60, 60];const lon = [-40, -30, -20, -10, 0, -40, -30, -20, -10, 0, -40, -30, -20, -10, 0, -40, -30, -20, -10, 0];const u = [3.85, 12.44, 7.97, -3.96, -1.15, 7.28, 4.22, 1.89, -9.2, -8.67, 8.37, 1.37, 3.82, -7.82, -3.94, -4.68, -1.52, -15.17, -4.25, 0.45];const v = [-10.43, -11.06, -6.97, 10.78, 2.46, -9.76, -12.69, 0.38, 7.63, 2.46, -6.45, -11.9, 4.17, 11.69, 3.09, 0.56, -7.17, 7.78, 10.99, 4.68];const scale = 0.25, r = 1 / Math.cos(52.5 * Math.PI / 180); // degrees per m/s, and the map's aspectconst x = [], y = [];lat.forEach((la, i) => {const dx = u[i] * scale, dy = v[i] * scale, x1 = lon[i] + dx * r, y1 = la + dy;const ang = Math.atan2(dy, dx), head = 0.3 * Math.hypot(dx, dy);x.push(lon[i], x1, null, x1 - head * Math.cos(ang - 0.35) * r, x1, x1 - head * Math.cos(ang + 0.35) * r, null);y.push(la, y1, null, y1 - head * Math.sin(ang - 0.35), y1, y1 - head * Math.sin(ang + 0.35), null);});Plotly.newPlot("chart", [{x: x, y: y, mode: "lines", line: {color: "#2a78d6", width: 2}}], {width: 800, height: 520,xaxis: {title: {text: "Longitude"}},yaxis: {title: {text: "Latitude"}, scaleanchor: "x", scaleratio: r}});</script>
For a 3D field in JavaScript, use type: "cone" or type: "streamtube" with the same x, y, z, u, v and w arrays as in Python.
Share it
Every Plotly chart has a Share chart button (the cloud icon in its toolbar) that uploads it to Plotly Cloud and gives you a link. Only you can see it until you change its visibility.
For a live app, put the chart in a Dash app and publish it with the Plotly Cloud command-line interface (CLI). Coding agents can run these commands for you:
pip install "dash[cloud]"plotly user loginplotly app publish
If your data has to stay in your own cloud or data center, publish to Dash Enterprise instead.
Dos and don'ts
Most quiver plots go wrong in the same three places: too many arrows, the wrong aspect ratio and no scale. Here are the North Atlantic winds both ways, in the same color.
Don't draw an arrow at every grid point with a scale that makes them overlap, let the map stretch to fill the frame (without scaleanchor and scaleratio every arrow's angle is wrong), or leave out a key for the arrow length:

Do thin the arrows until they don't touch, keep the map in proportion with scaleratio = 1/cos(latitude) (and pass it to create_quiver), add a key arrow, and label the axes in degrees:

Checklist
Do
- Check the components: u is eastward and v northward, and wind directions in data files are usually where the wind comes from.
- Thin a dense grid (every nth point) and scale the longest arrow to a bit less than the spacing.
- Keep x and y in proportion with scaleanchor, and on a map use scaleratio = 1/cos(latitude).
- Add a key arrow, or color by speed with a titled colorbar.
- Put the scalar that explains the field underneath: isobars under wind, elevation under a gradient, speed under currents.
- Seed streamtubes yourself with starts, where the flow enters.
Don't
- Use length for speed when speeds span a factor of 100; use arrows of one length and color for speed (example 4).
- Draw streamlines across land or empty areas; cut them where there is no data.
- Compare cone sizes between traces with sizemode="scaled", which rescales each trace to its own largest vector.
Options to customize
A 2D vector field plot in Plotly is a line trace built by the figure factory, and a 3D one is a cone or streamtube trace, so these are the options that matter most. The full lists are in the quiver, streamline, cone trace and streamtube trace references.
- ff.create_quiver(x, y, u, v, scale=...): arrows at any points, gridded or scattered; scale turns data units into axis units.
- arrow_scale and angle: the arrowhead's length as a share of the arrow (default 0.3) and its half-angle in radians (default 0.35, 20°).
- scaleratio in create_quiver: keeps arrow angles and heads right when the y axis is drawn at a different scale, and sets yaxis.scaleanchor for you.
- ff.create_streamline(x, y, u, v, density=...): streamlines on an evenly spaced grid with x and y of equal length; density sets how many lines.
- line_color, line_width and hoverinfo: the figure factories pass these to the line trace; add an invisible marker trace at the arrow tails for hover values.
- go.Cone(sizemode=...): "scaled" (relative to the largest vector), "absolute" (sizeref used as given, for a scale shared across traces) or "raw" (cones as long as the vector, added in plotly.js 2.31).
- sizeref and anchor: cone size and whether the cone's "tail", "tip", "center" or "cm" (center of mass) sits on the point.
- go.Streamtube(starts=dict(x=..., y=..., z=...)): where the tubes begin; without it they start on the face of the grid with the smallest y.
- maxdisplayed and sizeref on streamtubes: the maximum number of segments drawn per tube (default 1000; a small value cuts tubes short) and the tube width scale.
- colorscale, cmin, cmax, colorbar: color by speed (the norm of u, v, w) on cones and streamtubes; set cmin and cmax to compare plots.
- hovertemplate with %{norm}, %{u}, %{v}, %{w} (and %{divergence} on streamtubes): hover labels with the speed and components.
- scene.aspectmode, aspectratio and camera.projection.type="orthographic": proportions of a 3D box when axes have different units, and a view without perspective that keeps near cones from hiding the axes.
More vector field plot examples
Six more vector field plots, five from real public data and one computed from a formula, each with its source, the technique that makes it work and a prompt to rebuild it.
1. The jet stream as streamtubes

Twenty tubes start off the Pacific coast at 128°W at four heights, turn north along the coast and then run east across the continent in the jet stream, whose core reaches 85 m/s. plotly.js sets each tube's width from the divergence of the flow, how fast it spreads out or squeezes together. Data: NOAA GFS analysis, November 1, 2026, 11 pressure levels every 2 degrees.
Tip: pass starts with a few points where the flow enters, and draw them as markers so readers see where each tube begins.
Get the data from the NOAA Global Forecast System (GFS) 0.25 degree analysis for 2026-11-01 00 UTC on the NOAAOpen Data Dissemination archive on AWS (noaa-gfs-bdp-pds): UGRD, VGRD and HGT at 1000, 925, 850, 700, 600, 500,400, 300, 250, 200 and 150 hPa, 20N to 60N and 130W to 60W, every 2 degrees, each level at its mean height in km(columns: lon, lat, z_km, u, v).Build a plotly go.Streamtube with w = 0, starts at 128W for latitudes 32 to 48 every 4 degrees and heights 3, 6, 9and 11.5 km, colored by speed from 0 to 80 m/s, the starting points as black markers and an orthographic camera.
2. California drifting north-west, from GPS stations

Relative to the stable interior of North America, GPS stations on the central California coast move north-west at a median 44 mm a year, and those in central Nevada at 4. The arrows grow across the boundary between the Pacific and North American plates. Data: Nevada Geodetic Laboratory, MIDAS velocities (Blewitt et al. 2016).
Tip: create_quiver doesn't need a grid, so keep one station per cell of a coarse grid to stop dense networks from turning into a blob.
Get the data from the Nevada Geodetic Laboratory MIDAS GPS velocities in the North America fixed frame(midas.NA.txt; columns 9 and 10 are east and north velocity in m/yr, 25 and 26 latitude and longitude, 5 theyears of data), stations between 32N and 42N and 125W and 114W with 8 or more years and data since 2020(columns: station, lat, lon, ve_mm, vn_mm).Build a plotly quiver plot with ff.create_quiver, one station per 0.3 degree cell, scaleratio = 1/cos(37),arrows of 25 mm/yr or more in orange and the rest in blue, a 20 mm/yr key and light gray land.
3. Where a compass points

A compass needle points along Earth's horizontal magnetic field, which near Seattle is about 15° east of true north and in Maine 16° west. Between them, the agonic line, where the two agree, runs down the middle of the continent. Data: World Magnetic Model 2025 (NOAA and the British Geological Survey), computed for November 9, 2026.
Tip: draw the arrows over contours of the angle you want people to read off (declination here), and highlight the zero line.
Get the data from NOAA NCEI's World Magnetic Model 2025 coefficients (WMM2025.COF), evaluated with pygeomag atsea level for 2026-11-09 on a 2.5 degree grid from 15N to 75N and 170W to 50W (columns: lat, lon, x_nt, y_nt,decl_deg).Build a plotly quiver plot of the horizontal field (u = y_nt, v = x_nt) every 5 degrees with ff.create_quiver andscaleratio = 1/cos(45), over go.Contour lines of decl_deg every 5 degrees with the 0 line in orange, and labelSeattle and Maine with their declination.
4. Wind at turbine hub height over the North Sea

At 100 m, the height of offshore turbine hubs, the wind averaged 11.0 m/s in the southern box and 6.1 m/s in the northern one. The power in the wind grows with the cube of its speed, so the southern box held 5.2 times as much. Data: NOAA GFS analysis, November 3, 2026, 00:00 UTC.
Tip: when magnitude matters most, put it in color and draw arrows of one length (divide u and v by the speed) for direction only.
Get the data from the NOAA Global Forecast System (GFS) 0.25 degree analysis for 2026-11-03 00 UTC on the NOAAOpen Data Dissemination archive on AWS: UGRD and VGRD at 100 m above ground, 50N to 62N and 5W to 12E(columns: lat, lon, u, v).Build a plotly go.Heatmap of wind speed with zsmooth="best" and arrows of equal length every 0.75 degrees fromff.create_quiver(u/speed, v/speed), scaleratio = 1/cos(56), and dashed boxes with the mean speed of each box.
5. RNA velocity in the developing pancreas

RNA velocity estimates which way each cell's gene expression is changing from its ratio of new (unspliced) to mature (spliced) messenger RNA. Averaged on a grid, it flows from ductal cells through neurogenin 3 (Ngn3) progenitors into the hormone-producing alpha, beta, delta and epsilon cells that diabetes research is about. Data: Bastidas-Ponce et al. (2019), 3,696 cells, velocity computed with scVelo.
Tip: average the cell velocities onto a grid for create_streamline, then cut the lines wherever no cell is nearby.
Get the data from the scVelo pancreas data set (Bastidas-Ponce et al. 2019, mouse embryonic day 15.5,endocrinogenesis_day15.h5ad from the theislab scvelo_notebooks repository), run scVelo's velocity andvelocity_embedding on the UMAP, and average the velocities on a 40 x 40 grid (columns: umap_x, umap_y,cluster, vx, vy for cells; x, y, u, v for the grid).Build a plotly scatter of the cells colored by cluster with labels at each cluster's median, and streamlines fromff.create_streamline over it, cut where no cell is within one grid step.
6. Flow past a spinning cylinder, a computed field

This is the textbook example of lift, computed from a formula rather than measured. With circulation around the cylinder, the flow over the top speeds up to 3 times the free stream and the two stagnation points move underneath, so pressure is lower on top. Data: computed from the complex potential of ideal flow with circulation (radius 1, free-stream speed 1, circulation 2π).
Tip: for computational fluid dynamics (CFD) results or any analytic field, mask the body and draw it as a filled shape on top of the streamlines.
Compute ideal (potential) flow past a cylinder of radius 1 in a uniform stream of speed 1 with circulation 2*pi ona 120 x 120 grid from -3 to 3: dw/dz = U(1 - a^2/z^2) + i*Gamma/(2*pi*z), u = Re, v = -Im (columns: x, y, u, v).Build a plotly figure: a go.Heatmap of speed, ff.create_streamline on top with a tiny placeholder inside thecylinder, the lines inside it removed, the cylinder as a filled gray circle, and the two stagnation points marked.Say in the subtitle that the field is computed.