The Python/pyplot code below generates four figures and four windows. I need code that opens one window showing fig1. Then when the user presses right arrow button or right arrow key the same window clears fig1 and shows fig2. So basically only one of the four figures will be selected by the user for viewing in a slideshow. I have searched for an answer in the docs and online without success. I have edited the question to show the definition of six axes that appear in the four figures. It appears that one must associate the axes with a single figure and then draw, clear, and redraw axes to simulate a slideshow in the default GUI?
import numpy as np
import matplotlib.pyplot as plt
fig1 = plt.figure()
ax1 = fig1.add_subplot(3, 1, 1)
ax2 = fig1.add_subplot(3, 1, 2, sharex=ax1)
ax3 = fig1.add_subplot(3, 1, 3, sharex=ax1)
fig2 = plt.figure()
ax4 = fig2.add_subplot(1, 1, 1)
fig3 = plt.figure()
ax5 = fig2.add_subplot(1, 1, 1)
fig4 = plt.figure()
ax6 = fig2.add_subplot(1, 1, 1)
plt.show()
Ideally I would like to set the backend to ensure the same code functions on MacOS, Linux, and Windows. However I would be satisfied to get a very basic slideshow working on Windows 7 and develop for other OS later if necessary.
Maybe something like this:
(click on the graph to switch)
import matplotlib.pyplot as plt
import numpy as np
i = 0
def fig1(fig):
ax = fig.add_subplot(111)
ax.plot(x, np.sin(x))
def fig2(fig):
ax = fig.add_subplot(111)
ax.plot(x, np.cos(x))
def fig3(fig):
ax = fig.add_subplot(111)
ax.plot(x, np.tan(x))
def fig4(fig):
ax1 = fig.add_subplot(311)
ax1.plot(x, np.sin(x))
ax2 = fig.add_subplot(312)
ax2.plot(x, np.cos(x))
ax3 = fig.add_subplot(313)
ax3.plot(x, np.tan(x))
switch_figs = {
0: fig1,
1: fig2,
2: fig3,
3: fig4
}
def onclick1(fig):
global i
print(i)
fig.clear()
i += 1
i %= 4
switch_figs[i](fig)
plt.draw()
x = np.linspace(0, 2*np.pi, 1000)
fig = plt.figure()
switch_figs[0](fig)
fig.canvas.mpl_connect('button_press_event', lambda event: onclick1(fig))
plt.show()
Related
Looking at the matplotlib documentation, it seems the standard way to add an AxesSubplot to a Figure is to use Figure.add_subplot:
from matplotlib import pyplot
fig = pyplot.figure()
ax = fig.add_subplot(1,1,1)
ax.hist( some params .... )
I would like to be able to create AxesSubPlot-like objects independently of the figure, so I can use them in different figures. Something like
fig = pyplot.figure()
histoA = some_axes_subplot_maker.hist( some params ..... )
histoA = some_axes_subplot_maker.hist( some other params ..... )
# make one figure with both plots
fig.add_subaxes(histo1, 211)
fig.add_subaxes(histo1, 212)
fig2 = pyplot.figure()
# make a figure with the first plot only
fig2.add_subaxes(histo1, 111)
Is this possible in matplotlib and if so, how can I do this?
Update: I have not managed to decouple creation of Axes and Figures, but following examples in the answers below, can easily re-use previously created axes in new or olf Figure instances. This can be illustrated with a simple function:
def plot_axes(ax, fig=None, geometry=(1,1,1)):
if fig is None:
fig = plt.figure()
if ax.get_geometry() != geometry :
ax.change_geometry(*geometry)
ax = fig.axes.append(ax)
return fig
Typically, you just pass the axes instance to a function.
For example:
import matplotlib.pyplot as plt
import numpy as np
def main():
x = np.linspace(0, 6 * np.pi, 100)
fig1, (ax1, ax2) = plt.subplots(nrows=2)
plot(x, np.sin(x), ax1)
plot(x, np.random.random(100), ax2)
fig2 = plt.figure()
plot(x, np.cos(x))
plt.show()
def plot(x, y, ax=None):
if ax is None:
ax = plt.gca()
line, = ax.plot(x, y, 'go')
ax.set_ylabel('Yabba dabba do!')
return line
if __name__ == '__main__':
main()
To respond to your question, you could always do something like this:
def subplot(data, fig=None, index=111):
if fig is None:
fig = plt.figure()
ax = fig.add_subplot(index)
ax.plot(data)
Also, you can simply add an axes instance to another figure:
import matplotlib.pyplot as plt
fig1, ax = plt.subplots()
ax.plot(range(10))
fig2 = plt.figure()
fig2.axes.append(ax)
plt.show()
Resizing it to match other subplot "shapes" is also possible, but it's going to quickly become more trouble than it's worth. The approach of just passing around a figure or axes instance (or list of instances) is much simpler for complex cases, in my experience...
The following shows how to "move" an axes from one figure to another. This is the intended functionality of #JoeKington's last example, which in newer matplotlib versions is not working anymore, because axes cannot live in several figures at once.
You would first need to remove the axes from the first figure, then append it to the next figure and give it some position to live in.
import matplotlib.pyplot as plt
fig1, ax = plt.subplots()
ax.plot(range(10))
ax.remove()
fig2 = plt.figure()
ax.figure=fig2
fig2.axes.append(ax)
fig2.add_axes(ax)
dummy = fig2.add_subplot(111)
ax.set_position(dummy.get_position())
dummy.remove()
plt.close(fig1)
plt.show()
For line plots, you can deal with the Line2D objects themselves:
fig1 = pylab.figure()
ax1 = fig1.add_subplot(111)
lines = ax1.plot(scipy.randn(10))
fig2 = pylab.figure()
ax2 = fig2.add_subplot(111)
ax2.add_line(lines[0])
TL;DR based partly on Joe nice answer.
Opt.1: fig.add_subplot()
def fcn_return_plot():
return plt.plot(np.random.random((10,)))
n = 4
fig = plt.figure(figsize=(n*3,2))
#fig, ax = plt.subplots(1, n, sharey=True, figsize=(n*3,2)) # also works
for index in list(range(n)):
fig.add_subplot(1, n, index + 1)
fcn_return_plot()
plt.title(f"plot: {index}", fontsize=20)
Opt.2: pass ax[index] to a function that returns ax[index].plot()
def fcn_return_plot_input_ax(ax=None):
if ax is None:
ax = plt.gca()
return ax.plot(np.random.random((10,)))
n = 4
fig, ax = plt.subplots(1, n, sharey=True, figsize=(n*3,2))
for index in list(range(n)):
fcn_return_plot_input_ax(ax[index])
ax[index].set_title(f"plot: {index}", fontsize=20)
Outputs respect.
Note: Opt.1 plt.title() changed in opt.2 to ax[index].set_title(). Find more Matplotlib Gotchas in Van der Plas book.
To go deeper in the rabbit hole. Extending my previous answer, one could return a whole ax, and not ax.plot() only. E.g.
If dataframe had 100 tests of 20 types (here id):
dfA = pd.DataFrame(np.random.random((100,3)), columns = ['y1', 'y2', 'y3'])
dfB = pd.DataFrame(np.repeat(list(range(20)),5), columns = ['id'])
dfC = dfA.join(dfB)
And the plot function (this is the key of this whole answer):
def plot_feature_each_id(df, feature, id_range=[], ax=None, legend_bool=False):
feature = df[feature]
if not len(id_range): id_range=set(df['id'])
legend_arr = []
for k in id_range:
pass
mask = (df['id'] == k)
ax.plot(feature[mask])
legend_arr.append(f"id: {k}")
if legend_bool: ax.legend(legend_arr)
return ax
We can achieve:
feature_arr = dfC.drop('id',1).columns
id_range= np.random.randint(len(set(dfC.id)), size=(10,))
n = len(feature_arr)
fig, ax = plt.subplots(1, n, figsize=(n*6,4));
for i,k in enumerate(feature_arr):
plot_feature_each_id(dfC, k, np.sort(id_range), ax[i], legend_bool=(i+1==n))
ax[i].set_title(k, fontsize=20)
ax[i].set_xlabel("test nr. (id)", fontsize=20)
Assume a library function that creates a figure internally:
def plot_data(data): # cannot change this implementation
...
fig, ax = plt.subplots(1, figsize=(w, h))
ax.plot(...)
...
return fig, ax
I create two plots using the above function:
fig1, ax1 = plot_data(data1)
fig2, ax2 = plot_data(data2)
How can I put these two plots/figures side by side? A naive approach is as follows (does not work this way):
fig0, ax0 = plt.subplots(1, 2, figsize=(w, h))
ax0[0] = ax1
ax0[1] = ax2
plt.close(fig1)
plt.close(fig2)
plt.show()
I've got a figure that contains three subplots which are arranged vertically. Once I click into the figure, I want the second subplot ax2 to be hidden and the other plots to fill the space. A second click into the figure should restore the original plot and layout.
Hiding the subplot ax2 isn't a problem, but how can I rearrange the positions of the other subplots?
I've tried creating a new GridSpec, using the set_position and set_subplotspec methods, but nothing worked out. I'm sure I'm missing something here, any help would be appreciated.
This is my code:
import matplotlib.pyplot as plt
from matplotlib import gridspec
fig = plt.figure()
gs = gridspec.GridSpec(3, 1, height_ratios=[5, 2, 1])
ax1 = fig.add_subplot(gs[0])
ax2 = fig.add_subplot(gs[1], sharex=ax1)
ax3 = fig.add_subplot(gs[2], sharex=ax2)
visible = True
def toggle_ax2(event):
global visible
visible = not visible
ax2.set_visible(visible)
plt.draw()
fig.canvas.mpl_connect('button_press_event', toggle_ax2)
plt.show()
You can define two different GridSpecs. One would have 3 subplots, the other 2. Depending on the visibility of the middle axes, you change the position of the other two axes to obey to the first or second GridSpec.
(There is no need for any dummy figure or so, like other answers might suggest.)
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure()
gs = gridspec.GridSpec(3, 1, height_ratios=[5, 2, 1], hspace=0.3)
gs2 = gridspec.GridSpec(2,1, height_ratios=[5,3])
ax1 = fig.add_subplot(gs[0])
ax2 = fig.add_subplot(gs[1], sharex=ax1)
ax3 = fig.add_subplot(gs[2], sharex=ax2)
ax1.plot([1,2,3], [1,2,3], color="crimson")
ax2.plot([1,2,3], [2,3,1], color="darkorange")
ax3.plot([1,2,3], [3,2,1], color="limegreen")
visible = True
def toggle_ax2(event):
global visible
visible = not visible
ax2.set_visible(visible)
if visible:
ax1.set_position(gs[0].get_position(fig))
ax3.set_position(gs[2].get_position(fig))
else:
ax1.set_position(gs2[0].get_position(fig))
ax3.set_position(gs2[1].get_position(fig))
plt.draw()
fig.canvas.mpl_connect('button_press_event', toggle_ax2)
plt.show()
Left: original; right: after clicking
You can create a new gridspec instance, and use that to create some dummy figures in a second figure (you can close this before you plt.show, so you never actually see it, we just want to grab some positions from the axes here).
By storing the two possible positions for ax1 and ax3 from that dummy figure and the original figure, then you can use ax.set_position() in your toggle_ax2 function to change the positions of the remaining two axes.
import matplotlib.pyplot as plt
from matplotlib import gridspec
fig = plt.figure()
gs = gridspec.GridSpec(3, 1, height_ratios=[5, 2, 1])
ax1 = fig.add_subplot(gs[0])
ax2 = fig.add_subplot(gs[1], sharex=ax1)
ax3 = fig.add_subplot(gs[2], sharex=ax2)
# Store the original positions of ax1 and ax3
pos1_1 = ax1.get_position()
pos3_1 = ax3.get_position()
# Create a second gridspec for when ax2 is hidden. Keep 5:1 ratio
gs2 = gridspec.GridSpec(2, 1, height_ratios=[5, 1])
fig2 = plt.figure()
ax1_2 = fig2.add_subplot(gs2[0])
ax3_2 = fig2.add_subplot(gs2[1])
# Store the positions of ax1 and ax3 in the new gridspec
pos1_2 = ax1_2.get_position()
pos3_2 = ax3_2.get_position()
# Close the dummy figure2
plt.close(fig2)
visible = True
def toggle_ax2(event):
global visible
visible = not visible
ax2.set_visible(visible)
# Use the stored positions to switch between
# different arrangements of ax1 and ax3
if visible:
ax1.set_position(pos1_1)
ax3.set_position(pos3_1)
else:
ax1.set_position(pos1_2)
ax3.set_position(pos3_2)
plt.draw()
fig.canvas.mpl_connect('button_press_event', toggle_ax2)
plt.show()
Original configuration:
After removing ax2:
I've got a figure that contains three subplots which are arranged vertically. Once I click into the figure, I want the second subplot ax2 to be hidden and the other plots to fill the space. A second click into the figure should restore the original plot and layout.
Hiding the subplot ax2 isn't a problem, but how can I rearrange the positions of the other subplots?
I've tried creating a new GridSpec, using the set_position and set_subplotspec methods, but nothing worked out. I'm sure I'm missing something here, any help would be appreciated.
This is my code:
import matplotlib.pyplot as plt
from matplotlib import gridspec
fig = plt.figure()
gs = gridspec.GridSpec(3, 1, height_ratios=[5, 2, 1])
ax1 = fig.add_subplot(gs[0])
ax2 = fig.add_subplot(gs[1], sharex=ax1)
ax3 = fig.add_subplot(gs[2], sharex=ax2)
visible = True
def toggle_ax2(event):
global visible
visible = not visible
ax2.set_visible(visible)
plt.draw()
fig.canvas.mpl_connect('button_press_event', toggle_ax2)
plt.show()
You can define two different GridSpecs. One would have 3 subplots, the other 2. Depending on the visibility of the middle axes, you change the position of the other two axes to obey to the first or second GridSpec.
(There is no need for any dummy figure or so, like other answers might suggest.)
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure()
gs = gridspec.GridSpec(3, 1, height_ratios=[5, 2, 1], hspace=0.3)
gs2 = gridspec.GridSpec(2,1, height_ratios=[5,3])
ax1 = fig.add_subplot(gs[0])
ax2 = fig.add_subplot(gs[1], sharex=ax1)
ax3 = fig.add_subplot(gs[2], sharex=ax2)
ax1.plot([1,2,3], [1,2,3], color="crimson")
ax2.plot([1,2,3], [2,3,1], color="darkorange")
ax3.plot([1,2,3], [3,2,1], color="limegreen")
visible = True
def toggle_ax2(event):
global visible
visible = not visible
ax2.set_visible(visible)
if visible:
ax1.set_position(gs[0].get_position(fig))
ax3.set_position(gs[2].get_position(fig))
else:
ax1.set_position(gs2[0].get_position(fig))
ax3.set_position(gs2[1].get_position(fig))
plt.draw()
fig.canvas.mpl_connect('button_press_event', toggle_ax2)
plt.show()
Left: original; right: after clicking
You can create a new gridspec instance, and use that to create some dummy figures in a second figure (you can close this before you plt.show, so you never actually see it, we just want to grab some positions from the axes here).
By storing the two possible positions for ax1 and ax3 from that dummy figure and the original figure, then you can use ax.set_position() in your toggle_ax2 function to change the positions of the remaining two axes.
import matplotlib.pyplot as plt
from matplotlib import gridspec
fig = plt.figure()
gs = gridspec.GridSpec(3, 1, height_ratios=[5, 2, 1])
ax1 = fig.add_subplot(gs[0])
ax2 = fig.add_subplot(gs[1], sharex=ax1)
ax3 = fig.add_subplot(gs[2], sharex=ax2)
# Store the original positions of ax1 and ax3
pos1_1 = ax1.get_position()
pos3_1 = ax3.get_position()
# Create a second gridspec for when ax2 is hidden. Keep 5:1 ratio
gs2 = gridspec.GridSpec(2, 1, height_ratios=[5, 1])
fig2 = plt.figure()
ax1_2 = fig2.add_subplot(gs2[0])
ax3_2 = fig2.add_subplot(gs2[1])
# Store the positions of ax1 and ax3 in the new gridspec
pos1_2 = ax1_2.get_position()
pos3_2 = ax3_2.get_position()
# Close the dummy figure2
plt.close(fig2)
visible = True
def toggle_ax2(event):
global visible
visible = not visible
ax2.set_visible(visible)
# Use the stored positions to switch between
# different arrangements of ax1 and ax3
if visible:
ax1.set_position(pos1_1)
ax3.set_position(pos3_1)
else:
ax1.set_position(pos1_2)
ax3.set_position(pos3_2)
plt.draw()
fig.canvas.mpl_connect('button_press_event', toggle_ax2)
plt.show()
Original configuration:
After removing ax2:
I'm somewhat new to matplotlib. What I'm trying to do is write code that saves several figures to eps files, and then generates a composite figure. Basically what I'd like to do is have something like
import matplotlib.pyplot as plt
from matplotlib import gridspec
def my_plot_1():
fig = plt.figure()
...
return fig
def my_plot_2():
fig = plt.figure()
...
return fig
def my_combo_plot(fig1,fig2):
fig = plt.figure()
gs = gridspec.GridSpec(2,2)
ax1 = plt.subplot(gs[0,0])
ax2 = plt.subplot(gs[0,1])
ax1 COPY fig1
ax2 COPY fig2
...
where then later I could do something like
my_combo_plot( my_plot_1() , my_plot_2() )
and have all the data and settings get copied from the plots returned by the first two functions, but I can't figure out how this would be done with matplotlib.
Since pyplot kind of works like a state machine, I'm not sure if what you are asking for is possible. I would instead factor out the drawing code, something like this:
import matplotlib.pyplot as plt
def my_plot_1(ax=None):
if ax is None:
ax = plt.gca()
ax.plot([1, 2, 3], 'b-')
def my_plot_2(ax=None):
if ax is None:
ax = plt.gca()
ax.plot([3, 2, 1], 'ro')
def my_combo_plot():
ax1 = plt.subplot(1,2,1)
ax2 = plt.subplot(1,2,2)
my_plot_1(ax1)
my_plot_2(ax2)
Using the answer https://stackoverflow.com/a/46906599/5267751 it's possible to move the axes from one figure to other (using pickle it's also possible to keep the old figure).
Add set_subplotspec to position the resulting axes:
import matplotlib.pyplot as plt
from matplotlib import gridspec
def my_plot_1():
fig = plt.figure()
plt.plot([1, 2, 3], 'b-')
return fig
def my_plot_2():
fig = plt.figure()
plt.plot([3, 2, 1], 'ro')
return fig
fig1 = my_plot_1()
fig2 = my_plot_2()
def my_combo_plot(fig1,fig2):
fig = plt.figure()
gs = gridspec.GridSpec(2,2)
ax1 = fig1.axes[0]
ax1.remove()
ax1.figure = fig
fig.add_axes(ax1)
ax1.set_subplotspec(gs[0, 0])
ax2 = fig2.axes[0]
ax2.remove()
ax2.figure = fig
fig.add_axes(ax2)
ax2.set_subplotspec(gs[0, 1])
plt.close(fig1)
plt.close(fig2)
my_combo_plot( my_plot_1() , my_plot_2() )
plt.show()
The code assumes that each figure contains exactly one axes, however.