Skill: earl-chen/python-matplotlib-chinese-font (version 1.0.0)

# python-matplotlib-chinese-font **Python matplotlib 中文字体配置解决方案** --- ## 📌 问题描述 **matplotlib 默认不支持中文，中文会显示为方块/方框** 控制台警告： ``` UserWarning: Glyph XXXXX missing from current font ``` **根本原因**：matplotlib 的默认字体不包含中文字符。 --- ## 🎯 使用场景 **当以下情况时使用此 Skill**： 1. ✅ matplotlib 绑图中中文显示为方框/乱码 2. ✅ 需要配置中文字体 3. ✅ 负号显示为方框 4. ✅ 图例、标题、坐标轴标签中文显示异常 5. ✅ 跨平台字体兼容问题 6. ✅ 字体路径配置问题 7. ✅ 字体缓存问题 --- ## 🚀 快速开始 ### 第 0 步：下载字体文件 ```bash # 创建字体目录 mkdir -p fonts # 下载 BabelStoneHan 字体（约 50MB） curl -L -o fonts/BabelStoneHan.ttf "https://www.babelstone.co.uk/Fonts/Download/BabelStoneHan.ttf" # 或者使用 wget wget -O fonts/BabelStoneHan.ttf "https://www.babelstone.co.uk/Fonts/Download/BabelStoneHan.ttf" ``` **其他字体选择**（小于 10MB）： - **WenQuanYi Micro Hei** (文泉驿微米黑) - 约 4MB - **SimHei** (黑体) - 系统自带 - **Microsoft YaHei** (微软雅黑) - Windows 系统自带 ### 方法 1：复制模板（推荐） ```bash # 复制配置模板到你的项目 cp ~/.openclaw/skills/python-matplotlib-chinese-font/templates/setup_font.py ./ ``` ### 方法 2：直接使用字体文件 ```python import os import matplotlib.pyplot as plt from matplotlib import font_manager as fm # 字体文件路径（请修改为你的实际路径） font_file = './fonts/BabelStoneHan.ttf' # 关键：显式添加字体 fm.fontManager.addfont(font_file) # 创建 FontProperties font_prop = fm.FontProperties(fname=font_file) # 设置全局字体 plt.rcParams['font.family'] = font_prop.get_name() plt.rcParams['axes.unicode_minus'] = False # 使用 fig, ax = plt.subplots() ax.set_title('中文标题', fontproperties=font_prop) ax.set_xlabel('横轴', fontproperties=font_prop) ax.legend(['图例'], prop=font_prop) ``` --- ## 📋 完整配置代码（3 步） ### **第 1 步：下载并准备字体文件** ```bash # 创建字体目录 mkdir -p fonts # 下载 BabelStoneHan 字体 curl -L -o fonts/BabelStoneHan.ttf "https://www.babelstone.co.uk/Fonts/Download/BabelStoneHan.ttf" ``` **项目结构**： ``` your_project/ ├── fonts/ │ └── BabelStoneHan.ttf ← 中文字体文件（需自行下载） ├── scripts/ │ └── your_script.py └── ... ``` **字体推荐**： - `BabelStoneHan.ttf`（开源，支持中文）← **需自行下载**，约 50MB - `SimHei.ttf`（黑体）- 系统自带 - `Microsoft YaHei.ttf`（微软雅黑）- Windows 系统自带 - `WenQuanYi Micro Hei`（文泉驿微米黑）- 约 4MB --- ### **第 2 步：配置字体** ```python import os import matplotlib.pyplot as plt from matplotlib import font_manager as fm # 字体文件相对于脚本的路径 _FONT_FILE_RELATIVE = os.path.join('..', '..', 'fonts', 'BabelStoneHan.ttf') def _get_font_path(): """获取字体文件的绝对路径""" script_dir = os.path.dirname(os.path.abspath(__file__)) font_file = os.path.join(script_dir, _FONT_FILE_RELATIVE) return os.path.normpath(font_file) def setup_chinese_font(): """配置中文字体（兼容所有 matplotlib 版本）""" font_file = _get_font_path() if os.path.exists(font_file): # 创建 FontProperties font_prop = fm.FontProperties(fname=font_file) font_name = font_prop.get_name() # 注册字体（兼容新旧版本 matplotlib） try: # 方法1：matplotlib 3.2+ 使用 addfont if hasattr(fm.fontManager, 'addfont'): fm.fontManager.addfont(font_file) else: # 方法2：旧版本 matplotlib，手动添加到字体列表 try: fm.fontManager.ttflist.append(fm.FontEntry( fname=font_file, name=font_name, style=font_prop.get_style(), variant=font_prop.get_variant(), weight=font_prop.get_weight(), stretch=font_prop.get_stretch(), size=font_prop.get_size() )) except Exception: pass except Exception: pass # 设置全局字体 plt.rcParams['font.family'] = font_name plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题 return font_prop return fm.FontProperties() ``` **⚠️ 版本兼容性说明**： | matplotlib 版本 | 方法 | 说明 | |----------------|------|------| | **≥ 3.2** | `fontManager.addfont()` | 推荐方法 | | **< 3.2** | `fontManager.ttflist.append()` | 兼容方法 | # 创建 FontProperties font_prop = fm.FontProperties(fname=font_file) # 设置全局字体 plt.rcParams['font.family'] = font_prop.get_name() plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题 print(f"✅ 已加载中文字体: {font_file}") return font_prop else: print(f"⚠️ 未找到字体文件: {font_file}") return fm.FontProperties() # 使用 chinese_font = setup_chinese_font() ``` --- ### **第 3 步：在绑图时使用字体** #### **方法 A：全局设置（推荐）** ```python # 配置字体 chinese_font = setup_chinese_font() # 绑图时自动使用全局字体 fig, ax = plt.subplots() ax.set_title('中文标题') # 自动使用全局字体 ax.set_xlabel('横轴标签') ax.legend(['图例1', '图例2']) ``` #### **方法 B：局部指定（精确控制）** ```python # 配置字体 chinese_font = setup_chinese_font() # 绑图时显式指定 fontproperties fig, ax = plt.subplots() ax.set_title('中文标题', fontproperties=chinese_font) ax.set_xlabel('横轴标签', fontproperties=chinese_font) ax.legend(['图例1', '图例2'], prop=chinese_font) ``` --- ## 🎯 核心要点（5 个关键点） ### **1. 字体文件路径** ```python # ❌ 错误：使用运行目录 font_file = os.path.join(os.getcwd(), 'fonts', 'BabelStoneHan.ttf') # ✅ 正确：使用脚本所在目录 script_dir = os.path.dirname(os.path.abspath(__file__)) font_file = os.path.join(script_dir, '..', '..', 'fonts', 'BabelStoneHan.ttf') font_file = os.path.normpath(font_file) # 规范化路径 ``` ### **2. 显式添加字体（最关键！）** ```python # ❌ 错误：直接创建 FontProperties font_prop = fm.FontProperties(fname=font_file) # ✅ 正确：先 addfont，再创建 FontProperties fm.fontManager.addfont(font_file) # 关键！ font_prop = fm.FontProperties(fname=font_file) ``` ### **3. 设置全局字体** ```python # 设置全局字体 plt.rcParams['font.family'] = font_prop.get_name() plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题 ``` ### **4. 使用绝对路径** ```python # ✅ 使用 os.path.normpath() 规范化路径 font_file = os.path.normpath(font_file) # ✅ 检查文件是否存在 if os.path.exists(font_file): fm.fontManager.addfont(font_file) ``` ### **5. 字体文件在项目内** ``` ✅ 优点： - 不依赖系统字体 - 可移植性好 - 跨平台兼容 ❌ 避免： - 使用系统字体路径（/usr/share/fonts/...） - 假设字体已安装 ``` --- ## 🚨 常见问题 ### **问题 1：中文显示为方框** **原因**：没有调用 `addfont()` 显式添加字体 **解决**： ```python # ✅ 必须先调用 addfont fm.fontManager.addfont(font_file) font_prop = fm.FontProperties(fname=font_file) ``` --- ### **问题 2：负号显示为方框** **原因**：字体不支持负号 **解决**： ```python plt.rcParams['axes.unicode_minus'] = False ``` --- ### **问题 3：字体路径错误** **原因**：使用 `os.getcwd()` 而不是脚本所在目录 **解决**： ```python # ✅ 使用脚本所在目录 script_dir = os.path.dirname(os.path.abspath(__file__)) ``` --- ### **问题 4：图片已生成但中文仍是方框** **原因**：代码修改有误或发送了旧图片 **解决**： 1. 删除旧图片：`rm -f output.png` 2. 重新运行脚本 3. 检查图片修改时间：`stat output.png | grep Modify` --- ### **问题 6：`'FontManager' object has no attribute 'addfont'`** **原因**：`fontManager.addfont()` 是 matplotlib 3.2+ 才有的方法 **解决**：使用版本兼容性检查 ```python # 兼容所有 matplotlib 版本 if hasattr(fm.fontManager, 'addfont'): # matplotlib 3.2+ fm.fontManager.addfont(font_file) else: # matplotlib < 3.2 try: fm.fontManager.ttflist.append(fm.FontEntry(...)) except Exception: pass ``` **检查 matplotlib 版本**： ```python import matplotlib print(matplotlib.__version__) ``` --- ### **问题 7：字体缓存问题** **原因**：matplotlib 缓存了旧字体配置 **解决**： ```python # 清除字体缓存 try: fm._load_fontmanager(try_read_cache=False) except: pass ``` --- ## 📊 对比总结 | 方案 | 优点 | 缺点 | 推荐度 | |------|------|------|--------| | **字体文件在项目内** | 可移植、跨平台 | 需要管理字体文件 | ⭐⭐⭐⭐⭐ | | **使用系统字体** | 无需管理文件 | 依赖系统、不可移植 | ⭐⭐ | | **临时下载字体** | 自动化 | 网络依赖、速度慢 | ⭐⭐⭐ | --- ## 📁 Skill 包含内容 ``` python-matplotlib-chinese-font/ ├── SKILL.md # Skill 说明文档（本文件） ├── references/ │ ├── plot_utils.py # 完整工具模块 │ └── test_chinese_font.py # 测试代码 └── templates/ └── setup_font.py # 配置模板（可直接复制） ⚠️ 字体文件需自行下载，详见"第 0 步：下载字体文件" ``` --- ## 📚 参考代码 ### **完整工具模块**：`references/plot_utils.py` - ✅ 完整的字体配置函数 - ✅ 可直接导入使用 - ✅ 包含常用绘图参数 ### **测试代码**：`references/test_chinese_font.py` - ✅ 4 种测试场景 - ✅ 验证中文显示是否正常 - ✅ 包含负数测试 ### **配置模板**：`templates/setup_font.py` - ✅ 可直接复制到项目 - ✅ 只需修改字体路径 --- ## 💡 最佳实践 ```python # ✅ 推荐配置 import os import matplotlib.pyplot as plt from matplotlib import font_manager as fm # 1. 字体路径（相对于脚本） _FONT_FILE_RELATIVE = os.path.join('..', '..', 'fonts', 'BabelStoneHan.ttf') # 2. 获取绝对路径 def _get_font_path(): script_dir = os.path.dirname(os.path.abspath(__file__)) font_file = os.path.join(script_dir, _FONT_FILE_RELATIVE) return os.path.normpath(font_file) # 3. 配置字体 def setup_chinese_font(): font_file = _get_font_path() if os.path.exists(font_file): fm.fontManager.addfont(font_file) # 关键！ font_prop = fm.FontProperties(fname=font_file) plt.rcParams['font.family'] = font_prop.get_name() plt.rcParams['axes.unicode_minus'] = False return font_prop return fm.FontProperties() # 4. 使用 chinese_font = setup_chinese_font() ``` --- ## 🔗 相关链接 - **BabelStone Fonts**: https://www.babelstone.co.uk/Fonts/Han.html - **Matplotlib Font Management**: https://matplotlib.org/stable/api/font_manager_api.html - **Matplotlib Chinese Font Guide**: https://matplotlib.org/stable/tutorials/text/text_intro.html --- ## 📝 版本历史 | 版本 | 日期 | 说明 | |------|------|------| | **v1.0.0** | 2026-03-21 | 初始版本 - BabelStoneHan 字体配置方案 | | **v1.1.0** | 2026-03-21 | 添加 matplotlib 版本兼容性支持 | | **v1.2.0** | 2026-04-02 | 字体文件改为用户自行下载（满足 clawhub 文件大小限制） | --- **Skill 创建时间**：2026-03-21 **维护者**：太子 **当前版本**：v1.2.0

