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

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.

Related

Set size of subplot to other sublot with equal aspect ratio

I would like a representation consisting of a scatter plot and 2 histograms on the right and below the scatter plot
create. I have the following requirements:
1.) In the scatter plot, the apect ratio is equal so that the circle does not look like an ellipse.
2.) In the graphic, the subplots should be exactly as wide or high as the axes of the scatter plot.
This also works to a limited extent. However, I can't make the lower histogram as wide as the x axis of the scatter plot. How do I do that?
import matplotlib
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import random
#create some demo data
x = [random.uniform(-2.0, 2.0) for i in range(100)]
y = [random.uniform(-2.0, 2.0) for i in range(100)]
#create figure
fig = plt.figure()
gs = gridspec.GridSpec(2, 2, width_ratios = [3, 1], height_ratios = [3, 1])
ax = plt.subplot(gs[0])
# Axis labels
plt.xlabel('pos error X [mm]')
plt.ylabel('pos error Y [mm]')
ax.grid(True)
ax.axhline(color="#000000")
ax.axvline(color="#000000")
ax.set_aspect('equal')
radius = 1.0
xc = radius*np.cos(np.linspace(0,np.pi*2))
yc = radius*np.sin(np.linspace(0,np.pi*2))
plt.plot(xc, yc, "k")
ax.scatter(x,y)
hist_x = plt.subplot(gs[1],sharey=ax)
hist_y = plt.subplot(gs[2],sharex=ax)
plt.tight_layout() #needed. without no xlabel visible
plt.show()
what i want is:
Many thanks for your help!
The easiest (but not necessarily most elegant) solution is to manually position the lower histogram after applying the tight layout:
ax_pos = ax.get_position()
hist_y_pos = hist_y.get_position()
hist_y.set_position((ax_pos.x0, hist_y_pos.y0, ax_pos.width, hist_y_pos.height))
This output was produced by matplotlib version 3.4.3. For your example output, you're obviously using a different version, as I get a much wider lower histogram than you.
(I retained the histogram names as in your example although I guess the lower one should be hist_x instead of hist_y).

Read height of legend in Python

I have some plots with a lot of information and lines, so sometimes I tend to put the legend outside the plot itself using bbox_to_anchor. I also prefer to have a title of the plot, but this will positionally coincide with the legend in that case. The following example below is just an illustration of the problem.
import numpy as np
import matplotlib.pyplot as plt
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
r = 1 + np.sin(4 * np.pi * t)
q = 1 + np.sin(6 * np.pi * t)
fig, ax = plt.subplots()
ax.plot(t, s, label='S')
ax.plot(t, r, label='R')
ax.plot(t, q, label='Q')
leg = ax.legend(loc=3, ncol=3, bbox_to_anchor=(.0, 1.02, 1., .102), borderaxespad=0., mode='expand')
ax.set_title('SIMPLE PLOT', y=1.1)
plt.show()
To avoid this, I set some kind of y-value (e.g. y=1.1). I would like to automate this process because I keep updating the same plot with new data, so the legend grows in size, and I need to adjust the position of the title accordingly.
Is there a way to automate this process?
Is there a function in Python that is able to read the height of the legend so that this can be used to adjust the title position?
The height of the legend is determined at draw time. You can get it after having drawn the figure via legend.get_window_extent(). The resulting bounding box is in units of pixels. In order to find the offset of the title, you will need to subtract the upper limit of the legend from the upper limit of the axes. So you need to get the axes position in pixels as well.
The title can be offset either in figure coordinates (y=1.1) or points (pad=20). I would suggest to use points here, to make it independent of the size of the axes. So you can calculate the difference in upper positions, convert from pixels to points (i.e. distance [pixels] * ppi / dpi) and add some constant offset in points (because usually you would not want the title to sit exactly on the border of the legend). Then use that number as pad.
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots(constrained_layout=True)
ax.plot([1,2,3], np.random.rand(3,5), label='Label')
leg = ax.legend(loc="lower center", ncol=3, bbox_to_anchor=(.0, 1.02, 1., 1.02),
borderaxespad=0, mode='expand')
fig.canvas.draw()
leg_box = leg.get_window_extent()
ax_box = ax.get_position().transformed(fig.transFigure)
pad = (leg_box.y1 - ax_box.y1)*72./fig.dpi + 6
ax.set_title('SIMPLE PLOT', pad=pad)
plt.show()
Note that here I also used constrained_layout to have the title not cropped by the figure boundaries.

