5

我有一个 Tkinter GUI,它显示了一个 Matplotlib 图(Python 2.7.3 和 Matplotlib 1.2.0rc2)并允许用户配置图的某些方面。绘图往往会变大,因此该图形被包裹在滚动画布中。配置绘图的一个方面是更改其大小。

现在,一方面情节可以正确滚动,另一方面调整大小也可以正常工作,但这两个操作不能结合使用。下面是一个演示效果的脚本。(对不起,我不能让它更短。)您可以滚动绘图(使用滚动条),它可以变得越来越小(使用按钮)。但是,无论何时滚动,图形都会重置为其原始大小。显然,我希望图形的大小不会因使用滚动条而改变。

import math
from Tkinter import Tk, Button, Frame, Canvas, Scrollbar
import Tkconstants
from matplotlib import pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

def addScrollingFigure(figure, frame):

    # set up a canvas with scrollbars
    canvas = Canvas(frame)
    canvas.grid(row=0, column=0, sticky=Tkconstants.NSEW)

    xScrollbar = Scrollbar(frame, orient=Tkconstants.HORIZONTAL)
    yScrollbar = Scrollbar(frame)

    xScrollbar.grid(row=1, column=0, sticky=Tkconstants.EW)
    yScrollbar.grid(row=0, column=1, sticky=Tkconstants.NS)

    canvas.config(xscrollcommand=xScrollbar.set)
    xScrollbar.config(command=canvas.xview)
    canvas.config(yscrollcommand=yScrollbar.set)
    yScrollbar.config(command=canvas.yview)

    # plug in the figure
    figAgg = FigureCanvasTkAgg(figure, canvas)
    mplCanvas = figAgg.get_tk_widget()
    mplCanvas.grid(sticky=Tkconstants.NSEW)

    # and connect figure with scrolling region
    canvas.create_window(0, 0, window=mplCanvas)
    canvas.config(scrollregion=canvas.bbox(Tkconstants.ALL))

def changeSize(figure, factor):
    oldSize = figure.get_size_inches()
    print "old size is", oldSize
    figure.set_size_inches([factor * s for s in oldSize])
    print "new size is", figure.get_size_inches()
    print
    figure.canvas.draw()

if __name__ == "__main__":

    root = Tk()
    root.rowconfigure(0, weight=1)
    root.columnconfigure(0, weight=1)

    frame = Frame(root)
    frame.grid(column=0, row=0, sticky=Tkconstants.NSEW)
    frame.rowconfigure(0, weight=1)
    frame.columnconfigure(0, weight=1)

    figure = plt.figure(dpi=150, figsize=(4, 4))
    plt.plot(xrange(10), [math.sin(x) for x in xrange(10)])

    addScrollingFigure(figure, frame)

    buttonFrame = Frame(root)
    buttonFrame.grid(row=0, column=1, sticky=Tkconstants.NS)
    biggerButton = Button(buttonFrame, text="larger",
                          command=lambda : changeSize(figure, 1.5))
    biggerButton.grid(column=0, row=0)
    smallerButton = Button(buttonFrame, text="smaller",
                           command=lambda : changeSize(figure, .5))
    smallerButton.grid(column=0, row=1)

    root.mainloop()

我想我错过了一些关于情节和滚动画布是如何联系在一起的东西;我尝试在每次通话后重新配置滚动画布(使用canvas.create_window(...)and ) ,但这没有帮助。我开始工作的另一种方法是在每次调整大小后重新生成整个设置(图形、画布、滚动条)。(然而,除了看起来有点残酷之外,还有一个问题是我无法正确处理旧数据,使得程序随着时间的推移积累了相当多的内存。)canvas.config(...)changeSize

那么,是否有人对调整大小操作后如何让这些滚动条正常运行有任何想法?

4

2 回答 2

5

正确的; 在这个答案中的滚动条讨论之后,我最终经历了这个:

..而且我想我设法得到了一种缩放代码,它也缩放(有点)标签和填充,所以(大约)整个情节适合里面(注意,第二张图片使用来自 imgur 的“中等”比例):

最小的 中等的 大的

对于非常小的尺寸,标签再次开始消失 - 但它仍然适用于一系列尺寸。

请注意,对于较新的matplotlib(> = 1.1.1),有一个函数figure.tight_layout()可以为这种情况(它是一个子图)执行边距(但不是字体大小) - 但如果您使用的是较旧的matplotlib,您可以做figure.subplots_adjust(left=0.2, bottom=0.15, top=0.86)这就是这个例子所做的;并且已经过测试:

