当前位置: 首页 > news >正文

Matplotlib和Plotly知识点(Dash+Plotly分页展示)

Matplotlib和Plotly知识点(Dash+Plotly分页展示)

  • 0、Matplotlib、Plotly和Dash区别 (推荐用Dash+Plotly)
  • 1.1、Matplotlib (静态图)
      • 1. Figures(图形)
        • 概念
        • 创建Figure
        • 保存和显示Figure
      • 2. Subplots(子图)
        • 概念
        • 创建Subplots
      • 3. Axes(坐标轴对象)
        • 概念
        • 使用Axes对象绘图
      • 4. Ticks(刻度)
        • 概念
        • 定制刻度
      • 一、Matplotlib简介
      • 二、基本概念
        • 1. Figure(图形)
        • 2. Axes(子图)
        • 3. Axis(坐标轴)
        • 4. Artist(绘图元素)
      • 三、基本使用流程
        • 1. 导入库
        • 2. 准备数据
        • 3. 创建 Figure 和 Axes
        • 4. 绘制图形
        • 5. 设置图表属性
        • 6. 显示图表
      • 四、常见图表类型及示例
        • 1. 折线图(Line Plot)
        • 2. 散点图(Scatter Plot)
        • 3. 柱状图(Bar Chart)
        • 4. 直方图(Histogram)
        • 5. 饼图(Pie Chart)
      • 五、多子图布局
        • 1. 使用 `subplots` 函数
        • 2. 使用 `GridSpec` 实现复杂布局
      • 六、图表定制
        • 1. 颜色、标记和线条样式
        • 2. 字体和文本属性
        • 3. 坐标轴和刻度
      • 七、保存图表
  • 1.2 Matplotlib(完整示例)
  • 2、Seaborn
      • 一、概述
      • 二、安装与设置
      • 三、主要绘图函数
      • 四、数据准备
      • 五、统计估计
      • 六、社区与资源
  • 3.1、Plotly动态图(多组折线图 + 表格)
  • 3.2、Plotly示例
    • ✅ 1. `data_dict` 假数据构造(含 series 和 table)
    • ✅ 2. 使用 Plotly 绘制折线图 + 表格(每组上下两行)
    • ✅ 图表展示效果
    • ✅ 后续可选拓展:
  • 3.3 Plotly(完整示例)
  • 5.1、Dash分页显示多个plotly
    • ✅ 示例:分页显示 Plotly 折线图(Dash 实现)
    • 🔄 其他分页方式(替代 `dcc.Slider`):
  • 5.2、Dash分页显示多个plotly(已保存的)
    • ✅ 情况一:保存为多个 `.html` 文件
      • 示例代码:
    • ✅ 情况二:保存为 `.json` 的 Plotly 图对象(使用 `plotly.io.write_json`)
      • 示例代码(JSON方式):
    • ✅ 总结
  • 5.4 以日期时间分页展示
    • ✅ 目标功能
    • 🧩 第一步:保存图表 `save_plot.py`
    • 🧩 第二步:分页查看图表 `view_plot.py`
    • ✅ 使用说明
  • 5.3、Dash(完整示例)

0、Matplotlib、Plotly和Dash区别 (推荐用Dash+Plotly)

matplot (不支持滑动):了解fig, axes = plt.subplots即可
plotly(动态图):了解fig = make_subplots即可
Dash(分页):了解分页即可

🔍 一、三者功能定位对比

特性/库MatplotlibPlotlyDash
类型静态绘图库交互式绘图库基于 Plotly 的 Web 应用框架
图表交互性低(可嵌入GUI)高(支持缩放/悬停)高(完整 Web 前端)
图表保存保存为 PNG/PDF可保存为 HTML可通过网页访问/交互显示
适合场景快速本地分析Web + Notebook 展示搭建完整交互式仪表盘系统
安装pip 安装即可pip 安装即可需要安装 dash + 依赖

✅ 总结推荐

目标推荐
只保存静态图Matplotlib 保存 PNG
保存交互图Plotly 保存 HTML
分页浏览多个图Dash + Plotly 实现分页界面
图表标题为日期datetime.now().strftime(...) 命名文件

Dash+Plotly分页显示效果(适合用于图表展示):Plotly画图,Dash分页显示
在这里插入图片描述
完整代码:
Plotly画图

import numpy as np
import pandas as pd# 1.系列指标
def calculate_metrics_series(history_dict, risk_free_rate=0.0):"""# 折线图数据格式{"日收益率": {"series": [pd.Series1, pd.Series2, ...],  # 列表,而非dict"table": [{"model": "PPO1_MLP", ...},{"model": "PPO2_Lstm", ...}]},...}# 汇总表格数据(同上面的)[{'model': 'PPO1_MLP', 'max': 0.0099, 'min': 0.0, 'mean': 0.0089}{'model': 'PPO2_Lstm', 'max': 0.0099, 'min': 0.0, 'mean': 0.0089}]"""data_dict = {}for policy_name, history in history_dict.items():# net_worth = np.array(history['total_net_worth'])# profit = np.array(history['total_profit'])# rewards = np.array(history['total_reward'])# # dates = pd.date_range(end=history['current_data'], periods=len(net_worth))dates = pd.to_datetime(history['current_data'])# index = pd.to_datetime(historypre['current_data'])# 1. 计算每日收益率# 计算每日收益率,系列,# np.diff(net_worth):计算净值序列的相邻差值,net_worth[:-1]:取前n-1日的净值作为分母# 该计算方式与pandas的pct_change()方法等价:# daily_returns = np.diff(net_worth) / net_worth[:-1]# 假设 history["total_net_worth"] 已经有数据net_worth_series = pd.Series(history['total_net_worth'],dates)# 计算每日收益率daily_returns = net_worth_series.pct_change().fillna(0)  # 第一天收益率设为0# 2. 波动率# Step 2: 逐步计算从 day 1 到 t 的累积标准差#daily_returns.expanding():创建扩展窗口对象,第1个窗口:[0],第2个窗口:[0,1],第3个窗口:[0,1,2]daily_volatility = daily_returns.expanding().std().fillna(0)# # 计算从开始时间到当前步的波动率,和上面等价# volatility = [np.std(daily_returns[:i+1]) for i in range(len(daily_returns))]# 计算 5 日滚动标准差作为波动率# daily_volatility = daily_returns.rolling(window=5).std().fillna(0)# 添加到每个指标的 data_dictdef add_to_data_dict(name, series_data,policy_name, table_value=None):if name not in data_dict:data_dict[name] = {"series": [], "table": []}data_dict[name]["series"].append(pd.Series(series_data, index=dates, name=policy_name))data_dict[name]["table"].append({"model": policy_name, "max": np.max(series_data),"min": np.min(series_data), "mean": np.mean(series_data) if hasattr(series_data, "__len__") else series_data})# 原始数据# 构造 pandas.Series,索引为日期 # x = pd.to_datetime(historypre['current_data'])series_net = pd.Series(history['total_net_worth'], index=dates, name=policy_name)series_profit = pd.Series(history['total_profit'], index=dates, name=policy_name)series_reward = pd.Series(history['total_reward'], index=dates, name=policy_name)for metric_name, series in zip(["total_net_worth", "total_profit", "total_reward"],[series_net, series_profit, series_reward]):add_to_data_dict(metric_name, series, policy_name,table_value=None)# 计算的指标数据add_to_data_dict("日收益率", daily_returns,policy_name, table_value=None)add_to_data_dict("日波动率",daily_volatility,policy_name, table_value=None)return data_dict# 2.值指标,多个def calculate_metrics_values(history_dict, risk_free_rate=0.0):# data_dict = {}data_list = []for policy_name, history in history_dict.items():net_worth = np.array(history['total_net_worth'])profit = np.array(history['total_profit'])rewards = np.array(history['total_reward'])# dates = pd.date_range(end=history['current_data'], periods=len(net_worth))dates = pd.to_datetime(history['current_data'])# 收益率returns = net_worth / net_worth[0] - 1total_return = returns[-1]# 日收益率daily_returns = pd.Series(net_worth).pct_change().dropna()# 波动率(年化)volatility = daily_returns.std() * np.sqrt(252)# 夏普比率sharpe_ratio = (daily_returns.mean() - risk_free_rate) / daily_returns.std() * np.sqrt(252) if daily_returns.std() != 0 else 0# 最大回撤cumulative = pd.Series(net_worth)roll_max = cumulative.cummax()drawdown = (cumulative - roll_max) / roll_maxmax_drawdown = drawdown.min()# 卡玛比率calmar_ratio = total_return / abs(max_drawdown) if max_drawdown != 0 else np.inf# 胜率 & 盈亏比profit_diff = np.diff(profit)win_trades = profit_diff[profit_diff > 0]loss_trades = profit_diff[profit_diff < 0]win_rate = len(win_trades) / len(profit_diff) if len(profit_diff) > 0 else 0avg_win = win_trades.mean() if len(win_trades) > 0 else 0avg_loss = abs(loss_trades.mean()) if len(loss_trades) > 0 else 1e-8profit_factor = avg_win / avg_loss if avg_loss != 0 else np.inf# 存入列表,每个元素是一个字典data_list.append({"model": policy_name,"收益率": total_return,"波动率": volatility,"夏普比率": sharpe_ratio,"累计净值": net_worth[-1],"最大回撤": max_drawdown,"卡玛比率": calmar_ratio,"胜率": win_rate,"盈亏比": profit_factor,})return data_list#     # 存结果#     data_dict[policy_name] = {#         "收益率": total_return,#         "波动率": volatility,#         "夏普比率": sharpe_ratio,#         "累计净值": net_worth[-1],#         "最大回撤": max_drawdown,#         "卡玛比率": calmar_ratio,#         "胜率": win_rate,#         "盈亏比": profit_factor,#     }# return data_dictif __name__ == "__main__":import numpy as npimport pandas as pdfrom datetime import datetime, timedelta# 随机种子np.random.seed(42)n_steps = 100initial_net_worth = 10000price_changes = np.random.normal(loc=0.001, scale=0.01, size=n_steps)# 系列数据:净值、利润、奖励net_worth_series = initial_net_worth * np.cumprod(1 + price_changes)  # ✅ 系列profit_series = net_worth_series - initial_net_worth                  # ✅ 系列reward_series = price_changes * 100                                   # ✅ 系列history = {'total_net_worth': net_worth_series.tolist(),'total_profit': profit_series.tolist(),'total_reward': reward_series.tolist(),'current_data': pd.date_range('2023-01-01', periods=len(net_worth_series), freq='D').strftime('%Y-%m-%d').tolist()  # 存储格式化后的日期列表}# print(history)print("\n历史数据最后5条记录:")for key in history:print(f"{key}:")# 对数值数据保留4位小数,日期保持原格式values = [round(x, 4) if isinstance(x, (float, np.floating)) else x for x in history[key][-5:]]  # 可修改-5为其他数值调整显示数量print(values)# #### ************ceshi************# 1、计算指标,系列data_dict1 = calculate_metrics_series({'PPO1_MLP': history, 'PPO2_Lstm':history})print("\n方法一:评估指标:")# 新增打印代码print("各指标前3条数据预览:")for metric_name in data_dict1:print(f"\n=== {metric_name} ===")# 打印时序数据前3条print(f"时序数据({len(data_dict1[metric_name]['series'])}个模型):")for series in data_dict1[metric_name]['series']:print(f"{series.name}: {[round(x,4) for x in series.values[:3]]}...")# 打印表格数据前3条print(f"\n表格统计(前3项):")for table_item in data_dict1[metric_name]['table'][:3]:print({k: round(v,4) if isinstance(v, float) else v for k, v in table_item.items()})# 2、计算评估指标值,多值data_dict2 = calculate_metrics_values({'PPO1_MLP': history, 'PPO2_Lstm':history})print("\n方法二:评估指标:")print(data_dict2)# 3、绘图history_dict={'PPO1_MLP': history,'PPO2_Lstm': history,}# data_dict=metrics(history_dict)data_dict = calculate_metrics_series(history_dict)  # 生成时序和指标表格summary_values = calculate_metrics_values(history_dict)  # 生成整体指标汇总import pandas as pdimport numpy as npimport plotly.graph_objs as gofrom plotly.subplots import make_subplots# 总共有多少组指标,(折线图 + 表格)+(表格)n_metrics = len(data_dict) n_table = 1 #len(summary_values)# 创建子图:每组两行(折线图 + 表格),+(表格)fig = make_subplots(rows=n_metrics * 2+n_table, cols=1,row_heights=[0.6, 0.4] * n_metrics+[0.5]*n_table,# vertical_spacing=0.1,vertical_spacing=0.02,  # 间距改小specs=[[{"type": "xy"}], [{"type": "table"}]] * n_metrics+ [[{"type": "table"}]] * n_table,subplot_titles=[f"{metric} 折线图" if i % 2 == 0 else f"{metric} 表格"for metric in data_dict for i in range(2)]+[f"汇总指标表格{i}" for i in range(n_table)],)row = 1for metric_name, metric_data in data_dict.items():# 折线图部分for s in metric_data["series"]:fig.add_trace(go.Scatter(x=s.index,y=s.values,mode="lines",name=f"{metric_name} - {s.name}"),row=row, col=1)# 表格部分table = metric_data["table"]if not table:print(f"[警告] 表格为空: {metric_name}")# else:#     print(f"[信息] 表格不为空: {table}")#     for item in table:#         print(f"[信息] 表格内容: {item}")fig.add_trace(go.Table(header=dict(values=["模型", "最大值", "最小值", "平均值"],fill_color="lightblue",align="left"),cells=dict(# values=[#     [item["model"] for item in table],#     [item["max"] for item in table],#     [item["min"] for item in table],#     [item["mean"] for item in table],# ],values=[[item["model"] for item in table],[float(item["max"]) for item in table],[float(item["min"]) for item in table],[float(item["mean"]) for item in table],],fill_color="lavender",align="left")),row=row + 1, col=1)row += 2#  # 表格# table_df = pd.DataFrame(metric_data["table"])# fig.add_trace(#     go.Table(#         header=dict(values=list(table_df.columns), fill_color="lightgrey", align="left"),#         cells=dict(values=[table_df[col] for col in table_df.columns], fill_color="white", align="left")#     ),#     row=row + 1, col=1# )# row += 2# 汇总表格部分summary_df = pd.DataFrame(summary_values)fig.add_trace(go.Table(header=dict(values=list(summary_df.columns),fill_color="lightblue",align="left"),cells=dict(values=[summary_df[col] for col in summary_df.columns],fill_color="lavender",align="left")),row=row, col=1)# 布局调整fig.update_layout(height=600 * n_metrics,width=1000,title="多组指标:折线图 + 表格(含假数据)",showlegend=True,# margin=dict(t=40),  # 顶部边距,避免标题太靠上margin=dict(t=40, b=40, l=40, r=40),  # 上下左右边距autosize=True,  # 自动调整大小)# 显示图表fig.show()#保存到本地import os# 保存图表# 创建目录os.makedirs("charts", exist_ok=True)# 当前时间now = datetime.now()now_str = now.strftime("%Y-%m-%d %H:%M:%S")filename_str = now.strftime("%Y-%m-%d_%H-%M-%S")filepath = f"charts/linechart_{filename_str}.html"fig.write_html(filepath)print(f"✅ 图表保存:{filepath}")