path: references/test_chinese_font.py
content: #!/usr/bin/env python3
"""
test_chinese_font.py - 测试 matplotlib 中文显示

实验目的：
  验证中文字体配置是否正确
"""

import os
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib import font_manager as fm
import numpy as np

# ============================================================================
# 字体配置（参考 Matplotlib 中文显示完整指南）
# ============================================================================

# 字体文件相对于脚本的路径
# test_chinese_font.py 位于 spot_micro_body_control_sim/03_gait_control/tests/
# 字体文件位于 spot_micro_body_control_sim/fonts/BabelStoneHan.ttf
_FONT_FILE_RELATIVE = os.path.join('..', '..', 'fonts', 'BabelStoneHan.ttf')

def _get_font_path():
    """获取字体文件的绝对路径"""
    script_dir = os.path.dirname(os.path.abspath(__file__))
    font_file = os.path.join(script_dir, _FONT_FILE_RELATIVE)
    return os.path.normpath(font_file)

def setup_chinese_font():
    """配置中文字体"""
    font_file = _get_font_path()
    
    print("\n" + "=" * 70)
    print("【字体配置测试】")
    print("=" * 70)
    print(f"  字体路径: {font_file}")
    print(f"  存在: {os.path.exists(font_file)}")
    
    if os.path.exists(font_file):
        try:
            # 关键：显式添加字体到 matplotlib 的 fontManager 缓存
            fm.fontManager.addfont(font_file)
            
            # 创建 FontProperties
            font_prop = fm.FontProperties(fname=font_file)
            
            # 设置全局字体
            plt.rcParams['font.family'] = font_prop.get_name()
            plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题
            
            print(f"  ✅ 已加载: {font_prop.get_name()}")
            print("=" * 70 + "\n")
            return font_prop
        except Exception as e:
            print(f"  ❌ 加载失败: {e}")
            print("=" * 70 + "\n")
    else:
        print(f"  ⚠️ 未找到字体文件")
        print("=" * 70 + "\n")
    
    return fm.FontProperties()


