Matplotlib-3-0-秘籍-五-

45 阅读1小时+

Matplotlib 3.0 秘籍(五)

原文:Matplotlib 3.0 Cookbook

协议:CC BY-NC-SA 4.0

八、开发交互式绘图

交互式绘图是一个非常大的主题。 有很多书籍涵盖了它的各个方面。 在本章中,我们将涵盖三个不同的领域,每个领域都有关键秘诀,如下所示:

  • 事件和回调:
    • 异常处理
    • 按键和释放事件,用于使图例在保持时间内可见/不可见
    • 按键按下事件,用于进行缩放
    • 运动通知和按键按下事件,用于跟踪坐标
    • 选择活动,用于在选择图例标签后激活艺术家
    • 图形和轴域进入和离开事件,用于更改前景颜色并使艺术家可见/不可见
    • 使用双生轴域绘制四个温度刻度
  • 小部件:
    • 光标
    • 按键
    • 复选按键
    • 单选按键
    • 文本框
  • 动画:
    • Sigmoid 动画
    • 将动画保存到 MP4 文件
    • tan函数呈指数衰减
    • 气泡图动画
    • 多个线形图的动画
    • 图像动画

介绍

对于交互式绘图,通常的%matplotlib inline不起作用,因为它将静态图形输出到屏幕,需要使用 Matplotlib 支持的后端之一。 您可能还记得后端是 Matplotlib 架构的最底层,其中发送了 Matplotlib 创建的输出图进行显示。 后端有两种类型:一种允许用户与输出交互并用于交互式绘图,另一种用于保存图形以供打印或嵌入到其他应用中。

本章中的所有秘籍均通过以下后端进行了测试:

  • nbAgg
  • Qt5Agg
  • TkAgg
  • WXAgg

我们已经观察到某些功能不适用于某些后端。

使用以下选项之一激活这些后端中的任何一个:

import matplotlib
matplotlib.use('nbAgg')

或使用此:

import matplotlib.pyplot as plt
plt.switch_backend('nbAgg')

如果选择第一个选项,则必须在导入matplotlib.pyplot模块之前执行此代码,因为pyplot模块将加载所选后端所需的所有元素。 默认情况下,pyplot会按照和rcParams文件的定义加载后端,如果这些语句是在导入pyplot之后执行的,则模块可能与此后端冲突。

如果您选择第二个选项,则pyplot将通过加载新后端的相关模块将当前后端替换为新后端。 为了使其正常工作,在切换到新后端之前应该已经设置了一些后端。

nbAgg 是 Jupyter 笔记本使用的标准后端,它显示在笔记本的输出单元中,而其他后端则打开另一个用于输出的弹出窗口。

所有这些交互式后端均提供标准的交互式导航功能,尽管外观略有不同。 Matplotlib 文档位于这个页面中,提供了各种功能的导航和键盘映射的详细说明。

事件和回调

事件是用户与绘图进行交互时所执行的操作,例如鼠标操作,键盘单击或只是在可视化文件上输入和退出图形和轴域。 回调是事件触发时对事件的响应。 程序运行时引发的异常也是事件。 但是,除了KeyboardInterrupt以外,大多数都不涉及用户干预。

虽然我们尝试在此处涵盖尽可能多的事件,但要获取受支持事件的完整列表,请参阅 Matplotlib 文档

异常处理

在运行程序的过程中,会引发许多不同的异常。 如果未编写适当的操作,则程序将崩溃,并且需要紧急干预来解决问题并重新启动程序。 在本秘籍中,我们将学习如何处理两个异常KeyboardInterruptDivideByZero及其相应的回调动作。

准备

为了模拟异常,我们将开始运行直到异常发生的循环,捕获异常,触发合适的回调响应函数,最后关闭程序。

对于KeyboardInterrupt异常,一旦程序启动,您将在笔记本的“输入”单元中看到*,单击笔记本顶部的内核选项卡,然后按下拉框中的中断选项。

如果 30 秒内没有KeyboardInterruption,则它将执行代码,从而导致DivideByZero异常。

导入所需的库:

import time

操作步骤

我们将使用适当的方法编写一个类来模拟循环,并捕获异常和回调函数。 为了便于说明,我们提供了该类中各种方法的逐步说明; 为了执行,您将必须立即采用整个类来运行代码。 请参考本章提供的代码文件:

  1. 定义类:
class EventLoop:
  1. 定义init方法以初始化状态变量,并定义event_handlers字典,该字典将事件映射到和对应的回调函数:
def __init__(self):
    self.status = None
    self.event_handlers = {"interrupt": self.handle_interrupt,
                           "DivideByZero": self.handle_DivideByZero}
  1. 定义start()方法以运行循环,捕获异常并为每个异常调用对应的回调函数:
def start(self):
    try:
        self.loop()
    except KeyboardInterrupt:
        self.handle_event("interrupt")
    except ZeroDivisionError:
        self.handle_event("DivideByZero")
    finally:
        print('Ending the program')
  1. 定义创建和运行事件循环的方法:
def loop(self):
    self.status = "loop"
    strt_time = time.time()
    while self.status == "loop":
        elapsed_time = time.time() - strt_time
        if elapsed_time > 30:
            5 / 0
  1. 定义处理事件/异常的方法:
def handle_event(self, event: str):
    self.event_handlers[event]()
  1. KeyBoardInterrupt异常的回调操作定义方法:
def handle_interrupt(self):
    print("Stopping event loop Due to KeyboardInterrupt...")
  1. DivideByZero异常的回调操作定义方法:
def handle_DivideByZero(self):
    print("Stopping event loop due to DivideByZero Error...")
  1. 实例化类对象并调用start方法:
el = EventLoop()
el.start()

工作原理

以下是其工作原理的说明:

  • class EventLoop:定义事件循环的类函数。
  • __init__(self) EventLoop类对象的初始化函数。 它会初始化status变量,并使用两个具有相应回调函数的中断来定义event_handlers字典。
  • start(self)是启动EventLoop,捕获异常并将各自的回调函数路由到的方法:
    • try:  self.loop()运行loop方法。
    • exceptKeyboardInterrupt: self.handle_event("interrupt") 捕获KeyBoardInterrupt,并用interrupt参数调用handle_event方法。
    • exceptZeroDivisionError: self.handle_event("DivideByZero") 捕获DivideByZero异常,并使用DivideByZero参数调用handle_event方法。
    • finally: print('Ending the program')在循环结束时打印Ending the program。 即使在发生异常的情况下也会执行此操作,在这种情况下,它将首先执行相应的回调函数,然后执行finally子句下的代码。
  • loop(self)是运行事件循环直到发生异常之一的方法。 当发生任何指定的异常时,控制就会退出循环,并移至方法中中except中指定的异常处理方法:
    • self.status将变量状态设置为等于循环。
    • start_time是循环的开始时间。
    • while self.status == "loop":会启动循环并运行,只要状态保持循环或发生异常。
    • elapsed_time是从开始循环开始的时间。
    • 如果没有KeyboardInterrupt的时间超过 30 秒,则程序将进入if loop的内部,并尝试将五除以零,从而导致DivideByZero异常。
  • def handle_event(self, event: str): self.event_handlers[event]()是采用异常/事件并在event_handlers字典中查找以选择 [ 对应的callback方法并对其进行调用。
  • def handle_interrupt(self): print("Stopping event loop Due to KeyboardInterrupt...")KeyboardInterrupt异常的callback方法。 它仅显示以下消息:由于KeyboardInterrupt异常而导致 , 事件循环停止。
  • def handle_DivideByZero(self): print("Stopping event loop due to DivideByZero Error...")DivideByZero异常的callback方法。 它仅显示以下消息:由于DivideByZero错误, , 事件循环正在停止。
  • el = EventLoop()实例化EventLoop对象,el.start()调用实例类的start方法。

当您运行程序并从内核中断程序时,您应该在输出单元格中看到以下文本:

Stopping event loop Due to KeyboardInterrupt... 
Ending the program

当您运行该程序并只是不执行任何操作时,在 30 秒后,您应该在输出单元格中看到以下文本:

Stopping event loop due to DivideByZero Error...
Ending the program

更多

Python 编程环境中有许多此类异常。 由于它们不涉及任何用户交互,因此它们与交互式绘图无关。 如果您有兴趣,可以在这里找到关于它们的详尽文献

按下和释放事件

在本秘籍中,我们将学习如何捕获键盘pressrelease事件,并使用它们将图例的显示切换为可见/不可见并显示按键被按下的时间(按键事件和按键释放事件之间的时间差)。

按下X键,保持一段时间,然后松开时,它会将图例的图例状态从可见变为不可见,或相反,并显示时间(单位为秒),在释放之前按住键多长时间。

如果按下,按住和释放任何其他键,则它将不会更改图中的任何内容,但是程序打印按下的键的名称和保持时间到sysout

当使用 Matplotlib 时,我们不需要显式创建事件循环,因为pyplot模块会自动处理该功能。 仅当我们使用 Python 代码时才需要它,就像我们在前面的秘籍中所做的那样。

准备

导入所需的库并设置后端:

import numpy as np
import matplotlib.pyplot as plt
plt.switch_backend('Qt5Agg')
from time import time

操作步骤

这是带有图例和标题的sincos函数的绘图,并捕获键盘事件和相关的回调函数以响应键盘事件的代码:

  1. 定义一个响应key_press_event的回调函数:
def press(event):
    global prst
    prst = time()
    print('press', event.key)
  1. 定义一个响应key_release_event的回调函数:
def release(event):
    relt = time()
    ht = relt - prst
    print('hold time:',round(ht,2))
    if event.key == 'x':
        visible = lg.get_visible()
        lg.set_visible(not visible)
        tm = 'hold time: ' + str(round(ht, 2))
        t = plt.text(np.random.randint(1,4), 
                     np.random.randint(-3,4), tm)
        plt.setp(t, color='r', size=15, weight='bold')
        ax.add_artist(t)
        fig.canvas.draw()
  1. 准备要绘制的曲线的数据:
x = np.arange(1, 2.6, 0.1) 
y = 3*np.sin(2 * np.pi * x)
y1 = 3*np.cos(2 * np.pi * x) 
  1. 实例化图形和轴域对象:
fig, ax = plt.subplots()
  1. 在轴域上绘制曲线:
ax.plot(x ,y, 'go-', label='sin')
ax.plot(x, y1, 'bd-', label='cos')
  1. 设置图例和标题:
lg = ax.legend(loc='upper center', fontsize=15)
ax.set_title('Press a key', size=25)
  1. 捕获事件并为每个事件映射回调函数:
fig.canvas.mpl_connect('key_press_event', press)
fig.canvas.mpl_connect('key_release_event', release)
  1. 在设置的后端上显示该图:
plt.show()

工作原理

以下是其工作原理的说明:

  • fig, ax = plt.subplots()定义并实例化图形对象和轴域对象。
  • ax.plot(x ,y, 'go-', label='sin')ax.plot(x, y1, 'bd-', label='cos')绘制正弦和余弦函数。
  • lg = ax.legend(loc='upper center', fontsize=15)ax.set_title('Press a key', size=25)设置绘图的图例和标题。 在这里,我们将图例捕获到变量lg中,稍后将在回调函数中使用。
  • fig.canvas.mpl_connect()是从后端捕获事件并将它们连接到相应的回调函数以响应事件的方法。 这等效于我们在先前秘籍中定义的 ,handle_event()函数。
  • fig.canvas.mpl_connect('key_press_event', press)会在按下任何键时捕获,并调用"press"回调函数。
  • fig.canvas.mpl_connect('key_release_event', release)在释放键时捕获,并调用"release"回调函数。
  • presscallback函数,可响应任何按键事件:
    • 回调函数以event作为参数,这是 Matplotlib 用来传递可捕获事件特定细节的预定义事件的关键字,在这种情况下,此事件为键的名称。
    • global prst定义一个全局变量prst,用于捕获按键按下的时间。 它被定义为全局变量,因为在中需要release函数。
    • prst = time()捕获prst变量中的当前时间,表示按下该键的时间。
    • print('press', event.key)将被按下的键的名称打印到sysout中,您可以在笔记本电脑的输出单元中看到它,它声明为prst并具有全局范围,我们不需要将其返回以传递给release函数。
  • release是另一个响应任何键释放事件的回调函数:
    • 它还将event作为第一个参数。
    • relt捕获的当前时间,代表的键释放时间。
    • ht是按键被按下与释放之间经过的保持时间。
    • print('hold time:', round(ht,2))将保持时间打印到sysout,将时间四舍五入到小数点后两位。
    • if event.key == 'x':检查所按下的键是否为x; 如果是,那么它将执行如下指令集。 否则,它什么都不做:
      • lg.get_visible()获取lg图例对象的当前状态truefalse,以指示图例当前可见还是不可见,并将其状态分配给visible对象。
      • lg.set_visible(not visible)反转当前状态,这意味着如果可见,则现在不可见,如果不可见,则现在将可见。
      • tm = 'hold time: ' + str(round(ht, 2))准备放置在图形上的保持时间的文本字符串。
      • t = plt.text(np.random.randint(1,4), np.random.randint(-3,4), tm)创建要放置在图形上随机位置的文字艺术家。
      • plt.setp(t, color='r', size=15, weight='bold')将颜色,大小和权重属性设置为先前定义的文本艺术家。
      • ax.add_artist(t)将文本艺术家添加到轴域。
      • fig.canvas.draw()最后在所选后端上显示输出图形。

运行该程序时,您应该在左侧看到该图。 按住X键时,应该会显示图例切换,并且保持时间出现在绘图上。 当您按下G键时,您应该在绘图上看到网格线。 然后,您应该在此处看到右侧显示的图:

请注意,后端导航具有一些具有特殊功能的键的映射,例如对于主页的h,对于主网格的g,在 y 轴上的对数刻度的l,以及用于平移, 缩放,后退,前进,全屏等的一些其他键。 当按下这些键中的任何一个时,您将在绘图上看到其定义的功能,因此请不要与用户定义的功能混淆。 您可能要避免为这些键定义自己的功能,以免发生冲突。

鼠标按下事件

在本秘籍中,我们将学习如何捕获鼠标按键按下事件,并使用坐标在主窗口上按下和鼠标按键的点周围缩放绘图,并将其显示在[ 缩放窗口。

准备

导入所需的库:

import matplotlib.pyplot as plt
import numpy as np

操作步骤

以下步骤说明了如何编码和所需逻辑:

  1. 在主窗口和缩放窗口中定义图形和轴域:
figmain, axmain = plt.subplots()
figzoom, axzoom = plt.subplots()
  1. 设置主轴域和缩放轴域的属性:
axmain.set(xlim=(-5, 5), ylim=(-75, 175), autoscale_on=False, title='Click to zoom')
axzoom.set(xlim=(-2, 2), ylim=(-8, 8), autoscale_on=False, title='Zoom window')
  1. 准备绘图数据:
x = np.arange(-5, 5, 0.1)
y = x ** 3
  1. 在主轴域和缩放轴域上绘制相同的曲线:
axmain.plot(x, y, 'g-d')
axzoom.plot(x, y, 'b-.o')
  1. 定义响应鼠标按键按下事件的回调函数:
def onbuttonpress(event):
    if event.button == 1:             # left = 1, scroll=2, right=3
        x, y = event.xdata, event.ydata
        axzoom.set_xlim(x - 1, x + 1)
        axzoom.set_ylim(y - 10, y + 10)
        figzoom.canvas.draw()
  1. 将按键按下事件连接到回调函数:
figmain.canvas.mpl_connect('button_press_event', onbuttonpress)
  1. 在指定的后端显示数字:
plt.show()

工作原理

以下是前面代码的说明:

  • figmain, axmain = plt.subplots()定义主窗口的图形和轴域,figzoom, axzoom = plt.subplots()定义缩放窗口的图形和轴域。
  • axmain.set(xlim=(-5, 5), ylim=(-75, 175), autoscale_on=False, title='Click to zoom')设置属性,例如 xy 轴限制,自动缩放是打开还是关闭,以及主轴域标题。 axzoom.set(xlim=(-2, 2), ylim=(-8, 8), autoscale_on=False, title='Zoom window')为缩放轴域定义了和相同的属性。
  • x = np.arange(-5, 5, 0.1)y = x ** 3是用于绘制非线性曲线的数据。
  • axmain.plot(x, y, 'g-d')在主轴域上绘制非线性曲线,axzoom.plot(x, y, 'b-.o')在变焦轴域上绘制非线性曲线。
  • onbuttonpress(event)是响应按键按下事件的回调函数。 通常,event是函数接收的参数:
    • if event.button == 1:检查是否按下了左键(滚动按键代码为2按键代码为3)。 如果是按左键,则执行后续步骤,如下所示。 否则,它将不执行任何操作而完成该函数。
    • x, y = event.xdata, event.ydata捕获绘制点上按下鼠标按键的点的坐标,在数据坐标系中。 应当注意,event.xevent.y在显示坐标系中具有相同的坐标。
    • axzoom.set_xlim(x - 1, x + 1)将缩放轴域的 x 轴限制从按下鼠标键的位置(鼠标单击)设置为+/-1。 因此,仅在这些限制内的曲线上的点将出现在缩放窗口中。
    • axzoom.set_ylim(y - 10, y + 10)将缩放轴域的 y 轴限制从按下鼠标键的点开始设置为+/- 10。 因此,仅在这些限制范围内的曲线上的点将出现在缩放窗口中。
    • figzoom.canvas.draw()用新的限制绘制数字。
  • figmain.canvas.mpl_connect('button_press_event', onbuttonpress)捕获按键按下事件,并调用回调函数来响应该事件。
  • plt.show()将输出发送到指定的后端。

运行代码时,您应该在单独的窗口中看到两个图,如下所示。 当它们都出现时,它们可能彼此重叠,因此您必须拖动图 2 使其从图 1 移开:

在点(2, 11.5)上单击图 1 中的图时,则图 2 中的图应更改,如下所示。 它是图 1 中围绕点(2, 11.5)的绘图的缩放版本,因此它包含 X 范围为 1 至 3,为。 Y 的范围大约为 0 到 20:

运动通知和鼠标按键按下事件

在本秘籍中,我们将学习如何捕获motion_notify_eventbutton_press_event并显示发生这些事件的点的坐标。

准备

导入所需的库:

import matplotlib.pyplot as plt
import numpy as np
from time import time

操作步骤

以下步骤说明了如何编码和所需逻辑:

  1. 设置要绘制的图的数据:
t = np.arange(0.0, 1.0, 0.01)
s = np.sin(2 * np.pi * t)
  1. 定义图形和轴域:
fig, ax = plt.subplots()
  1. 绘制图形:
ax.plot(t, s)
  1. str_time变量中捕获开始时间:
str_time = time()
  1. 定义响应鼠标移动事件的回调函数:
def on_move(event):
    if event.inaxes:
        print('onmove data coords %.2f %.2f' % (event.xdata, 
               event.ydata))
    elapsed_time = time() - str_time
    print('elapsed time', elapsed_time)
    if elapsed_time > 10:
        print('Closing onmove event after 10 sec')
        plt.disconnect(binding_id)
  1. 定义响应鼠标按键按下事件的回调函数:
def on_click(event):
    if event.inaxes is not None:
        if event.button == 1:
            print('left click data coords %.2f %.2f' % (event.xdata, event.ydata))
            ax.text(event.xdata, event.ydata, 'left click here', 
                    weight='bold', color='m')
        elif event.button == 2:
              print('scroll click data coords %.2f %.2f' % (event.xdata, event.ydata))
              ax.text(event.xdata, event.ydata, 'scroll click here', 
                      weight='bold', color='m')
        elif event.button == 3:
              print('right click data coords %.2f %.2f' % (event.xdata, event.ydata))
              ax.text(event.xdata, event.ydata, 'right click here', weight='bold', color='m')
        fig.canvas.draw()
  1. 将事件连接到对应的回调函数:
binding_id = plt.connect('motion_notify_event', on_move)
plt.connect('button_press_event', on_click)
  1. 在指定的后端显示图形:
plt.show()

工作原理

以下是上述代码的说明:

  • on_move(event)是响应鼠标移动事件的回调函数:
    • if event.inaxes检查鼠标移动事件是否在轴域内或轴域外发生。 如果在轴域内,则执行下一组指令 ; 否则,事实并非如此。
    • print()打印数据坐标系中的坐标,在鼠标移至sysout的轴域上的点的时候。 您可以在笔记本的输出单元中看到它。
    • elapsed_time是从程序开始到现在的经过时间。 这用于停用move_event回调函数,因为鼠标移动事件会产生大量sysout ] 通过打印语句。
    • if elapsed_time > 10检查经过时间是否大于 10 秒; 如果是,它将closing onmove event after 10 sec打印到sysout,并且plt.disconnect(binding_id)断开on_move事件的连接。binding_id是定义事件和回调之间的连接时捕获的标识。
    • on_click(event)是响应鼠标单击(按键按下)事件的回调函数。 鼠标上有三个按键:左侧为 1,滚动为2,右侧为3。 每次单击按键时,它会在绘图本身上打印相应消息:
      • if event.button == 1:检查按下的按键是否为左键,然后将坐标打印到sysoutax.text(event.xdata, event.ydata, 'left click here', weight='bold', color='m')创建要在绘图上显示的文字艺术家,当调用draw()函数时,该艺术家将可见。
      • 按键 2(滚动)和 3(右)重复相同的功能。
  • binding_id = plt.connect('motion_notify_event', on_move)将运动通知事件与相应回调函数on_move()相连,并且其标识保存在binding_id和中,如果以后要断开此事件,则需要使用此标识。
  • plt.connect('button_press_event', on_click)将按键按下事件与对应的回调函数on_click()连接起来。
  • 请注意,在此秘籍中,我们已使用plt.connect()方法将该事件与相应的回调函数连接,而之前我们使用canvas类的方法mpl_connect()。 区别在于plt.connect()pyplot API 的一部分,而mpl_connect()是面向对象的 API 的一部分,我们在第 5 章“使用面向对象的 API 进行绘图”中了解。

运行该程序时,您应该看到以下图表,并将鼠标悬停在该图表上时,在输出单元格中的 10 秒钟内,您将在sysout中看到文本消息elapsed time 0.6132094860076904,  onmove data coords 0.14 -1.04。 实际的经过时间和坐标对您而言将有所不同,具体取决于您将鼠标悬停在绘图上的时间和位置:

10 秒后,如果按住鼠标左键(1),滚动(2)或鼠标右键(3),您将在图形上以及在输出单元格中看到相应消息。 该图应如下所示:

拾取事件

在本秘籍中,我们将学习如何捕获一个拾取事件,并使用它来激活/去激活给定轴域上一组线形图中的特定线形图。 我们使用图例来选择特定的线形图。

准备

我们将使用 受试者工作特性ROC)曲线数据,该数据在第 3 章“绘制多个图表,子图和图”中引入,来绘制各种折线图,与相关的图例。

