Matplotlib plot_surface: How to convert 1D arrays to required 2D input? - python

Maybe this question is a duplicate because I can imagine that many people face this problem. Forgive me if so.
I want to plot a sphere in Matplotlib 3D. For that, I have a bunch of xyz coordinates. When I plot it with plot_trisurf, I get this:
So I wanted to try plot_surface, but then I get the error ValueError: Argument Z must be 2-dimensional.
This post explains why the input for plot_surface is 2D.
My question ist: How can I convert my regular xyz coordinates into the format plot_surface needs?
Edit:
Okay, I understood that 3-tuples can be differently interpreted. Is there a way then to use plot_trisurf with some kind of polar coordinates, so that it doesn't interpolate "through the xy plane" but from the coordinate origin, spherically?

If your points are created in a mesh-like way, it is best to create mesh at the same time, such as in this post.
It seems plot_trisurf creates a mesh for an open surface (like a rectangular table cloth) but not for a closed surface.
If the points aren't nicely organized, but you know all points lie on a convex 3D surface (e.g. a sphere), you can calculate the 3D convex hull and draw that.
The code below does just that. Note that some triangles look darker and some lighter. This is because the triangles returned by ConvexHull aren't nicely oriented (so that e.g. a clockwise orientation would indicate the outside face of the polygon). For that you'd need to calculate the surface normal for each triangle and reverse the triangle in case the dot product of that normal with the center of the triangle would be negative (supposing 0,0,0 lies inside the sphere).
If you need more 3D plotting power, the Mayawi library would be more appropriate.
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.spatial import ConvexHull
import numpy as np
xyz = np.random.randn(3, 50) # random 3D points
xyz /= np.linalg.norm(xyz, axis=0) # project each point on a unit sphere
fig = plt.figure()
ax = fig.gca(projection='3d')
hull = ConvexHull(xyz.T)
ax.plot_trisurf(*xyz, triangles=hull.simplices, linewidth=0.2, antialiased=True)
plt.show()

Related

Rotate 3d plot to look like 2d plot (no perspective)

I have created a 3d plot and want it to rotate so that the observer looks straight onto the yz-plane. I used ax.view_init(0,360) for this rotation but there is still some perspective, as you can see in the second picture. In a 2D plot the maroon and orange colored plots would meet exactly in the middle of the red plot as can be seen in the third picture. I intend to animate this rotation, and want to seemlessly continue with a 2D plot after the rotation, so ideally it would be possible to get rid of the perspective in this 3d environment, because I'm having a hard time matching the style of the 3d plot with a 2d plot.
I'd like to post a complete example but you didn't help very much (no MVE), however you can specify the projection type when you instantiate the axes:
In [6]: import matplotlib.pyplot as plt
...: from mpl_toolkits.mplot3d import Axes3D
...: %matplotlib
...:
...: ax = plt.axes(projection='3d', proj_type='ortho')
...: ax.view_init(0,360)

How to create a modified voronoi algorithm for random points with physical restriction

