开源软件架构-matplotlib

matplotlib是基于Python的绘图库,广泛用于Python科学计算界。它完整支持二维绘图以及部分支持三维绘图。该绘图库致力于能适应广泛的用户需求。它可以根据所选的用户接口工具来嵌入绘图算法。与此同时,对于使用 GTK+、Qt、Tk、FLTK、wxWidgets 与 Cocoa 的所有主要桌面操作系统,matplotlib 能支持交互式绘图。在Python的交互式shell中,我们可以使用简单的、过程式的命令交互式地调用 matplotlib 来生成图形,与使用 Mathematica、IDL或者MATLAB绘图非常相似。matplotlib也可以嵌入到无报文头的 Web 服务器中,以供基于光栅(如 PNG 格式)与向量(如 Postscript、PDF 以及纸面效果很好的 SVG 格式)这两种格式的图形硬拷贝。

原文地址:http://aosabook.org/en/matplotlib.html;作者:John Hunter 和 Michael Droettboom

matplotlib的起源

我们其中一位开发者(JohnHunter)与他的研究癫痫症的同事们试图在不借助专有软件的情况下进行脑皮层电图(ECoG)分析,于是便有了最初的matplotlib。John Hunter当时所在的实验室只有一份电图分析软件的许可证,但有各式各样的工作人员,如研究生、医科学生、博士后、实习生、以及研究员,他们轮流共享该专有软件的硬件电子锁。生物医学界广泛使用 MATLAB 进行数据分析与可视化,所以Hunter着手使用基于 MATLAB 的 matplotlib 来代替专有软件,这样很多研究员都可以使用并且对其进行扩展。但是 MATLAB 天生将数据当作浮点数的数组来处理。然而在实际情况中,癫痫手术患者的医疗记录具有多种数据形式(CT、MRI、ECoG 与 EEG 等),并且存储在不同的服务器上。 MATLAB 作为数据管理系统勉强能应付这样的复杂性。由于感到 MATLAB 不适合于这项任务,Hunter 开始编写一个新的建立在用户接口工具 GTK+(当时是 Linux 下的主流桌面视窗系统)之上的 Python 应用程序。

所以 matplotlib 这一 GTK+应用程序最初便被开发成 EEG/ECoG 可视化工具。这样的用例决定了它最初的软件架构。matplotlib 最初的设计也服务于另一个目的:代替命令驱动的交互式图形生成(这一点 MATLAB 做得很好)工具。MATLAB 的设计方法使得加载数据文件与绘图这样的任务非常简单,而要使用完全面向对象的 API 则会在语法上过于繁琐。所以 matplotlib 也供状态化的脚本编程接口来快速、简单地生成与 MATLAB 类似的图形。因为 matplotlib 是 Python 库,所以用户可以使用 Python 中各种丰富的数据结构,如列表(list)、辞典(dict)与集合(set)等等。

开源软件架构-matplotlib

matplotlib 软件架构概述

顶层的 matplotlib 对象名为 Figure,它包含与管理某个图形的所有元素。matplotlib 必须完成的一个核心架构性任务是实现 Figure 的绘制与操作框架,并且做到该框架与 Figure到用户视窗接口或硬拷贝渲染行为是分离的。这使得我们可以为 Figure 添加越来越复杂的特性与逻辑,同时保持“后端”或输出设备的相对简化。matplotlib 不仅封装了用于向多种设备渲染的绘图接口,还封装了基本事件处理以及多数流行的用户界面工具的视窗功能。因此,用 户可以创建相当丰富的交互式图形算法与用户界面工具( 用到可能存在的鼠标与键盘),而又不必修改 matplotlib 已经支持的6种界面工具。

要实现这些,matplotlib 的架构被逻辑性地分为三层。这三层逻辑可以视为一个栈。每层逻辑知道如何与其下的一层逻辑进行通信,但在下层逻辑看来,上层是透明的。这三层从底向上分别为:后端、美工与脚本。

后端

