skills/fund-slides/references/charts-base.md
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

8.4 KiB
Raw Permalink Blame History

Charts Base -- 基础设施与工具

ECharts 集成基础:内联引入、容器规范、初始化模式、格式化工具、配色适配。

按需加载具体图表配置:


ECharts 内联引入(零外部依赖)

禁止使用 CDN <script src="..."> ECharts 必须内嵌到 HTML 中确保离线可用。

生成流程:

  1. 读取本地文件 echarts.min.js~1MB已预存在 references 目录)
  2. 将完整内容包裹在 <script>/* ECharts v5 */...内容...</script>
  3. 放置位置:模板的 <!-- SLOT: ECHARTS INLINE --> 处(在 SLOT: SLIDES 之后、base JS 之前)
<!-- 正确:内联(读取 references/echarts.min.js 内容) -->
<script>/* ECharts v5 - inline */
...references/echarts.min.js 的完整内容...
</script>

<!-- 错误CDN 引用(禁止) -->
<!-- <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script> -->

容器规范

<div class="chart-container" id="chart1"></div>
.chart-container {
    width: min(90vw, 800px);
    height: min(50vh, 400px);
    margin: 0 auto;
}
@media (max-height: 700px) { .chart-container { height: min(40vh, 300px); } }
@media (max-height: 500px) { .chart-container { height: min(35vh, 250px); } }

初始化模式

图表必须在 slide 可见时初始化,否则容器尺寸为 0

const chartObserver = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const charts = entry.target.querySelectorAll('.chart-container[data-chart]');
            charts.forEach(el => {
                if (!el._echartInstance) {
                    const chart = echarts.init(el);
                    chart.setOption(JSON.parse(el.dataset.chart));
                    el._echartInstance = chart;
                }
            });
        }
    });
}, { threshold: 0.3 });

窗口 resize

window.addEventListener('resize', () => {
    document.querySelectorAll('.chart-container').forEach(el => {
        if (el._echartInstance) el._echartInstance.resize();
    });
});

中文数字格式化工具

每个包含数据的演示必须内嵌:

function formatCN(num, decimals = 1) {
    if (num === null || num === undefined) return '--';
    const abs = Math.abs(num);
    const sign = num < 0 ? '-' : '';
    if (abs >= 1e8) return sign + (abs / 1e8).toFixed(decimals).replace(/\.0$/, '') + '\u4ebf';
    if (abs >= 1e4) return sign + (abs / 1e4).toFixed(decimals).replace(/\.0$/, '') + '\u4e07';
    return sign + abs.toLocaleString('zh-CN');
}

function formatPct(num, decimals = 2) {
    if (num === null || num === undefined) return '--';
    return (num * 100).toFixed(decimals) + '%';
}

function formatDateCN(dateStr) {
    const d = new Date(dateStr);
    return d.getFullYear() + '\u5e74' + (d.getMonth() + 1) + '\u6708' + d.getDate() + '\u65e5';
}

配色适配

function getThemeColors() {
    const s = getComputedStyle(document.documentElement);
    return {
        primary:   s.getPropertyValue('--accent').trim() || '#2563eb',
        secondary: s.getPropertyValue('--accent-secondary').trim() || '#60a5fa',
        negative:  s.getPropertyValue('--color-negative').trim() || '#dc2626',
        positive:  s.getPropertyValue('--color-positive').trim() || '#16a34a',
        text:      s.getPropertyValue('--text-primary').trim() || '#333',
        textSec:   s.getPropertyValue('--text-secondary').trim() || '#666',
        bg:        s.getPropertyValue('--bg-primary').trim() || '#fff'
    };
}

function initThemedChart(el, option) {
    const colors = getThemeColors();
    const chart = echarts.init(el, null, { renderer: 'canvas' });
    option.textStyle = option.textStyle || {};
    option.textStyle.fontFamily = getComputedStyle(document.documentElement)
        .getPropertyValue('--font-body').trim() || 'Noto Sans SC, sans-serif';
    option.textStyle.color = colors.text;
    chart.setOption(option);
    return chart;
}

图表类型速查

