Files
MorphDoc/packages/markdown-echarts/README.md
T
SkyJourney 58087d0c7e release: 发布 v0.5.0
新增共享 Preview Engine,统一 Web 连续预览、快速分页、Playwright PDF 与 Electron PDF;实现稳定前缀复用和修改位置后的增量分页,保留媒体块按文档顺序串行回填与单次重排。

完善跨端链接与桌面文档工作流:Web 受控处理锚点和 HTTP/HTTPS 外链;Desktop 支持本地路径、file URI、系统协议、多窗口、同文件单例、Markdown 当前或新窗口打开,以及聚焦时外部文件变化提示。

统一四套内置主题名称并默认使用 Typora Github;修复连续预览双滚动条、ECharts 尺寸、PDF 本地链接、围栏代码块 Typora DOM 与重复行内样式;桌面发行链强制完整重建内嵌 Web,避免安装包携带陈旧资源。

发布 Web/Compose 与 Windows NSIS/ZIP:镜像 yixiong/md-to-pdf:v0.5.0 已健康部署;NSIS SHA-256 为 60992D1FDCA513F46346C78478537EB4159D8C0E76B41ECF3CDC25BE77707D92,ZIP SHA-256 为 D74F82293FB67126E583546CBA894569EFC9A0B1B6343FAC648CCC95F1D188D8,本机安装版已升级至 v0.5.0。

验证:全项目 238 项测试通过,类型检查、生产构建和 git diff --check 通过;Web 快速/连续/精确预览、Compose、Desktop 多窗口、窗口状态、文件关联、链接与代码块均完成真实环境验收。
2026-07-28 18:01:22 +08:00

