skills/ppt-station-skill/ppt_station/chart_builder/styles.py
wangyitong a65adcc2e5 Initial commit: merged, deduplicated, and vetted skill collection
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>
2026-07-13 14:47:12 +08:00

334 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
图表样式配置模块
定义默认的颜色方案、线型、标记点等样式。
"""
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,
)