Voronoi algorithm has no doubt provided a amenable approach to divide a plane into regions based on distance to points in a specific subset of the plane. Such the Voronoi diagram of a set of points is dual to its Delaunay triangulation.This goal now can be directly achieved by using the module scipy as
import scipy.spatial
point_coordinate_array = np.array(point_coordinates)
delaunay_mesh = scipy.spatial.Delaunay(point_coordinate_array)
voronoi_diagram = scipy.spatial.Voronoi(point_coordinate_array)
# plot(delaunay_mesh and voronoi_diagram using matplotlib)
when the given points beforehand. The results can be shown in Fig 1,
in which the areas bounded by green dashed lines are the delaunay triangles of all points and the closed region into blue solid lines is of course the voronoi cell of the center point(for a better Visualization, only the closed region is shown here)
Up to now, all things are seen to be perfect. But for actual application, all points may have their own physical meanings. (For example, these points may have 'radii' varible when they represent the natural particles). And the common voronoi algorithm above is to be more or less inappropriate for such the case in which the complex physical restriction may be considered. As shown in Fig 2, the ridges of voronoi cell may intersect the particle's boundary. It can not meet the physical requirment any more.
My question now is how to create a modified voronoi algorithm (maybe it can not called voronoi any more) to deal with this physical restriction. This purpose is shown roughly in Fig 3, the region closed by blue dashed lines is just what I want.
All the pip requirments are:
1.numpy-1.13.3+mkl-cp36-cp36m-win_amd64.whl
2.scipy-0.19.1-cp36-cp36m-win_amd64.whl
3.matplotlib-2.1.0-cp36-cp36m-win_amd64.whl
and all of them can be directly downloaded in
http://www.lfd.uci.edu/~gohlke/pythonlibs/
My codes are updated for a better modifcation, they are as
import numpy as np
import scipy.spatial
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
from matplotlib.collections import PatchCollection
# give the point-coordinate array for contribute the tri-network.
point_coordinate_array = np.array([[0,0.5],[8**0.5,8**0.5+0.5],[0,-3.5],[-np.sqrt(15),1.5]])
# give the physical restriction (radius array) here.
point_radius_array = np.array([2.5,1.0,1.0,1.0])
# create the delaunay tri-mesh and voronoi diagram for the given points here.
point_trimesh = scipy.spatial.Delaunay(point_coordinate_array)
point_voronoi = scipy.spatial.Voronoi(point_coordinate_array)
# show the results using matplotlib.
# do the matplotlib setting here.
fig_width = 8.0; fig_length = 8.0
mpl.rc('figure', figsize=((fig_width * 0.3937), (fig_length * 0.3937)), dpi=300)
mpl.rc('axes', linewidth=0.0, edgecolor='red', labelsize=7.5, labelcolor='black', grid=0)
mpl.rc('xtick.major', size=0.0, width=0.0, pad=0)
mpl.rc('xtick.minor', size=0.0, width=0.0, pad=0)
mpl.rc('ytick.major', size=0.0, width=0.0, pad=0)
mpl.rc('ytick.minor', size=0.0, width=0.0, pad=0)
mpl.rc('figure.subplot', left=0.0, right=1.0, bottom=0.065, top=0.995)
mpl.rc('savefig', dpi=300)
ax_1 = plt.figure().add_subplot(1, 1, 1)
plt.gca().set_aspect('equal')
ax_1.set_xlim(-5.5, 8.5)
ax_1.set_ylim(-4.5, 7.5)
ax_1.set_xticklabels([])
ax_1.set_yticklabels([])
# plot all the given points and vertices here.
ax_1.scatter(point_coordinate_array[:,0],point_coordinate_array[:,1],
s=7.0,c='black')
ax_1.scatter(point_voronoi.vertices[:,0],point_voronoi.vertices[:,1],
s=7.0,c='blue')
# plot the delaunay tri-mesh here.
ax_1.triplot(point_trimesh.points[:,0],point_trimesh.points[:,1],
point_trimesh.vertices,
linestyle='--',dashes=[2.0]*4,color='green',lw=0.5)
# plot the voronoi cell here.(only the closed one)
ax_1.plot(point_voronoi.vertices[:,0],point_voronoi.vertices[:,1],
lw=1.0,color='blue')
ax_1.plot([point_voronoi.vertices[-1][0],point_voronoi.vertices[0][0]],
[point_voronoi.vertices[-1][1],point_voronoi.vertices[0][1]],
lw=1.0,color='blue')
# plot all the particles here.(point+radius)
patches1 = [Circle(point_coordinate_array[i], point_radius_array[i])
for i in range(len(point_radius_array))]
ax_1.add_collection(PatchCollection(patches1, linewidths=1.0,
edgecolor='black', facecolors='none', alpha=1.0))
# save the .png file.
plt.savefig('Fig_a.png',dpi=300)
plt.close()
It should be solved by a Power Diagram approach. You can find algorithms for 2D Power diagrams:
In a former question
In GitHub
The Power diagram is constructed from a finite set of circles in the space. The cell for a given circle consists of all the points for which the power distance to the circle is smaller than the power distance to the other circles. The power distance of a point is defined as the square of the point distance to circle center minus the square of circle radius.
Power Diagram have straight cell edges. This seems to correspond to the question. Weighted Voronoi diagrams also exist but can have as edges circle arcs.

