Plotly Python 图工厂(Figure Factory)子图布局实战:quiver、streamline 与表格的组合网格
2026/9/21 2:41:19 网站建设 项目流程
  • 数据可视化
  • 数据分析

【免费下载链接】plotly.py

The interactive graphing library for Python :sparkles:

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

本篇技术指南聚焦 Plotly 的 figure factory 模块在子图(subplot)场景下的使用方法。由于图工厂函数(如create_quivercreate_streamlinecreate_table)返回的是完整独立的 Figure 对象,无法像普通 trace 一样直接放入make_subplots网格,因此需要一套特殊的坐标轴重组技巧。读完本文,你将掌握「先创建图工厂图 → 重映射 x/y 坐标轴 → 合并 data 与 layout」的完整工作流,能够自由组合矢场图、流线图与数据表格,构建横排、竖排乃至任意网格布局的多面板图。

为什么图工厂图需要特殊的子图处理

Plotly 的 Python API 中包含一个 figure factory 模块,它提供了一批包装函数,用于创建尚未内置于 plotly.js 中的特殊图表类型。图工厂函数返回的是一个完整的 Figure 对象plotly.graph_objs.Figure),因此像子图(subplot)这类依赖网格布局的功能,需要以略微不同的方式实现。

从当前仓库的 plotly/figure_factory/init.py 可以看到,plotly.figure_factory模块导出了以下工厂函数:

工厂函数图表类型依赖
create_dendrogram树状图numpy
create_quiver矢量场(quiver)图numpy
create_streamline流线图numpy
create_table表格numpy(可选 pandas)
create_trisurf三角剖分曲面numpy
create_hexbin_map六边形分箱地图numpy + pandas
create_ternary_contour三元等高线numpy + scikit-image

其中create_quivercreate_streamlinecreate_table是子图组合中最常用的三个工厂函数。关键区别在于:普通 trace(如go.Scattergo.Bar)只是data列表中的一个元素,可以自由指定xaxis/yaxis归属;而图工厂函数内部已经生成了完整的Figure(data, layout),坐标轴名称、布局域(domain)都已确定,想要放入子图就必须手动重组。

核心思路:手动重映射坐标轴后合并 Figure

图工厂子图的基本流程分为三步:

  1. 分别创建每个图工厂图(fig1fig2……);
  2. 改写坐标轴引用:把每个 trace 的xaxis/yaxis指向新坐标轴名(如x1/y1x2/y2),并在layout中初始化这些坐标轴、设置各自的domain(归一化画布上的位置区间)和anchor(锚定关系);
  3. 合并:用go.Figure()新建空图,通过add_traces加入各图的data,再通过layout.update依次合并各图的layout

示例一:垂直布局的 Quiver 与 Streamline

首先创建两个要放进子图的图工厂图:

import plotly.figure_factory as ff import plotly.graph_objects as go import numpy as np ## Create first figure x1, y1 = np.meshgrid(np.arange(0, 2, .2), np.arange(0, 2, .2)) u1 = np.cos(x1) * y1 v1 = np.sin(x1) * y1 fig1 = ff.create_quiver(x1, y1, u1, v1, name='Quiver') ## Create second figure x = np.linspace(-3, 3, 100) y = np.linspace(-3, 3, 100) Y, X = np.meshgrid(x, y) u = -1 - X**2 + Y v = 1 + X - Y**2 fig2 = ff.create_streamline(x, y, u, v, arrow_scale=.1, name='Streamline')

接着编辑两张图的 x/y 轴属性来构成子图。核心操作是:把每个 trace 的xaxis/yaxis改成显式的坐标轴名,然后为该坐标轴设置anchordomain

for i in range(len(fig1.data)): fig1.data[i].xaxis = 'x1' fig1.data[i].yaxis = 'y1' fig1.layout.xaxis1.update({'anchor': 'y1'}) fig1.layout.yaxis1.update({'anchor': 'x1', 'domain': [.55, 1]}) for i in range(len(fig2.data)): fig2.data[i].xaxis = 'x2' fig2.data[i].yaxis = 'y2' # initialize xaxis2 and yaxis2 fig2['layout']['xaxis2'] = {} fig2['layout']['yaxis2'] = {} fig2.layout.xaxis2.update({'anchor': 'y2'}) fig2.layout.yaxis2.update({'anchor': 'x2', 'domain': [0, .45]})

最后合并 data 与 layout,得到完整图形:

