如何堆叠10+子图?解决57张子图绘制的ValueError问题
解决Matplotlib子图数量限制的问题
问题描述
我需要绘制57张图表,每张包含两个数据集(下载和上传速度),尝试用子图堆叠显示,但绘制第3张子图时出现ValueError: num must be 1 <= num <= 2, not 3,看起来子图数量有限制。我的代码如下:
for k, v in tests.items(): print("Building graphs for " + str(k)) i = 1 while i <= v: print("Building graph for Config " + str(i)) cur.execute('Select Iteration, Download, Upload From Ookla_Client where Config = ' + str(i) + ';') db_return = cur.fetchall() x = [] for test in db_return: x.append(test[0]) y = [] for test in db_return: y.append(test[1]) y2 = [] for test in db_return: y2.append(test[2]) plt.subplot(2,1,i) plt.plot(x,y,'.-') plt.plot(x,y2,'.-') plt.title('Config 1') plt.xlabel('Test') plt.ylabel('Mb/s') plt.legend(['Download', 'Upload'], loc='upper right') plt.grid() i += 1
错误原因
你遇到的问题根源在于plt.subplot(2,1,i)的参数逻辑:plt.subplot(rows, cols, num)里的num参数必须是1到rows*cols之间的整数。你设置的是2行1列的子图布局,最多只能容纳2个子图,当i增加到3时,自然就超出了范围,触发报错。
解决方案
根据你的需求(绘制57张独立图表),推荐两种可行的实现方式:
方法1:每张图表单独创建一个新画布
这是最直接的方案,每次循环都创建一个新的figure对象,这样每张图都是独立的,不会受子图布局的限制。同时注意修正标题里的固定Config 1,改成对应当前的i值:
import matplotlib.pyplot as plt for k, v in tests.items(): print("Building graphs for " + str(k)) i = 1 while i <= v: print("Building graph for Config " + str(i)) cur.execute('Select Iteration, Download, Upload From Ookla_Client where Config = ' + str(i) + ';') db_return = cur.fetchall() # 简化数据提取的代码(可选优化) x = [test[0] for test in db_return] y = [test[1] for test in db_return] y2 = [test[2] for test in db_return] # 创建新的画布 plt.figure() plt.plot(x,y,'.-') plt.plot(x,y2,'.-') plt.title(f'Config {i}') # 动态设置标题 plt.xlabel('Test') plt.ylabel('Mb/s') plt.legend(['Download', 'Upload'], loc='upper right') plt.grid() # 如果需要保存每张图,可以在这里添加plt.savefig(f'config_{i}_speed.png') i += 1 plt.show() # 最后一次性显示所有图表
方法2:使用动态网格布局排列所有子图(适合需要在一个画布展示的场景)
如果你希望把所有图表放在同一个大画布上,可以动态计算合适的行列数(比如9行7列,总共63个位置,足够容纳57张图),然后用plt.subplot(rows, cols, i)来定位:
import matplotlib.pyplot as plt import math total_plots = 57 # 或者根据v的值计算总数量 rows = math.ceil(math.sqrt(total_plots)) # 计算行数,也可以手动设置比如9 cols = math.ceil(total_plots / rows) plt.figure(figsize=(cols*5, rows*4)) # 设置画布大小,避免图表太挤 for k, v in tests.items(): print("Building graphs for " + str(k)) i = 1 while i <= v: print("Building graph for Config " + str(i)) cur.execute('Select Iteration, Download, Upload From Ookla_Client where Config = ' + str(i) + ';') db_return = cur.fetchall() x = [test[0] for test in db_return] y = [test[1] for test in db_return] y2 = [test[2] for test in db_return] plt.subplot(rows, cols, i) plt.plot(x,y,'.-') plt.plot(x,y2,'.-') plt.title(f'Config {i}', fontsize=8) # 缩小标题字体避免重叠 plt.xlabel('Test', fontsize=7) plt.ylabel('Mb/s', fontsize=7) plt.legend(['Download', 'Upload'], loc='upper right', fontsize=6) plt.grid() plt.tick_params(axis='both', labelsize=6) # 缩小刻度字体 i += 1 plt.tight_layout() # 自动调整子图间距 plt.show()
额外优化建议
- 数据提取部分可以用列表推导式简化,让代码更简洁(如上面示例所示);
- 如果需要保存图表,建议在循环内添加
plt.savefig(),避免一次性显示57张图导致卡顿; - 注意SQL语句的安全性,直接拼接
str(i)存在SQL注入风险,建议使用参数化查询:cur.execute('Select Iteration, Download, Upload From Ookla_Client where Config = ?;', (i,))(具体语法根据你使用的数据库驱动调整)。
内容的提问来源于stack exchange,提问作者DaemonSlayer2048
相关产品推荐
相关产品推荐