def main():
    """主测试函数"""
    
    # 1. 配置字体
    chinese_font = setup_chinese_font()
    
    # 2. 创建测试数据
    x = np.linspace(0, 2*np.pi, 100)
    y1 = np.sin(x)
    y2 = np.cos(x)
    
    # 3. 创建图表
    fig, axes = plt.subplots(2, 2, figsize=(14, 10))
    
    # ============================================================================
    # 子图 1：基础测试
    # ============================================================================
    ax = axes[0, 0]
    ax.plot(x, y1, label='正弦曲线', linewidth=2)
    ax.plot(x, y2, label='余弦曲线', linewidth=2)
    ax.set_title('基础测试：中文标题', fontsize=14, fontweight='bold')
    ax.set_xlabel('横轴：角度（弧度）', fontsize=11)
    ax.set_ylabel('纵轴：幅值', fontsize=11)
    ax.legend(prop=chinese_font, loc='upper right')
    ax.grid(True, alpha=0.3)
    ax.axhline(y=0, color='gray', linestyle='--', alpha=0.5)
    
    # ============================================================================
    # 子图 2：多元素测试
    # ============================================================================
    ax = axes[0, 1]
    categories = ['类别一', '类别二', '类别三', '类别四', '类别五']
    values = [23, 45, 56, 78, 32]
    colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8']
    
    bars = ax.bar(categories, values, color=colors, alpha=0.8)
    ax.set_title('多元素测试：柱状图', fontsize=14, fontweight='bold')
    ax.set_xlabel('分类标签', fontsize=11)
    ax.set_ylabel('数值大小', fontsize=11)
    ax.set_xticklabels(categories, fontproperties=chinese_font)
    
    # 添加数值标签
    for bar, value in zip(bars, values):
        height = bar.get_height()
        ax.text(bar.get_x() + bar.get_width()/2., height,
                f'{value}',
                ha='center', va='bottom', fontsize=10, fontproperties=chinese_font)
    
    # ============================================================================
    # 子图 3：负数测试
    # ============================================================================
    ax = axes[1, 0]
    y_negative = y1 - 0.5
    ax.plot(x, y_negative, label='偏移正弦（含负数）', linewidth=2, color='#E94F37')
    ax.fill_between(x, y_negative, alpha=0.3, color='#E94F37')
    ax.set_title('负数测试：验证负号显示', fontsize=14, fontweight='bold')
    ax.set_xlabel('横轴标签', fontsize=11)
    ax.set_ylabel('纵轴（含负值）', fontsize=11)
    ax.legend(prop=chinese_font, loc='upper right')
    ax.grid(True, alpha=0.3)
    ax.axhline(y=0, color='gray', linestyle='--', alpha=0.5)
    
    # ============================================================================
    # 子图 4：综合测试
    # ============================================================================
    ax = axes[1, 1]
    
    # 多条曲线
    ax.plot(x, y1, label='数据系列一', linewidth=2, marker='o', markevery=10)
    ax.plot(x, y2, label='数据系列二', linewidth=2, marker='s', markevery=10)
    ax.plot(x, y1*0.5, label='数据系列三', linewidth=2, marker='^', markevery=10)
    
    ax.set_title('综合测试：多系列图例', fontsize=14, fontweight='bold')
    ax.set_xlabel('时间（秒）', fontsize=11)
    ax.set_ylabel('测量值（单位）', fontsize=11)
    ax.legend(prop=chinese_font, loc='upper right')
    ax.grid(True, alpha=0.3)
    
    # 添加注释
    ax.text(0.5, 0.95, '这是中文注释文本', 
            transform=ax.transAxes, fontsize=10, 
            verticalalignment='top', fontproperties=chinese_font,
            bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))
    
    # ============================================================================
    # 总标题
    # ============================================================================
    fig.suptitle('Matplotlib 中文显示完整测试', fontsize=16, fontweight='bold', y=0.995)
    
    plt.tight_layout(rect=[0, 0, 1, 0.98])
    
    # ============================================================================
    # 保存图片
    # ============================================================================
    output_path = os.path.join(os.getcwd(), 'test_chinese_font.png')
    plt.savefig(output_path, dpi=150, bbox_inches='tight')
    
    print("=" * 70)
    print("【测试结果】")
    print("=" * 70)
    print(f"  ✅ 图片已保存: {output_path}")
    print("=" * 70)


