简体   繁体   中英

Wireframe joins the wrong way in numpy matplotlib mplot3d

I'm trying to create a 3D wireframe in Python using matplotlib.

When I get to the actual graph plotting, however, the wireframe joins the wrong way, as shown in the images below.

How can I force matplotlib to join the wireframe along a certain axis?

My code is below:

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d

def rossler(x_n, y_n, z_n, h, a, b, c):
#defining the rossler function
x_n1=x_n+h*(-y_n-z_n)
y_n1=y_n+h*(x_n+a*y_n)
z_n1=z_n+h*(b+z_n*(x_n-c))   
return x_n1,y_n1,z_n1

#defining a, b, and c
a = 1.0/5.0
b = 1.0/5.0
c = 5

#defining time limits and steps
t_0 = 0
t_f = 32*np.pi
h = 0.01
steps = int((t_f-t_0)/h)

#3dify
c_list = np.linspace(5,10,6)
c_size = len(c_list)
c_array = np.zeros((c_size,steps))

for i in range (0, c_size):
    for j in range (0, steps):
        c_array[i][j] = c_list[i]

#create plotting values
t = np.zeros((c_size,steps))
for i in range (0, c_size):
    t[i] = np.linspace(t_0,t_f,steps)
x = np.zeros((c_size,steps))
y = np.zeros((c_size,steps))
z = np.zeros((c_size,steps))
binvar, array_size = x.shape

#initial conditions
x[0] = 0
y[0] = 0
z[0] = 0

for j in range(0, c_size-1):
    for i in range(array_size-1):
        c = c_list[j]
        #re-evaluate the values of the x-arrays depending on the initial conditions
        [x[j][i+1],y[j][i+1],z[j][i+1]]=rossler(x[j][i],y[j][i],z[j][i],t[j][i+1]-t[j][i],a,b,c)

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_wireframe(t,x,c_array, rstride=10, cstride=10)
plt.show()

I am getting this as an output:

在此处输入图片说明

The same output from another angle:

在此处输入图片说明

Whereas I'd like the wireframe to join along the wave-peaks. Sorry, I can't give you an image I'd like to see, that's my problem, but I guess it'd be more like the tutorial image.

I'm quite unsure about what you're exactly trying to achieve, but I don't think it will work.

Here's what your data looks like when plotted layer by layer (without and with filling):

无填充 填充

You're trying to plot this as a wireframe plot. Here's how a wireframe plot looks like as per the manual :

示例线框

Note the huge differene: a wireframe plot is essentially a proper surface plot, the only difference is that the faces of the surface are fully transparent. This also implies that you can only plot

  1. single-valued functions of the form z(x,y), which are furthermore
  2. specified on a rectangular mesh (at least topologically)

Your data is neither: your points are given along lines , and they are stacked on top of each other, so there's no chance that this is a single surface that can be plotted.

If you just want to visualize your functions above each other, here's how I plotted the above figures:

from mpl_toolkits.mplot3d.art3d import Poly3DCollection

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for zind in range(t.shape[0]):
    tnow,xnow,cnow = t[zind,:],x[zind,:],c_array[zind,:]
    hplot = ax.plot(tnow,xnow,cnow)

    # alternatively fill:
    stride = 10
    tnow,xnow,cnow = tnow[::stride],xnow[::stride],cnow[::stride]
    slice_from = slice(None,-1)
    slice_to = slice(1,None)
    xpoly = np.array([tnow[slice_from],
                      tnow[slice_to],
                      tnow[slice_to],
                      tnow[slice_from]]
                      ).T
    ypoly = np.array([xnow[slice_from],
                      xnow[slice_to],
                      np.zeros_like(xnow[slice_to]),
                      np.zeros_like(xnow[slice_from])]
                      ).T
    zpoly = np.array([cnow[slice_from],
                      cnow[slice_to],
                      cnow[slice_to],
                      cnow[slice_from]]
                      ).T

    tmppoly = [tuple(zip(xrow,yrow,zrow)) for xrow,yrow,zrow in zip(xpoly,ypoly,zpoly)]
    poly3dcoll = Poly3DCollection(tmppoly,linewidth=0.0)
    poly3dcoll.set_edgecolor(hplot[0].get_color())
    poly3dcoll.set_facecolor(hplot[0].get_color())
    ax.add_collection3d(poly3dcoll)

plt.xlabel('t')
plt.ylabel('x')
plt.show()

