¿Cómo uso un botón para cambiar entre dos gráficos diferentes en matplotlib?

Tengo dos gráficos de matplotlib diferentes que me gustaría cambiar al presionar un botón. El código que tengo agregará el segundo gráfico debajo del primer gráfico cuando se presione el botón, pero quiero que reemplace el primer gráfico. Esta es una pregunta similar a la de stackoverflow ( ¿Cómo actualizar un matplotlib incrustado en tkinter? ) Pero parece que no logro que esto se aplique a mi situación.

Los gráficos que he codificado, graph_one y graph_two, son dos gráficos simples que saqué de la documentación de matplotlib. En mi uso real tengo dos gráficos mucho más complicados que son muy diferentes, uno es un solo gráfico y el otro tiene un argumento secundario adicional. Debido a que los gráficos que deseo alternar entre ellos son tan diferentes, es importante para mí que la solución pueda manejar las entradas del gráfico como definiciones separadas. También se debe tener en cuenta que mis gráficos están integrados en un widget tkinter y es importante que la solución también tenga en cuenta esta integración. Aquí está el código que tengo:

import matplotlib matplotlib.use("TkAgg") import matplotlib.pyplot as plt import numpy as np from tkinter import * from matplotlib.backends.backend_tkagg import ( FigureCanvasTkAgg, NavigationToolbar2Tk) # Implement the default Matplotlib key bindings. from matplotlib.backend_bases import key_press_handler def graph_one(): t = np.arange(0.0, 2.0, 0.01) s = 1 + np.sin(2 * np.pi * t) fig, ax = plt.subplots() ax.plot(t, s) ax.set(xlabel='time (s)', ylabel='voltage (mV)', title='Graph One') #plt.show() return fig def graph_two(): t = np.arange(0.0, 2.0, 0.01) s = 1 + np.cos(2 * np.pi * t) fig, ax = plt.subplots() ax.plot(t, s) ax.set(xlabel='time (s)', ylabel='voltage (mV)', title='Graph Two') #plt.show() return fig class matplotlibSwitchGraphs: def __init__(self, master): self.master = master self.frame = Frame(self.master) self.embed_graph_one() self.frame.pack(expand=YES, fill=BOTH) def embed_graph_one(self): fig = graph_one() canvas = FigureCanvasTkAgg(fig, self.master) canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1) canvas.draw() canvas.mpl_connect("key_press_event", self.on_key_press) toolbar = NavigationToolbar2Tk(canvas, self.master) toolbar.update() canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1) self.button = Button(self.master, text="Quit", command=self._quit) self.button.pack(side=BOTTOM) self.button_switch = Button(self.master, text="Switch Graphs", command=self.switch_graphs) self.button_switch.pack(side=BOTTOM) def embed_graph_two(self): fig = graph_two() canvas = FigureCanvasTkAgg(fig, self.master) canvas.draw() canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1) canvas.mpl_connect("key_press_event", self.on_key_press) toolbar = NavigationToolbar2Tk(canvas, self.master) toolbar.update() canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1) self.button = Button(self.master, text="Quit", command=self._quit) self.button.pack(side=BOTTOM) self.button_switch = Button(self.master, text="Switch Graphs", command=self.switch_graphs) self.button_switch.pack(side=BOTTOM) def on_key_press(event): print("you pressed {}".format(event.key)) key_press_handler(event, canvas, toolbar) def _quit(self): self.master.quit() # stops mainloop def switch_graphs(self): self.embed_graph_two() def main(): root = Tk() matplotlibSwitchGraphs(root) root.mainloop() if __name__ == '__main__': main() 

Parece que debería poder usar un comando como

 ax.clear() 

en la definición de switch_graphs para borrar el primer gráfico, pero eso no funciona. Cualquier ayuda sería apreciada.

Estoy publicando un código actualizado para mostrar algunos pequeños avances que he realizado, así como para representar mejor la naturaleza diferente de los dos gráficos que deseo cambiar. Ambos gráficos siguen siendo gráficos simples tomados directamente de la documentación de matplotlib, pero representan mejor que uno de mis gráficos es un solo gráfico, mientras que el segundo gráfico tiene dos gráficos ubicados directamente uno encima del otro.