Dash分页:

# view_plot.py
import os
from dash import Dash, dcc, html, Input, Output, State
import dash
import dash_bootstrap_components as dbcCHART_DIR = "charts"def get_sorted_chart_files():files = [f for f in os.listdir(CHART_DIR) if f.endswith(".html")]return sorted(files, key=lambda f: os.path.getmtime(os.path.join(CHART_DIR, f)))app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
app.title = "📊 图表分页浏览器"app.layout = html.Div([html.H3("📈 Plotly 图表分页浏览", style={"marginBottom": "20px"}),html.Div(id="chart-title", style={"fontSize": "18px", "marginBottom": "10px"}),html.Iframe(id="chart-frame", width="100%", height="600px", style={"border": "1px solid #ccc"}),html.Div([dbc.Button("⬅ 上一页", id="prev", color="primary"),dbc.Button("下一页 ➡", id="next", color="primary"),html.Span(id="page-indicator", style={"margin": "0 20px", "fontSize": "16px"}),dcc.Dropdown(id="page-selector", style={"width": "300px"}, clearable=False),], style={"marginTop": "20px", "display": "flex", "alignItems": "center", "gap": "10px"}),dcc.Store(id="chart-index", data=0)
], style={"padding": "20px"})@app.callback(Output("chart-frame", "srcDoc"),Output("chart-index", "data"),Output("chart-title", "children"),Output("page-indicator", "children"),Output("page-selector", "options"),Output("page-selector", "value"),Input("prev", "n_clicks"),Input("next", "n_clicks"),Input("page-selector", "value"),State("chart-index", "data"),prevent_initial_call=True
)
def update_chart(prev_clicks, next_clicks, selected_page, current_index):charts = get_sorted_chart_files()total = len(charts)if total == 0:return "<h3>📭 没有图表</h3>", 0, "无图表", "第 0 页 / 共 0 页", [], Nonectx = dash.callback_contexttriggered_id = ctx.triggered[0]["prop_id"].split(".")[0] if ctx.triggered else Noneif triggered_id == "next" and current_index < total - 1:current_index += 1elif triggered_id == "prev" and current_index > 0:current_index -= 1elif triggered_id == "page-selector" and selected_page is not None:current_index = selected_pagefile = charts[current_index]with open(os.path.join(CHART_DIR, file), "r", encoding="utf-8") as f:content = f.read()title = file.replace("linechart_", "").replace(".html", "").replace("_", " ")page_text = f"第 {current_index + 1} 页 / 共 {total} 页"dropdown_options = [{"label": f"{i + 1} - {charts[i].replace('linechart_', '').replace('.html', '').replace('_', ' ')}", "value": i}for i in range(total)]return content, current_index, f"图表时间:{title}", page_text, dropdown_options, current_index# 初始填充
@app.callback(Output("chart-frame", "srcDoc", allow_duplicate=True),Output("chart-title", "children", allow_duplicate=True),Output("page-indicator", "children", allow_duplicate=True),Output("page-selector", "options", allow_duplicate=True),Output("page-selector", "value", allow_duplicate=True),Input("chart-index", "data"),prevent_initial_call="initial_duplicate"
)
def initialize_chart(index):charts = get_sorted_chart_files()if not charts:return "<h3>📭 没有图表</h3>", "无图表", "第 0 页 / 共 0 页", [], Nonefile = charts[index]with open(os.path.join(CHART_DIR, file), "r", encoding="utf-8") as f:content = f.read()title = file.replace("linechart_", "").replace(".html", "").replace("_", " ")total = len(charts)page_text = f"第 {index + 1} 页 / 共 {total} 页"dropdown_options = [{"label": f"{i + 1} - {charts[i].replace('linechart_', '').replace('.html', '').replace('_', ' ')}", "value": i}for i in range(total)]return content, f"图表时间:{title}", page_text, dropdown_options, indexif __name__ == "__main__":# app.run_server(debug=True)app.run(debug=True)

1.1、Matplotlib (静态图)

https://www.runoob.com/matplotlib/matplotlib-tutorial.html
https://www.runoob.com/w3cnote/matplotlib-tutorial.html
https://github.com/rougier/matplotlib-tutorial?tab=readme-ov-file
https://github.com/datawhalechina/fantastic-matplotlib

知识点:
https://github.com/rougier/matplotlib-tutorial?tab=readme-ov-file
Introduction
Simple plot
Figures, Subplots, Axes and Ticks
Animation
Other Types of Plots
Beyond this tutorial
Quick references

Matplotlib 应用
Matplotlib 通常与 NumPy 和 SciPy(Scientific Python)一起使用, 这种组合广泛用于替代 MatLab,是一个强大的科学计算环境,有助于我们通过 Python 学习数据科学或者机器学习。
SciPy 是一个开源的 Python 算法库和数学工具包。
SciPy 包含的模块有最优化、线性代数、积分、插值、特殊函数、快速傅里叶变换、信号处理和图像处理、常微分方程求解和其他科学与工程中常用的计算。
NumPy(Numerical Python) 是 Python 语言的一个扩展程序库,支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。