$ python2.7 -c 'import matplotlib; print(matplotlib.__version__)'
0.99.3
$ python3.2 -c 'import matplotlib; print(matplotlib.__version__)'
1.2.0

我确实尝试过看看是否可以复制tight_layout旧的 matplotlib - 不幸的是,它需要一组相当复杂的函数,这些函数包含在tight_layout.py中,这反过来又要求 Figure 和 Axes 也有特定的规范,在 v.0.99 中不存在

由于subplots_adjust采用相对参数(从 0.0 到 1.0),原则上我们可以只设置一次 - 并希望它们在我们想要的比例范围内保持不变。对于其余的(字体和标签板的缩放),请参见下面的代码:

import math
import sys
if sys.version_info[0] < 3:
  from Tkinter import Tk, Button, Frame, Canvas, Scrollbar
  import Tkconstants
else:
  from tkinter import Tk, Button, Frame, Canvas, Scrollbar
  import tkinter.constants as Tkconstants

import matplotlib
from matplotlib import pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import pprint, inspect

frame = None
canvas = None
ax = None

def printBboxes(label=""):
  global canvas, mplCanvas, interior, interior_id, cwid, figure
  print("  "+label,
    "canvas.bbox:", canvas.bbox(Tkconstants.ALL),
    "mplCanvas.bbox:", mplCanvas.bbox(Tkconstants.ALL),
    "subplotpars:", figure.subplotpars.__dict__ )

def addScrollingFigure(figure, frame):
  global canvas, mplCanvas, interior, interior_id, cwid
  # set up a canvas with scrollbars
  canvas = Canvas(frame)
  canvas.grid(row=1, column=1, sticky=Tkconstants.NSEW)

  xScrollbar = Scrollbar(frame, orient=Tkconstants.HORIZONTAL)
  yScrollbar = Scrollbar(frame)

  xScrollbar.grid(row=2, column=1, sticky=Tkconstants.EW)
  yScrollbar.grid(row=1, column=2, sticky=Tkconstants.NS)

  canvas.config(xscrollcommand=xScrollbar.set)
  xScrollbar.config(command=canvas.xview)
  canvas.config(yscrollcommand=yScrollbar.set)
  yScrollbar.config(command=canvas.yview)

  # plug in the figure
  figAgg = FigureCanvasTkAgg(figure, canvas)
  mplCanvas = figAgg.get_tk_widget()

  # and connect figure with scrolling region
  cwid = canvas.create_window(0, 0, window=mplCanvas, anchor=Tkconstants.NW)
  printBboxes("Init")
  changeSize(figure, 1)

def changeSize(figure, factor):
  global canvas, mplCanvas, interior, interior_id, frame, cwid
  oldSize = figure.get_size_inches()
  print("old size is", oldSize)
  figure.set_size_inches([factor * s for s in oldSize])
  wi,hi = [i*figure.dpi for i in figure.get_size_inches()]
  print("new size is", figure.get_size_inches())
  print("new size pixels: ", wi,hi)
  mplCanvas.config(width=wi, height=hi) ; printBboxes("A")
  canvas.itemconfigure(cwid, width=wi, height=hi) ; printBboxes("B")
  canvas.config(scrollregion=canvas.bbox(Tkconstants.ALL),width=200,height=200)
  tz.set_fontsize(tz.get_fontsize()*factor)
  for item in ([ax.title, ax.xaxis.label, ax.yaxis.label] +
               ax.get_xticklabels() + ax.get_yticklabels()):
    item.set_fontsize(item.get_fontsize()*factor)
  ax.xaxis.labelpad = ax.xaxis.labelpad*factor
  ax.yaxis.labelpad = ax.yaxis.labelpad*factor
  #figure.tight_layout() # matplotlib > 1.1.1
  figure.subplots_adjust(left=0.2, bottom=0.15, top=0.86)
  figure.canvas.draw() ; printBboxes("C")
  print()