fig = go.Figure() fig.add_traces([fig1.data[0], fig2.data[0]]) fig.layout.update(fig1.layout) fig.layout.update(fig2.layout) fig.show()

要点解读:

  • domain: [0.55, 1]domain: [0, 0.45]把两张图分别放在画布的上半区与下半区,实现垂直堆叠;
  • anchor用于指定坐标轴彼此锚定:xaxis1锚定y1yaxis1锚定x1,两个坐标轴互为锚定对象,图表才会被正确放置;
  • 由于图工厂的layout中原本只包含默认的xaxis/yaxis,直接访问fig2.layout.xaxis2之前必须先用fig2['layout']['xaxis2'] = {}显式初始化,否则会因属性不存在而报错。

实战扩展一:水平布局的表格 + 折线图

图工厂的create_table非常适合与普通 trace 混排。下面把一张冰球联赛统计表放在左侧,把折线图放在右侧:

import plotly.graph_objects as go import plotly.figure_factory as ff table_data = [['Team', 'Wins', 'Losses', 'Ties'], ['Montréal<br>Canadiens', 18, 4, 0], ['Dallas Stars', 18, 5, 0], ['NY Rangers', 16, 5, 0], ['Boston<br>Bruins', 13, 8, 0], ['Chicago<br>Blackhawks', 13, 8, 0], ['LA Kings', 13, 8, 0], ['Ottawa<br>Senators', 12, 5, 0]] fig = ff.create_table(table_data, height_constant=60) teams = ['Montréal Canadiens', 'Dallas Stars', 'NY Rangers', 'Boston Bruins', 'Chicago Blackhawks', 'LA Kings', 'Ottawa Senators'] GFPG = [3.54, 3.48, 3.0, 3.27, 2.83, 2.45, 3.18] GAPG = [2.17, 2.57, 2.0, 2.91, 2.57, 2.14, 2.77] trace1 = go.Scatter(x=teams, y=GFPG, marker=dict(color='#0099ff'), name='Goals For<br>Per Game', xaxis='x2', yaxis='y2') trace2 = go.Scatter(x=teams, y=GAPG, marker=dict(color='#404040'), name='Goals Against<br>Per Game', xaxis='x2', yaxis='y2') fig.add_traces([trace1, trace2]) # initialize xaxis2 and yaxis2 fig['layout']['xaxis2'] = {} fig['layout']['yaxis2'] = {} # Edit layout for subplots fig.layout.xaxis.update({'domain': [0, .5]}) fig.layout.xaxis2.update({'domain': [0.6, 1.]}) # The graph's yaxis MUST BE anchored to the graph's xaxis fig.layout.yaxis2.update({'anchor': 'x2'}) fig.layout.yaxis2.update({'title': 'Goals'}) # Update the margins to add a title and see graph x-labels. fig.layout.margin.update({'t': 50, 'b': 100}) fig.layout.update({'title': '2016 Hockey Stats'}) fig.show()

注意这里 table 是直接由ff.create_table(table_data, height_constant=60)生成的 Figure(其 trace 默认使用x1/y1),所以无需新建空 Figure;随后用fig.add_traces([trace1, trace2])把两个折线 trace 追加进去,并把它们指向新初始化的x2/y2。表格占用xaxisdomain: [0, .5](左半),折线图占用xaxis2domain: [0.6, 1.](右半)。

实战扩展二:垂直布局的表格 + 柱状图

若想把表格放在上方、图表放在下方,则改为操纵yaxisdomain

