Sources: extracted from two upstream archives (skill-repo, skills-main),
merged with the following policy:
- 15 broken symlinks (pointing to /Users/jameslee/.cc-switch/skills or
../../.agents/skills on a foreign machine) discarded
- 3 real name collisions with identical content (ai-pair, ifind-http-api,
zhipu-websearch) kept as one copy
- Functional overlaps deduped keeping the strongest variant:
- docx family: kept docx (official, full toolchain) + docx-cn
(GB/T 9704 Chinese official-document constants),
dropped docx_writer (no scripts, name collided with docx)
- humanizer family: kept humanizer-zh (6 zh reference docs),
dropped humanizer (en, redundant for CN workflow)
- Skills that only ran in a foreign environment removed:
ablemind-ops, app-publish, hlb-design-system, openclaw-adj-skill,
claude-driver
- alphapai excluded from this public repo because its SKILL.md hard-coded
live credentials
Result: 25 skills, 572 files, ~7.5 MB.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
334 lines
11 KiB
Python
334 lines
11 KiB
Python
"""
|
||
图表样式配置模块
|
||
|
||
定义默认的颜色方案、线型、标记点等样式。
|
||
"""
|
||
|
||
from typing import Dict, List, Tuple
|
||
from lxml import etree
|
||
|
||
from .oxml_ns import NAMESPACES
|
||
|
||
# ============================================================================
|
||
# 颜色方案定义(RGB 格式)
|
||
# ============================================================================
|
||
|
||
# 深色系列(主色)
|
||
DARK_RED = "C00000" # 深红色
|
||
DARK_GRAY = "595959" # 深灰色
|
||
DARK_BLUE = "0070C0" # 深蓝色
|
||
DARK_ORANGE = "ED7D31" # 深橙色
|
||
|
||
# 浅色系列(辅助色)
|
||
LIGHT_RED = "FF9999" # 浅红色
|
||
LIGHT_GRAY = "BFBFBF" # 浅灰色
|
||
LIGHT_BLUE = "9DC3E6" # 浅蓝色
|
||
LIGHT_ORANGE = "F4B183" # 浅橙色
|
||
|
||
# ⭐ aim00 风格颜色(对比色方案:浅灰柱状图 + 深红折线)
|
||
AIM00_LIGHT_GRAY = "C0C0C0" # 浅灰色 - 用于柱状图/面积图(第一系列)
|
||
AIM00_DARK_RED = "C00000" # 深红色 - 用于折线图(第二系列)
|
||
AIM00_DARK_BLUE = "305496" # 深蓝色 - 备用
|
||
AIM00_GRAY = "808080" # 中灰色 - 备用
|
||
|
||
# 默认颜色序列(深色 + 浅色交替)
|
||
DEFAULT_COLOR_SEQUENCE = [
|
||
DARK_RED,
|
||
DARK_BLUE,
|
||
DARK_ORANGE,
|
||
DARK_GRAY,
|
||
LIGHT_RED,
|
||
LIGHT_BLUE,
|
||
LIGHT_ORANGE,
|
||
LIGHT_GRAY,
|
||
]
|
||
|
||
# ── 主题配套图表系列色(每个主题一套 8 色) ──
|
||
COLOR_SCHEMES = {
|
||
"midnight": ["CADCFC", "1E2761", "E8B931", "4A6FA5", "9BB5D6", "B08C28", "8896AB", "484E5C"],
|
||
"charcoal": ["A8B4BE", "36454F", "E8B931", "607D8B", "B0BEC5", "C4A035", "78909C", "4A565E"],
|
||
"jp_finance": ["A8D5DC", "1B3D6E", "C9A84C", "4A8FB8", "7FBFCF", "A07840", "8FA8C0", "505868"],
|
||
"pension_warm": ["A8C4D4", "2E5FA3", "D4903F", "7BA7BC", "B0C9B0", "9C7B48", "8A9DB8", "606870"],
|
||
"tech_blue": ["90CAF9", "1565C0", "00BFA5", "7E57C2", "29B6F6", "FF7043", "90A4AE", "546E7A"],
|
||
"state_red": ["CDAC60", "8B0000", "B8860B", "4A6FA5", "C07840", "507050", "907878", "585048"],
|
||
"esg_green": ["A5D6A7", "1A5C2A", "8BC34A", "00838F", "C8E6C9", "795548", "78909C", "607D8B"],
|
||
"dark_pro": ["00BFFF", "FFD700", "00E676", "7B68EE", "FF6D00", "E040FB", "A0B0C0", "4A5868"],
|
||
"morningstar": ["A4C2D8", "1D2B3A", "E67E22", "5A9BD5", "95C8D8", "B87333", "7B8794", "495057"],
|
||
"macro_research":["AED6F1", "2C3E50", "3498DB", "95A5A6", "85C1E9", "1ABC9C", "BDC3C7", "566573"],
|
||
# 兜底别名
|
||
"default": ["CADCFC", "1E2761", "E8B931", "4A6FA5", "9BB5D6", "B08C28", "8896AB", "484E5C"],
|
||
}
|
||
|
||
# ============================================================================
|
||
# 线型配置
|
||
# ============================================================================
|
||
|
||
# 线宽(EMUs: English Metric Units, 1 pt = 12700 EMUs)
|
||
LINE_WIDTH_PT = {
|
||
0.5: 6350, # 0.5 pt
|
||
0.75: 9525, # 0.75 pt
|
||
1.0: 12700, # 1 pt (默认)
|
||
1.5: 19050, # 1.5 pt
|
||
2.0: 25400, # 2 pt
|
||
2.25: 28575, # 2.25 pt
|
||
3.0: 38100, # 3 pt
|
||
}
|
||
|
||
DEFAULT_LINE_WIDTH = LINE_WIDTH_PT[1.0] # 1 pt
|
||
|
||
# ============================================================================
|
||
# 标记点配置
|
||
# ============================================================================
|
||
|
||
# 标记点样式
|
||
MARKER_STYLES = {
|
||
"none": None, # 无标记
|
||
"circle": "circle", # 圆形
|
||
"square": "square", # 方形
|
||
"diamond": "diamond", # 菱形
|
||
"triangle": "triangle", # 三角形
|
||
}
|
||
|
||
DEFAULT_MARKER_STYLE = "none" # 默认无标记点
|
||
DEFAULT_MARKER_SIZE = 5 # 标记点大小(pt)
|
||
|
||
# ============================================================================
|
||
# 样式应用函数
|
||
# ============================================================================
|
||
|
||
def apply_series_style(
|
||
ser_element,
|
||
color: str = None,
|
||
line_width: int = None,
|
||
marker_style: str = None,
|
||
marker_size: int = None,
|
||
):
|
||
"""
|
||
为系列元素应用样式
|
||
|
||
Args:
|
||
ser_element: 系列元素 (<c:ser>)
|
||
color: RGB 颜色(6位十六进制,如 "C00000")
|
||
line_width: 线宽(EMUs)
|
||
marker_style: 标记点样式("none", "circle", "square" 等)
|
||
marker_size: 标记点大小(pt)
|
||
"""
|
||
# ⭐ 检测图表类型(通过父元素)
|
||
parent = ser_element.getparent()
|
||
parent_tag = parent.tag.split('}')[1] if '}' in parent.tag else parent.tag
|
||
is_area_or_bar = parent_tag in ('barChart', 'areaChart')
|
||
|
||
# 查找或创建 spPr (shape properties) 元素
|
||
spPr = ser_element.find('c:spPr', namespaces=NAMESPACES)
|
||
if spPr is None:
|
||
# 在 idx/order/tx 之后插入 spPr
|
||
insert_index = _find_insert_position(ser_element, 'spPr')
|
||
spPr = etree.Element(f"{{{NAMESPACES['c']}}}spPr")
|
||
ser_element.insert(insert_index, spPr)
|
||
|
||
# 应用线条样式(传入图表类型信息)
|
||
if line_width is not None or color is not None:
|
||
_apply_line_style(spPr, color, line_width, is_area_or_bar)
|
||
|
||
# 应用标记点样式
|
||
if marker_style is not None:
|
||
_apply_marker_style(ser_element, marker_style, marker_size, color)
|
||
|
||
|
||
def _apply_line_style(spPr, color: str = None, line_width: int = None, is_area_or_bar: bool = False):
|
||
"""
|
||
应用线条样式
|
||
|
||
Args:
|
||
spPr: shape properties 元素
|
||
color: 颜色
|
||
line_width: 线宽
|
||
is_area_or_bar: 是否为面积图或柱状图(这些图表类型需要设置填充色而非边框色)
|
||
"""
|
||
# 清除现有的 ln 元素
|
||
for ln in spPr.findall('a:ln', namespaces=NAMESPACES):
|
||
spPr.remove(ln)
|
||
|
||
# 创建新的 ln (line) 元素
|
||
ln = etree.SubElement(spPr, f"{{{NAMESPACES['a']}}}ln")
|
||
|
||
# ⭐ 如果是面积图或柱状图,设置为无边框
|
||
if is_area_or_bar:
|
||
# 设置为无边框(noFill)
|
||
noFill = etree.SubElement(ln, f"{{{NAMESPACES['a']}}}noFill")
|
||
|
||
# ⭐ 核心修复:为柱状图/面积图添加填充色
|
||
if color is not None:
|
||
# 清除现有的 solidFill 元素
|
||
for fill in spPr.findall('a:solidFill', namespaces=NAMESPACES):
|
||
spPr.remove(fill)
|
||
|
||
# 在 ln 之后添加 solidFill
|
||
solidFill = etree.SubElement(spPr, f"{{{NAMESPACES['a']}}}solidFill")
|
||
srgbClr = etree.SubElement(solidFill, f"{{{NAMESPACES['a']}}}srgbClr")
|
||
srgbClr.set('val', color)
|
||
else:
|
||
# 折线图/散点图:设置线宽和颜色
|
||
if line_width is not None:
|
||
ln.set('w', str(line_width))
|
||
|
||
# 设置线条颜色
|
||
if color is not None:
|
||
solidFill = etree.SubElement(ln, f"{{{NAMESPACES['a']}}}solidFill")
|
||
srgbClr = etree.SubElement(solidFill, f"{{{NAMESPACES['a']}}}srgbClr")
|
||
srgbClr.set('val', color)
|
||
|
||
|
||
def _apply_marker_style(
|
||
ser_element,
|
||
marker_style: str,
|
||
marker_size: int = None,
|
||
color: str = None
|
||
):
|
||
"""应用标记点样式"""
|
||
# 清除现有的 marker 元素
|
||
for marker in ser_element.findall('c:marker', namespaces=NAMESPACES):
|
||
ser_element.remove(marker)
|
||
|
||
if marker_style == "none" or marker_style is None:
|
||
# 创建无标记的 marker 元素
|
||
insert_index = _find_insert_position(ser_element, 'marker')
|
||
marker = etree.Element(f"{{{NAMESPACES['c']}}}marker")
|
||
ser_element.insert(insert_index, marker)
|
||
|
||
symbol = etree.SubElement(marker, f"{{{NAMESPACES['c']}}}symbol")
|
||
symbol.set('val', 'none')
|
||
else:
|
||
# 创建有标记的 marker 元素
|
||
insert_index = _find_insert_position(ser_element, 'marker')
|
||
marker = etree.Element(f"{{{NAMESPACES['c']}}}marker")
|
||
ser_element.insert(insert_index, marker)
|
||
|
||
# 标记样式
|
||
symbol = etree.SubElement(marker, f"{{{NAMESPACES['c']}}}symbol")
|
||
symbol.set('val', marker_style)
|
||
|
||
# 标记大小
|
||
if marker_size is not None:
|
||
size = etree.SubElement(marker, f"{{{NAMESPACES['c']}}}size")
|
||
size.set('val', str(marker_size))
|
||
|
||
# 标记颜色(如果提供)
|
||
if color is not None:
|
||
spPr = etree.SubElement(marker, f"{{{NAMESPACES['c']}}}spPr")
|
||
solidFill = etree.SubElement(spPr, f"{{{NAMESPACES['a']}}}solidFill")
|
||
srgbClr = etree.SubElement(solidFill, f"{{{NAMESPACES['a']}}}srgbClr")
|
||
srgbClr.set('val', color)
|
||
|
||
|
||
def _find_insert_position(parent, target_element: str) -> int:
|
||
"""
|
||
查找元素的正确插入位置(按照 OOXML 规范的顺序)
|
||
|
||
OOXML 规范中 <c:ser> 的子元素顺序:
|
||
1. idx
|
||
2. order
|
||
3. tx
|
||
4. spPr (shape properties)
|
||
5. marker
|
||
6. cat / xVal
|
||
7. val / yVal
|
||
8. smooth
|
||
"""
|
||
element_order = {
|
||
'idx': 0,
|
||
'order': 1,
|
||
'tx': 2,
|
||
'spPr': 3,
|
||
'marker': 4,
|
||
'cat': 5,
|
||
'xVal': 5,
|
||
'val': 6,
|
||
'yVal': 6,
|
||
'smooth': 7,
|
||
}
|
||
|
||
target_order = element_order.get(target_element, 99)
|
||
|
||
# 查找第一个顺序大于 target 的元素位置
|
||
for i, child in enumerate(parent):
|
||
tag = child.tag.split('}')[-1] if '}' in child.tag else child.tag
|
||
child_order = element_order.get(tag, 99)
|
||
if child_order > target_order:
|
||
return i
|
||
|
||
# 如果没找到,返回末尾位置
|
||
return len(parent)
|
||
|
||
|
||
def get_color_for_series(series_index: int, color_scheme: str = "default") -> str:
|
||
"""
|
||
为系列获取颜色
|
||
|
||
Args:
|
||
series_index: 系列索引(0-based)
|
||
color_scheme: 颜色方案名称
|
||
|
||
Returns:
|
||
RGB 颜色字符串(6位十六进制)
|
||
"""
|
||
colors = COLOR_SCHEMES.get(color_scheme, DEFAULT_COLOR_SEQUENCE)
|
||
return colors[series_index % len(colors)]
|
||
|
||
|
||
# ============================================================================
|
||
# 预设样式配置
|
||
# ============================================================================
|
||
|
||
class StyleConfig:
|
||
"""样式配置类"""
|
||
|
||
def __init__(
|
||
self,
|
||
color_scheme: str = "default",
|
||
line_width_pt: float = 1.0,
|
||
marker_style: str = "none",
|
||
marker_size: int = 5,
|
||
):
|
||
"""
|
||
初始化样式配置
|
||
|
||
Args:
|
||
color_scheme: 颜色方案("default", "dark_only", "light_only" 等)
|
||
line_width_pt: 线宽(pt)
|
||
marker_style: 标记点样式("none", "circle", "square" 等)
|
||
marker_size: 标记点大小(pt)
|
||
"""
|
||
self.color_scheme = color_scheme
|
||
self.line_width = LINE_WIDTH_PT.get(line_width_pt, DEFAULT_LINE_WIDTH)
|
||
self.marker_style = marker_style
|
||
self.marker_size = marker_size
|
||
|
||
def apply_to_series(self, ser_element, series_index: int):
|
||
"""
|
||
将样式应用到系列元素
|
||
|
||
Args:
|
||
ser_element: 系列元素
|
||
series_index: 系列索引(用于选择颜色)
|
||
"""
|
||
color = get_color_for_series(series_index, self.color_scheme)
|
||
|
||
apply_series_style(
|
||
ser_element,
|
||
color=color,
|
||
line_width=self.line_width,
|
||
marker_style=self.marker_style,
|
||
marker_size=self.marker_size,
|
||
)
|
||
|
||
|
||
# 默认样式配置
|
||
DEFAULT_STYLE_CONFIG = StyleConfig(
|
||
color_scheme="default",
|
||
line_width_pt=1.0,
|
||
marker_style="none",
|
||
marker_size=5,
|
||
)
|
||
|