Matplotlib Pyplot
Pyplot 是 Matplotlib 的子库,提供了和 MATLAB 类似的绘图 API。
Pyplot 是常用的绘图模块,能很方便让用户绘制 2D 图表。
Pyplot 包含一系列绘图函数的相关函数,每个函数会对当前的图像进行一些修改,例如:给图像加上标记,生新的图像,在图像中产生新的绘图区域等等。
以下是一些常用的 pyplot 函数:
plot():用于绘制线图和散点图
scatter():用于绘制散点图
bar():用于绘制垂直条形图和水平条形图
hist():用于绘制直方图
pie():用于绘制饼图
imshow():用于绘制图像
subplots():用于创建子图

  1. 基本绘图函数pyplot
    • 折线图plt.plot(x, y, [format_string], **kwargs)xy分别是x轴和y轴的数据,format_string可以用来指定线条的颜色、样式和标记,例如'r--o'表示红色虚线带圆形标记。
    • 柱状图
      • 垂直柱状图:plt.bar(x, height, width=0.8, **kwargs)x是柱子的位置,height是柱子的高度。
      • 水平柱状图:plt.barh(y, width, height=0.8, **kwargs)
    • 散点图plt.scatter(x, y, s=None, c=None, marker=None, **kwargs)s是点的大小,c是点的颜色,marker是点的形状。
    • 饼图plt.pie(sizes, labels=None, autopct='%1.1f%%', **kwargs)sizes是各部分的比例,labels是各部分的标签,autopct用于显示百分比。
    • 直方图plt.hist(x, bins=None, range=None, density=False, **kwargs)x是数据,bins是分组数量,range是数据范围,density表示是否将直方图归一化。
  2. 基本概念(Figures、Subplots、Axes 和 Ticks)
    1. Figure(图形)
      整个绘图区域的顶层容器,类似于一张画布,可以包含多个子图(Axes)。
    2. Axes(子图)
      绘图区域中的一个子区域,用于实际绘制数据。一个 Figure 可以包含多个 Axes。
    3. Axis(坐标轴)
      Axes 中的坐标轴,包括 x 轴和 y 轴,用于确定数据的范围和刻度。
    4. Artist(绘图元素)
      Matplotlib 中所有可绘制对象的统称,如线条、文本、图像等。

在 Matplotlib 中,Figures(图形)、Subplots(子图)、Axes(坐标轴对象)和 Ticks(刻度)是创建和定制可视化图形的重要概念,下面详细介绍它们。

1. Figures(图形)

概念

在Matplotlib里,Figure 是最顶层的容器,它就像是一张完整的画布,代表整个绘图窗口或者页面。所有的绘图元素,比如子图、标题、图例等,都要放置在这个 Figure 之上。一个 Figure 可以容纳多个 Axes(也就是子图)。

创建Figure

可以使用 plt.figure() 函数来创建新的 Figure 对象。通过函数的参数,能够指定图形的大小、分辨率等属性。

import matplotlib.pyplot as plt# 创建一个宽8英寸、高6英寸,分辨率为100 dpi的Figure
fig = plt.figure(figsize=(8, 6), dpi=100)
保存和显示Figure
  • 保存:使用 fig.savefig() 方法能将图形保存为文件,它支持多种文件格式,像 .png.jpg.pdf 等。
fig.savefig('my_plot.png')
  • 显示:调用 plt.show() 函数就可以将图形显示出来。
plt.show()

2. Subplots(子图)

概念

Subplots 用于在一个 Figure 中划分出多个小的绘图区域,这些小区域就叫做子图。每个子图都可以独立地绘制不同的图形,这对于对比和展示不同的数据非常有用。

创建Subplots
  • 使用 plt.subplots() 函数:这个函数可以一次性创建多个子图,并且会返回一个 Figure 对象和一个包含所有子图 Axes 对象的数组。
import matplotlib.pyplot as plt
import numpy as np# 创建一个2行2列的子图布局
fig, axes = plt.subplots(2, 2, figsize=(10, 8))# 生成示例数据
x = np.linspace(0, 10, 100)# 在不同子图中绘图
axes[0, 0].plot(x, np.sin(x))
axes[0, 1].plot(x, np.cos(x))
axes[1, 0].plot(x, np.tan(x))
axes[1, 1].plot(x, np.exp(x))plt.tight_layout()  # 自动调整子图布局,避免子图之间相互重叠
plt.show()
  • 使用 plt.subplot() 函数:该函数需要逐个创建子图,要指定子图的行数、列数以及当前子图的索引。
import matplotlib.pyplot as plt
import numpy as npx = np.linspace(0, 10, 100)# 创建第一个子图
plt.subplot(2, 2, 1)
plt.plot(x, np.sin(x))# 创建第二个子图
plt.subplot(2, 2, 2)
plt.plot(x, np.cos(x))# 创建第三个子图
plt.subplot(2, 2, 3)
plt.plot(x, np.tan(x))# 创建第四个子图
plt.subplot(2, 2, 4)
plt.plot(x, np.exp(x))plt.show()

3. Axes(坐标轴对象)

概念

AxesFigure 中的一个具体绘图区域,它包含了实际的绘图元素,例如线条、散点以及坐标轴等。每个 Axes 对象都有自己独立的坐标轴系统,能够在上面进行各种绘图操作。

使用Axes对象绘图

在使用 plt.subplots() 函数创建子图时,返回的 axes 数组中的每个元素都是一个 Axes 对象。可以通过调用 Axes 对象的方法来进行绘图和设置相关属性。

import matplotlib.pyplot as plt
import numpy as npfig, ax = plt.subplots()  # 创建一个包含单个Axes对象的Figurex = np.linspace(0, 10, 100)
y = np.sin(x)# 在Axes对象上绘图
ax.plot(x, y)# 设置Axes对象的属性
ax.set_title('正弦波')
ax.set_xlabel('X轴')
ax.set_ylabel('Y轴')plt.show()

4. Ticks(刻度)

概念

Ticks 指的是坐标轴上的刻度标记,其作用是表示坐标轴的取值范围和刻度间隔。我们可以对刻度的位置、标签、格式等进行自定义设置。

定制刻度
  • 设置刻度位置:使用 ax.set_xticks()ax.set_yticks() 方法分别设置 x 轴和 y 轴的刻度位置。
import matplotlib.pyplot as plt
import numpy as npfig, ax = plt.subplots()x = np.linspace(0, 10, 100)
y = np.sin(x)ax.plot(x, y)# 设置x轴刻度位置
ax.set_xticks([0, 2, 4, 6, 8, 10])plt.show()
  • 设置刻度标签:利用 ax.set_xticklabels()ax.set_yticklabels() 方法分别设置 x 轴和 y 轴的刻度标签。
import matplotlib.pyplot as plt
import numpy as npfig, ax = plt.subplots()x = np.linspace(0, 10, 100)
y = np.sin(x)ax.plot(x, y)# 设置x轴刻度位置
ax.set_xticks([0, 2, 4, 6, 8, 10])
# 设置x轴刻度标签
ax.set_xticklabels(['零', '二', '四', '六', '八', '十'])plt.show()
  • 自动调整刻度:使用 plt.tick_params() 函数能够对刻度的大小、颜色、方向等进行更精细的调整。
import matplotlib.pyplot as plt
import numpy as npfig, ax = plt.subplots()x = np.linspace(0, 10, 100)
y = np.sin(x)ax.plot(x, y)# 调整刻度参数
plt.tick_params(axis='both', which='major', labelsize=12, length=6, width=2, color='red')plt.show()

总之,FiguresSubplotsAxesTicks 是Matplotlib中构建和定制图形的关键要素,合理运用它们可以创建出各种各样复杂且美观的可视化图表。

一、Matplotlib简介

Matplotlib 是一个强大的 Python 数据可视化库,可用于创建各种静态、动态、交互式的图表。它具有高度的可定制性,能满足不同场景下的数据可视化需求,广泛应用于数据分析、科学研究、机器学习等领域。

二、基本概念

1. Figure(图形)

整个绘图区域的顶层容器,类似于一张画布,可以包含多个子图(Axes)。

2. Axes(子图)

绘图区域中的一个子区域,用于实际绘制数据。一个 Figure 可以包含多个 Axes。

3. Axis(坐标轴)

Axes 中的坐标轴,包括 x 轴和 y 轴,用于确定数据的范围和刻度。

4. Artist(绘图元素)

Matplotlib 中所有可绘制对象的统称,如线条、文本、图像等。

三、基本使用流程

1. 导入库
import matplotlib.pyplot as plt
import numpy as np
2. 准备数据
x = np.linspace(0, 10, 100)
y = np.sin(x)
3. 创建 Figure 和 Axes
fig, ax = plt.subplots()
4. 绘制图形
ax.plot(x, y)
5. 设置图表属性
ax.set_title('Sine Wave')
ax.set_xlabel('X-axis')
ax.set_ylabel('Y-axis')
6. 显示图表
plt.show()

四、常见图表类型及示例

1. 折线图(Line Plot)

用于展示数据随时间或其他连续变量的变化趋势。

import matplotlib.pyplot as plt
import numpy as npx = np.linspace(0, 10, 100)
y = np.cos(x)fig, ax = plt.subplots()
ax.plot(x, y, color='blue', linestyle='--', label='Cosine Wave')
ax.set_title('Cosine Wave')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.legend()
plt.show()
2. 散点图(Scatter Plot)

用于展示两个变量之间的关系,观察数据的分布情况。

import matplotlib.pyplot as plt
import numpy as npx = np.random.rand(50)
y = np.random.rand(50)
colors = np.random.rand(50)
sizes = 1000 * np.random.rand(50)fig, ax = plt.subplots()
ax.scatter(x, y, c=colors, s=sizes, alpha=0.5)
ax.set_title('Scatter Plot')
ax.set_xlabel('X')
ax.set_ylabel('Y')
plt.show()
3. 柱状图(Bar Chart)

用于比较不同类别之间的数据大小。

import matplotlib.pyplot as plt
import numpy as npcategories = ['A', 'B', 'C', 'D', 'E']
values = [20, 35, 30, 25, 40]fig, ax = plt.subplots()
ax.bar(categories, values)
ax.set_title('Bar Chart')
ax.set_xlabel('Categories')
ax.set_ylabel('Values')
plt.show()
4. 直方图(Histogram)

用于展示数据的分布情况,将数据分组并显示每个组内数据的频率或数量。

