How can I dynamically add a new plot to bunch of subplots if I\'m using more than one column to display my subplots? This answers this question for one column, but I cant se
Here's the solution I ended up with. It lets you reference subplots by name, and adds a new subplot if that name has not been used yet, repositioning all previous subplots in the process.
Usage:
set_named_subplot('plot-a') # Create a new plot
plt.plot(np.sin(np.linspace(0, 10, 100))) # Plot a curve
set_named_subplot('plot-b') # Create a new plot
plt.imshow(np.random.randn(10, 10)) # Draw image
set_named_subplot('plot-a') # Set the first plot as the current one
plt.plot(np.cos(np.linspace(0, 10, 100))) # Plot another curve in the first plot
plt.show() # Will show two plots
The code:
import matplotlib.pyplot as plt
import numpy as np
def add_subplot(fig = None, layout = 'grid'):
"""
Add a subplot, and adjust the positions of the other subplots appropriately.
Lifted from this answer: http://stackoverflow.com/a/29962074/851699
:param fig: The figure, or None to select current figure
:param layout: 'h' for horizontal layout, 'v' for vertical layout, 'g' for approximately-square grid
:return: A new axes object
"""
if fig is None:
fig = plt.gcf()
n = len(fig.axes)
n_rows, n_cols = (1, n+1) if layout in ('h', 'horizontal') else (n+1, 1) if layout in ('v', 'vertical') else \
vector_length_to_tile_dims(n+1) if layout in ('g', 'grid') else bad_value(layout)
for i in range(n):
fig.axes[i].change_geometry(n_rows, n_cols, i+1)
ax = fig.add_subplot(n_rows, n_cols, n+1)
return ax
_subplots = {}
def set_named_subplot(name, fig=None, layout='grid'):
"""
Set the current axes. If "name" has been defined, just return that axes, otherwise make a new one.
:param name: The name of the subplot
:param fig: The figure, or None to select current figure
:param layout: 'h' for horizontal layout, 'v' for vertical layout, 'g' for approximately-square grid
:return: An axes object
"""
if name in _subplots:
plt.subplot(_subplots[name])
else:
_subplots[name] = add_subplot(fig=fig, layout=layout)
return _subplots[name]
def vector_length_to_tile_dims(vector_length):
"""
You have vector_length tiles to put in a 2-D grid. Find the size
of the grid that best matches the desired aspect ratio.
TODO: Actually do this with aspect ratio
:param vector_length:
:param desired_aspect_ratio:
:return: n_rows, n_cols
"""
n_cols = np.ceil(np.sqrt(vector_length))
n_rows = np.ceil(vector_length/n_cols)
grid_shape = int(n_rows), int(n_cols)
return grid_shape
def bad_value(value, explanation = None):
"""
:param value: Raise ValueError. Useful when doing conditional assignment.
e.g.
dutch_hand = 'links' if eng_hand=='left' else 'rechts' if eng_hand=='right' else bad_value(eng_hand)
"""
raise ValueError('Bad Value: %s%s' % (value, ': '+explanation if explanation is not None else ''))
Assuming at least 1 dimension of your grid and the total number of plots is known, I would use the gridspec
module and a little bit of math.
import math
import matplotlib.pyplot as plt
from matplotlib import gridspec
def do_plot(ax):
ax.plot([1,2,3], [4,5,6], 'k.')
N = 11
cols = 3
rows = int(math.ceil(N / cols))
gs = gridspec.GridSpec(rows, cols)
fig = plt.figure()
for n in range(N):
ax = fig.add_subplot(gs[n])
do_plot(ax)
fig.tight_layout()
from math import ceil
from PyQt5 import QtWidgets, QtCore
from matplotlib.gridspec import GridSpec
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
class MplCanvas(FigureCanvas):
"""
Frontend class. This is the FigureCanvas as well as plotting functionality.
Plotting use pyqt5.
"""
def __init__(self, parent=None):
self.figure = Figure()
gs = GridSpec(1,1)
self.figure.add_subplot(gs[0])
self.axes = self.figure.axes
super().__init__(self.figure)
self.canvas = self.figure.canvas
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.updateGeometry()
self.setParent(parent)
def add(self, cols=2):
N = len(self.axes) + 1
rows = int(ceil(N / cols))
grid = GridSpec(rows, cols)
for gs, ax in zip(grid, self.axes):
ax.set_position(gs.get_position(self.figure))
self.figure.add_subplot(grid[N-1])
self.axes = self.figure.axes
self.canvas.draw()
Was doing some PyQt5 work, but the add method shows how to dynamically add a new subplot. The set_position method of Axes is used to change the old position to new position. Then you add a new subplot with the new position.