Showing end point of the 3d line: Python 3D plot - python

I made a 3D plot using the following code in python. Here three arrays x, y and z are used for the plot. I want to show the last point of the arrays (or the end point of the 3D line) in the plot. I used the approach I would use in 2d plotting, i.e., I asked for plotting only the last points of each array using this command ax.plot(x[-1],y[-1],z[-1],'o'). But it doesn't work.
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import
x=np.linspace(0,2*np.pi)
y=np.sin(x)
z=np.cos(x)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot(x, y, z, lw=1)
ax.plot(x[-1],y[-1],z[-1],'o') # This line doesn't work
plt.show()

import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import
x=np.linspace(0,2*np.pi)
y=np.sin(x)
z=np.cos(x)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot(x, y, z, lw=1)
ax.scatter(x[-1],y[-1],z[-1],'-') # This should do the job
plt.show()
Add Color and Label
ax.scatter(x[-1],y[-1],z[-1],'-',c="yellow",label="End Point")
plt.legend()
plt.show()
Additional explanation on why you were having an error:
You were telling python to draw you a ax.plot for 1 point. Which is impossible, because you cant draw a line using 1 point only. Therefore, you tell it to draw a scatter.

Related

How to do a 3D circle in Matplotlib

I am a beginner in Python. I'm trying to plot a circle using matplotlib that has tangent to Z axis. I know how to draw a sphere in 3D but don't know how to draw a circle/ring in 3D plot. Can someone help me with the code? Thanks in advance!
You need the usual imports, plus the 3D toolkit
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
You need a 3D enabled axes object
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
You need a circle, contained in the plane y-z
theta = np.linspace(0, 2 * np.pi, 201)
y = 10*np.cos(theta)
z = 10*np.sin(theta)
now we can plot the original circle and, as an example, a number of circles rotated about the z-axis and whose centers are also placed at a fixed distance (equal to the c ircles'radius) from the z-axis, so that they are tangent to it
for i in range(18):
phi = i*np.pi/9
ax.plot(y*np.sin(phi)+10*np.sin(phi),
y*np.cos(phi)+10*np.cos(phi), z)
eventually we place a vertical axis and a legend
ax.plot((0,0),(0,0), (-10,10), '-k', label='z-axis')
ax.legend()
It's time to see what we got
plt.show()
mpl_toolkits.mplot3d.art3d
https://matplotlib.org/3.2.1/gallery/mplot3d/pathpatch3d.html was mentioned
in a comment, the example can be minimized to:
#!/usr/bin/env python3
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
import mpl_toolkits.mplot3d.art3d as art3d
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# Draw a circle on the x=0 'wall'
p = Circle((5, 5), 3)
ax.add_patch(p)
art3d.pathpatch_2d_to_3d(p, z=0, zdir="x")
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.set_zlim(0, 10)
plt.show()
which gives:
This is a bit nicer than https://stackoverflow.com/a/56871467/895245 as it uses a higher level Circle object directly, instead of requiring you to explicitly plot the lines.
Unfortunately, 3D support in matplotlib is a bit limited as mentioned in the documentation itself, and you have to do some extra work to plot on planes not parallel to the main coordinate plane: How can matplotlib 2D patches be transformed to 3D with arbitrary normals?
Tested on matplotlib==3.2.2.

Matplotlib copy/duplicate a 3D figure?

I've tried to find a way to copy a 3D figure in matplotlib but I didn't find a solution which is appropriate in my case.
From these posts
How do I reuse plots in matplotlib?
and
How to combine several matplotlib figures into one figure?
Using fig2._axstack.add(fig2._make_key(ax),ax) as in the code below gives quite the good result but figure 2 is not interactive I can't rotate the figure etc :
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(1)
ax = fig.gca(projection = '3d')
ax.plot([0,1],[0,1],[0,1])
fig2 = plt.figure(2)
fig2._axstack.add(fig2._make_key(ax),ax)
plt.show()
An alternative would be to copy objects from ax to ax2 using a copy method proposed in this post How do I reuse plots in matplotlib? but executing the code below returns RuntimeError: Can not put single artist in more than one figure :
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np, copy
fig = plt.figure(1)
ax = fig.gca(projection = '3d')
ax.plot([0,1],[0,1],[0,1])
fig2 = plt.figure(2)
ax2 = fig2.gca(projection = '3d')
for n in range(len(ax.lines)) :
ax2.add_line(copy.copy(ax.lines[n]))
plt.show()
Those codes are pretty simple but I don't want to copy/paste part of my code for drawing similar figures
Thanks in advance for your reply !

The plot3d figure in matplotlib is somewhat canted

I am using matplotlib to get a water fall figure, but the results look very strange. Anyone have any idea what could be wrong with it?
Here I attached the figures. The second one is the same data but in an ordinary plot. In the waterfall figure, why the color is not fully filled?
Here is the code:
def water_fall_1(x,y,Z):
#x=[...]
#y=[...]
#Z=[[z1],[z2],...z[ny]]
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection
from matplotlib.colors import colorConverter
from mpl_toolkits.mplot3d import Axes3D
figs=[]
for jc in range(len(y)):
figs.append(list(zip(x,Z[jc])))
x=np.array(x)
y=np.array(y)
Z=np.array(Z)
xmin=np.floor(np.min((x.astype(np.float))))
xmax=np.ceil(np.max((x.astype(np.float))))
ymin=np.min((y.astype(np.float)))
ymax=np.max((y.astype(np.float)))
zmin=(np.min((Z.astype(np.float))))
zmax=np.max((Z.astype(np.float)))
fig=plt.figure()
ax = Axes3D(fig)
poly = PolyCollection(figs, facecolors=colorConverter.to_rgba("r", alpha=0.5))
ax.add_collection3d(poly, zs=y.astype(np.float), zdir='y')
ax.set_xlim(xmin,xmax)
ax.set_ylim(ymin,ymax)
ax.set_zlim(zmin,zmax)
ax.set_xlabel('$\omega$')
ax.set_ylabel('$T$')
#ax.set_zlabel('$\\frac{1}{2}$')
plt.show()
The curve is fully filled. I.e. the surface in between the points of the curve is red.
Consider the following example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection
from mpl_toolkits.mplot3d import Axes3D
bottom=-0.3
x = np.linspace(0,6, num=50)
z = np.sinc(x-4)
verts = zip(x,z)
#verts=verts + [(x.max(),bottom),(x.min(),bottom)]
fig=plt.figure()
ax = Axes3D(fig)
poly = PolyCollection([verts], facecolors="r", alpha=0.5)
ax.add_collection3d(poly, zs=1, zdir='y')
ax.set_xlim(x.min(),x.max())
ax.set_ylim(0,2)
ax.set_zlim(bottom,z.max())
plt.show()
which produces the following plot, where everything between the points of the curve is filled as expected.
If we now want to have the area between the curve and some bottom line filled, we would need to add some points,
verts=verts + [(x.max(),bottom),(x.min(),bottom)]
such that the bottom line is part of the curve and can thus be filled as well.

mplot3D fill_between extends over axis limits

I have questions related to creating a simple lineplot in Python with mplot3D where the area under the plot is filled. I am using Python 2.7.5 on RedHatEnterprise 7.2, matplotlib 1.2.0 and numpy 1.7.2.
Using the code below, I am able to generate a line plot. This is displayed as expected with the beginning / end of the plot set by the limits of the imported data set.
I am then trying to fill the area between the line plot and -0.1 using the answer given by Bart from Plotting a series of 2D plots projected in 3D in a perspectival way. This works, however, the filled area is continued beyond the limits of the data set. This is also the case when running the example from the link.
This screen shot shows the plot generated with filled area extending beyond the set axis limits.
How do I achieve that the filled area is only the range of the data set or the axis limits whichever is smaller?
How do I add a legend for those plots onto the figure?
Code as follows:
from numpy import *
import matplotlib.pylab as plt
from mpl_toolkits.mplot3d import Axes3D
x,y = genfromtxt("data.dat",unpack=True)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.add_collection3d(plt.fill_between(x,y,-0.1, color='orange', alpha=0.3,label="filled plot"),1, zdir='y')
ax.plot(x,y,1,zdir="y",label="line plot")
ax.legend()
ax.set_xlim3d(852.353,852.359)
ax.set_zlim3d(-0.1,5)
ax.set_ylim3d(0,2)
ax.get_xaxis().get_major_formatter().set_useOffset(False)
plt.show()
I don't know how to put fill_between working the way you want it to, but I can provide an alternative using a 3D polygon:
from numpy import *
import matplotlib.pylab as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection # New import
#x,y = genfromtxt("data.dat",unpack=True)
# Generated some random data
w = 3
x,y = np.arange(100), np.random.randint(0,100+w,100)
y = np.array([y[i-w:i+w].mean() for i in range(3,100+w)])
z = np.zeros(x.shape)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
#ax.add_collection3d(plt.fill_between(x,y,-0.1, color='orange', alpha=0.3,label="filled plot"),1, zdir='y')
verts = [(x[i],z[i],y[i]) for i in range(len(x))] + [(x.max(),0,0),(x.min(),0,0)]
ax.add_collection3d(Poly3DCollection([verts],color='orange')) # Add a polygon instead of fill_between
ax.plot(x,z,y,label="line plot")
ax.legend()
ax.set_ylim(-1,1)
plt.show()
The code above generates some random data. Builds vertices from it and plots a polygon with those vertices. This will give you the plot you wish (but does not use fill_between). The result is:

Invert y axis on matplotlib trisurf 3d graph python

I'm using matplotlib to produce a 3d trisurf graph. I have everything working except that I would like to invert the y-axis, so that the origin is 0,0 not 0,100. I've looked through the matplotlib axes3d API and cannot figure out how to do this. Here is my code:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
# my data, xs=xaxis, ys=yaxis, zs=zaxis
mortar_xs = []
cycles_ys = []
score_zs = []
#... populate my data for the 3 arrays: mortar_xs, cycles_ys, score_zs
# plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_trisurf(mortar_xs,cycles_ys,score_zs,cmap=cm.coolwarm)
ax.set_zlim(bottom=0.0,top=1.0)
ax.legend()
ax.set_xlabel("# Mortar")
ax.set_ylabel("# Goals")
ax.set_zlabel("# Score")
plt.show()
My graph produced is the following, but I need the '# Goals' or the y-axis inverted, so that the origin is 0,0 not 0,100. If possible, I would like to do this without changing my data.
tmdavison's comment is what I was looking for:
ax.set_ylim(0,100)
Or
ax.set_ylim(100,0)
The simplest method would be to use ax.invert_yaxis()

Categories