Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Bug]: Surface draws incorrectly after set_verts on Poly3DCollection #21163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
ccaprani opened this issue Sep 24, 2021 · 10 comments
Open

[Bug]: Surface draws incorrectly after set_verts on Poly3DCollection #21163

ccaprani opened this issue Sep 24, 2021 · 10 comments

Comments

@ccaprani
Copy link

Bug summary

Surface is not rendered properly after changing the data using set_verts: the simple MWE below shows the effect.

Code for reproduction

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

x = np.array([10, -10])
y = np.array([25, -25])
z = 5

X, Y = np.meshgrid(x, y)
Z = z * np.ones_like(X)

fig = plt.figure(figsize=(8, 4.5))
ax = fig.add_subplot(projection="3d")

ax.axes.set_xlim3d(left=-15, right=15)
ax.axes.set_ylim3d(bottom=-30, top=30)
ax.axes.set_zlim3d(bottom=0, top=10)

surf = ax.plot_surface(X, Y, Z, color="r", alpha=0.3)
plt.pause(1.0)

time = np.linspace(0, 5, 101)
for t in time:
    zt = z + 2 * np.sin(2 * np.pi * t)
    Z = zt * np.ones_like(X)
    verts = np.array([X, Y, Z]).T.reshape(4, 3)
    surf.set_verts(verts)
    plt.pause(0.1)

Actual outcome

image

Expected outcome

The full surface, as is shown prior to updating using set_verts should be shown:

image

Operating system

Ubuntu 21.04

Matplotlib Version

3.4.2

Matplotlib Backend

Qt5Agg

Python version

3.8.11

Jupyter version

No response

Other libraries

No response

Installation

conda

Conda channel

No response

@jklymak
Copy link
Member

jklymak commented Sep 24, 2021

Can you modify that to not have the loop/animation? Is it just a clipping problem?

@ccaprani
Copy link
Author

No, it appears as part of a more detailed animation, and setting the axes limits outside the surface should mean it is not a clipping problem.

@jklymak
Copy link
Member

jklymak commented Sep 24, 2021

I mean does it happen at t[0]? t[1]? Please remove the loop from the example if possible

@ccaprani
Copy link
Author

Sorry for not being clear: a minimum of one further call to draw the surface is necessary to demonstrate the problem. The initial call to plot_surface correctly renders the surface (and this is visible during the first plt.pause(1.0) call. Then, once it has the set_verts called again, it does not render correctly. This demonstrates the problem without a loop:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

x = np.array([10, -10])
y = np.array([25, -25])
z = 5

X, Y = np.meshgrid(x, y)
Z = z * np.ones_like(X)

fig = plt.figure(figsize=(8, 4.5))
ax = fig.add_subplot(projection="3d")

ax.axes.set_xlim3d(left=-15, right=15)
ax.axes.set_ylim3d(bottom=-30, top=30)
ax.axes.set_zlim3d(bottom=0, top=10)

surf = ax.plot_surface(X, Y, Z, color="r", alpha=0.3, edgecolor="r", lw=2)
plt.pause(1.0)

zt = 6
Z = zt * np.ones_like(X)
verts = np.array([X, Y, Z]).T.reshape(4, 3)
surf.set_verts(verts)
plt.show()

@ccaprani
Copy link
Author

@jklymak some insight perhaps: why does surf._vec return a 4x4 array? It should be 4x3 surely (4 points, x,y,z triple)?

@jklymak
Copy link
Member

jklymak commented Sep 24, 2021

If you try print(verts) you will see that you have

[[ 10  25   6]
 [ 10 -25   6]
 [-10  25   6]
 [-10 -25   6]]

which traces the corners out in a non-closed order. plot_surface goes from X, Y, Z to polygons in a somewhat obfuscated way (cbook._array_patch_perimeters), so I am not 100% sure what the "best" way to move the vertices around is supposed to be for three-D surfaces. Worse, it appears we do not have a public way to get the vertices that are in the Poly3DCollection?

So I think that this is more of a feature request than a "bug" per-se. I'm not expert enough in the 3D API to help further.

@ccaprani
Copy link
Author

I do think it is a bug: Looking at the code it calls get_vector which interprets non-zero length input (variable passed is now called segments3d using

xs, ys, zs = np.row_stack(segments3d).T

so it is expecting an (N,3) shape. And while the documentation for 3D is not great, the 2D version is categoric in expecting an (N,2) input.

The issue is also implicit in a number of questions (not or badly answered on SO), e.g. here and here.

I'm also not sure about the "non closed order" - looking in the negative z direction, these are list clockwise from the first to 4th quadrant?

@jklymak
Copy link
Member

jklymak commented Sep 24, 2021

[[ 10  25   6]
 [ 10 -25   6]
 [-10  25   6]
 [-10 -25   6]]

draws out a an N instead of a square.

@ccaprani
Copy link
Author

ccaprani commented Sep 24, 2021

My bad @jklymak - thanks for picking that up. Actually, this seems key to the whole thing. So let's say I have four points, one in each quadrant when looking along negative z direction, labelled as follows:

       y
       ^
   l   |   i
--------------> x
   k   |   j
       |

np.meshgrid generates the X and Y matrices in the order [[i, l], [j, k]] where the appropriate x or y coord is in the corresponding entry; but surf._vec is in the order [i, l, k, j] where each of i,j,k,l is a 4x1 column vector, of which the first three entries are the x,y,z coordinate triple.

So it seems that vertices are expected in an anticlockwise manner, but even this is not consistent. I've extended the MWE to show some various ways of setting the coords and their effects; the last one is most strange.

import numpy as np
import matplotlib.pyplot as plt

x = np.array([10, -10])
y = np.array([25, -25])
z = 5

X, Y = np.meshgrid(x, y)
Z = z * np.ones_like(X)

fig = plt.figure(figsize=(8, 4.5))
ax = fig.add_subplot(projection="3d")

ax.axes.set_xlim3d(left=-15, right=15)
ax.axes.set_ylim3d(bottom=-30, top=30)
ax.axes.set_zlim3d(bottom=0, top=10)

surf = ax.plot_surface(X, Y, Z, color="r", alpha=0.3, edgecolor="r", lw=2)
print(surf._vec)
plt.pause(1.0)

# This shifts z ok, but what is the order of verts?
verts = surf._vec
verts[2] = 6 * np.ones(4)
plt.draw()
plt.pause(1.0)

# Makes a bow, even though the order seems reasonable
Z = 7 * np.ones_like(X)
surf._vec = np.vstack((X.flatten(), Y.flatten(), Z.flatten(), np.ones(X.size)))
plt.draw()
plt.pause(1.0)

# Does something rather wild, even though it seems like a reasonable input
Z = 4 * np.ones_like(X)
verts = np.array([X, Y, Z]).reshape(4, 3)
surf.set_verts(verts)
plt.draw()
plt.pause(1.0)

# And even with careful manipulation of the point coords, to match the original _vec
# this doesn't plot correctly (check print output)
Z = 5 * np.ones_like(X)
i, l, j, k = np.array([X, Y, Z]).reshape(3, 4).T
surf._vec = np.vstack((np.array([i, l, k, j]).T, np.ones(4)))
print(surf._vec)
plt.draw()
plt.pause(1.0)

@ccaprani
Copy link
Author

@apaszke would #16688 solve this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants