【免费下载链接】plotly.py
The interactive graphing library for Python :sparkles:
plotly.subplots.make_subplots 是 plotly.py 中用于生成多子图(multi-plot)布局的核心辅助函数,它返回一个预先在 layout 中配置好子图网格的 plotly.graph_objects.Figure 实例。本文以官方 API 参考文档(plotly.subplots.rst)为骨架,结合 plotly/subplots.py 与 plotly/_subplots.py 的真实实现,系统讲解全部 20 余个参数的取值规则、默认值与底层行为,并给出可直接运行的代码示例,帮助你熟练搭建从标准网格到混合类型、跨行跨列、inset 叠加等各类复杂子图布局。
make_subplots 是什么
make_subplots 是 plotly.subplots 模块对外暴露的唯一公共接口(参见 plotly/subplots.py 中的 make_subplots 定义)。它本身只是薄薄的一层转发封装,真正的实现位于 plotly/_subplots.py 中的同名函数。
from plotly.subplots import make_subplots
import plotly.graph_objects as go
fig = make_subplots(rows=2)
调用后,fig 的 layout 中会按网格几何自动生成对应的坐标轴(如 xaxis1/yaxis1、xaxis2/yaxis2),随后你可以通过 fig.add_trace(..., row=r, col=c) 或 fig.add_scatter(..., row=r, col=c) 等快捷方法把 trace 放进指定格子。trace 与子图的绑定由 basedatatypes.py 中的 _set_trace_grid_position 调用 _set_trace_grid_reference 完成,后者会把 xaxis、yaxis 等引用写入 trace 属性。
网格基础参数:rows、cols 与 start_cell
rows / cols
rows:子图网格的行数,整数,必须大于 0(默认 1)。cols:子图网格的列数,整数,必须大于 0(默认 1)。
源码在 plotly/_subplots.py 中对二者做了严格校验,传入非整数或小于等于 0 的值会抛出 ValueError。测试用例 tests/test_core/test_subplots/test_make_subplots.py 覆盖了 rows=2.1、rows=-2、cols=2/3、cols=-10 等非法输入。
# 垂直堆叠两个子图,并各添加一条 scatter trace
from plotly.subplots import make_subplots
import plotly.graph_objects as go
fig = make_subplots(rows=2)
fig.add_scatter(y=[2, 1, 3], row=1, col=1)
fig.add_scatter(y=[1, 3, 2], row=2, col=1)
start_cell
start_cell:网格起始格(即(1, 1)格)的位置,取'top-left'(默认)或'bottom-left'。'top-left':(1, 1)位于左上角,(rows, cols)位于右下角;'bottom-left':(1, 1)位于左下角,编号自下而上。
源码用 row_dir / col_dir 两个方向变量控制遍历顺序:'top-left' 对应 row_dir = -1,'bottom-left' 对应 row_dir = 1(plotly/_subplots.py)。其他值会直接抛出 ValueError。
共享坐标轴:shared_xaxes 与 shared_yaxes
shared_xaxes 和 shared_yaxes 用于在 2D 笛卡尔子图(xy 类型)之间建立链接轴,取值可以是布尔值或字符串:
| 取值 | shared_xaxes 效果 | shared_yaxes 效果 |
|---|---|---|
False(默认) | 不共享 | 不共享 |
True / 'columns' | 同一列内的子图共享 x 轴 | 同一列内的子图共享 y 轴 |
'rows' | 同一行内的子图共享 x 轴 | 同一行内的子图共享 y 轴(True 也等价于 'rows') |
'all' | 网格内全部子图共享 x 轴 | 网格内全部子图共享 y 轴 |
源码在 plotly/_subplots.py 中限定合法取值为 [None, True, False, "rows", "columns", "all"],随后调用 _configure_shared_axes 逐行/逐列遍历网格,把后续轴的 matches 属性指向首个轴,并根据需要隐藏重复的刻度标签(showticklabels=False)。典型用法是纵向堆叠的多行时序图:
# 纵向堆叠两行,共享 x 轴,避免重复显示时间刻度
fig = make_subplots(rows=2, shared_xaxes=True)
fig.add_scatter(y=[2, 1, 3], row=1, col=1)
fig.add_scatter(y=[1, 3, 2], row=2, col=1)
注意:共享轴只对 type='xy' 且 colspan/rowspan 为 1 的子图生效(见 update_axis_matches 中的 span == 1 判断)。
间距控制:horizontal_spacing 与 vertical_spacing
horizontal_spacing:子图列之间的间距,归一化坐标下的浮点数,取值范围[0, 1],默认0.2 / cols;若网格中存在secondary_y子图则默认改为0.4 / cols(为右侧次轴留出空间)。vertical_spacing:子图行之间的间距,归一化坐标下的浮点数,取值范围[0, 1],默认0.3 / rows;若设置了subplot_titles则默认改为0.5 / rows(为标题留出空间)。
默认值逻辑见 plotly/_subplots.py。间距过大(大于 1 / (rows - 1) 或 1 / (cols - 1))时 _check_hv_spacing 会抛出 ValueError,提示“The resulting plot would have N rows/columns”。
子图标题:subplot_titles
subplot_titles:每个子图标题的列表,按**行主序(row-major)**排列,长度为 rows * cols。可以用空字符串 "" 占位,使标题索引与网格位置一一对应。
fig = make_subplots(rows=2, subplot_titles=('Plot 1', 'Plot 2'))
fig.add_scatter(x=[1, 2, 3], y=[2, 1, 2], row=1, col=1)
fig.add_bar(x=[1, 2, 3], y=[2, 1, 2], row=2, col=1)
标题在底层实现为 layout.annotations 中的文本标注,由 _build_subplot_title_annotations 生成:默认字体 font=dict(size=16),标注以 xref="paper"、yref="paper" 定位在每个子图 domain 的几何中心上方。make_subplots 也接受 font 关键字用于统一定制标题字体(见 plotly/_subplots.py)。
specs:逐格子精确定制布局
specs 是与 rows × cols 等尺寸的二维字典列表,每个字典描述一个格子的子图类型、跨行跨列与内边距。specs 的行索引对应 start_cell 起始的行方向('top-left' 时从上到下),列索引始终从左到右;specs[0][0] 即起始格子。
格子项为 None 表示该格留空(也可用于跨行/跨列后跳过的位置)。
specs 字典可用键
| 键 | 类型 / 默认值 | 说明 |
|---|---|---|
type | str,默认 'xy' | 子图类型,见下文“子图类型清单” |
secondary_y | bool,默认 False | 是否创建位于子图右侧的次 y 轴,仅对 type='xy' 有效 |
colspan | int,默认 1 | 该子图横向跨越的列数 |
rowspan | int,默认 1 | 该子图纵向跨越的行数 |
l | float,默认 0.0 | 格子左内边距 |
r | float,默认 0.0 | 格子右内边距 |
t | float,默认 0.0 | 格子上内边距 |
b | float,默认 0.0 | 格子下内边距 |
l/r/t/b 四个内边距参数会被直接叠加进子图 domain 计算(plotly/_subplots.py),用于在同一格内“挤”出更多空间;当内边距组合导致 domain 超出 [0, 1] 范围时,源码会抛出异常提示“A combination of the 'b' values, heights, and number of subplots too large”。
specs 的校验在 plotly/_subplots.py:维度必须与 rows × cols 完全一致,元素必须为字典或 None,未知键会报 Invalid key specified。测试用例 tests/test_core/test_subplots/test_make_subplots.py 覆盖了 specs 的各类非法形态。
子图类型清单
'xy':2D 笛卡尔子图,用于 scatter、bar、heatmap 等;'scene':3D 笛卡尔子图,用于 scatter3d、cone、mesh3d 等(兼容旧版is_3d=True写法,见 plotly/_subplots.py);'polar':极坐标子图,用于 scatterpolar、barpolar 等;'ternary':三元子图,用于 scatterternary;'map':地图子图,用于 scattermap、choroplethmap、densitymap;'domain':独立定位子图,用于 pie、parcoords、parcats 等自带domain属性的 trace;- trace 类型名:直接写 trace 类型(如
'scatter3d')也可,源码会通过 _subplot_type_for_trace_type 自动推断其所属子图类型。
子图类型集合定义在 plotly/_subplots.py:_single_subplot_types = {"scene", "geo", "polar", "ternary", "map"},再加上 "xy" 与 "domain"。其中 polar、ternary、map 三类 trace 上的绑定属性名是 subplot,其余是 scene、geo 等同名属性。
跨列 / 跨行示例
# 不规则布局:第一行两个子图,第二行一个横跨两列的子图
fig = make_subplots(
rows=2, cols=2,
specs=[[{}, {}],
[{'colspan': 2}, None]],
)
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[2, 1, 2]), row=1, col=1)
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[2, 1, 2]), row=1, col=2)
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[2, 1, 2]), row=2, col=1)
# 跨行示例:第一列一个纵向跨两行的子图,第二列两个子图
specs=[[{'rowspan': 2}, {}],
[None, {}]]
源码计算 domain 时,colspan/rowspan 会把格子扩展到对应行/列(plotly/_subplots.py),跨度过大(超出网格边界)会抛出 "Some 'colspan' value is too large" 异常。
混合子图类型示例
fig = make_subplots(
rows=2, cols=2,
specs=[[{'type': 'xy'}, {'type': 'polar'}],
[{'type': 'scene'}, {'type': 'ternary'}]],
)
fig.add_traces(
[go.Scatter(y=[2, 3, 1]),
go.Scatterpolar(r=[1, 3, 2], theta=[0, 45, 90]),
go.Scatter3d(x=[1, 2, 1], y=[2, 3, 1], z=[0, 3, 5]),
go.Scatterternary(a=[0.1, 0.2, 0.1],
b=[0.2, 0.3, 0.1],
c=[0.7, 0.5, 0.8])],
rows=[1, 1, 2, 2],
cols=[1, 2, 1, 2],
)
这里 add_traces 的 rows 与 cols 列表分别指定每条 trace 的落格位置。不同类型的子图在 layout 中由不同的容器承载:xy 生成 xaxis/yaxis 对(plotly/_subplots.py),scene/polar 等生成带 domain 的单一容器(plotly/_subplots.py),domain 类型则不写入 layout 而直接为 trace 计算 domain(plotly/_subplots.py)。
secondary_y:次 y 轴
在 type='xy' 的格子中设置 secondary_y=True 会为该子图额外创建一条位于右侧的次 y 轴(底层生成 yaxisN,属性 overlaying 指向主 y 轴、side="right",见 plotly/_subplots.py)。向次轴添加 trace 时使用 add_trace(..., secondary_y=True)。源码会校验:secondary_y 仅对 xy 类型有效,否则抛出 ValueError(plotly/_subplots.py)。
insets:在格子上叠加小图
insets:列表,每项一个字典,描述一个叠加在网格子图之上的 inset 子图。可用的键:
| 键 | 类型 / 默认值 | 说明 |
|---|---|---|
cell | tuple,默认 (1, 1) | 要叠加的网格子图 (row, col) 索引 |
type | str,默认 'xy' | inset 的子图类型 |
l | float,默认 0.0 | inset 左内边距,占格子宽度的比例 |
w | float 或 'to_end',默认 'to_end' | inset 宽度,占格子宽度的比例('to_end' 表示延伸到格子右边缘) |
b | float,默认 0.0 | inset 下内边距,占格子高度的比例 |
h | float 或 'to_end',默认 'to_end' | inset 高度,占格子高度的比例('to_end' 表示延伸到格子上边缘) |
# 在 (1,1) 格子的右上区域叠加一个 inset 子图
fig = make_subplots(insets=[{'cell': (1, 1), 'l': 0.7, 'b': 0.3}])
fig.add_scatter(x=[1, 2, 3], y=[2, 1, 1]) # 主图,默认 xaxis/yaxis
fig.add_scatter(x=[1, 2, 3], y=[2, 1, 2], xaxis='x2', yaxis='y2') # inset 图
inset 的 domain 计算见 plotly/_subplots.py:l/b 按格子宽高的比例换算,w/h 取 'to_end' 时延伸至格子边界。cell 行列越界会抛出 "Some 'cell' row value is out of range"。
列宽行高:column_widths 与 row_heights
column_widths:长度为cols的相对列宽列表。内部先归一化,再在扣除水平间距后按比例分配总宽度(plotly/_subplots.py)。兼容旧关键字column_width。row_heights:长度为rows的相对行高列表。start_cell='top-left'时从上到下应用,'bottom-left'时从下到上应用(plotly/_subplots.py)。兼容旧关键字row_width(旧写法始终自下而上应用)。
fig = make_subplots(rows=2, cols=2, column_widths=[3, 1], row_heights=[1, 2])
源码会在存在次 y 轴(max_width = 0.94)或行标题(max_width = 0.98)时自动压缩可用宽度,为图例与标题预留空间。
行列与全局标题:column_titles、row_titles、x_title、y_title
column_titles:长度为cols的列表,显示在每列顶部子图上方;row_titles:长度为rows的列表,显示在每行子图右侧,方向随start_cell变化;x_title:显示在最后一行子图下方、水平居中的全局 x 标题;y_title:显示在最左列子图左侧、垂直居中的全局 y 标题。
这些标题同样以 layout.annotations 实现,分别通过 title_edge="top" / "right" / "bottom" / "left" 指定锚定边(plotly/_subplots.py)。注意 x_title/y_title 的 domain 列表是写死的 (0, max_width) / (0, 1),即相对整图 paper 坐标系定位。
print_grid 与 Figure.print_grid:可视化网格布局
print_grid:布尔值,默认 False。设为 True 时,make_subplots 会打印一幅 ASCII 网格示意图,展示每个格子的坐标轴引用(plotly/_subplots.py)。生成的 Figure 本身也会把网格信息挂载到 fig.__dict__["_grid_ref"] 与 fig.__dict__["_grid_str"](plotly/_subplots.py),并通过 Figure.print_grid() 随时查看(定义于 basedatatypes.py)。网格字符串由 _build_grid_str 生成,跨行跨列分别以 : 与 - 标记,inset 以 [ x2,y2 ] over [ (1,1) x1,y1 ] 形式展示。
fig = make_subplots(rows=2, cols=2, specs=[[{}, {}], [{'colspan': 2}, None]])
# 输出:
# This is the format of your plot grid:
# [ (1,1) xaxis1,yaxis1 ] [ (1,2) xaxis2,yaxis2 ]
# [ (2,1) xaxis3,yaxis3 - ]
figure 参数:在既有 Figure 上叠加网格
figure:go.Figure 或 None(默认 None)。
- 为
None时,内部创建全新go.Figure()并填充坐标轴后返回; - 传入既有
go.Figure时,坐标轴会写入该图的layout并返回该图,若图中已有坐标轴则会被覆盖(plotly/_subplots.py)。
与 Figure API 的联动:add_trace(row/col) 与 get_subplot
使用 make_subplots 创建图后,最常用的落格方式是 add_trace(..., row=r, col=c)(以及 add_scatter、add_bar 等 trace 快捷方法,都带 row、col 参数)。其内部通过 _set_trace_grid_reference 依据 fig._grid_ref 查找格子并写入 xaxis/yaxis 等引用;格子为空白、行列越界、trace 类型与子图类型不兼容时都会抛出带明确提示的异常(plotly/_subplots.py)。
反向查询可用 Figure.get_subplot(row, col, secondary_y=False)(basedatatypes.py),返回该格子的轴对象:xy 类型返回 SubplotXY(xaxis=..., yaxis=...) 命名元组,scene 等单一类型返回对应的 layout 容器,domain 类型返回 SubplotDomain(x=..., y=...)(plotly/_subplots.py)。配套的测试见 tests/test_core/test_subplots/test_get_subplot.py。
实战组合示例
把上述能力组合起来,可以快速搭建一个带共享 x 轴、次 y 轴、跨列标题和全局标题的专业仪表盘式布局:
import plotly.graph_objects as go
from plotly.subplots import make_subplots
fig = make_subplots(
rows=2, cols=2,
shared_xaxes=True,
specs=[[{'secondary_y': True}, {'type': 'polar'}],
[{'colspan': 2}, None]],
subplot_titles=('Main + Secondary Y', 'Polar', 'Spanning Bottom'),
column_titles=('Column A', 'Column B'),
x_title='Time',
print_grid=True,
)
fig.add_scatter(y=[2, 1, 3], row=1, col=1)
fig.add_scatter(y=[4, 2, 5], row=1, col=1, secondary_y=True)
fig.add_scatterpolar(r=[1, 3, 2], theta=[0, 45, 90], row=1, col=2)
fig.add_bar(y=[1, 3, 2], row=2, col=1)
小结
make_subplots 是 plotly.py 中最常用的子图入口,其能力边界完全由 plotly/_subplots.py 中的参数校验与 domain 计算逻辑支撑:rows/cols 决定网格骨架,specs 决定每个格子的类型、跨度与内边距,shared_xaxes/shared_yaxes 决定轴联动,insets 支持叠加小图,column_widths/row_heights 与各类标题参数负责最终观感。理解这些参数在源码中的默认值与校验规则,可以帮助你在搭建复杂多图报告时一次到位,减少试错。
【免费下载链接】plotly.py
The interactive graphing library for Python :sparkles:
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