matplotlib 逻辑栈最底层是后端,它具体实现了下面的抽象接口类:

  • FigureCanvas 对绘图表面(如“绘图纸”)的概念进行封装。
  • Renderer 执行绘图动作(如“画笔”)。
  • Event 处理键盘与鼠标事件这样的用户输入。

对于如Qt这样的用户界面工具,FigureCanvas 中包含的具体实现可以完成三个任务:将 自身嵌入到原生的Qt视窗(QtGui.QMainWindow)中,能将 matplotlib 的 Renderer 命令转换到 canvas 上(QtGui.QPainter),以及将原生 Qt 事件转换到 matplotlib 的 Event框架下 (后者产生回调信号让上行监听者进行处理 )。抽象基类定义在 matplotlib.backend_bases 中,且所有派生类都定义在如 matplotlib.backends.backend_qt4agg 这样的专用模块中。对于专门生成硬拷贝输出(如 PDF、PNG、SVG 或 PS)的纯图像后端而言,FigureCanvas 的实现可能只是简单地建立一个类似文件的对象,其中定义默认的文件头、字体与宏函数,以及 Renderer 创建的个别对象(如直线、文本与矩形等)。

Renderer 的任务是供底层的绘图接口,即在画布上绘图的动作。上文已经提到,最初的matplotlib 程序是一个基于 GTK+ 的 ECoG 查看器,而且很多早期设计灵感都源自当时已有的 GDK/GTK+ 的 API。最初 Renderer 的 API 源自 GDK 的 Drawable 接口,后者实现了 draw_point、draw_line、draw_rectangle、draw_image、draw_polygon 以及draw_glyphs 这样的基本方法。我们完成的每个不同后端-最早有 PostScript 与GD-都实现了 GDK 的 Drawable,并将其转换为独立于后端的原生绘图命令。如上所述,这毫无必要地增加了后端的实现复杂度,原因是单独实现 Drawable 造成函数泛滥。此后,Renderer 已经被极大的简化,将 matplotlib 移植到新的用户界面或文件格式已经是非常简单的过程。

一个对 matplotlib 有利的设计决定是支持使用 C++ 模板库 Anti-Grain Geometry(缩写为 agg)的基于像素点的核心渲染器。这是一个高性能库,可以进行2D反锯齿渲染,生成的图像非常漂亮。matplotlib 支持将 agg 后端渲染的像素缓存插入到每种支持的用户界面中,所以在不同的 UI 与操作系统下都能得到精确像素点的图形。因为 matplotlib 生成的 PNG 输出也使用 agg 渲染器,所以硬拷贝与屏幕显示完全相同,也就是说在不同的 UI 与操作系统下,PNG 的输出所见即所得。

matplotlib 的 Event 框架将 key-press-eventmouse-motion-event 这样的潜在 UI 事件映射到 KeyEventMouseEvent 类。用户可以连接到这些事件进行函数回调,以及图形与数据的交互,如要 pick 一个或一组数据点,或对图形或其元素的某方面性质进行操作。下面的示例代码演示了当用户键入‘t’ 时,对 Axes 窗口中的线段进行显示开关。

对底层 UI 事件框架的抽象使得 matplotlib 的开发者与最终用户都可以编写 UI 事件处理代码,而且“一次编写,随处运行”。譬如,在所有用户界面下都可以对 matplotlib 图像进行交互式平移与放缩,这种交互式操作就是在 matplotlib 的事件框架下实现的。

Artis 层

Artist 层次结构处于 matplotlib 的中间层,负责很大一部分繁重的计算任务。延续之前将后端的 FigureCanvas 看作画纸的比喻,Artis 对象知道如何用 Renderer(画笔)在画布上画出墨迹。matplotlib 中的 Figure 就是一个 Artist 对象实例。标题、直线、刻度标记以及图像等等都对应某个 Artist 实例(如图11.3)。Artist 的基类是 matplotlib.artist.Artist,其中包含所有 Artist 的共享属性,包括从美工坐标系统到画布坐标系统的变换(后面将详细介绍)、可见性、定义用户可绘制区域的剪切板、标签,以及处理“选中”这样的用户交互动作的接口,即在美工层检测鼠标点击事件。

