Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
539 views
in Technique[技术] by (71.8m points)

python - How do I use a button to switch between two different graphs in matplotlib

I have two different matplotlib graphs that I would like to switch between upon a button press. The code I have will add the second graph below the first graph when the button is pressed but I want it to replace the first graph. This is a somewhat similar stackoverflow question (How to update a matplotlib embedded into tkinter?) but I can't seem to get this to apply to my situation.

The graphs that I have coded, graph_one and graph_two, are two simple graphs that I pulled from the matplotlib documentation. In my actual use I have two much more complicated graphs that are very dissimilar, one is a single plot and the other has an additional subplot. Because the graphs I wish to switch back and forth between are so dissimilar it is important to me that the solution be able to handle the graph inputs as separate definitions. It should also be noted that my graphs are embedded in a tkinter widget and it is important that the solution also account for this embedding. Here is the code I have:

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

It seems like I should be able to use a command like

ax.clear()

in the switch_graphs def to clear out the first graph but that doesn't work. Any help would be appreciated.

I'm posting updated code to show some small progress I've made as well as to better represent the different nature of the two graphs I wish to switch between. Both graphs are still simple graphs taken directly from the matplotlib documentation but they better represent that one of my graphs is a single plot while the second graph has two plots positioned directly on top of one another.

In my actual case I am trying to use a button to be able to switch between a candlestick chart with a volume overlay and one without the volume overlay. Posting all the code to show the candlestick charts would make for a very long piece of code so I've simplified it by using these simpler graphs. I've also eliminated matplotlib's navigation toolbar for the sake of simplicity. Here is my revised code:

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

This code still doesn't replace the first graph with second graph but just places the second graph below the first. Any help on getting this code to replace the first graph with the second would be appreciated.

The graphs that I am plotting are an OHLC candlestick chart and an OHLC candlestick chart with a volume overlay. Unfortunately The

self.canvas.draw()

command in the draw_graph defs doesn't seem to apply here. When I try to display the graphs all I get is a blank figure. Here is the code I am using that plots the candlestick chart. This would correspond to 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')  # e.g., Jan 12 2018
    dayFormatter = DateFormatter('%d')  # e.g., 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() 

How would I modify this to get it to show the data?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

New answer to include Tk embedding (significant changes from the other answer, so adding new answer instead of editing that one). I moved graph_one() and graph_two() into the switch graph wrapper class, and renamed them to draw_graph_one() and draw_graph_two(). Those two new class methods replaced the embed_graph_one() and embed_graph_two() methods. Most of the content within embed() methods were duplicates, and were therefore moved to a config_window() method which is called when the class object is instantiated. Created a few class data members to capture the plt variables (e.g. canvas, ax, fig) and created a new data member to keep track of which graph is currently displayed (graphIndex) so that we can properly draw the correct graph when switch_graphs() is called (instead of calling embed_graph_two() every time "switch" is made, as in the original code). Optionally, you can take out t from the draw methods and make it a class data member (if the values for t don't change).

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

Output (two windows, alternating whenever switch graph button is clicked): enter image description here


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...