导入所需的库并设置后端:

import matplotlib.pyplot as plt
plt.switch_backend('nbAgg')
import pandas as pd

操作步骤

以下步骤对逻辑进行了编码,以演示选择事件的用法:

  1. 从 Excel 文件中读取fprtpr数据以获取各种算法:
fpr_logreg = pd.read_excel('ROC_Curves.xlsx', 'fpr_logreg')
tpr_logreg = pd.read_excel('ROC_Curves.xlsx', 'tpr_logreg')
fpr_KNN = pd.read_excel('ROC_Curves.xlsx', 'fpr_KNN')
tpr_KNN = pd.read_excel('ROC_Curves.xlsx', 'tpr_KNN')
fpr_MLP = pd.read_excel('ROC_Curves.xlsx', 'fpr_MLP')
tpr_MLP = pd.read_excel('ROC_Curves.xlsx', 'tpr_MLP')
fpr_SGD = pd.read_excel('ROC_Curves.xlsx', 'fpr_SGD')
tpr_SGD = pd.read_excel('ROC_Curves.xlsx', 'tpr_SGD')
fpr_GNB = pd.read_excel('ROC_Curves.xlsx', 'fpr_GNB')
tpr_GNB = pd.read_excel('ROC_Curves.xlsx', 'tpr_GNB')
fpr_svc = pd.read_excel('ROC_Curves.xlsx', 'fpr_svc')
tpr_svc = pd.read_excel('ROC_Curves.xlsx', 'tpr_svc')
fpr_RF = pd.read_excel('ROC_Curves.xlsx', 'fpr_RF')
tpr_RF = pd.read_excel('ROC_Curves.xlsx', 'tpr_RF')
fpr_DT = pd.read_excel('ROC_Curves.xlsx', 'fpr_DT')
tpr_DT = pd.read_excel('ROC_Curves.xlsx', 'tpr_DT')
  1. 定义并实例化图形:
fig = plt.figure(figsize=(10,8))
  1. 绘制所有折线图:
plt.plot([0, 1], [0, 1], 'k--')
l1, = plt.plot(fpr_logreg, tpr_logreg, label='LogReg',color='purple')
l2, = plt.plot(fpr_KNN, tpr_KNN, label='KNN',color='green')
l3, = plt.plot(fpr_DT, tpr_DT, label='DecisionTree', color='orange')
l4, = plt.plot(fpr_RF, tpr_RF, label='Random Forest',color='yellow')
l5, = plt.plot(fpr_MLP, tpr_MLP, label='MLP',color='red')
l6, = plt.plot(fpr_svc, tpr_svc, label='SVC',color='violet')
l7, = plt.plot(fpr_GNB, tpr_GNB, label='GNB',color='grey')
l8, = plt.plot(fpr_SGD, tpr_SGD, label='SGD', color='pink')
  1. 将 alpha(透明度)设置为0.4来设置标签,标题和图例:
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC curve')
lgd = plt.legend(loc='lower right', fancybox=True, shadow=True)
  1. 创建一个字典以使用和对应的图例标签映射绘图线:
pltlines = [l1, l2, l3, l4, l5, l6, l7, l8]
leg_ln_map = dict()
for leg_line, plot_line in zip(lgd.get_lines(), pltlines):
    leg_line.set_picker(10) 
    leg_ln_map[leg_line] = plot_line
  1. 定义响应和on_pick事件的回调函数:
def onpick(event):
 # on the pick event, find the plot line corresponding to the legend line, and toggle the visibility
    leg_line = event.artist
    plot_line = leg_ln_map[leg_line]
    vis = not plot_line.get_visible()
    plot_line.set_visible(vis)
 # Change the alpha on the line in the legend so we can see what lines have been toggled
    if vis:
        leg_line.set_alpha(1.0)
    else:
        leg_line.set_alpha(0.2)
    fig.canvas.draw()
  1. 将事件与对应的回调函数连接:
fig.canvas.mpl_connect('pick_event', onpick)
  1. 将输出图形发送到选定的后端:
plt.show()

工作原理

以下是上述代码的说明:

  • 读取数据,定义图形以及绘制各种折线图的前三个步骤已经为我们所熟悉。
  • lgd = plt.legend(loc='lower right', fancybox=True, shadow=True)设置图例,fancybox=True指定在图例项周围绘制边界框,shadow=True指定该边界框具有阴影。
  • pltlines = [l1, l2, l3, l4, l5, l6, l7, l8]是图中所有绘图线的列表。
  • leg_ln_map = dict()是将绘图线映射到相应图例标签的字典。
  • for循环为每个图例标签设置选择器,并将它们映射到和对应的线形图。
  • leg_line.set_picker(5)设置图例标签行的选择器。 数字 10 表示,即鼠标单击被捕获以触发相应对应选择事件的点数。 1 点是 1/72 英寸。 如果此数字太大,则单击可能会变得足够接近多个图例项目,并且所有这些项目可能会同时被触发! 因此,请将其设置得足够小以避免给定鼠标单击时图例行之间的重叠。
  • 选择器的此参数还有其他选项,None表示禁用选择器,布尔值True表示在图例行上单击时触发事件; 它也可以是用户定义的函数。
  • onpick(event)pick_event的回调函数:
    • leg_line = event.artist捕获发生 , 鼠标单击的特定图例行。
    • plot_line = leg_ln_map[leg_line]获得与图例线相对应的绘图线。
    • vis = not plot_line.get_visible()获取绘图线的当前可见性状态,并将其设置为与vis相反。
    • plot_line.set_visible(vis)设置绘图线的新可见性状态。
    • ifelse语句使用[alpha]为对应的图例行设置相同的状态。1表示完全可见,0.2表示部分可见。
    • fig.canvas.draw()在输出设备上绘制图形。
  • fig.canvas.mpl_connect('pick_event', onpick)pick事件与对应的回调函数onpick连接起来。

运行程序并在图例中单击 LogReg,Random Forest 和 SVC 标签时,应该看到以下图表。 禁用它们,因此您在图中看不到这些曲线:

请注意,此图中,标头和导航工具栏与以前秘籍中显示的数字有所不同。 在这种情况下,已使用nbAgg后端,而使用了较早的Qt5Agg。 虽然外观不同,但所有后端在工具栏中都提供和相同的功能。

图和轴域的进入和离开事件

在本秘籍中,我们将学习如何捕获图形输入和离开事件以及轴域进入和离开事件,并使用它们来更改图形和轴域的属性,并使轴域上的线形图可见/不可见。

准备

导入所需的库:

import matplotlib.pyplot as plt
import numpy as np

操作步骤

以下是编码所需逻辑的步骤:

  1. 定义图形ax1ax2,并设置图形标题:
fig = plt.figure()
fig.suptitle('mouse hover over figure or axes to trigger events')
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212)
  1. 准备用于绘制正弦和余弦函数的数据:
x = np.arange(1, 2.0, 0.1) 
y = np.sin(2 * np.pi * x)
y1 = np.cos(2 * np.pi * x)
  1. ax1上绘制正弦函数,在ax2上绘制余弦函数:
ax1.plot(x,y, color='g')
ax2.plot(x, y1, color='b')
  1. figure_enter_event定义回调函数:
def figure_enter(event):
    print('figure_enter', event.canvas.figure)
    event.canvas.figure.patch.set_facecolor('grey')
    event.canvas.draw()
  1. figure_leave_event定义回调函数:
def figure_exit(event):
    print('figure_exit', event.canvas.figure)
    event.canvas.figure.patch.set_facecolor('red')
    event.canvas.draw()
  1. axes_enter_event定义回调函数:
def axes_enter(event):
    print('axes_enter', event.inaxes)
    event.inaxes.patch.set_facecolor('white')
    event.inaxes.get_lines()[0].set_visible(True)
    event.canvas.draw()
  1. axes_leave_event定义回调函数:
def axes_exit(event):
    print('axes_exit', event.inaxes)
    event.inaxes.patch.set_facecolor('orange')
    event.inaxes.get_lines()[0].set_visible(False)
    event.canvas.draw()
  1. 将事件与对应的回调函数连接:
fig.canvas.mpl_connect('figure_enter_event', figure_enter)
fig.canvas.mpl_connect('figure_leave_event', figure_exit)
fig.canvas.mpl_connect('axes_enter_event', axes_enter)
fig.canvas.mpl_connect('axes_leave_event', axes_exit)

工作原理

以下是上述代码步骤的说明:

  • 前三个步骤是定义图形及其布局,准备数据和绘制图表。
  • figure_enter(event)是响应figure_enter_event的回调函数。
    • print('figure_enter', event.canvas.figure)将文本figure enter输出到sysout,然后是图形对象。
    • event.canvas.figure.patch.set_facecolor('grey')将图形的前景颜色设置为灰色
    • event.canvas.draw()在输出设备的上绘制更新的图形。
  • figure_exit(event)是响应figure_leave_event的回调函数。 此函数还将文本打印到sysout,将图形的前景颜色设置为红色,并绘制更新的图形。
  • axes_enter(event)是响应axes_enter_event的回调函数。
    • print('axes_enter', event.inaxes)将文本axes_enter打印到sysout,随后是访问对象。
    • event.inaxes.patch.set_facecolor('white')将轴域的面色设置为白色
    • event.inaxes.get_lines()[0].set_visible(True)将轴域上的线形图设置为可见状态; 方法get_lines()从轴域获取所有线对象,因此它是对象列表,我们需要使用index [0]从列表中获取第一个对象。
    • event.canvas.draw()在输出设备上绘制更新的图形
  • axes_exit(event)是响应axes_leave_event的回调函数。 还将,文本和轴域对象打印到sysout,将轴域的面色设置为橙色,并将线形图设置为不可见状态。
  • fig.canvas.mpl_connect('figure_enter_event', figure_enter)figure_enter_event连接到对应的回调函数figure_enter。 同样,其他三个事件也分别连接到它们各自的回调函数。

运行该程序时,应该看到如下所示的第一个图; 当鼠标在上方时,您应该看到第二个图; 当鼠标在该图的边界之外时,您应该看到第三个图:

使用双生轴域绘制四个温度刻度

在第 4 章,“开发可视化来提高发布质量”的“双生轴域”秘籍中,我们学习了如何在左和右轴域绘制两个不同的比例尺的两个独立变量。 当我们在交互模式下使用此功能时,缩放和平移功能也可以正常工作,因为两个变量是独立的。 但是,当两个变量相互依赖时,例如以弧度和度为度量单位; 公斤和磅; 或华氏温度,摄氏温度,开尔文温度和朗肯温度,那么两个变量必须同步。

在本秘籍中,我们将通过一个温度示例学习如何做到这一点。 我们将在,主轴域上使用摄氏温度单位,并在从属轴域上使用华氏,开尔文和朗肯。

准备

设置交互式分析的后端,在导入所需的库,并设置用于生成随机数的种子:

import matplotlib
matplotlib.use('tkagg')

import matplotlib.pyplot as plt
import numpy as np

np.random.seed(19681211)

操作步骤

以下是绘制和所需图形的步骤:

  1. 定义函数,将摄氏温度转换为和其他三个测量单位:
def c2f(temp):
    return (9\. / 5.) * temp + 32
def c2k(temp):
    return temp + 273.15
def c2r(temp):
    return (9\. / 5.) * temp + 427.9
  1. 根据主轴域上的摄氏刻度的当前限制来定义更新所有刻度的函数:
def refresh_scales(ax_c):
    y1, y2 = ax_c.get_ylim()

    ax_f.set_ylim(c2f(y1), c2f(y2))
    ax_f.figure.canvas.draw()

    ax_k.set_ylim(c2k(y1), c2k(y2))
    ax_k.figure.canvas.draw()

    ax_r.set_ylim(c2r(y1), c2r(y2))
    ax_r.figure.canvas.draw()
  1. 定义图形,主轴域和三个从属轴域,其他三个测量单位各一个:
fig, ax_c = plt.subplots()
ax_f = ax_c.twinx()
ax_r = ax_c.twinx()
ax_k = ax_c.twinx()
  1. 在图的右侧定义两个从属轴域,远离图的默认右轴域:
ax_k.spines["right"].set_position(("axes", 1.15))
ax_r.spines["right"].set_position(("axes", 1.30))
  1. ylim_changed事件连接到相应的回调函数:
ax_c.callbacks.connect("ylim_changed", refresh_scales)
  1. 在主轴域上以摄氏度为单位绘制30天的随机温度:
ax_c.plot(np.random.uniform(low=-40., high=40., size=(30,)))
ax_c.set_xlim(0, 30)
  1. 为所有四个比例绘制图形标题和 y 轴标签:
ax_c.set_title('Temperature on Four different Scales')
ax_c.set_ylabel('Celsius', color='g')
ax_f.set_ylabel('Fahrenheit')
ax_k.set_ylabel('Kelvin')
ax_r.set_ylabel('Rankine')
  1. 为所有轴域设置 y 轴标签的颜色:
ax_f.yaxis.label.set_color('b')
ax_k.yaxis.label.set_color('r')
ax_r.yaxis.label.set_color('m')
  1. 设置的颜色,所有 y 轴的刻度和刻度标签:
ax_c.tick_params(axis='y', colors='g')
ax_f.tick_params(axis='y', colors='b')
ax_k.tick_params(axis='y', colors='r')
ax_r.tick_params(axis='y', colors='m')
  1. 设置所有 y 轴轴线的颜色:
ax_c.spines["left"].set_edgecolor('g')
ax_f.spines["right"].set_edgecolor('b')
ax_k.spines["right"].set_edgecolor('r')
ax_r.spines["right"].set_edgecolor('m')
  1. 在屏幕上显示该图:
plt.show()

工作原理

这是前面代码的解释:

  • def c2f(temp):是将摄氏温度转换为华氏温度的函数。
  • def c2k(temp):是将摄氏温度转换为开尔文的函数。
  • def c2r(temp):是将摄氏温度转换为朗肯的函数。
  • def refresh_scales(ax_c):是根据当前摄氏温度限制刷新和从属轴域上所有刻度的函数:
    • y1, y2 = ax_c.get_ylim()在主轴域上获得当前摄氏刻度的上限和下限。
    • ax_f.set_ylim(c2f(y1), c2f(y2))使用给定的摄氏限制设置了华氏刻度的新限制。
    • ax_f.set_ylim(c2k(y1), c2f(y2))使用给定的摄氏限制设置了开尔文刻度的新限制。
    • ax_f.set_ylim(c2r(y1), c2f(y2))使用给定的摄氏限制设置了朗肯刻度的新限制。
  • fig, ax_c = plt.subplots()定义了主轴域和从属轴域上的摄氏刻度。
  • ax_f = ax_c.twinx()定义了主轴域和从属轴域上的华氏刻度。
  • ax_k = ax_c.twinx()定义了主轴域从属轴域上的开尔文刻度。
  • ax_r = ax_c.twinx()定义了主轴域从属轴域上的朗肯刻度。
  • ax_k.spines["right"].set_position(("axes", 1.15))将从属轴域向右侧移动主轴域宽度的 15%。
  • ax_r.spines["right"].set_position(("axes", 1.30))将从属轴域向右侧移动主轴域宽度的 30% ,以使所有轴刻度线,刻度标签或轴标签都不相互重叠。
  • ax_c.callbacks.connect("ylim_changed",refresh_scales)"ylim_changed"事件连接到相应的回调函数refresh_scales
  • ax_c.plot(np.random.uniform(low=-40., high=40., size=(30,)))在摄氏刻度上绘制-40 至 40 之间的 30 个随机数。
  • ax_c.set_xlim(0, 30)X 轴的下限和上限分别设置为零和 30 天。
  • ax_c.set_title('Temparature on Four different Scales')绘制该图的标题。
  • ax_c.set_ylabel('Celsius', color='g')y 轴标签设置为摄氏度。
  • ax_f.set_ylabel('Fahrenheit')y 轴标签设置为华氏度。
  • ax_k.set_ylabel('Kelvin')y 轴标签设置为开尔文。
  • ax_r.set_ylabel('Rankine')y 轴标签设置为朗肯。
  • ax_f.yaxis.label.set_color('b')设置华氏度的 y 轴标签的颜色; 通常的set_ylabel(color='b')不适用于从属轴域。
  • ax_k.yaxis.label.set_color('r')y 轴标签的颜色设置为开尔文刻度。
  • ax_r.yaxis.label.set_color('m')设置朗肯刻度的 y 轴标签的颜色。
  • ax_c.tick_params(axis='y', colors='g')设置摄氏刻度的刻度和刻度标签的颜色。
  • ax_f.tick_params(axis='y', colors='b')设置华氏刻度的刻度和刻度标签的颜色。
  • ax_k.tick_params(axis='y', colors='r')设置开尔文刻度的刻度和刻度标签的颜色。
  • ax_r.tick_params(axis='y', colors='m')设置朗肯刻度的刻度和刻度标签的颜色。
  • ax_c.spines["left"].set_edgecolor('g')设置摄氏刻度轴的颜色。
  • ax_f.spines["right"].set_edgecolor('b')设置华氏度刻度轴的颜色。
  • ax_k.spines["right"].set_edgecolor('r')设置开尔文刻度轴的颜色。
  • ax_r.spines["right"].set_edgecolor('m')设置朗肯刻度轴的颜色。

运行前面的代码后,您应该在屏幕上看到下图:

小部件

在上一节中,我们学习了如何在粒度级别捕获事件并使用这些事件执行某些基本任务。 可以扩展它来开发丰富的图形用户界面GUI)应用,这些应用包括按键,复选框,单选按键,滑块和跨度控制器等功能。 但是,这需要大量的编程工作。 因此,Matplotlib 预定义了许多这些功能,称为小部件,我们可以轻松使用这些功能来开发我们需要的 GUI 应用。

在本节中,我们将学习如何使用其中的一些小部件。

光标

当您将鼠标悬停在图形/轴域上时,Cursor小部件会用十字突出显示图形中的特定点。 这类似于我们在“运动通知和鼠标按下事件”秘籍中所做的操作,但是在这里我们将使用易于使用的小部件。

准备

导入所需的库:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Cursor

操作步骤

以下是实现逻辑的步骤:

  1. 准备绘图数据:
x = np.arange(-5, 5, 0.1)
y = x ** 2
  1. 设置图形,布局和轴域:
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, facecolor='skyblue')
  1. 绘制图形:
ax.plot(x, y, 'o')
  1. 实例化光标小部件:
cursor = Cursor(ax, color='red', linewidth=5)
  1. 将输出发送到指定的后端:
plt.show()

工作原理

以下是上述代码步骤的说明:

  • 前三个步骤定义了数据和图形的布局,并绘制了图表。
  • cursor = Cursor(ax, color='red', linewidth=5)定义并激活光标小部件。

您应该看到小部件,如此处所示。 请注意,此图已使用TkAgg后端:

按键

单击按键可启用某些功能。 通常,它们用于通过prevnext按键来回滚动数据。 我们将学习如何使用此功能。

准备

我们将使用熟悉的Iris数据集,该数据集具有三个不同的数据簇。 我们将实现prevnext按键来回滚动这些数据簇。

导入所需的库:

import matplotlib.pyplot as plt
import pandas as pd
from matplotlib.widgets import Button

操作步骤

以下代码步骤实现了我们所需的逻辑:

  1. 使用prevnext方法定义类以表示要显示的群集:
class Cluster(object):
   ind = 0    # index to move back and forth as we click prev and  
                next buttons

## Method for next button click
   def next(self, event):
       self.ind += 1
       i = self.ind % len(species)
       index = iris['species'] == species[i]
       axs.clear()
       axs.scatter(x[index], y[index], s=50, marker='o')
       axs.set_title(species[i], size=25, color='r')
       plt.draw()

## Method for prev button click
   def prev(self, event):
       self.ind -= 1
       i = self.ind % len(species)
       index = iris['species'] == species[i]
       axs.clear()
       axs.scatter(x[index], y[index], s=50, marker='o')
       axs.set_title(species[i], size=25, color='r')
       plt.draw()
  1. 实例化cluster对象:
cluster = Cluster()
  1. 读取和Iris数据,并设置属性,以绘制三个群集:
iris = pd.read_csv('iris_dataset.csv', delimiter=',')
species = ['setosa', 'versicolor', 'virginica']
x,y = iris['petal_length'], iris['petal_width']
index = iris['species'] == species[cluster.ind]
  1. 定义图形和轴域:
fig, axs = plt.subplots()
  1. 绘制第一个群集:
axs.scatter(x[index], y[index], s=50, marker='o')
axs.set_title(species[cluster.ind], size=25, color='r')
  1. 定义上一个和下一个按键的轴域:
axprev = plt.axes([0.7, 0.005, 0.1, 0.05])
axnext = plt.axes([0.81, 0.005, 0.1, 0.05])
  1. 实例化上一个和下一个按键:
bnext = Button(axnext, 'Next')
bprev = Button(axprev, 'Previous')
  1. 将的回调函数映射到prevnexton_clicked事件:
bnext.on_clicked(cluster.next)
bprev.on_clicked(cluster.prev)
  1. 在指定的后端显示图形:
plt.show()

工作原理

