MATLAB-like streamtubes in Matplotlib.
In this repository you will find a script (streamtubes.py
) that can be used as module for plotting streamtubes in Matplotlib.
In this section you will find a couple of examples on how to use streamtubes.py
to generate the streamtubes.
The module can be used by placing streamtubes.py
in your working directory and importing it using
import streamtubes as st
Then, it can be used, for example, running a code line that looks like this:
st.plot_streamtube(ax, x, y, z, r)
The following two images are the result of running the scripts streamtube_examples_1.py
and streamtube_examples_2.py
respectively.
The streamtubes created by the streamtubes.py
script are formed by a Matplotlib Poly3DCollection
. This collection is built using the vertices of properly aligned polygons.
The first step is to build the polygons. Each polygon, centered on a given
Note: To differentiate the regular polygons given by the points from the polygons of the Poly3DCollection
, we will call the regular polygons "sections".
Fig.1: Image of the points of the regular polygon (or section) laying on the YZ plane.
In practice, the polygonYZ
function will take four arguments and one optional argument. The first argument will be the radius of the circle containing the section's points; the other three arguments are the coordinates
We will also need to properly align the sections in order to create a smoother streamtube. For this, we will use the scipy.spatial.transform.Rotation.from_rotvec
function from the Scipy library. This function takes a rotation vector as a parameter. The rotation vector is a vector whose direction is aligned with the rotation axis and whose norm gives the angle of rotation.
Then, our problem is to find the rotation vector of each section. Therefore, we will define the vecsi
vectors, which will point in the direction in which we want to align the corresponding sections. The vecsi[i]
vector of the i-th section is defined as the vector joining the centers of the (i-1)-th section and the (i+1)-th section as shown in Fig.2. Therefore, we can normalize this resulting vector and define the vecs[i]
vector of the i-th section as also shown in Fig.2.
Fig.2: Image showing how is the vecsi[i] vector defined for the i-th section. Remember that the vecs[i] unit vector lays parallel to it.
In the case of the first an last sections, instead of taking into account the two adjacent sections as shown in Fig.2, their vecsi
vector will be defined only taking into account the considered section and the adjacent section.
With all this, the rotation vector of the i-th section (rot_axis_i[i]
) will be result of the cross product of the xaxis_vec
and vecs[i]
vectors as seen in Fig.3 (rot_axis_i[i]
= xaxis_vec
vecs[i]
). Because the two latter vectors are normalized, the rotation vector will also be normalized.
The rotation angle, rot_angle_i[i]
, can be obtained through the dot product of the xaxis_vec
and vecs[i]
normalized vectors:
rot_angle_i[i]
) = xaxis_vec
· vecs[i]
rot_angle_i[i]
= xaxis_vec
· vecs[i]
).
Fig.3: Calculation of the rotation vector of the i-th section, rot_axis_i[i], and its corresponding rotation angle, rot_angle_i[i].
Therefore, we can prepare the rotation vectors, rot_vecs
, for the Scipy's scipy.spatial.transform.Rotation.from_rotvec
function by multiplying the already obtained rotation vectors with the rotation angles: rot_vecs[i]
= rot_angle_i[i]
· rot_axis_i[i]
.
All this is the job done by the calc_angles
function, which takes as argument only the points where the sections are centered. The centers
parameter must be an array containing the coordinates centers
must be an array of shape
The make_sections
function is defined requiring five parameters: x
, y
, z
, r
and num_sides
. The latter parameter is optional, its default value is num_sides = 10
and corresponds to the number of sides of the polygons defining the sections. The x
, y
and z
parameters must be 1D arrays containing, respectively, the r
array would be the value of the plotted function at each center. The value of the function is represented as the radius of the circle containing the points of the correspondent section.
The make_sections
function is divided in two parts. In the first part, the function creates the sections from their centers specified in the centers
argument. An example of the result of the first part of the function can be seen in Fig.4.
Fig.4: Example of sections generated by the first part of the make_sections function.
The second part of the make_sections
function rotates each section properly by using the calc_angles
function to generate the rotation vectors, using the Scipy's scipy.spatial.transform.Rotation.from_rotvec
function to generate the Rotation object from the rotation vector and using the .apply()
method from said object to apply the rotation to the points of the section.
In order to carry out this last step, each section is moved to the origin of coordinates; then, it is rotated and lastly it is centered back to where it was before. This process results in, for example, something like what is shown in Fig.5.
Fig.5: Representation of the sections shown in Fig.4 after the rotation.
The last function in the streamtubes.py
script is the plot_streamtube
function. It is the main function and it is responsible for drawing the streamtube.
The function takes 5 required arguments and 7 optional arguments. The first required argument, ax
, is the Matplotlib axis where the streamtube is going to be plotted and the other four required arguments are the ones required by the make_sections
function, that is, x
, y
, z
and r
. One of the optional parameters is also shared with the make_sections
function. This optional argument is num_sides
and its default value is num_sides = 10
.
The other 6 optional arguments are the following:
Name | Default value |
---|---|
color |
"black" |
alpha |
0.2 |
linewidths |
0.5 |
cmap_name |
None |
vmin |
None |
vmax |
None |
The color
, alpha
and linewidths
arguments are for the Poly3DCollection
of Matplotlib, the cmap_name
argument is for the plt.cm.get_cmap
function and the vmin
and vmax
arguments are for the matplotlib.colors.Normalize
function.
The plot_streamtube
function draws the streamtube as a Poly3DCollection
. Each polygon from the Poly3DCollection
lays between two sections and takes four points (two from each section) as vertices. The way the plot_streamtube
function generates the Poly3DCollection
is inspired by the fill_between_3d
function in the matplotlib-fill_between-in-3D repository. A minimal example for understanding how the polygons are arranged is shown in Fig.6.
Fig.6: Example of a Poly3DCollection generated from two sections.
Note: Figures made with LibreOffice Impress.