I am having trouble finishing a plot in matplotlib. Here is the code:
arrays_k, arrays_v = splitbyrecordcount(ycsb[2])
checktype = [ "Update", "Read", "Verification" ]
fig = plt.figure()
ax = fig.add_subplot('111', projection='3d')
for z in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]:
xs = arrays_k[z]
ys = arrays_v[z]
c = colmap.spectral(z/10.,1)
ax.plot(xs, ys, zs=z, zdir='z', color=c)
It produces this:
I want the time plot how you might expect: more "in the plane" of the screen as opposed to sort of "perpendicular" to it as in the image above. I have tried lots of different combinations of the ax.plot() part, but if I change it to:
ax.plot(xs, ys, zs=z, zdir='y', color=c)
I get this:
Changing it to:
ax.plot(xs, ys, zs=z, zdir='x', color=c)
...doesn't help either. It just makes a thin bar of everything in the z direction at the origin.
Any ideas? Even if someone knows a way to just rotate the whole graph so the plots are in the plane of the screen would be better than nothing.
Without having the data to check I believe the issue is the order of the arguments for ax.plot. Try this:
ax.plot(xs, z * np.ones(xs.shape), zs=ys, zdir='z', color=c)
So, you want what would be your 'y' axis in a 2D plot to be the height, hence zs=ys.
Related
I am testing the clip_box feature of Artist using the code snippet below:
import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox
import numpy as np
fig = plt.figure()
ax = fig.subplots(1, 2)
x = [1, 2, 3, 4]
y = [3, 8, 5, 2]
line_a, = ax[0].plot(x, y, color='red', linewidth=3.0)
line_b, = ax[1].plot(x, y, color='red', linewidth=3.0)
boundingbox = Bbox(np.array([[0, 0], [3, 9]]))
line_b.set_clip_box(boundingbox)
line_b.set_clip_on(True)
plt.show()
What I expect is the last part of line_b will be cut out by the clip box, and line_b will be a bit shorter than line_a.
It turns out that there's nothing left on the second subplot. It's totally empty. Is my understanding of the clip_box wrong or are there some issues in the code snippet?
The "natural" clip box for the right hand side plot is ax[1].bbox. Finding its extent tells us what units should be used to specify the clip box Bbox.
Since we don't add the Bbox instance to any axes when we create, it could only be relative to the figure. When we print ax[1].bbox, we can see that its size is to be specified in pixels.
It's indeed much simpler to use a Rectangle or Polygon to specify the clip box because they can be added to axes. Using 'none' color for its facecolor could be more convenient because it's figure style-independent.
import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox
fig = plt.figure(dpi=89)
ax = fig.subplots(1, 2)
x = [1, 2, 3, 4]
y = [3, 8, 5, 2]
line_a, = ax[0].plot(x, y, color='red', linewidth=3.0)
line_b, = ax[1].plot(x, y, color='red', linewidth=3.0)
print(ax[1].bbox, '\n', ax[1].bbox.extents)
# the line above prints
# TransformedBbox(
# Bbox(x0=0.5477272727272726, y0=0.10999999999999999, x1=0.8999999999999999, y1=0.88),
# BboxTransformTo(
# TransformedBbox(
# Bbox(x0=0.0, y0=0.0, x1=6.393258426966292, y1=4.797752808988764),
# Affine2D().scale(178.0))))
# [ 623.31363636 93.94 1024.2 751.52 ]
# 178.0 is 2 * dpi, I believe the doubling happens because of what screen I have got
boundingbox = Bbox.from_extents([623.31363636, 93.94, 900.2, 751.52])
print(boundingbox, '\n', boundingbox.extents)
# the line above prints
# Bbox(x0=623.31363636, y0=93.94, x1=900.2, y1=751.52)
# [623.31363636 93.94 900.2 751.52 ]
line_b.set_clip_box(boundingbox)
line_b.set_clip_on(True)
plt.show()
I've spent some time reading about Bboxes in Matplotlib and they are pretty complicated. The set_clip_box method you refer to has not got very helpful documentation, and the examples of its use both use the bbox of an Axes, which is a nested transformation; ie _, ax = plt.subplots(); ax.bbox is a TransformedBbox based on a linear transform of another TransformedBbox based on an Affine2D transform of a plain Bbox! (All of this explained in more detail here.)
It seems that these involve transformations between different sets of co-ordinates; in the case of a regular Axes it is between x- and y-values, pixels, and the specific adaptations to screen size. I would be happy to hear from someone who knows more about Bboxes why your Bbox acts the way it does. But what you want to achieve can be done much more easily, using a FancyBboxPatch (a Rectangle patch would work just as well):
from matplotlib.patches import FancyBboxPatch
f, ax = plt.subplots(1, 2)
x = [1, 2, 3, 4]
y = [3, 8, 5, 2]
line_a, = ax[0].plot(x, y, color='red', linewidth=3.0)
line_b, = ax[1].plot(x, y, color='red', linewidth=3.0)
bb = Bbox([[0, 0], [3, 9]])
ax[1].add_patch(FancyBboxPatch((bb.xmin, bb.ymin), bb.width, bb.height, boxstyle="square",
ec='white', fc='white', zorder=2.1))
(ec and fc are edge colour and fill colour; zorder determines the artist order. Lines are 2, so we just need out Bbox patch to be slightly higher.)
Is there something wrong with the 3d plt.scatter(x,y,z) method?
Plots all z values at zero:
x = [1, 1]
y = [1, 1]
z = [-10, 10]
fig = plt.figure(figsize=(16, 18))
plt.axes(projection ="3d")
plt.scatter(x, y, z, color='k')
plt.show()
Working correctly:
x = [1, 1]
y = [1, 1]
z = [-10, 10]
fig = plt.figure(figsize=(16, 18))
ax = plt.axes(projection ="3d")
ax.scatter(x, y, z, color='k')
plt.show()
In your above examples you used the two matplotlib's interfaces: pyplot vs object oriented.
If you'll look at the source code of pyplot.scatter you'll see that even if you are going to provide 3 arguments plt.scatter(x, y, z, color='k'), it is actually going to call the 2D version, with x, y, s=z, s being the marker size.
So, it appears that you have to use the object oriented approach to achieve your goals.
I'm trying to plot 3D poses of hand using matplotlib. For every frame of video or we can say if data is changed then plot size(X, Y, Z) also changed with respect to object(hand poses) size and position. For more detail below are two screenshots with next and previous frame, in screenshots we can see that x, y and z axis are changed as hand pose is changed.
My question is that how to get a stable plot that it's size will not change even object position will be changed.
As a reference below is another image that shows a stable plot and I'm trying to get like this. As we can see that input frames and object size is changing but plot is with same size. No matter if object size will be changed in plot.
Below is my plotting code. Kindly take it just as plotting sample, I'm sharing only plotting code because problem is in plotting code.
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.view_init(elev=20, azim=75)
ax.set_xlabel('X Axis')
ax.set_ylabel('Y Axis')
ax.set_zlabel('Z Axis')
rgb_dict = get_keypoint_rgb(skeleton)
for i in range(len(skeleton)): # here is skeleton, just ignore it
joint_name = skeleton[i]['name']
pid = skeleton[i]['parent_id']
parent_joint_name = skeleton[pid]['name']
x = np.array([kps_3d[i, 0], kps_3d[pid, 0]]) # kps_3d is pose data
y = np.array([kps_3d[i, 1], kps_3d[pid, 1]])
z = np.array([kps_3d[i, 2], kps_3d[pid, 2]])
ax.plot(x, z, -y, c = np.array(rgb_dict[parent_joint_name])/255., linewidth = line_width)
ax.scatter(kps_3d[i, 0], kps_3d[i, 2], -kps_3d[i, 1], c = np.array(rgb_dict[joint_name]).reshape(1, 3)/255., marker='o')
ax.scatter(kps_3d[pid, 0], kps_3d[pid, 2], -kps_3d[pid, 1], c = np.array(rgb_dict[parent_joint_name]).reshape(1, 3)/255., marker='o')
As ax.scatter docs show that it is used to get a scatter plot of y vs. x with varying marker size and/or color.
Which alternative function I can use to get stable plots?
Looking for some valuable suggestions.
Try set the plot limits by these lines of code:
ax.set_xlim3d(-0.1, 0.1)
ax.set_ylim3d(-0.1, 0.1)
ax.set_zlim3d(-0.1, 0.1)
Adjust the numbers as needed. You may also need to adjust viewing angle:
ax.view_init(elev=28., azim=55)
I need to plot 3D data of the form z_i as function of (x_i, y_i) using a wireframe. I wrote the code below:
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import axes3d
import scipy.interpolate as spint
## Data to plot
sim_data = np.array([[ 20, 1, 8],
[ 20, 2, 7],
[ 20, 4, 7],
[ 20, 6, 6],
[ 20, 10, 6],
[ 50, 0.4, 15],
[ 50, 0.8, 11],
[ 50, 1, 10],
[ 50, 2, 8],
[ 50, 4, 7],
[ 50, 6, 7],
[ 50, 10, 7],
[100, 0.4, 22],
[100, 0.8, 15],
[100, 1, 13],
[100, 2, 10],
[100, 4, 8],
[100, 6, 7],
[100, 10, 7]])
x = sim_data[:, 0]
y = sim_data[:, 1]
z = sim_data[:, 2]
# Do trisurf plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_trisurf(x, y, z)
ax.set_xlabel('Air flow')
ax.set_ylabel('Fuel rate')
ax.set_zlabel('Temp.')
ax.text2D(0.05, 0.95, "Trisurf plot", transform=ax.transAxes)
# Transform from vector to grid
X, Y = np.meshgrid(x, y)
xi = (X, Y)
Z = spint.griddata((x,y), z, xi)
# Do wireframe plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_wireframe(X, Y, Z)
ax.set_xlabel('Air flow')
ax.set_ylabel('Fuel rate')
ax.set_zlabel('Temp.')
ax.text2D(0.05, 0.95, "Wireframe plot", transform=ax.transAxes)
# Do surface plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z)
ax.set_xlabel('Air flow')
ax.set_ylabel('Fuel rate')
ax.set_zlabel('Temp.')
ax.text2D(0.05, 0.95, "Surface plot", transform=ax.transAxes)
But I get some annoying extra wires (marked with a red arrow):
How can I get rid of this arrow? I have the same problem while trying a surface plot by the way:
My goal is to have a plot similar to a trisurf plot like the one below, but with a wireframe visualization.
Many thanks in advance.
What's wrong with wireframe?
I am not sure, but I think the problem is in your data. It's small and if you look carefully you will see that it looks like a stack of three different lines (observations). Look at this plot:
it's definitely there are three parallel lines over there. I suppose, that's might cause confusion with plot_wireframe, as well as with plot from the previous image. I see 3 possible solutions:
Solution 1: Use plot
Ok, so the first solution is not to use plot_wireframe at all. Let's use good old one plot to build our own wires. But first, let's break our data into 3 lines data:
line1 = sim_data[0:5][::-1] # NOTE: the first line is shorter
line2 = sim_data[5:12][::-1]
line3 = sim_data[12:][::-1]
Plot them all!
# a helper function
def prepare_fig(fw=7, fh=7, view = (25, 30)):
fig = plt.figure(figsize=(fw, fh))
ax = fig.add_subplot(111, projection='3d')
ax.view_init(view[0], view[-1])
return ax
ax = prepare_fig()
ax.title.set_text('3 Lines')
for line in [line1, line2, line3]:
x, y, z = line[:, 0], line[:, 1], line[:, 2]
ax.plot(x, y, z, c='tab:blue', linewidth=3)
Ok, we fixed undesired links, now let's add parallel links (lines) to connect our main lines:
ax = prepare_fig()
ax.title.set_text('Paralel links')
for i in range(len(line3)):
x, y, z = [], [], []
if i < len(line1):
x.append(line1[:, 0][i])
y.append(line1[:, 1][i])
z.append(line1[:, 2][i])
else:
# line1 is shorter so we will put nan here (for now)
x.append(np.nan)
y.append(np.nan)
z.append(np.nan)
x.extend([line2[:, 0][i], line3[:, 0][i]])
y.extend([line2[:, 1][i], line3[:, 1][i]])
z.extend([line2[:, 2][i], line3[:, 2][i]])
ax.plot(x, y, z, c='tab:blue', linewidth=3)
Now all in one:
Final Code:
ax = prepare_fig()
ax.title.set_text('Handmade Wireframe (enclosed)')
line1 = sim_data[0:5][::-1]
line2 = sim_data[5:12][::-1]
line3 = sim_data[12:][::-1]
for line in [line1, line2, line3]:
x, y, z = line[:, 0], line[:, 1], line[:, 2]
ax.plot(x, y, z, c='tab:blue', linewidth=3)
for i in range(len(line3)):
x, y, z = [], [], []
if i < len(line1):
x.append(line1[:, 0][i])
y.append(line1[:, 1][i])
z.append(line1[:, 2][i])
else:
# put nan because line1 is shorter
# x.append(np.nan)
# y.append(np.nan)
# z.append(np.nan)
# Or you can just replace it with last line1 value
x.append(line1[:, 0][-1])
y.append(line1[:, 1][-1])
z.append(line1[:, 2][-1])
x.extend([line2[:, 0][i], line3[:, 0][i]])
y.extend([line2[:, 1][i], line3[:, 1][i]])
z.extend([line2[:, 2][i], line3[:, 2][i]])
ax.plot(x, y, z, c='tab:blue', linewidth=3)
Solution 2: Use plot_trisurf.
If triangles are acceptable, another solution is to transform trisurf to wireframe-like by some tweaking.
x = sim_data[:, 0]
y = sim_data[:, 1]
z = sim_data[:, 2]
ax = prepare_fig()
ax.title.set_text('Trisurf Wireframe')
trisurf = ax.plot_trisurf(x, y, z)
# turn of surface color, you can control it with alpha here:
trisurf.set_facecolor(mpl.colors.colorConverter.to_rgba('w', alpha=0.0))
# setting wire color
trisurf.set_edgecolor('tab:blue')
#setting wire width
trisurf.set_linewidth(3)
Solution 3: Use plot_wireframe and interpolation at linspace grid.
This might be solution if you want good looking smooth surface. You just need to generate new grid and then using scipy's spint.griddata to perform interpolation:
import scipy.interpolate as spint
x = sim_data[:, 0]
y = sim_data[:, 1]
z = sim_data[:, 2]
# generate new linear grid based on previous
X, Y = np.meshgrid(np.linspace(min(x), max(x), len(x)),
np.linspace(min(y), max(y), len(y)))
Z = spint.griddata((x, y), z, (X, Y))
ax = prepare_fig()
ax.title.set_text('Interpotation on Linspace Grid')
# ax.plot_wireframe(X, Y, Z, rstride=3, cstride=3)
ax.plot_surface(X, Y, Z, rstride=3, cstride=3)
And you will get something like this:
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.set_xlim([0, 1])
ax.set_ylim([0, 1])
ax.set_zlim([0, 90])
x = np.linspace(xL, xR, nx)
z = np.linspace(zL, zR, nz)
X, Z = np.meshgrid(x, z)
r = T[:,5,:,0]
graph = ax.plot_surface(X, Z, r)
def update_graph(q):
r = T[:,5,:,q]
graph.set_3d_properties(r)
return graph
ani = matplotlib.animation.FuncAnimation(fig, update_graph, frames = 11)
plt.show()
I have the code above, T is a 100x100x100x12 matrix, and I want to make an animation showing a surface plot as the 4th axis goes from 0-11. However it seems that the animation portion is not working correctly, and I believe the issue is in my update_graph function that it is not passing back an updated value of r to be used in the plot.
It should be noted that for Poly3DCollection set_3d_properties() is only implemented as the following
def set_3d_properties(self):
# Force the collection to initialize the face and edgecolors
# just in case it is a scalarmappable with a colormap.
self.update_scalarmappable()
self._sort_zpos = None
self.set_zsort('average')
self._facecolors3d = PolyCollection.get_facecolor(self)
self._edgecolors3d = PolyCollection.get_edgecolor(self)
self._alpha3d = PolyCollection.get_alpha(self)
self.stale = True
So it doesn't actually modify the data as it does with other 3d objects (e.g. Line3D).
Instead, I would recommend you do something like this
graph = ax.plot_surface(X, Z, r)
def update_graph(q):
r = T[:,5,:,q]
plt.cla()
ax.set_xlim([0, 1])
ax.set_ylim([0, 1])
ax.set_zlim([0, 90])
graph = ax.plot_surface(X, Z, r)
return graph,
Obviously it is a bit tedious to reset all the axis properties on each update but I don't think there is any easier way.
Also note the trailing comma in return graph, - this is necessary when blit=True because FuncAnimation expects an iterable of artists to be returned. However, the return statement is ignored when blit=False.
Here is the result of a simple example using this approach