import plotly.graph_objects as go import plotly.figure_factory as ff # Add table data table_data = [['Team', 'Wins', 'Losses', 'Ties'], ['Montréal<br>Canadiens', 18, 4, 0], ['Dallas Stars', 18, 5, 0], ['NY Rangers', 16, 5, 0], ['Boston<br>Bruins', 13, 8, 0], ['Chicago<br>Blackhawks', 13, 8, 0], ['Ottawa<br>Senators', 12, 5, 0]] # Initialize a figure with ff.create_table(table_data) fig = ff.create_table(table_data, height_constant=60) # Add graph data teams = ['Montréal Canadiens', 'Dallas Stars', 'NY Rangers', 'Boston Bruins', 'Chicago Blackhawks', 'Ottawa Senators'] GFPG = [3.54, 3.48, 3.0, 3.27, 2.83, 3.18] GAPG = [2.17, 2.57, 2.0, 2.91, 2.57, 2.77] # Make traces for graph trace1 = go.Bar(x=teams, y=GFPG, xaxis='x2', yaxis='y2', marker=dict(color='#0099ff'), name='Goals For<br>Per Game') trace2 = go.Bar(x=teams, y=GAPG, xaxis='x2', yaxis='y2', marker=dict(color='#404040'), name='Goals Against<br>Per Game') # Add trace data to figure fig.add_traces([trace1, trace2]) # initialize xaxis2 and yaxis2 fig['layout']['xaxis2'] = {} fig['layout']['yaxis2'] = {} # Edit layout for subplots fig.layout.yaxis.update({'domain': [0, .45]}) fig.layout.yaxis2.update({'domain': [.6, 1]}) # The graph's yaxis2 MUST BE anchored to the graph's xaxis2 and vice versa fig.layout.yaxis2.update({'anchor': 'x2'}) fig.layout.xaxis2.update({'anchor': 'y2'}) fig.layout.yaxis2.update({'title': 'Goals'}) # Update the margins to add a title and see graph x-labels. fig.layout.margin.update({'t': 75, 'l': 50}) fig.layout.update({'title': '2016 Hockey Stats'}) # Update the height because adding a graph vertically will interact with # the plot height calculated for the table fig.layout.update({'height': 800}) # Plot! fig.show()

与水平版相比,垂直版的关键差异:

  • 通过yaxis.update({'domain': [0, .45]})yaxis2.update({'domain': [.6, 1]})划分上下区域;
  • 图表坐标轴需要双向锚定yaxis2锚定x2xaxis2锚定y2
  • 因为create_table会根据行数自动计算画布高度(见下文源码),竖向叠加图表后会挤压空间,因此需要手动fig.layout.update({'height': 800})撑高画布;
  • 边距margin需要预留顶部(t: 75)与左侧(l: 50)空间,以免标题与 y 轴标签被截断。

方案对比:为什么不直接用 make_subplots

plotly.subplots.make_subplots是 Plotly 创建子图网格的标准入口(详见 plotly/subplots.py 中的make_subplots(rows, cols, shared_xaxes, shared_yaxes, start_cell, horizontal_spacing, vertical_spacing, subplot_titles, column_widths, row_heights, specs, insets, ...)),它预先生成xaxis1/yaxis1xaxis2/yaxis2等一套完整的坐标轴与 domain。对于普通 trace(go.Scattergo.Bar等),直接add_trace(trace, row, col)即可自动归属坐标轴。

图工厂返回的是完整 Figure 而非单个 trace,且其内部 layout 自带一套坐标轴,直接塞进make_subplots网格会与网格预置的坐标轴冲突。因此官方示例采用「手动重映射坐标轴」这一更底层的方案,这同时也是理解 Plotly 坐标轴模型(domain+anchor+xaxis/yaxis引用)的最佳切入点。

如果希望使用更现代的写法,也可以在make_subplots建好的网格中手动为 trace 指定坐标轴引用:例如先用make_subplots(rows=1, cols=2)得到预置的xaxis1/yaxis1xaxis2/yaxis2,再分别把表格 heatmap trace 与普通 scatter trace 的xaxis/yaxis指向对应轴名后add_trace。两种思路本质相同——坐标轴名称与 domain 的对应关系最终决定了每个 trace 落在哪个面板

图工厂函数参数深度解析

create_table的参数

create_table定义于 plotly/figure_factory/_table.py,签名如下:

create_table(table_text, colorscale=None, font_colors=None, index=False, index_title="", annotation_offset=0.45, height_constant=30, hoverinfo="none", **kwargs)
参数类型默认值说明
table_textpandas.DataFramelist[list]必填表格数据;传入 DataFrame 时自动取columns作为表头
colorscalestrlist[list][[0, '#00083e'], [0.5, '#ededee'], [1, '#ffffff']]表格配色:0 对应表头色,0.5 与 1 对应两条交替行色(设成相同值可取消斑马纹)
font_colorslist['#ffffff', '#000000', '#000000']字体颜色,长度须为 1、3 或len(table_text);为 3 时依次对应表头、奇数行、偶数行
indexboolFalse是否生成表头色的索引列
index_titlestr''索引列标题
annotation_offsetfloat0.45单元格文本相对格心的偏移量
height_constantint30行高系数,画布高度 =行数 × height_constant + 50
hoverinfostr'none'hover 信息内容
**kwargs--透传给底层 heatmap trace 的属性