if __name__ == '__main__':
    main()


path: references/plot_utils.py
content: #!/usr/bin/env python3
# =============================================================================
# plot_utils.py - matplotlib 绑图工具模块
# =============================================================================
#
# 功能说明：
#   提供 matplotlib 绑图的公共配置，包括中文字体支持等。
#
# 使用方法：
#   from plot_utils import setup_matplotlib
#   setup_matplotlib()
#
# =============================================================================

import os
import matplotlib.pyplot as plt
from matplotlib import font_manager


# =============================================================================
# 中文字体配置
# =============================================================================
# 问题描述：
#   matplotlib 默认不支持中文，中文会显示为方块，控制台警告：
#   "UserWarning: Glyph XXXXX missing from current font"
#
# 解决方案：
#   使用 font_manager.fontManager.addfont() 显式添加字体到缓存
#
# 核心要点：
#   - addfont() 是关键：必须显式把字体添加到 matplotlib 的 fontManager 缓存
#   - 使用绝对路径：避免路径问题
#   - 字体文件放在项目内：不依赖系统字体，可移植性好
# =============================================================================

# 字体文件相对于本模块的路径
# plot_utils.py 位于 your_project/scripts/
# 字体文件位于 your_project/fonts/BabelStoneHan.ttf
# 相对路径: ../fonts/BabelStoneHan.ttf
_FONT_FILE_RELATIVE = os.path.join('..', 'fonts', 'BabelStoneHan.ttf')