if __name__ == "__main__":
  global root, figure
  root = Tk()
  root.rowconfigure(1, weight=1)
  root.columnconfigure(1, weight=1)

  frame = Frame(root)
  frame.grid(column=1, row=1, sticky=Tkconstants.NSEW)
  frame.rowconfigure(1, weight=1)
  frame.columnconfigure(1, weight=1)

  figure = plt.figure(dpi=150, figsize=(4, 4))
  ax = figure.add_subplot(111)
  ax.plot(range(10), [math.sin(x) for x in range(10)])
  #tz = figure.text(0.5,0.975,'The master title',horizontalalignment='center', verticalalignment='top')
  tz = figure.suptitle('The master title')

  ax.set_title('Tk embedding')
  ax.set_xlabel('X axis label')
  ax.set_ylabel('Y label')
  print(tz.get_fontsize()) # 12.0
  print(ax.title.get_fontsize(), ax.xaxis.label.get_fontsize(), ax.yaxis.label.get_fontsize()) # 14.4 12.0 12.0

  addScrollingFigure(figure, frame)

  buttonFrame = Frame(root)
  buttonFrame.grid(row=1, column=2, sticky=Tkconstants.NS)
  biggerButton = Button(buttonFrame, text="larger",
                        command=lambda : changeSize(figure, 1.2))
  biggerButton.grid(column=1, row=1)
  smallerButton = Button(buttonFrame, text="smaller",
                         command=lambda : changeSize(figure, 0.833))
  smallerButton.grid(column=1, row=2)
  qButton = Button(buttonFrame, text="quit",
                         command=lambda :  sys.exit(0))
  qButton.grid(column=1, row=3)

  root.mainloop()
于 2013-05-19T22:34:39.343 回答
3

我刚刚遇到了同样的问题-据我所知(通过实验),在做之前,您还必须为它设置画布创建的窗口figure.set_size_inches()的新大小(这也迫使一个人使用全局变量 - 或类定义)。此外,显然不需要“网格” - 因为它已经是 的子级,它已经是“网格”了。并且可能想要锚定 NW,因此在每次调整大小时,绘图都会在左上角的 0,0 处重新绘制。mplCanvasfigure.canvas.draw()mplCanvascanvas

这对我有用(我也尝试使用“内部”框架,如Python Tkinter 滚动条中的 frame,但这没有用;其中一些留在片段的末尾):

import math
import sys
if sys.version_info[0] < 3:
  from Tkinter import Tk, Button, Frame, Canvas, Scrollbar
  import Tkconstants
else:
  from tkinter import Tk, Button, Frame, Canvas, Scrollbar
  import tkinter.constants as Tkconstants

from matplotlib import pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import pprint

frame = None
canvas = None

def printBboxes(label=""):
  global canvas, mplCanvas, interior, interior_id, cwid
  print("  "+label,
    "canvas.bbox:", canvas.bbox(Tkconstants.ALL),
    "mplCanvas.bbox:", mplCanvas.bbox(Tkconstants.ALL))

def addScrollingFigure(figure, frame):
  global canvas, mplCanvas, interior, interior_id, cwid
  # set up a canvas with scrollbars
  canvas = Canvas(frame)
  canvas.grid(row=1, column=1, sticky=Tkconstants.NSEW)

  xScrollbar = Scrollbar(frame, orient=Tkconstants.HORIZONTAL)
  yScrollbar = Scrollbar(frame)

  xScrollbar.grid(row=2, column=1, sticky=Tkconstants.EW)
  yScrollbar.grid(row=1, column=2, sticky=Tkconstants.NS)

  canvas.config(xscrollcommand=xScrollbar.set)
  xScrollbar.config(command=canvas.xview)
  canvas.config(yscrollcommand=yScrollbar.set)
  yScrollbar.config(command=canvas.yview)

  # plug in the figure
  figAgg = FigureCanvasTkAgg(figure, canvas)
  mplCanvas = figAgg.get_tk_widget()
  #mplCanvas.grid(sticky=Tkconstants.NSEW)

  # and connect figure with scrolling region
  cwid = canvas.create_window(0, 0, window=mplCanvas, anchor=Tkconstants.NW)
  printBboxes("Init")
  canvas.config(scrollregion=canvas.bbox(Tkconstants.ALL),width=200,height=200)

def changeSize(figure, factor):
  global canvas, mplCanvas, interior, interior_id, frame, cwid
  oldSize = figure.get_size_inches()
  print("old size is", oldSize)
  figure.set_size_inches([factor * s for s in oldSize])
  wi,hi = [i*figure.dpi for i in figure.get_size_inches()]
  print("new size is", figure.get_size_inches())
  print("new size pixels: ", wi,hi)
  mplCanvas.config(width=wi, height=hi) ; printBboxes("A")
  #mplCanvas.grid(sticky=Tkconstants.NSEW)
  canvas.itemconfigure(cwid, width=wi, height=hi) ; printBboxes("B")
  canvas.config(scrollregion=canvas.bbox(Tkconstants.ALL),width=200,height=200)
  figure.canvas.draw() ; printBboxes("C")
  print()

