Supppse that I wanted to take the following three [x,y,z] coordinates:
[0.799319 -3.477045e-01 0.490093]
[0.852512 9.113778e-16 -0.522708]
[0.296422 9.376042e-01 0.181748]
And plot them as vectors where the vector's start at the origin [0,0,0]. How can I go about doing this? I've been trying to use matplotlib's quiver, but I keep geting the following value error:
ValueError: need at least one array to concatenate
Here's my code (document_matrix_projections are the three coordinates above represented as a matrix):
D1, D2, D3 = zip(*document_matrix_projections)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.quiver(D1)
plt.show()
A good and pretty alternative to using matplolib's quiver() will be to plot using plotly which has the advantage of being interactive. The following function can plot vectors using the Scatter3d() in plotly. Here, the vectors have a big point to mark the direction instead of an arrowhead.
import numpy as np
import plotly.graph_objs as go
def vector_plot(tvects,is_vect=True,orig=[0,0,0]):
"""Plot vectors using plotly"""
if is_vect:
if not hasattr(orig[0],"__iter__"):
coords = [[orig,np.sum([orig,v],axis=0)] for v in tvects]
else:
coords = [[o,np.sum([o,v],axis=0)] for o,v in zip(orig,tvects)]
else:
coords = tvects
data = []
for i,c in enumerate(coords):
X1, Y1, Z1 = zip(c[0])
X2, Y2, Z2 = zip(c[1])
vector = go.Scatter3d(x = [X1[0],X2[0]],
y = [Y1[0],Y2[0]],
z = [Z1[0],Z2[0]],
marker = dict(size = [0,5],
color = ['blue'],
line=dict(width=5,
color='DarkSlateGrey')),
name = 'Vector'+str(i+1))
data.append(vector)
layout = go.Layout(
margin = dict(l = 4,
r = 4,
b = 4,
t = 4)
)
fig = go.Figure(data=data,layout=layout)
fig.show()
Plotting can be done simply by,
p0 = [0.799319, -3.477045e-01, 0.490093]
p1 = [0.852512, 9.113778e-16, -0.522708]
p2 = [0.296422, 9.376042e-01, 0.181748]
vector_plot([p0,p1,p2])
The output of the above looks:
The quiver() function needs locations of the arrows as X,Y,Z and U,V,W as the components of the arrow. So the following script can plot your data:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
p0 = [0.799319, -3.477045e-01, 0.490093]
p1 = [0.852512, 9.113778e-16, -0.522708]
p2 = [0.296422, 9.376042e-01, 0.181748]
origin = [0,0,0]
X, Y, Z = zip(origin,origin,origin)
U, V, W = zip(p0,p1,p2)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.quiver(X,Y,Z,U,V,W,arrow_length_ratio=0.01)
plt.show()
But the results are not pretty. If you would like to use Mayavi, the following works:
import numpy as np
import mayavi.mlab as m
p0 = [0.799319, -3.477045e-01, 0.490093]
p1 = [0.852512, 9.113778e-16, -0.522708]
p2 = [0.296422, 9.376042e-01, 0.181748]
origin = [0,0,0]
X, Y, Z = zip(origin,origin,origin)
U, V, W = zip(p0,p1,p2)
m.quiver3d(X,Y,Z,U,V,W)
Related
I want to create and save a number of sequential plots so I can then make an mp4 movie out of those plots. I want the color of the plot to depend on z (the value of the third axis):
The code I am using:
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LinearLocator
import numpy as np
file_dir1 = r"C:\Users\files\final_files\B_6_sec\_read.csv"
specs23 = pd.read_csv(file_dir1, sep=',')
choose_file = specs23 # Choose file betwenn specs21, specs22,...
quant = 0 # Choose between 0,1,...,according to the following list
column = ['$\rho$', '$V_{x}$', '$V_{y}$', '$V_{z}$','$B_{x}$', '$B_{y}$','$B_{z}$','$Temperature$']
choose_column = choose_file[column[quant]]
resolution = 1024 # Specify resolution of grid
t_steps = int(len(specs23)/resolution) # Specify number of timesteps
fig, ax = plt.subplots(subplot_kw={"projection": "3d"},figsize=(15,10))
# Make data.
X = np.arange(0, resolution, 1)
Y = np.arange(0, int(len(specs23)/resolution),1)
X, Y = np.meshgrid(X, Y)
Z = choose_file[column[quant]].values
new_z = np.zeros((t_steps,resolution)) # Selected quantity as a function of x,t
### Plot figure ###
for i in range(0,int(len(choose_file)/resolution)):
zs = choose_column[i*resolution:resolution*(i+1)].values
new_z[i] = zs
for i in range(len(X)):
ax.plot(X[i], Y[i], new_z[i]) #%// color binded to "z" values
ax.zaxis.set_major_locator(LinearLocator(10))
# A StrMethodFormatter is used automatically
ax.zaxis.set_major_formatter('{x:.02f}')
plt.show()
What I am getting looks like this:
I would like to look it like this:
I have created the second plot using the LineCollection module. The problem is that it prints all the lines at once not allowing me to save each separately to create a movie.
You can find the dataframe I am using to create the figure here:
https://www.dropbox.com/s/idbeuhyxqfy9xvw/_read.csv?dl=0
The poster wants two things
lines with colors depending on z-values
animation of the lines over time
In order to achieve(1) one needs to cut up each line in separate segments and assign a color to each segment; in order to obtain a colorbar, we need to create a scalarmappable object that knows about the outer limits of the colors.
For achieving 2, one needs to either (a) save each frame of the animation and combine it after storing all the frames, or (b) leverage the animation module in matplotlib. I have used the latter in the example below and achieved the following:
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt, numpy as np
from mpl_toolkits.mplot3d.art3d import Line3DCollection
fig, ax = plt.subplots(subplot_kw = dict(projection = '3d'))
# generate data
x = np.linspace(-5, 5, 500)
y = np.linspace(-5, 5, 500)
z = np.exp(-(x - 2)**2)
# uggly
segs = np.array([[(x1,y2), (x2, y2), (z1, z2)] for x1, x2, y1, y2, z1, z2 in zip(x[:-1], x[1:], y[:-1], y[1:], z[:-1], z[1:])])
segs = np.moveaxis(segs, 1, 2)
# setup segments
# get bounds
bounds_min = segs.reshape(-1, 3).min(0)
bounds_max = segs.reshape(-1, 3).max(0)
# setup colorbar stuff
# get bounds of colors
norm = plt.cm.colors.Normalize(bounds_min[2], bounds_max[2])
cmap = plt.cm.plasma
# setup scalar mappable for colorbar
sm = plt.cm.ScalarMappable(norm, plt.cm.plasma)
# get average of segment
avg = segs.mean(1)[..., -1]
# get colors
colors = cmap(norm(avg))
# generate colors
lc = Line3DCollection(segs, norm = norm, cmap = cmap, colors = colors)
ax.add_collection(lc)
def update(idx):
segs[..., -1] = np.roll(segs[..., -1], idx)
lc.set_offsets(segs)
return lc
ax.set_xlim(bounds_min[0], bounds_max[0])
ax.set_ylim(bounds_min[1], bounds_max[1])
ax.set_zlim(bounds_min[2], bounds_max[2])
fig.colorbar(sm)
from matplotlib import animation
frames = np.linspace(0, 30, 10, 0).astype(int)
ani = animation.FuncAnimation(fig, update, frames = frames)
ani.save("./test_roll.gif", savefig_kwargs = dict(transparent = False))
fig.show()
I would like to 4D plot over the cube (x,y,z) vs. q, using the colormap on the 3 surfaces of the cubes, where the color and contour are determined depending on the q variable. Basically, I am looking for a similar image like this:
Any help is appreciated.
See my example of 3D ABC feild
import pyvista as pv
import numpy as np
from numpy import mgrid
import matplotlib.pyplot as plt
print('initializing domain')
xmin = -800.
xmax = 800.
Lx = xmax-xmin
B0 = 1
k = 1
alpha = 2.0*np.pi*k/Lx
x, y, z = Lx*mgrid[0:1:51j, 0:1:51j, 0:1:51j]
print('initializing 3D B field')
Bx = B0*(np.sin(alpha*z) + np.cos(alpha*y))
By = B0*(np.sin(alpha*x) + np.cos(alpha*z))
Bz = B0*(np.sin(alpha*y) + np.cos(alpha*x))
B = np.column_stack((Bx.ravel(), By.ravel(), Bz.ravel()))
grid = pv.StructuredGrid(x, y, z)
grid["ABC field magnitude"] = np.linalg.norm(B, axis=1)
grid["ABC field vectors"] = B
grid.set_active_vectors("ABC field vectors")
#contours = grid.contour(8, scalars="ABC field magnitude")
#arrows = contours.glyph(orient="ABC field vectors", factor=50.0)
print('plotting')
pv.set_plot_theme('document')
p = pv.Plotter(notebook=0, shape=(1,1))
#p.background_color='white'
#p.window_size
cmap = plt.cm.get_cmap("viridis", 4)
p.add_mesh(grid, cmap=cmap)
p.show_grid()
#p.add_mesh(arrows)
#p.subplot(0,1)
#slices = grid.slice_orthogonal(x=20, y=20, z=30)
#slices = grid.slice_orthogonal()
#p.add_mesh(slices, cmap=cmap)
##p.subplot(1,0)
#p.add_mesh(contours, opacity=1)
#p.subplot(1,1)
#p.add_mesh(arrows)
#single_slice = arrows.slice(normal=[1, 1, 0])
#slices = arrows.slice_orthogonal(x=20, y=20, z=30)
#slices = grid.slice_orthogonal()
#p.add_mesh(single_slice, cmap=cmap)
p.show_grid()
p.link_views()
p.view_isometric()
p.show(screenshot='abc3d_slicing.png')
A simple answer is
import numpy as np
import matplotlib.pyplot as plt
length = 10
data = length*np.mgrid[0:1:51j, 0:1:51j, 0:1:51j].reshape(3,-1).T
contour = np.random.rand(data.shape[0])
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
data_plot = ax.scatter(data[:,0], data[:,1], data[:,2], c=contour)
fig.colorbar(data_plot)
To optimize to only boundary points
length = 10
vol_data = length*np.mgrid[0:1:51j, 0:1:51j, 0:1:51j].reshape(3,-1).T
bound_data = np.array([data_i for data_i in vol_data
if any([coord in [0, length] for coord in data_i])])
contour = np.random.rand(bound_data.shape[0])
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
data_plot = ax.scatter(bound_data[:,0], bound_data[:,1], bound_data[:,2], c=contour)
fig.colorbar(data_plot)
Here is an example creating a point cloud which I then want to fit a grided surface to. The problem comes at the end when I try to pass in meshgrid arrays to a function which interpolated the data:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
# Create some point cloud data:
c = 1
a = 3
b = 4
slice = {}
t = np.linspace(0,2*np.pi,50)
for s in np.linspace(1,9,10):
c = 5*s
r = (-s**2+10.0*s)/10.0
X = r*np.cos(t)
Y = r*np.sin(t)
Z = c*(Y**2/b**2 - X**2/a**2) + c
slice[str(int(s))] = np.vstack([X,Y,Z])
# Visualize it:
fig = plt.figure()
ax = fig.gca(projection = '3d')
for k,v in slice.iteritems():
print type(v)
print np.shape(v)
X = v[0,:]
Y = v[1,:]
Z = v[2,:]
ax.scatter(X,Y,Z)
plt.show()
It looks like this:
I now need to create a surface mesh based on these points. There are multiple interpretations of surface in this case because I just have a point cloud rather than a function z = f(x,y) but the correct surface in this case should be the one that creates a hollow "warped cylinder". I thought of attacking the problem like this:
# stack all points from each slice into one vector for each coordinate:
Xs = []
Ys = []
Zs = []
for k,v in slice.iteritems():
#ax.plot_surface(X,Y,Z)
X = v[0,:]
Y = v[1,:]
Z = v[2,:]
Xs = np.hstack((Xs,X))
Ys = np.hstack((Ys,Y))
Zs = np.hstack((Zs,Z))
XX, YY = np.meshgrid(Xs,Ys)
from scipy import interpolate
f = interpolate.interp2d(Xs,Ys,Zs, kind = 'cubic')
ZZ = f(XX,YY)
which I would then be able to plot using
fig = plt.figure()
ax = fig.gca(projection = '3d')
ax.plot_surface(XX, YY, ZZ)
plt.show()
However the interpolated function does not seem to accept arrays as inputs so this method might not work. Can anyone come up with a suggestion on how to do this properly?
Edit:
Actually the data is obviously not able to be represented as a function as it would not be one to one.
I stumbled upon the same question and wondered why it has not been solved in the last 7 years. Here's my solution for any future reader based on plot_trisurf (and the corresponding code examples).
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.tri as mtri
# Create some point cloud data:
a = 3
b = 4
# def grid of parametric variables
u = np.linspace(0,2*np.pi,50)
v = np.linspace(1,9,50)
U, V = np.meshgrid(u, v)
U, V = U.flatten(), V.flatten()
# Triangulate parameter space to determine the triangles
tri = mtri.Triangulation(U, V)
# get the transformed data as list
X,Y,Z = [],[],[]
for _u in u:
for _v in v:
r = (-_v**2+10.0*_v)/10.0
x = r*np.cos(_u)
y = r*np.sin(_u)
z = 5*_v*(y**2/b**2 - x**2/a**2) + 5*_v
X.append(x)
Y.append(y)
Z.append(z)
# Visualize it:
fig = plt.figure()
ax = fig.gca(projection = '3d')
ax.scatter(X,Y,Z, s=1, c='r')
ax.plot_trisurf(X, Y, Z, triangles=tri.triangles, alpha=.5)
plt.show()
This produces the following plot.
So I have a dataset that I am trying to bin into a matrix and then make a wireframe plot out of. When I show the plot, all that shows is a flat surface along the x=y line of the 3d image. I would like the full matrix to show. I have included my code as well as a sample of the stats.txt:
from numpy import *
from pylab import *
f = open('stats.txt')
bins = 10
xs = []
ys = []
for line in f:
line = line.strip().split(' ')
xs.append(float(line[0]))
ys.append(float(line[1]))
xlin = linspace(min(xs),max(xs),bins+1)
ylin = linspace(min(ys),max(ys),bins+1)
matrix = zeros((bins,bins))
for i in range(bins):
for j in range(bins):
count = 0
for s in range(len(xs)):
if xs[s] >= xlin[i] and xs[s] <= xlin[i+1] and ys[s] >= ylin[j] and ys[s] <= ylin[j+1]:
count +=1
matrix[i,j] = count
print matrix
x = []
y = []
for i in range(bins):
x.append([0.,1.,2.,3.,4.,5.,6.,7.,8.,9.])
for i in range(bins):
y.append([0.,1.,2.,3.,4.,5.,6.,7.,8.,9.])
#for i in range(bins):
# y.append(linspace(0,bins-1,bins))
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.axes3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
print shape(x)
print shape(y)
print shape(matrix)
ax.plot_wireframe(x, y, matrix)
#plt.imshow(matrix,cmap=plt.cm.ocean)
plt.show()
Sample of stats.txt:
10385.8694574 114.758131279
11379.8955938 -166.830995639
10347.5572407 165.168099188
11698.0834105 110.188708959
12100.3323331 185.316597413
11530.3943217 287.99795812
11452.2864796 474.890116234
12181.4426414 149.266756079
10962.8512477 -544.794117131
10601.2128384 49.782478266
The problem with your code is that your x-coordinates are in the same as the y-coordinates for every data point. Thus, you're effectively telling matplotlib that you only have values on the diagonal in the x-y-plane.
One possible solution would be to simply transpose your y-coordinates. However, using numpy's meshgrid (link) function is probably a lot more comfortable.
x,y = np.meshgrid(np.arange(bins),np.arange(bins))
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_wireframe(x, y, matrix)
I can draw a circle by scatter, which has been shown in the image. But I want to draw them buy a line, because there are many circles in total, I need to link nodes together for a certain circle. Thanks.
I the order of the points is random, you can change X-Y to polar, and sort the data by angle:
create some random order points first:
import pylab as pl
import numpy as np
angle = np.arange(0, np.pi*2, 0.05)
r = 50 + np.random.normal(0, 2, angle.shape)
x = r * np.cos(angle)
y = r * np.sin(angle)
idx = np.random.permutation(angle.shape[0])
x = x[idx]
y = y[idx]
Then use arctan2() to calculate the angle, and sort the data by it:
angle = np.arctan2(x, y)
order = np.argsort(angle)
x = x[order]
y = y[order]
fig, ax = pl.subplots()
ax.set_aspect(1.0)
x2 = np.r_[x, x[0]]
y2 = np.r_[y, y[0]]
ax.plot(x, y, "o")
ax.plot(x2, y2, "r", lw=2)
here is the output:
Here is one way to do it. This answer uses different methods than the linked possible duplicate, so may be worth keeping.
import matplotlib.pyplot as plt
from matplotlib import patches
fig = plt.figure(figsize=plt.figaspect(1.0))
ax = fig.add_subplot(111)
cen = (2.0,1.0); r = 3.0
circle = patches.Circle(cen, r, facecolor='none')
ax.add_patch(circle)
ax.set_xlim(-6.0, 6.0)
ax.set_ylim(-6.0, 6.0)
If all you have are the x and y points, you can use PathPatch. Here's a tutorial
If your data points are already in order, the plot command should work fine. If you're looking to generate a circle from scratch, you can use a parametric equation.
>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> t = np.linspace(0,2*np.pi, 100)
>>> x = np.cos(t)
>>> y = np.sin(t)
>>> plt.plot(x,y)