328 lines
9.9 KiB
Markdown
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.
# @md-to-pdf/markdown-echarts
为 Markdown 增加安全、可验证、可复用的 ECharts YAML 围栏,
并在浏览器中渲染为 SVG。
该包不依赖 React、Paged.js 或特定 PDF 引擎。宿主可以将生成的
HTML 用于网页预览,也可以在图表完成渲染后交给 Chromium 输出 PDF。
本项目由 `@md-to-pdf/renderer` 接入围栏插件,再由
`@md-to-pdf/preview-engine` 统一完成连续预览、分页适配和 SVG 冻结。
## 目录结构
```text
src/
markdown-it-plugin.ts ECharts YAML 围栏与错误占位
parse-yaml.ts 受限 YAML 解析
protocol.ts 围栏协议与基础校验
security-policy.ts URL、函数、原型和规模安全策略
semantic-validator.ts 系列数据与坐标系语义校验
dom-contract.ts 宿主与浏览器运行时共用的 DOM 协议
browser.ts 浏览器入口
browser/ ECharts 按需加载、渲染和 SVG 冻结
index.ts Markdown 侧公共导出
styles/
echarts.css 图表、标题和错误占位样式
tests/ 解析、安全、语义、插件和真实 SVG 测试
```
## 基本语法
````markdown
```echarts
height: 80mm
caption: 年度收入
option:
xAxis:
type: category
data: [2023, 2024]
yAxis:
type: value
series:
- type: bar
data: [120, 180]
```
````
围栏内容必须使用 YAML,并包含一个 ECharts `option` 对象。YAML
会先转换为安全的纯数据对象,再交给 ECharts;不能使用函数、
YAML 锚点、别名或自定义标签。
## 围栏字段
| 字段 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `version` | `1` | `1` | 围栏协议版本 |
| `height` | CSS 长度 | `80mm` | 支持 `mm`、`cm`、`in`、`px` |
| `aspectRatio` | 正数 | 无 | 与 `height` 二选一;指定后不补默认高度 |
| `theme` | `document`、`light`、`dark` | `document` | ECharts 主题 |
| `caption` | 字符串 | 无 | 图表标题说明 |
| `option` | 对象 | 必填 | 经过安全和语义验证的 ECharts 配置 |
## 数据来源
每个系列必须从以下任一位置获得有效数据:
- 非空的 `series[].data`
- 非空的 `dataset.source`
- 引用具有有效上游数据源的内置 `filter` 或 `sort` transform。
只有表头的二维数组、所有列均为空的对象以及空数组不属于有效
数据。使用 `dataset` 时保留 ECharts 的默认维度映射,也可以通过
`series.encode` 显式声明映射。
```yaml
option:
dataset:
source:
- [year, sales]
- [2025, 120]
- [2026, 180]
series:
- type: line
encode:
x: year
y: sales
```
## 系列验证规则
当前支持以下系列:
| 系列 | 主要规则 |
| --- | --- |
| `line` | 需要有效数据;支持直角坐标系和极坐标系 |
| `bar` | 需要有效数据;支持直角坐标系和极坐标系 |
| `scatter` | 直接数据至少包含两个坐标值 |
| `pie` | 直接数据项的 `value` 必须是有限数值 |
| `radar` | 必须提供非空 `radar.indicator`;数据维数不得少于指标数 |
| `heatmap` | 直角坐标数据为 `[x, y, value]`;日历数据为 `[date, value]` |
| `boxplot` | 直接数据为 `[min, Q1, median, Q3, max]` |
| `candlestick` | 直接数据为 `[open, close, lowest, highest]` |
| `tree` | 必须提供非空层级 `data`;递归校验 `children` |
| `treemap` | 必须提供非空层级 `data`;叶节点必须具有有限数值 |
| `sunburst` | 必须提供非空层级 `data`;叶节点必须具有有限数值 |
| `graph` | 必须提供 `data` 或 `nodes`;校验边引用;缺少坐标时默认 `force` 布局 |
| `sankey` | 必须提供节点和关系边;边权重必须是非负有限数值 |
| `chord` | 使用 ECharts 6 和弦图;必须提供节点、关系边和非负权重 |
| `funnel` | 数据项必须是有限数值,或使用有效 `dataset` |
| `gauge` | 数据项必须是有限数值,或使用有效 `dataset` |
| `pictorialBar` | 需要有效数据和直角坐标系;允许安全的 `path://` 内嵌矢量符号 |
直角坐标系列缺少 `xAxis` 或 `yAxis` 时会补充空对象,让 ECharts
使用自身的轴默认值。极坐标、雷达和日历坐标系表达了明确语义,
因此不会猜测配置,缺少对应组件时会报告错误。
使用 `dataset` 时,验证器只验证数据源是否存在,不重复实现
ECharts 的维度推断和 `encode` 映射规则。
## 第二阶段系列示例
### 层级图
```yaml
height: 60mm
option:
series:
- type: treemap
nodeClick: false
breadcrumb:
show: false
label:
show: true
formatter: "{b}"
color: "#1f2937"
fontWeight: bold
backgroundColor: "rgba(255, 255, 255, 0.88)"
borderRadius: 3
padding: [3, 6]
itemStyle:
borderColor: "#ffffff"
borderWidth: 2
gapWidth: 2
levels:
- itemStyle:
borderWidth: 0
gapWidth: 2
- upperLabel:
show: true
height: 28
color: "#1f2937"
fontWeight: bold
backgroundColor: "rgba(255, 255, 255, 0.88)"
padding: [3, 6]
itemStyle:
borderColor: "#ffffff"
borderWidth: 2
gapWidth: 2
- label:
show: true
formatter: "{b}"
color: "#1f2937"
fontWeight: bold
backgroundColor: "rgba(255, 255, 255, 0.88)"
borderRadius: 3
padding: [3, 6]
itemStyle:
borderColor: "#ffffff"
borderWidth: 2
gapWidth: 2
data:
- name: 渲染
children:
- { name: Markdown, value: 32 }
- { name: ECharts, value: 24 }
- name: 导出
children:
- { name: PDF, value: 28 }
```
`tree`、`treemap` 和 `sunburst` 共用递归 `children` 结构。
`treemap` 与 `sunburst` 的叶节点需要数值;父节点可以省略数值,
由 ECharts 根据子节点汇总。
### 关系图
```yaml
height: 60mm
option:
series:
- type: sankey
data:
- { name: Markdown }
- { name: HTML }
- { name: PDF }
links:
- { source: Markdown, target: HTML, value: 100 }
- { source: HTML, target: PDF, value: 40 }
```
`graph` 的关系边可以省略;`sankey` 和 ECharts 6 `chord` 必须
具有非空 `links` 或 `edges`。`source`、`target` 必须引用节点
名称、ID 或数组索引。
### 业务图表
```yaml
height: 60mm
option:
series:
- type: gauge
min: 0
max: 100
progress:
show: true
detail:
formatter: "{value}%"
data:
- { name: 完成率, value: 78 }
```
`funnel` 和 `gauge` 使用有限数值数据;`pictorialBar` 复用柱图
坐标系和数据规则,并允许不加载外部资源的 `path://` SVG 路径。
## 错误行为
配置错误不会中断整篇 Markdown。对应围栏会变成局部错误占位:
```html
<div class="md-echarts-error" role="alert">
ECharts 图表配置无效:option.series.0.data 必须是非空数组……
</div>
```
错误消息包含配置路径,便于用户或 AI Agent 定位并修正 YAML。
宿主还可以通过 `onError` 收集结构化错误。
```ts
markdown.use(markdownItECharts, {
onError(error, context) {
console.warn(error.code, error.message, context.tokenIndex);
}
});
```
## Markdown-it 接入
```ts
import MarkdownIt from "markdown-it";
import {
markdownItECharts
} from "@md-to-pdf/markdown-echarts";
const markdown = new MarkdownIt().use(markdownItECharts);
const html = markdown.render(source);
```
如果宿主还会对 HTML 做安全过滤,需要保留包生成的 `figure`、
`figcaption`、隐藏配置节点及 `data-echarts-*` 属性。
## 浏览器渲染
```ts
import {
renderEChartsBlocks
} from "@md-to-pdf/markdown-echarts/browser";
import "@md-to-pdf/markdown-echarts/styles.css";
const outcomes = await renderEChartsBlocks(document, {
outputMode: "inline-svg",
concurrency: 2,
timeoutMs: 5000
});
```
输出模式:
- `interactive`:保留 ECharts 实例和响应式尺寸监听;
- `inline-svg`:冻结为内联 SVG,适合打印和 PDF;
- `svg-image`:冻结为 SVG Data URL 图片。
PDF 输出前应等待所有图表、字体和图片完成,再开始分页或调用
Chromium `page.pdf()`。
浏览器引擎会强制关闭全局及各系列动画,Markdown 中的动画参数不会改变
静态 SVG、快速预览、精确预览或 PDF,避免输出停留在动画中间帧。
ECharts 围栏使用专用 `figure.md-echarts` DOM,不会套用普通代码围栏的
`pre.md-fences` 样式。
## 安全边界
默认策略会拒绝:
- `custom` 系列及 `renderItem`
- JavaScript、HTTP、文件和 Data URL 外部资源;
- HTML formatter
- 原型污染属性;
- 未允许的 toolbox 功能;
- 非内置数据转换;
- 超过节点数、深度、字符串、数组、系列、层级节点或关系边数量
上限的配置。
这些限制适用于不受信任的 Markdown。宿主如需扩大能力,应在明确
风险后扩展验证器,而不是直接执行 YAML 中的 JavaScript。
## 扩展系列验证
各系列规则注册在 `echartsSeriesValidators`。新增图形时需要同步:
1. 将系列类型加入协议支持列表;
2. 在浏览器引擎中注册对应 ECharts Chart
3. 注册该系列的语义验证器;
4. 增加有效、缺少数据、错误数据结构和真实 SVG 渲染测试。
## 开发与验证
```powershell
npm run dev -w @md-to-pdf/markdown-echarts
npm run test -w @md-to-pdf/markdown-echarts
npm run typecheck -w @md-to-pdf/markdown-echarts
npm run build -w @md-to-pdf/markdown-echarts
```
参考:
- [ECharts 配置参考](https://echarts.apache.org/en/option.html)
- [ECharts Dataset](https://echarts.apache.org/handbook/en/concepts/dataset/)
- [ECharts Data Transform](https://echarts.apache.org/handbook/en/concepts/data-transform/)