import matplotlib.pyplot as plt
import numpy as npdata = np.random.randn(1000)fig, ax = plt.subplots()
ax.hist(data, bins=30)
ax.set_title('Histogram')
ax.set_xlabel('Values')
ax.set_ylabel('Frequency')
plt.show()
5. 饼图(Pie Chart)

用于展示各部分占总体的比例关系。

import matplotlib.pyplot as pltsizes = [15, 30, 45, 10]
labels = ['Frogs', 'Hogs', 'Dogs', 'Logs']
explode = (0, 0.1, 0, 0)fig, ax = plt.subplots()
ax.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%', startangle=90)
ax.axis('equal')
ax.set_title('Pie Chart')
plt.show()

五、多子图布局

1. 使用 subplots 函数
import matplotlib.pyplot as plt
import numpy as npx = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)fig, axs = plt.subplots(2, 1)
axs[0].plot(x, y1)
axs[0].set_title('Sine Wave')
axs[1].plot(x, y2)
axs[1].set_title('Cosine Wave')plt.tight_layout()
plt.show()
2. 使用 GridSpec 实现复杂布局
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as npx = np.linspace(0, 10, 100)
y = np.sin(x)fig = plt.figure()
gs = gridspec.GridSpec(2, 2)ax1 = fig.add_subplot(gs[0, :])
ax1.plot(x, y)
ax1.set_title('Full Width Plot')ax2 = fig.add_subplot(gs[1, 0])
ax2.scatter(x, y)
ax2.set_title('Scatter Plot')ax3 = fig.add_subplot(gs[1, 1])
ax3.bar(x[:10], y[:10])
ax3.set_title('Bar Plot')plt.tight_layout()
plt.show()

六、图表定制

1. 颜色、标记和线条样式
import matplotlib.pyplot as plt
import numpy as npx = np.linspace(0, 10, 10)
y = np.random.rand(10)fig, ax = plt.subplots()
ax.plot(x, y, color='red', marker='s', linestyle='-.')
ax.set_title('Customized Line Plot')
ax.set_xlabel('X')
ax.set_ylabel('Y')
plt.show()
2. 字体和文本属性
import matplotlib.pyplot as plt
import numpy as npx = np.linspace(0, 10, 100)
y = np.sin(x)fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_title('Sine Wave', fontsize=18, fontweight='bold')
ax.set_xlabel('X-axis', fontsize=14)
ax.set_ylabel('Y-axis', fontsize=14)
plt.show()
3. 坐标轴和刻度
import matplotlib.pyplot as plt
import numpy as npx = np.linspace(0, 10, 100)
y = np.cos(x)fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_xlim(0, 12)
ax.set_ylim(-1.2, 1.2)
ax.set_xticks([0, 2, 4, 6, 8, 10])
ax.set_xticklabels(['Zero', 'Two', 'Four', 'Six', 'Eight', 'Ten'])
ax.set_title('Cosine Wave with Custom Ticks')
ax.set_xlabel('X')
ax.set_ylabel('Y')
plt.show()

七、保存图表

import matplotlib.pyplot as plt
import numpy as npx = np.linspace(0, 10, 100)
y = np.sin(x)fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_title('Sine Wave')
ax.set_xlabel('X')
ax.set_ylabel('Y')fig.savefig('sine_wave.png', dpi=300, bbox_inches='tight')

以上就是 Matplotlib 的基本教程,涵盖了常见图表类型、多子图布局、图表定制和保存等方面的知识和示例。通过学习这些内容,你可以使用 Matplotlib 创建出各种美观、实用的可视化图表。

1.2 Matplotlib(完整示例)

多模型多指标

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd# 假设我们有3个模型和3个指标,时间维度是10个step
models = ['Model A', 'Model B', 'Model C']
metrics = ['Sharpe Ratio', 'Max Drawdown', 'Annual Return']
time = np.arange(1, 11)# 模拟数据:每个模型的每个指标在不同时间的值
np.random.seed(0)
data = {metric: {model: np.random.rand(10) for model in models} for metric in metrics}# 模拟最后一个时间点的对比指标(表格)
final_scores = {metric: {model: round(data[metric][model][-1], 3) for model in models} for metric in metrics}# 开始绘图
fig, axes = plt.subplots(nrows=len(metrics), ncols=1, figsize=(12, 4 * len(metrics)))if len(metrics) == 1:axes = [axes]for idx, metric in enumerate(metrics):ax = axes[idx]# 绘制折线图for model in models:ax.plot(time, data[metric][model], label=model)ax.set_title(f'{metric} Over Time')ax.set_xlabel('Time Step')ax.set_ylabel(metric)ax.legend(loc='upper left')# 添加表格(在图下方)cell_text = [[final_scores[metric][model] for model in models]]table = ax.table(cellText=cell_text,colLabels=models,rowLabels=[f'{metric} Final'],loc='bottom',cellLoc='center',bbox=[0, -0.35, 1, 0.2])  # 表格位置:x, y, width, heightax.margins(y=0.2)plt.tight_layout()
plt.subplots_adjust(hspace=0.6)  # 留出空间给表格
plt.show()

2、Seaborn

https://www.runoob.com/matplotlib/seaborn-tutorial.html
https://www.cainiaoya.com/seaborn/seaborn-jiaocheng.html
Seaborn 是一个建立在 Matplotlib 基础之上的 Python 数据可视化库,专注于绘制各种统计图形,以便更轻松地呈现和理解数据。
Seaborn 的设计目标是简化统计数据可视化的过程,提供高级接口和美观的默认主题,使得用户能够通过少量的代码实现复杂的图形。
Seaborn 提供了一些简单的高级接口,可以轻松地绘制各种统计图形,包括散点图、折线图、柱状图、热图等,而且具有良好的美学效果。

User: Seaborn知识点汇总,不要输出代码
Kimi: 以下是 Seaborn 的知识点汇总,不包括代码示例:

一、概述

  • 定义:Seaborn 是一个基于 Matplotlib 的 Python 数据可视化库,专注于绘制统计图形,旨在简化数据可视化过程[1],并提供美观的默认主题和高级接口,兼具统计学的深度和可视化艺术性[2]。
  • 特点
    • 提供多种美观的默认主题和颜色调色板。
    • 支持多种绘图函数,能够轻松绘制散点图、折线图、柱状图、箱线图、热图等统计图形。
    • 兼容 NumPy 和 Pandas 数据结构,方便处理数据。
    • 支持对数据子集进行绘图和比较,适用于单变量和双变量分布的可视化[3]。
    • 内置丰富的主题和样式,可根据需求快速更改图表的外观[3]。
    • 提供高级的绘图函数,能够在单个图表中可视化多个统计趋势[3]。

二、安装与设置

  • 安装:可通过 pip install seaborn 安装 Seaborn。
  • 设置主题和样式:Seaborn 提供了 darkgridwhitegriddarkwhiteticks 等多种主题,以及 papernotebooktalkposter 等绘图上下文。
  • 调色板:Seaborn 提供了丰富的调色板,包括分类调色板、连续调色板和离散调色板等。
  • 多面板绘图工具:提供了 FacetGridPairGrid 等工具,可以轻松创建复杂的多面板图形[5]。

三、主要绘图函数

  1. 散点图:可用于显示两个变量之间的关系,支持添加趋势线和分组[1]。
  2. 折线图:可用于显示数据随时间或其他连续变量的变化趋势,支持多条折线的绘制和比较[1]。
  3. 柱状图:可用于比较不同类别数据的均值或其他统计量[1]。
  4. 箱线图:可用于展示数据的分布情况,包括中位数、四分位数、异常值等[1]。
  5. 热图:可用于可视化矩阵数据,常用于展示相关性矩阵[1]。
  6. 小提琴图:可用于展示数据分布及其概率密度[5]。
  7. 回归图:可用于拟合回归模型并显示回归结果[5]。
  8. 分布图:可用于绘制单变量或多变量的分布情况[2],包括直方图、核密度估计图等。
  9. 联合图:可用于同时展示两个变量的联合分布和各自的边际分布[2]。
  10. 重叠区间的估计:可用于绘制置信区间和其他统计估计[3]。

四、数据准备

  • 数据格式:Seaborn 通常使用 Pandas 的数据框作为输入,支持长格式和宽格式数据[5]。
  • 数据加载:数据可以通过 pd.read_csv() 等函数从文件加载,或通过手动创建。
  • 数据处理:可使用 Pandas 对数据进行清洗、转换和聚合等操作,以便更好地进行可视化。

五、统计估计

  • 回归分析:Seaborn 可以轻松绘制回归线,并显示回归模型的拟合效果[5]。
  • 分布拟合:可以对数据进行分布拟合,并绘制相应的密度曲线[5]。
  • 置信区间:支持绘制置信区间和标准误,有助于评估统计估计的可靠性[5]。

六、社区与资源

  • 官方文档:Seaborn 官方文档提供了详细的 API 介绍和图表示例[2]。
  • GitHub 仓库:Seaborn 的 GitHub 仓库包含代码仓库和问题追踪[2]。
  • Kaggle Notebooks:其他用户在 Kaggle 上分享的 Seaborn 相关 Notebook,可供参考[2]。

3.1、Plotly动态图(多组折线图 + 表格)