以下是和先前代码的解释:

  • class Cluster(object):定义一个名为Cluster的类。
  • ind = 0初始化索引,当按下prevnext按键时,索引将来回移动。
  • def next(self, event):next方法定义为next按键的callback函数:
    • self.ind += 1将索引增加 1。
    • 对于物种中个簇的数量,i = self.ind % len(species)将运行索引转换为零,1 或 2。
    • index = iris['species'] == species[i]拾取与next群集相对应的数据的索引。
    • axs.clear()清除轴域上的先前数据。 如果我们不这样做,则所有群集都将在轴域上可见,并且只有正在刷新的群集的颜色才会根据属性循环中设置的默认颜色来修改。
    • axs.scatter(x[index], y[index], s=50, marker='o')使用之前计算的索引绘制新的群集数据。
    • axs.set_title(species[i], size=25, color='r')将轴域的标题设置为,即当前群集名称。
    • plt.draw()刷新图形。
  • def prev(self, event):prev方法定义为prev按键的回调函数:
    • self.ind -= 1将索引减少 1。
    • i = self.ind % len(species)将物种的簇数转换为 0、1 或 2。
    • index = iris['species'] == species[i]拾取与上一个簇相对应的数据索引。
    • axs.clear()清除轴域上的先前数据。 如果不这样做,则所有群集都将在轴域上可见,并且仅刷新的群集的颜色将根据属性周期中设置的默认颜色进行更改。
    • axs.scatter(x[index], y[index], s=50, marker='o')使用计算出的索引绘制新的群集数据。
    • axs.set_title(species[i], size=25, color='r')将轴域的标题设置为当前群集名称。
    • plt.draw()刷新图形。
  • cluster = Cluster()实例化Cluster对象。
  • iris = pd.read_csv('iris_dataset.csv', delimiter=',')读取iris数据。
  • species = ['setosa', 'versicolor', 'virginica']列出唯一的群集名称。
  • x,y = iris['petal_length'], iris['petal_width']从鸢尾数据集的 x 和 y 坐标中选取所需属性。
  • index = iris['species'] == species[cluster.ind]获得与和第一簇相对应的数据的索引。 请注意,cluster.ind已初始化为零。
  • fig, axs = plt.subplots()实例化图形和轴域。
  • axs.scatter(x[index], y[index], s=50, marker='o')绘制第一个群集的散点图,axs.set_title(species[cluster.ind], size=25, color='r')设置当前群集名称作为图形的标题。
  • axprev = plt.axes([0.7, 0.005, 0.1, 0.05])定义图中prev按键的轴域,axnext = plt.axes([0.81, 0.005, 0.1, 0.05])定义图中next按键的轴域。 如第 6 章中的解释,“带有高级功能的绘图”,“控制轴域位置”秘籍,轴域的定义是从左,下,宽度和高度, 它们位于轴域坐标系中。
  • bnext = Button(axnext, 'Next')使用,Next标签和,bnext连接 ID 实例化next按键,并且bprev = Button(axprev, 'Previous')用和实例化上一个按键。 ] prev标签和- bprev连接 ID。
  • bnext.on_clicked(cluster.next)将回调函数映射到next按键的事件,bprev.on_clicked(cluster.prev)callback函数映射到prev按键的事件。
  • plt.show()将输出发送到指定的后端。

当您运行程序并单击Next时,您将看到以下三个图,代表每个群集,如每个图的标题所示:

如果注释掉axs.clear()语句,则该图将如下所示:

复选按键

复选框用于选择或取消选择一组属性以可视化数据。 在本秘籍中,我们将学习如何使用,CheckButtons小部件来实现复选框功能。

准备

我们将使用 ROC 图来实现CheckButtons

导入所需的库:

import matplotlib.pyplot as plt
import pandas as pd
from matplotlib.widgets import CheckButtons

操作步骤

以下是实现逻辑的步骤:

  1. 从本章前面的“选择事件”秘籍中使用的相同 Excel 文件中读取数据:
fpr_logreg = pd.read_excel('ROC_Curves.xlsx', 'fpr_logreg')
tpr_logreg = pd.read_excel('ROC_Curves.xlsx', 'tpr_logreg')
fpr_KNN = pd.read_excel('ROC_Curves.xlsx', 'fpr_KNN')
tpr_KNN = pd.read_excel('ROC_Curves.xlsx', 'tpr_KNN')
fpr_MLP = pd.read_excel('ROC_Curves.xlsx', 'fpr_MLP')
tpr_MLP = pd.read_excel('ROC_Curves.xlsx', 'tpr_MLP')
fpr_SGD = pd.read_excel('ROC_Curves.xlsx', 'fpr_SGD')
tpr_SGD = pd.read_excel('ROC_Curves.xlsx', 'tpr_SGD')
fpr_GNB = pd.read_excel('ROC_Curves.xlsx', 'fpr_GNB')
tpr_GNB = pd.read_excel('ROC_Curves.xlsx', 'tpr_GNB')
fpr_svc = pd.read_excel('ROC_Curves.xlsx', 'fpr_svc')
tpr_svc = pd.read_excel('ROC_Curves.xlsx', 'tpr_svc')
fpr_RF = pd.read_excel('ROC_Curves.xlsx', 'fpr_RF')
tpr_RF = pd.read_excel('ROC_Curves.xlsx', 'tpr_RF')
fpr_DT = pd.read_excel('ROC_Curves.xlsx', 'fpr_DT')
tpr_DT = pd.read_excel('ROC_Curves.xlsx', 'tpr_DT')
  1. 定义并实例化图形:
fig = plt.figure(figsize=(10,8))
  1. 绘制所有 ROC 曲线:
plt.plot([0, 1], [0, 1], 'k--')
l1, = plt.plot(fpr_logreg, tpr_logreg, 
               label='LogReg',color='purple')
l2, = plt.plot(fpr_KNN, tpr_KNN, label='KNN',color='green')
l3, = plt.plot(fpr_DT, tpr_DT, label='DecisionTree', color='orange')
l4, = plt.plot(fpr_RF, tpr_RF, label='Random Forest',color='yellow')
l5, = plt.plot(fpr_MLP, tpr_MLP, label='MLP',color='red')
l6, = plt.plot(fpr_svc, tpr_svc, label='SVC',color='violet')
l7, = plt.plot(fpr_GNB, tpr_GNB, label='GNB',color='grey')
l8, = plt.plot(fpr_SGD, tpr_SGD, label='SGD', color='pink')
  1. 设置图形的属性,并在和左侧调整空间,以容纳和复选框:
plt.xlabel('False Positive Rate', size=20, color='m')
plt.ylabel('True Positive Rate', size=20, color='m')
plt.title('ROC curve', size=25, color='b')
plt.subplots_adjust(left=0.35)
  1. 准备CheckButtons所需的每条曲线的数据轴域,标签和可见性状态:
lines = [l1, l2, l3, l4, l5, l6, l7, l8]
cax = plt.axes([0.05, 0.27, 0.15, 0.5])
labels = [str(line.get_label()) for line in lines]
visibility = [line.get_visible() for line in lines]
  1. 使用先前准备的必需属性实例化CheckButtons对象。
check = CheckButtons(cax, labels, visibility)
  1. 定义响应复选框点击的回调函数:
def onclick(label):
    index = labels.index(label)
    lines[index].set_visible(not lines[index].get_visible())
    plt.draw()
  1. 将事件映射到对象上的回调函数。

工作原理

这是前面代码块的说明。

前四个步骤已经很熟悉,因此我们将在步骤 5 中进行说明:

  • lines = [l1, l2, l3, l4, l5, l6, l7, l8]是所有 ROC 线形图的列表。
  • cax = plt.axes([0.05, 0.27, 0.15, 0.5])定义绘制,CheckButtons框的轴域。
  • labels = [str(line.get_label()) for line in lines]定义 ROC 图中每个的标签列表。 这是,与绘制这些曲线时所给的标签相同。 在这里,我们不再使用系统函数line.get_label()对其进行硬编码。
  • visibility = [line.get_visible() for line in lines]是所有 ROC 曲线的可见性状态列表(可见或不可见)。
  • check = CheckButtons(cax, labels, visibility)实例化CheckButtons对象。
  • def onclick(label):是单击复选按键时激活的回调函数。label是系统变量,例如event,其中包含标签和单击的复选按键的标签:
    • index = labels.index(label)从所有标签列表中获得单击的标签的索引,该索引将用于访问该标签的 ROC 图。
    • lines[index].set_visible(not lines[index].get_visible())切换单击的标签的的可见性状态。
    • plt.draw()刷新图。
  • check.on_clicked(onclick)将点击事件与对应的回调函数连接起来。
  • plt.show()将输出发送到指定的后端。

运行程序时,您应该获得第一个数字,并且取消选中 K 最近邻KNN),决策树DT)和多层感知器MLP)曲线,那么您应该看到第二个图:

单选按键

RadioButtonsCheckButtons相似,只是在任何时候它的一个属性都将处于活动状态。 在本秘籍中,我们将学习如何使用RadioButtons小部件。

准备

我们将为该秘籍定义美国,中国的 GDP 数据以及和英国的 Python 列表,并将其用于此秘籍。

导入所需的库:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import RadioButtons

操作步骤

以下是实现RadioButtons逻辑的步骤:

  1. 定义图形和轴域,然后在左侧的上调整空间以容纳RadioButtons
fig, axs = plt.subplots()
plt.subplots_adjust(left=0.3)
  1. 准备要在轴域上绘制的数据:
## Data taken from https://data.worldbank.org/country
Year = ['2007', '2008', '2009', '2010', '2011', '2012', '2013', 
        '2014', '2015', '2016']
China_GDP = [3.552, 4.598, 5.11, 6.101, 7.573, 8.561, 9.607, 10.482, 
             11.065, 11.191]
US_GDP = [14.478, 14.719, 14.419, 14.964, 15.518, 16.155, 16.692, 
          17.428, 18.121, 18.624]
UK_GDP = [3.074, 2.891, 2.383, 2.441, 2.62, 2.662, 2.74, 3.023, 
          2.886, 2.651]
  1. 绘制图形并设置标签,标题和轴限制:
line, = axs.plot(Year, US_GDP, lw=5, color='g', ls='-.')
axs.set_ylim(1,20)
axs.set_title('GDP(in trillion $)')
axs.set_xlabel('Year')
  1. 定义将绘制单选按键的轴域,并实例化对象:
rax = plt.axes([0.05, 0.5, 0.15, 0.25], facecolor='skyblue')
radio = RadioButtons(rax, ('USA', 'China', 'UK'))
  1. 定义单击RadioButtons之一时执行的回调函数:
countrydict = {'USA': [US_GDP, 'g', '-.'], 'China': [China_GDP, 'b', 
               '--'], 'UK': [UK_GDP, 'm', '-']}
def country(label):
    ydata, color, ls = countrydict[label]
    line.set_ydata(ydata)
    line.set_color(color)
    line.set_linestyle(ls)
    plt.draw()
  1. 将点击事件连接到先前定义的对应的回调函数:
radio.on_clicked(country)
  1. 将输出发送到指定的后端:
plt.show()

工作原理

这是前面代码块的解释:

  • Year是要绘制 GDP 数据的年份的列表。

  • China_GDP是,年列表中指定的年份的 GDP 清单,以兆美元($)表示,US_GDPUK_GDP同样。

  • rax = plt.axes([0.05, 0.5, 0.15, 0.25], facecolor='skyblue')定义将绘制RadioButtons的轴域,并且将面色设置为skyblue

  • radio = RadioButtons(rax, ('USA', 'China', 'UK'))在给定轴域上实例化RadioButtons对象,并使用实例化给定标签。

  • countrydict()是包含country作为key以及相关的 GDP 和的字典,绘图属性colorlinestyle作为values

  • def GDP(label):定义了响应单击RadioButtons而被激活的回调函数。label是一个系统变量,其中包含单击的RadioButton的标签:

    • ydata, color, ls = countrydict[label]从与单击的标签相对应的字典中获取 GDP,颜色和线条样式。
    • line.set_ydata(ydata)设置 y 轴的数据。
    • line.set_color(color)设置绘图的颜色。
    • line.set_linestyle(ls)设置线条样式。
    • plt.draw()刷新图。
  • radio.on_clicked(GDP)将点击事件与相应的回调函数country连接起来。

  • plt.show()将输出发送到指定的后端。

当您运行该程序并开始检查其他单选按键时,您应该看到下图:

文本框

TextBox小部件是求值任何表达式的自由格式实现。 如果您想检查各种不同的数学表达式/函数,这将非常方便。

准备

导入所需的库:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import TextBox

操作步骤

以下是实现TextBox小部件的步骤:

  1. 定义图形和轴域,并在底部调整空间以适应TextBox
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.2)
  1. 准备要绘制的初始数据:
x = np.arange(-5.0, 5.0, 0.01)
y = x ** 5
  1. 绘制数据:
l, = plt.plot(x, y, lw=2)
  1. 定义提交TextBox中的新表达式时要执行的回调函数:
def submit(text):
    ydata = eval(text)
    l.set_ydata(ydata)
    ax.set_ylim(np.min(ydata), np.max(ydata))
    plt.draw()
  1. 定义TextBox实现所需的轴域和initial_text
axbox = plt.axes([0.1, 0.05, 0.8, 0.075])
initial_text = "x ** 5"
  1. 实例化TextBox对象:
text_box = TextBox(axbox, 'Evaluate', initial=initial_text)
  1. 将提交事件与先前定义的对应的回调函数连接:
text_box.on_submit(submit)
  1. 将输出发送到指定的后端:
plt.show()

工作原理

这是前面的代码块的说明:

  • 前三步很熟悉,因此我们将从第四步开始。
  • def submit(text):定义了提交回调函数。text是系统变量,包含提交之前在文本框中输入的表达式:
    • eval()是 Python 函数,用于计算作为参数提供的表达式。
    • eval(text)是执行在TextBox中输入并将ydata变量分配给的表达式的结果。
    • l.set_ydata(ydata)将和新ydata设置为线对象的 y 轴。
    • ax.set_ylim(np.min(ydata), np.max(ydata))设置, xy 轴的限制,并使用和当前数据进行缩放。
    • plt.draw()刷新图。
  • axbox = plt.axes([0.1, 0.05, 0.8, 0.075])定义将放置TextBox的轴域。
  • initial_text = "x ** 5"是在第一次出现时出现的绘图时TextBox中出现的表达式。
  • text_box = TextBox(axbox, 'Evaluate', initial=initial_text)定义并实例化TextBox对象。
  • text_box.on_submit(submit)submit事件与对应的回调函数submit连接起来。
  • plt.show()将输出发送到指定的后端。

运行该程序时,应该看到第一个数字。 当您将文本更改为不同的表达式时,您将看到第二个和第三个数字:

动画

在本节中,我们将介绍绘图的动画。 动画是用于根据原始数据创建故事绘图的强大工具。 它是一系列视觉图表框架,这些框架连接在一起以创建视频。 Matplotlib 中的动画图可以另存为.mp4文件,可以像其他任何视频一样使用媒体播放器进行播放。

Matplotlib 为和提供了以下两个类来实现动画绘图:

  • FuncAnimation
  • ArtistAnimation

在本节中,我们将学习如何使用它们。

Sigmoid 曲线动画