# 是否已初始化
_initialized = False


def _get_font_path():
    """获取字体文件的绝对路径"""
    script_dir = os.path.dirname(os.path.abspath(__file__))
    font_file = os.path.join(script_dir, _FONT_FILE_RELATIVE)
    return os.path.normpath(font_file)


def setup_chinese_font(verbose=False):
    """
    配置中文字体（兼容所有 matplotlib 版本）

    Args:
        verbose: 是否打印详细信息

    Returns:
        FontProperties: 字体属性对象

    兼容性：
        - matplotlib 3.2+: 使用 fontManager.addfont()
        - matplotlib < 3.2: 使用 fontManager.ttflist.append()
    """
    global _initialized
    
    if _initialized:
        return font_manager.FontProperties()
    
    font_file = _get_font_path()
    
    if verbose:
        print("\n" + "=" * 70)
        print("【字体配置】")
        print("=" * 70)
        print(f"  字体路径: {font_file}")
        print(f"  存在: {os.path.exists(font_file)}")
    
    if os.path.exists(font_file):
        try:
            # 创建 FontProperties
            font_prop = font_manager.FontProperties(fname=font_file)
            font_name = font_prop.get_name()
            
            # 注册字体（兼容新旧版本 matplotlib）
            try:
                # 方法1：matplotlib 3.2+ 使用 addfont
                if hasattr(font_manager.fontManager, 'addfont'):
                    font_manager.fontManager.addfont(font_file)
                    if verbose:
                        print(f"  ✅ 已加载 (addfont): {font_name}")
                else:
                    # 方法2：旧版本 matplotlib，手动添加到字体列表
                    try:
                        font_manager.fontManager.ttflist.append(font_manager.FontEntry(
                            fname=font_file,
                            name=font_name,
                            style=font_prop.get_style(),
                            variant=font_prop.get_variant(),
                            weight=font_prop.get_weight(),
                            stretch=font_prop.get_stretch(),
                            size=font_prop.get_size()
                        ))
                        if verbose:
                            print(f"  ✅ 已加载 (ttflist): {font_name}")
                    except Exception:
                        if verbose:
                            print(f"  ✅ 已加载 (FontProperties): {font_name}")
            except Exception as e:
                if verbose:
                    print(f"  ⚠️ 字体注册失败: {e}")
            
            # 设置全局字体
            plt.rcParams['font.family'] = font_name
            plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题
            
            _initialized = True
            
            if verbose:
                print("=" * 70 + "\n")
            
            return font_prop
        except Exception as e:
            if verbose:
                print(f"  ❌ 加载失败: {e}")
                print("=" * 70 + "\n")
    else:
        if verbose:
            print(f"  ⚠️ 未找到字体文件")
            print("=" * 70 + "\n")
    
    return font_manager.FontProperties()
        bool: 是否成功配置
    """
    font_file = _get_font_path()

    if os.path.exists(font_file):
        font_manager.fontManager.addfont(font_file)
        font_prop = font_manager.FontProperties(fname=font_file)
        plt.rcParams['font.family'] = font_prop.get_name()
        plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题
        if verbose:
            print(f"[字体] 已加载中文字体: {font_file}")
        return True
    else:
        print(f"[警告] 未找到中文字体文件: {font_file}")
        return False


def setup_matplotlib(chinese_font=True, style=None, verbose=False):
    """
    初始化 matplotlib 配置

    Args:
        chinese_font: 是否配置中文字体
        style: matplotlib 样式（如 'seaborn', 'ggplot' 等）
        verbose: 是否打印详细信息

    Returns:
        bool: 是否成功配置
    """
    global _initialized

    if _initialized:
        return True

    success = True

    # 设置样式
    if style:
        try:
            plt.style.use(style)
            if verbose:
                print(f"[样式] 已应用样式: {style}")
        except Exception as e:
            print(f"[警告] 无法应用样式 {style}: {e}")
            success = False

    # 配置中文字体
    if chinese_font:
        if not setup_chinese_font(verbose=verbose):
            success = False

    _initialized = True
    return success


# =============================================================================
# 常用绑图参数
# =============================================================================
# 标准字体大小
FONT_SIZE = {
    'title': 14,
    'subtitle': 12,
    'label': 10,
    'tick': 9,
    'legend': 9,
    'annotation': 8,
}

# 标准线宽
LINE_WIDTH = {
    'main': 1.5,
    'secondary': 1.0,
    'thin': 0.5,
    'thick': 2.0,
}

# 标准颜色
COLORS = {
    'target': '#2E86AB',      # 蓝色 - 目标值
    'actual': '#E94F37',      # 红色 - 实际值
    'error': '#2ECC71',       # 绿色 - 误差
    'torque': '#9B59B6',      # 紫色 - 力矩
    'reference': '#95A5A6',   # 灰色 - 参考线
}


# =============================================================================
# 交互功能：点击图例切换曲线显示/隐藏
# =============================================================================
def enable_legend_toggle(ax, legend=None):
    """
    启用点击图例切换曲线显示/隐藏功能

    用法：
        fig, ax = plt.subplots()
        line1, = ax.plot(x, y1, label='曲线1')
        line2, = ax.plot(x, y2, label='曲线2')
        legend = ax.legend()
        enable_legend_toggle(ax, legend)
        plt.show()

    Args:
        ax: matplotlib Axes 对象
        legend: Legend 对象（如果为 None，则使用 ax.get_legend()）
    """
    if legend is None:
        legend = ax.get_legend()

    if legend is None:
        return

    # 建立图例项与曲线的映射
    legend_lines = legend.get_lines()
    legend_texts = legend.get_texts()
    ax_lines = ax.get_lines()

    # 创建映射字典：图例线条 -> 曲线，图例文字 -> 曲线
    line_map = {}
    text_map = {}

    for i, (leg_line, ax_line) in enumerate(zip(legend_lines, ax_lines)):
        # 设置图例线条可点击
        leg_line.set_picker(True)
        leg_line.set_pickradius(5)
        line_map[leg_line] = (ax_line, leg_line, legend_texts[i] if i < len(legend_texts) else None)

        # 设置图例文字可点击
        if i < len(legend_texts):
            legend_texts[i].set_picker(True)
            text_map[legend_texts[i]] = (ax_line, leg_line, legend_texts[i])

    def on_pick(event):
        artist = event.artist

        # 查找对应的曲线和图例元素
        if artist in line_map:
            ax_line, leg_line, leg_text = line_map[artist]
        elif artist in text_map:
            ax_line, leg_line, leg_text = text_map[artist]
        else:
            return

        # 切换可见性
        visible = not ax_line.get_visible()
        ax_line.set_visible(visible)

        # 更新图例样式：隐藏的曲线图例变淡
        alpha = 1.0 if visible else 0.2
        leg_line.set_alpha(alpha)
        if leg_text:
            leg_text.set_alpha(alpha)

        # 重绘
        ax.figure.canvas.draw()

    ax.figure.canvas.mpl_connect('pick_event', on_pick)


def enable_legend_toggle_for_figure(fig):
    """
    为整个 figure 的所有子图启用图例切换功能

    Args:
        fig: matplotlib Figure 对象
    """
    for ax in fig.get_axes():
        legend = ax.get_legend()
        if legend is not None:
            enable_legend_toggle(ax, legend)


path: templates/setup_font.py
content: #!/usr/bin/env python3
"""
setup_font.py - matplotlib 中文字体配置模板