开源软件架构-matplotlib

开源软件架构-matplotlib

Artist 层于后端之间的耦合性存在于 draw 方法中。譬如,下面假想的 SomeArtist 类是Artist 的子类,它要实现的关键方法是 draw,用来传递给后端的渲染器。Artist 不知道渲染器要向哪种后端进行绘制(PDF、SVG 与 GTK+绘图区等),但知道 Renderer 的 API,并且会调用适当的方法(draw_text 或 draw_path)。因为 Renderer 能访问画布,并且知道如何绘制,所以 draw 方法将 Artist 的抽象表示转换为像素缓存中的颜色、SVG文件中的轨迹或者其他具体表示。

该层次结构中有两种类型的 Artist。基本 Artist 表示我们在图形中能看到的一类对象,如 Line2D、Rectangle、Circle 与 Text。复合 Artist 是 Artist 的集合,如 Axis、Tick、Axes 与 Figure。每个复合 Artsit 可能包含其他复合 Artist 与基本 Artist。譬如,Figure 包含一个或多个 Axes,并且 Figure 的背景是基本的 Rectangle。

最重要的复合 Artist 是 Axes,其中定义了大多数 matplot 的绘图方法。Axes 不仅仅包含大多数构成绘图背景(如标记、轴线、网格线、色块等)的图形元素,还包括了大量生成基 本 Artist 并添加到 Axes 实例中的帮助函数。譬如,表11.1 列出了一些 Axes 函数, 这些函数进行对象的绘制,并将它们存储在 Axes 实例中。

开源软件架构-matplotlib

下面这个简单的 Python 脚本解释了以上架构。它定义了后端,将 Figure 链接至该后端,然后使用数组库 numpy 创建10,000个正太分布的随机数,最后绘制出它们的柱状图。

脚本层(pyplot)

使用以上 API 的脚本效果很好,尤其是对于程序员而言,并且在编写 Web 应用服务器、

UI 应用程序或者是与其他开发人员共享的脚本时,这通常是比较合适的编程范式。对于日常用途,尤其对于非专业程序员而要完成一些交互式的研究工作的实验科学家而言,以上 API 的语法可能有些难以掌握。大多数用于数据分析与可视化的专用语言都会供轻量级的脚本接口来简化一些常见任务。matplotlib 在其 matplotlib.pyplot 接口中便实现了这一 点。以上代码改用 pyplot 之后如下所示。

开源软件架构-matplotlib

pyplot 是一个状态化接口,大部分工作是处理样本文件的图形与坐标的生成,以及与所选后端的连接。它还维护了模块级的内部数据结构。这些数据结构表示了直接接收绘图命令的当前图形与坐标。

下面仔细分析示例脚本中比较重要的几行,观察其内部状态的管理方式。

  • import matplotlib.pyplot as plt:当 pyplot 模块被加载时,它分析本地配置文件。配置文件除了完成一些其他工作外,主要声明了默认的后端。可能是类似 QtAgg 的用户接口后端,于是上面的脚本将导入 GUI 框架并启动嵌入了图形的 Qt 窗口;或者可以是一个类似 Agg 的纯图像后端,这样脚本会生成硬拷贝输出然后退出。
  • plt.hist(x, 100):这是脚本中第一个绘图命令。pyplot 会检测其内部数据结构已查看是否存在当前 Figure 实例。如果存在,则取当前 Axes,并将绘图行为导向 Axes.hist 的 API 调用。在该脚本中不存在 Figure 实例,所以会生成一个 FIgure 与 Axes,并将它们设为当前值,然后将绘图行为导向 Axes.hist。
  • plt.title(r’Normal distribution with $\mu=0, \sigma=1$’):(未完)

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注