Matplotlib 绘图秘籍(二)
五、文件输出
在本章中,我们将介绍:
- 生成 PNG 图片文件
- 处理透明度
- 控制输出分辨率
- 生成 PDF 或 SVG 文档
- 处理多页 PDF 文档
简介
像其他类型的技术图表一样,科学图表很少是独立的文档,它们应成为文档的一部分。 matplotlib 可以将任何图形渲染为各种常见的文件格式,例如 PNG,EPS,SVG 和 PDF。 默认情况下,显示的图形具有简约的用户界面,可让您将图形保存到文件中。 但是,如果必须生成大量图形,则此方法不方便。 此外,您可能希望每次更新某些数据时都能生成一个新图形。 在本章中,我们将探讨 matplotlib 的文件输出功能。 除了以编程方式生成文件输出之外,我们还将学习如何控制重要因素,例如输出的分辨率和大小以及透明度。
生成 PNG 图片文件
默认情况下,matplotlib 在带有基本用户界面的窗口中显示图形。 该界面允许您将图形保存到文件中。 尽管这是一种合理的原型制作方法,但在几种常见的使用情况下并不方便。 例如,您可能想生成一堆图片,这些图片将包含在自动生成的报告中。 您可能想为每个输入文件生成一张图片作为批处理器。 matplotlib 允许您非常灵活地将图形直接保存到图片文件中。
首先,我们将了解如何将图形输出到 PNG 文件。 PNG 文件是位图输出的理想选择,它也是位图图片的实际标准。 这是一个得到良好支持的标准; 它依靠无损压缩算法(从而避免了难看的压缩伪像),并处理透明性。
操作步骤
当要求 matplotlib 渲染图形时,我们将使用pyplot.savefig() 调用而不是通常的pyplot.show() 调用:
import numpy as np
from matplotlib import pyplot as plt
X = np.linspace(-10, 10, 1024)
Y = np.sinc(X)
plt.plot(X, Y)
plt.savefig('sinc.png', c = 'k')
该脚本不会在带有用户界面的窗口中显示图形,而只会创建一个名为sinc.png的文件。 它的分辨率为800 x 600像素,采用 8 位颜色(每像素 24 位)。 该文件表示以下图形:
工作原理
函数pyplot.savefig()的工作方式与pyplot.show()完全相同,它解释了发给pyplot的所有命令并生成图形。 唯一的区别是在处理结束时执行的操作。 pyplot.show()函数将图片数据发送到它可以使用的任何用户界面库,而pyplot.savefig()函数将该数据写入文件。 因此,无论最终输出的性质如何,所有命令的工作方式都完全相同。
pyplot.savefig()函数提供了多种可选参数,我们将在以下各节中进行探讨。
处理透明度
创建图形时,很少将它们单独使用。 例如,数字可以是网站或演示文稿的一部分。 在这种情况下,这些图形必须与其他图形集成在一起。 透明度对于这种整合非常重要-数字将以美观和一致的方式与背景融为一体。 在本秘籍中,我们将看到如何输出透明的图形。
操作步骤
为了演示的透明度,我们将创建一个图形并将其嵌入到网页中。 该图将与网页背景融合在一起。 在此秘籍中创建的所有文件应位于同一目录中。 我们将在本节中执行以下操作:
- 将图形渲染为具有透明背景的 PNG 文件
- 制作一个包含图形的 HTML 页面
将图形渲染为具有透明背景的 PNG 文件
要将图形渲染为 PNG 文件,我们将再次使用pyplot.savefig()。 但是,可选参数transparent设置为True,如以下脚本所示:
import numpy as np
import matplotlib.pyplot as plt
X = np.linspace(-10, 10, 1024)
Y = np.sinc(X)
plt.plot(X, Y, c = 'k')
plt.savefig('sinc.png', transparent = True)
制作一个包含图形的 HTML 页面
让我们在具有背景的网页上使用 PNG 文件。 最小的 HTML 代码显示sinc.png,并在背景中平铺background.png图片,如下所示:
<html>
<head>
<style>
body {
background: white url(background.png);
}
</style>
</head>
<body>
<img src='sinc.png' width='800 height='600'></img>
</body>
</html>
使用浏览器查看网页时,该图会与平铺的背景融合在一起,如下图所示。 在其他情况下(例如在演示中使用图形时)也会发生相同的情况。
工作原理
默认情况下,的pyplot.savefig()将不在输出中包含透明度信息。 例如,当我们输出 PNG 图片时,默认情况下,PNG 文件每个像素使用 24 位,仅在 8 位上存储像素的红色,绿色和蓝色分量。 但是,启用transparent输出时,pyplot.savefig()每个像素将使用 32 位-另一个通道 alpha 通道存储透明度信息。
更多
到目前为止,唯一涉及图形背景的透明度信息是图形的元素是背景(完全透明)或前景(完全不透明)。 但是,我们可以控制使用 matplotlib 生成的任何图形的透明度。
matplotlib 允许您将图形的透明级别定义为可选参数alpha。 如果alpha等于1,则图形将完全不透明,这是默认设置。 如果alpha等于0,则该图形将完全不可见。 中间值alpha将提供部分透明度。 可选参数 alpha可用于大多数图形函数。
以下脚本演示了这一点:
import numpy as np
import matplotlib.pyplot as plt
name_list = ('Omar', 'Serguey', 'Max', 'Zhou', 'Abidin')
value_list = np.random.randint(99, size=len(name_list))
pos_list = np.arange(len(name_list))
plt.bar(pos_list, value_list, alpha = .75, color = '.75', align = 'center')
plt.xticks(pos_list, name_list)
plt.savefig('bar.png', transparent = True)
前面的脚本将创建一个条形图,并将该图保存到 PNG 文件中。 在网页中使用此 PNG 文件时,我们不仅可以看到图形的背景融合在一起,而且图形的内容也可以融合进去,如以下屏幕截图所示:
控制输出分辨率
默认情况下,当将输出用于位图图片时,matplotlib 为我们选择输出的大小和分辨率。 根据位图图片的用途,我们可能要自己选择分辨率。 例如,如果图片要成为大海报的一部分,我们可能更喜欢高分辨率,或者,如果我们想生成缩略图,则分辨率会非常低。 在本秘籍中,我们将学习如何控制输出分辨率。
操作步骤
pyplot.savefig()函数提供了一个可选参数来控制输出分辨率,如以下脚本所示:
import numpy as np
from matplotlib import pyplot as plt
X = np.linspace(-10, 10, 1024)
Y = np.sinc(X)
plt.plot(X, Y)
plt.savefig('sinc.png', dpi = 300)
前面的脚本绘制一条曲线并将结果输出到文件中。 而不是通常的800 x 600像素输出,而是2400 x 1800像素。
工作原理
pyplot.savefig()函数具有一个称为dpi的可选参数。 此参数控制以 DPI(每英寸点数)表示的图片的分辨率。 对于更熟悉公制单位的人,1 英寸等于 2.54 厘米。 该单位表示在实际文档的 1 英寸中可以找到多少个点。 好的喷墨打印机将以 300 dpi 的分辨率打印文档。 高质量的激光打印机可以轻松以 600 dpi 的速度打印。
默认情况下,matplotlib 将输出8 x 6空间单位(长宽比为 4/3)的图形。 在 matplotlib 中,1 个空间单位等于 100 个像素。 因此,默认情况下,matplotlib 将提供800 x 600像素的图片文件。 如果我们使用dpi = 300,则图片大小将为8 * 300 x 6 * 300,即2400 x 1800像素。
更多
在第 4 章“处理图形”,我们看到了如何控制长宽比。 如果我们将宽高比和 DPI 结合起来,则可以完全控制图片的一般比例。 假设我们要在512 x 512像素的图片中显示一个六边形。 我们将执行以下操作:
import numpy as np
import matplotlib.pyplot as plt
theta = np.linspace(0, 2 * np.pi, 8)
points = np.vstack((np.cos(theta), np.sin(theta))).transpose()
plt.figure(figsize=(4., 4.))
plt.gca().add_patch(plt.Polygon(points, color = '.75'))
plt.grid(True)
plt.axis('scaled')
plt.savefig('polygon.png', dpi = 128)
上一个脚本的结果将是以下图形:
我们将图形显示在4 x 4单位区域上,并以 128 dpi 的分辨率输出-输出将为512 x 512像素。 我们还可以显示 512 像素,单位面积为8 x 8,并以 64 dpi 的分辨率输出。 这将为我们带来以下结果:
标注更小,网格线更细。 标注和线条的粗细具有它们自己的默认值,以空间单位表示。 因此,将输出分辨率除以 2 将使标注变小两倍。 如果您开始操纵空间分辨率和每个单独的元素大小,它会很快变得混乱。 通常,最好只相对彼此更改单个元素的大小(注解和厚度)。 如果要使所有标注一致地变大,则可以使用分辨率设置。
生成 PDF 或 SVG 文档
到位图图片的输出并不总是理想的。 位图图片将图片表示为一个给定比例的像素数组。 放大,您将得到一些众所周知的伪影(锯齿,阶梯,模糊等),具体取决于所采用的采样算法。 向量图片是尺度不变的。 无论您以多大的比例观察它们,都不会丢失任何细节或工件。 这样,在编写较大的文档(例如日记帐文章)时,向量图片是理想的。 调整图形比例时,我们不需要生成新图片。 matplotlib 可以输出向量图片,例如 PDF 和 SVG 图片。
操作步骤
PDF 文档的输出很简单,如以下脚本所示:
import numpy as np
from matplotlib import pyplot as plt
X = np.linspace(-10, 10, 1024)
Y = np.sinc(X)
plt.plot(X, Y)
plt.savefig('sinc.pdf')
前面的脚本将绘制一个图形并将其保存到名为sinc.pdf的文件中。
工作原理
我们已经讨论了pyplot.savefig()函数,该函数将图形渲染到文件中。 文件名足以指定文件是 PNG,PDF 还是 SVG。 matplotlib 将查看文件名的文件扩展名并推断文件的类型。
更多
在某些情况下,您可能想以给定格式保存文件,例如 SVG,但是您不希望文件名具有.svg扩展名。 pyplot.savefig参数(作为可选参数)使您可以执行此操作。 设置format = 'svg', pyplot.savefig不会从传递给函数的文件名推断出输出文件类型,而是会使用传递给格式的名称。
处理多页 PDF 文档
在第 4 章“处理图形”中,看到看到了如何在一张 matplotlib 图形中构成多个图形。 这使您可以创建非常复杂的图。 使用 PDF 输出时,我们必须记住该图形必须适合一页。 但是,通过一些额外的工作,我们可以输出多页的 PDF 文档。 请注意,matplotlib 是科学的绘图包,而不是文档合成系统,例如 LaTeX 或 ReportLab。 因此,对多个页面的支持非常少。 在本秘籍中,我们将了解如何生成多页 PDF 文档。
操作步骤
为了演示使用 matplotlib 的多页 PDF 输出,让我们生成 15 个条形图,每页五个图。 以下脚本将输出名为barcharts.pdf的三页文档:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
## Generate the data
data = np.random.randn(15, 1024)
## The PDF document
pdf_pages = PdfPages('barcharts.pdf')
## Generate the pages
plots_count = data.shape[0]
plots_per_page = 5
pages_count = int(np.ceil(plots_count / float(plots_per_page)))
grid_size = (plots_per_page, 1)
for i, samples in enumerate(data):
# Create a figure instance (ie. a new page) if needed
if i % plots_per_page == 0:
fig = plt.figure(figsize=(8.27, 11.69), dpi=100)
# Plot one bar chart
plt.subplot2grid(grid_size, (i % plots_per_page, 0))
plt.hist(samples, 32, normed=1, facecolor='.5', alpha=0.75)
# Close the page if needed
if (i + 1) % plots_per_page == 0 or (i + 1) == plots_count:
plt.tight_layout()
pdf_pages.savefig(fig)
## Write the PDF document to the disk
pdf_pages.close()
条形图整齐地布置在三个页面上,如以下屏幕截图所示:
工作原理
通常,使用 matplotlib 的脚本不依赖于输出的类型。 我们始终将pyplot.savefig()用于各种输出。 但是,在这里,我们必须处理 PDF 输出的细节。 因此,此脚本执行以下操作:
- 导入可处理 PDF 输出的 matplotlib 包
matplotlib.backends.backend_pdf。 从这个包中,我们只需要PdfPages对象。 该对象代表 PDF 文档。 - 创建名为
pdf_pages的 PDF 文档实例。 这是通过使用pdf_pages = PdfPages('histograms.pdf')函数完成的。 - 要生成每个页面,它会执行以下操作:
- 创建一个具有 A4 页面尺寸的新图形实例。 这是通过使用
fig = plot.figure(figsize=(8.27, 11.69), dpi=100)函数完成的。 - 用图填充图。 在此示例中,我们使用子图在一个图形中放置多个图。
- 在 PDF 文档中创建一个新页面,其中将包含我们的图形。 这是通过使用
pdf_pages.savefig(fig)函数完成的。
- 创建一个具有 A4 页面尺寸的新图形实例。 这是通过使用
- 生成所需的所有图形后,即可使用
pdf_pages.close()函数输出文档。
请注意,页面在此处是一个相当笼统的术语。 页面不必具有特定大小。 不同的页面可以具有不同的大小。 编写脚本是为了从总的图形数和每页的图形数自动计算出页面数。
更多
由于 matplotlib 并不是完整的文档组成系统,因此如果没有可怕的技巧,就很难实现页码或页眉之类的东西。 如果确实需要这些功能,明智的做法是将每个图形生成为单个 PDF 文档。 然后,这些数字将由文档撰写系统用来自动生成 PDF 文档。 例如,DocBook 是一个使用 XML 描述来生成 PDF 或其他常见格式的文档的系统。 当然,这是一个完全不同的努力规模。
六、处理地图
在本章中,我们将介绍以下主题:
- 可视化 2D 数组的内容
- 向图中添加有色图例
- 可视化非均匀 2D 数据
- 可视化 2D 标量场
- 可视化等高线
- 可视化 2D 向量场
- 可视化 2D 向量场的流线
简介
到现在为止,我们已经涵盖了基本一维字符数据的绘图基元。 通过绘制某种类型的地图,您可以可视化两个变量对第三个变量的影响。 想象一下,您的气象站遍布全国。 地图可视化可以一目了然显示全国的降雨和风向分布。 matplotlib 提供了由简单 API 驱动的强大原语来创建地图。
可视化 2D 数组的内容
让我们从最基本方案开始。 我们有一个 2D 数组,我们想可视化它的内容。 例如,我们将可视化曼德勃罗集。 曼德勃罗集(一种著名的分形形状)将多次迭代与平面上的每个点相关联。
操作步骤
我们将首先用值填充 2D 正方形数组,然后调用pyplot.imshow()到使其可视化,如以下代码所示:
import numpy as np
import matplotlib.cm as cm
from matplotlib import pyplot as plt
def iter_count(C, max_iter):
X = C
for n in range(max_iter):
if abs(X) > 2.:
return n
X = X ** 2 + C
return max_iter
N = 512
max_iter = 64
xmin, xmax, ymin, ymax = -2.2, .8, -1.5, 1.5
X = np.linspace(xmin, xmax, N)
Y = np.linspace(ymin, ymax, N)
Z = np.empty((N, N))
for i, y in enumerate(Y):
for j, x in enumerate(X):
Z[i, j] = iter_count(complex(x, y), max_iter)
plt.imshow(Z, cmap = cm.gray)
plt.show()
根据您的计算机,此脚本可能需要几秒钟到几分钟才能产生输出。 减少N,即我们正在填充的正方形数组的大小,将减少计算量。 结果将是曼德勃罗集的所有分形荣耀的视图:
注意,在轴域上显示的坐标是 2D 数组索引。
工作原理
pyplot.imshow()函数非常简单; 给它一个 2D 数组,它将渲染一张图片,其中每个像素代表一个从 2D 数组获取的值。 像素的颜色是从颜色表中选取的-数组的每个值都在[0, 1]间隔内线性归一化。 pyplot.imshow()函数可渲染图表,但不会显示它。 与往常一样,我们应该调用pyplot.show()来查看该图。 但是,具有两个具有类似名称的函数可能会引起混淆。
脚本的其余部分生成我们的示例数据。 创建 2D 数组Z,然后用双循环填充。 此循环对[-2.2, 0.8]*[-1.5, 1.5]正方形进行采样。 对于每个样本,iter_count函数都会计算曼德勃罗集的迭代次数。 Z数组中的数据可能来自文件或任何其他来源。
更多
我们从pyplot.imshow()得到的结果有点原始。 轴域上显示的坐标是 2D 数组索引。 我们可能更喜欢不同的坐标; 在这种情况下,我们采样的正方形的坐标。 此处使用的颜色表不理想。 可以使用pyplot.imshow()的可选参数来解决。 让我们将调用更改为pyplot.imshow():
plt.imshow(Z, cmap = cm.binary, extent=(xmin, xmax, ymin, ymax))
由于我们使用了颜色表,因此我们应该导入 matplotlib 的颜色表模块。 在脚本的开头,添加以下行:
import matplotlib.cm as cm
虽然数据严格相同,但是输出将更改:
extent 可选参数指定 2D 数组中存储的数据的坐标系。 坐标系是四个值的元组。 最小和最大范围在水平轴上,然后在垂直轴上。 现在,轴域显示我们采样以计算曼德勃罗集的正方形的坐标。 参数cmap指定颜色表。
现在,让我们在脚本中将样本数据的大小从512减小为32。 输出将类似于以下屏幕截图:
我们使用32*32样本而不是512*512样本来表示曼德勃罗集。 但是的结果并不小。 实际上,pyplot.imshow()所做的不只是为 2D 数组着色像素。 如果输入数据小于或大于数字,pyplot.imshow()函数将产生给定任意大小的图片并执行插值。 在此示例中,我们可以看到默认插值是线性的。 这并不总是理想的。 pyplot.imshow()函数有一个可选参数interpolation,它允许我们指定要使用的插值类型。 matplotlib 提供了令人印象深刻的插值方案列表。 让我们看一下最简单的插值方案:最近邻插值:
plt.imshow(Z, cmap = cm.binary, interpolation = 'nearest', extent=(xmin, xmax, ymin, ymax))
原始数据现在更加明显:
我们可能想使用比细线线性插值更复杂的插值方案。 后者计算起来很便宜,但会产生难看的伪像。 让我们使用双三次插值,使用interpolation = 'bicubic'。 我们会得到更好的结果:
注意
matplotlib 具有更复杂的插值方案,例如 sinc和 lanzcos。
向图中添加颜色表
颜色表是产生可读和视觉悦目的图形的关键要素。 但是,我们在这里进行科学,而审美只是一个副目标。 使用颜色表时,我们想知道哪个值对应于给定的颜色。 在本秘籍中,我们将探讨一种将此类信息添加到图形的简单方法。
操作步骤
我们将使用相同的示例曼德勃罗集。 我们只需将调用添加到pyplot.colorbar():
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.cm as cm
def iter_count(C, max_iter):
X = C
for n in range(max_iter):
if abs(X) > 2.:
return n
X = X ** 2 + C
return max_iter
N = 512
max_iter = 64
xmin, xmax, ymin, ymax = -2.2, .8, -1.5, 1.5
X = np.linspace(xmin, xmax, N)
Y = np.linspace(ymin, ymax, N)
Z = np.empty((N, N))
for i, y in enumerate(Y):
for j, x in enumerate(X):
Z[i, j] = iter_count(complex(x, y), max_iter)
plt.imshow(Z,
cmap = cm.binary,
interpolation = 'bicubic',
extent=(xmin, xmax, ymin, ymax))
cb = plt.colorbar(orientation='horizontal', shrink=.75)
cb.set_label('iteration count')
plt.show()
代码前面的将产生以下输出:
整洁的颜色条可让您将颜色表中的颜色与感兴趣的值相关联。 这是曼德勃罗迭代计数。
工作原理
大部分脚本与上一秘籍中介绍的脚本严格相同。 脚本的相关内容如下:
cb = plt.colorbar(orientation='horizontal', shrink=.75)
cb.set_label('iteration count')
pyplot.colorbar()函数向 matplotlib 发出信号,要求我们将颜色条显示为。 为了演示,我们在此处使用一些可选参数。 orientation参数用于选择颜色条是垂直还是水平。 默认情况下为垂直。 shrink参数用于将颜色栏缩小为其默认大小。 默认情况下,颜色栏没有图例。 可以设置图例,但这样做有点尴尬。 调用pyplot.colorbar()函数将产生一个Colorbar实例。 然后,我们调用该Colorbar实例的set_label()方法。
可视化非均匀 2D 数据
到目前为止,我们假设已对 2D 数据进行了统一采样; 我们的数据以网格模式进行采样。 但是,非均匀采样的数据非常普遍。 例如,我们可能希望可视化气象站的测量结果。 尽可能地建立气象站; 它们被布置成一个完美的网格。 在进行采样时,我们可能会使用不产生网格布局的复杂采样过程(自适应采样,准随机采样等)。 在这里,我们展示了一种处理此类 2D 数据的简单方法。
操作步骤
该脚本绘制了从与先前秘籍相同的正方形采样的曼德勃罗集。 但是,我们不使用常规的网格采样,而是对曼德勃罗集进行随机采样,如下例所示:
import numpy as np
from numpy.random import uniform, seed
from matplotlib import pyplot as plt
from matplotlib.mlab import griddata
import matplotlib.cm as cm
def iter_count(C, max_iter):
X = C
for n in range(max_iter):
if abs(X) > 2.:
return n
X = X ** 2 + C
return max_iter
max_iter = 64
xmin, xmax, ymin, ymax = -2.2, .8, -1.5, 1.5
sample_count = 2 ** 12
A = uniform(xmin, xmax, sample_count)
B = uniform(ymin, ymax, sample_count)
C = [iter_count(complex(a, b), max_iter) for a, b in zip(A, B)]
N = 512
X = np.linspace(xmin, xmax, N)
Y = np.linspace(ymin, ymax, N)
Z = griddata(A, B, C, X, Y, interp = 'linear')
plt.scatter(A, B, color = (0., 0., 0., .5), s = .5)
plt.imshow(Z,
cmap = cm.binary,
interpolation = 'bicubic',
extent=(xmin, xmax, ymin, ymax))
plt.show()
该脚本将显示随机采样的曼德勃罗集。 样本点显示为黑色小点:
显然,由于的随机采样过程,结果比常规采样的结果更加混乱。 但是,我们仅使用了 4,096 个样本,而不是先前示例中使用的 262,144 个样本,因此我们得到的结果是令人满意的。 通过 matplotlib 的非均匀采样功能,使用自适应采样方法将使您能够以比常规网格采样低得多的计算成本获得曼德勃罗集的高分辨率视图。
工作原理
首先,该脚本对曼德勃罗集进行随机采样,这由脚本的以下部分完成:
sample_count = 2 ** 12
A = uniform(xmin, xmax, sample_count)
B = uniform(ymin, ymax, sample_count)
C = [iter_count(complex(a, b), max_iter) for a, b in zip(A, B)]
数组A和B保存样本的坐标,而列表C包含这些样本中每个样本的值。
然后,脚本将从不均匀的样本中生成一个二维数据数组,这由以下部分完成:
N = 512
X = np.linspace(xmin, xmax, N)
Y = np.linspace(ymin, ymax, N)
Z = griddata(A, B, C, X, Y, interp = 'linear')
数组X和Y定义了规则的网格。 数组Z是通过内插非均匀样本而构建的 2D 数组。 该插值是通过matplotlib.mlab包中的griddata()函数完成的。 由于现在有了 2D 数组,因此可以使用pyplot.imshow()函数对其进行可视化。 对pyplot.scatter()的附加调用用于显示原始采样点。
为了演示,我们对pyplot.griddata()使用线性插值,并使用可选参数interp。 默认情况下,此参数设置为'nn',代表自然邻居插值。 后一种方案在大多数情况下是首选,因为它非常健壮。
可视化 2D 标量场
matplotlib 和 NumPy 提供一些有趣的机制,使二维标量场的可视化变得方便。 在本秘籍中,我们展示了一种非常简单的方法来可视化 2D 标量场。
操作步骤
numpy.meshgrid()函数从显式 2D 函数生成样本。 然后,pyplot.pcolormesh()是用于显示的函数,如以下代码所示:
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.cm as cm
n = 256
x = np.linspace(-3., 3., n)
y = np.linspace(-3., 3., n)
X, Y = np.meshgrid(x, y)
Z = X * np.sinc(X ** 2 + Y ** 2)
plt.pcolormesh(X, Y, Z, cmap = cm.gray)
plt.show()
前面的脚本将产生以下输出:
注意明智地选择颜色表会有所帮助; 在此,负值显示为黑色,正值显示为白色。 这样,我们就可以一目了然地看到符号和大小信息。 使用从红色到蓝色,中间为白色的颜色表,效果更好。
工作原理
numpy.meshgrid()函数取两个坐标x和y,并建立两个坐标为X和Y的网格。 因为X和Y是 NumPy 2D 数组,所以我们可以像处理单个变量一样操作它们。 我们不必编写循环来生成矩阵Z。 这使得计算标量字段简洁明了:
Z = X * numpy.sinc(X ** 2 + Y ** 2)
然后,调用函数pyplot.pcolormesh()渲染样本。 我们可以从pyplot.imshow()获得相同的结果。 但是,我们只需要在此处传递X,Y和Z即可获得正确的坐标系,而不是使用可选参数。 这样做使脚本更易于阅读。 同样,对于大量数据,pyplot.pcolormesh()可能会快得多。
可视化等高线
到目前为止,我们已经通过给每个数据点着色来可视化数据,并在顶部插入了一些插值。 matplotlib 能够为 2D 数据提供更复杂的表示形式。 等高线以相同的值链接所有点,从而帮助您捕获否则可能不容易看到的要素。 在本秘籍中,我们将看到如何显示这种等高线。
操作步骤
函数pyplot.contour()允许您生成轮廓标注。 为了演示,让我们重用先前秘籍中的代码,以研究曼德勃罗集的放大部分:
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.cm as cm
def iter_count(C, max_iter):
X = C
for n in range(max_iter):
if abs(X) > 2.:
return n
X = X ** 2 + C
return max_iter
N = 512
max_iter = 64
xmin, xmax, ymin, ymax = -0.32, -0.22, 0.8, 0.9
X = np.linspace(xmin, xmax, N)
Y = np.linspace(ymin, ymax, N)
Z = np.empty((N, N))
for i, y in enumerate(Y):
for j, x in enumerate(X):
Z[i, j] = iter_count(complex(x, y), max_iter)
plt.imshow(Z,
cmap = cm.binary,
interpolation = 'bicubic',
origin = 'lower',
extent=(xmin, xmax, ymin, ymax))
levels = [8, 12, 16, 20]
ct = plt.contour(X, Y, Z, levels, cmap = cm.gray)
plt.clabel(ct, fmt='%d')
plt.show()
前面的脚本将显示带有复杂轮廓标注的曼德勃罗集的详细信息:
工作原理
我们认识到先前在曼德勃罗集中展示的秘籍中使用的代码。 唯一的区别是,我们通过更改xmin,xmax,ymin和ymax的值来放大曼德勃罗集的特定细节。 和以前一样,我们使用pyplot.imshow()渲染每个样本的迭代计数。
仅添加了一个:调用pyplot.contour()。 此函数获取样本网格的坐标X和Y以及存储在矩阵Z中的样本。 然后,该函数将渲染与levels列表中指定的值相对应的轮廓。 可以使用可选参数cmap使用颜色表对级别进行着色。 我们可以使用可选参数color为所有轮廓指定一种唯一的颜色。
每个轮廓的水平可以用色条显示或直接在图形上显示。 pyplot.contour()函数返回Contour实例。 pyplot.clabel()函数采用Contour实例和一个可选的格式字符串来渲染每个轮廓的标签。
更多
在此,轮廓仅显示为线条。 但是,我们可以显示填充轮廓。 让我们在之前使用的曼德勃罗集的相同细节上进行演示:
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.cm as cm
def iter_count(C, max_iter):
X = C
for n in range(max_iter):
if abs(X) > 2.:
return n
X = X ** 2 + C
return max_iter
N = 512
max_iter = 64
xmin, xmax, ymin, ymax = -0.32, -0.22, 0.8, 0.9
X = np.linspace(xmin, xmax, N)
Y = np.linspace(ymin, ymax, N)
Z = np.empty((N, N))
for i, y in enumerate(Y):
for j, x in enumerate(X):
Z[i, j] = iter_count(complex(x, y), max_iter)
levels = [0, 8, 12, 16, 20, 24, 32]
plt.contourf(X, Y, Z, levels, cmap = cm.gray, antialiased = True)
plt.show()
上面的脚本将产生以下输出:
在这里,我们简单地用pyplot.contourf()将替换为pyplot.contour(),并为轮廓使用了附加等级。 默认情况下,不填充轮廓。 我们使用antialiased可选参数来获得更悦目的结果。
可视化 2D 向量场
到目前为止,我们一直在使用 2D 标量字段:将值与 2D 平面的每个点相关联的函数。 向量场将 2D 向量关联到 2D 平面的每个点。 向量场在物理学中很常见,因为它们提供了微分方程的解。 matplotlib 提供了可视化向量场的函数。
准备
对于此示例,我们将需要 SymPy 包; 用于符号计算的包。 该包仅用于使示例简短,并且对于使用向量字段不是必需的。
操作步骤
为了说明向量场的可视化,让我们可视化圆柱周围不可压缩流体的速度流。 我们无需费心如何计算这样的向量场,而仅需担心如何显示它。 我们需要pyplot.quiver()函数; 请参考以下代码:
import numpy as np
import sympy
from sympy.abc import x, y
from matplotlib import pyplot as plt
import matplotlib.patches as patches
def cylinder_stream_function(U = 1, R = 1):
r = sympy.sqrt(x ** 2 + y ** 2)
theta = sympy.atan2(y, x)
return U * (r - R ** 2 / r) * sympy.sin(theta)
def velocity_field(psi):
u = sympy.lambdify((x, y), psi.diff(y), 'numpy')
v = sympy.lambdify((x, y), -psi.diff(x), 'numpy')
return u, v
U_func, V_func = velocity_field(cylinder_stream_function() )
xmin, xmax, ymin, ymax = -2.5, 2.5, -2.5, 2.5
Y, X = np.ogrid[ymin:ymax:16j, xmin:xmax:16j]
U, V = U_func(X, Y), V_func(X, Y)
M = (X ** 2 + Y ** 2) < 1\.
U = np.ma.masked_array(U, mask = M)
V = np.ma.masked_array(V, mask = M)
shape = patches.Circle((0, 0), radius = 1., lw = 2., fc = 'w', ec = 'k', zorder = 0)
plt.gca().add_patch(shape)
plt.quiver(X, Y, U, V, zorder = 1)
plt.axes().set_aspect('equal')
plt.show()
上面的脚本将产生以下输出:
工作原理
尽管脚本长,但纯图形部分很简单。 向量字段存储在矩阵U和V中:我们从向量字段中采样的每个向量的坐标。 矩阵X和Y包含样本位置。 矩阵X,Y,U和V被传递到 pyplot.quiver(),从而渲染向量场。 请注意,pyplot.quiver()只能使用U和V作为参数,但图例将显示样本的索引而不是其坐标。
由于我们在此处用作说明的向量场是圆柱体周围的流体流动,圆柱体本身显示如下:
shape = patches.Circle((0, 0), radius = 1., lw = 2., fc = 'w', ec = 'k', zorder = 0)
plt.gca().add_patch(shape)
圆柱体内的向量场不会出现; 我们使用蒙版数组。 我们首先创建一个定义应显示哪些样本的蒙版。 然后,将此掩码应用于U和V,如以下脚本所示:
M = (X ** 2 + Y ** 2) < 1\.
U = np.ma.masked_array(U, mask = M)
V = np.ma.masked_array(V, mask = M)
这使您可以隐藏解决方案中的奇点。
可视化 2D 向量场的流线
使用箭头指向表示向量场效果很好。 但是 matplotlib 可以做得更好,它可以显示向量场的流线。 一条流线显示了向量场的流动方式。 在本秘籍中,我们将向您展示如何创建流线型。
操作步骤
让我们使用上一个秘籍的流体流动示例。 我们将简单地用流线替换箭头,如以下代码所示:
import numpy as np
import sympy
from sympy.abc import x, y
from matplotlib import pyplot as plt
import matplotlib.patches as patches
def cylinder_stream_function(U = 1, R = 1):
r = sympy.sqrt(x ** 2 + y ** 2)
theta = sympy.atan2(y, x)
return U * (r - R ** 2 / r) * sympy.sin(theta)
def velocity_field(psi):
u = sympy.lambdify((x, y), psi.diff(y), 'numpy')
v = sympy.lambdify((x, y), -psi.diff(x), 'numpy')
return u, v
psi = cylinder_stream_function()
U_func, V_func = velocity_field(psi)
xmin, xmax, ymin, ymax = -3, 3, -3, 3
Y, X = np.ogrid[ymin:ymax:128j, xmin:xmax:128j]
U, V = U_func(X, Y), V_func(X, Y)
M = (X ** 2 + Y ** 2) < 1\.
U = np.ma.masked_array(U, mask = M)
V = np.ma.masked_array(V, mask = M)
shape = patches.Circle((0, 0), radius = 1., lw = 2., fc = 'w', ec = 'k', zorder = 0)
plt.gca().add_patch(shape)
plt.streamplot(X, Y, U, V, color = 'k')
plt.axes().set_aspect('equal')
plt.show()
前面的脚本将显示圆柱周围的流,如以下屏幕快照中的所示:
工作原理
生成样本向量坐标的代码与先前的秘籍相同。 在这里,我们使用更多样本(用128*128代替32*32)来获得准确的流线型。 除此之外,唯一的区别是我们使用pyplot.streamlines()代替了pyplot.quiver()的。 四个必需参数相同:样本的坐标X和Y,以及向量的坐标U和V。 可选参数color用于设置流线的颜色。
更多
我们可以使用带有可选参数color和cmap的颜色表为流线着色:
plt.streamplot(X, Y, U, V, color = U ** 2 + V ** 2, cmap = cm.binary)
color参数采用 2D 数组,该数组用于为流线着色。 在此示例中,颜色反映了流的速度,如以下输出所示:
七、处理 3D 图形
在本章中,我们将介绍以下主题:
- 创建 3D 散点图
- 创建 3D 线形图
- 在 3D 中绘制标量场
- 绘制参数化 3D 曲面
- 将 2D 图形嵌入 3D 图形
- 创建 3D 条形图
简介
matplotlib 对三维图的支持不断增加。 从 1.2 版开始,制作 3D 图形的 API 与 2D API 非常相似。 在地块上再增加一个维度可以一目了然地可视化更多信息。 此外,3D 图在演示文稿或课程期间非常引人注目。 在本章中,我们将探讨 matplotlib 在第三维中可以做什么。
创建 3D 散点图
散点图是非常简单的图。 对于数据集的每个点,图中显示一个点。 一个点的坐标就是相应数据的坐标。 我们已经在第 1 章“第一步”中探索了二维散点图。 在本秘籍中,我们将看到三个维度的散点图以相同的方式工作,但变化很小。
为了使本示例中的数据有趣,我们将使用洛伦兹奇异吸引子。 这是一个 3D 结构,代表了来自气象学的简单动力系统的解决方案。 这个动力学系统是一个著名的教科书例子,一个混沌系统。
操作步骤
在下面的代码中,我们将从Axes实例调用图形渲染方法,而不是从pyplot调用方法:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
## Dataset generation
a, b, c = 10., 28., 8\. / 3.
def lorenz_map(X, dt = 1e-2):
X_dt = np.array([a * (X[1] - X[0]),
X[0] * (b - X[2]) - X[1],
X[0] * X[1] - c * X[2]])
return X + dt * X_dt
points = np.zeros((2000, 3))
X = np.array([.1, .0, .0])
for i in range(points.shape[0]):
points[i], X = X, lorenz_map(X)
## Plotting
fig = plt.figure()
ax = fig.gca(projection = '3d')
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_zlabel('Z axis')
ax.set_title('Lorenz Attractor a=%0.2f b=%0.2f c=%0.2f' % (a, b, c))
ax.scatter(points[:, 0], points[:, 1], points[:, 2], zdir = 'y', c = 'k')
plt.show()
上面的代码将显示现在熟悉的用户界面,如下图:
在我们眼前,洛伦兹吸引子! 如果将鼠标拖到图形中(按住鼠标左键移动鼠标),则 3D 形状将旋转,就像您正在操纵轨迹球一样。 您可以旋转图形并以所有可能的角度检查洛伦兹吸引子。
请注意,尽管所有点都以蓝色显示,但有些点趋向于白色阴影。 matplotlib 应用这种类似于雾的效果来增强散点图的深度感知。 距我们较远的点将抖动为白色,这是文艺复兴时期画家已经知道的古老技巧。
工作原理
对于本示例,我们不会在数据生成上花太多时间; 这不是重点。 我们只需要知道数据集存储在具有三列的矩阵点中,每个维度一列。
在使用 matplotlib 进行三维操作之前,我们首先需要为 matplotlib 导入 3D 扩展名:这是以下import指令的目的:
from mpl_toolkits.mplot3d import Axes3D
到目前为止,在的大部分时间内,我们已经通过调用pyplot中的方法提交了所有渲染指令。 但是,对于三维图,涉及的内容更多,如以下代码所示:
fig = plt.figure()
ax = fig.gca(projection = '3d')
我们创建一个Figure实例,并将一个Axes3D实例附加到该实例。 当Axes实例负责通常的 2D 渲染时,Axes3D将负责 3D 渲染。 然后,3D 散点图的工作方式与 2D 散点图完全相同,如以下代码所示:
ax.scatter(points[:, 0], points[:, 1], points[:, 2])
我们给出要表示的点的X,Y和Z坐标。 在这里,我们仅给出点矩阵的三列。 我们可以使用普通的 Python 列表,但是为了方便起见,我们使用 NumPy 数组。 再次注意,我们调用了Axes3D实例的scatter()方法,而不是pyplot中的scatter方法。 Axes3D中只有scatter()方法会解释 3D 数据。
最后,尽管在Axes3D实例中调用了它们,但是我们在第 3 章,“处理标注”中探讨的功能也可用。 用set_title()设置标题,并用set_xlabel(),set_ylabel()和set_zlabel()标注轴。
更多
正如我们已经看到的,3D 中的散点图的工作方式与 2D 中的散点图相同。 实际上,除了用于创建Axes3D实例的设置代码之外,所有内容似乎都像在 2D 模式下一样工作。 这不仅仅是印象。 例如,自定义散点图的工作方式完全相同。 让我们通过替换对Axes3D.scatter()的调用来更改标记的形状和颜色,如下所示:
ax.scatter(points[:, 0], points[:, 1], points[:, 2],
marker = 's',
edgecolor = '.5',
facecolor = '.5')
现在,输出将如下图所示:
实际上,第 2 章“自定义颜色和样式”中的所有技巧都可以在 3D 中实现。
创建 3D 线形图
正如先前的秘籍所展示的那样,在创建三维图形时,我们在前几章中学到的内容是正确的。 让我们通过绘制 3D 参数曲线来确认这一点。 在此秘籍中,我们保留与上一个秘籍相同的数据集; 就是洛伦兹吸引子。
操作步骤
在 2D 中,我们通过调用pyplot.plot()绘制曲线。 正如前面的秘籍所暗示的,我们要做的就是设置一个Axes3D实例并调用其plot()方法,如以下代码所示:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
a, b, c = 10., 28., 8\. / 3.
def lorenz_map(X, dt = 1e-2):
X_dt = np.array([a * (X[1] - X[0]),
X[0] * (b - X[2]) - X[1],
X[0] * X[1] - c * X[2]])
return X + dt * X_dt
points = np.zeros((10000, 3))
X = np.array([.1, .0, .0])
for i in range(points.shape[0]):
points[i], X = X, lorenz_map(X)
fig = plt.figure()
ax = fig.gca(projection = '3d')
ax.plot(points[:, 0], points[:, 1], points[:, 2], c = 'k')
plt.show()
前面的代码将显示熟悉的洛伦兹吸引子,但是这些点通过曲线链接,而不是简单地显示每个数据点,如下图所示:
当我们使用用户界面旋转视图时,洛伦兹吸引子的特殊缠绕螺旋结构非常明显。
工作原理
对于任何三维图形,我们首先设置一个Axes3D实例。 然后,对plot()的调用类似于其 2D 对应项:我们为每个维度提供一个列表,并为每个维度提供点的坐标。
绘制 3D 标量场
到目前为止,我们已经看到 3D 图基本上模仿了 2D 对应图。 但是,还有 matplotlib 的三维绘图函数。 许多特定于三维的图形也是可能的。 让我们从一个简单的用例开始:将 2D 标量场绘制为 3D 表面。
操作步骤
和往常一样,我们将要生成一些测试数据,设置一个Axes3D实例,然后将我们的数据传递给它:
import numpy as np
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
x = np.linspace(-3, 3, 256)
y = np.linspace(-3, 3, 256)
X, Y = np.meshgrid(x, y)
Z = np.sinc(np.sqrt(X ** 2 + Y ** 2))
fig = plt.figure()
ax = fig.gca(projection = '3d')
ax.plot_surface(X, Y, Z, cmap=cm.gray)
plt.show()
上面的代码将显示下图:
工作原理
数据生成的工作方式与第 6 章和“处理地图”中演示的完全相同。 将创建两个矩阵X和Y,其中包含规则网格的坐标。 我们计算矩阵Z以及X和Y的标量场函数。
从这里开始,事情变得非常琐碎。 我们调用plot_surface()方法,该方法使用X,Y和Z将标量场显示为 3D 曲面。 颜色来自颜色表(cmap可选参数)和矩阵Z。
更多
您可能不希望看到 3D 表面上显示的黑色曲线。 可以使用plot_surface()的一些其他可选参数来完成,如以下代码所示:
ax.plot_surface(X, Y, Z,
cmap=cm.gray,
linewidth=0,
antialiased=False)
黑色曲线现已消失,从而使图形更简单:
另一方面,我们可能想要保留黑色曲线并摆脱花哨的颜色。 也可以使用plot_surface()的可选参数来完成,如以下代码所示:
ax.plot_surface(X, Y, Z, color = 'w')
并且仅保留黑色曲线,从而形成了极简的表面图,如下图所示:
最后,我们可能希望摆脱隐藏面的去除,并希望表面由线框制成。 现在,这不是plot_surface()可以实现的。 但是,plot_wireframe()正是为此而制作的,如以下代码所示:
ax.plot_wireframe(X, Y, Z, cstride=8, rstride=8, color = 'k')
现在,以线框样式渲染相同的曲面,如下图所示:
plot_wireframe()参数采用与输入相同的X,Y和Z坐标作为plot_surface()参数。 我们使用两个可选参数rstride和cstride来告诉 matplotlib 跳过X和Y轴上的每八个坐标。 如果没有这个,曲线之间的间隔将太小,我们将只能看到一个大的黑色轮廓。
绘制参数化 3D 曲面
在先前的秘籍中,我们使用plot_surface()绘制了标量场:即f(x, y) = z形式的函数。 但是,matplotlib 能够绘制通用的参数化 3D 曲面。 让我们通过绘制圆环来演示这一点,圆环是一个相当简单的参数化曲面。
操作步骤
我们将使用以下代码再次使用plot_surface()显示圆环:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
## Generate torus mesh
angle = np.linspace(0, 2 * np.pi, 32)
theta, phi = np.meshgrid(angle, angle)
r, R = .25, 1.
X = (R + r * np.cos(phi)) * np.cos(theta)
Y = (R + r * np.cos(phi)) * np.sin(theta)
Z = r * np.sin(phi)
## Display the mesh
fig = plt.figure()
ax = fig.gca(projection = '3d')
ax.set_xlim3d(-1, 1)
ax.set_ylim3d(-1, 1)
ax.set_zlim3d(-1, 1)
ax.plot_surface(X, Y, Z, color = 'w', rstride = 1, cstride = 1)
plt.show()
前面的代码将显示我们的圆环,如下所示:
工作原理
圆环是可以用两个参数theta和phi进行参数化的曲面,其范围从0到2 * pi,如下代码所示:
angle = np.linspace(0, 2 * np.pi, 32)
theta, phi = np.meshgrid(angle, angle)
theta和phi变量描述了规则的网格布局。 圆环网格的 3D 坐标根据theta和phi编写,如以下代码所示:
r, R = .25, 1.
X = (R + r * np.cos(phi)) * np.cos(theta)
Y = (R + r * np.cos(phi)) * np.sin(theta)
Z = r * np.sin(phi)
然后,我们只需将X,Y和Z传递给plot_surface()方法。 plot_surface()方法假定X,Y和Z是网格数据。 我们需要设置可选参数rstride和cstride,以明确X,Y和Z是网格数据。
我们明确地将的轴限制设置为[-1, 1]范围。 默认情况下,在创建 3D 图时,matplotlib 将自动缩放每个轴。 我们的圆环在X和Y轴的[-1, 1]范围内延伸,但仅在Z轴的[-.25, .25]范围内延伸。 如果让 matplotlib 缩放轴,则圆环将显示在Z轴上拉伸,如下图所示:
因此,在绘制 3D 曲面时,我们必须手动设置每个轴范围以获得正确缩放的视图。
更多
如前面的秘籍所示,我们可以使用以下代码将对plot_surface()的调用替换为对plot_wireframe()的调用,以获取圆环的线框视图:
ax.plot_wireframe(X, Y, Z, color = 'k', rstride = 1, cstride = 1)
这个简单的更改足以获得线框视图,如下图所示:
将 2D 图形嵌入 3D 图形
我们已经在第 3 章“处理标注”来了解如何对图形进行标注。 标注三维图形的一种有效方法是简单地使用二维图形。 此秘籍是说明这种可能性的简单示例。
操作步骤
为了说明这个想法,我们将仅使用我们之前已经看到的图元绘制一个简单的 3D 表面和两条曲线,如以下代码所示:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
x = np.linspace(-3, 3, 256)
y = np.linspace(-3, 3, 256)
X, Y = np.meshgrid(x, y)
Z = np.exp(-(X ** 2 + Y ** 2))
u = np.exp(-(x ** 2))
fig = plt.figure()
ax = fig.gca(projection = '3d')
ax.set_zlim3d(0, 3)
ax.plot(x, u, zs=3, zdir='y', lw = 2, color = '.75')
ax.plot(x, u, zs=-3, zdir='x', lw = 2., color = 'k')
ax.plot_surface(X, Y, Z, color = 'w')
plt.show()
上面的代码将产生下图:
黑色和灰色曲线绘制为投影在平面上的 2D 曲线。
工作原理
如先前秘籍所示,将生成 3D 表面。 Axes3D实例ax支持常用的 2D 渲染命令,例如plot(),如以下代码所示:
ax.plot(x, u, zs=3, zdir='y', lw = 2, color = '.75')
但是,对plot()的调用具有两个新的可选参数:zs和zdir:
Zdir:这决定了要在哪个平面上绘制 2D 图,X,Y 或 ZZs:这确定平面的偏移
因此,要将 2D 图形嵌入 3D 图形中,我们只需要记住Axes3D即可使用所有 2D 图元。 我们只有两个可选参数zdir和zs,以设置放置需要绘制图形的平面。
更多
将 2D 图形嵌入到 3D 图形中非常简单,但是使用到目前为止我们探索的简单基元创建复杂图形提供了很多可能性。 例如,我们已经知道使用以下代码制作分层条形图的一切:
import numpy as np
from matplotlib import cm
import matplotlib.colors as col
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
## Data generation
alpha = 1\. / np.linspace(1, 8, 5)
t = np.linspace(0, 5, 16)
T, A = np.meshgrid(t, alpha)
data = np.exp(-T * A)
## Plotting
fig = plt.figure()
ax = fig.gca(projection = '3d')
cmap = cm.ScalarMappable(col.Normalize(0, len(alpha)), cm.gray)
for i, row in enumerate(data):
ax.bar(4 * t, row, zs=i, zdir='y', alpha=0.8, color=cmap.to_rgba(i))
plt.show()
上面的代码将产生下图:
我们可以看到前面的代码使用了前面各章中介绍的功能:
- 条形图的创建已在第 1 章,“第一步”中进行了介绍。
- 第 2 章“自定义颜色和样式”中已经介绍了使用颜色表对条形图进行着色。
- 本秘籍涵盖了条形图的分层
创建 3D 条形图
在 3D 图形中使用几个 2D 图层,我们可以绘制多个条形图。 但是,我们也可以使用完整的 3D 和带有实际 3D 条形图的条形图。
操作步骤
为了演示 3D 条形图,我们将使用先前秘籍中的简单合成数据集,如以下代码所示:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
## Data generation
alpha = np.linspace(1, 8, 5)
t = np.linspace(0, 5, 16)
T, A = np.meshgrid(t, alpha)
data = np.exp(-T * (1\. / A))
## Plotting
fig = plt.figure()
ax = fig.gca(projection = '3d')
Xi = T.flatten()
Yi = A.flatten()
Zi = np.zeros(data.size)
dx = .25 * np.ones(data.size)
dy = .25 * np.ones(data.size)
dz = data.flatten()
ax.set_xlabel('T')
ax.set_ylabel('Alpha')
ax.bar3d(Xi, Yi, Zi, dx, dy, dz, color = 'w')
plt.show()
这次,这些条显示为 3D 块,如下图所示:
工作原理
这些条以网格布局放置在中。 bar3d()方法采用六个必填参数作为输入。 前三个参数是每个条形图下端的X,Y和Z坐标。 在这里,我们根据数据集构建条形的坐标,如下所示:
Xi = T.flatten()
Yi = A.flatten()
Zi = np.zeros(data.size)
每个小节将从同一级别0开始。 X和Y坐标是数据集的坐标。 bar3d()方法将坐标列表而不是网格坐标作为输入,这就是为什么我们在矩阵A和T上调用flatten方法的原因。
bar3d()方法的接下来的三个必需参数是每个尺寸上每个条的尺寸。 在此,条形的高度取自data矩阵。 钢筋的宽度和深度设置为.25,如以下代码所示:
dx = .25 * np.ones(data.size)
dy = .25 * np.ones(data.size)
dz = data.flatten()
现在,我们可以使用以下代码调用bar3d():
ax.bar3d(Xi, Yi, Zi, dx, dy, dz, color = 'w')
八、用户界面
在本章中,我们将介绍:
- 制作用户可控制的图
- 将图整合到 Tkinter 用户界面中
- 将图整合到 wxWidgets 用户界面中
- 将图整合到 GTK 用户界面中
- 将图整合到 Pyglet 应用中
简介
matplotlib 不仅可以绘制图表,还可以绘制您可以与之交互的图形。 交互式可视化可能是探索一些数据并发现一些有趣模式的好方法。 同样,交互式图表可以为教学目的提供很好的支持。 在本章中,我们将探讨创建 交互式绘图所必须使用的不同选项。
制作用户可控制的绘图
开箱即用的不需要任何其他包,matplotlib 提供了在图上添加控制器的原语,以便用户可以与其交互。 在本秘籍中,我们将了解如何绘制著名的参数曲线: SuperShape 曲线。 该曲线由六个参数控制:A,B,M,N1,N2 和 N3。 这些参数确定曲线的形状。 用户可以通过在图形上移动光标来交互式设置它们。
操作步骤
以下代码将使用pyplot.plot()显示一条曲线,这时应该很简单。 但是,我们现在使用用户界面元素(通常更称为小部件),即滑块。 可以通过以下步骤完成:
-
我们从必要的导入指令开始,如下所示:
import numpy as np from matplotlib import pyplot as plt from matplotlib.widgets import Slider -
SuperShape 曲线由以下函数定义:
def supershape_radius(phi, a, b, m, n1, n2, n3): theta = .25 * m * phi cos = np.fabs(np.cos(theta) / a) ** n2 sin = np.fabs(np.sin(theta) / b) ** n3 r = (cos + sin) ** (-1\. / n1) r /= np.max(r) return r -
然后,我们使用以下代码定义 SuperShape 曲线的参数的初始值:
phi = np.linspace(0, 2 * np.pi, 1024) m_init = 3 n1_init = 2 n2_init = 18 n3_init = 18 -
我们定义图并按如下所示放置滑块:
fig = plt.figure() ax = fig.add_subplot(111, polar = True) ax_m = plt.axes([0.05, 0.05, 0.25, 0.025]) ax_n1 = plt.axes([0.05, 0.10, 0.25, 0.025]) ax_n2 = plt.axes([0.7, 0.05, 0.25, 0.025]) ax_n3 = plt.axes([0.7, 0.10, 0.25, 0.025]) slider_m = Slider(ax_m, 'm', 1, 20, valinit = m_init) slider_n1 = Slider(ax_n1, 'n1', .1, 10, valinit = n1_init) slider_n2 = Slider(ax_n2, 'n2', .1, 20, valinit = n2_init) slider_n3 = Slider(ax_n3, 'n3', .1, 20, valinit = n3_init) -
我们使用以下代码绘制一次曲线:
r = supershape_radius(phi, 1, 1, m_init, n1_init, n2_init, n3_init) lines, = ax.plot(phi, r, lw = 3.) -
我们指定用户更新滑块时的操作,如以下代码所示:
def update(val): r = supershape_radius(phi, 1, 1, np.floor(slider_m.val), slider_n1.val, slider_n2.val, slider_n3.val) lines.set_ydata(r) fig.canvas.draw_idle() slider_n1.on_changed(update) slider_n2.on_changed(update) slider_n3.on_changed(update) slider_m.on_changed(update) -
现在,我们已经完成,可以使用以下命令结束脚本:
plt.show() -
前面的代码将按预期显示一条曲线,并带有(基本)滑块控件,如下图所示:
您可以拖动左侧或右侧的滑块,以查看曲线的变化。 请注意,在较旧的计算机上,动画感觉很慢。
工作原理
该代码比平时更长。 让我们分手吧!
SuperShape 曲线是极线。 supershape_radius函数计算[0, 2 * pi]间隔中每个角度的半径。 该函数将角度数组和 SuperShape 曲线的六个参数作为输入。
我们显式创建一个Figure实例fig和一个Axes实例ax,如以下代码所示:
fig = plt.figure()
ax = fig.add_subplot(111, polar = True)
所有小部件都在matplotlib.widgets包中定义。 我们在图中为参数 M,N1,N2 和 N3 放置了四个滑块控件。
每个滑块与与通过调用plot.axes()创建的子图相关联。 每个Slider实例都是通过调用Slider构造器来创建的。 构造器采用四个强制性参数:子图实例,标签,最小值和最大值。 我们使用可选参数valinit设置每个滑块的初始位置,如以下代码所示:
ax_m = plt.axes([0.05, 0.05, 0.25, 0.025])
ax_n1 = plt.axes([0.05, 0.10, 0.25, 0.025])
ax_n2 = plt.axes([0.7, 0.05, 0.25, 0.025])
ax_n3 = plt.axes([0.7, 0.10, 0.25, 0.025])
slider_m = Slider(ax_m, 'm', 1, 20, valinit = m_init)
slider_n1 = Slider(ax_n1, 'n1', .1, 10, valinit = n1_init)
slider_n2 = Slider(ax_n2, 'n2', .1, 20, valinit = n2_init)
slider_n3 = Slider(ax_n3, 'n3', .1, 20, valinit = n3_init)
我们绘制曲线本身,但是跟踪要渲染的内容:lines变量中存储的线的集合,这是使用以下代码完成的:
lines, = ax.plot(phi, r, lw = 3.)
我们定义了每个滑块在更改位置时的行为:它们将调用名为update的函数:
slider_n1.on_changed(update)
slider_n2.on_changed(update)
slider_n3.on_changed(update)
slider_m.on_changed(update)
update函数读取每个滑块的位置,并更新要显示的曲线的每个点的位置,更新线的集合,最后,将更改通知给Figure实例fig, 如以下代码所示:
def update(val):
lines.set_ydata(supershape_radius(phi, 1, 1, np.floor(slider_m.val), slider_n1.val, slider_n2.val, slider_n3.val))
fig.canvas.draw_idle()
最后,我们准备使用以下命令绘制所有内容:
plt.show()
更多
尽管滑块控件绝对是一种交互式调整参数的好方法,但是matplotlib.widgets包中提供了更多小部件。 按钮和复选框也可用。
将绘图集成到 Tkinter 用户界面
matplotlib 提供了基本的小部件来构建交互式图形。 但是,这些小部件非常初级,不能很好地扩展,而只需要几个控制器即可。 真正的图形用户界面库更适合于创建复杂的交互。 幸运的是,Python 附带了这样的库: Tkinter。 Tkinter 允许您创建一些小部件并为其提供 Windows 布局。 更好的是,matplotlib 提供了一个方便的钩子,可以将绘图集成到使用 Tkinter 制作的用户界面中。 在本秘籍中,我们将重现前面的示例,但将 Tkinter 用于用户界面部分。
操作步骤
方便地, matplotlib 提供了一个特殊的 Tkinter 小部件,可用于渲染图形。 如前面的秘籍中所述,更新了该特殊小部件中的图形。 这是我们需要遵循的步骤:
-
我们从常规导入指令开始,如下所示:
import numpy as np from tkinter import * from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.figure import Figure请注意,此处 Tkinter 的
import指令对 Python 3 有效。如果使用 Python 2,则应将tkinter替换为Tkinter。 -
然后,我们使用以下代码定义 SuperShape 曲线的函数:
def supershape_radius(phi, a, b, m, n1, n2, n3): theta = .25 * m * phi cos = np.fabs(np.cos(theta) / a) ** n2 sin = np.fabs(np.sin(theta) / b) ** n3 r = (cos + sin) ** (-1\. / n1) r /= np.max(r) return r -
我们定义了一个工具对象,以将范围线性缩放为另一个范围,如下所示:
class LinearScaling(object): def __init__(self, src_range, dst_range): self.src_start, src_diff = src_range[0], src_range[1] - src_range[0] self.dst_start, dst_diff = dst_range[0], dst_range[1] - dst_range[0] self.src_to_dst_coeff = dst_diff / src_diff self.dst_to_src_coeff = src_diff / dst_diff def src_to_dst(self, X): return (X - self.src_start) * self.src_to_dst_coeff + self.dst_start def dst_to_src(self, X): return (X - self.dst_start) * self.dst_to_src_coeff + self.src_start -
现在进入了用户界面,该用户界面的编码如下:
class SuperShapeFrame(Frame): def __init__(self, master = None): Frame.__init__(self, master) self.grid() self.m = 3 self.n1 = 2 self.n1_scaling = LinearScaling((.1, 20), (0, 200)) self.n2 = 18 self.n2_scaling = LinearScaling((.1, 20), (0, 200)) self.n3 = 18 self.n3_scaling = LinearScaling((.1, 20), (0, 200)) self.fig = Figure((6, 6), dpi = 80) canvas = FigureCanvasTkAgg(self.fig, master = self) canvas.get_tk_widget().grid(row = 0, column = 0, columnspan = 4) label = Label(self, text = 'M') label.grid(row = 1, column = 1) self.m_slider = Scale(self, from_ = 1, to = 20, orient = HORIZONTAL, command = lambda i : self.update_m()) self.m_slider.grid(row = 1, column = 2) label = Label(self, text = 'N1') label.grid(row = 2, column = 1) self.n1_slider = Scale(self, from_ = 0, to = 200, orient = HORIZONTAL, command = lambda i : self.update_n1()) self.n1_slider.grid(row = 2, column = 2) label = Label(self, text = 'N2') label.grid(row = 3, column = 1) self.n2_slider = Scale(self, from_ = 0, to = 200, orient = HORIZONTAL, command = lambda i : self.update_n2()) self.n2_slider.grid(row = 3, column = 2) label = Label(self, text = 'N3') label.grid(row = 4, column = 1) self.n3_slider = Scale(self, from_ = 0, to = 200, orient = HORIZONTAL, command = lambda i : self.update_n3()) self.n3_slider.grid(row = 4, column = 2) self.draw_figure() def update_m(self): self.m = self.m_slider.get() self.refresh_figure() def update_n1(self): self.n1 = self.n1_scaling.dst_to_src(self.n1_slider.get()) self.refresh_figure() def update_n2(self): self.n2 = self.n2_scaling.dst_to_src(self.n2_slider.get()) self.refresh_figure() def update_n3(self): self.n3 = self.n3_scaling.dst_to_src(self.n3_slider.get()) self.refresh_figure() def refresh_figure(self): r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3) self.lines.set_ydata(r) self.fig.canvas.draw_idle() def draw_figure(self): self.phi = np.linspace(0, 2 * numpy.pi, 1024) r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3) ax = self.fig.add_subplot(111, polar = True) self.lines, = ax.plot(self.phi, r, lw = 3.) self.fig.canvas.draw() -
最后,我们设置并启动我们的用户界面,如下所示:
app = SuperShapeFrame() app.master.title('SuperShape') app.mainloop() -
绘制了
SuperShape曲线,可以用四个滑块控件对其进行控制,如下图所示:
工作原理
在此示例中,的所有工作均由SuperShapeFrame对象完成,该对象是 TKinter Frame类的子类。 Frame对象只是 Tkinter 词典中的一个窗口。
matplotlib 提供了一个FigureCanvasTKAgg对象作为matplotlib.backends.backend_tkagg模块的一部分。 FigureCanvasTKAgg对象封装了Figure实例,其行为类似于 Tkinter 对象。 因此,在此示例中,我们创建一个窗口(Frame对象),并使用小部件填充该窗口:四个滑块实例和一个FigureCanvasTKAgg实例。 画布创建如下:
self.fig = Figure((6, 6), dpi = 80)
canvas = FigureCanvasTkAgg(self.fig, master = self)
我们首先创建一个 matplotlib 图形,并将其作为参数传递给FigureCanvasTkAgg构造器。 我们不需要跟踪画布本身。 我们只需要跟踪该数字即可。 画布的大小取决于图形的大小及其分辨率。 在这里,我们的数字是 80 dpi 的六个单位的正方形:480 像素。
我们需要执行两个操作:绘制图形并刷新它。 我们只需要画一次图。 然后,当用户更改我们显示的曲线的某些参数时,我们必须刷新图形。
使用draw_figure()方法绘制该图,如下所示:
def draw_figure(self):
self.phi = np.linspace(0, 2 * np.pi, 1024)
r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3)
ax = self.fig.add_subplot(111, polar = True)
self.lines, = ax.plot(self.phi, r, lw = 3.)
self.fig.canvas.draw()
我们将Axes实例ax附加到我们的Figure实例。 我们绘制曲线并跟踪此操作的结果:线的集合。 最后,我们告诉画布渲染图。
使用refresh_figure()方法刷新图,如下所示:
def refresh_figure(self):
r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3)
self.lines.set_ydata(r)
self.fig.canvas.draw_idle()
刷新图形时,我们不会重新绘制所有图形(但是可以那样做)。 我们只需更新行集合并通知画布来更新图形。 每次用户修改滑块时,我们通过调用refresh_figure()刷新图形。
使用 Tkinter 滑块的一个怪癖是这些滑块仅返回整数值; 但是,实际上,至少在科学或工程领域,我们需要浮点值。 要解决,我们实现了LinearScaling类,将值从一个范围线性缩放到另一个范围。 滑块的范围为 0 到 200。为四个参数中的每个参数创建一个LinearScaling实例,以将滑块位置转换为参数的实际值。
将绘图集成到 wxWidgets 用户界面
使用 Tkinter,我们可以将 matplotlib 的绘图函数与功能齐全的 GUI 库相结合。 该解决方案的优点是仅依赖于标准 Python。 但是,针对 Tkinter 的经典说法是外观:用户界面具有自己的外观,而不是其运行平台的外观。
wxWidgets 用户界面是 Python 的另一个 GUI 模块,它绑定了 wx 库。 wx 库公开了用于在 Windows,OSX 和 Linux 上创建图形界面的通用 API。 用 wx 创建的图形界面将具有运行它们的平台的外观。 在本秘籍中,我们将研究如何将 wxWidgets 与 matplotlib 接口。
操作步骤
总体思路与 matplotlib/Tkinter 集成所做的非常相似。 matplotlib 提供了一个特殊的 wxWidget 小部件,该小部件嵌入了Figure对象。 创建和更新Figure对象的工作方式与以前相同,如以下步骤所示:
-
我们从如下的导入指令开始:
import wx import numpy as np from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg from matplotlib.figure import Figure -
我们添加了使用以下代码定义 SuperShape 曲线的函数:
def supershape_radius(phi, a, b, m, n1, n2, n3): theta = .25 * m * phi cos = np.fabs(np.cos(theta) / a) ** n2 sin = np.fabs(np.sin(theta) / b) ** n3 r = (cos + sin) ** (-1\. / n1) r /= np.max(r) return r -
我们将要需要一个工具对象,以将从一个范围线性缩放到另一个范围,如下所示:
class LinearScaling(object): def __init__(self, src_range, dst_range): self.src_start, src_diff = src_range[0], src_range[1] - src_range[0] self.dst_start, dst_diff = dst_range[0], dst_range[1] - dst_range[0] self.src_to_dst_coeff = dst_diff / src_diff self.dst_to_src_coeff = src_diff / dst_diff def src_to_dst(self, X): return (X - self.src_start) * self.src_to_dst_coeff + self.dst_start def dst_to_src(self, X): return (X - self.dst_start) * self.dst_to_src_coeff + self.src_start -
我们使用以下代码定义用户界面:
class SuperShapeFrame(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, style = wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER, size = (480, 640)) self.m = 3 self.n1 = 2 self.n1_scaling = LinearScaling((.01, 20), (0, 200)) self.n2 = 18 self.n2_scaling = LinearScaling((.01, 20), (0, 200)) self.n3 = 18 self.n3_scaling = LinearScaling((.01, 20), (0, 200)) self.fig = Figure((6, 6), dpi = 80) panel = wx.Panel(self, -1) self.m_slider = wx.Slider(panel, -1, self.m, 1, 20, size = (250, -1), style = wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | wx.SL_LABELS) self.n1_slider = wx.Slider(panel, -1, self.n1_scaling.src_to_dst(self.n1), 0, 200, size = (250, -1), style = wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | wx.SL_LABELS) self.n2_slider = wx.Slider(panel, -1, self.n1_scaling.src_to_dst(self.n2), 0, 200, size = (250, -1), style = wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | wx.SL_LABELS) self.n3_slider = wx.Slider(panel, -1, self.n1_scaling.src_to_dst(self.n3), 0, 200, size = (250, -1), style = wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | wx.SL_LABELS) self.m_slider.Bind(wx.EVT_SCROLL, self.on_m_slide) self.n1_slider.Bind(wx.EVT_SCROLL, self.on_n1_slide) self.n2_slider.Bind(wx.EVT_SCROLL, self.on_n2_slide) self.n3_slider.Bind(wx.EVT_SCROLL, self.on_n3_slide) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(FigureCanvasWxAgg(panel, -1, self.fig), 0, wx.TOP) sizer.Add(self.m_slider, 0, wx.ALIGN_CENTER) sizer.Add(self.n1_slider, 0, wx.ALIGN_CENTER) sizer.Add(self.n2_slider, 0, wx.ALIGN_CENTER) sizer.Add(self.n3_slider, 0, wx.ALIGN_CENTER) panel.SetSizer(sizer) self.draw_figure() def on_m_slide(self, event): self.m = self.m_slider.GetValue() self.refresh_figure() def on_n1_slide(self, event): self.n1 = self.n1_scaling.dst_to_src(self.n1_slider.GetValue()) self.refresh_figure() def on_n2_slide(self, event): self.n2 = self.n2_scaling.dst_to_src(self.n2_slider.GetValue()) self.refresh_figure() def on_n3_slide(self, event): self.n3 = self.n3_scaling.dst_to_src(self.n3_slider.GetValue()) self.refresh_figure() def refresh_figure(self): r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3) self.lines.set_ydata(r) self.fig.canvas.draw_idle() def draw_figure(self): self.phi = np.linspace(0, 2 * np.pi, 1024) r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3) ax = self.fig.add_subplot(111, polar = True) self.lines, = ax.plot(self.phi, r, lw = 3.) self.fig.canvas.draw() -
现在,我们可以按照下面的初始化和启动用户界面:
app = wx.App(redirect = True) top = SuperShapeFrame(None, -1, 'SuperShape') top.Show() app.MainLoop() -
该脚本生成一个显示
SuperShape曲线的窗口。 与本章前面的方法一样,移动滑块将修改曲线的形状,如下图所示:
用户界面的外观将根据在哪个平台上运行脚本而有所不同:Linux,Windows,OSX 等。
工作原理
matplotlib 在matplotlib.backends.backend_wxagg模块中提供FigureCanvasWxAgg对象。 FigureCanvasWxAgg对象是一个 wxWidget 小部件,其中包含 matplotlib 图形。 该小部件的实际大小取决于它包含的图形。 在这里,我们创建一个6 x 6单位的Figure实例,每单位 80 像素:480 x 480像素。 创建Figure实例及其小部件就像运行以下代码一样容易:
self.fig = Figure((6, 6), dpi = 80)
canvas = FigureCanvasWxAgg(canvas_container, -1, self.fig)
与 Tkinter 示例一样,使用 matplotlib 小部件需要执行两个步骤。 我们必须绘制该图并进行更新。 同样,我们创建draw_figure()和refresh_figure()方法来处理这些步骤。
draw_figure()方法创建一个Axes实例,绘制曲线并跟踪结果,即一组线。 最后,该图呈现如下:
def draw_figure(self):
self.phi = np.linspace(0, 2 * np.pi, 1024)
r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3)
ax = self.fig.add_subplot(111, polar = True)
self.lines, = ax.plot(self.phi, r, lw = 3.)
self.fig.canvas.draw()
然后,由于需要用户输入,每次需要刷新图形时,我们将其称为refresh_figure()。 refresh_figure()方法使用以下代码更新绘制绘图的线组:
def refresh_figure(self):
r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3)
self.lines.set_ydata(r)
self.fig.canvas.draw_idle()
因此,正如我们所看到的,使用 wxWidget 或 Tkinter 不会在 matplotlib 方面引入任何明显的区别。 请注意,就像 Tkinter 一样,wxWidgets 滑块只能输出整数值的位置,并且我们必须使用先前秘籍的LinearScaling对象来获取实际值的位置。
将绘图集成到 GTK 用户界面
GTK 是一个用户界面库,在 Linux 环境中特别流行。 GTK 非常完整,其针对 Python 的PyGObject绑定使用起来特别方便。 在本秘籍中,我们演示了如何将 GTK 与 matplotlib 接口。 我们使用 SuperShape 应用进行此演示。
准备
此秘籍演示了如何将最新的 Python 绑定用于 GTK PyGObject。 因此,您将需要安装PyGObject(大多数 Linux 发行版都具有标准包),并且显然如果您还没有 GTK,则需要安装 GTK。
操作步骤
到现在为止,如果经历了 Tkinter 和 WxWidget 上的先前秘籍,则将看到 matplotlib 与用户界面集成方式的一种模式。 这里的模式是相同的:Matplolib 提供了特定于 GTK 的画布对象,该对象嵌入了Figure实例。 可以通过以下步骤将绘图集成到 GTK 用户界面:
-
我们从必要的导入指令开始,如下所示:
from gi.repository import Gtk import numpy as np from matplotlib.figure import Figure from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg -
我们添加以下 SuperShape 曲线定义:
def supershape_radius(phi, a, b, m, n1, n2, n3): theta = .25 * m * phi cos = np.fabs(np.cos(theta) / a) ** n2 sin = np.fabs(np.sin(theta) / b) ** n3 r = (cos + sin) ** (-1\. / n1) r /= np.max(r) return r -
然后,我们使用以下代码定义用户界面:
class SuperShapeWindow(Gtk.Window): def __init__(self): Gtk.Window.__init__(self, title = 'SuperShape') layout_box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) self.add(layout_box) self.m = 3 self.n1 = 2 self.n2 = 18 self.n3 = 18 self.fig = Figure((6, 6), dpi = 80) w, h = self.fig.get_size_inches() dpi_res = self.fig.get_dpi() w, h = int(np.ceil(w * dpi_res)), int(np.ceil(h * dpi_res)) canvas = FigureCanvasGTK3Agg(self.fig) canvas.set_size_request(w, h) layout_box.add(canvas) self.m_slider = Gtk.HScale.new(Gtk.Adjustment(self.m, 1, 20, 1., .1, 1)) self.m_slider.connect('value-changed', self.on_m_slide) layout_box.add(self.m_slider) self.n1_slider = Gtk.HScale.new(Gtk.Adjustment(self.n1, .01, 20, 1., .1, 1)) self.n1_slider.connect('value-changed', self.on_n1_slide) layout_box.add(self.n1_slider) self.n2_slider = Gtk.HScale.new(Gtk.Adjustment(self.n2, .01, 20, 1., .1, 1)) self.n2_slider.connect('value-changed', self.on_n2_slide) layout_box.add(self.n2_slider) self.n3_slider = Gtk.HScale.new(Gtk.Adjustment(self.n3, .01, 20, 1., .1, 1)) self.n3_slider.connect('value-changed', self.on_n3_slide) layout_box.add(self.n3_slider) self.draw_figure() def on_m_slide(self, event): self.m = self.m_slider.get_value() self.refresh_figure() def on_n1_slide(self, event): self.n1 = self.n1_slider.get_value() self.refresh_figure() def on_n2_slide(self, event): self.n2 = self.n2_slider.get_value() self.refresh_figure() def on_n3_slide(self, event): self.n3 = self.n3_slider.get_value() self.refresh_figure() def draw_figure(self): self.phi = np.linspace(0, 2 * np.pi, 1024) ax = self.fig.add_subplot(111, polar = True) r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3) self.lines, = ax.plot(self.phi, r, lw = 3.) self.fig.canvas.draw() def refresh_figure(self): r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3) self.lines.set_ydata(r) self.fig.canvas.draw_idle() -
总结一下,我们设置了,并使用以下代码启动了该应用:
win = SuperShapeWindow() win.connect('delete-event', Gtk.main_quit) win.show_all() Gtk.main() -
SuperShape曲线显示在一个窗口中,并且可以使用滑块调整曲线的参数,如下图所示:
工作原理
matplotlib 在matplotlib.backends.backend_gtk3agg模块中提供FigureCanvasGTK3Agg对象。 FigureCanvasGtk3Agg对象是一个包含 matplotlib 图形的 GTK 小部件。 我们必须使用以下代码设置canvas对象的大小:
self.fig = Figure((6, 6), dpi = 80)
w, h = self.fig.get_size_inches()
dpi_res = self.fig.get_dpi()
w, h = int(np.ceil(w * dpi_res)), int(np.ceil(h * dpi_res))
canvas = FigureCanvasGTK3Agg(self.fig)
canvas.set_size_request(w, h)
从那里,我们回到了熟悉的组织。 我们有,draw_figure()方法创建图和refresh_figure()方法更新图。 这些方法与 WxWidget 秘籍的方法相同。 WxWidget 秘籍的一些细微差异来自 GTK API 规范。 例如,GTK 中的滑块小部件可与浮点单元一起使用。
将绘图集成到 Pyglet 应用
Pyglet 是一个编写良好的 Python 模块,可以在任何平台上使用 OpenGL。 使用 Pyglet(从而使用 OpenGL)可以最大程度地使用计算机的图形硬件。 例如,使用 Pyglet 在具有花哨过渡效果的三个相邻屏幕上显示图形将非常容易。 在本秘籍中,我们将了解如何将 matplotlib 与 Pyglet 进行接口。 与前面的示例一样,我们将在整个屏幕上显示 SuperShape 曲线,并且没有任何小部件。
操作步骤
Pyglet 不与 Tkinter 和 wxWidgets 小部件具有相同的功能。 该脚本将曲线渲染为内存图像。 然后,该图像将简单地显示在整个屏幕表面上。 因此,该图将以全屏模式显示。 让我们看看如何使用以下代码完成此操作:
import pyglet, StringIO
import numpy as np
from matplotlib.figure import Figure
from matplotlib.backends.backend_agg import FigureCanvasAgg
def render_figure(fig):
w, h = fig.get_size_inches()
dpi_res = fig.get_dpi()
w, h = int(np.ceil(w * dpi_res)), int(np.ceil(h * dpi_res))
canvas = FigureCanvasAgg(fig)
pic_data = StringIO.StringIO()
canvas.print_raw(pic_data, dpi = dpi_res)
return pyglet.image.ImageData(w, h, 'RGBA', pic_data.getvalue(), -4 * w)
def draw_figure(fig):
X = np.linspace(-6, 6, 1024)
Y = np.sinc(X)
ax = fig.add_subplot(111)
ax.plot(X, Y, lw = 2, color = 'k')
window = pyglet.window.Window(fullscreen = True)
dpi_res = min(window.width, window.height) / 10
fig = Figure((window.width / dpi_res, window.height / dpi_res), dpi = dpi_res)
draw_figure(fig)
image = render_figure(fig)
@window.event
def on_draw():
window.clear()
image.blit(0, 0)
pyglet.app.run()
该脚本将以全屏模式显示曲线,并利用整个屏幕表面。 请注意,您必须按 Esc 键以关闭应用。
工作原理
matplotlib 提供了一个特殊对象FigureCanvasAgg,作为matplotlib.backends.backend_agg模块的一部分。 该对象构造器将图形作为输入,并将结果呈现到文件中。 使用print_raw方法,文件将包含原始像素数据。 标准的StringIO模块允许我们创建一个内存文件。 因此,我们仅要求FigureCanvasAgg渲染为StringIO文件,如下所示:
canvas = FigureCanvasAgg(fig)
pic_data = StringIO.StringIO()
canvas.print_raw(pic_data, dpi = dpi_res)
然后,我们可以检索内存中的数据并将其用于创建 Pyglet Image对象,如下所示:
pyglet.image.ImageData(w, h, 'RGBA', pic_data.getvalue(), -4 * w)
注意,我们必须指定图片的宽度w和高度h。 可以使用以下代码从Figure实例的尺寸及其分辨率推导得出:
w, h = fig.get_size_inches()
dpi_res = fig.get_dpi()
w, h = int(np.ceil(w * dpi_res)), int(np.ceil(h * dpi_res))
该秘籍更一般地向您展示如何将 matplotlib 图形渲染到内存缓冲区中。 例如,可以编写一个脚本,在内存中呈现多个图形,然后将其输入模块以创建视频。 因为所有这些都发生在内存中,所以它比仅将图片文件保存在硬盘上并随后将图片编译成视频要快。