在本秘籍中,我们将学习如何使用和FuncAnimation类为 Sigmoid 曲线制作动画。

准备

导入所需的库:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

操作步骤

以下是实现所需逻辑的步骤:

  1. 定义图形和轴域:
fig, ax = plt.subplots()
  1. 准备要绘制的图形的数据,在这种情况下为 Sigmoid,然后进行绘制:
x = np.arange(-10, 10, 0.01)
y = 1 / (1 + np.exp(-x))
line, = ax.plot(x, y)
  1. 定义初始化函数init()
def init(): 
    line.set_ydata([np.nan] * len(x))
    return line
  1. 定义动画函数animate()
def animate(i):
    line.set_ydata(1 / (1 + np.exp(-(x+i/100)))) # keep refreshing frame by frame
    return line,
  1. 激活动画:
ani = FuncAnimation(fig, animate, 1000, init_func=init, blit=True, 
                    interval=2, save_count=50, 
                    repeat=False, repeat_delay=1000)
  1. 将输出发送到指定的后端:
plt.show()

工作原理

这是前面代码块的解释:

  • ani = FuncAnimation(fig, animate, 1000, init_func=init, blit=True, interval=2, save_count=50,  repeat=False, repeat_delay=1000)激活动画。 由于我们需要维护对象的状态,因此必须将其分配给变量,如此处所做的:ani。 这也有助于将动画保存到.mp4文件,我们将在下一个秘籍中学习如何做:
    • fig是正在动画的图形。
    • animate是用于指示将和动画应用于绘图的方式的函数:
      • i是系统变量,包含正在处理的当前帧的帧号。
      • 我们将 x 轴数据保持不变,但将 y 轴数据更改为帧号的函数。
    • 1000是完成动画的帧数。 该数字可以是任意数字,具体取决于您希望动画运行多长时间。 这也可以是为animate函数提供数据的另一个功能。 我们将在后续秘籍中学习。
    • init_func=init一次指定要在开始时应用的初始化函数。 这是一个可选函数,也可以省略。 仅在重新启动blit=True时有用。 在这种情况下,我们只是将数据设置为nan(不是数字)。
    • blit=True指定仅刷新从前一帧更改为当前帧的图形部分。 这提高了处理速度。
    • interval=2指定连续帧之间的时间延迟为 2 毫秒。
    • save_count=50指定要保存在和高速缓存中的帧数,以提高性能。
    • repeat=False指定在完成所有 1,000 帧后停止动画。 如果我们说True,那么它将一次又一次地重复 1,000 帧的循环,直到结束会话为止。
    • repeat_delay=1000指定在完成上一个周期后的 1000 毫秒开始下一个周期。

您应该在动画的末尾看到下图:

将动画保存到 mp4 文件

在本秘籍中,我们将学习如何保存可由媒体播放器播放的动画。 我们将重新创建在上一个秘籍中学习到的动画并将其保存。

您需要在计算机上安装ffmpeg包,才能以.mp4文件格式保存动画。 对于 Windows 安装,您可以在此处获取说明。 有关此包及其应用的更多详细信息,请参见此处的文档

准备

导入所需的库:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

操作步骤

您可以一次运行先前秘籍的的所有步骤,如下所示,以创建动画:

fig, ax = plt.subplots()

x = np.arange(-10, 10, 0.01)
y = 1 / (1 + np.exp(-x))
line, = ax.plot(x, y)

def init(): # only required for blitting to give a clean slate.
    line.set_ydata([np.nan] * len(x))
    return line

def animate(i):
    line.set_ydata(1 / (1 + np.exp(-(x+i/100)))) # update the data.
    return line

ani = FuncAnimation(fig, animate, 1000, init_func=init, blit=True, 
                    interval=2, save_count=50, repeat=False, repeat_delay=1000)

有两种方法可以保存图形:

ani.save("sigmoid.mp4")             

您也可以这样保存它:

from matplotlib.animation import FFMpegWriter
writer = FFMpegWriter(fps=25, metadata=dict(title='expdecay',artist='line'), 
                      bitrate=1800)
ani.save("sigmoid.mp4", writer=writer)

工作原理

ani.save("sigmoid.mp4")ani动画保存到工作目录中,名称为sigmoid.mp4

第二个选项也做同样的事情,但是通过传递各种参数,在保存文件的方式上提供了更大的灵活性。 您可以参考文档详细了解这些参数,以及 Matplotlib 支持的其他编写器函数。 有关详细信息,请参见这里

您可以在代码库中看到sigmoid.mp4文件。 您可以运行它并查看动画的工作方式。

呈指数衰减的tan函数

在本秘籍中,我们将使用指数衰减函数图来查看FuncAnimation的另一个示例。

准备

导入所需的库:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

操作步骤

以下是实现和所需逻辑的步骤:

  1. 定义图形和轴域:
fig, ax = plt.subplots()
  1. 绘制空白直线:
xdata, ydata = [], []
line, = ax.plot(xdata, ydata)
  1. 将限制和网格设置为True
ax.set_xlim(0, 10)
ax.set_ylim(-3.0, 3.0)
ax.grid()
  1. 定义生成数据的函数,该函数将代替和帧数使用:
def frame_gen(x=0):
    while x < 50:
        x += 0.1
        yield x, np.tan(2*np.pi*x) * np.exp(-x/5.)
  1. 定义用于引导动画的函数:
def animate(data):
    x, y = data
    xdata.append(x)
    ydata.append(y)
    xmin, xmax = ax.get_xlim()
    if x >= xmax:
         ax.set_xlim(xmin, 2*xmax)
         ax.figure.canvas.draw()
    line.set_data(xdata, ydata)
    return line
  1. 激活动画:
ani = FuncAnimation(fig, animate, frame_gen, blit=True, interval=2, repeat=False)
  1. 将输出发送到指定的后端:
plt.show()

工作原理

这是前面代码块的说明。 它的工作方式类似于之前的 Sigmoid 动画的工作方式。

但是,我们这里不使用init_func,而是在main函数本身中进行初始化。 代替使用固定数量的帧,我们使用一个函数为每个帧生成数据,直到x的值达到50为止。 当 x 达到 50 时,一个周期完成,如果设置了repeat=yes,则整个周期将继续重复。 否则,它将在一个周期后停止:

  • ax.set_xlim(0, 10)将的 x 的轴限制设置为从 0 到 10,ax.set_ylim(-3.0, 3.0)将的 y 的轴限制设置为 -3.0 至 +3.0。
  • def frame_gen(x=0):是以x的 0.1 增量逐一生成帧的函数。while循环在x达到值 50 时终止。返回xy = np.tan(2*np.pi*x) * np.exp(-x/5.)
  • def animate(data):是指示动画必须起作用的方法的函数:
    • 它将frame_gen(),函数返回的帧接收到,data变量中。
    • data变量将其分为 xy 坐标。
    • 当前帧的 xy 坐标和当前帧被附加到包含所有先前帧的列表中。
    • xminxmax = ax.get_xlim()得到 x 的当前限制xminxmax变量。
    • if x >= xmax:检查当前帧的 x 坐标是否大于xmax,如果是,则ax.set_xlim(xmin, 2*xmax)x 轴设置了新的上限,并将上限加倍 。
    • ax.figure.canvas.draw()刷新图形以激活新限制。
    • line.set_data(xdata, ydata)绘制带有 , 新框架的图形,包括所有 , 先前的框架。
  • frame_gen()animate()的调用发生在FuncAnimation中,因此我们不必担心在函数之间传递数据。

您应该在动画的末尾看到下图。 您还可以在代码库中找到Exp_decay.mp4,可以运行它并查看:

气泡图动画

该示例已从 Matplotlib 中采用。 原始示例模拟雨滴,但在这里我们添加了不同的颜色组合,使其成为带有动画的气泡图。

准备

导入所需的库:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

操作步骤

以下是实现和所需逻辑的步骤:

  1. 定义并实例化图形和轴域:
fig = plt.figure(figsize=(5, 5))
ax = fig.add_axes([0, 0, 1, 1], frameon=False)
  1. 定义气泡数据结构:
n_bubbles = 50
bubbles = np.zeros(n_bubbles, dtype=[('position', float, 2), ('size', float, 1),
                                     ('growth', float, 1), ('color', float, 4)])
  1. 随机初始化起始位置:
bubbles['position'] = np.random.uniform(0, 1, (n_bubbles, 2))
  1. 绘制散点图:
scat = ax.scatter(bubbles['position'][:, 0], bubbles['position'][:, 1], 
                  s=bubbles['size'], lw=0.5, facecolors=bubbles['color'])
  1. 定义引导动画的动画函数:
def animate(frame_number):
    # Get the index of the bubble to update in this frame, repeat the cycle after all 
    # bubbles are covered
    current_index = frame_number % n_bubbles

    # Make all colors more transparent as time progresses.
    bubbles['color'][:, 3] -= 1.0/len(bubbles)
    bubbles['color'][:, 3] = np.clip(bubbles['color'][:, 3], 0, 1)

    # Increase the bubble size by growth factor
    bubbles['size'] += bubbles['growth']

    # reset position, size, color and growth factor for the current bubble
    bubbles['position'][current_index] = np.random.uniform(0, 1, 2)
    bubbles['size'][current_index] = 5
    bubbles['color'][current_index] = (0.5, 0.2, 0.8, 0.8)
    bubbles['growth'][current_index] = np.random.uniform(50, 250)

    # Update the scatter collection, with the new colors, sizes and positions.
    scat.set_facecolors(bubbles['color'])
    scat.set_sizes(bubbles['size'])
    scat.set_offsets(bubbles['position'])
  1. 激活动画:
animation = FuncAnimation(fig, animate, interval=20)
  1. 将输出发送到指定的后端:
plt.show()

工作原理

以下是和前面的代码块的解释:

  • ax = fig.add_axes([0, 0, 1, 1], frameon=False)设置轴域,frameon=False指定不绘制轴线。
  • n_bubbles = 50将气泡数设置为 50。
  • bubbles使用位置,大小,增长因子和颜色为定义数据结构,并使用零初始化所有这些数据结构。 位置是 xy 坐标,大小和增长分别是一个属性,颜色是 RGBA,因此是四个属性。
  • bubbles['position'] = np.random.uniform(0, 1, (n_bubbles, 2))随机设置所有气泡的初始位置,所有坐标都在零和 1 之间。
  • ax.scatter()使用位置坐标,大小和颜色参数绘制散点图。
  • def animate(frame_number):定义动画函数,该函数指示动画应如何表现:
    • frame_number指定当前正在处理的当前帧,由FuncAnimation自动管理。
    • current_index = frame_number % n_bubbles得出在n_bubbles范围内的当前气泡的索引。 在每一帧中,都会更新一个气泡。
    • bubbles['color'][:, 3] -= 1.0/len(bubbles)会降低所有气泡的透明度(alpha)属性,以便随着时间的推移它们会变得更加透明。
    • bubbles['color'][:, 3] = np.clip(bubbles['color'][:, 3], 0, 1)如果透明度属性的值小于零,则将其裁剪为零;如果大于 1,则将其为 1。
    • bubbles['size'] += bubbles['growth']以所有气泡的相应增长率增加它们的大小。 由于增长已初始化为零,因此第一次只会为所有气泡添加零。
    • bubbles['position'][current_index] = np.random.uniform(0, 1, 2)重置当前气泡的位置,两个坐标介于零和 1 之间。
    • bubbles['size'][current_index] = 5将当前气泡的大小重置为 5 点。
    • bubbles['color'][current_index] = (0.5, 0.2, 0.8, 0.8)将 RGBA 格式的颜色重置为当前气泡。
    • bubbles['growth'][current_index] = np.random.uniform(50, 250)重置当前气泡的增长因子。
    • scat.set_facecolors(bubbles['color'])重设气泡的脸色,scat.set_sizes(bubbles['size'])重设大小,scat.set_offsets(bubbles['position'])重设散点图上的位置。
  • animation = FuncAnimation(fig, animate, interval=20)激活动画。
  • plt.show()将输出发送到指定的后端。

在动画的最后,figure应该看起来像这里显示的那样。 您还可以在代码库中看到bubbles.mp4

多个折线图的动画

在本秘籍中,我们将学习如何使用ArtistAnimation为序列中的多个线形图设置动画。 对于本示例,我们将使用本章已经使用过两次的相同 ROC 曲线数据。

准备

导入所需的库:

import matplotlib.pyplot as plt
import pandas as pd
from matplotlib.animation import ArtistAnimation

操作步骤

以下是实现和所需逻辑的步骤:

  1. 从和对应的 Excel 文件中读取所有 ROC 图的tprfpr数据:
fpr_logreg = pd.read_excel('ROC_Curves.xlsx', 'fpr_logreg')
tpr_logreg = pd.read_excel('ROC_Curves.xlsx', 'tpr_logreg')
fpr_KNN = pd.read_excel('ROC_Curves.xlsx', 'fpr_KNN')
tpr_KNN = pd.read_excel('ROC_Curves.xlsx', 'tpr_KNN')
fpr_MLP = pd.read_excel('ROC_Curves.xlsx', 'fpr_MLP')
tpr_MLP = pd.read_excel('ROC_Curves.xlsx', 'tpr_MLP')
fpr_SGD = pd.read_excel('ROC_Curves.xlsx', 'fpr_SGD')
tpr_SGD = pd.read_excel('ROC_Curves.xlsx', 'tpr_SGD')
fpr_GNB = pd.read_excel('ROC_Curves.xlsx', 'fpr_GNB')
tpr_GNB = pd.read_excel('ROC_Curves.xlsx', 'tpr_GNB')
fpr_svc = pd.read_excel('ROC_Curves.xlsx', 'fpr_svc')
tpr_svc = pd.read_excel('ROC_Curves.xlsx', 'tpr_svc')
fpr_RF = pd.read_excel('ROC_Curves.xlsx', 'fpr_RF')
tpr_RF = pd.read_excel('ROC_Curves.xlsx', 'tpr_RF')
fpr_DT = pd.read_excel('ROC_Curves.xlsx', 'fpr_DT')
tpr_DT = pd.read_excel('ROC_Curves.xlsx', 'tpr_DT')
  1. 定义图并绘制所有 ROC 曲线:
fig = plt.figure()
l0 = plt.plot([0, 1], [0, 1], 'k--')
l1 = plt.plot(fpr_logreg, tpr_logreg, label='LogReg', color='purple', animated=True)
l2 = plt.plot(fpr_KNN, tpr_KNN, label='KNN', color='green', animated=True)
l3 = plt.plot(fpr_DT, tpr_DT, label='DecisionTree', color='orange', animated=True)
l4 = plt.plot(fpr_RF, tpr_RF, label='Random Forest', color='yellow', animated=True)
l5 = plt.plot(fpr_MLP, tpr_MLP, label='MLP', color='red', animated=True)
l6 = plt.plot(fpr_svc, tpr_svc, label='SVC', color='violet', animated=True)
l7 = plt.plot(fpr_GNB, tpr_GNB, label='GNB', color='grey', animated=True)
l8 = plt.plot(fpr_SGD, tpr_SGD, label='SGD', color='pink', animated=True)
  1. 设置图的标签,标题和图例:
