前面一篇文章 Python 绘制直方图 Matplotlib Pyplot figure bar legend gca text 有介绍如何用 Python 绘制直方图,但现实应用中需要在直方图的基础上再绘制折线图。比如测试报告中,就可以用图形反映测试用例的运行情况,直方图可以反映测试用例 pass 了多少,失败了多少,如果想反映 pass rate 趋势,就可以用折线图,本文介绍如何绘制多个图,以及最后应用在同一图上绘制直方图和折线图并存。
内容提要:
plt.figure 的作用是定义一个大的图纸,可以设置图纸的大小、分辨率等。
例如:
初始化一张画布
fig = plt.figure(figsize=(7,10),dpi=100)
直接在当前活跃的的 axes 上面作图,注意是当前活跃的
plt.plot() 或 plt.bar()
那么现在来看 subplot 和 subplots ,两者的区别在于 suplots 绘制多少图已经指定了,所以 ax 提前已经准备好了,而 subplot 函数调用一次就绘制一次,没有指定。
subplot 函数是添加一个指定位置的子图到当前画板中。
matplotlib.pyplot.subplot(*args, **kwargs)
函数签名:
subplot(nrows, ncols, index, **kwargs) 括号里的数值依次表示行数、列数、第几个
subplot(pos, **kwargs)
subplot(ax)
这个我没用应用成功
如果需要自定义画板大小,使用 subplot 这个函数时需要先定义一个自定义大小的画板,因为 subplot 函数无法更改画板的大小和分辨率等信息;所以必须通过 fig = plt.figure(figsize=(12, 4), dpi=200) 来定义画板相关设置;不然就按默认画板的大小; 同时,后续对于这个函数便捷的操作就是直接用 plt,获取当前活跃的图层。
例如:添加 4 个子图
没有自定义画板,所以是在默认大小的画板上添加子图的。
plt.subplot(221) 中的 221 表示子图区域划分为 2 行 2 列,取其第一个子图区域。子区编号从左上角为 1 开始,序号依次向右递增。
import matplotlib.pyplot as plt
# equivalent but more general than plt.subplot(221)
ax1 = plt.subplot(2, 2, 1)
# add a subplot with no frame
ax2 = plt.subplot(222, frameon=False)
# add a polar subplot
plt.subplot(223, projection='polar')
# add a red subplot that shares the x-axis with ax1
plt.subplot(224, sharex=ax1, facecolor='red')
plt.show()
效果:
subplots 函数主要是创建一个画板和一系列子图。
matplotlib.pyplot.subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True, subplot_kw=None, gridspec_kw=None, **fig_kw)
该函数返回两个变量,一个是 Figure 实例,另一个 AxesSubplot 实例 。Figure 代表整个图像也就是画板整个容器,AxesSubplot 代表坐标轴和画的子图,通过下标获取需要的子区域。
例 1:一个画板中只有 1 个子图
plt.subplots() 默认 1 行 1 列
import numpy as np
import matplotlib.pyplot as plt
# First create some toy data:
x = np.linspace(0, 2*np.pi, 400)
y = np.sin(x**2)
# Creates just a figure and only one subplot
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_title('Simple plot')
plt.show()
例 2:一个画板中只有 2 个子图
plt.subplots(1, 2)1 行 2 列
import numpy as np
import matplotlib.pyplot as plt
# First create some toy data:
x = np.linspace(0, 2*np.pi, 400)
y = np.sin(x**2)
# Creates two subplots and unpacks the output array immediately
f, (ax1, ax2) = plt.subplots(1, 2, sharey=True)
ax1.plot(x, y)
ax1.set_title('Sharing Y axis')
ax2.scatter(x, y)
plt.show()
例 3:一个画板中只有 4 个子图
plt.subplots(2, 2)2 行 2 列
通过索引来访问子图:
axes[0, 0] 第一个图位置
axes[0, 1] 第二个图位置
axes[1, 0] 第三个图位置
axes[1, 1] 第四个图位置
import numpy as np
import matplotlib.pyplot as plt
# First create some toy data:
x = np.linspace(0, 2*np.pi, 400)
y = np.sin(x**2)
# Creates four polar axes, and accesses them through the returned array
fig, axes = plt.subplots(2, 2, subplot_kw=dict(polar=True))
axes[0, 0].plot(x, y)
axes[1, 1].scatter(x, y)
plt.show()
例 4:子图共享 X 轴
sharex=‘col’
import numpy as np
import matplotlib.pyplot as plt
# Share a X axis with each column of subplots
plt.subplots(2, 2, sharex='col')
plt.show()
例 5:子图共享 Y 轴
sharey=‘row’
import numpy as np
import matplotlib.pyplot as plt
# Share a X axis with each column of subplots
plt.subplots(2, 2, sharey='row')
plt.show()
例 6:子图共享 X 和 Y 轴
sharex=‘all’, sharey=‘all’
import numpy as np
import matplotlib.pyplot as plt
# Share a X axis with each column of subplots
plt.subplots(2, 2, sharex='all', sharey='all')
# Note that this is the same as
# plt.subplots(2, 2, sharex=True, sharey=True)
plt.show()
例 7:创建制定编号的画图
编号为 10 的画板,如果该编号已经存在,则删除编号。
import numpy as np
import matplotlib.pyplot as plt
# Creates figure number 10 with a single subplot
# and clears it if it already exists.
fig, ax=plt.subplots(num=10, clear=True)
plt.show()
了解了 subplots 函数,对 twinx 函数的理解就容易多了。
twinx 返回一个新的子图并共用当前子图的 X 轴。新的子图会覆盖 X 轴(如果新子图 X 轴是 None 就会用当前子图的 X 轴数据),其 Y 轴会在右侧。同理 twiny 函数是共享 Y 轴的。
例:我们先画了一个红色线子图,再画一个蓝色线子图,蓝色线子图是共用红色线子图的 X 轴的,那么就可以实样实现:
fig, ax1 = plt.subplots() 生成一个画板 fig,并默认第一个子图 ax1.
ax1.plot(t, data1, color=color) ax1 第一子图区域画折线图
ax2 = ax1.twinx() 生成一个新的子图 ax2 ,并共享 ax1 第一子图的 X 轴
ax2.plot(t, data2, color=color) 在新的子图 ax2 区域画折线图
效果图:
代码:
import numpy as np
import matplotlib.pyplot as plt
# Create some mock data
t = np.arange(0.01, 10.0, 0.01)
data1 = np.exp(t)
data2 = np.sin(2 * np.pi * t)
fig, ax1 = plt.subplots()
color = 'tab:red'
ax1.set_xlabel('time (s)')
ax1.set_ylabel('exp', color=color)
ax1.plot(t, data1, color=color)
ax1.tick_params(axis='y', labelcolor=color)
ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis
color = 'tab:blue'
ax2.set_ylabel('sin', color=color) # we already handled the x-label with ax1
ax2.plot(t, data2, color=color)
ax2.tick_params(axis='y', labelcolor=color)
fig.tight_layout() # otherwise the right y-label is slightly clipped
plt.show()
有了前面的基础,我们来画一个直方图和折线图并存。基于前面一篇文章 Python 绘制直方图 Matplotlib Pyplot figure bar legend gca text 直方图的例子基础上再画折线图。
应用场景是测试用例运行结果图,直方图描述 Smoke 和 Regressin 用例pass 和 fail 数量,折线图描述总的用例 Pass Rate。把 Mock 的数字换一下就可以直接应用了,具体代码细节就不详细介绍了,可以参考 文章 Python 绘制直方图 Matplotlib Pyplot figure bar legend gca text 。
效果图:
代码:
import matplotlib.pyplot as plt
import matplotlib.font_manager as font_manager
import numpy as np
from matplotlib.ticker import FuncFormatter
def to_percent(num, position):
return str(num) + '%'
def draw_trend_image_test(reg_total_count_pass, reg_total_count_fail, smoke_total_count_pass, smoke_total_count_fail, date_list, image_file_name = 'test_result_trend.png'):
# attributes
bar_width = 0.3
regression_pass_color = '#0ac00a'
smoke_pass_color = '#068606'
font_name = 'Calibri'
label_size = 10
text_size = 8
title_size = 14
# set the color for failure
smoke_fail_color = []
for item in smoke_total_count_fail:
if item > 0:
smoke_fail_color.append("red")
else:
smoke_fail_color.append(smoke_pass_color)
reg_fail_color = []
for item in reg_total_count_fail:
if item > 0:
reg_fail_color.append("red")
else:
reg_fail_color.append(regression_pass_color)
total_pass_rate = [round((s_p+r_p)/(s_p+r_p+s_f+r_f), 2)*100 for s_p, r_p, s_f, r_f in zip(
smoke_total_count_pass, reg_total_count_pass, smoke_total_count_fail, reg_total_count_fail)]
if len(date_list) > 5:
fig, ax1 = plt.subplots(figsize=(10.8, 4.8))
else:
fig, ax1 = plt.subplots()
# draw bar
x = np.arange(len(date_list))
ax1.bar(x - bar_width/2, smoke_total_count_pass, color=smoke_pass_color, edgecolor=smoke_pass_color, width= bar_width, label="Smoke Passed")
ax1.bar(x - bar_width/2, smoke_total_count_fail, color="red", edgecolor=smoke_fail_color, width= bar_width, bottom=smoke_total_count_pass)
ax1.bar(x + bar_width/2, reg_total_count_pass, color=regression_pass_color, edgecolor=regression_pass_color, width= bar_width, label="Regression Passed")
ax1.bar(x + bar_width/2, reg_total_count_fail, color="red", edgecolor=reg_fail_color, width= bar_width, label="Failed", bottom=reg_total_count_pass)
# set title, labels
ax1.set_title("Test Result Trend", fontsize=title_size, fontname=font_name)
ax1.set_xticks(x, date_list,fontsize=label_size, fontname=font_name)
ax1.set_ylabel("Count",fontsize=label_size, fontname=font_name)
# set bar text
for i in x:
if smoke_total_count_fail[i] > 0:
ax1.text(i-bar_width/2, smoke_total_count_fail[i] + smoke_total_count_pass[i], smoke_total_count_fail[i],horizontalalignment = 'center', verticalalignment='bottom',fontsize=text_size,family=font_name,color='red',weight='bold')
ax1.text(i-bar_width, smoke_total_count_pass[i], smoke_total_count_pass[i],horizontalalignment = 'right', verticalalignment='top',fontsize=text_size,family=font_name,color=smoke_pass_color,weight='bold')
ax1.text(i, reg_total_count_pass[i], reg_total_count_pass[i], horizontalalignment = 'right', verticalalignment='top',fontsize=text_size,family=font_name,color=regression_pass_color,weight='bold')
if reg_total_count_fail[i] > 0:
ax1.text(i+ bar_width/2, reg_total_count_fail[i] + reg_total_count_pass[i], reg_total_count_fail[i],horizontalalignment = 'center', verticalalignment='bottom',fontsize=text_size,family=font_name,color='red',weight='bold')
# draw plot
ax2 = ax1.twinx()
ax2.plot(x, total_pass_rate, label='Total Pass Rate',
linewidth=2, color='#FFB90F')
ax2.yaxis.set_major_formatter(FuncFormatter(to_percent))
ax2.set_ylim(0, 100)
ax2.set_ylabel("Percent",fontsize=label_size, fontname=font_name)
# plt.show() # should comment it if save the pic as a file, or the saved pic is blank
# dpi: image resolution, better resolution, bigger image size
legend_font = font_manager.FontProperties(family=font_name, weight='normal',style='normal', size=label_size)
fig.legend(loc="lower center", ncol=4, frameon=False, prop=legend_font)
fig.savefig(image_file_name,dpi = 100)
if __name__ == '__main__':
reg_total_count_pass = [916,916,916,906,916,716,916,916,916,916]
reg_total_count_fail = [73,73,73,83,73,273,73,73,73,73]
smoke_total_count_pass = [420, 420, 420,420, 420, 400,420, 420, 420,420]
smoke_total_count_fail = [5,5,5,5,5,25,5,5,5,5]
date_list = ['2022/1/1', '2022/1/2', '2022/1/3','2022/1/4','2022/1/5', '2022/1/6', '2022/1/7','2022/1/8', '2022/1/9', '2022/1/10']
draw_trend_image_test(reg_total_count_pass, reg_total_count_fail, smoke_total_count_pass, smoke_total_count_fail, date_list)