使用方法：
0. 先下载字体文件（首次使用）：
   mkdir -p fonts
   curl -L -o fonts/BabelStoneHan.ttf "https://www.babelstone.co.uk/Fonts/Download/BabelStoneHan.ttf"

1. 复制此文件到你的项目
2. 修改 _FONT_FILE_RELATIVE 路径
3. 在你的代码中导入并调用 setup_chinese_font()

示例：
    from setup_font import setup_chinese_font
    chinese_font = setup_chinese_font()
    
    # 使用全局字体（推荐）
    ax.set_title('中文标题')
    
    # 或显式指定（精确控制）
    ax.set_title('中文标题', fontproperties=chinese_font)
"""

import os
import matplotlib.pyplot as plt
from matplotlib import font_manager as fm


# ============================================================================
# 字体文件路径配置
# ============================================================================
# 修改这个路径，使其指向你的字体文件
# 示例：
#   - 如果 setup_font.py 和 fonts/ 在同一目录：
#     _FONT_FILE_RELATIVE = 'fonts/BabelStoneHan.ttf'
#   - 如果 setup_font.py 在 scripts/ 目录，字体在项目根目录的 fonts/：
#     _FONT_FILE_RELATIVE = os.path.join('..', 'fonts', 'BabelStoneHan.ttf')

_FONT_FILE_RELATIVE = os.path.join('..', 'fonts', 'BabelStoneHan.ttf')


# ============================================================================
# 字体配置函数
# ============================================================================

def _get_font_path():
    """获取字体文件的绝对路径"""
    script_dir = os.path.dirname(os.path.abspath(__file__))
    font_file = os.path.join(script_dir, _FONT_FILE_RELATIVE)
    return os.path.normpath(font_file)


def setup_chinese_font(verbose=False):
    """
    配置中文字体（兼容所有 matplotlib 版本）
    
    Args:
        verbose: 是否打印详细信息
    
    Returns:
        FontProperties: 字体属性对象
    
    兼容性：
        - matplotlib 3.2+: 使用 fontManager.addfont()
        - matplotlib < 3.2: 使用 fontManager.ttflist.append()
    """
    font_file = _get_font_path()
    
    if verbose:
        print("\n" + "=" * 70)
        print("【字体配置】")
        print("=" * 70)
        print(f"  字体路径: {font_file}")
        print(f"  存在: {os.path.exists(font_file)}")
    
    if os.path.exists(font_file):
        try:
            # 创建 FontProperties
            font_prop = fm.FontProperties(fname=font_file)
            font_name = font_prop.get_name()
            
            # 注册字体（兼容新旧版本 matplotlib）
            try:
                # 方法1：matplotlib 3.2+ 使用 addfont
                if hasattr(fm.fontManager, 'addfont'):
                    fm.fontManager.addfont(font_file)
                    if verbose:
                        print(f"  ✅ 已加载 (addfont): {font_name}")
                else:
                    # 方法2：旧版本 matplotlib，手动添加到字体列表
                    try:
                        fm.fontManager.ttflist.append(fm.FontEntry(
                            fname=font_file,
                            name=font_name,
                            style=font_prop.get_style(),
                            variant=font_prop.get_variant(),
                            weight=font_prop.get_weight(),
                            stretch=font_prop.get_stretch(),
                            size=font_prop.get_size()
                        ))
                        if verbose:
                            print(f"  ✅ 已加载 (ttflist): {font_name}")
                    except Exception as e:
                        if verbose:
                            print(f"  ✅ 已加载 (FontProperties): {font_name}")
            except Exception as e:
                if verbose:
                    print(f"  ⚠️ 字体注册失败: {e}")
            
            # 设置全局字体
            plt.rcParams['font.family'] = font_name
            plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题
            
            if verbose:
                print("=" * 70 + "\n")
            
            return font_prop
        except Exception as e:
            if verbose:
                print(f"  ❌ 加载失败: {e}")
                print("=" * 70 + "\n")
    else:
        if verbose:
            print(f"  ⚠️ 未找到字体文件")
            print("=" * 70 + "\n")
    
    return fm.FontProperties()


# ============================================================================
# 使用示例
# ============================================================================

if __name__ == '__main__':
    import numpy as np
    
    # 1. 配置字体
    chinese_font = setup_chinese_font(verbose=True)
    
    # 2. 创建测试数据
    x = np.linspace(0, 2*np.pi, 100)
    y1 = np.sin(x)
    y2 = np.cos(x)
    
    # 3. 绘图
    fig, ax = plt.subplots(figsize=(10, 6))
    
    ax.plot(x, y1, label='正弦曲线', linewidth=2)
    ax.plot(x, y2, label='余弦曲线', linewidth=2)
    
    ax.set_title('matplotlib 中文显示测试', fontsize=14, fontweight='bold')
    ax.set_xlabel('横轴：角度（弧度）', fontsize=11)
    ax.set_ylabel('纵轴：幅值', fontsize=11)
    ax.legend(prop=chinese_font, loc='upper right')
    ax.grid(True, alpha=0.3)
    
    # 4. 保存
    output_path = 'test_chinese_font.png'
    plt.savefig(output_path, dpi=150, bbox_inches='tight')
    print(f"✅ 图片已保存: {output_path}")


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)