set a full-bound legend of figure and the problem with non discrete x value

I`d like to have a multiline figure with a dataframe.
Original data is as following:
from numpy.random import randn
import numpy as np
df=pd.DataFrame()
df['Years']=range(1995,2013)
np.random.seed(0)
df['Goverment']=randn(len(df.Years))
df['Household']=randn(len(df.Years))
df['Corporate']=randn(len(df.Years))
print(df)
and I want to set the legend along fully the bound pf figure box. I referred to the answer of #Joe Kington but this problem hasn`t been solved.
For plotting:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(10,6))
ax = plt.subplot(111)
ax.plot(df.Years, df.Government,ls='--',label='Goverment',color='black')
ax.plot(df.Years,df.Household,ls=':',label='Household',color='black')
ax.plot(df.Years,df.Corporate,ls='-',label='Corporate',color='black')
plt.xlabel("common X")
box = ax.get_position()
ax.set_position([box.x0, box.y0 + box.height * 0.1,
box.width, box.height * 1])
# Put a legend below current axis
ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.1),borderaxespad=1,mode='expand',ncol=3)
plt.show()
and the following is my result. Apparently the mode='expand' doesn`t work here.
My questions are:
1. Why the values on X axis are not integral but floats?
2. How to expand the legend box into one line instrad fully along the bound of box?
The ideal legend box should be:
The difference is indeed that you use the mode='expand'. Now this will tell the legend to expand in its bounding box. However the bounding box has no extent, it is a single point. The legend will hence expand inside a zero-width box and hence become shrunk to zero width itself.
The solution is to specify a bounding box with 4 coordinates (i.e. a true box). In principle this should also be explained in my answer to the linked question. So here we would use axes coordinates for the bbox_transform and make the box one unit in axes coordinates wide.
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(10)
fig = plt.figure()
fig.subplots_adjust(bottom=0.2, top=0.95)
ax = plt.subplot(111)
for i in range(5):
line, = ax.plot(x, i * x, label='$y = %ix$'%i)
# Put a legend below current axis
ax.legend(loc="upper center", mode='expand',
bbox_to_anchor=(0,-0.2,1,.1), bbox_transform=ax.transAxes,
fancybox=True, shadow=True, ncol=5)
plt.show()

How to decide which bars are plotted on top/last in overlay of 2 Pandas bar plots where one plot uses alpha [duplicate]

In pyplot, you can change the order of different graphs using the zorder option or by changing the order of the plot() commands. However, when you add an alternative axis via ax2 = twinx(), the new axis will always overlay the old axis (as described in the documentation).
Is it possible to change the order of the axis to move the alternative (twinned) y-axis to background?
In the example below, I would like to display the blue line on top of the histogram:
import numpy as np
import matplotlib.pyplot as plt
import random
# Data
x = np.arange(-3.0, 3.01, 0.1)
y = np.power(x,2)
y2 = 1/np.sqrt(2*np.pi) * np.exp(-y/2)
data = [random.gauss(0.0, 1.0) for i in range(1000)]
# Plot figure
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twinx()
ax2.hist(data, bins=40, normed=True, color='g',zorder=0)
ax2.plot(x, y2, color='r', linewidth=2, zorder=2)
ax1.plot(x, y, color='b', linewidth=2, zorder=5)
ax1.set_ylabel("Parabola")
ax2.set_ylabel("Normal distribution")
ax1.yaxis.label.set_color('b')
ax2.yaxis.label.set_color('r')
plt.show()
Edit: For some reason, I am unable to upload the image generated by this code. I will try again later.
You can set the zorder of an axes, ax.set_zorder(). One would then need to remove the background of that axes, such that the axes below is still visible.
ax2 = ax1.twinx()
ax1.set_zorder(10)
ax1.patch.set_visible(False)

Legend specifying 3d position in 3D axes matplotlib

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()

Categories

Resources