En mi caso real, estoy tratando de usar un botón para poder cambiar entre un gráfico de velas con una superposición de volumen y una sin la superposición de volumen. La publicación de todo el código para mostrar los gráficos de velas generaría un código muy largo, así que lo simplifiqué utilizando estos gráficos más simples. También he eliminado la barra de herramientas de navegación de matplotlib por simplicidad. Aquí está mi código revisado:

 import matplotlib matplotlib.use("TkAgg") import matplotlib.pyplot as plt import numpy as np from tkinter import * from matplotlib.backends.backend_tkagg import ( FigureCanvasTkAgg, NavigationToolbar2Tk) # Implement the default Matplotlib key bindings. from matplotlib.backend_bases import key_press_handler class matplotlibSwitchGraphs: def __init__(self, master): self.master = master self.frame = Frame(self.master) self.embed_graph_one() self.frame.pack(expand=YES, fill=BOTH) # the def creates the first matplotlib graph def graph_one(self): t = np.arange(0.0, 2.0, 0.01) s = 1 + np.sin(2 * np.pi * t) fig, ax = plt.subplots() ax.plot(t, s) ax.set(xlabel='time (s)', ylabel='voltage (mV)', title='Graph One') # plt.show() return fig, ax # This def creates the second matplotlib graph that uses subplot # to place two graphs one on top of the other def graph_four(self): x1 = np.linspace(0.0, 5.0) y1 = np.cos(2 * np.pi * x1) * np.exp(-x1) fig = plt.figure() ax = plt.subplot2grid((5, 4), (0, 0), rowspan=4, colspan=4) ax.plot(x1, y1, 'o-') means_men = (20, 35, 30, 35, 27) std_men = (2, 3, 4, 1, 2) ax2 = plt.subplot2grid((5, 4), (4, 0), sharex=ax, rowspan=1, colspan=4) ax2.bar(std_men, means_men, color='green', width=0.5, align='center') return fig, ax # this def takes graph one and embeds it in a tkinter widget def embed_graph_one(self): fig, ax = self.graph_one() canvas = FigureCanvasTkAgg(fig, self.master) canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1) canvas.draw() canvas.mpl_connect("key_press_event", self.on_key_press) self.button = Button(self.master, text="Quit", command=self._quit) self.button.pack(side=BOTTOM) self.button_switch = Button(self.master, text="Switch Graphs", command=lambda: self.switch_graphs(canvas, fig, ax)) self.button_switch.pack(side=BOTTOM) # This def takes the second graph and embeds it in a tkinter # widget def embed_graph_two(self): fig, ax = self.graph_two() canvas = FigureCanvasTkAgg(fig, self.master) canvas.draw() canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1) canvas.mpl_connect("key_press_event", self.on_key_press) self.button = Button(self.master, text="Quit", command=self._quit) self.button.pack(side=BOTTOM) self.button_switch = Button(self.master, text="Switch Graphs", command=lambda: self.switch_graphs(canvas, fig, ax)) self.button_switch.pack(side=BOTTOM) # the def defines the key press event handler def on_key_press(event): key_press_handler(event, canvas, toolbar) # This def quits the tkinter widget def _quit(self): self.master.quit() # stops mainloop # This def switches between the two embedded graphs def switch_graphs(self, fig, canvas, ax): ax.clear() self.embed_graph_two() canvas.draw() def main(): root = Tk() matplotlibSwitchGraphs(root) root.mainloop() if __name__ == '__main__': main() 

Este código aún no reemplaza el primer gráfico con el segundo gráfico, sino que simplemente coloca el segundo gráfico debajo del primero. Cualquier ayuda para obtener este código para reemplazar el primer gráfico con el segundo sería apreciada.

Los gráficos que estoy trazando son un gráfico de velas OHLC y un gráfico de velas OHLC con una superposición de volumen. Desafortunadamente, el

 self.canvas.draw() 

El comando en las definiciones de draw_graph no parece aplicarse aquí. Cuando bash mostrar los gráficos, todo lo que obtengo es una figura en blanco. Aquí está el código que estoy usando para trazar el gráfico de velas. Esto correspondería a draw_graph_one.

 def ohlc_daily_date_axis(self, stock_sym): mondays = WeekdayLocator(MONDAY) # major ticks on the mondays alldays = DayLocator() # minor ticks on the days weekFormatter = DateFormatter('%b %d %Y') # eg, Jan 12 2018 dayFormatter = DateFormatter('%d') # eg, 12 quotes = get_stock_price_data_list_of_tuples(stock_sym) graph_header_text = 'Daily OHLC Candlestick Chart: ' + stock_sym + ' Date Range: ' + str( num2date(quotes[0][0]).date()) + ' - ' + str(num2date(quotes[-1][0]).date()) if len(quotes) == 0: raise SystemExit self.fig, self.ax = plt.subplots(figsize=(18, 5)) plt.subplots_adjust(bottom=0.2) self.ax.xaxis.set_major_locator(mondays) self.ax.xaxis.set_minor_locator(alldays) self.ax.xaxis.set_major_formatter(weekFormatter) # ax.xaxis.set_minor_formatter(dayFormatter) plt.title(graph_header_text) self.ax.set_ylabel('Share Price ($)', size=10) # plot_day_summary(ax, quotes, ticksize=3) candlestick_ohlc(self.ax, quotes, width=0.6) self.ax.xaxis_date() self.ax.autoscale_view() plt.setp(plt.gca().get_xticklabels(), rotation=45, horizontalalignment='right') self.ax.format_coord = self.get_ohlc_from_date_xy # ax.fmt_xdata = get_ohlc_from_date_x #plt.show() self.canvas.draw() 

¿Cómo modificaría esto para que muestre los datos?