plt.xlabel('False Positive Rate', size=15, color='m')
plt.ylabel('True Positive Rate', size=15, color='m')
plt.title('ROC curve', size=25, color='b')
plt.legend(loc='lower right', fancybox=True, shadow=True)
  1. 为所有 ROC 曲线的线定义一个列表:
lines = [l1, l2, l3, l4, l5, l6, l7, l8]
  1. 激活动画:
ani = ArtistAnimation(fig, lines, blit=True, interval=1000, repeat=True, repeat_delay=2500)
  1. 将输出发送到指定的后端:
plt.show()

工作原理

这是前面代码块的说明。

前三个步骤是您熟悉的,除了在每个 plot 语句中我们添加一个额外的参数animated=True来指示艺术家将成为动画的一部分。

lines = [l1, l2, l3, l4, l5, l6, l7, l8]是在动画步骤 2 中定义的所有艺术家的列表,并将其作为参数传递给ArtistAnimation

ani = ArtistAnimation(fig, lines, blit=True, interval=1000, repeat=True, repeat_delay=2500)通过以 1,000 毫秒的时间间隔一次显示一位艺术家来激活动画,并以 2500 毫秒的时间延迟重复循环。

这是输出的外观。 您还可以在代码库中看到ROC_Curves.mp4

图像动画

在本秘籍中,我们将学习如何使用相同的ArtistAnimation类对图像进行动画处理。

准备

导入所需的库:

import matplotlib.pyplot as plt
from matplotlib.pyplot import imshow, imread
from matplotlib.animation import ArtistAnimation

操作步骤

以下是实现和所需逻辑的步骤:

  1. 定义图形和轴域:
fig = plt.figure(figsize=(5,5), dpi=50)
ax = fig.add_axes([0, 0, 1, 1], frameon=False)
  1. 阅读和所需的图像,并创建所有这些图像的列表:
images = []
image1 = imshow(imread("monet.png"), animated=True)
images.append([image1])

image2 = imshow(imread("louvre_small.png"), animated=True)
images.append([image2])

image3 = imshow(imread("vangogh.png"), animated=True)
images.append([image3])

image4 = imshow(imread("persepalis.png"), animated=True)
images.append([image4])
  1. 激活动画:
ani = ArtistAnimation(fig, images, interval=1000, blit=False, 
                      repeat=True, repeat_delay=2500)
  1. 将输出发送到指定的后端:
plt.show()

工作原理

读取四个图像并将其保存到一个列表中,该列表将传递到ArtistAnimation。 保存图像时,我们使用和animated=True参数,这与我们在前面的秘籍中所做的类似。 动画将一次显示一张图像。 因此,这里我们没有显示这四个图像中的任何一个,但是您可以在动画图的代码库中看到images.mp4文件 。

九、在图形用户界面中嵌入绘图

在本章中,我们将介绍以下秘籍:

  • 使用 Matplotlib 的滑块和按钮小部件
  • 使用嵌入在 Matplotlib 应用中的 Tkinter GUI 的滑块和按钮小部件
  • 将 Matplotlib 嵌入到 Tkinter GUI 应用中
  • 使用 Matplotlib 应用中嵌入的 wxPython GUI 的滑块和按钮小部件
  • 在 wxPython GUI 应用中嵌入 Matplotlib
  • 使用嵌入在 Matplotlib 应用中的 Qt GUI 的滑块和按钮小部件
  • 将 Matplotlib 嵌入 Qt GUI 应用中

介绍

GUI 本身是一个非常大的主题,每个 GUI 框架/工具包都需要单独的一本书来全面地介绍它。 因此,对每个 GUI 框架功能的详细讨论超出了本书的范围。 此处的目的是演示某些 GUI 框架如何利用 Matplotlib 的可视化功能。

在上一章中,我们学习了如何使用三个图形用户界面GUI)框架 Tkinter 使用事件,小部件和动画 , wxPython 和 Qt 作为后端。 在那里,我们仅将这些后端 GUI 用于显示目的,因此只需切换后端即可在所有 GUI 框架中使用相同的 Matplotlib 代码。

在本章中,我们将学习交互式绘图的另一个功能,即使用相同的三个 GUI 框架将 Matplotlib 嵌入 GUI。 首先,我们将学习如何利用 Matplotlib 图形中的 GUI 工具包的一些功能来代替 Matplotlib 的本机功能。 这意味着我们仍将运行 Matplotlib 应用来控制流程,包括打开和关闭应用。 这实际上是将 GUI 功能嵌入 Matplotlib 的图形中。 由于每个 GUI 框架都有自己的架构和语法,因此和相同的代码不能用于所有 GUI 框架,就像我们在上一章中所做的那样。 这对于三个 GUI 框架中的每一个都必须重复进行。

然后,我们将学习如何使用 GUI 框架运行应用,但如何利用 Matplotlib 的功能进行绘图。 当您的主应用以任何这些 GUI 框架编写时,这将派上用场,但是您想要在这些应用中利用 Matplotlib 丰富的可视化功能。

Matplotlib 支持许多 GUI 框架。 我们基于 Tkinter,wxPython 和 Qt 的流行程度以及它们带有标准发行版(例如 Anaconda)的事实选择了 Tkinter,wxPython 和 Qt,因此与其他 GUI 框架不同,不需要进一步的安装和配置。 但是,Matplotlib 接口类似于所有 GUI 框架,因此从这三个 GUI 框架中学习可以轻松扩展到其他 GUI 框架。

Matplotlib 和 GUI 应用之间的接口

在继续具体秘籍之前,重要的是了解内部工作原理,了解 Matplotlib 和, 后端 GUI 工具包有效。 这使您更容易理解三种操作模式之间的区别:

  1. 使用 GUI 后端的的 Matplotlib 应用,仅用于显示
  2. 一个 Matplotlib 应用,它使用一些 GUI 功能/部件(在 Matplotlib 应用的中嵌入了 GUI 功能)
  3. 使用 Matplotlib 绘图的 GUI 应用(将 Matplotlib 嵌入 GUI 应用中)

Matplotlib 中的以下三个对象与该接口有关:

  • 图形
  • 画布
  • 管理器

尽管,内部工作原理和这些对象之间的关系有点复杂,但足以理解和图是进入整个绘图的所有艺术家的根艺术家(包括子图,网格等),画布是在其上绘制图形的游乐场,而管理器则是通过控制接口指定的后端。

管理器的工作是处理三个 GUI 元素:窗口,画布和导航工具栏。 窗口是主要的 GUI 应用,导航工具栏特定于和 GUI 应用,可对图形画布进行交互式分析。 每个 GUI 从FigureCanvasBase继承其画布。 因此,GUI 和 Matplotlib 之间的接口是画布,GUI 框架和 Matplotlib 对象之间的交互由管理器管理。

在第一种操作模式下,后端仅用于显示,plt.figure()调用的FigureFigureCanvasFigureManager适用于指定的后端;FigureManager依次将创建 GUI 的主窗口和导航工具栏对象,并将FigureCanvas和导航工具栏打包到该窗口中。

在第二种操作模式下,在 Matplotlib 应用中使用了某些和 GUI 功能,我们仍将使用plt.figure()来执行第一种模式中的功能; 此外, Matplotlib 应用通过主窗口完成事件循环,将 GUI 小部件和这些小部件上的事件与画布连接起来。

在第三种操作模式中,由于两个原因,我们将完全消除对pyplot的使用,从而彻底取消对FigureManager的使用。 首先,默认情况下pyplot假定打开和关闭应用,而在这种模式下,我们需要 GUI 来控制应用流。 其次,FigureManager的所有功能都是现在由 GUI 应用控制。 因此,在这种模式下,GUI 工具包功能控制着应用的打开,关闭以及 GUI 工具包与 Matplotlib 之间的交互。 这些功能因一个 GUI 工具箱的不同而异,因此没有通用的语法或代码。

使用 Matplotlib 的滑块和按钮小部件

我们在上一章中学习了 Matplotlib 的Button小部件,但是滑块是一个新的小部件,我们将在这里学习。 但是,此秘籍的目的是演示如何仅通过更改后端即可在不进行任何代码更改的情况下跨 GUI 框架使用 Matplotlib 小部件应用。 我们还将在所有 GUI 框架中使用相同的极坐标图,以便我们可以看到它们工作方式上的差异,而不是迷失在图本身的细节中!

我们将使用Slider确定极坐标图中要绘制的叶片数。 每次拖动Slider时,都会使用Slider的值来绘制该图中的许多叶子。

我们将使用Quit按钮通过关闭图形对象和窗口对象来退出应用。

准备

让我们设置要使用的后端。 这是为不同的 GUI 框架更改的唯一代码,以跨,三个选定的 GUI 框架来运行此绘图:

import matplotlib matplotlib.use('tkagg')

让我们导入所需的库:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button, Slider

操作步骤

这是编码所需逻辑的所需步骤:

  1. 准备极坐标图的数据:
theta = np.arange(0., 2., 1./180.)*np.pi
  1. 定义并实例化图形:
fig = plt.figure(figsize=(6, 5), dpi=100)
  1. 定义轴域并在图中实例化它:
ax = fig.add_subplot(111, projection='polar')
  1. 绘制极坐标图:
initial_n = 4
ax.plot(theta, 5*np.cos(initial_n*theta))
  1. 定义并实例化Slider小部件:
ax_s = plt.axes([0.15, 0.05, 0.25, 0.05])
slider_n = Slider(ax_s, '#of leaves', 3, 10, valinit=initial_n, 
                  valstep=1.0)
  1. 定义滑块小部件的回调函数:
def onchanged(s_value):
    ax.clear()
    ax.plot(theta, 5*np.cos(int(s_value)*theta)) 
  1. 将事件与对应的回调函数onchanged映射到Slider小部件:事件和Slider小部件:
slider_n.on_changed(onchanged)
  1. 定义并实例化Button小部件:
ebx = plt.axes([0.5, 0.005, 0.1, 0.05])
exit = Button(ebx, 'Quit')
  1. 定义按钮的回调函数:
def close(event):
    plt.close('all')
  1. on_clicked事件与,对应的回调函数close对应起来:
exit.on_clicked(close)
  1. 在指定的后端上显示图:
plt.show()

工作原理

这是代码的说明:

  • 前四个步骤已经为您所熟悉。
  • ax_s = plt.axes([0.15, 0.05, 0.25, 0.05])定义将放置Slider小部件的轴域。
  • slider_n = Slider(ax_s, '#of leaves', 3, 10, valinit=initial_n, valstep=1.0)定义滑块:
    • 第一个参数是轴域引用,和第二个参数是要在图中的滑块上显示的标签。
    • 滑块的最小值为 3,即起点,最大值为 10。 因此,通过使用鼠标拖动滑块,该值将从 3 更改为 10。
    • valinit指定滑块上的起点,当图形第一次显示时显示。 在这里,我们将其指定为 4。
    • valstep指定当我们将鼠标拖动到滑块上时,值以什么步长增加。 在这里,我们将其指定为 1.0,因为它表示叶子数。
  • def onchanged(s_value):是滑块的回调函数。 它接收滑块的当前值,清除先前在轴域上的图,并用新值绘制图形。
  • slider_n.on_changed(onchanged)捕获滑块上的on_changed事件,然后调用回调函数onchanged
  • ebx = plt.axes([0.5, 0.005, 0.1, 0.05])定义要放置按钮的轴域,exit = Button(ebx, 'Quit')定义按钮。
  • ebx是轴域引用; 'Quit'是要放置在图中按钮小部件上的标签
  • def close(event):Button小部件的回调函数。 它只是关闭所有图形窗口。
  • exit.on_clicked(close)捕获按钮上的on_clicked事件 ,并调用回调函数close
  • plt.show()tkagg后端上显示图。

运行前面的代码时,应该获得以下输出图。 第一个图是您第一次获得图时看到的。 当我们使用 4 初始化滑块时,它将具有四片叶子。将滑块拖动到 8.00 时,您将看到第二个图:

使用 Tkinter GUI 的滑块和按钮小部件

在本秘籍中,我们将学习如何使用,Tkinter GUI 的滑块和按钮小部件代替 Matplotlib 小部件。 该绘图的功能与先前秘籍的完全相同。 Matplotlib 仍具有程序控制流程,我们仍将使用plt.figure()来调用,后端和关联的小部件。 此外,我们将调用 Tkinter 的tk.Scale缩放器和tk.Button按钮小部件,带有关联的回调函数,代替了我们在前面的秘籍中使用的 Matplotlib 的滑块和按钮小部件。

准备

设置后端:

import matplotlib
matplotlib.use('tkagg')

导入所需的库:

import tkinter as tk 
import numpy as np
import matplotlib.pyplot as plt

操作步骤

以下是实现逻辑所需的步骤:

  1. 准备极坐标图的数据:
theta = np.arange(0., 2., 1./180.)*np.pi
  1. 定义并实例化图形:
fig = plt.figure(figsize=(6, 5), dpi=100)
  1. 定义并实例化图中的轴域:
ax = fig.add_subplot(111,projection='polar')
  1. 绘制极坐标图:
ax.plot(theta, 5*np.cos(3*theta))
  1. 定义将要放置和 Tkinter 小部件的窗口:
window = fig.canvas.manager.window
  1. 定义滑块小部件的回调函数:
def update():
    n=n_slider.get()
    ax.clear()
    ax.plot(theta, 5*np.cos(n*theta))
    fig.canvas.draw()
  1. 定义并实例化滑块:
n_slider = tk.Scale(master=window,variable=tk.IntVar(), from_=3, 
                    to=10, label='#of leaves', orient=tk.HORIZONTAL,length=int(fig.bbox.width), 
       width=int(fig.bbox.height * 0.05), command = lambda i : 
             update())
  1. 将滑块打包到窗口中:
n_slider.set(4)
n_slider.pack(after=fig.canvas.get_tk_widget())
  1. 定义退出按钮的回调函数:
def close():
    plt.close('all') 
    window.quit()
  1. 定义并实例化Button
button = tk.Button(master=window, text="Quit", command=close)
  1. 将按钮打包到窗口中:
button.pack(side=tk.BOTTOM)
  1. 在 Tkinter 后端上显示图:
plt.show()

工作原理

