简体   繁体   English

线框在 numpy matplotlib mplot3d 中以错误的方式加入

[英]Wireframe joins the wrong way in numpy matplotlib mplot3d

I'm trying to create a 3D wireframe in Python using matplotlib.我正在尝试使用 matplotlib 在 Python 中创建一个 3D 线框。

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?如何强制 matplotlib 沿某个轴加入线框?

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 z(x,y) 形式的单值函数,此外
  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.还有另一种选择:切换坐标轴,使 (x,t) 对对应于垂直平面而不是水平平面。 In this case your functions for various c values are drawn on parallel planes.在这种情况下,不同c值的函数绘制在平行平面上。 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.可以尝试沿t轴使用很少的图,并希望极值接近。 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.由于缺乏深度信息,3d 散点图和线图很难理解。 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.即使你做了类似我展示的图的事情,你也应该意识到 matplotlib 在历史上一直未能正确绘制复杂的 3d 对象。 Now by "properly" I mean "with physically reasonable apparent depth", see also the mplot3d FAQ note describing exactly this.现在,“正确”是指“具有物理上合理的表观深度”,另请参阅mplot3d 常见问题解答说明,准确描述了这一点。 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.问题的核心是matplotlib将每个3d对象投影到2d,然后将这些煎饼一个接一个地绘制在屏幕上。 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 .类似的事情经常发生在3d 条形图复杂曲面上
  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.如果我理解,您想将 6 条轨迹与多边形联系起来。 You can do that by triangulating the traces 2 by 2, then plotting the surface with no edges or antialising.您可以通过对轨迹进行 2 x 2 三角测量,然后绘制没有边缘或抗锯齿的表面来实现。 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 :)导出的 SVG 重量为 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:这是生成的图像: 在此处输入图片说明

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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