Plotting patches of random shapes with matplotlib

This is my target to plot:
Several ellipses which are not regular shape.
(source: clouddn.com)
I was thinking about generating some random number as vertices location.
But it can only build a polygon. So, how to plot several arcs and make them close up?
To create any arbitrary shape, you will need to use the matplotlib.patches.Polygon class and just provide enough x,y samples to make it appear as smooth of a path as necessary (at the end of the day it's still straight line segments when you zoom in close enough).
If you only have a few points, you can use one of many interpolation methods (such as scipy.interpolate.spline) to create a smooth interpolant of the data that you can then feed to the Polygon constructor.
Here is a simple example creating a circle using the Polygon class by supplying x,y points around the circle.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Polygon
from matplotlib.collections import PatchCollection
# Circle coordinates (100 points around the circle)
t = np.linspace(0, 2 * np.pi, 100).reshape(100,1)
coords = np.concatenate((np.cos(t), np.sin(t)), axis=1)
ax = plt.axes()
polygons = [];
polygons.append(Polygon(coords))
p = PatchCollection(polygons, alpha=0.4)
ax.add_collection(p)
ax.axis('equal')
Sonds just like the example in the official documentation:
http://matplotlib.org/examples/pylab_examples/ellipse_demo.html
the main part, they just construct a list of the ellipses:
ells = [Ellipse(xy=rnd.rand(2)*10, width=rnd.rand(), height=rnd.rand(), angle=rnd.rand()*360) for i in range(250)]
...or did I miss your point? :)

Best fitting rectangular mesh to a smooth 3D surface