plotly(动态图)
plotly绘制多组折线图和表格,数据是panda.series格式索引是日期

  1. 单组:

    import pandas as pd
    import numpy as np
    import plotly.graph_objs as go
    from plotly.subplots import make_subplots# 模拟多组 Series 数据
    date_range = pd.date_range(start="2022-01-01", periods=100)
    series1 = pd.Series(np.random.randn(100).cumsum(), index=date_range, name="Model A")
    series2 = pd.Series(np.random.randn(100).cumsum(), index=date_range, name="Model B")
    series3 = pd.Series(np.random.randn(100).cumsum(), index=date_range, name="Model C")series_list = [series1, series2, series3]# 计算统计表格数据
    table_data = {"Model": [],"Max": [],"Min": [],"Mean": [],
    }
    for series in series_list:table_data["Model"].append(series.name)table_data["Max"].append(f"{series.max():.2f}")table_data["Min"].append(f"{series.min():.2f}")table_data["Mean"].append(f"{series.mean():.2f}")# 创建带表格的子图(上下布局)
    fig = make_subplots(rows=2, cols=1,row_heights=[0.7, 0.3],specs=[[{"type": "xy"}],[{"type": "table"}]]
    )# 添加折线图
    for series in series_list:fig.add_trace(go.Scatter(x=series.index,y=series.values,mode='lines',name=series.name),row=1, col=1)# 添加表格
    fig.add_trace(go.Table(header=dict(values=["Model", "Max", "Min", "Mean"],fill_color='paleturquoise',align='left'),cells=dict(values=[table_data["Model"],table_data["Max"],table_data["Min"],table_data["Mean"]],fill_color='lavender',align='left')),row=2, col=1
    )# 图表布局
    fig.update_layout(height=700,width=1000,title="多模型折线图 + 统计表格",showlegend=True
    )fig.show()
  2. 多组
    plotly绘制多组(多个折线图和表格)折线图和表格,折线图数据是panda.series格式索引是日期,表格数据是max、min、mean。

    import pandas as pd
    import numpy as np
    import plotly.graph_objs as go
    from plotly.subplots import make_subplots# 模拟数据:两个指标,每个包含三个模型
    np.random.seed(0)
    dates = pd.date_range("2022-01-01", periods=100)# 创建数据结构:dict[指标名] = list[pd.Series]
    data_dict = {"收益率": [pd.Series(np.random.randn(100).cumsum(), index=dates, name="Model A"),pd.Series(np.random.randn(100).cumsum(), index=dates, name="Model B"),pd.Series(np.random.randn(100).cumsum(), index=dates, name="Model C")],"回撤": [pd.Series(np.random.randn(100).cumsum(), index=dates, name="Model A"),pd.Series(np.random.randn(100).cumsum(), index=dates, name="Model B"),pd.Series(np.random.randn(100).cumsum(), index=dates, name="Model C")]
    }# 每个指标对应两个 subplot:折线图和表格
    n_metrics = len(data_dict)
    fig = make_subplots(rows=n_metrics * 2, cols=1,vertical_spacing=0.1,row_heights=[0.6, 0.4] * n_metrics,specs=[[{"type": "xy"}], [{"type": "table"}]] * n_metrics,subplot_titles=[f"{metric} 折线图" if i % 2 == 0 else f"{metric} 统计表格" for metric in data_dict for i in range(2)]
    )row_idx = 1
    for metric_name, series_list in data_dict.items():# 折线图for series in series_list:fig.add_trace(go.Scatter(x=series.index,y=series.values,mode='lines',name=f"{metric_name} - {series.name}"),row=row_idx, col=1)# 表格统计数据models = [s.name for s in series_list]max_vals = [f"{s.max():.2f}" for s in series_list]min_vals = [f"{s.min():.2f}" for s in series_list]mean_vals = [f"{s.mean():.2f}" for s in series_list]fig.add_trace(go.Table(header=dict(values=["模型", "最大值", "最小值", "平均值"],fill_color="lightblue", align="left"),cells=dict(values=[models, max_vals, min_vals, mean_vals],fill_color="lavender", align="left")),row=row_idx + 1, col=1)row_idx += 2# 图表设置
    fig.update_layout(height=400 * n_metrics,width=1000,title_text="多组折线图与统计表格展示",showlegend=True
    )fig.show()
  3. 数据格式

    plotly绘制多组(多个折线图和表格)折线图和表格,折线图数据是panda.series格式索引是日期,表格数据是max、min、mean。data_dict存储多组折线图和表格数据:
    data_dict = {"收益率": {"series": [...],"table": [{"model": "Model A", "max": ..., "min": ..., "mean": ...},...]},...
    }
    
    data_dict = {"收益率": {"series": [SeriesA, SeriesB, ...],  # 每个是 pandas.Series,索引为日期"table": [{"model": "Model A", "max": ..., "min": ..., "mean": ...},...]},"回撤": {...}
    }
    # 模拟数据:两个指标,每个包含三个模型
    np.random.seed(0)
    dates = pd.date_range("2022-01-01", periods=100)# 创建数据结构:dict[指标名] = list[pd.Series]
    data_dict = {"收益率": [pd.Series(np.random.randn(100).cumsum(), index=dates, name="Model A"),pd.Series(np.random.randn(100).cumsum(), index=dates, name="Model B"),pd.Series(np.random.randn(100).cumsum(), index=dates, name="Model C")],"回撤": [pd.Series(np.random.randn(100).cumsum(), index=dates, name="Model A"),pd.Series(np.random.randn(100).cumsum(), index=dates, name="Model B"),pd.Series(np.random.randn(100).cumsum(), index=dates, name="Model C")]
    }# 计算统计表格数据
    table_data = {"Model": [],"Max": [],"Min": [],"Mean": [],
    }
    
    import pandas as pd
    import numpy as np
    import plotly.graph_objs as go
    from plotly.subplots import make_subplots# 日期索引
    dates = pd.date_range("2023-01-01", periods=50)# 随机构造 Series
    def fake_series(name):return pd.Series(np.random.randn(50).cumsum(), index=dates, name=name)# 构造带假统计数据的 data_dict
    data_dict = {"收益率": {"series": [fake_series("Model A"), fake_series("Model B"), fake_series("Model C")],"table": [{"model": "Model A", "max": 12.3, "min": -5.6, "mean": 3.2},{"model": "Model B", "max": 10.1, "min": -3.2, "mean": 2.8},{"model": "Model C", "max": 9.7, "min": -4.1, "mean": 1.5},]},"回撤": {"series": [fake_series("Model A"), fake_series("Model B"), fake_series("Model C")],"table": [{"model": "Model A", "max": -1.2, "min": -8.6, "mean": -4.5},{"model": "Model B", "max": -0.5, "min": -7.9, "mean": -3.9},{"model": "Model C", "max": -2.0, "min": -6.3, "mean": -4.1},]},"夏普比": {"series": [fake_series("Model A"), fake_series("Model B"), fake_series("Model C")],"table": [{"model": "Model A", "max": 2.1, "min": 0.3, "mean": 1.1},{"model": "Model B", "max": 1.9, "min": 0.5, "mean": 1.0},{"model": "Model C", "max": 2.3, "min": 0.6, "mean": 1.2},]}
    }# 总共有多少组指标
    n_metrics = len(data_dict)# 创建子图:每组两行(折线图 + 表格)
    fig = make_subplots(rows=n_metrics * 2, cols=1,row_heights=[0.6, 0.4] * n_metrics,vertical_spacing=0.1,specs=[[{"type": "xy"}], [{"type": "table"}]] * n_metrics,subplot_titles=[f"{metric} 折线图" if i % 2 == 0 else f"{metric} 表格"for metric in data_dict for i in range(2)]
    )row = 1
    for metric_name, metric_data in data_dict.items():# 折线图部分for s in metric_data["series"]:fig.add_trace(go.Scatter(x=s.index,y=s.values,mode="lines",name=f"{metric_name} - {s.name}"),row=row, col=1)# 表格部分table = metric_data["table"]fig.add_trace(go.Table(header=dict(values=["模型", "最大值", "最小值", "平均值"],fill_color="lightblue",align="left"),cells=dict(values=[[item["model"] for item in table],[item["max"] for item in table],[item["min"] for item in table],[item["mean"] for item in table],],fill_color="lavender",align="left")),row=row + 1, col=1)row += 2# 布局调整
    fig.update_layout(height=400 * n_metrics,width=1000,title="多组指标:折线图 + 表格(含假数据)",showlegend=True
    )fig.show()	

3.2、Plotly示例

下面是一个完整可运行的示例,包含:

✅ 使用 假数据 构建的 data_dict(不做计算,直接写死 max/min/mean);
✅ 使用 Plotly 绘制 多组折线图 + 表格,每组上下排列展示。


plotly绘制多组(多个折线图和表格)折线图和表格,折线图数据是panda.series格式索引是日期,
表格数据是max、min、mean。data_dict存储多组折线图和表格数据(弄一些假数据即可不需要计算):
data_dict = {"收益率": {"series": [...],"table": [{"model": "Model A", "max": ..., "min": ..., "mean": ...},...]},...
}

✅ 1. data_dict 假数据构造(含 series 和 table)

import pandas as pd
import numpy as np
import plotly.graph_objs as go
from plotly.subplots import make_subplots# 日期索引
dates = pd.date_range("2023-01-01", periods=50)# 随机构造 Series
def fake_series(name):return pd.Series(np.random.randn(50).cumsum(), index=dates, name=name)# 构造带假统计数据的 data_dict
data_dict = {"收益率": {"series": [fake_series("Model A"), fake_series("Model B"), fake_series("Model C")],"table": [{"model": "Model A", "max": 12.3, "min": -5.6, "mean": 3.2},{"model": "Model B", "max": 10.1, "min": -3.2, "mean": 2.8},{"model": "Model C", "max": 9.7, "min": -4.1, "mean": 1.5},]},"回撤": {"series": [fake_series("Model A"), fake_series("Model B"), fake_series("Model C")],"table": [{"model": "Model A", "max": -1.2, "min": -8.6, "mean": -4.5},{"model": "Model B", "max": -0.5, "min": -7.9, "mean": -3.9},{"model": "Model C", "max": -2.0, "min": -6.3, "mean": -4.1},]},"夏普比": {"series": [fake_series("Model A"), fake_series("Model B"), fake_series("Model C")],"table": [{"model": "Model A", "max": 2.1, "min": 0.3, "mean": 1.1},{"model": "Model B", "max": 1.9, "min": 0.5, "mean": 1.0},{"model": "Model C", "max": 2.3, "min": 0.6, "mean": 1.2},]}
}

✅ 2. 使用 Plotly 绘制折线图 + 表格(每组上下两行)

# 总共有多少组指标
n_metrics = len(data_dict)# 创建子图:每组两行(折线图 + 表格)
fig = make_subplots(rows=n_metrics * 2, cols=1,row_heights=[0.6, 0.4] * n_metrics,vertical_spacing=0.1,specs=[[{"type": "xy"}], [{"type": "table"}]] * n_metrics,subplot_titles=[f"{metric} 折线图" if i % 2 == 0 else f"{metric} 表格"for metric in data_dict for i in range(2)]
)row = 1
for metric_name, metric_data in data_dict.items():# 折线图部分for s in metric_data["series"]:fig.add_trace(go.Scatter(x=s.index,y=s.values,mode="lines",name=f"{metric_name} - {s.name}"),row=row, col=1)# 表格部分table = metric_data["table"]fig.add_trace(go.Table(header=dict(values=["模型", "最大值", "最小值", "平均值"],fill_color="lightblue",align="left"),cells=dict(values=[[item["model"] for item in table],[item["max"] for item in table],[item["min"] for item in table],[item["mean"] for item in table],],fill_color="lavender",align="left")),row=row + 1, col=1)row += 2# 布局调整
fig.update_layout(height=400 * n_metrics,width=1000,title="多组指标:折线图 + 表格(含假数据)",showlegend=True
)fig.show()

✅ 图表展示效果

  • 每组(如“收益率”、“回撤”、“夏普比”):

    • 上方是多条模型折线图;
    • 下方是手动填入的 max/min/mean 表格;
  • 使用 Plotly 的交互功能(缩放、图例隐藏、悬浮提示等);


✅ 后续可选拓展:

功能说明
自动计算 max/min/mean可直接用 Series.max() 等代替表格数据手填
横向子图排列使用 cols=2 配置,分列展示各指标
导出图像或 HTMLfig.write_html("result.html")fig.write_image()
响应式宽度/滑动缩放可嵌入网页或交互报告中

如需将其封装为函数、支持滑块、分页、搜索过滤等功能,也可以告诉我,我可以继续帮你增强。

3.3 Plotly(完整示例)

时间日期保存,后面的dash完整示例可按照时间显示

import numpy as np
import pandas as pd# 1.系列指标
def calculate_metrics_series(history_dict, risk_free_rate=0.0):"""# 折线图数据格式{"日收益率": {"series": [pd.Series1, pd.Series2, ...],  # 列表,而非dict"table": [{"model": "PPO1_MLP", ...},{"model": "PPO2_Lstm", ...}]},...}# 汇总表格数据(同上面的)[{'model': 'PPO1_MLP', 'max': 0.0099, 'min': 0.0, 'mean': 0.0089}{'model': 'PPO2_Lstm', 'max': 0.0099, 'min': 0.0, 'mean': 0.0089}]"""data_dict = {}for policy_name, history in history_dict.items():# net_worth = np.array(history['total_net_worth'])# profit = np.array(history['total_profit'])# rewards = np.array(history['total_reward'])# # dates = pd.date_range(end=history['current_data'], periods=len(net_worth))dates = pd.to_datetime(history['current_data'])# index = pd.to_datetime(historypre['current_data'])# 1. 计算每日收益率# 计算每日收益率,系列,# np.diff(net_worth):计算净值序列的相邻差值,net_worth[:-1]:取前n-1日的净值作为分母# 该计算方式与pandas的pct_change()方法等价:# daily_returns = np.diff(net_worth) / net_worth[:-1]# 假设 history["total_net_worth"] 已经有数据net_worth_series = pd.Series(history['total_net_worth'],dates)# 计算每日收益率daily_returns = net_worth_series.pct_change().fillna(0)  # 第一天收益率设为0# 2. 波动率# Step 2: 逐步计算从 day 1 到 t 的累积标准差#daily_returns.expanding():创建扩展窗口对象,第1个窗口:[0],第2个窗口:[0,1],第3个窗口:[0,1,2]daily_volatility = daily_returns.expanding().std().fillna(0)# # 计算从开始时间到当前步的波动率,和上面等价# volatility = [np.std(daily_returns[:i+1]) for i in range(len(daily_returns))]# 计算 5 日滚动标准差作为波动率# daily_volatility = daily_returns.rolling(window=5).std().fillna(0)# 添加到每个指标的 data_dictdef add_to_data_dict(name, series_data,policy_name, table_value=None):if name not in data_dict:data_dict[name] = {"series": [], "table": []}data_dict[name]["series"].append(pd.Series(series_data, index=dates, name=policy_name))data_dict[name]["table"].append({"model": policy_name, "max": np.max(series_data),"min": np.min(series_data), "mean": np.mean(series_data) if hasattr(series_data, "__len__") else series_data})# 原始数据# 构造 pandas.Series,索引为日期 # x = pd.to_datetime(historypre['current_data'])series_net = pd.Series(history['total_net_worth'], index=dates, name=policy_name)series_profit = pd.Series(history['total_profit'], index=dates, name=policy_name)series_reward = pd.Series(history['total_reward'], index=dates, name=policy_name)for metric_name, series in zip(["total_net_worth", "total_profit", "total_reward"],[series_net, series_profit, series_reward]):add_to_data_dict(metric_name, series, policy_name,table_value=None)# 计算的指标数据add_to_data_dict("日收益率", daily_returns,policy_name, table_value=None)add_to_data_dict("日波动率",daily_volatility,policy_name, table_value=None)return data_dict# 2.值指标,多个def calculate_metrics_values(history_dict, risk_free_rate=0.0):# data_dict = {}data_list = []for policy_name, history in history_dict.items():net_worth = np.array(history['total_net_worth'])profit = np.array(history['total_profit'])rewards = np.array(history['total_reward'])# dates = pd.date_range(end=history['current_data'], periods=len(net_worth))dates = pd.to_datetime(history['current_data'])# 收益率returns = net_worth / net_worth[0] - 1total_return = returns[-1]# 日收益率daily_returns = pd.Series(net_worth).pct_change().dropna()# 波动率(年化)volatility = daily_returns.std() * np.sqrt(252)# 夏普比率sharpe_ratio = (daily_returns.mean() - risk_free_rate) / daily_returns.std() * np.sqrt(252) if daily_returns.std() != 0 else 0# 最大回撤cumulative = pd.Series(net_worth)roll_max = cumulative.cummax()drawdown = (cumulative - roll_max) / roll_maxmax_drawdown = drawdown.min()# 卡玛比率calmar_ratio = total_return / abs(max_drawdown) if max_drawdown != 0 else np.inf# 胜率 & 盈亏比profit_diff = np.diff(profit)win_trades = profit_diff[profit_diff > 0]loss_trades = profit_diff[profit_diff < 0]win_rate = len(win_trades) / len(profit_diff) if len(profit_diff) > 0 else 0avg_win = win_trades.mean() if len(win_trades) > 0 else 0avg_loss = abs(loss_trades.mean()) if len(loss_trades) > 0 else 1e-8profit_factor = avg_win / avg_loss if avg_loss != 0 else np.inf# 存入列表,每个元素是一个字典data_list.append({"model": policy_name,"收益率": total_return,"波动率": volatility,"夏普比率": sharpe_ratio,"累计净值": net_worth[-1],"最大回撤": max_drawdown,"卡玛比率": calmar_ratio,"胜率": win_rate,"盈亏比": profit_factor,})return data_list#     # 存结果#     data_dict[policy_name] = {#         "收益率": total_return,#         "波动率": volatility,#         "夏普比率": sharpe_ratio,#         "累计净值": net_worth[-1],#         "最大回撤": max_drawdown,#         "卡玛比率": calmar_ratio,#         "胜率": win_rate,#         "盈亏比": profit_factor,#     }# return data_dictif __name__ == "__main__":import numpy as npimport pandas as pdfrom datetime import datetime, timedelta# 随机种子np.random.seed(42)n_steps = 100initial_net_worth = 10000price_changes = np.random.normal(loc=0.001, scale=0.01, size=n_steps)# 系列数据:净值、利润、奖励net_worth_series = initial_net_worth * np.cumprod(1 + price_changes)  # ✅ 系列profit_series = net_worth_series - initial_net_worth                  # ✅ 系列reward_series = price_changes * 100                                   # ✅ 系列history = {'total_net_worth': net_worth_series.tolist(),'total_profit': profit_series.tolist(),'total_reward': reward_series.tolist(),'current_data': pd.date_range('2023-01-01', periods=len(net_worth_series), freq='D').strftime('%Y-%m-%d').tolist()  # 存储格式化后的日期列表}# print(history)print("\n历史数据最后5条记录:")for key in history:print(f"{key}:")# 对数值数据保留4位小数,日期保持原格式values = [round(x, 4) if isinstance(x, (float, np.floating)) else x for x in history[key][-5:]]  # 可修改-5为其他数值调整显示数量print(values)# #### ************ceshi************# 1、计算指标,系列data_dict1 = calculate_metrics_series({'PPO1_MLP': history, 'PPO2_Lstm':history})print("\n方法一:评估指标:")# 新增打印代码print("各指标前3条数据预览:")for metric_name in data_dict1:print(f"\n=== {metric_name} ===")# 打印时序数据前3条print(f"时序数据({len(data_dict1[metric_name]['series'])}个模型):")for series in data_dict1[metric_name]['series']:print(f"{series.name}: {[round(x,4) for x in series.values[:3]]}...")# 打印表格数据前3条print(f"\n表格统计(前3项):")for table_item in data_dict1[metric_name]['table'][:3]:print({k: round(v,4) if isinstance(v, float) else v for k, v in table_item.items()})# 2、计算评估指标值,多值data_dict2 = calculate_metrics_values({'PPO1_MLP': history, 'PPO2_Lstm':history})print("\n方法二:评估指标:")print(data_dict2)# 3、绘图history_dict={'PPO1_MLP': history,'PPO2_Lstm': history,}# data_dict=metrics(history_dict)data_dict = calculate_metrics_series(history_dict)  # 生成时序和指标表格summary_values = calculate_metrics_values(history_dict)  # 生成整体指标汇总import pandas as pdimport numpy as npimport plotly.graph_objs as gofrom plotly.subplots import make_subplots# 总共有多少组指标,(折线图 + 表格)+(表格)n_metrics = len(data_dict) n_table = 1 #len(summary_values)# 创建子图:每组两行(折线图 + 表格),+(表格)fig = make_subplots(rows=n_metrics * 2+n_table, cols=1,row_heights=[0.6, 0.4] * n_metrics+[0.5]*n_table,# vertical_spacing=0.1,vertical_spacing=0.02,  # 间距改小specs=[[{"type": "xy"}], [{"type": "table"}]] * n_metrics+ [[{"type": "table"}]] * n_table,subplot_titles=[f"{metric} 折线图" if i % 2 == 0 else f"{metric} 表格"for metric in data_dict for i in range(2)]+[f"汇总指标表格{i}" for i in range(n_table)],)row = 1for metric_name, metric_data in data_dict.items():# 折线图部分for s in metric_data["series"]:fig.add_trace(go.Scatter(x=s.index,y=s.values,mode="lines",name=f"{metric_name} - {s.name}"),row=row, col=1)# 表格部分table = metric_data["table"]if not table:print(f"[警告] 表格为空: {metric_name}")# else:#     print(f"[信息] 表格不为空: {table}")#     for item in table:#         print(f"[信息] 表格内容: {item}")fig.add_trace(go.Table(header=dict(values=["模型", "最大值", "最小值", "平均值"],fill_color="lightblue",align="left"),cells=dict(# values=[#     [item["model"] for item in table],#     [item["max"] for item in table],#     [item["min"] for item in table],#     [item["mean"] for item in table],# ],values=[[item["model"] for item in table],[float(item["max"]) for item in table],[float(item["min"]) for item in table],[float(item["mean"]) for item in table],],fill_color="lavender",align="left")),row=row + 1, col=1)row += 2#  # 表格# table_df = pd.DataFrame(metric_data["table"])# fig.add_trace(#     go.Table(#         header=dict(values=list(table_df.columns), fill_color="lightgrey", align="left"),#         cells=dict(values=[table_df[col] for col in table_df.columns], fill_color="white", align="left")#     ),#     row=row + 1, col=1# )# row += 2# 汇总表格部分summary_df = pd.DataFrame(summary_values)fig.add_trace(go.Table(header=dict(values=list(summary_df.columns),fill_color="lightblue",align="left"),cells=dict(values=[summary_df[col] for col in summary_df.columns],fill_color="lavender",align="left")),row=row, col=1)# 布局调整fig.update_layout(height=600 * n_metrics,width=1000,title="多组指标:折线图 + 表格(含假数据)",showlegend=True,# margin=dict(t=40),  # 顶部边距,避免标题太靠上margin=dict(t=40, b=40, l=40, r=40),  # 上下左右边距autosize=True,  # 自动调整大小)# 显示图表# fig.show()#保存到本地import os# 保存图表# 创建目录os.makedirs("charts", exist_ok=True)# 当前时间now = datetime.now()now_str = now.strftime("%Y-%m-%d %H:%M:%S")filename_str = now.strftime("%Y-%m-%d_%H-%M-%S")filepath = f"charts/linechart_{filename_str}.html"fig.write_html(filepath)print(f"✅ 图表保存:{filepath}")

5.1、Dash分页显示多个plotly

Dash 本身不直接支持 Plotly 图表的“分页”显示,但我们可以通过在 Dash 回调中动态更新图表数据,结合分页控件(如 dcc.Sliderdcc.Dropdown 或自定义页码按钮),来实现“分页”的效果。

下面是一个使用 dcc.Slider 实现分页显示 Plotly 图表的示例(假设每页显示一组线图):


✅ 示例:分页显示 Plotly 折线图(Dash 实现)

import dash
from dash import dcc, html, Input, Output
import plotly.graph_objs as go
import pandas as pd
import numpy as np# 模拟数据:5 页,每页 1 组折线图
pages = 5
x = np.arange(0, 10)
data_dict = {f'Line {i+1}': np.sin(x + i)for i in range(pages)
}# Dash app 初始化
app = dash.Dash(__name__)app.layout = html.Div([html.H3("分页显示 Plotly 折线图"),dcc.Graph(id='line-plot'),html.Div([html.Label("选择页码:"),dcc.Slider(id='page-slider',min=1,max=pages,step=1,value=1,marks={i: str(i) for i in range(1, pages+1)},tooltip={"placement": "bottom", "always_visible": True})], style={"margin": "40px"})
])# 回调:根据页码更新图表
@app.callback(Output('line-plot', 'figure'),Input('page-slider', 'value')
)
def update_plot(page):line_name = f'Line {page}'y = data_dict[line_name]fig = go.Figure()fig.add_trace(go.Scatter(x=x, y=y, mode='lines+markers', name=line_name))fig.update_layout(title=f"第 {page} 页 - {line_name}")return figif __name__ == '__main__':app.run_server(debug=True)

🔄 其他分页方式(替代 dcc.Slider):

  • 使用 dcc.Dropdown:适合页数多时选择更方便
  • 使用 html.Button("上一页") + html.Button("下一页") 自定义页码控制逻辑(需加状态变量)

如果你希望分页显示多个图表或表格组合,或者希望“每页显示多个子图”,我也可以给出更高级版本。你可以告诉我你具体的数据结构和需求样式(比如:每页显示多少组、是否带表格等),我会按需修改示例。

5.2、Dash分页显示多个plotly(已保存的)

Dash分页显示已保存在本地的多个plotpy

如果你有多个已经保存在本地的 Plotly 图(.html 文件或 .json 文件),你可以使用 Dash 实现分页浏览这些图像。

我们分两种情况来讲解:


✅ 情况一:保存为多个 .html 文件

例如:

saved_figs/├── fig_1.html├── fig_2.html├── fig_3.html└── ...

你可以用 Dash 的 html.Iframe 来分页嵌入这些图:

示例代码:

import dash
from dash import html, dcc, Input, Output
import os# 图像目录
fig_dir = "saved_figs"
fig_files = sorted([f for f in os.listdir(fig_dir) if f.endswith('.html')])
num_pages = len(fig_files)# 构建完整路径列表
fig_paths = [os.path.join(fig_dir, fname) for fname in fig_files]app = dash.Dash(__name__)app.layout = html.Div([html.H3("分页显示本地保存的 Plotly 图"),html.Iframe(id='plot-frame',srcDoc=open(fig_paths[0], 'r', encoding='utf-8').read(),width='100%',height='600'),dcc.Slider(id='page-slider',min=1,max=num_pages,step=1,value=1,marks={i: str(i) for i in range(1, num_pages + 1)},tooltip={"placement": "bottom", "always_visible": True},style={'marginTop': '40px'})
])@app.callback(Output('plot-frame', 'srcDoc'),Input('page-slider', 'value')
)
def update_page(page):filepath = fig_paths[page - 1]with open(filepath, 'r', encoding='utf-8') as f:return f.read()if __name__ == '__main__':app.run_server(debug=True)

✅ 情况二:保存为 .json 的 Plotly 图对象(使用 plotly.io.write_json

例如你保存的是:

import plotly.io as pio
pio.write_json(fig, "saved_figs/fig_1.json")

可以在 Dash 中读取 .json 文件并直接构造图:

示例代码(JSON方式):

import dash
from dash import dcc, html, Input, Output
import plotly.io as pio
import osfig_dir = "saved_figs"
fig_files = sorted([f for f in os.listdir(fig_dir) if f.endswith('.json')])
fig_paths = [os.path.join(fig_dir, f) for f in fig_files]app = dash.Dash(__name__)app.layout = html.Div([html.H3("分页显示本地 Plotly JSON 图"),dcc.Graph(id='plot'),dcc.Slider(id='page-slider',min=1,max=len(fig_paths),step=1,value=1,marks={i: str(i) for i in range(1, len(fig_paths)+1)},tooltip={"placement": "bottom", "always_visible": True})
])@app.callback(Output('plot', 'figure'),Input('page-slider', 'value')
)
def update_plot(page):fig = pio.read_json(fig_paths[page - 1])return figif __name__ == '__main__':app.run_server(debug=True)

✅ 总结

图保存格式分页显示方法
.htmlhtml.Iframe + srcDoc
.jsondcc.Graph + plotly.io.read_json

你可以告诉我你当前保存的文件类型,我可以进一步精简对应版本(甚至支持每页多个图分页)。需要我帮你做自动扫描和分页布局也可以提!

5.4 以日期时间分页展示

下面是完整实现方案,支持以下功能:


✅ 目标功能

  1. 使用 Plotly 绘制简单折线图,标题为当前日期时间;

  2. 按当前时间命名并保存为 .html 文件;

  3. 使用 Dash 实现图表分页浏览:

    • 显示当前图表;
    • 支持 上一页 / 下一页
    • 支持 滑动页码选择(下拉列表或滑块);
    • 显示当前页 / 总页数;
    • 图表标题为保存时间。

🧩 第一步:保存图表 save_plot.py

# save_plot.py
import plotly.graph_objects as go
from datetime import datetime
import osos.makedirs("charts", exist_ok=True)def save_line_chart():now = datetime.now()title_str = now.strftime("%Y-%m-%d %H:%M:%S")filename_str = now.strftime("%Y-%m-%d_%H-%M-%S")x = ["2025-07-17", "2025-07-18", "2025-07-19"]y = [10, 12, 9]fig = go.Figure()fig.add_trace(go.Scatter(x=x, y=y, mode="lines+markers", name="示例数据"))fig.update_layout(title=f"{title_str} 折线图")filepath = f"charts/linechart_{filename_str}.html"fig.write_html(filepath)print(f"✅ 图表已保存:{filepath}")# 可多次运行,生成多个图表
save_line_chart()

🧩 第二步:分页查看图表 view_plot.py

# view_plot.py
import os
from dash import Dash, dcc, html, Input, Output, State
import dash
import dash_bootstrap_components as dbcCHART_DIR = "charts"def get_sorted_chart_files():files = [f for f in os.listdir(CHART_DIR) if f.endswith(".html")]return sorted(files, key=lambda f: os.path.getmtime(os.path.join(CHART_DIR, f)))app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
app.title = "📊 图表分页浏览器"app.layout = html.Div([html.H3("📈 Plotly 图表分页浏览", style={"marginBottom": "20px"}),html.Div(id="chart-title", style={"fontSize": "18px", "marginBottom": "10px"}),html.Iframe(id="chart-frame", width="100%", height="600px", style={"border": "1px solid #ccc"}),html.Div([dbc.Button("⬅ 上一页", id="prev", color="primary"),dbc.Button("下一页 ➡", id="next", color="primary"),html.Span(id="page-indicator", style={"margin": "0 20px", "fontSize": "16px"}),dcc.Dropdown(id="page-selector", style={"width": "300px"}, clearable=False),], style={"marginTop": "20px", "display": "flex", "alignItems": "center", "gap": "10px"}),dcc.Store(id="chart-index", data=0)
], style={"padding": "20px"})@app.callback(Output("chart-frame", "srcDoc"),Output("chart-index", "data"),Output("chart-title", "children"),Output("page-indicator", "children"),Output("page-selector", "options"),Output("page-selector", "value"),Input("prev", "n_clicks"),Input("next", "n_clicks"),Input("page-selector", "value"),State("chart-index", "data"),prevent_initial_call=True
)
def update_chart(prev_clicks, next_clicks, selected_page, current_index):charts = get_sorted_chart_files()total = len(charts)if total == 0:return "<h3>📭 没有图表</h3>", 0, "无图表", "第 0 页 / 共 0 页", [], Nonectx = dash.callback_contexttriggered_id = ctx.triggered[0]["prop_id"].split(".")[0] if ctx.triggered else Noneif triggered_id == "next" and current_index < total - 1:current_index += 1elif triggered_id == "prev" and current_index > 0:current_index -= 1elif triggered_id == "page-selector" and selected_page is not None:current_index = selected_pagefile = charts[current_index]with open(os.path.join(CHART_DIR, file), "r", encoding="utf-8") as f:content = f.read()title = file.replace("linechart_", "").replace(".html", "").replace("_", " ")page_text = f"第 {current_index + 1} 页 / 共 {total} 页"dropdown_options = [{"label": f"{i + 1} - {charts[i].replace('linechart_', '').replace('.html', '').replace('_', ' ')}", "value": i}for i in range(total)]return content, current_index, f"图表时间:{title}", page_text, dropdown_options, current_index# 初始填充
@app.callback(Output("chart-frame", "srcDoc", allow_duplicate=True),Output("chart-title", "children", allow_duplicate=True),Output("page-indicator", "children", allow_duplicate=True),Output("page-selector", "options", allow_duplicate=True),Output("page-selector", "value", allow_duplicate=True),Input("chart-index", "data"),prevent_initial_call="initial_duplicate"
)
def initialize_chart(index):charts = get_sorted_chart_files()if not charts:return "<h3>📭 没有图表</h3>", "无图表", "第 0 页 / 共 0 页", [], Nonefile = charts[index]with open(os.path.join(CHART_DIR, file), "r", encoding="utf-8") as f:content = f.read()title = file.replace("linechart_", "").replace(".html", "").replace("_", " ")total = len(charts)page_text = f"第 {index + 1} 页 / 共 {total} 页"dropdown_options = [{"label": f"{i + 1} - {charts[i].replace('linechart_', '').replace('.html', '').replace('_', ' ')}", "value": i}for i in range(total)]return content, f"图表时间:{title}", page_text, dropdown_options, indexif __name__ == "__main__":app.run_server(debug=True)
if __name__ == "__main__":# app.run_server(debug=True)app.run(debug=True)

✅ 使用说明

  1. 运行 save_plot.py 多次,生成多个图表文件;

  2. 运行 view_plot.py 启动 Dash 网页浏览;

  3. 打开浏览器访问 http://127.0.0.1:8050

  4. 页面上可以:

    • 上一页 / 下一页按钮切换;
    • 下拉选择任意页;
    • 实时显示当前页码和图表时间。

是否还需要添加:

  • 删除图表按钮?
  • 上传新图表功能?
  • 图表内容关键词搜索功能?

我可以继续为你扩展页面逻辑。

5.3、Dash(完整示例)

plotpy简单折线图(日期时间为标题)以日期时间保存图表,以日期时间分页显示已保存在本地的多个plotpy图表,显示具体的页和上下页及页的滑动列表。
✅ 目标功能

  1. 使用 Plotly 绘制简单折线图,标题为当前日期时间;

  2. 按当前时间命名并保存为 .html 文件;

  3. 使用 Dash 实现图表分页浏览:

    • 显示当前图表;
    • 支持 上一页 / 下一页
    • 支持 滑动页码选择(下拉列表或滑块);
    • 显示当前页 / 总页数;
    • 图表标题为保存时间。

# save_plot.py
import plotly.graph_objects as go
from datetime import datetime
import os# 创建目录
os.makedirs("charts", exist_ok=True)# 当前时间
now = datetime.now()
now_str = now.strftime("%Y-%m-%d %H:%M:%S")
filename_str = now.strftime("%Y-%m-%d_%H-%M-%S")# 示例数据
x = ["2025-07-17", "2025-07-18", "2025-07-19"]
y = [10, 12, 9]# 创建图表
fig = go.Figure()
fig.add_trace(go.Scatter(x=x, y=y, mode="lines+markers", name="示例数据"))
fig.update_layout(title=f"折线图 {now_str}")# 保存图表
filepath = f"charts/linechart_{filename_str}.html"
fig.write_html(filepath)
print(f"✅ 图表保存:{filepath}")
# view_plot.py
import os
from dash import Dash, dcc, html, Input, Output, State
import dash
import dash_bootstrap_components as dbcCHART_DIR = "charts"def get_sorted_chart_files():files = [f for f in os.listdir(CHART_DIR) if f.endswith(".html")]return sorted(files, key=lambda f: os.path.getmtime(os.path.join(CHART_DIR, f)))app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
app.title = "📊 图表分页浏览器"app.layout = html.Div([html.H3("📈 Plotly 图表分页浏览", style={"marginBottom": "20px"}),html.Div(id="chart-title", style={"fontSize": "18px", "marginBottom": "10px"}),html.Iframe(id="chart-frame", width="100%", height="600px", style={"border": "1px solid #ccc"}),html.Div([dbc.Button("⬅ 上一页", id="prev", color="primary"),dbc.Button("下一页 ➡", id="next", color="primary"),html.Span(id="page-indicator", style={"margin": "0 20px", "fontSize": "16px"}),dcc.Dropdown(id="page-selector", style={"width": "300px"}, clearable=False),], style={"marginTop": "20px", "display": "flex", "alignItems": "center", "gap": "10px"}),dcc.Store(id="chart-index", data=0)
], style={"padding": "20px"})@app.callback(Output("chart-frame", "srcDoc"),Output("chart-index", "data"),Output("chart-title", "children"),Output("page-indicator", "children"),Output("page-selector", "options"),Output("page-selector", "value"),Input("prev", "n_clicks"),Input("next", "n_clicks"),Input("page-selector", "value"),State("chart-index", "data"),prevent_initial_call=True
)
def update_chart(prev_clicks, next_clicks, selected_page, current_index):charts = get_sorted_chart_files()total = len(charts)if total == 0:return "<h3>📭 没有图表</h3>", 0, "无图表", "第 0 页 / 共 0 页", [], Nonectx = dash.callback_contexttriggered_id = ctx.triggered[0]["prop_id"].split(".")[0] if ctx.triggered else Noneif triggered_id == "next" and current_index < total - 1:current_index += 1elif triggered_id == "prev" and current_index > 0:current_index -= 1elif triggered_id == "page-selector" and selected_page is not None:current_index = selected_pagefile = charts[current_index]with open(os.path.join(CHART_DIR, file), "r", encoding="utf-8") as f:content = f.read()title = file.replace("linechart_", "").replace(".html", "").replace("_", " ")page_text = f"第 {current_index + 1} 页 / 共 {total} 页"dropdown_options = [{"label": f"{i + 1} - {charts[i].replace('linechart_', '').replace('.html', '').replace('_', ' ')}", "value": i}for i in range(total)]return content, current_index, f"图表时间:{title}", page_text, dropdown_options, current_index# 初始填充
@app.callback(Output("chart-frame", "srcDoc", allow_duplicate=True),Output("chart-title", "children", allow_duplicate=True),Output("page-indicator", "children", allow_duplicate=True),Output("page-selector", "options", allow_duplicate=True),Output("page-selector", "value", allow_duplicate=True),Input("chart-index", "data"),prevent_initial_call="initial_duplicate"
)
def initialize_chart(index):charts = get_sorted_chart_files()if not charts:return "<h3>📭 没有图表</h3>", "无图表", "第 0 页 / 共 0 页", [], Nonefile = charts[index]with open(os.path.join(CHART_DIR, file), "r", encoding="utf-8") as f:content = f.read()title = file.replace("linechart_", "").replace(".html", "").replace("_", " ")total = len(charts)page_text = f"第 {index + 1} 页 / 共 {total} 页"dropdown_options = [{"label": f"{i + 1} - {charts[i].replace('linechart_', '').replace('.html', '').replace('_', ' ')}", "value": i}for i in range(total)]return content, f"图表时间:{title}", page_text, dropdown_options, indexif __name__ == "__main__":# app.run_server(debug=True)app.run(debug=True)
http://www.lryc.cn/news/593371.html

相关文章:

  • Typecho博客评论无限滚动加载实现指南
  • windows wsl ubuntu 如何安装 maven
  • 算法题(175):小明的游戏
  • Github Actions Workflows 上传 Dropbox
  • Visual Studio Code(VSCode)中设置中文界面
  • 11.1Redis高可用集群部署
  • Elastic Search 8.x 分片和常见性能优化
  • PHP 就业核心技能速查手册
  • windows docker-01-desktop install windows10 + wls2 启用
  • LangGraph教程6:LangGraph工作流人机交互
  • 博图SCL语言中常用运算符使用详解及实战案例(下)
  • LangGraph教程10:LangGraph ReAct应用
  • Python Pandas读取Excel表格中数据并根据时间字段筛选数据
  • 月舟科技近调记录
  • 网络爬虫概念初解
  • ndexedDB 与 LocalStorage:全面对比分析
  • C++数据结构————集合
  • 【Keil5-map文件】
  • 阿里云服务器 CentOS 7 安装 MySQL 8.4 超详细指南
  • c#泛型集合(ArrayList和List、Dictionary的对比)
  • 每日面试题09:进程、线程、协程的区别
  • 48Days-Day03 | 删除公共字符,两个链表的第一个公共结点,mari和shiny
  • 【每日算法】专题十五_BFS 解决 FloodFill 算法
  • HD Video Converter Factory pro 高清视频转换器 v27.7.0 绿色中文便携版
  • 【2025最新】 .NET FrameWork微软离线运行库合集,一键安装版
  • Spring之【AnnotatedBeanDefinitionReader】
  • 前端面试专栏-工程化:28.团队协作与版本控制(Git)
  • Baumer工业相机堡盟工业相机如何通过YoloV8深度学习模型实现动物分类(C#源码,UI界面版)
  • Selenium 中 findElement 方法全解析:定位网页元素的 7 种方式
  • RPC(Remote Procedure Call,远程过程调用)介绍