要表达什么 推荐图表 次选 配置文件
单指标趋势 面积图 折线图 charts-trend
多指标趋势 柱线混合图 堆叠面积图 charts-trend
增长率趋势 双Y轴折线 面积图 charts-trend
构成占比 (3-6项) 环形饼图 玫瑰图 charts-composition
差异大的占比 玫瑰图 Treemap charts-composition
两级分类构成 嵌套环形图 Treemap charts-composition
三级以上层级 旭日图 Treemap charts-composition
面积=数值的层级 Treemap 旭日图 charts-composition
单一达标率 半环仪表盘 KPI卡片 charts-composition
排名 / Top N 横向条形图 柱状图 charts-flow
多主体多指标对比 分组柱状图 雷达图 charts-trend
构成+时间趋势 堆叠柱状图 堆叠面积图 charts-trend
多维综合评分 雷达图 CSS评分条 charts-matrix
两变量相关性 散点图 热力图 charts-matrix
NxM 矩阵数据 热力图 表格 charts-matrix
相关系数矩阵 相关性热力图 表格 charts-matrix
逐步递减/转化 漏斗图 瀑布图 charts-flow
加减拆解 瀑布图 堆叠柱 charts-flow
流向/流转 桑基图 嵌套环形 charts-flow
股价走势 K线图 面积折线 charts-flow

Inline SVG (零依赖)

不引入 ECharts 时,用 SVG 实现简单图表。

简单柱状图

<svg class="chart-svg" viewBox="0 0 400 200" preserveAspectRatio="xMidYMid meet">
    <style>
        .bar { transition: height 0.6s var(--ease-out-expo), y 0.6s var(--ease-out-expo); }
        .bar-label { font-family: var(--font-body); font-size: 11px; fill: var(--text-secondary); text-anchor: middle; }
        .bar-value { font-family: var(--font-body); font-size: 10px; fill: var(--text-primary); text-anchor: middle; }
    </style>
    <line x1="50" y1="10" x2="50" y2="170" stroke="var(--text-secondary)" stroke-width="0.5"/>
    <rect class="bar" x="70" y="50" width="40" height="120" fill="var(--accent)" rx="2"/>
    <rect class="bar" x="130" y="80" width="40" height="90" fill="var(--accent)" rx="2" opacity="0.7"/>
    <rect class="bar" x="190" y="30" width="40" height="140" fill="var(--accent)" rx="2"/>
    <rect class="bar" x="250" y="60" width="40" height="110" fill="var(--accent)" rx="2" opacity="0.7"/>
    <text class="bar-label" x="90" y="190">Q1</text>
    <text class="bar-label" x="150" y="190">Q2</text>
    <text class="bar-label" x="210" y="190">Q3</text>
    <text class="bar-label" x="270" y="190">Q4</text>
    <text class="bar-value" x="90" y="45">1.2亿</text>
    <text class="bar-value" x="150" y="75">0.9亿</text>
    <text class="bar-value" x="210" y="25">1.4亿</text>
    <text class="bar-value" x="270" y="55">1.1亿</text>
</svg>

简单折线图

<svg class="chart-svg" viewBox="0 0 400 200" preserveAspectRatio="xMidYMid meet">
    <defs>
        <linearGradient id="lineGrad" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stop-color="var(--accent)" stop-opacity="0.3"/>
            <stop offset="100%" stop-color="var(--accent)" stop-opacity="0"/>
        </linearGradient>
    </defs>
    <path d="M50,140 L120,100 L190,110 L260,60 L330,40 L330,170 L50,170 Z" fill="url(#lineGrad)"/>
    <polyline points="50,140 120,100 190,110 260,60 330,40"
              fill="none" stroke="var(--accent)" stroke-width="2"
              stroke-dasharray="500" stroke-dashoffset="500">
        <animate attributeName="stroke-dashoffset" to="0" dur="1.5s" fill="freeze"
                 begin="0.3s" calcMode="spline" keySplines="0.16 1 0.3 1"/>
    </polyline>
    <circle cx="50" cy="140" r="3" fill="var(--accent)"/>
    <circle cx="120" cy="100" r="3" fill="var(--accent)"/>
    <circle cx="190" cy="110" r="3" fill="var(--accent)"/>
    <circle cx="260" cy="60" r="3" fill="var(--accent)"/>
    <circle cx="330" cy="40" r="3" fill="var(--accent)"/>
</svg>
.chart-svg {
    width: min(90vw, 600px);
    height: min(40vh, 300px);
    display: block;
    margin: 0 auto;
}