graficos graficas figurecanvastkagg python matplotlib tkinter

python - graficas - Cómo obtener una figura de Matplotlib para desplazarse+cambiar el tamaño correctamente en una GUI de Tkinter



matplotlib tkinter (2)

Tengo una GUI de Tkinter que muestra un gráfico de Matplotlib (Python 2.7.3 con Matplotlib 1.2.0rc2) y permite al usuario configurar ciertos aspectos de la gráfica. Las tramas tienden a agrandarse, por lo que la figura está envuelta en un lienzo de desplazamiento. Un aspecto de la configuración de la trama es cambiar su tamaño.

Ahora bien, mientras la trama se desplaza correctamente, por una parte, y el redimensionamiento también funciona en la otra, las dos operaciones no funcionan en combinación. A continuación hay una secuencia de comandos para demostrar el efecto. (Perdón por la longitud, no pude obtenerla más corta). Puede desplazarse por la gráfica (usando las barras de desplazamiento), y puede hacerse cada vez más pequeña (usando los botones). Sin embargo, cada vez que se desplaza, la figura se restablece a su tamaño original . Evidentemente, me gustaría que el tamaño de la figura no cambie con el uso de las barras de desplazamiento.

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

Creo que me falta algo acerca de cómo la trama y el lienzo de desplazamiento están unidos; He intentado canvas.create_window(...) configurar el lienzo de desplazamiento (con canvas.create_window(...) y canvas.config(...) ) después de cada llamada changeSize , pero eso no ayudó. Una de las alternativas que obtuve para trabajar fue regenerar toda la configuración (figura, lienzo, barras de desplazamiento) después de cada cambio de tamaño. (Sin embargo, aparte de parecer un poco brutal, tenía el problema de que no podía deshacerse de las figuras antiguas de forma adecuada, lo que hacía que el programa acumulara bastante memoria con el tiempo).

Entonces, ¿alguien tiene alguna idea sobre cómo lograr que esas barras de desplazamiento se comporten correctamente después de las operaciones de cambio de tamaño?


Acabo de tropezar con el mismo problema, y ​​por lo que puedo ver (por experimentación), más allá de figure.set_size_inches() , también debes establecer el nuevo tamaño de mplCanvas y de la ventana creada con canvas para él, antes de hacer figure.canvas.draw() (que a su vez también obliga a uno a usar variables globales o definiciones de clase). Además, no es necesario "reticular" mplCanvas parecer, ya que ya es un elemento secundario del canvas , que ya está "cuadriculado". Y probablemente desee anclar NW, por lo que en cada cambio de tamaño, la trama se vuelve a dibujar en 0,0 en la esquina superior izquierda.

Esto es lo que funcionó para mí (también probé con el marco "interno" como en la barra de desplazamiento de Python Tkinter para el marco , pero eso no funcionó, algo de eso queda al final del fragmento):

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

Una nota interesante es que mplCanvas obedecerá el tamaño si aumenta de tamaño (como al hacer clic en "más grande"), pero conserva el tamaño anterior si se reduce:

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

El mismo comportamiento de mplCanvas se puede ver en Python3.2, también ... no estoy seguro si esto es una especie de error, o yo tampoco estoy entendiendo algo bien :)

Tenga en cuenta también que este escalado de esta forma no maneja el cambio de tamaño de fuentes de ejes / tics, etc. (las fuentes intentarán mantener el mismo tamaño); esto es lo que eventualmente puedo obtener con el código anterior (tics truncados):

... y es aún peor si agrega etiquetas de ejes, etc.

De todos modos, espero que esto ayude,
¡Aclamaciones!


Derecha; después de la discusión de la barra de desplazamiento en esta respuesta , terminé pasando por esto:

.. y creo que logré obtener una especie de código de escala que también escala (algo) las etiquetas y el relleno, por lo que (aproximadamente) toda la trama encaja dentro (nota, la segunda imagen usa la escala "media" de imgur):

Para tamaños muy pequeños, las etiquetas comienzan a desaparecer de nuevo, pero aún así es válida para una variedad de tamaños.

Tenga en cuenta que para matplotlib más matplotlib (> = 1.1.1), hay una función figure.tight_layout() que hace los márgenes (pero no el tamaño de fuente) para casos como este (es una subparcela única), pero si está usando un matplotlib más matplotlib , puedes hacer figure.subplots_adjust(left=0.2, bottom=0.15, top=0.86) que es lo que hace este ejemplo; y ha sido probado en:

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

( tight_layout ver si puedo copiar tight_layout para tight_layout antiguo; desafortunadamente, requiere un conjunto bastante complejo de funciones incluidas de tight_layout.py , que a su vez requiere que Figure y Axes también tengan especificaciones específicas, no presentes en v.0.99 )

Como subplots_adjust toma parámetros relativos (de 0.0 a 1.0), en principio podemos simplemente configurarlos una vez, y esperamos que sean válidos para nuestro rango de escala deseado. Para el resto (escalado de fuentes y etiqueta), consulte el siguiente código:

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