这是前面代码的解释:

  • 前四个步骤已经为您所熟悉。
  • window = fig.canvas.manager.window可通过其窗口访问 Tkinter 的应用。
  • def update():tk.Scale小部件的回调函数:
    • n=n_slider.get()获取拖动和释放鼠标时滑块的当前值,在这种情况下为 7。
    • ax.clear()删除轴域上的前一个图。
    • ax.plot(theta, 5*np.cos(n*theta))绘制新图,其当前值为n
    • fig.canvas.draw()刷新图形。
  • n_slider = tk.Scale()定义带有以下参数的滑块小部件:
    • master=window指定将滑块小部件创建为主窗口的的子级。
    • variable=tk.IntVar()指定滑块变量是整数; 由于它代表绘图上的叶子数,因此将其定义为整数是有意义的。
    • from_=3to=10表示滑块的起始值和结束值,因此用户可以在 3 到 10 之间拖动滑块。
    • label='#of leaves'指定要在滑块左侧显示的滑块标签。
    • orient=tk.HORIZONTAL指定缩放器是水平绘制的,而不是垂直绘制的。
    • length=int(fig.bbox.width)指定窗口小部件的长度(以像素为单位)。fig.bbox.width以浮点数返回图形的宽度,int将其转换为整数。 因此,缩放器将完整的图形水平分布。
    • width=int(fig.bbox.height * 0.05)指定缩放器的高度(以像素为单位)。 通过乘以 0.05,我们得到的是缩放器总图形高度的 5%。
    • command = lambda i : update()将滑块的回调函数指定为update(),因此,当我们在滑块上拖动鼠标并释放时,它将调用更新函数。
  • n_slider.set(4)设置滑块的初始值,第一次显示绘图时,将显示 , 。 因此,当该图第一次显示时,我们应该看到有四个叶子。
  • n_slider.pack(after=fig.canvas.get_tk_widget())指定在画布下方(之后)绘制滑块小部件。 Tkinter 没有画布的子类,但是可以通过 ,get_tk_widget()方法访问画布。pack()是将小部件打包到窗口中的 Tkinter 方法。
  • def close():是按钮小部件的回调函数:
    • plt.close('all')关闭所有数字
    • window.quit()退出窗口应用
  • button = tk.Button()定义了 Tkinter 的按钮小部件:
    • master=window指定应将按钮小部件创建为主窗口的子级。
    • text="Quit"指定按钮的标签,该标签将显示在按钮上。
    • command=close指定回调函数为close()
  • button.pack(side=tk.BOTTOM)指定按钮应包装在底部的窗口中。
  • plt.show()在 Tkinter 后端上显示图形。

当您运行前面的代码时,您应该第一次获得第一张图。 将滑块拖动到 7 时,应该会看到第二个图。 您可以通过单击 退出按钮退出:

将 Matplotlib 嵌入到 Tkinter GUI 应用中

在本秘籍中,我们将学习如何将 Matplotlib 嵌入到 Tkinter GUI 应用中。 在这里,应用流程的控制将通过 Tkinter 进行。 在这里,我们将不使用, pyplot模块和相关的plt.figure()方法。 相反,我们调用 Tkinter 应用并将 Matplotlib 的画布作为小部件嵌入。

准备

在这里,我们将不会设置后端,因为应用本身将从 GUI 的启动。

导入所需的库:

import tkinter as tk 
import numpy as np
from tkinter.font import Font
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)

操作步骤

以下是实现逻辑要遵循的步骤:

  1. 定义并实例化图形:
fig = Figure(figsize=(6, 5), dpi=100)
  1. 定义 Tkinter 窗口并设置该窗口的标题:
window=tk.Tk()
window.wm_title("Embedding in Tk")
  1. 定义和画布和导航工具栏,然后将它们打包到 Tkinter 窗口中:
canvas = FigureCanvasTkAgg(fig, master=window) 
toolbar = NavigationToolbar2Tk(canvas, window)
canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
  1. 在各种小部件上定义用于文本的字体:
myfont = Font(family='Helvetica', size=12, weight='bold')
  1. 在图上定义轴域,准备数据,并绘制极坐标图:
ax = fig.add_subplot(111,projection='polar')
theta = np.arange(0., 2., 1./180.)*np.pi
ax.plot(theta, 5*np.cos(3*theta))
  1. 定义Slider小部件的回调函数:
def update():
    n=n_slider.get()
    ax.clear()
    ax.plot(theta, 5*np.cos(n*theta))
    fig.canvas.draw()
  1. 定义并实例化Slider小部件,并将其打包到window中:
n_slider = tk.Scale(master=window,variable=tk.IntVar(), from_=3, 
                 to=10, label='#of leaves', 
                 orient=tk.HORIZONTAL,length=int(fig.bbox.width), 
                 width=int(fig.bbox.height * 0.05), command = 
                 lambda i : update(),font=myfont)
n_slider.set(4)
n_slider.pack(after=fig.canvas.get_tk_widget())
  1. 退出按钮小部件定义callback函数:
def exit():
    window.quit() 
    window.destroy()
  1. 定义并实例化Quit按钮并将其打包到window中:
button = tk.Button(master=window, text="Quit", command=exit, 
                   font=myfont)
button.pack(side=tk.BOTTOM)
  1. 启动mainloop以捕获用户触发的事件:
tk.mainloop()

工作原理

这是前面代码的解释:

  • fig = Figure(figsize=(6, 5), dpi=100)定义并实例化图形对象。Figure()matplotlib.figure类的导入。 在前两个秘籍中,我们为此目的使用了plt.figure()
  • window=tk.Tk()通过其主窗口小部件调用 Tkinter GUI 应用。
  • window.wm_title("Embedding in Tk")设置窗口的标题,该标题将显示在窗口顶部。
  • canvas = FigureCanvasTkAgg(fig, master=window)定义画布(图形的游乐场,例如绘制图形的纸张),并将图形附加到此画布上:
    • master=window指定画布是 Tkinter 窗口对象的子级。
    • toolbar = NavigationToolbar2Tk(canvas, window)再次将导航工具栏指定为 Tkinter 窗口的子级,并将其附加到画布上。
  • canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)将画布和导航工具栏打包到 Tkinter 的主窗口中:
    • side=tk.TOP指定要放置在window顶部的画布,要位于其下面的工具栏。
    • fill=tk.BOTH指定画布应在 X(水平)和 Y(垂直)方向上填充window
    • expand=1指定在容纳所有小部件之后,将主window中的任何附加空间均等地调整为具有expand=1的小部件。
    • myfont = Font(family='Helvetica', size=12, weight='bold')定义了用于小部件上的文本的字体字典,以便它们在图中看起来都相似。
  • 步骤 5 定义极坐标图的轴域,数据,并将其绘制在轴域上。
  • def update():是滑块小部件的回调函数,如前面的秘籍中所述。
  • n_slider = tk.Scale()定义滑块控件,然后将其初始化为 4,然后将其打包到窗口中,如前面的秘籍中所述。
  • def exit():退出按钮的回调函数,用于退出应用。 这与先前秘籍中使用的close()函数不同:
    • window.quit()mainloop退出。
    • window.destroy()销毁了该应用,以避免特别是 Windows OS 中的错误。
    • button = tk.Button()button.pack(side=tk.BOTTOM)与前面的秘籍中所述相同。
  • tk.mainloop()调用 Tkinter 的主应用循环以捕获事件,调用适当的回调函数。

运行前面的代码时,应该第一次看到第一个图,将滑块拖动到 9 时,应该看到第二个图。 您可以通过单击 退出按钮来退出应用:

使用 WxPython GUI 的滑块和按钮小部件

在本秘籍中,我们将学习如何使用 wxPython 的滑块和按钮小部件代替 Matplotlib 小部件,就像我们使用 Tkinter 小部件一样。

wxPython 是 wxWidgets 模块的包装。 它还与标准 Python 发行版(如 Anaconda)打包在一起。

准备

将后端设置为wxAgg

import matplotlib
matplotlib.use('wxagg')

导入所需的库:

import wx
import numpy as np
import matplotlib.pyplot as plt

操作步骤

以下是实现所需逻辑的步骤:

  1. 定义并实例化图形:
fig = plt.figure(figsize=(6, 5), dpi=100)
  1. 定义轴域,定义极坐标图的数据,然后绘制极坐标图:
ax = fig.add_subplot(111,projection='polar')
theta = np.arange(0., 2., 1./180.)*np.pi
ax.plot(theta, 5*np.cos(4*theta))
  1. 定义我们用来通过 Matplotlib 连接wx小部件的窗口:
window = fig.canvas.manager.window
  1. 为滑块小部件定义回调函数:
def update(event):
    n=n_slider.GetValue()
    ax.clear()
    ax.plot(theta, 5*np.cos(n*theta))
    fig.canvas.draw()
  1. 定义并实例化wxPython的滑块小部件,并将其与回调函数绑定:
n_slider = wx.Slider(window, wx.ID_ANY, 4, 3, 10, size=(250,10),
                     style=(wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | 
                            wx.SL_LABELS))
n_slider.Bind(wx.EVT_SCROLL, update)
  1. 退出按钮小部件定义回调函数:
def close(event):
    plt.close('all') 
  1. 定义并实例化Quit按钮小部件并将其与回调函数绑定:
button = wx.Button(window, wx.ID_ANY, "Quit")
button.Bind(wx.EVT_BUTTON, close)
  1. 用定义当前window大小的sizer
sizer = window.GetSizer()
  1. 将按钮小部件插入到window中:
sizer.Insert(0, button, 0, wx.ALIGN_CENTER)
  1. 将滑块小部件插入window的中:
sizer.Insert(2, n_slider, 0, wx.ALIGN_RIGHT)
  1. wxPython后端上显示图形:
plt.show()

工作原理

这是前面代码的解释:

  • 前两个步骤使用plt.figure(),轴域和数据定义图形,并在图形上绘制和极坐标图。
  • window = fig.canvas.manager.window可以通过其窗口访问wxPython应用。
  • def update(event):wx.Slider()小部件的回调函数。 在这里,我们必须将event参数传递给函数:
    • n=n_slider.GetValue()获取滑块的当前值。 请注意,语法与 Tkinter 相比有所不同。
    • 此函数的其余三个步骤与 Tkinter 完全相同。
  • n_slider = wx.Slider(window, wx.ID_ANY, 3, 3, 10, size=(250,10), style=(wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | wx.SL_LABELS))定义了滑块小部件:
    • window指定滑块是主窗口的子对象。
    • wx.ID_ANY指定为此小部件使用系统生成的标识号。
    • 滑块第一次出现时的初始值为 3。
    • 3 和 10 是滑块的起始值和结束值。
    • size=(250,10)指定滑块控件的长度和高度(以像素为单位)。
    • style()指定滑块的各种参数:
      • wx.SL_AUTOTICKS指定滑块应显示刻度线。
      • wx.SL_HORIZONTAL指定滑块应水平放置。
      • wx.SL_LABELS指定滑块的开始,当前值和结束值的打印标签。
  • n_slider.Bind(wx.EVT_SCROLL, update)将滑块的滚动事件与对应的回调函数update()连接起来。
  • def close(event):退出按钮的回调函数。 它基本上关闭了所有数字。
  • button = wx.Button(window, wx.ID_ANY, "Quit")定义 WxPython 的按钮小部件:
    • window指定按钮是主窗口的子对象
    • wx.ID_ANY指定为此小部件使用系统生成的标识号
    • "Quit"是要在按钮小部件上打印的标签
  • button.Bind(wx.EVT_BUTTON, close)将按钮的点击事件连接到相应的回调函数close()上。
  • sizer = window.GetSizer()获取当前窗口大小。sizer是 WxPython 创建绘制在图中的各种小部件的布局的方式。
  • sizer.Insert(0, button, 0, wx.ALIGN_CENTER)在主窗口中绘制按钮小部件:
    • 0指定此窗口小部件在窗口中的放置顺序。 0 表示将其放置在索引 0 处,这意味着该按钮将位于窗口的顶部。
    • button将小部件放置在图形上。
    • 0指定不进行窗口小部件的相对大小调整。
    • wx.ALIGN_CENTER指定窗口小部件应在中心对齐。
  • sizer.Insert(2, n_slider, 0, wx.ALIGN_RIGHT)将滑块控件绘制在第二个索引处。 画布会自动放置在索引 1,将滑块推到窗口底部:
    • wx.ALIGN_RIGHT指定滑块小部件应在窗口中右对齐
  • plt.show()wxPython's后端显示图形。

当您运行上述代码时,将滑块拖动到 10 时,您应该第一次看到的第一个图,第一次,的第二个图出现。您可以通过单击退出应用的 退出按钮:

将 Matplotlib 嵌入到 wxPython GUI 应用中

在本秘籍中,我们将学习如何在 wxPython GUI 应用中嵌入 Matplotlib 图。 我们将使用与本章相同的数据和绘图。

准备

导入所需的库:

import wx
import numpy as np
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wxagg import NavigationToolbar2WxAgg 

操作步骤

以下是实现逻辑所需的步骤:

  1. 定义wxPython应用:
app = wx.App()
  1. 定义显示绘图和小部件的窗口:
window = wx.Frame(None, -1, "Embedding with wxPython")
  1. 定义并实例化figurecanvastoolbar
fig = Figure(figsize=(6, 5), dpi=100)
canvas = FigureCanvas(window, -1, fig)
toolbar = NavigationToolbar2WxAgg(canvas)
  1. 定义极坐标图的轴域和数据,并绘制极坐标图:
ax = fig.add_subplot(111,projection='polar')
theta = np.arange(0., 2., 1./180.)*np.pi
ax.plot(theta, 5*np.cos(3*theta))
  1. Slider小部件定义callback函数:
def update(event):
    n=n_slider.GetValue()
    ax.clear()
    ax.plot(theta, 5*np.cos(n*theta))
    fig.canvas.draw()
  1. 定义Slider小部件并将其与callback函数绑定:
n_slider = wx.Slider(window, wx.ID_ANY, 3, 3, 10, size=(250,20),
                     style=(wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | 
                            wx.SL_LABELS)) 
n_slider.Bind(wx.EVT_SCROLL, update)
  1. 在小部件上定义要应用于文本的字体:
myfont = wx.Font(12, wx.ROMAN, wx.ITALIC, wx.BOLD)
  1. 为滑块小部件设置字体:
n_slider.SetFont(myfont)
  1. Quit按钮小部件定义callback函数:
def close(event):
    window.Close()
  1. 定义Quit按钮小部件并将其与callback函数绑定:
button = wx.Button(window, wx.ID_ANY, "Quit", size=
                  (int(fig.bbox.width),int(fig.bbox.height)*0.1))
button.Bind(wx.EVT_BUTTON, close)
  1. Quit按钮小部件中设置字体:
button.SetFont(myfont)
  1. window的初始大小设置为figure的初始大小:
window.SetInitialSize(wx.Size(int(fig.bbox.width), 
                      int(fig.bbox.height)))
  1. 定义sizer并将canvastoolbarsliderbutton小部件插入window中:
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Insert(0, canvas, 1, wx.EXPAND | wx.ALL)
sizer.Insert(1, button, 0, wx.EXPAND)
sizer.Insert(2, n_slider, 0, wx.ALIGN_RIGHT)
sizer.Insert(3, toolbar, 0, wx.ALIGN_LEFT)
  1. 设置window以适合所有小部件:
