I have a numpy array vertices of shape (N,3) containing the N vertices of a spherical polygon in 3D, i.e. all these points lie on the surface of a sphere. The center and radius of the sphere is known (take the unit sphere for example). I would like to plot the spherical polygon bounded by these vertices. (Mathematically speaking, I want to plot the spherically convex hull generated by these vertices).
How can I do that using matplotlib? I tried Poly3DCollection, but this only plots the Euclidean polygon. I managed to plot the entire unit sphere using plot_surface like this:
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
x = np.outer(np.cos(u), np.sin(v))
y = np.outer(np.sin(u), np.sin(v))
z = np.outer(np.ones(np.size(u)), np.cos(v))
ax.plot_surface(x, y, z, rstride=5, cstride=5, color='y', alpha=0.1)
I guess one could manually calculate what points to remove from x, y, z and then still use plot_surface in order to plot the polygon. Would this be the correct way to use matplotlib or does it have another module, which I could use directly?
In case there is no convenient way to do this in matplotlib, can you recommend any other library, which does that?
Related
I am unable to understand from the matplotlib documentation(https://matplotlib.org/mpl_toolkits/mplot3d/tutorial.html), the working of a trisurf plot. Can someone please explain how the X,Y and Z arguments result in a 3-D plot?
Let me talk you through this example taken from the docs
'''
======================
Triangular 3D surfaces
======================
Plot a 3D surface with a triangular mesh.
'''
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
n_radii = 8
n_angles = 36
# Make radii and angles spaces (radius r=0 omitted to eliminate duplication).
radii = np.linspace(0.125, 1.0, n_radii)
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
# Repeat all angles for each radius.
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
# Convert polar (radii, angles) coords to cartesian (x, y) coords.
# (0, 0) is manually added at this stage, so there will be no duplicate
# points in the (x, y) plane.
x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())
# Compute z to make the pringle surface.
z = np.sin(-x*y)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_trisurf(x, y, z, linewidth=0.2, antialiased=True)
plt.show()
The x, y values are a range of values over which we calculate the surface. For each (x, y) pair of coordinates, we have a single value of z, which represents the height of the surface at that point.
I plotted a 3D plot and used quiver to plot x, y and z axis.
In matplotlib's interactive plot, I can drag and rotate the 3D plot, but there is one issue:
it seems that the Z-axis is restricted to a plane when I drag the plot. No matter how I drag the plot, Z-axis can only
rotate in a limited manner(in a plane), while X-axis and Y-axis can be rotated freely.
My question is: is this a limitation of matplotlib or is there any method that I can configure how x, y
and z-axis can be rotated?
Any suggestions are appreciated.
A mininum reproducible example is attached for reference:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
n_radii = 8
n_angles = 36
# Make radii and angles spaces (radius r=0 omitted to eliminate duplication).
radii = np.linspace(0.125, 1.0, n_radii)
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
# Repeat all angles for each radius.
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
# Convert polar (radii, angles) coords to cartesian (x, y) coords.
# (0, 0) is manually added at this stage, so there will be no duplicate
# points in the (x, y) plane.
x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())
# Compute z to make the pringle surface.
z = np.sin(-x*y)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_trisurf(x, y, z, linewidth=0.2, antialiased=True)
steps = 100
theta = np.linspace(0, 2 * np.pi, steps)
r_max = 1.2
x = np.zeros_like(theta)
y = r_max * np.cos(theta)
z = r_max * np.sin(theta)
ax.plot(x, y, z, 'r')
ax.plot(y, x, z, 'g')
ax.plot(z, y, x, 'b')
scale = 1.08
ax.quiver((0,), (0), (0),
(0), (0), (r_max), color=('c'))
ax.text(0, 0, r_max * scale, 'Z Theta', weight='bold')
ax.quiver((0), (0), (0),
(0), (r_max), (0), color=('m'))
ax.text(0, r_max * scale, 0, 'Y', weight='bold')
ax.quiver((0), (0), (0),
(r_max), (0), (0), color=('y'))
ax.text(r_max * scale, 0, 0, 'X', weight='bold')
plt.show()
My first recommendation is this.
But if that is not possible at all, I found a solution that could work.
The method _on_move in Axes3Dresponsible for processing the mouse events and rotating the plot.
As you can see this function only thinks in azimuth and elevation. That`s why it behaves the way it does.
It is possible to re-bind the default _on_move as seen in the method mouse_init() which is called in the constructor of Axes3D.
Say our custom mouse interaction style is defined in
def _my_on_move(self, event):
print('my custom mouse style', event)
This does not work:
ax._on_move = _my_on_move
because _my_on_move is a function but we need it to be a bound method so the self is available.
The solution is to bind the function as method, this is described in detail here:
import types
ax._on_move = types.MethodType(_my_on_move, ax)
and re-run the mouse initialization:
ax.mouse_init()
This part in the original _on_move will set elev and azim which are then used by get_proj() to set the
transformation matrix used in figure.canvas.draw_idle():
self.elev = art3d._norm_angle(self.elev - (dy/h)*180)
self.azim = art3d._norm_angle(self.azim - (dx/w)*180)
self.get_proj()
self.figure.canvas.draw_idle()
Somehow we have to sneak in a modified transformation matrix. I am not sure if there is a better way,
but we could just pass in modified values for elev and azim.
Since we want something smarter we should switch to quaternions.
I recommend using transformations.py
but there is also a module called mathutils from Blender with works fine.
Now to the fun part:
You have to get the current view (the current transformation matrix) and
rotate it based on the mouse movement. Then extract the equivalent elev and azim
from the rotated matrix. Fun task, some math, but it should be possible.
But I will leave that for to someone else :)
Maybe there is some inspitation found in VTK`s interactors or the ones from Blender.
If you want to try the interactors from Mayavi / VTK:
pip install mayavi (or pip3 install mayavi depending on your version and your virtual environment).
Then run
from mayavi import mlab
from tvtk.api import tvtk
for i in [tvtk.InteractorStyleTerrain(),
tvtk.InteractorStyleJoystickActor(),
tvtk.InteractorStyleTrackballActor(),
tvtk.InteractorStyleTrackball()]:
mlab.test_surf()
fig = mlab.gcf()
fig.scene.interactor.interactor_style = i
mlab.show()
I want to interpolate a circle by using some given points. I refer to the scipy.interpolate, and use the interpolate.splprep to interpolate my circle. However, the interpolated circle is weird, and it is different from the standard circle:
And my code is:
import numpy as np
import matplotlib.pyplot as plt
from scipy import interpolate
t = np.arange(0, 1.25, 0.25)
x = np.sin(2*np.pi*t)
y = np.cos(2*np.pi*t)
tck,u = interpolate.splprep([x,y], s=0)
unew = np.arange(0, 1.01, 0.01)
out = interpolate.splev(unew, tck)
plt.figure()
plt.plot(x, y, 'x', out[0], out[1])
plt.axis([-1.05, 1.05, -1.05, 1.05])
plt.title('Spline of parametrically-defined curve')
plt.show()
Of course, if I give more points, such as: t = np.arange(0, 1.25, 0.1), the circle would look better. But, I still can not accept this result. Is there any better interpolation method to interpolate the circle?
Fisrt Edit:
#gregory mention that scipy.interpolate.CubicSpline can be used to interpolate a circle. And the example code is:
theta = 2 * np.pi * np.linspace(0, 1, 5)
y = np.c_[np.cos(theta), np.sin(theta)]
cs = CubicSpline(theta, y, bc_type='periodic')
However, it use the theta and [cos, sin] to represent the circle, but what if we do not know the formulation about the curve? What if we only have (x, y)? Can we parametrically represent the curve, like interpolate.splprep?
The cubic spline will fit a third degree polynomial to your data, supposedly a circle, and your points are far appart pi/2 radians, so you should consider using more closely spaced data or otherwise, interpolating with polar coordinates. This way your data will be constant radius, turning interpolation unneeded. If you add some noise or pertubation to your data, option bc_type='periodic' just works fine with polar coordinates, as #gregory stated.
Matplotlib (plt) Polar Coordinates:
plt.axes(projection = 'polar')
Coordinates will be angles in radians and a function of (or measured) radius at the corresponding angles.
Is there anyway to get the polygon data (i.e triangle edges) from the plot_trisurf function? Since it takes x,y and z data and creates a Delaunay triangulation it must have this data somewhere...?
Something that can be used with the below example would be great.
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
n_angles = 36
n_radii = 8
radii = np.linspace(0.125, 1.0, n_radii)
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())
z = np.sin(-x*y)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0.2)
plt.show()
I'd like to use matplotlib for this, as it uses a Delaunay algorithm that is more suited to 3D data. From what I can workout, doesn't parametrise it into 2D, and is easily available on a much wider range of hardware without anything close to the same amount of faff, unlike mayavi. Plotly could also be an option, although I believe this requires the data to be parametrised into 2D, which is not ideal for my application.
I'm aware there are Delaunay libraries in python scipy that will do this, but again the parametrisation of the data is the problem.
You can look at the source code of plot_trisurf to find out how matplotlib does the triangulation.
It basically performs a triangulation on a 2D grid and takes over the z component from the input.
The 2D triangulation is done by the matplotlib.tri.triangulation.Triangulation class.
The following could do what you want, giving you an array of the vertices.
from matplotlib.tri.triangulation import Triangulation
import numpy as np
n_angles = 36
n_radii = 8
radii = np.linspace(0.125, 1.0, n_radii)
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())
z = np.sin(-x*y)
tri, args, kwargs = Triangulation.get_from_args_and_kwargs(x, y, z)
triangles = tri.get_masked_triangles()
xt = tri.x[triangles][..., np.newaxis]
yt = tri.y[triangles][..., np.newaxis]
zt = z[triangles][..., np.newaxis]
verts = np.concatenate((xt, yt, zt), axis=2)
print verts
Poly3DCollection is for this purpose, plotting 3D polygons without interpolation. It accepts a list of the Polygon vertices' coordinates, and you simply call ax.add_collection3d(collection) to add them to the axes.
I have a set of data that I want to use to produce a contour plot in polar co-ordinates using Matplotlib.
My data is the following:
theta - 1D array of angle values
radius - 1D array of radius values
value - 1D array of values that I want to use for the contours
These are all 1D arrays that align properly - eg:
theta radius value
30 1 2.9
30 2 5.3
35 5 9.2
That is, all of the values are repeated enough times so that each row of this 'table' of three variables defines one point.
How can I create a polar contour plot from these values? I've thought about converting the radius and theta values to x and y values and doing it in cartesian co-ordinates, but the contour function seems to require 2D arrays, and I can't quite understand why.
Any ideas?
Matplotlib's contour() function expects data to be arranged as a 2D grid of points and corresponding grid of values for each of those grid points. If your data is naturally arranged in a grid you can convert r, theta to x, y and use contour(r*np.cos(theta), r*np.sin(theta), values) to make your plot.
If your data isn't naturally gridded, you should follow Stephen's advice and used griddata() to interpolate your data on to a grid.
The following script shows examples of both.
import pylab as plt
from matplotlib.mlab import griddata
import numpy as np
# data on a grid
r = np.linspace(0, 1, 100)
t = np.linspace(0, 2*np.pi, 100)
r, t = np.meshgrid(r, t)
z = (t-np.pi)**2 + 10*(r-0.5)**2
plt.subplot(121)
plt.contour(r*np.cos(t), r*np.sin(t), z)
# ungrid data, then re-grid it
r = r.flatten()
t = t.flatten()
x = r*np.cos(t)
y = r*np.sin(t)
z = z.flatten()
xgrid = np.linspace(x.min(), x.max(), 100)
ygrid = np.linspace(y.min(), y.max(), 100)
xgrid, ygrid = np.meshgrid(xgrid, ygrid)
zgrid = griddata(x,y,z, xgrid, ygrid)
plt.subplot(122)
plt.contour(xgrid, ygrid, zgrid)
plt.show()
I don't know if it's possible to do a polar contour plot directly, but if you convert to cartesian coordinates you can use the griddata function to convert your 1D arrays to 2D.