Wireframe joins the wrong way in numpy matplotlib mplot3d - python

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.

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:

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
single-valued functions of the form z(x,y), which are furthermore
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.
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.
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.
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.

Related

Color Coding Scatterplot Based on Defined Names

I have some excel data that I wanted to graph viz. a circular scatterplot. The data looks like this:
I began writing the script in Python as follows:
# Initializing
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
# Importing dataframe and defining main variables
tdtData = pd.read_excel(r'C:\Users\Bri-Guy\Desktop\tdtData.xlsx', sheet_name='Sheet1')
tdtData.head()
probe=tdtData.Probe
area=tdtData.Area
light=tdtData.Lightavg
np.random.seed(19680801)
# Compute areas and colors
N = 234
r = area
theta = 2 * np.pi * np.random.rand(N)
size = light
colors = theta
fig = plt.figure()
ax = fig.add_subplot(projection='polar')
c = ax.scatter(theta, r, c=colors, s=size, cmap='hsv', alpha=0.75)
ax.set_thetamin(0)
ax.set_thetamax(310)
Right now I have it such that the colors change based on the angle parameter theta. However, I want to color the scatterplot based on the specific probes associated with each datapoint (S100b+, S100b-, scn10a, fxyd2, fxyd2/scn10a). It would also be nice to implement a legend that goes with these names.
Additionally, the plot generates with labels for the r-axis. I want to see those values because they help to show the range of the area variable. However, they're only visible if I restrict the range of theta. I'm wondering if there's a better way to go about it? Potentially increase the size of the figure, rotate the r-axis labels, or something else entirely?
Thank you for the help.
*Edit: I just realized that setting a defined range on theta actually obscures part of the data plotted for theta in(310,360). However, increasing figure size seems to be enough.
fig.set_size_inches(18.5, 10.5)

Swap axis for a linspace plot

I have a function with an histogram, plotted like this :
import matplotlib.pyplot as plt
import numpy as np
lin = np.linspace(min(foo), max(foo), len(foo))
plt.plot(lin, bar)
plt.hist(bar, density=True, bins=100, histtype='stepfilled', alpha=0.2)
plt.show()
Where foo and bar are simple arrays.
However, I would want to have the whole thing in a vertical way... I could add orientation='horizontal' to the histogram, but it would not change the function (and from what I have seen, there is nothing similar for a plot -> obviously it wouldn't be a function then, but a curve). Otherwise, I could add plt.gca().invert_yaxis() somewhere, but the same problem resides : plot is used for functions, so the swap of it does... well, that :
So, the only way I have now is to manually turn the whole original picture by 90 degrees, but then the axis are turned too and will no longer be on the left and bottom (obviously).
So, have you another idea ? Maybe I should try something else than plt.plot ?
EDIT : In the end, I would want something like the image below, but with axes made right.
If you have a plot of y vs x, you can swap axes by swapping arrays:
plt.plot(bar, lin)
There's no special feature because it's supported out of the box. As you've discovered, plotting a transposed histogram can be accomplished by passing in
orientation='horizontal'
I couldn't find any matplotlib method dealing with the issue. You can rotate the curve in a purely mathematical way, i.e. do it through the rotation matrix. In this simple case it is sufficient to just exchange variables x and y but in general it looks like this (let's take a parabola for a clear example):
rotation = lambda angle: np.array([[ np.cos(angle), -np.sin(angle)],
[np.sin(angle), np.cos(angle)]])
x = np.linspace(-10,10,1000)
y = -x**2
matrix = np.vstack([x,y]).T
rotated_matrix = matrix # rotation(np.deg2rad(90))
fig, ax = plt.subplots(1,2)
ax[0].plot(rotated_matrix[:,0], rotated_matrix[:,1])
ax[1].plot(x,y)
rotated_matrix = matrix # rotation(np.deg2rad(-45))
fig, ax = plt.subplots(1,2)
ax[0].plot(rotated_matrix[:,0], rotated_matrix[:,1])
ax[1].plot(x,y)

Matplotlib plot has slanted lines