G'day, I'm struggling to find a way to create a rectangular mesh that best fits a smooth 3D surface. Particularly I have a model of an earthquake fault shown in this plot.
These are the depth contours to the fault. I want to find a rectangular mesh of defined dimension (say 10x10km) that best fits the surface. It doesn't have to (and it can't) be exactly on the surface, just the closest possible and it HAS to be a rectangle, not just a quadrangle. I have the nodes that define the surface and I can easily interpolate them.
Python solutions are welcome or suggestions on open-source code that my tackle this. I've tried commercial meshers (ABAQUS) but they always return quadrangles. I haven't been able to figure this out so any hints are appreciated.
If you have the nodes that define the surface, that means you have an irregular grid of coordinates and corresponding values. So you can generate a triangulation from this (most likely the tool you're using to show these filled contours uses the same behind the screens).
Matplotlib has two very useful classes that can convert a triangulation to a rectilinear grid (the more generic form of a rectangular grid): LinearTriInterpolator and CubicTriInterpolator. They are being used in this matplotlib example.
These are the basic steps from that same example, annotated by me, but credit goes to the matplotlib contributors:
import matplotlib.pyplot as plt
import matplotlib.tri as mtri
import numpy as np
# Create triangulation.
coords, earthquake_fault = get_coordinate_data() # to be filled in by you
x = coords['x']
y = coords['y']
triang = mtri.Triangulation(x, y)
# Interpolate to regularly-spaced quad grid.
z = earthquake_fault # the "height" data
xi, yi = np.meshgrid(np.linspace(x.min(), x.max() 20), np.linspace(y.min(), y.max(), 20))
interp_lin = mtri.LinearTriInterpolator(triang, z)
zi_lin = interp_lin(xi, yi)
# Plot the triangulation.
plt.subplot(121)
plt.tricontourf(triang, z)
plt.triplot(triang, 'ko-')
plt.title('Triangular grid')
# Plot linear interpolation to quad grid.
plt.subplot(122)
plt.contourf(xi, yi, zi_lin)
plt.title('Rectangular grid')

Best way to plot a 3D matrix in python

I am trying to visualize 3D data. This is a full 3D matrix: each (x,y,z) coordinate has a value, unlike a surface or a collection of individual data vectors. The way I am trying to do this is to plot an opaque cube, where each edge of the cube shows the sum of the data over the orthogonal dimension.
Some example data -- basically, a blob centered at (3,5,7):
import numpy as np
(x,y,z) = np.mgrid[0:10,0:10, 0:10]
data = np.exp(-((x-3)**2 + (y-5)**2 + (z-7)**2)**(0.5))
edge_yz = np.sum(data,axis=0)
edge_xz = np.sum(data,axis=1)
edge_xy = np.sum(data,axis=2)
So the idea would be here to generate a 3D plot that showed a cube; each surface of the cube would show the appropriate 2D matrix edge_*. This would be like plotting 3 4-sided polygons at the appropriate 3D positions (or 6 if you did the back sides of the cube as well) except that each polygon is actually a matrix of values to be plotted in color.
My best approximation at the moment is to compute larger matrices that contained skewed versions of edge, and concatenate these into a single, larger 2D matrix, and imshow() that larger matrix. Seems pretty clumsy, and does a lot of work that some engine in matplotlib or m3plot or something I'm sure already does. It also only works to view a static image at a single view angle, but that's not something I need to overcome at the moment.
Is there a good way to plot these cube edges in a true 3D plot using an existing python tool? Is there a better way to plot a 3D matrix?
Falko's suggestion to use contourf works with a bit of finagling. It's a bit limited since at least my version of contourf has a few bugs where it sometimes renders one of the planes in front of other planes it should be behind, but for now only plotting either the three front or three back sides of the cube will do:
import numpy as np
import math
import matplotlib.pyplot as plot
import mpl_toolkits.mplot3d.axes3d as axes3d
def cube_marginals(cube, normalize=False):
c_fcn = np.mean if normalize else np.sum
xy = c_fcn(cube, axis=0)
xz = c_fcn(cube, axis=1)
yz = c_fcn(cube, axis=2)
return(xy,xz,yz)
def plotcube(cube,x=None,y=None,z=None,normalize=False,plot_front=False):
"""Use contourf to plot cube marginals"""
(Z,Y,X) = cube.shape
(xy,xz,yz) = cube_marginals(cube,normalize=normalize)
if x == None: x = np.arange(X)
if y == None: y = np.arange(Y)
if z == None: z = np.arange(Z)
fig = plot.figure()
ax = fig.gca(projection='3d')
# draw edge marginal surfaces
offsets = (Z-1,0,X-1) if plot_front else (0, Y-1, 0)
cset = ax.contourf(x[None,:].repeat(Y,axis=0), y[:,None].repeat(X,axis=1), xy, zdir='z', offset=offsets[0], cmap=plot.cm.coolwarm, alpha=0.75)
cset = ax.contourf(x[None,:].repeat(Z,axis=0), xz, z[:,None].repeat(X,axis=1), zdir='y', offset=offsets[1], cmap=plot.cm.coolwarm, alpha=0.75)
cset = ax.contourf(yz, y[None,:].repeat(Z,axis=0), z[:,None].repeat(Y,axis=1), zdir='x', offset=offsets[2], cmap=plot.cm.coolwarm, alpha=0.75)
# draw wire cube to aid visualization
ax.plot([0,X-1,X-1,0,0],[0,0,Y-1,Y-1,0],[0,0,0,0,0],'k-')
ax.plot([0,X-1,X-1,0,0],[0,0,Y-1,Y-1,0],[Z-1,Z-1,Z-1,Z-1,Z-1],'k-')
ax.plot([0,0],[0,0],[0,Z-1],'k-')
ax.plot([X-1,X-1],[0,0],[0,Z-1],'k-')
ax.plot([X-1,X-1],[Y-1,Y-1],[0,Z-1],'k-')
ax.plot([0,0],[Y-1,Y-1],[0,Z-1],'k-')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plot.show()
plot_front=True
plot_front=False
Other data (not shown)
Take a look at MayaVI. The contour3d() function may be what you want.
Here's an answer I gave to a similar question with an example of the code and resulting plot https://stackoverflow.com/a/24784471/3419537

Categories