plotly.py 子图布局完全指南:make_subplots 参数详解与源码级原理

  • 数据可视化
  • 数据分析

【免费下载链接】plotly.py

The interactive graphing library for Python :sparkles:

项目地址: https://gitcode.com/gh_mirrors/pl/plotly.py
点击查看 免费下载

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 字典可用键

键类型 / 默认值说明
typestr,默认 'xy'子图类型,见下文“子图类型清单”
secondary_ybool,默认 False是否创建位于子图右侧的次 y 轴,仅对 type='xy' 有效
colspanint,默认 1该子图横向跨越的列数
rowspanint,默认 1该子图纵向跨越的行数
lfloat,默认 0.0格子左内边距
rfloat,默认 0.0格子右内边距
tfloat,默认 0.0格子上内边距
bfloat,默认 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 子图。可用的键:

键类型 / 默认值说明
celltuple,默认 (1, 1)要叠加的网格子图 (row, col) 索引
typestr,默认 'xy'inset 的子图类型
lfloat,默认 0.0inset 左内边距,占格子宽度的比例
wfloat 或 'to_end',默认 'to_end'inset 宽度,占格子宽度的比例('to_end' 表示延伸到格子右边缘)
bfloat,默认 0.0inset 下内边距,占格子高度的比例
hfloat 或 '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:

项目地址: https://gitcode.com/gh_mirrors/pl/plotly.py
点击查看 免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值