【问题标题】:Dynamically add subplots in matplotlib with more than one column在 matplotlib 中动态添加多于一列的子图
【发布时间】:2015-07-22 22:54:08
【问题描述】:

如果我使用多个列来显示我的子图,如何动态地将新图添加到一堆子图中? This 为一列回答了这个问题,但我似乎无法修改那里的答案以使其动态添加到带有 x 列的子图中

我修改了Sadarthrion's answer 并尝试了以下操作。在这里,为了举例,我制作了number_of_subplots=11num_cols = 3

import matplotlib.pyplot as plt

def plotSubplots(number_of_subplots,num_cols):
    # Start with one
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.plot([1,2,3])

    for j in range(number_of_subplots):
        if j > 0: 
            # Now later you get a new subplot; change the geometry of the existing
            n = len(fig.axes)
            for i in range(n):
                fig.axes[i].change_geometry(n+1, num_cols, i+1)

            # Add the new
            ax = fig.add_subplot(n+1, 1, n+1)
            ax.plot([4,5,6])

            plt.show() 

   plotSubplots(11,3) 

如您所见,这并没有给我想要的东西。第一个图占据了所有列,其他图小于它们应有的值

编辑:

('2.7.6 | 64-bit | (default, Sep 15 2014, 17:36:35) [MSC v.1500 64 bit (AMD64)]'

我还有 matplotlib 1.4.3 版:

import matplotlib as mpl
print mpl.__version__
1.4.3

我在下面尝试了 Paul 的回答并收到以下错误消息:

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 = 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() 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-1-f74203b1c1bf> in <module>()
     15 fig = plt.figure()
     16 for n in range(N):
---> 17     ax = fig.add_subplot(gs[n])
     18     do_plot(ax)
     19 

C:\Users\user\AppData\Local\Enthought\Canopy\User\lib\site-packages\matplotlib\figure.pyc in add_subplot(self, *args, **kwargs)
    962                     self._axstack.remove(ax)
    963 
--> 964             a = subplot_class_factory(projection_class)(self, *args, **kwargs)
    965 
    966         self._axstack.add(key, a)

C:\Users\user\AppData\Local\Enthought\Canopy\User\lib\site-packages\matplotlib\axes\_subplots.pyc in __init__(self, fig, *args, **kwargs)
     73             raise ValueError('Illegal argument(s) to subplot: %s' % (args,))
     74 
---> 75         self.update_params()
     76 
     77         # _axes_class is set in the subplot_class_factory

C:\Users\user\AppData\Local\Enthought\Canopy\User\lib\site-packages\matplotlib\axes\_subplots.pyc in update_params(self)
    113         self.figbox, self.rowNum, self.colNum, self.numRows, self.numCols =     114             self.get_subplotspec().get_position(self.figure,
--> 115                                                 return_all=True)
    116 
    117     def is_first_col(self):

C:\Users\user\AppData\Local\Enthought\Canopy\User\lib\site-packages\matplotlib\gridspec.pyc in get_position(self, fig, return_all)
    423 
    424         figBottoms, figTops, figLefts, figRights = --> 425                     gridspec.get_grid_positions(fig)
    426 
    427 

C:\Users\user\AppData\Local\Enthought\Canopy\User\lib\site-packages\matplotlib\gridspec.pyc in get_grid_positions(self, fig)
    103             cellHeights = [netHeight*r/tr for r in self._row_height_ratios]
    104         else:
--> 105             cellHeights = [cellH] * nrows
    106 
    107         sepHeights = [0] + ([sepH] * (nrows-1))

TypeError: can't multiply sequence by non-int of type 'float' 

【问题讨论】:

  • 在这种情况下正确的输出应该是什么样的?
  • 哦,对不起,我认为这很明显。我只想平铺所有地块并尽可能多地占用窗口空间。所以在上面的例子中,我希望出现在底部的图出现在第二列中。最后一组子图应显示为 4x3(4 行,3 列)网格,最后有一个空白区域(因为我们只有 11 个图)
  • 你知道先验的列数和总图数吗?
  • 图的总数是从一个全局且恒定的列表的长度得知的,但其长度可能因初始化而不同。列数由用户控制。这有什么改变吗?两者都是变量。我修改了问题。我觉得现在更清楚了
  • 看我的回答。我担心关于列数或绘图总数的知识来自比如说,一个可能在绘图中途改变的 GUI 事件。那时事情会复杂得多。

标签: python matplotlib


【解决方案1】:

假设您的网格至少有 1 个维度并且图的总数是已知的,我会使用 gridspec 模块和一些数学知识。

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

【讨论】:

  • 当我复制粘贴代码以考虑树冠时出现以下错误:TypeError: can't multiply sequence by non-int of type 'float' 。 “ax = fig.add_subplot(gs[n])”发生错误 天真地将其更改为“ax = fig.add_subplot(int(gs[n]))”会导致另一个错误:TypeError: int() argument must be a字符串或数字,而不是 'SubplotSpec'
  • 哈哈,这是 Python 2 vs 3(我认为)。我会尽快修复。
  • 我应该使用 Python 3,谢谢!
  • @DirkHaupt 哦等等,我误读了错误。您使用的是哪个版本的 MPL?
  • 1.4.2(我花了一段时间才发现添加评论按钮没有损坏,我需要插入更多字符)
【解决方案2】:

这是我最终得到的解决方案。它允许您按名称引用子图,如果该名称尚未使用,则添加一个新的子图,重新定位该过程中所有以前的子图。

用法:

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

代码:

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

【讨论】:

    【解决方案3】:
    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()
    

    正在做一些 PyQt5 工作,但 add 方法显示了如何动态添加新的子图。 Axes 的 set_position 方法用于将旧位置更改为新位置。然后,您添加一个具有新位置的新子图。

    【讨论】:

      【解决方案4】:

      我编写了一个函数,可以自动将所有子图格式化为紧凑的方形。

      为了摆脱 Paul H 的回答,我们可以使用 gridspec 动态地将子图添加到图形中。不过,我后来做了改进。我的代码会自动排列子图,使整个图形紧凑且呈方形。这可确保子图中的行数和列数大致相同。

      列数等于n_plots 的平方根四舍五入,然后创建足够的行,以便所有子图都有足够的点。

      检查一下:

      import numpy as np
      import matplotlib.pyplot as plt
      from matplotlib import gridspec
      
      def arrange_subplots(xs, ys, n_plots):
        """
        ---- Parameters ----
        xs (n_plots, d): list with n_plot different lists of x values that we wish to plot
        ys (n_plots, d): list with n_plot different lists of y values that we wish to plot
        n_plots (int): the number of desired subplots
        """
      
        # compute the number of rows and columns
        n_cols = int(np.sqrt(n_plots))
        n_rows = int(np.ceil(n_plots / n_cols))
      
        # setup the plot
        gs = gridspec.GridSpec(n_rows, n_cols)
        scale = max(n_cols, n_rows)
        fig = plt.figure(figsize=(5 * scale, 5 * scale))
      
        # loop through each subplot and plot values there
        for i in range(n_plots):
          ax = fig.add_subplot(gs[i])
          ax.plot(xs[i], ys[i])
      

      这里有几个示例图片可供比较

      n_plots = 5

      n_plots = 6

      n_plots = 10

      n_plots = 15

      【讨论】:

        猜你喜欢
        • 2012-09-01
        • 2017-07-20
        • 2021-12-02
        • 1970-01-01
        • 2023-03-08
        • 2013-04-22
        • 1970-01-01
        • 2016-04-28
        • 1970-01-01
        相关资源
        最近更新 更多