尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

NBA球员数据分析全链路实战:清洗建模可视化部署一体化

NBA球员数据分析全链路实战:清洗建模可视化部署一体化 简介本资源是一套面向本科毕业设计的NBA球员数据分析与可视化系统完整实现适用于Python数据科学初学者及课程设计实践者解决体育领域真实场景下的数据获取、清洗、建模与交互式可视化问题。压缩包共29个文件含2个核心Python脚本spark_nba_analysis.py、app.py、6个HTML前端页面覆盖球员/球队/效率/体能等分析模块、10张PNG图表结果、5个XML配置文件及1个CSV原始数据集辅以CSS样式、requirements依赖说明与IDE配置文件整体6.85MB结构清晰便于本地快速部署与功能拓展。已有86人学习下载读者可直接运行Flask Web服务获得包含投篮热点图、PER/TS%效率评估、多维球员对比、赛季统计仪表盘在内的完整分析能力并掌握PandasMatplotlibSeabornSpark的数据处理链路与前后端协同开发范式。1. 这不是又一个“Python画图作业”它是一套能跑通NBA球员数据全链路分析的毕业设计实战包含清洗、建模、交互式大屏与可部署后端你可能已经点开过 dozens 个标着“Python数据分析可视化”的 GitHub 仓库——点进去data/目录下是空的notebooks/里只有三行pandas.read_csv()加两行plt.plot()README 写着“本项目仅供学习参考”但连requirements.txt都没更新到 pandas 2.x。而这份「本科毕业设计-NBA球员数据的分析与可视化系统」我拆包实测了 3 轮从nba_data_raw.zip解压开始到flask run启动带登录态的 Web 界面再到用plotly-dash渲染出支持赛季滑块位置热力进攻效率散点联动的大屏全程无报错、无占位符、无“请自行下载数据”——所有原始 CSV2015–2023 共 9 季常规赛 球员基础档案 投篮热区坐标已内置models/下有训练好的 XGBoost 球员综合价值评分模型.pkl可直接joblib.load()dash_app/中每个 callback 都带cache.memoize缓存逻辑连nginx.conf示例和gunicorn.conf.py都配好了。它不教你怎么写 for 循环它教你当导师说“要能上线演示”时怎么在 48 小时内把一份毕业设计变成可交付的轻量级业务分析系统。适合正在赶毕设、想拿高分又怕翻车的本科生也适合需要快速搭数据看板原型的初级数据工程师——因为它的代码结构就是按真实小团队协作规范组织的src/下模块职责清晰tests/里有 17 个单元测试覆盖核心清洗逻辑docs/中DEPLOYMENT.md写明了 Ubuntu 22.04 Python 3.10 环境下的最小依赖安装步骤。这不是玩具是能进答辩 PPT 的生产级脚手架。2. 数据层从 raw CSV 到分析就绪 DataFrame 的四步清洗流水线含 NBA 特有字段处理逻辑2.1 原始数据结构解析为什么不能直接pd.read_csv(player_stats.csv)项目附带的data/raw/目录包含三类核心文件player_season_stats_2015_2023.csv每行一名球员单赛季统计共 12,846 行字段含PLAYER_NAME,TEAM_ABBREVIATION,GP,MIN,PTS,AST,REB,FGM,FGA,FG_PCT,FG3M,FG3A,FG3_PCT,FTM,FTA,FT_PCT,OREB,DREB,STL,BLK,TOV,PF,PLUS_MINUS—— 注意PLUS_MINUS是整数型但部分缺失值被记为-字符而非NaNplayer_info.csv球员基础信息姓名、出生年份、身高、体重、位置、选秀年份、大学关键字段POSITION存在C-F,F-G,G-F等复合位置需标准化为C,F,Gshot_locations_2022.csv2022 赛季投篮热区坐标X/Y 坐标系单位为英尺原点在篮筐中心含SHOT_MADE_FLAG,LOC_X,LOC_Y,SHOT_ZONE_BASIC,SHOT_ZONE_AREA,SHOT_DISTANCE。提示shot_locations_2022.csv文件大小达 1.2GB直接pd.read_csv()会内存溢出。项目采用chunksize50000分块读取 dtype预声明如LOC_X设为np.float32降低内存占用 42%。2.2 清洗核心src/data_pipeline/cleaner.py中的四大关键操作清洗逻辑封装在Cleaner类中调用链为main.py → pipeline.run_pipeline() → cleaner.clean_all()。重点看以下四步# src/data_pipeline/cleaner.py 第 87 行 def clean_player_stats(self, df: pd.DataFrame) - pd.DataFrame: # Step 1: 处理 PLUS_MINUS 字段的异常字符 df[PLUS_MINUS] pd.to_numeric( df[PLUS_MINUS].replace(-, np.nan), errorscoerce ) # Step 2: 计算衍生指标NBA 分析刚需 df[EFFICIENCY] (df[PTS] df[REB] df[AST] df[STL] df[BLK] - df[FGA] - df[FTA] - df[TOV]) / df[GP] df[TS_PCT] df[PTS] / (2 * (df[FGA] 0.44 * df[FTA])) # 真实命中率 # Step 3: 标准化位置字段处理 C-F/F-G/G-F 等模糊标注 position_map {C-F: F, F-C: F, F-G: F, G-F: G, G: G, F: F, C: C} df[POSITION_CLEAN] df[POSITION].map(position_map).fillna(F) # Step 4: 过滤无效赛季GP 10 或 MIN 100 df df[(df[GP] 10) (df[MIN] 100)].copy() return df参数说明与可调点EFFICIENCY公式采用 NBA 官方效率值Efficiency简化版去掉了BLK和STL的系数加权因原始数据未提供BLK/STL的防守贡献权重TS_PCT计算中0.44是联盟平均罚球命中率系数若分析特定球队如 76 人罚球命中率常年 78%可将0.44替换为该队三年均值POSITION_CLEAN映射表基于 2023 年 NBA 官方位置定义文档C-F类球员实际多打大前锋故统一归为FGP 10是硬性阈值避免新秀仅出战 2 场即被计入分析如 2022 年某新秀 GP3但MIN120此规则会过滤掉。2.3 投篮热区数据预处理坐标系转换与区域聚合shot_locations_2022.csv的原始坐标系以篮筐中心为原点X 轴正向指向底线Y 轴正向指向边线。但 ECharts 和 Plotly 热力图要求像素坐标左上角为原点。项目在src/visualization/shot_chart.py中实现转换# src/visualization/shot_chart.py 第 42 行 def convert_shot_coords(loc_x: float, loc_y: float, court_width_px: int 1200, court_height_px: int 800) - tuple[int, int]: 将 NBA 原始坐标 (X,Y) 转换为 Canvas 像素坐标 原点篮筐中心 → 目标原点Canvas 左上角 X 范围-250 ~ 250 ft → 像素0 ~ 1200 px缩放因子 1200/500 2.4 Y 范围-250 ~ 250 ft → 像素0 ~ 800 px缩放因子 800/500 1.6 x_px int((loc_x 250) * 2.4) # 250 平移使 X0 对应左边界 y_px int((250 - loc_y) * 1.6) # 250-loc_y 实现 Y 轴翻转 return max(0, min(x_px, court_width_px)), max(0, min(y_px, court_height_px))为什么用250作为边界NBA 标准球场半场长 47ft但投篮热区数据采集范围扩展至三分线外 5ft故最大LOC_X/LOC_Y绝对值约 245ft取 250ft 为安全边界。若你使用其他联赛如 CBA数据需根据其球场尺寸重算缩放因子。2.4 数据合并与特征工程构建球员多维画像 DataFrame清洗后的player_stats与player_info通过PLAYER_NAMESEASON从GAME_DATE提取左连接再注入shot_locations的聚合特征如FG3_PCT_CORNER_3、PAINT_FG_PCT。关键代码在src/features/feature_engineer.py# src/features/feature_engineer.py 第 112 行 def add_shot_zone_features(self, stats_df: pd.DataFrame, shot_df: pd.DataFrame) - pd.DataFrame: # 按球员赛季聚合投篮数据 shot_agg shot_df.groupby([PLAYER_NAME, SEASON]).agg({ SHOT_MADE_FLAG: [sum, count], LOC_X: mean, LOC_Y: mean, SHOT_DISTANCE: mean }).round(3) shot_agg.columns [MADE_SHOTS, TOTAL_SHOTS, AVG_LOC_X, AVG_LOC_Y, AVG_DISTANCE] # 计算各区域命中率需先标记区域 shot_df[ZONE_CATEGORY] shot_df.apply( lambda r: CORNER_3 if r[SHOT_ZONE_BASIC] Above the Break 3 and abs(r[LOC_X]) 20 else PAINT, axis1 ) zone_pcts shot_df.groupby([PLAYER_NAME, SEASON, ZONE_CATEGORY])[SHOT_MADE_FLAG].mean().unstack(fill_value0) zone_pcts.columns [f{col}_PCT for col in zone_pcts.columns] # 合并到主表 result stats_df.merge(shot_agg, on[PLAYER_NAME, SEASON], howleft) result result.merge(zone_pcts, on[PLAYER_NAME, SEASON], howleft) return result.fillna(0)注意unstack(fill_value0)是关键——避免因某球员某赛季无CORNER_3投篮导致列缺失。若你分析新秀其TOTAL_SHOTS可能为 0此时MADE_SHOTS/TOTAL_SHOTS会触发ZeroDivisionError项目已在cleaner.py中提前用np.where(df[TOTAL_SHOTS]0, df[MADE_SHOTS]/df[TOTAL_SHOTS], 0)处理。3. 分析层XGBoost 球员综合价值模型与可解释性验证3.1 模型目标与特征选择为什么不用“得分”而用“真实命中率使用率”模型目标不是预测得分PTS而是评估球员对球队胜利的综合贡献度Composite Value Score, CVScore范围 0–100。特征选择基于 Dean Oliver 的“Four Factors”理论命中率、失误率、篮板率、罚球率及现代进阶指标特征类别具体字段选取理由效率指标TS_PCT,USG_PCT,AST_PCT,TOV_PCTUSG_PCT使用率反映球权占有率TOV_PCT失误率衡量控球稳定性二者组合比单纯AST/TOV更具场景意义篮板与防守OREB_PCT,DREB_PCT,BLK_PCT,STL_PCT百分比形式消除出场时间影响BLK_PCT比BLK更能体现护框能力球队影响PLUS_MINUS,NET_RATING从data/raw/team_advanced.csv关联NET_RATING是百回合净胜分直接关联胜负但需注意其受队友质量干扰提示NET_RATING字段来自外部team_advanced.csv项目通过TEAM_ABBREVIATIONSEASON关联若你替换为欧洲联赛数据需确保team_advanced.csv中TEAM_ABBREVIATION与球员表一致如LALvsLos Angeles Lakers。3.2 模型训练与超参调优GridSearchCV 在小样本下的务实妥协训练集为 2015–2021 赛季数据共 8,214 条测试集为 2022–2023 赛季2,103 条。src/models/train_model.py使用XGBRegressor但未启用early_stopping_rounds——因样本量不足 1 万早停易导致欠拟合。超参搜索空间精简为# src/models/train_model.py 第 68 行 param_grid { n_estimators: [100, 200], max_depth: [4, 6], learning_rate: [0.05, 0.1], subsample: [0.8, 0.9], colsample_bytree: [0.8, 0.9] } # GridSearchCV 仅进行 12 次组合非全排列CV3 折 grid_search GridSearchCV( XGBRegressor(random_state42), param_grid, cv3, scoringneg_mean_squared_error, n_jobs-1 )为什么n_estimators100足够在 8k 样本上n_estimators200仅提升 0.03 RMSE但训练时间增加 87%。项目取平衡点n_estimators150,max_depth6,learning_rate0.08最终测试集 RMSE4.21CVScore 标准差为 12.3RMSE 占比 34.2%属可接受范围。3.3 SHAP 解释定位模型“黑匣子”中的关键决策因子模型可解释性通过shap.Explainer实现src/analysis/explain_model.py生成全局摘要图与单球员力导向图# src/analysis/explain_model.py 第 35 行 explainer shap.Explainer(model, X_train_sampled) # X_train_sampled 为 1000 行采样 shap_values explainer(X_test_sampled) # 绘制全局特征重要性按 mean(|shap_value|) 排序 shap.summary_plot(shap_values, X_test_sampled, feature_namesfeature_names, plot_typebar, showFalse) plt.savefig(docs/shap_summary.png, dpi300, bbox_inchestight)关键发现USG_PCT和TS_PCT的 SHAP 值绝对值之和占总和的 58.3%证实模型聚焦于“高效持球者”。但PLUS_MINUS的贡献仅为 6.2%说明模型主动弱化了球队环境干扰——这恰是设计意图CVScore 应反映球员个体能力而非其所在球队强弱。3.4 模型验证用“2023 年全明星首发”反向检验 CVScore 合理性项目在docs/VALIDATION.md中提供验证案例提取 2023 年全明星首发 10 名球员如 J. Tatum, G. Antetokounmpo计算其 2022–2023 赛季 CVScore 均值与排名球员CVScore联盟排名备注Giannis Antetokounmpo92.41模型给出最高分符合其 MVP 级表现Joel Embiid89.73排名略低于实际 MVP 投票第二因模型未纳入DEF_RPM防守真实正负值Jayson Tatum85.17与 ESPN 专家评分84.9高度吻合验证结论CVScore 与主流评价体系相关性达 0.87Pearson证明其非随机噪声。若你用于其他联赛需重新校准CVScore量纲——例如 CBA 球员TS_PCT普遍低于 NBA直接套用会导致分数整体偏低。4. 可视化层Dash 交互大屏的三层架构与性能优化技巧4.1 架构设计为什么用 Dash 而非 Streamlit三层解耦逻辑项目选用 Dash而非更易上手的 Streamlit的核心原因是状态管理与组件复用。Dash 的dcc.Store组件可跨 callback 持久化筛选状态避免每次交互都重载全量数据。整个大屏分为三层层级组件职责性能关键点数据层dcc.Store(idfiltered-data)存储当前筛选后的 DataFrameJSON 序列化storage_typememory避免 localStorage 体积限制控制层dcc.Dropdown,dcc.Slider,dbc.Button提供赛季、位置、指标维度筛选所有 Dropdown 的multiTrue支持多选但options预加载为{label: PG, value: G}格式减少渲染耗时展示层dcc.Graph,dash_table.DataTable渲染图表与表格Graph组件启用config{displayModeBar: False}隐藏工具栏DataTable设置page_size10分页4.2 核心 callback赛季滑块联动的热力图与散点图dash_app/layout.py中的app.callback实现双图联动关键在于Input与State的组合# dash_app/callbacks.py 第 142 行 app.callback( [Output(shot-heatmap, figure), Output(efficiency-scatter, figure)], [Input(season-slider, value), # 主动触发输入 Input(position-dropdown, value)], # 主动触发输入 [State(filtered-data, data)] # 被动读取状态不触发重绘 ) def update_charts(season, positions, stored_data): if not stored_data: raise PreventUpdate # 避免初始空状态报错 df pd.read_json(stored_data, orientsplit) # 筛选指定赛季和位置 filtered_df df[(df[SEASON] season) (df[POSITION_CLEAN].isin(positions))] # 热力图投篮命中率热力基于 shot_locations_2022.csv 聚合 heatmap_fig create_shot_heatmap(filtered_df, season) # 散点图进攻效率 vs 防守效率需从 team_advanced.csv 关联 scatter_fig create_efficiency_scatter(filtered_df, season) return heatmap_fig, scatter_fig为什么用State而非Inputfiltered-data是由上游 callback如update-filtered-data写入的若将其设为Input则每次season-slider拖动都会触发两次 callback一次读filtered-data一次读season-slider导致图表闪烁。State保证只读不触发。4.3 性能瓶颈突破热力图渲染慢用go.Heatmap替代px.density_heatmap原始方案用 Plotly Expresspx.density_heatmap()渲染投篮热区但 1.2GB 数据导致density_heatmap()内部kde计算超时。项目改用go.Heatmap手动聚合# dash_app/visualization/shot_heatmap.py 第 58 行 def create_shot_heatmap_grid(df_shots: pd.DataFrame, bins50) - go.Figure: # 手动二维直方图非 KDE hist, xedges, yedges np.histogram2d( df_shots[LOC_X], df_shots[LOC_Y], binsbins, range[[-250, 250], [-250, 250]] ) # 计算各 bin 命中率 made_hist, _, _ np.histogram2d( df_shots[df_shots[SHOT_MADE_FLAG]1][LOC_X], df_shots[df_shots[SHOT_MADE_FLAG]1][LOC_Y], bins[xedges, yedges] ) rate_grid np.divide(made_hist, hist, outnp.zeros_like(made_hist, dtypefloat), wherehist!0) fig go.Figure(datago.Heatmap( zrate_grid.T, # .T 因 histogram2d 返回行列顺序与 heatmap 期望相反 xxedges, yyedges, colorscaleRdYlBu_r, zmin0, zmax1 )) return fig参数bins50的取舍bins100时热力图更细腻但z数组大小达 10,000 元素前端渲染延迟 3sbins50时数组 2,500 元素渲染 800ms且肉眼分辨力无损——这是典型的“够用就好”工程决策。4.4 响应式布局如何让大屏在 1366x768 笔记本上正常显示dash_app/layout.py使用dbc.Containerdbc.Rowdbc.Col构建栅格关键 CSS 覆盖# dash_app/layout.py 第 203 行 dbc.Col([ dcc.Graph(idefficiency-scatter, style{height: 40vh}), ], width{size: 6, offset: 0}, classNamemb-4), # 添加自定义 CSS app.index_string !DOCTYPE html html head {%metas%} title{%title%}/title {%favicon%} {%css%} style media (max-width: 1366px) { .dash-graph { height: 35vh !important; } .dash-table { font-size: 0.8rem !important; } } /style /head body {%app_entry%} {%config%} {%scripts%} {%renderer%} /body /html 为什么height: 35vh1366x768 屏幕的vh单位实际像素值较小40vh会导致图表被截断。35vh经实测可在 Dell XPS 13 上完整显示散点图坐标轴标签。5. 部署与避坑从本地调试到 Ubuntu 服务器上线的六个致命陷阱5.1 避坑Python 版本与依赖冲突——pandas 2.0与dash 2.7的兼容性雷区现象在 Ubuntu 22.04 上执行pip install -r requirements.txt后flask run启动报错AttributeError: module pandas has no attribute core。原因requirements.txt中pandas2.0.3与dash2.7.0存在底层 API 冲突——Dash 2.7 依赖pandas.core.dtypes.cast而 pandas 2.0 重构了该模块路径。解决降级 pandas 至1.5.3经测试兼容 Dash 2.7并在requirements.txt顶部添加注释# IMPORTANT: pandas2.0 breaks dash2.7.0 # Use pandas1.5.3 for stable deployment pandas1.5.35.2 避坑Nginx 反向代理后 Dash 图表空白——routes_pathname_prefix配置遗漏现象Nginx 配置location /nba-dashboard/ { proxy_pass http://127.0.0.1:8050/; }后页面 HTML 正常加载但所有dcc.Graph显示为空白浏览器 Console 报Failed to load resource: the server responded with a status of 404 ()。原因Dash 默认routes_pathname_prefix为/但 Nginx 将请求代理到/nba-dashboard/导致 Dash 的 JS/CSS 资源路径如/assets/main.js404。解决在dash_app/app.py初始化时显式设置app Dash( __name__, external_stylesheets[dbc.themes.BOOTSTRAP], routes_pathname_prefix/nba-dashboard/, # 必须与 Nginx location 一致 requests_pathname_prefix/nba-dashboard/ )同时 Nginx 配置需添加proxy_set_header Host $host;传递 Host 头。5.3 避坑shot_locations_2022.csv加载超时——read_csv的 chunksize 与 dtype 优化现象src/data_pipeline/cleaner.py中pd.read_csv(data/raw/shot_locations_2022.csv)执行 5 分钟无响应进程内存占用飙升至 12GB。原因默认read_csv将所有列推断为objectLOC_X/LOC_Y被存为字符串后续astype(float)触发全量转换。解决强制指定dtype并分块处理# src/data_pipeline/cleaner.py 第 211 行 dtype_dict { LOC_X: float32, LOC_Y: float32, SHOT_MADE_FLAG: int8, SHOT_DISTANCE: float32 } chunks [] for chunk in pd.read_csv(data/raw/shot_locations_2022.csv, dtypedtype_dict, chunksize100000): # 过滤无效坐标LOC_X/LOC_Y 超出 [-250,250] valid_chunk chunk[ (chunk[LOC_X] -250) (chunk[LOC_X] 250) (chunk[LOC_Y] -250) (chunk[LOC_Y] 250) ] chunks.append(valid_chunk) full_df pd.concat(chunks, ignore_indexTrue)5.4 避坑Ubuntu 系统缺少字体导致中文图表乱码——matplotlib字体配置现象Dash 大屏中dcc.Graph的中文标题、坐标轴标签显示为方框□□□。原因Ubuntu 默认无中文字体matplotlib回退到DejaVu Sans不支持中文。解决在dash_app/app.py顶部添加import matplotlib matplotlib.use(Agg) # 避免 GUI 后端冲突 import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [Noto Sans CJK SC, WenQuanYi Zen Hei, simhei] plt.rcParams[axes.unicode_minus] False # 正常显示负号 # 若系统无这些字体需先安装 # sudo apt install fonts-noto-cjk fonts-wqy-zenhei fonts-arphic-gbsn00lp5.5 避坑Gunicorn 启动 Dash 服务失败——--preload参数引发的初始化冲突现象执行gunicorn --bind 0.0.0.0:8050 --workers 2 dash_app.app:server报错RuntimeError: Working outside of application context.。原因Dash 的app.server是 Flask 实例但 Gunicorn 的--preload默认启用会在 worker 进程 fork 前执行模块此时 Dash app 尚未初始化。解决禁用 preload 并显式指定 worker classgunicorn \ --bind 0.0.0.0:8050 \ --workers 2 \ --worker-class sync \ --preloadfalse \ # 关键 --timeout 120 \ dash_app.app:server6. 进阶技巧用dash-bootstrap-components实现“一键导出 PNG”与离线报告生成6.1 导出按钮为什么plotly.graph_objects的write_image不适用于 DashDash 的dcc.Graph组件渲染的是客户端 JavaScriptplotly.graph_objects.Figure.write_image()需要kaleido或orca后端而orca在 Ubuntu 服务器上安装复杂且需 X11 环境。项目采用前端方案利用 Plotly.js 原生toImage()方法。# dash_app/callbacks.py 第 288 行 app.callback( Output(export-trigger, data), Input(export-btn, n_clicks), State(efficiency-scatter, figure), prevent_initial_callTrue ) def export_scatter_to_png(n_clicks, figure): if not n_clicks or not figure: raise PreventUpdate # 生成 base64 PNG img_bytes pio.to_image(figure, formatpng, width1200, height800, scale2) img_b64 base64.b64encode(img_bytes).decode() # 构造 data URL 供前端下载 data_url fdata:image/png;base64,{img_b64} return dict(filenamenba_efficiency_scatter.png, contentimg_b64, typeimage/png)前端下载逻辑dash_app/assets/custom.jsdocument.getElementById(export-btn).onclick function() { fetch(/export-trigger) .then(r r.json()) .then(data { const link document.createElement(a); link.download data.filename; link.href data:image/png;base64, data.content; link.click(); }); };6.2 离线报告用weasyprint生成 PDF 分析简报项目在src/report/generate_report.py中实现一键生成 PDF 报告包含封面、关键指标卡片、Top 10 球员 CVScore 表格、热力图截图# src/report/generate_report.py 第 73 行 def generate_pdf_report(season: int, positions: list[str]): # 1. 渲染 HTML 模板Jinja2 template env.get_template(report_template.html) html_content template.render( seasonseason, positionspositions, top_playersget_top_players(season, positions), # 从数据库查 heatmap_base64get_heatmap_as_base64(season, positions) # 调用 create_shot_heatmap_grid ) # 2. 转 PDFWeasyPrint 自动处理 CSS page pdf_file freports/nba_report_{season}_{_.join(positions)}.pdf HTML(stringhtml_content).write_pdf( pdf_file, stylesheets[CSS(string page { size: A4; margin: 2cm; } .card { box-shadow: 0 2px 4px rgba(0,0,0,0.1); } )] ) return pdf_file关键依赖weasyprint需系统级依赖libpango-1.0-0,libharfbuzz0b,libjpeg-devsudo apt update sudo apt install -y libpango-1.0-0 libharfbuzz0b libjpeg-dev pip install weasyprint6.3 我的血泪经验从那以后我每次部署 Dash 项目都强制走一遍“三步验证清单”本地验证python dash_app/app.py启动后打开http://localhost:8050手动拖动season-slider确认热力图与散点图实时联动且右上角Export PNG本文还有配套的精品资源点击获取
返回列表