首页 > 解决方案 > 如何在两个图表之间切换并保持单选按钮和滑块更新正常工作?

问题描述

我使用 Tkinter 和 matplotlib 在 Python 中制作了一个图形查看器 GUI 程序,在其中我在两个图形之间切换。

我有三个问题我不知道如何解决:

  1. 移动停止更新的滑块后,无法更改单选按钮。
  2. 切换图表后无法更改单选按钮。
  3. 我想在带有 1 个子图和 2 个子图的图形之间切换,但是当我切换到带有滑块和单选栏的 2 个子图的图形时,我无法回到第一个。

我认为问题可能出在我更新滑块和单选按钮的方式上

这是代码:

import matplotlib
import tkinter as Tk
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
import numpy as np
from tkinter import *
from matplotlib.backends.backend_tkagg import (
    FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.backend_bases import key_press_handler
from matplotlib.widgets import Slider, RadioButtons

# 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 plot_data(self):
        def func3(x, y):
            return (1 - x / 2 + x ** 5 + y ** 3) * np.exp(-(x ** 2 + y ** 2))
        dx, dy = 0.05, 0.05
        x = np.arange(-3.0, 3.0, dx)
        y = np.arange(-3.0, 3.0, dy)
        X, Y = np.meshgrid(x, y)
        self.extent = np.min(x), np.max(x), np.min(y), np.max(y)
        self.Z1 = np.add.outer(range(8), range(8)) % 2  # chessboard
        self.Z2 = func3(X, Y)

    def draw_graph_one(self):
        self.plot_data()
        self.ax.remove()
        self.ax = plt.imshow(self.Z2, cmap=plt.cm.viridis, alpha=.9, interpolation='bilinear',
                             extent=self.extent)
        self.canvas.draw()

    def draw_graph_two(self):
        self.plot_data()
        self.ax.remove()
        self.ax = plt.subplot(1, 2, 1)
        self.ax = plt.imshow(self.Z1, cmap=plt.cm.gray, interpolation='nearest',
                         extent=self.extent)
        self.ax = plt.subplot(1, 2, 2)
        self.a = plt.imshow(self.Z2, cmap=plt.cm.viridis, alpha=.9, interpolation='bilinear',
                         extent=self.extent)
        self.b = plt.imshow(self.Z1, cmap=plt.cm.gray, interpolation='nearest',
                         extent=self.extent)
        self.canvas.draw()

        plt.subplots_adjust(left=0.15, bottom=0.15)
        slider_ax = plt.axes([0.06, 0.25, 0.0225, 0.5])

        alfa_slider = Slider(slider_ax,
                             label="Transparency",
                             valmin=0,
                             valmax=1,
                             valinit=0,
                             orientation="vertical"
                             )

        alfa_slider.on_changed(self.update_slider)
        rax = plt.axes([0.06, 0.05, 0.15, 0.15])
        radio = RadioButtons(rax, ('Reds', 'Greens', 'Blues', 'Oranges', 'Wistia', 'plasma', 'inferno'), active=0)
        radio.on_clicked(self.update_radio)
        self.canvas.draw()

    def update_slider(self,alpha_):
        self.b.set_alpha(alpha_)
        self.canvas.draw()

    def update_radio(self,label_):
        self.b.set_cmap(label_)
        self.canvas.draw()

    def on_key_press(self,event,toolbar):
        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()

我试图在函数内部输入 update_slider 和 update_radio 函数,但我不能使用 self.canvas.draw()。.remove() 和 .cla() 也不会清除窗口。

这是我想要切换的图表视图: 在此处输入图像描述 在此处输入图像描述

标签: pythonmatplotlibtkinter

解决方案


首先回答你的最后一个问题,你的大问题是你试图使用一个图形来显示两个单独的图形,而不了解 pyplot 的实际工作原理。正如我将在下面概述的那样,这样做是可能的,但是当您在图表之间切换时,您会丢失对图表所做的任何修改。我建议阅读pyplot 教程的使用多个轴和图形部分。与 MATLAB 和 pyplot 一样,关键点在于,所有绘图函数都应用于最后一个图形和轴。

当您调用时self.draw_graph_two(),您创建的最后一个轴是RadioButtons. 因此,pyplot 将这些轴作为当前轴。然后,当您第二次调用self.draw_graph_one()时(第一次是在您初始化时),它会在当前图形和当前轴上绘制绘图,它们在哪里RadioButtons

要解决此问题,请在尝试绘制新图形之前调用andplt.clf()的第一行。这将清除图形,允许从头开始绘制新图(这是您的两个绘制函数所做的)。这允许您在两个绘图之间连续切换,尽管每次切换绘图都会被清除并且对绘图的任何修改都会丢失。您也可以删除这些行,因为它们会导致错误并且不需要。draw_graph_onedraw_graph_twoself.ax.remove()

对于您的前两个问题,可以通过阅读RadioButtonsSlider 此处的文档来回答这些问题。您必须保留对小部件的引用,以使它们保持响应。所以修改你draw_graph_two的返回alfa_sliderradio保持这些小部件响应。或者,将它们声明为实例变量也会保留引用(self.alfa_sliderself.radio)。

所以你的工作图查看器是:

import matplotlib
import tkinter as tk
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backends.backend_tkagg import (
    FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.backend_bases import key_press_handler
from matplotlib.widgets import Slider, RadioButtons

# 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 = tk.Frame(self.master)
        self.fig, self.ax = config_plot()
        self.graphIndex = 0
        self.canvas = FigureCanvasTkAgg(self.fig, self.master)
        self.config_window()
        self.plot_data()
        self.draw_graph_one()
        self.frame.pack(expand=True, fill=tk.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=tk.TOP, fill=tk.BOTH, expand=1)
        self.button = tk.Button(self.master, text="Quit", command=self._quit)
        self.button.pack(side=tk.BOTTOM)
        self.button_switch = tk.Button(self.master, text="Switch Graphs", command=self.switch_graphs)
        self.button_switch.pack(side=tk.BOTTOM)

    def plot_data(self):
        def func3(x, y):
            return (1 - x / 2 + x ** 5 + y ** 3) * np.exp(-(x ** 2 + y ** 2))
        dx, dy = 0.05, 0.05
        x = np.arange(-3.0, 3.0, dx)
        y = np.arange(-3.0, 3.0, dy)
        X, Y = np.meshgrid(x, y)
        self.extent = np.min(x), np.max(x), np.min(y), np.max(y)
        self.Z1 = np.add.outer(range(8), range(8)) % 2  # chessboard
        self.Z2 = func3(X, Y)

    def draw_graph_one(self):
        plt.clf()
        self.ax = plt.imshow(self.Z2, cmap=plt.cm.viridis, alpha=.9, interpolation='bilinear',
                             extent=self.extent)
        self.canvas.draw()

    def draw_graph_two(self):
        plt.clf()
        self.ax = plt.subplot(1, 2, 1)
        self.ax = plt.imshow(self.Z1, cmap=plt.cm.gray, interpolation='nearest',
                         extent=self.extent)
        self.ax = plt.subplot(1, 2, 2)
        self.a = plt.imshow(self.Z2, cmap=plt.cm.viridis, alpha=.9, interpolation='bilinear',
                         extent=self.extent)
        self.b = plt.imshow(self.Z1, cmap=plt.cm.gray, interpolation='nearest',
                         extent=self.extent)
        self.canvas.draw()

        plt.subplots_adjust(left=0.15, bottom=0.15)
        slider_ax = plt.axes([0.06, 0.25, 0.0225, 0.5])

        self.alfa_slider = Slider(slider_ax,
                             label="Transparency",
                             valmin=0,
                             valmax=1,
                             valinit=0,
                             orientation="vertical"
                             )

        self.alfa_slider.on_changed(self.update_slider)
        rax = plt.axes([0.06, 0.05, 0.15, 0.15])
        self.radio = RadioButtons(rax, ('Reds', 'Greens', 'Blues', 'Oranges', 'Wistia', 'plasma', 'inferno'), active=0)
        self.radio.on_clicked(self.update_radio)
        self.canvas.draw()

    def update_slider(self,alpha_):
        self.b.set_alpha(1-alpha_)
        self.canvas.draw()

    def update_radio(self,label_):
        self.b.set_cmap(label_)
        self.canvas.draw()

    def on_key_press(self,event,toolbar):
        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.Tk()
    matplotlibSwitchGraphs(root)
    root.mainloop()


if __name__ == '__main__':
    main()

注意我还做了一些其他的轻微修改。不要使用from tkinter import *,你已经在上面导入了它,使用import *是一个坏习惯。我颠倒了滑块,这样透明度才有意义。self.plot_data()只调用一次,__init__因为它不会改变。


推荐阅读