There is one other option: switching your coordinate axes, such that the (x,t) pair corresponds to a vertical plane rather than a horizontal one. In this case your functions for various c values are drawn on parallel planes. This allows a wireframe plot to be used properly, but since your functions have extrema in different time steps, the result is as confusing as your original plot. You can try using very few plots along the t axis, and hoping that the extrema are close. This approach needs so much guesswork that I didn't try to do this myself. You can plot each function as a filled surface instead, though:

from matplotlib.collections import PolyCollection

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for zind in range(t.shape[0]):
    tnow,xnow,cnow = t[zind,:],x[zind,:],c_array[zind,:]
    hplot = ax.plot(tnow,cnow,xnow)
    # alternative to fill:
    stride = 10
    tnow,xnow,cnow = tnow[::stride],xnow[::stride],cnow[::stride]
    slice_from = slice(None,-1)
    slice_to = slice(1,None)
    xpoly = np.array([tnow[slice_from],
                      tnow[slice_to],
                      tnow[slice_to],
                      tnow[slice_from]]
                      ).T
    ypoly = np.array([xnow[slice_from],
                      xnow[slice_to],
                      np.zeros_like(xnow[slice_to]),
                      np.zeros_like(xnow[slice_from])]
                      ).T
    tmppoly = [tuple(zip(xrow,yrow)) for xrow,yrow in zip(xpoly,ypoly)]
    polycoll = PolyCollection(tmppoly,linewidth=0.5)
    polycoll.set_edgecolor(hplot[0].get_color())
    polycoll.set_facecolor(hplot[0].get_color())
    ax.add_collection3d(polycoll,zdir='y',zs=cnow[0])
    hplot[0].set_color('none')

ax.set_xlabel('t')
ax.set_zlabel('x')
plt.show()

This results in something like this: 旋转填充图

There are a few things to note, however.

  1. 3d scatter and wire plots are very hard to comprehend, due to the lacking depth information. You might be approaching your visualization problem in a fundamentally wrong way: maybe there are other options with which you can visualize your data.
  2. Even if you do something like the plots I showed, you should be aware that matplotlib has historically been failing to plot complicated 3d objects properly. Now by "properly" I mean "with physically reasonable apparent depth", see also the mplot3d FAQ note describing exactly this. The core of the problem is that matplotlib projects every 3d object to 2d, and draws these pancakes on the sreen one after the other. Sometimes the asserted drawing order of the pancakes doesn't correspond to their actual relative depth, which leads to artifacts that are both very obvious to humans and uncanny to look at. If you take a closer look at the first filled plot in this post, you'll see that the gold flat plot is behind the magenta one, even though it should be on top of it. Similar things often happen with 3d bar plots and convoluted surfaces .
  3. When you're saying " Sorry, I can't give you an image I'd like to see, that's my problem ", you're very wrong. It's not just your problem. It might be crystal clear in your head what you're trying to achieve, but unless you very clearly describe what you see in your head, the outside world will have to resort to guesswork. You can make the work of others and yourself alike easier by trying to be as informative as possible.

If I understood, you want to link the 6 traces with polygons. You can do that by triangulating the traces 2 by 2, then plotting the surface with no edges or antialising. Maybe choosing a good colormap will also help.

Just keep in mind that this will be a very heavy plot. The exported SVG weight 10mb :)

import matplotlib.tri as mtri

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

for LineIndex in range(c_size-1):
    # If plotting all at once, you get a MemoryError. I'll plot each 6 points
    for Sample in range(0, array_size-1, 3):
        # I switched x and c_array, because the surface  and the triangles 
        # will look better by default
        X = np.concatenate([t[LineIndex,Sample:Sample+3], t[LineIndex+1,Sample:Sample+3]])
        Y = np.concatenate([c_array[LineIndex,Sample:Sample+3], c_array[LineIndex+1,Sample:Sample+3]])
        Z = np.concatenate([x[LineIndex,Sample:Sample+3], x[LineIndex+1,Sample:Sample+3]])
        T = mtri.Triangulation(X, Y)

        ax.plot_trisurf(X, Y, Z, triangles=T.triangles, edgecolor='none', antialiased=False)

ax.set_xlabel('t')
ax.set_zlabel('x')
plt.savefig('Test.png', format='png', dpi=600)
plt.show()

Here is the resulting image: 在此处输入图片说明

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM