如何让 Matplotlib 图形在 Tkinter GUI 中正确滚动 + 调整大小

Han*_*ans 5 python tkinter matplotlib

我有一个 Tkinter GUI,它显示一个 Matplotlib 图(Python 2.7.3 with 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()
Run Code Online (Sandbox Code Playgroud)

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

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

sda*_*aau 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
Run Code Online (Sandbox Code Playgroud)

我确实尝试过查看是否可以复制tight_layout旧的 matplotlib - 不幸的是,它需要包含来自tiny_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()
Run Code Online (Sandbox Code Playgroud)