资讯动态

用Plotly打造交互式数据可视化:从go.Figure到嵌入式看板

发布时间:2026/10/14 7:16:07 来源:尧图企业网站定制
搞数据可视化这些年我最大的一个体会是图表一旦要交给别人看静态图就有点不够用了。以前用静态绘图库画好一张折线图发给业务方之后对方大概率会追问这个日期范围能不能自己选这条线我能不能去掉别的只看一条某个异常点后面到底是什么情况每次都得重新改代码、重新出图、重新发文件一来一回就消耗掉大半个下午。后来我切换到Plotly来做交互式图表这个问题基本被彻底解决。Plotly 是一套开源交互式可视化方案核心思路是让图表保留探索能力鼠标悬停看数据、框选一段区间放大、点击图例开关系列、滑动范围选择器看局部趋势这些能力开箱即得不需要单独写前端代码。这正是我做数据周报、临时分析、甚至是给运营团队做自助看板时最需要的东西。这篇文章我会完整梳理自己的实操路径为什么选它、Figure 对象该怎么理解、五种高频图表怎么从零写出来、交互细节如何定制、数据量变大后如何优化以及最后怎么把图表真正嵌入到页面或仪表盘里交付出去。适合刚接触 Plotly 的新手也适合已经用过 px 但想进一步掌控图表细节的进阶读者。1. 为什么最终选了Plotly图表需求从静态走向探索1.1 一个真实项目场景老板要看“能点的图”某一次我在做内部数据周报的时候需求本身听起来很简单十二个业务指标、七个区域、按周更新想要一份能解读趋势的图表。刚开始我用静态绘图库画了一张包含多条折线的大图十二条线挤在一个坐标系里颜色都叠到分不清谁是谁。于是领导提了一个看起来非常朴素的要求“这个图能不能点一下某条线其他的先隐藏掉”就这一句话静态图的边界就被戳穿了。虽然用交互式后端也能实现但对于一个以数据分析和快速迭代为主的项目来说我不想把时间花在表格组件、点击事件、前端样式这些细节上。我需要的是“生成一个 HTML 文件双击打开就能看的那一种交互”。Plotly 恰好就是这个定位Python 里描述图表 → 图直接变成自带交互的 HTML 片段或网页渲染由浏览器完成我不需要自己写 JavaScript。1.2 与matplotlib和ECharts的取舍交互方式和技术栈的差异没有对比就没有伤害。这里简单说一下我为什么没有继续停留在静态方案也没有转向另一类前端图表库。方案交互能力与Python的集成度学习成本我的适用判断静态绘图库弱基本只能看要靠插件补充极高较低适合快速出印刷级图表不适合探索式阅读前端图表库 中间层封装交互强但需要理解前端体系依赖封装层有时折损能力偏高适合前端团队主导的BI项目不适合个人快速交付Plotly开箱即用hover/缩放/框选/图例开关齐全原生Python API数据帧直接进图中低个人分析、团队报表、轻量看板都很合适用一句话总结交互式图表的核心是把“看图权”交还给阅读者这正好是 Plotly 从设计之初就在做的事。而它用 Python 描述图形的方式又极其直观你不需要切换第二门语言。1.3 两种Python API的正确姿势px探路go交付刚接触 Plotly 的人经常被两套 API 弄混一套是高层的plotly.express通常导入为px另一套是底层的plotly.graph_objects通常导入为go)。我的使用习惯是探索数据时用 px因为它一行就能把数据帧映射成图表比如px.scatter(df, xrevenue, yprofit, colorregion)但是到了正式交付我基本会改用 go因为这种底层 API 能控制更多细节比如 hover 模板、坐标轴结构、子图布局、标记样式。听起来可能有点绕但你在实际做上三四个图之后就会自然形成这个判断px 是快车道go 才是修车厂。遇到需要微调的地方最终还是得回到 go 的世界里去。2. 先搞懂go.Figure的骨架data、layout与frames2.1 trace是一条数据的“图层”go.Figure是 Plotly 最核心的对象可以理解成一个“画布容器”。它内部其实只有三块内容data、layout和frames。data是一个 trace 列表。trace 这个概念一开始确实容易把人绕晕我自己的理解方式是一个 trace 就是画布上的一个数据图层。比如要画一条营收折线就是往data里面加一个go.Scatter要再叠加一张柱状图就再加一个go.Bar。每个 trace 有自己独立的颜色、标记方式、hover 内容互不干扰。import plotly.graph_objects as go fig go.Figure() fig.add_trace(go.Scatter(x[1, 2, 3], y[3, 1, 2], modelinesmarkers)) fig.add_trace(go.Bar(x[1, 2, 3], y[1, 2, 3])) fig.show()这段代码运行之后你会看到折线和柱状图叠在同一张图上。这个“叠加”动作就是 trace 存在的意义。2.2 layout是全局的“摆盘”如果说 trace 是菜那layout就是摆盘坐标轴范围、标题、图例位置、网格线、背景色、边距这些全都由 layout 控制。它同样是一个字典式结构你可以用属性访问的方式来设置比如fig.update_layout( title某区域营收趋势, xaxis_title日期, yaxis_title营收万元, legenddict(orientationh, y1.1), templateplotly_white )template是个好东西它相当于主题。我常用plotly_white因为白色背景放在报表里最干净如果你要深色大屏也可以直接用plotly_dark。frames 这块我反而用得不多它主要用于动画场景比如按时间播放不同月份的分布变化。说实话生产环境里动画看板有点华而不实我目前只在原型展示时用过知道它存在就行不必急着深入。2.3 update系列方法用增量更新替代全量赋值go 与 px 最让新手不适应的点之一就是它不像普通 Python 字典那样直接fig[layout] xxx。我更推荐的方式是使用它的update_layout、update_traces、update_xaxes这些增量更新方法。原因很简单一个 Figure 对象在创建之后会有很多默认值直接全量覆盖很容易把一些隐式设置改没了而 update 方法是在默认值基础上做“补丁式修改”安全得多。例如我只想改所有 trace 的标记大小和透明度fig.update_traces(markerdict(size8, opacity0.7))一行代码就作用于当前 Figure 里的所有 trace不用写循环。这种“批量操作”的设计思路在调整多子图时尤其顺手。更高级一点当你用make_subplots创建了多子图网格之后add_trace 可以指定放到哪一个子图from plotly.subplots import make_subplots fig make_subplots(rows1, cols2, subplot_titles(营收, 订单量)) fig.add_trace(go.Scatter(x[1,2,3], y[3,1,2]), row1, col1) fig.add_trace(go.Bar(x[1,2,3], y[2,3,1]), row1, col2) fig.update_layout(showlegendFalse) fig.show()有了这个能力图表就不再局限于“一张图一条线”而是可以布局成一个小型的组合仪表板。3. 五类高频图表从零实现附可运行代码与定制细节3.1 折线图与时间序列范围选择与悬浮信息折线图是日常分析里出现频率最高的图形。用 px 快速生成一手数据很容易import plotly.express as px df px.data.stocks() # 自带的示例数据集 fig px.line(df, xdate, y[GOOG, AAPL], title股价趋势) fig.show()但实际项目里我更常用 go 来画折线因为时间范围选择器这个功能实在太实用了。用rangeslider在图表下方加一个缩略滑块阅读者就能像看视频进度条一样快速把范围拖到某个时间段fig go.Figure(go.Scatter(xdf[date], ydf[GOOG], modelines, nameGOOG)) fig.update_xaxes(rangeslider_visibleTrue) fig.show()这个滑块组件会在 x 轴下方自动展开不需要额外写一行前端代码。第一次看到这个效果的人通常会惊叹“这就是我想要的”。3.2 柱状图与分组对比barmode与颜色映射柱状图的关键在于对比。下面这个例子是某个虚拟业务里不同渠道的月订单量对比fig go.Figure( go.Bar( x[线上, 门店, 分销], y[320, 180, 120], marker_color[#4C78A8, #F58518, #54A24B], text[320, 180, 120], textpositionoutside ) ) fig.update_layout(barmodegroup, yaxis_title订单量) fig.show()barmode有两个常用值group是并排分组适合对比同维度下的多类别stack是堆叠适合看总量构成。如果你手里是宽表数据px.bar(df, barmodegroup)会更快但 go 的灵活性体现在你可以在同一张图里手动组合不同数据系列的视觉表现。3.3 散点图的气泡编码size与color的二维信息叠加散点图最实用的地方在于用三个维度同时编码x 轴、y 轴各一维再用点大小和颜色各加一维。这就是所谓的气泡图做渠道绩效分析时非常好用。fig go.Figure( go.Scatter( x[1200, 800, 600], y[0.35, 0.28, 0.42], modemarkers, markerdict( size[45, 30, 20], color[0.8, 0.6, 0.9], colorscaleViridis, showscaleTrue, colorbardict(title转化率) ), text[渠道A, 渠道B, 渠道C] ) ) fig.update_layout(xaxis_title曝光量, yaxis_title转化率) fig.show()这里showscaleTrue会在图右侧生成一个颜色图例条读者能看懂颜色对应的数值范围。不过要提醒一句气泡面积与数值之间容易失真如果 size 编码的是原始数值而不是开方后的值视觉上会夸大差异。重要汇报前最好自己验证一下刻度是否合理。3.4 分布展示直方图与箱线图的一次性配置单看平均值很容易被极值带偏。所以我做数据分析的时候习惯先把直方图和箱线图并排摆出来一次性看数据的整体形状和离群点。from plotly.subplots import make_subplots fig make_subplots(rows1, cols2, subplot_titles(时长分布, 分组箱线图)) fig.add_trace(go.Histogram(x[1,2,2,3,3,3,4,4,4,4,5,5,6,10], nbinsx12), row1, col1) fig.add_trace( go.Box(y[1,2,3,4,5,20], x[A]*6, boxpointsoutliers), row1, col2 ) fig.show()直方图最重要的参数是nbinsx分箱数太少会丢失细节太多又会让柱子参差不齐。箱线图里的boxpointsoutliers可以让离群点以散点形式显示方便快速定位异常值。这块不需要太多花哨配置几个参数就能把数据分布讲得很清楚。3.5 相关性热力图的细节处理做多维指标分析时我几乎必画热力图。比如用 pandas 算出相关系数矩阵之后直接丢给 Plotlyimport pandas as pd corr df.corr(numeric_onlyTrue) fig go.Figure( go.Heatmap( zcorr.values, xcorr.columns, ycorr.index, colorscaleRdBu_r, zmin-1, zmax1, textcorr.round(2).values, texttemplate%{text}, textfont{size: 10} ) ) fig.update_layout(title指标相关性矩阵) fig.show()这里有两个细节值得注意RdBu_r这个色带用红蓝双色能一眼看出正负相关性而zmin和zmax固定为 -1 和 1是为了保证不同图表之间色标口径一致。如果不固定Plotly 会根据当前矩阵的极值自动缩放色标两张图放在一起对比时就会产生严重误导。4. 交互功能不是开关hover模板、工具栏与选择事件4.1 hovertemplate让悬浮框只说重点Plotly 的默认 hover 信息是够用的但在正式交付时那种“trace name 加 x、y 原始值”的默认框远远达不到要求。你需要自己控制悬浮框的内容和格式方法就是hovertemplate。fig go.Figure( go.Scatter( x[2024-01, 2024-02, 2024-03], y[120, 180, 150], customdata[[华东, 98], [华南, 120], [华北, 110]], hovertemplateb%{customdata[0]}/bbr月份%{x}br营收%{y}万br订单数%{customdata[1]}extra/extra ) ) fig.show()%{x}和%{y}是对应数据点的坐标值customdata是一个额外数据通道你可以把不方便放在坐标轴上的信息塞进去。extra/extra这个标签必须写上它用来隐藏默认的 trace 名称气泡否则悬浮框底部会出现一个不大不小的灰色小尾巴。整个模板字符串的写法很像 HTML但不需要你会前端照着拼接就行。4.2 config与modebar把不需要的按钮藏起来图表右上角默认有一排模式栏按钮包括框选、套索、缩放、下载图片等。这些功能很强大但在嵌入给业务方的页面里太多按钮反而让人不知道点哪里。我通常会把不常用的藏掉并关掉 logoconfig { displaylogo: False, modeBarButtonsToRemove: [lasso2d, autoScale2d], modeBarButtonsToAdd: [drawline] } fig.show(configconfig)如果你连模式栏都不想让用户看到可以把displayModeBar设为 False更好的做法是设置成hover鼠标碰到图表时再显示平时保持干净。config这个参数不只用于show()在write_html里同样可以传入。我一般习惯写一个固定的基础配置对象复用避免每次重新写。4.3 框选、点选与数据回调的联动思路交互不只是“鼠标指上去看”更大的价值在于“操作之后触发后续动作”。Plotly 在 Jupyter 环境下提供了FigureWidget可以把框选事件绑定到 Python 回调函数。不过说实话生产环境里我更推荐的做法是把整个交互逻辑放到 Dash 里那是 Plotly 官方的仪表盘框架数据联动和组件响应都围绕回调设计得非常成熟。如果是轻量场景简单思路是先给用户选择能力再用 Python 处理结果比如用fig.add_trace动态隐藏部分数据或者用fig.update_layout在回调里刷新坐标范围。对于前端嵌入的场景Plotly 还支持把图表事件通过 JavaScript 抛出去后端接到事件后再返回最新数据。链路本质上还是 Web 应用那套但核心绘图仍然只由 Plotly 负责。5. 数据量一大就卡怎么办从WebGL到分箱聚合的实操方案5.1 认清瓶颈DOM节点与重绘开销图表卡顿首先要理解卡在哪。默认模式下 Plotly 使用 SVG 渲染每一个数据点都对应一个页面节点。当数据量只有几百上千时没什么问题一旦到几万甚至几十万点浏览器光是维护这些节点的数量就已经很吃力zoom、hover 每一次重绘都要遍历大量元素不卡才怪。这类性能问题不是 Plotly 独有的而是 SVG 渲染机制天然的上限。遇到这种情况先不要急着加服务器先做两件事一是减少点位数二是切换渲染后端。5.2 换用WebGL类型的tracescattergl实战Plotly 提供了一套基于 WebGL 的散点图变体叫做Scattergl。它把绘图任务交给 GPU能承载的数据量比 SVG 高至少一个数量级。替换起来极其简单fig go.Figure( go.Scattergl( xbig_df[x], ybig_df[y], modemarkers, markerdict(size4, colorbig_df[z], colorscaleTurbo) ) ) fig.show()我测试过用Scattergl渲染几十万点缩放和刷选的流畅度还能接受如果用普通Scatter图表基本处于假死状态。缺点是Scattergl的一些高级定制能力比Scatter弱比如复杂的文本标记和部分 hover 格式支持不完整但在量级面前这些都可以取舍。5.3 分箱聚合让图表讲“群体规律”而不是“每一条记录”如果你的数据到了百万级连Scattergl都吃力那就需要换思路了让图表展示聚合后的规律而不是每条明细。最常用的办法是分箱统计。比如把时间段切分成若干个桶对每个桶求均值、分位数或总量把结果用go.Bar或go.Scatter画出来。这个思路的本质是用“统计量”代替“原始点”而业务阅读者真正关心的大多数也是规律而不是孤立细节。更激进一点的做法是借助降采样工具做数据聚合后再交给 Plotly 渲染但那个方案会引入额外工具链中小项目没必要一上来就上。5.4 静态嵌入与图片导出交付时的三个技巧图表最终要交付除了网页交互形式PDF 报告和 PPT 往往还是要静态图片。Plotly 5.x 之后推荐用 Kaleido 导出图片fig.write_image(output.png, enginekaleido, width1200, height600)另外两个技巧也建议收藏fig.write_html(report.html, include_plotlyjscdn)可以生成不内嵌整套 JS 的 HTML 文件体积大幅缩小适合放在服务器上引用公共 CDN 资源。如果是内网环境CDN 可能访问不了那么就保留默认的include_plotlyjsTrue虽然文件会大不少但胜在离线可用。full_htmlFalse可以只输出一个div片段方便你把它嵌进已有的页面框架而不是生成一整份 HTML 文档。6. 把图表交给用户页面嵌入、仪表盘与样式统一6.1 最小可用的HTML嵌入write_html与include_plotlyjs如果只是临时交付给两三个人看fig.write_html()是最快的路径。生成的 HTML 文件双击就能打开对方不需要装 Python也不需要联网装库。这个方案对非技术同事极其友好我经常直接把trend.html发到工作群大家打开就能自己缩放查看。fig.write_html(trend.html, include_plotlyjscdn, config{displaylogo: False})有一点要注意include_plotlyjscdn意味着 HTML 要访问公共 CDN离线时图表会显示不出来。如果对方环境无法访问外网就把这个参数去掉用默认的自包含模式。体积从几百 KB 涨到几 MB 的代价在离线场景下是值得的。6.2 与Streamlit集成的一个入门案例我目前最常用来做内部分析看板的组合是 “Plotly Streamlit”。Streamlit 是个纯 Python 的仪表盘框架画图表只需要一行import streamlit as st import plotly.express as px df px.data.gapminder() fig px.scatter( df.query(year 2007), xgdpPercap, ylifeExp, sizepop, colorcontinent, log_xTrue, size_max60 ) st.plotly_chart(fig, use_container_widthTrue)运行streamlit run app.py之后浏览器会打开一个带侧边栏的页面Plotly 图的所有交互能力都被原样保留。你可以再加st.selectbox选择年份、区域让用户自己切换数据这一步就把静态报表升级成了真正的自助探索工具。对这个组合我个人的体会是Streamlit 负责“页面框架”和“控件状态”Plotly 负责“数据表达”和“交互反馈”两者分工明确代码量却少得惊人。相比自己 Flask 搭前端这种方案能让数据分析师把精力集中在业务逻辑本身。6.3 维护经验配色、字体与样式统一图表一旦多了最怕的就是风格不统一。今天这个图用深色背景明天那个图用默认模板放在一个看板里会很乱。我的做法是建一个公共的 layout 配置函数统一定义模板、字体、边距和图例位置def apply_custom_style(fig): fig.update_layout( templateplotly_white, fontdict(familyPingFang SC, Microsoft YaHei, size13), margindict(l40, r40, t60, b40), legenddict(orientationh, yanchorbottom, y1.02, xanchorright, x1) ) return fig所有出图前都调用一次这个函数。这样你在写业务分析时就不需要每张图都重复强调字体和边距了。这个习惯帮我省下了非常多重复劳动也让最终交付给业务方的图表看起来像出自同一套设计语言。结尾部分最后再分享一个小技巧也是我踩过几次坑之后养成的习惯每次用 Plotly 出图都要先想清楚交付形式。如果只是自己在 Notebook 里探索用 px 一行到底如果要发 HTML 给别人看记得处理 CDN 和 config如果要嵌进看板尽早把公共样式抽出来。不要一张图一个风格最后统一调整的时候会非常痛苦。我个人用 Plotly 做交互式图表已经覆盖了从周报到嵌入式看板的绝大多数场景它真正解决的不是“画图”的问题而是“让别人自己看图”的问题。这份把探索权交给阅读者的能力才是交互式图表最大的价值所在。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