matplotlib + wxpython 没有用图例正确调整大小

Posted

技术标签:

【中文标题】matplotlib + wxpython 没有用图例正确调整大小【英文标题】:matplotlib + wxpython not sizing correctly with legend 【发布时间】:2013-09-17 04:05:45 【问题描述】:

我在 wxpython 框架中嵌入了一个 matplotlib 图形,并带有一些大小调整器。一切正常,直到我包含一个图例,但是尺寸测量器似乎没有与图例一起工作。

即使我通过拖动角落来调整窗口大小,主图也会改变大小,但只会显示图例的边缘。

也就是说,注意图例在 wxFrame 中是不可见的。

import wx
import matplotlib as mpl
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as Canvas
from random import shuffle  

class PlotFrame(wx.Frame):

    def __init__(self):     

        wx.Frame.__init__(self, None, -1, title="Plot", size=(-1, -1))
        self.main_panel = wx.Panel(self, -1)
        self.plot_panel = PlotPanel(self.main_panel)

        s0 = wx.BoxSizer(wx.VERTICAL)
        s0.Add(self.main_panel, 1, wx.EXPAND)
        self.SetSizer(s0)
        self.s0 = s0

        self.main_sizer = wx.BoxSizer(wx.VERTICAL)        
        self.main_sizer.Add(self.plot_panel, 1, wx.EXPAND)        
        self.main_panel.SetSizer(self.main_sizer)       

class PlotPanel(wx.Panel):

    def __init__(self, parent, id = -1, dpi = None, **kwargs):
        wx.Panel.__init__(self, parent, id=id, **kwargs)
        self.figure = mpl.figure.Figure(dpi=dpi, figsize=(2,2))
        self.canvas = Canvas(self, -1, self.figure)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.canvas,1,wx.EXPAND)
        self.SetSizer(sizer)
        sizer.SetMinSize((600, 500))
        self.sizer = sizer

def test(plot_panel):
    axes = plot_panel.figure.gca()
    for c in ['r', 'b', 'k']:
        vals = [20, 30, 40, 50, 80, 20, 50, 60, 70, 70, 80]
        shuffle(vals)
        axes.plot(range(len(vals)), vals, "-o", color=c, label=c*10)
    legend = axes.legend(loc='center left', bbox_to_anchor=(1.05, 0.5))
    return legend

if __name__=="__main__":
    app = wx.PySimpleApp()
    frame = PlotFrame()
    legend = test(frame.plot_panel)
    frame.Fit()
    print "legend frame pre show: ", legend.get_frame()
    frame.Show(True)
    print "legend frame post show:", legend.get_frame()
    frame.Fit()
    app.MainLoop()

编辑: 对于对我有用的解决方案,我希望它在程序自动绘制图形时看起来不错,因此可以在程序中硬编码调整参数,或者例如在窗口调整大小事件上,但不是为每个地块手动调整。我希望在这里更改的主要内容是:1) 标签的长度(例如,1 到 25 个字符),2) 窗口大小(通常由用户拖动拐角处,以及3)点和线的数量。 (另外,如果重要的话,我最终希望在底轴上有日期。)

我已将图例放在坐标区之外,这样它就不会覆盖任何数据点,而且我希望它留在坐标区的右侧。

我正在使用 Python 2.6.6、wxPython 2.8.12.1 和 matplotlib 1.1.0,现在我坚持使用这些。

【问题讨论】:

Fit 是一个 wx 级别的函数,它将调整 figure 的大小以适合您的窗口,图例是 figure 小部件的 part of。绘制后边界框的大小会发生变化,因为文本的大小在绘制图形之前不会被排序。 我知道你说的。还是没有解决问题。 您希望 a) 图例不在 axes 和 b) 能够看到图例。使用右侧的图例做到这一点的唯一方法是让您的轴以 figure fraction 为单位占用更少的空间。然后,您可以通过调整窗口大小来扩大您的身材。 似乎发生的事情要么是一个错误,要么是我做错了什么,但直观地说,wx 使用的图形/画布的大小也应该包括图例,即使图例位于轴之外。还是我错了?我希望有人能告诉我如何正确使用总大小,否则我将自己将其写入sizer。这在代码方面有点难看,但我认为它会比手动更改大小(例如,使用图形分数单位)得到一个更好看的图形。 这可能是缺少功能,但不是错误。 【参考方案1】:

它的大小调整正确,只是你没有告诉它做你想做的事。

问题出在这一行:

axes.legend(loc='center left', bbox_to_anchor=(1.05, 0.5))

很确定bbox_to_anchor kwarg 覆盖了loc kwarg,并且您将图例的左下角与轴单位的 (1.05, 0.5) 挂钩。如果轴扩展以填满您的窗口,则图例的左边缘将始终是轴右边缘右侧宽度轴的 5%,因此始终不在视野范围内。

您要么需要将您的图例放在其他地方,要么缩小您的坐标轴(以数字分数)。

选项1移动图例:

axes.legend(bbox_to_anchor=(0.5, 0.5)) #find a better place this is in the center

选项 2 移动轴 + 调整图形大小:

axes.set_position([.1, .1, .5, .8]) # units are in figure fraction

set_position

fig = figure()
axes = fig.add_subplot(111)

for c in ['r', 'b', 'k']:
    vals = [20, 30, 40, 50, 80, 20, 50, 60, 70, 70, 80]
    shuffle(vals)
    axes.plot(range(len(vals)), vals, "-o", color=c, label=c*10)

legend = axes.legend(loc='center left', bbox_to_anchor=(1.05, 0.5))

# adjust the figure size (in inches)
fig.set_size_inches(fig.get_size_inches() * np.array([1.5, 1]), forward=True)

# and the axes size (in figure fraction)
# to (more-or-less) preserve the aspect ratio of the original axes
# and show the legend
pos = np.array(axes.get_position().bounds)
pos[2] = .66
axes.set_position(pos)

选项 3:自动化选项 2

fig = figure() # use plt to set this up for demo purposes
axes = fig.add_subplot(111) # add a subplot

# control paramters 
left_pad = .05 
right_pad = .05

# plot data
for c in ['r', 'b', 'k']:
    vals = [20, 30, 40, 50, 80, 20, 50, 60, 70, 70, 80]
    shuffle(vals)
    axes.plot(range(len(vals)), vals, "-o", color=c, label=c*10)
# set axes labels
axes.set_xlabel('test x')
axes.set_ylabel('test y')

# make the legend
legend = axes.legend(loc='center left', bbox_to_anchor=(1 + left_pad, 0.5))

# function to 'squeeze' the legend into place
def squeeze_legend(event):
    fig.tight_layout()
    right_frac = 1 - legend.get_window_extent().width / fig.get_window_extent().width - left_pad - right_pad
    fig.subplots_adjust(right=right_frac)
    fig.canvas.draw()

# call it so the first draw is right
squeeze_legend()

# use the resize event call-back to make sure it works even if the window is re-sized
fig.canvas.mpl_connect('resize_event', squeeze_legend)

【讨论】:

好的,我看到了你的编辑,但这不是我要找的。相反,我想要一个很好的方法来让情节很好地适应框架,而图例在主要人物之外。不过感谢您的想法。 matplotlib 只能绘制到它的画布上,它的画布是figure 小部件的大小。请注意命名法,figure 是 wiget,axes 是您可以在其上画线的白框。您可以将图例放在axes 之外,但不能将anything 放在figure 之外 感谢您的意见,但这对我不起作用。我需要一个解决方案,它可以在不手动调整每个图的情况下工作,并且可以通过拖动来调整大小、具有不同长度的标签等,并且仍然看起来不错。 好吧,你不能使用不存在的功能(据我所知,我会说我对此有 80% 的把握)。看看tight_layout 在下面是如何工作的。您可以询问艺术家(坐标轴和图例)它们在渲染图形上的大小,然后以编程方式重新调整它们的大小。 另一种方法是查看DraggableLegend matplotlib.org/api/… ***.com/questions/2539477/…

以上是关于matplotlib + wxpython 没有用图例正确调整大小的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 matplotlib blitting 将 matplot.patches 添加到 wxPython 中的 matplotlib 图?

用wxpython制作可以用于 特征筛选gui程序

用matplotlib画图,但是显示不出来是怎么回事

matplotlib中什么是后端

防止 wxPython 显示“未处理的异常”对话框

matplotlib 绘图在谷歌 colab 中不起作用