源码实现的几个关键点:

  • 表格内部由一个heatmap trace承载底色(z为行索引矩阵:表头行取 0、奇数行取 0.5、偶数行取 1,配合 colorscale 形成斑马纹),所有单元格文字通过annotations逐格绘制(见_Table.make_table_annotations),表头与索引列文字自动加粗(<b>...</b>);
  • font_colors的合法性由validate_table校验,长度只能是 1、3 或行数,否则抛出PlotlyError
  • 布局中yaxis设置了autorange="reversed",保证表头位于顶部。

create_quiver的参数

create_quiver定义于 plotly/figure_factory/_quiver.py:

create_quiver(x, y, u, v, scale=0.1, arrow_scale=0.3, angle=math.pi / 9, scaleratio=None, **kwargs)
参数默认值说明
x,y必填箭头起点坐标(list 或 ndarray)
u,v必填箭头的 x/y 方向分量
scale0.1箭头整体缩放系数([0, 1],越小越不容易重叠)
arrow_scale0.3箭杆长度乘以该系数得到箭翼长度
anglepi/9箭翼张开角(弧度)
scaleratioNoney 轴与 x 轴比例(scale_y/scale_x);传入后布局会设置yaxis.scaleratio并锚定scaleanchor="x",用于保持箭头几何比例
**kwargs-透传给go.Scatter(如nameline

quiver 图内部由两个 scatter 组合:箭杆(barbs)与箭翼(arrowheads),通过None分隔成多段线段,最终mode="lines"。文档示例中的fig1 = ff.create_quiver(x1, y1, u1, v1, name='Quiver')即使用默认scale=0.1arrow_scale=0.3

create_streamline的参数

create_streamline定义于 plotly/figure_factory/_streamline.py:

create_streamline(x, y, u, v, density=1, angle=math.pi / 9, arrow_scale=0.09, **kwargs)
参数默认值说明
x,y必填一维且等间距的坐标数组
u,v必填二维速度场分量(ndarray)
density1流线密度,内部乘以 30 得到积分种子网格(与 matplotlib 等工具量纲对齐)
anglepi/9箭头张开角(弧度)
arrow_scale0.09箭翼长度缩放系数
**kwargs-透传给go.Scatter(如name

实现层面,validate_streamline会校验xy是否等间距(相邻步长差超过 0.0001 即抛PlotlyError),并要求 numpy 可用;流线轨迹采用RK4 四阶龙格-库塔积分rk4_integrate,算法源自 Bokeh 的 streamline 实现)沿速度场正反向追踪,再用网格空白标记避免轨迹重叠。文档示例中fig2 = ff.create_streamline(x, y, u, v, arrow_scale=.1, name='Streamline')将默认箭头比例略调大以增强可视化效果。

测试与验证

仓库在 tests/test_optional/test_figure_factory/test_figure_factory.py 中提供了工厂函数的行为验证,例如:

  • create_streamline对非等间距x/y输入抛出ValueError/PlotlyError的校验用例;
  • create_quiver在传入scale=1, scaleratio=0.5时生成固定比例箭头的用例。

这些测试印证了参数校验逻辑与「返回完整plotly.graph_objs.Figure」的契约,也说明在组合子图前,工厂函数产出的 data/layout 结构是稳定可预期的。

小结与进一步阅读

图工厂子图的本质是一套「坐标轴重映射」流程:把工厂函数产出的 trace 显式绑定到新坐标轴,通过domain划分画布区域、anchor建立轴间锚定,最后合并 layout。掌握了这套机制,无论表格 + 图表、quiver + streamline,还是任意工厂图与普通 trace 的组合,都能按需排布。

进一步参考:

  • 图工厂模块总览与各工厂函数清单:doc/python/figure-factories.md
  • 表格工厂专项文档:doc/python/figure-factory-table.md
  • Quiver 图专项文档:doc/python/quiver-plots.md
  • Streamline 图专项文档:doc/python/streamline-plots.md
  • 多坐标轴与子图布局专题:doc/python/multiple-axes.md 与 doc/python/mixed-subplots.md
  • make_subplots完整参数说明见 plotly/subplots.py
  • 坐标轴、domain、anchor 等图表属性的 API 参考见 doc/apidoc/plotly.graph_objects.rst
  • 数据可视化
  • 数据分析

【免费下载链接】plotly.py

The interactive graphing library for Python :sparkles:

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

相关推荐

上一篇:Apache Pulsar消息压缩算法深度测评:LZ4/Snappy/Zstd性能终极对决
下一篇:突破GAN训练瓶颈:StyleGAN中的Wasserstein距离与梯度惩罚技术解析

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询