if __name__ == "__main__":
  root = Tk()
  root.rowconfigure(1, weight=1)
  root.columnconfigure(1, weight=1)

  frame = Frame(root)
  frame.grid(column=1, row=1, sticky=Tkconstants.NSEW)
  frame.rowconfigure(1, weight=1)
  frame.columnconfigure(1, weight=1)

  figure = plt.figure(dpi=150, figsize=(4, 4))
  plt.plot(range(10), [math.sin(x) for x in range(10)])

  addScrollingFigure(figure, frame)

  buttonFrame = Frame(root)
  buttonFrame.grid(row=1, column=2, sticky=Tkconstants.NS)
  biggerButton = Button(buttonFrame, text="larger",
                        command=lambda : changeSize(figure, 1.5))
  biggerButton.grid(column=1, row=1)
  smallerButton = Button(buttonFrame, text="smaller",
                         command=lambda : changeSize(figure, .5))
  smallerButton.grid(column=1, row=2)

  root.mainloop()

"""
  interior = Frame(canvas) #Frame(mplCanvas) #cannot
  interior_id = canvas.create_window(0, 0, window=interior)#, anchor=Tkconstants.NW)
  canvas.config(scrollregion=canvas.bbox("all"),width=200,height=200)
  canvas.itemconfigure(interior_id, width=canvas.winfo_width())

  interior_id = canvas.create_window(0, 0, window=interior)#, anchor=Tkconstants.NW)
  canvas.config(scrollregion=canvas.bbox("all"),width=200,height=200)
  canvas.itemconfigure(interior_id, width=canvas.winfo_width())
"""

一个有趣的注意事项是,mplCanvas如果它变大(如单击“更大”),它将遵守大小 - 但如果它变小,则保持旧大小:

$ python2.7 test.py 
('  Init', 'canvas.bbox:', (0, 0, 610, 610), 'mplCanvas.bbox:', (0, 0, 600, 600))
## here click "larger":
('old size is', array([ 4.06666667,  4.06666667]))
('new size is', array([ 6.1,  6.1]))
('new size pixels: ', 915.0, 915.0)
('  A', 'canvas.bbox:', (0, 0, 925, 925), 'mplCanvas.bbox:', (0, 0, 926, 926))
('  B', 'canvas.bbox:', (0, 0, 915, 915), 'mplCanvas.bbox:', (0, 0, 926, 926))
('  C', 'canvas.bbox:', (0, 0, 915, 915), 'mplCanvas.bbox:', (0, 0, 926, 926))
()
## here click "larger":
('old size is', array([ 6.1,  6.1]))
('new size is', array([ 9.15,  9.15]))
('new size pixels: ', 1372.4999999999998, 1372.4999999999998)
('  A', 'canvas.bbox:', (0, 0, 915, 915), 'mplCanvas.bbox:', (0, 0, 926, 926))
('  B', 'canvas.bbox:', (0, 0, 1372, 1372), 'mplCanvas.bbox:', (0, 0, 926, 926))
('  C', 'canvas.bbox:', (0, 0, 1372, 1372), 'mplCanvas.bbox:', (0, 0, 1372, 1372))
()
## here click "smaller":
('old size is', array([ 9.14666667,  9.14666667]))
('new size is', array([ 4.57333333,  4.57333333]))
('new size pixels: ', 686.0, 686.0)
('  A', 'canvas.bbox:', (0, 0, 1372, 1372), 'mplCanvas.bbox:', (0, 0, 1372, 1372))
('  B', 'canvas.bbox:', (0, 0, 686, 686), 'mplCanvas.bbox:', (0, 0, 1372, 1372))
('  C', 'canvas.bbox:', (0, 0, 686, 686), 'mplCanvas.bbox:', (0, 0, 1372, 1372))
()

在 Python3.2中也可以看到相同的行为mplCanvas......不确定这是否是某种错误,或者我也没有理解正确的东西:)

另请注意,以这种方式进行的缩放不能处理轴/抽动等字体的大小调整(字体将尝试保持相同的大小);这就是我最终可以用上面的代码得到的(截断的抽动):

代码截图

...如果添加轴标签等,情况会变得更糟。

无论如何,希望这会有所帮助,
干杯!

于 2013-05-19T11:20:55.930 回答