Legend specifying 3d position in 3D axes matplotlib - python

I have a scatter 3d plot using matplotlib.
What I'm trying to do is to position the legend inside the plot. I have read the documentation and it seems that is only possible to select predefined positions or only specify x and y coordinates.
Is there a way to position the legend specifying the 3 coordinates?
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
#more code
ax.legend(loc=(0.5,0.5,0.5), frameon=0)
The last line is what I thought might work but obviously is not working.
This is what I've now:
I'm trying to position the legend inside the axes, sort of like:
I reached that position by trial and error using ax.legend(loc=(0.15,0.65),frameon=0) because the legend doesn't move as the axes are rotated. The issue is that I'll be doing several plots thus I'm trying to avoid the trial and error approach.
Thanks.

To place the legend in a 3D plot using data coordinates, one may first get the projected coordinates of a point in 3D space using
mpl_toolkits.mplot3d.proj3d.proj_transform(x,y,z, ax.get_proj())
and provide those to the bbox_to_anchor argument of the legend. Than changing the bbox_transform to the data coordinate system produces the desired plot.
The following places the lower left corner of the legend at position (70,1000,80) in data coordinates.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D, proj3d
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = 25 * r * np.sin(theta)
y = 350* r * np.cos(theta)
ax.plot(x, y, 70*z, label='parametric curve')
ax.plot(x*.6, y*0.5, 70*z, label='parametric curve 2')
f = lambda x,y,z: proj3d.proj_transform(x,y,z, ax.get_proj())[:2]
ax.legend(loc="lower left", bbox_to_anchor=f(70,1000,80),
bbox_transform=ax.transData)
plt.show()

Related

Get X,Y Position in Figure (not in Plot)

I have a question about the X and Y Position in figures. How you can see I am working with gridspec for a better layout and adding Text to a figure. The problem is that I am trying to get the exact Position manually. Which means I am changing the X and Y in fig.text(0.2, 0.5, 'matplotlib') until I get the final figure.
import matplotlib.pylab as plt
import numpy as np
vector = np.arange(0,100)
time = np.arange(0,vector.shape[0])
fig = plt.figure(figsize=(10,10))
plt.rcParams['axes.grid'] = True
gs = fig.add_gridspec(2, 2)
ax1 = fig.add_subplot(gs[0, :])
ax1.plot(time,vector)
fig.text(0.2, 0.5, 'matplotlib')
At Link I already found an interactive solution but its only working for the Plot.
Does someone have an idea how to manage this?
You can create a blended transform, where the y-coordinates have a figure transform. And the x-coordinates have a axes transform. The figure transform is measured 0 at the left/bottom and 1 at the right/top of the figure. The axes transform is similar, but regarding the axes. The parameter clip_on=False allows to draw outside the axes region (text allows this by default).
import matplotlib.transforms as mtransforms
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
fig, ax = plt.subplots(gridspec_kw={})
# the x coords of this transformation are axes, and the y coord are fig
trans = mtransforms.blended_transform_factory(ax.transAxes, fig.transFigure)
x, w = 1, -0.3 # axes coordinates
y, h = 0.04, 0.06 # figure coordinates
ax.text(x + w / 2, y + h / 2, 'hello', transform=trans, ha='center', va='center')
rect = mpatches.Rectangle((x, y), w, h, transform=trans, edgecolor='crimson', facecolor='yellow', clip_on=False)
ax.add_patch(rect)
fig.tight_layout(pad=2)
plt.show()
PS: You can set the vertical alignment va='right' to have the right margin of the text align with the right axis. You can also use transform=ax.transAxes with negative y-coordinates to plot everything relative to the axes.

plot 3D polygons in 2D

I search for the correct implementation for this a long time now.
I have a 3D delaunay triangulation and want to plot this in 2D.
In 3D i manage to do so:
I need a 2D plot though. What i get using matplotlib.tripcolor method or the matplotlib.collections.PolyCollection is:
How do i plot this in 2D without the top and back triangles all mixed up? With all methods tried so far, some triangles are hidden by triangles that should be in the back of the structure.
I see, that the methods just do not have the information necessary to plot in the correct order, since i have to provide 2D arrays already. The depth information is lost.
Does anybody know how to do this?
Thanks a lot!
You can mimic a 2D plot with Axes3d by setting an orthographic projection, initialising the view to face the desired plane, and removing unwanted plot elements along the axis orthogonal to the chosen plane of view. In addition, you can plot 2D elements using the zdir keyword argument.
Here's one of the matplotlib 3D plot examples I modified to demonstrate
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
# Enable orthographic projection
# https://stackoverflow.com/questions/23840756/how-to-disable-perspective-in-mplot3d
from mpl_toolkits.mplot3d import proj3d
def orthogonal_proj(zfront, zback):
a = (zfront+zback)/(zfront-zback)
b = -2*(zfront*zback)/(zfront-zback)
return np.array([[1,0,0,0],
[0,1,0,0],
[0,0,a,b],
[0,0,-0.000001,zback]])
proj3d.persp_transformation = orthogonal_proj
fig = plt.figure()
ax = fig.gca(projection='3d')
# Init view to YZ plane
ax.view_init(azim=0, elev=0)
# Hide the X axis
ax.w_xaxis.line.set_lw(0.)
ax.set_xticks([])
# Change YZ plane colour to white
ax.w_xaxis.set_pane_color((1.0, 1.0, 1.0, 1.0))
# Make data.
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
# Plot the surface.
surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
ax.set_zlim(-1.1, 1.1)
ax.set_ylabel('y')
ax.set_zlabel('z')
# Plot 2D elements with zdir argument
# https://stackoverflow.com/questions/29549905/pylab-3d-scatter-plots-with-2d-projections-of-plotted-data
stepsize = 0.1
t = np.arange(-4, 4+stepsize, step=stepsize)
ax.plot(t, 0.5*np.sin(t), 'k', zdir='x', linewidth=1.0)
ax.text(0, 0, 1, 'Text', zdir='y', ha='center', va='top')
plt.show()