I'm trying to plot projections of coordinates onto a line, but for some reason, Matplotlib is plotting the projections in a slightly slanted manner. Ideally, I would like the (blue) projections to be perpendicular to the (green) line. Here's an image of how it looks with sample data:
As you can see, the angles between the blue lines and the green line are slightly obtuse instead of right. I tried playing around with the rotation parameter to the annotate function, but this did not help. The code for this plot is below, although the data might look a bit different since the random generator is not seeded:
import numpy as np
import matplotlib.pyplot as plt
prefs = {'color':'purple','edgecolors':'black'}
X = np.dot(np.random.rand(2,2), np.random.rand(2,50)).T
pts = np.linspace(-1,1)
v1_m = 0.8076549717643662
plt.scatter(X[:,0],X[:,1],**prefs)
plt.plot(pts, [v1_m*x for x in pts], color='lightgreen')
for x,y in X:
# slope of connecting line
# y = mx+b
m = -np.reciprocal(v1_m)
b = y-m*x
# find intersecting point
zx = b/(v1_m-m)
zy = v1_m*zx
# draw line
plt.annotate('',(zx,zy),(x,y),arrowprops=dict(linewidth=2,arrowstyle='-',color='lightblue'))
plt.show()
The problem lies in the unequal axes which makes it look like they are not at a right angle. Use plt.axis('equal') to have equal axis spans on x- and y-axis and a square figure with equal height and width. plt.axis('scaled') works the same way. As pointed out by #CedricZoppolo, you should set the equal aspect ratios before plt.show(). As per docs, setting the aspect ratio to "equal" means
same scaling from data to plot units for x and y
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(8,8))
# Your code here
plt.axis('equal')
plt.show()
Choosing a square figure is not necessary as it works also with rectangular figures as
fig = plt.figure(figsize=(8,6))
# Your code here
plt.axis('equal')
plt.show()
The blue lines not being perpendicular is due to axis not being equal.
You just need to add below line before plt.show()
plt.gca().set_aspect('equal')
Below you can see the resulted graph:

imagesc like feature with non-rectangular grids [MATLAB]

If i want to color a square grid with different color in each grid cells, then it is possible in MATLAB with a simple call to imagesc command like here.
What if i want to color different cells in a grid like this:
Is this functionality available by default in either python or Matlab? I tried discretizing this grid with very small square cells. And then color each cell. That works. But it seems ordinary. Is there a smarter way to get his done?
In python, there is the builtin polar projection for the axes. This projection allows you to automatically use almost every plotting method in polar coordinates. In particular, you need to you pcolor or pcolormesh as follows
import numpy as np
from matplotlib import pyplot as plt
r = np.linspace(0,4,5)
theta = np.linspace(0,2*np.pi,10)
theta,r = np.meshgrid(theta,r)
values = np.random.rand(*(theta.shape))
ax = plt.subplot(111,polar=True)
ax.pcolor(theta,r,values)
plt.show()
Note that this will produce a plot like this
which is almost what you want. The obvious problem is that the patch vertices are joined by straight lines and not lines that follow the circle arc. You can solve this by making the angles array denser. Here is a posible way to do it.
import numpy as np
from matplotlib import pyplot as plt
r = np.linspace(0,4,5)
theta = np.linspace(0,2*np.pi,10)
values = np.random.rand(r.size,theta.size)
dense_theta = np.linspace(0,2*np.pi,100)
v_indeces = np.zeros_like(dense_theta,dtype=np.int)
i = -1
for j,dt in enumerate(dense_theta):
if dt>=theta[i+1]:
i+=1
v_indeces[j] = i
T,R = np.meshgrid(dense_theta,r)
dense_values = np.zeros_like(T)
for i,v in enumerate(values):
for j,ind in enumerate(v_indeces):
dense_values[i,j] = v[ind]
ax = plt.subplot(111,polar=True)
ax.pcolor(T,R,dense_values)
plt.show()
Which would produce
I am not aware of a way to do this in matlab but I googled around and found this that says it can produce pcolor plots in polar coordinates. You should check it out.

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