Nueva respuesta para incluir la incorporación de Tk (cambios significativos con respecto a la otra respuesta, por lo que se agrega una nueva respuesta en lugar de editar esa). graph_one() y graph_two() a la clase de envoltorio de graph de switch, y les draw_graph_one() nombre a draw_graph_one() y draw_graph_two() . Esos dos nuevos métodos de clase reemplazaron los métodos embed_graph_one() y embed_graph_two() . La mayoría del contenido de los métodos embed() eran duplicados y, por lo tanto, se movieron a un método config_window() que se llama cuando se config_window() una instancia del objeto de clase. Creé unos pocos miembros de datos de clase para capturar las variables PLT (por ejemplo, canvas , ax , fig ) y creamos un nuevo miembro de datos para mantener un registro de qué gráfico se muestra actualmente ( graphIndex ) para que podamos dibujar correctamente el gráfico cuando switch_graphs() se llama (en lugar de llamar a embed_graph_two() cada vez que se hace “switch”, como en el código original). Opcionalmente, puede sacar t de los métodos de draw y convertirlo en un miembro de datos de clase (si los valores para t no cambian).

 import matplotlib matplotlib.use("TkAgg") import matplotlib.pyplot as plt import numpy as np from tkinter import * from matplotlib.backends.backend_tkagg import ( FigureCanvasTkAgg, NavigationToolbar2Tk) # Implement the default Matplotlib key bindings. from matplotlib.backend_bases import key_press_handler # Seperated out config of plot to just do it once def config_plot(): fig, ax = plt.subplots() ax.set(xlabel='time (s)', ylabel='voltage (mV)', title='Graph One') return (fig, ax) class matplotlibSwitchGraphs: def __init__(self, master): self.master = master self.frame = Frame(self.master) self.fig, self.ax = config_plot() self.graphIndex = 0 self.canvas = FigureCanvasTkAgg(self.fig, self.master) self.config_window() self.draw_graph_one() self.frame.pack(expand=YES, fill=BOTH) def config_window(self): self.canvas.mpl_connect("key_press_event", self.on_key_press) toolbar = NavigationToolbar2Tk(self.canvas, self.master) toolbar.update() self.canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1) self.button = Button(self.master, text="Quit", command=self._quit) self.button.pack(side=BOTTOM) self.button_switch = Button(self.master, text="Switch Graphs", command=self.switch_graphs) self.button_switch.pack(side=BOTTOM) def draw_graph_one(self): t = np.arange(0.0, 2.0, 0.01) s = 1 + np.sin(2 * np.pi * t) self.ax.clear() # clear current axes self.ax.plot(t, s) self.ax.set(title='Graph One') self.canvas.draw() def draw_graph_two(self): t = np.arange(0.0, 2.0, 0.01) s = 1 + np.cos(2 * np.pi * t) self.ax.clear() self.ax.plot(t, s) self.ax.set(title='Graph Two') self.canvas.draw() def on_key_press(event): print("you pressed {}".format(event.key)) key_press_handler(event, self.canvas, toolbar) def _quit(self): self.master.quit() # stops mainloop def switch_graphs(self): # Need to call the correct draw, whether we're on graph one or two self.graphIndex = (self.graphIndex + 1 ) % 2 if self.graphIndex == 0: self.draw_graph_one() else: self.draw_graph_two() def main(): root = Tk() matplotlibSwitchGraphs(root) root.mainloop() if __name__ == '__main__': main() 

Salida (dos ventanas, alternando cada vez que se hace clic en el botón de gráfico de cambio): introduzca la descripción de la imagen aquí

Saliendo de la documentación de matplotlib en los botones y la pregunta de stackoverflow que vinculó , aquí está el código que hace lo básico de lo que quiere decir. Lamento no haber usado tu código, pero era un bloque de código demasiado grande.

 import numpy as np import matplotlib.pyplot as plt from matplotlib.widgets import Button # Button handler object class Index(object): ind = 0 # This function is called when bswitch is clicked def switch(self, event): self.ind = (self.ind+1) % len(functions) ydata = 1 + functions[self.ind](2 * np.pi * t) l.set_ydata(ydata) ax.set(title='Graph '+str(self.ind + 1)) plt.draw() # This function is called when bquit is clicked def quit(self, event): plt.close() # Store the functions you want to use to plot the two different graphs in a list functions = [np.sin, np.cos] # Adjust bottom to make room for Buttons fig, ax = plt.subplots() plt.subplots_adjust(bottom=0.25) # Get t and s values for Graph 1 t = np.arange(0.0, 2.0, 0.01) s = 1 + np.sin(2 * np.pi * t) # Plot Graph 1 and set axes and title l, = plt.plot(t, s, lw=2) ax.set(xlabel='time (s)', ylabel='voltage (mV)', title='Graph 1') # Initialize Button handler object callback = Index() # Connect to a "switch" Button, setting its left, top, width, and height axswitch = plt.axes([0.40, 0.07, 0.2, 0.05]) bswitch = Button(axswitch, 'Switch graph') bswitch.on_clicked(callback.switch) # Connect to a "quit" Button, setting its left, top, width, and height axquit = plt.axes([0.40, 0.01, 0.2, 0.05]) bquit = Button(axquit, 'Quit') bquit.on_clicked(callback.quit) # Show plt.show() 

Resultado:

introduzca la descripción de la imagen aquí

Haga clic en “Cambiar gráfico” y obtendrá:

introduzca la descripción de la imagen aquí