Put legend on a place of a subplot

I would like to put a legend on a place of a central subplot (and remove it).
I wrote this code:
import matplotlib.pylab as plt
import numpy as np
f, ax = plt.subplots(3,3)
x = np.linspace(0, 2. * np.pi, 1000)
y = np.sin(x)
for axis in ax.ravel():
axis.plot(x, y)
legend = axis.legend(loc='center')
plt.show()
I do not know how to hide a central plot. And why legend is not appear?
This link did not help http://matplotlib.org/1.3.0/examples/pylab_examples/legend_demo.html
There are several problems with your code. In your for loop, you are attempting to plot a legend on each axis (the loc="center" refers to the axis, not the figure), yet you have not given a plot label to represent in your legend.
You need to choose the central axis in your loop and only display a legend for this axis. This iteration of the loop should have no plot call either, if you don't want a line there. You can do this with a set of conditionals like I have done in the following code:
import matplotlib.pylab as plt
import numpy as np
f, ax = plt.subplots(3,3)
x = np.linspace(0, 2. * np.pi, 1000)
y = np.sin(x)
handles, labels = (0, 0)
for i, axis in enumerate(ax.ravel()):
if i == 4:
axis.set_axis_off()
legend = axis.legend(handles, labels, loc='center')
else:
axis.plot(x, y, label="sin(x)")
if i == 3:
handles, labels = axis.get_legend_handles_labels()
plt.show()
This gives me the following image:

Fix the plot size after displaying a matrix with spy

I have a matplotlib figure that am using embedded into a pyQT GUI, therefore I need to recycle the figure to display several resuls.
When I display a matrix using the spy function, I get what I spect:
However when I clear the figure and plot a series I get this:
Instead of this:
Which I get if I plot the series without displaying a matrix before.
So a script to reproduce the issue is:
from matplotlib.pyplot import figure, show
import numpy
fig = figure()
ax = fig.add_subplot(111)
mat = numpy.random.randn(20, 20)
# display the matrix
ax.spy(mat, markersize=5)
x = numpy.linspace(0, 1, 100)
y = x**2 + x - 5
ax.clear()
ax.plot(x, y)
I have also tried
ax.relim() # make sure all the data fits
ax.autoscale() # auto-scale
But it doesn't do anything noticeable.
plt.spy will automatically set the aspect ratio of the axes to 'equal' in order to ensure that the sparsity plot for a square matrix looks square. If the x-axis scale of your series is much larger than that of the y-axis, an equal aspect ratio will result in a very long and thin line plot.
To switch back to the 'default' mode where the aspect ratio is determined automatically you can call ax.set_aspect('auto'):
from matplotlib.pyplot import figure, show
import numpy
fig = figure()
ax = fig.add_subplot(111)
mat = numpy.random.randn(20, 20)
# display the matrix
ax.spy(mat, markersize=5)
x = numpy.linspace(0, 1, 100)
y = x**2 + x - 5
ax.clear()
ax.set_aspect('auto')
ax.plot(x, y)

Multiple y-scales but only one enabled for pan and zoom

Consider the following python code for plotting a matplotlib figure:
import matplotlib.pylab as pp
import numpy as np
alpha = np.linspace(0, 2 * np.pi, 400)
sig1 = np.sin(alpha)
sig2 = np.sin(2 * alpha) + 2 * (alpha > np.pi)
ax1 = pp.subplot(111)
ax2 = ax1.twinx()
ax1.plot(alpha, sig1, color='b')
ax2.plot(alpha, sig2, color='r')
ax1.set_ylabel('sig1 value', color='b')
ax2.set_ylabel('sig2 value', color='r')
pp.grid()
pp.show()
Giving me a nice plot
I would like to find out how to disable one of the axes for panning / zooming, so when I use the pan / zoom tool, only ax2 will rescale for example. Is there a way to do this? I want to do it programmatically.
You can do this using ax2.set_navigate(False):
from matplotlib.pyplot import *
import numpy as np
fig,ax1 = subplots(1,1)
ax2 = ax1.twinx()
ax2.set_navigate(False)
x = np.linspace(0,2*np.pi,100)
ax1.plot(x,np.sin(x),'b')
ax1.set_xlabel('Scaleable axis')
ax1.set_ylabel('Scaleable axis')
ax2.plot(x,np.sin(x+1),'r')
ax2.set_ylabel('Static axis',weight='bold')
A slightly more complex example with two plot areas and three vertical axes. Only the common horizontal axis and the left vertical axis of the lower subplot are interactive.
fig, ax_left = plt.subplots()
ax_right = ax_left.twinx()
ax_status = make_axes_locatable(ax_left).append_axes('top', size=1.2, pad=0., sharex=ax_left)
ax_status.xaxis.set_tick_params(labelbottom=False)
ax_right.set_navigate(False)
ax_status.set_navigate(False)
Before I added set_navigate(False) according to ali_m's answer, the two vertical axes of the lower plot were both affected by dragging the mouse vertically in the lower plot, while the status axis was unaffected as it should but only after the first mouse gesture. Dragging the mouse for the first time, all axes are affected. This seems to be a bug in matplotlib, just reported as #12613.

Categories

Resources