window.SetSizer(sizer)
window.Fit()
window.Show()
  1. 启动mainloop来捕获用户触发的事件:
app.MainLoop()

工作原理

这是前面代码的解释:

  • app = wx.App()启动wxPython GUI 应用。
  • window = wx.Frame(None, -1, "Embedding with wxPython")定义并实例化window对象,在其中显示,figure的对象:
    • None指定,window是父对象
    • -1指定window标识号
    • "Embedding with wxPython"指定将显示在window顶部的窗口标签
  • fig = Figure(figsize=(6, 5), dpi=100)定义并实例化figure
  • canvas = FigureCanvas(window, -1, fig)定义了连接图形并将其包装到window中的画布
  • toolbar = NavigationToolbar2WxAgg(canvas)定义toolbar并将其打包到canvas
  • 步骤 4 定义极坐标图的轴域和数据,并将其绘制在轴域上
  • def update(event):与 , 先前秘籍的callback函数相同
  • n_slider = wx.Slider()n_slider.Bind()与相同与相同与相同 ]
  • myfont = wx.Font(12, wx.ROMAN, wx.ITALIC, wx.BOLD)定义要应用于小部件上文本的字体字典,字体大小为 12,罗马字体,斜体样式和粗体。
  • n_slider.SetFont(myfont)将字体字典应用于滑块小部件上的文本
  • def close(event):Quit按钮的回调函数:
  • window.Close()关闭window GUI 应用
  • button = wx.Button()button.Bind()分别与前面相同。 我们在此处为按钮小部件指定了特定的宽度和高度,而我们在前面的秘籍中使用了默认大小
  • button.SetFont(myfont)将字体字典应用于按钮上的文本。
  • window.SetInitialSize(wx.Size(int(fig.bbox.width), int(fig.bbox.height)))设置窗口大小等于图形大小
  • sizer = wx.BoxSizer(wx.VERTICAL)为具有垂直布局的图形的布局定义缩放器对象。 这意味着添加到此滑块上的所有小部件将垂直排列,一个在另一个下方:
  • sizer.Insert(0, canvas, 1, wx.EXPAND | wx.ALL)将画布添加到索引为 0(顶部)的缩放器中;wx.EXPAND | wx.ALL确保图和画布的大小一致
  • sizer.Insert(1, button, 0, wx.EXPAND)向大小调整器添加一个按钮;wx.EXPAND指定将按钮的全长展开
    • sizer.Insert(2, n_slider, 0, wx.ALIGN_RIGHT)向右对齐将 ,滑块添加到sizer
    • sizer.Insert(3, toolbar, 0, wx.ALIGN_LEFT)将工具栏左对齐添加到大小调整器中
  • window.SetSizer(sizer)sizer布局添加到主window
  • window.Fit()调整空间,使所有小部件都正确地适合图形
  • window.Show()window中显示figure
  • app.MainLoop()启动 GUI 应用循环以捕获事件并触发各自的回调函数。

当您运行上述代码时,您应该第一次看到第一个绘图,它带有三个叶子,在将滑块拖动到滑块的末尾时,第二个绘图带有值 10 。

使用 Qt GUI 的滑块和按钮小部件

在本秘籍中,我们将学习如何利用, Qt GUI 滑块和按钮小部件代替 Matplotlib 小部件。 此代码以及和下一个秘籍中的代码均适用于, Qt4 和 Qt5 GUI 版本。

Qt GUI 非常类似于,wxPython GUI。 我们将使用QHBoxLayoutQVBoxLayout代替和sizer; 代替fig.canvas.manager.window,我们将拥有fig.canvas.setLayout; 并以connect代替Bind

准备

  1. 将后端设置为Qt5
import matplotlib
matplotlib.use('Qt5Agg')
  1. 导入所需的库:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.qt_compat import QtCore, QtWidgets, is_pyqt5, QtGui
if is_pyqt5():
    from matplotlib.backends.backend_qt5agg import FigureCanvas
else:
    from matplotlib.backends.backend_qt4agg import FigureCanvas

操作步骤

以下是实现逻辑的步骤:

  1. 定义并实例化figure
fig = plt.figure(figsize=(8, 6), dpi=100)
  1. 定义轴域和数据的极坐标图,并绘制极坐标图:
ax = fig.add_subplot(111,projection='polar')
theta = np.arange(0., 2., 1./180.)*np.pi
ax.plot(theta, 5*np.cos(4*theta))
  1. 定义滑块小部件的回调函数:
def update():
    n=n_slider.value()
    ax.clear()
    ax.plot(theta, 5*np.cos(n*theta))
    fig.canvas.draw()
  1. 定义滑块小部件:
n_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
n_slider.setRange(3, 10)
n_slider.setSingleStep(1)
n_slider.setValue(4)
n_slider.setTickPosition(QtWidgets.QSlider.TicksBelow)
n_slider.setTickInterval(1)
n_slider.setFont(QtGui.QFont("Arial",30))
  1. 将,滑块小部件与,对应的callback函数连接:
n_slider.sliderReleased.connect(update)
  1. 为的退出按钮定义callback函数:
def close():
    plt.close('all') 
  1. 定义和退出按钮小部件:
button = QtWidgets.QPushButton("Quit")
button.setGeometry(QtCore.QRect(250, 0, 75, 25))
  1. 将和退出按钮与和对应的callback函数连接起来:
button.clicked.connect(close)
  1. 在垂直框内定义,然后向其添加滑块和按钮小部件:
hbox = QtWidgets.QHBoxLayout()
hbox.addWidget(n_slider)
hbox.addWidget(button)
  1. 定义垂直框并添加,隔离对象和hbox水平布局框:
vbox = QtWidgets.QVBoxLayout()
vspace = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
vbox.addItem(vspace)
vbox.addSpacing(20)
vbox.addLayout(hbox)
  1. vbox垂直布局框连接到图形的画布:
fig.canvas.setLayout(vbox)
  1. 在 Qt 后端上显示该图:
plt.show()

工作原理

这是前面代码的解释:

  • 您在前面的秘籍中已经看到了前两个步骤。
  • def update():是滑块小部件的回调函数。n=n_slider.value()获取滑块的当前值。 请注意和前面部分中其他两个 GUI 的语法差异。 此函数的其余三个语句与前面的秘籍完全相同和
  • n_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)定义Qt的滑块控件,并指定应水平绘制:
    • n_slider.setRange(3, 10)指定滑块的最小值和最大值
    • n_slider.setSingleStep(1)指定在滑块上拖动鼠标时每个步骤的大小。 在这里,我们将其指定为 1
    • n_slider.setValue(4)指定滑块首次出现时的初始值
    • n_slider.setTickPosition(QtWidgets.QSlider.TicksBelow)指定 , 滑块应绘制刻度线,并且应在滑块下方
    • n_slider.setTickInterval(1)指定刻度之间的间隔。 在这里,我们将其指定为 1
    • n_slider.setFont(QtGui.QFont("Arial",30))的字体设置为Arial, 设置字体大小为 30。
  • n_slider.sliderReleased.connect(update)将滑块事件"sliderReleased"与对应的回调函数,update()连接起来
  • def close():Quit按钮的回调函数,它与先前的 GUI 完全相同
  • button = QtWidgets.QPushButton("Quit")指定带有标签Quit的按钮小部件
  • button.setGeometry(QtCore.QRect(250, 0, 75, 25))定义按钮的放置位置,长度和高度。(250, 0, 75, 25)从图的左侧指定 250 像素,从底部指定 0 像素,长度为 75 像素,高度为 25 像素
  • button.clicked.connect(close)将按钮事件clicked与对应的回调函数close()连接,
  • hbox = QtWidgets.QHBoxLayout()定义水平布局框:
    • hbox.addWidget(n_slider)将滑块添加到水平框
    • hbox.addWidget(button)将按钮小部件添加到水平框
    • 滑块和按钮小部件将在一行中水平相邻放置
  • vbox = QtWidgets.QVBoxLayout()定义垂直布局框:
    • vspace = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)创建一个要包含在垂直布局框中的空间对象,以便在图的底部绘制 S 封面和按钮小部件。
    • vbox.addItem(vspace)向垂直布局框添加垂直空间
    • vbox.addSpacing(20)为垂直布局框中的像素增加了额外的空间
    • vbox.addLayout(hbox)将水平布局框添加到垂直布局框中
    • fig.canvas.setLayout(vbox)将垂直布局框连接到图形的画布。
  • plt.show()Qt5后端显示图形。

运行前面的代码后,您应该第一次看到第一个图,将滑块拖动到 5 后,您应该看到第二个图:

将 Matplotlib 嵌入到 Qt GUI 应用中

在本秘籍中,我们将学习如何在Qt GUI 应用中嵌入 Matplotlib 画布。

准备

导入所需的库:

import sys
import numpy as np
from matplotlib.figure import Figure
from matplotlib.backends.qt_compat import QtCore, QtWidgets, is_pyqt5, 
     QtGui

if is_pyqt5():
    from matplotlib.backends.backend_qt5agg import (
                                    FigureCanvas, NavigationToolbar2QT 
                                    as  NavigationToolbar)
else:
    from matplotlib.backends.backend_qt4agg import (
                                    FigureCanvas, NavigationToolbar2QT 
                                    as NavigationToolbar)

操作步骤

以下是实现逻辑的步骤:

  1. 定义 GUI 应用和窗口来绘制图形:
qApp = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QMainWindow()
  1. 定义图形,画布和工具栏:
fig = Figure(figsize=(8, 6), dpi=100)
canvas = FigureCanvas(fig)
toolbar = NavigationToolbar(canvas, window)
  1. 定义轴域和数据,并绘制极坐标图:
ax = fig.add_subplot(111,projection='polar')
theta = np.arange(0., 2., 1./180.)*np.pi
ax.plot(theta, 5*np.cos(4*theta))
  1. 定义滑块的回调函数:
def update():
    n=n_slider.value()
    ax.clear()
    ax.plot(theta, 5*np.cos(n*theta))
    fig.canvas.draw()
  1. 定义滑块小部件并将其与对应的回调函数连接:
n_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
n_slider.setRange(3, 10)
n_slider.setSingleStep(1)
n_slider.setValue(4)
n_slider.setTickPosition(QtWidgets.QSlider.TicksBelow)
n_slider.setTickInterval(1)
n_slider.setFont(QtGui.QFont("Arial",20)) 
n_slider.sliderReleased.connect(update)
  1. 退出按钮定义回调函数:
def close():
    window.close()
  1. 定义退出按钮小部件,并将其连接到相应的回调函数:
button = QtWidgets.QPushButton("Quit")
button.setFont(QtGui.QFont("Arial",30))
button.clicked.connect(close)
  1. 定义水平框,然后将滑块及其标签项放在其中:
hbox = QtWidgets.QHBoxLayout()
minn = QtWidgets.QLabel('3')
minn.setFont(QtGui.QFont("Arial",20))
maxn = QtWidgets.QLabel('10')
maxn.setFont(QtGui.QFont("Arial",20))
hbox.addWidget(minn)
hbox.addWidget(n_slider)
hbox.addWidget(maxn)
  1. 定义垂直框并在其中放置工具栏,间隔,按钮小部件和水平框的:
vbox = QtWidgets.QVBoxLayout()
vbox.addWidget(toolbar)
vspace = QtWidgets.QSpacerItem(0, 750)
vbox.addItem(vspace)
vbox.addSpacing(20)
vbox.addLayout(hbox)
vbox.addWidget(button)
  1. 将垂直框连接到图形画布:
fig.canvas.setLayout(vbox)
  1. 设置窗口标题并显示它:
window.setWindowTitle("Embedding with Qt")
window.setCentralWidget(canvas)
window.show()
  1. 启动应用循环以捕获用户操作:
qApp.exec_()

工作原理

这是前面代码的解释:

  • qApp = QtWidgets.QApplication(sys.argv)调用Qt GUI 应用。 它期望sys.argv作为参数,即使它为空
  • window = QtWidgets.QMainWindow()定义显示图形的主窗口
  • fig = Figure(figsize=(8, 6), dpi=100)定义图形
  • canvas = FigureCanvas(fig)定义画布并将图形附加到画布上
  • toolbar = NavigationToolbar(canvas, window)定义了 GUI 的工具栏,并将其附加到主windowcanvas
  • 步骤 3 定义极坐标图的轴域和数据,并绘制极坐标图
  • def update():是滑块的回调函数, 是 ,与前面的秘籍完全相同
  • n_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)定义了滑块,并且所有参数都完全相同 , 与先前的秘籍
  • n_slider.sliderReleased.connect(update)"sliderReleased"事件连接到相应的回调函数,update()
  • def close():Quit按钮的回调函数,它仅关闭窗口,而不是应用
  • button = QtWidgets.QPushButton("Quit")定义Qt的按钮小部件。 与 , 前面的秘籍不同,此处无需设置button.setGeometry(QtCore.QRect(250, -10, 75, 25)),因为我们将按钮单独垂直放置在一行中
  • button.setFont(QtGui.QFont("Arial",30))设置字体为Arial,字体大小为 30
  • button.clicked.connect(close)将按钮的"clicked"事件连接到相应的回调函数,close()
  • hbox = QtWidgets.QHBoxLayout()定义水平布局框:
    • minn = QtWidgets.QLabel('3')为要显示的滑块最小值定义标签对象
    • minn.setFont(QtGui.QFont("Arial",20))将字体和字体大小设置为最小值
    • maxn = QtWidgets.QLabel('10')定义要显示的滑块最大值的标签对象
    • maxn.setFont(QtGui.QFont("Arial",20))设置最大值的字体和字体大小
    • hbox.addWidget(minn)minn标签添加到hbox
    • hbox.addWidget(n_slider)将滑块添加到hbox
    • hbox.addWidget(maxn)将标签maxn添加到hox
  • vbox = QtWidgets.QVBoxLayout()定义垂直布局框
    • vbox.addWidget(toolbar)添加在图顶部的工具栏
    • vspace = QtWidgets.QSpacerItem(0, 750)定义空间对象
    • vbox.addItem(vspace)在画布/图形将出现的位置添加空间对象
    • vbox.addSpacing(20)在画布下添加了 20 个像素的额外空间
    • vbox.addLayout(hbox)添加包含 , 滑块以及相关的最小和最大标签的hbox
    • vbox.addWidget(button)将按钮小部件添加到vox
  • fig.canvas.setLayout(vbox)向图形画布添加了vbox布局。
  • window.setWindowTitle("Embedding with Qt")设置主窗口的标题
  • window.setCentralWidget(canvas)将画布设置为中央小部件,以便主极图出现在中央
  • window.show()显示图形窗口
  • qApp.exec_()调用主循环以捕获事件并触发相应的回调函数

当您运行上述代码时,将滑块第一次拖动到 8 时,您应该会第一次看到左侧的图,而右侧则是右侧的图。您可以通过单击退出按钮退出应用: