Files
MorphDoc/packages/markdown-echarts
SkyJourney 925b0d1485 release: 发布 v0.4.5
新增能力:Web 与 Desktop 支持新建和保存 Markdown;桌面端注册 .md/.markdown 打开程序,支持单实例文件转交、自定义主题目录以及窗口位置、尺寸和最大化状态的延迟原子持久化与屏幕越界修正。

界面与兼容性:保留打开 Markdown 为独立主操作,将新建、保存和主题目录收纳到更多菜单;Web 移除本地素材目录入口并明确默认不支持本地素材;新文档默认使用一级标题或首行合法化生成文件名,可不保存直接导出 PDF。

渲染修复:图片、Mermaid 和 ECharts 按文档顺序串行执行媒体分页回填,每个媒体元素至多重排一次;图片以限宽后的高度作为缩放基准,只缩放媒体主体并保持标题尺寸;ECharts 冻结为 SVG 图片后参与统一分页。

示例与文档:默认示例仅保留一张网络图片,增加有效 Base64 横图、竖图、边界图片及多尺寸 Mermaid/ECharts;更新根 README、Desktop README、部署文档、四个 packages README、PROGRESS 和 AGENTS 发布规范;登记 v0.4.6 超链接导航问题。

部署与验证:构建并运行 yixiong/md-to-pdf:v0.4.5 正式镜像,Compose 健康且 PDF 冒烟无 Mermaid/ECharts 错误;生成 NSIS 安装包和免安装 ZIP,本机已升级到 NSIS v0.4.5 并清理旧 Squirrel 安装;202 项测试、类型检查、生产构建和 git diff --check 全部通过。

发布产物:Setup.exe SHA-256 9CBD6362E15AA745185805E753D5A7749434E0E142E6CE0F66463E6A19468F1D;ZIP SHA-256 97B2D44E72F626A3396E5AB5B330FFB7F9BECAF945A1707808C876940A82E850;当前 Windows 产物未配置代码签名。
2026-07-28 13:31:42 +08:00
..
2026-07-28 13:31:42 +08:00

@md-to-pdf/markdown-echarts

为 Markdown 增加安全、可验证、可复用的 ECharts YAML 围栏, 并在浏览器中渲染为 SVG。

该包不依赖 React、Paged.js 或特定 PDF 引擎。宿主可以将生成的 HTML 用于网页预览,也可以在图表完成渲染后交给 Chromium 输出 PDF。

目录结构

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 测试

基本语法

```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 支持 mmcminpx
aspectRatio 正数 height 二选一;指定后不补默认高度
theme documentlightdark document ECharts 主题
caption 字符串 图表标题说明
option 对象 必填 经过安全和语义验证的 ECharts 配置

数据来源

每个系列必须从以下任一位置获得有效数据:

  • 非空的 series[].data
  • 非空的 dataset.source
  • 引用具有有效上游数据源的内置 filtersort transform。

只有表头的二维数组、所有列均为空的对象以及空数组不属于有效 数据。使用 dataset 时保留 ECharts 的默认维度映射,也可以通过 series.encode 显式声明映射。

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 必须提供 datanodes;校验边引用;缺少坐标时默认 force 布局
sankey 必须提供节点和关系边;边权重必须是非负有限数值
chord 使用 ECharts 6 和弦图;必须提供节点、关系边和非负权重
funnel 数据项必须是有限数值,或使用有效 dataset
gauge 数据项必须是有限数值,或使用有效 dataset
pictorialBar 需要有效数据和直角坐标系;允许安全的 path:// 内嵌矢量符号

直角坐标系列缺少 xAxisyAxis 时会补充空对象,让 ECharts 使用自身的轴默认值。极坐标、雷达和日历坐标系表达了明确语义, 因此不会猜测配置,缺少对应组件时会报告错误。

使用 dataset 时,验证器只验证数据源是否存在,不重复实现 ECharts 的维度推断和 encode 映射规则。

第二阶段系列示例

层级图

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 }

treetreemapsunburst 共用递归 children 结构。 treemapsunburst 的叶节点需要数值;父节点可以省略数值, 由 ECharts 根据子节点汇总。

关系图

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 必须 具有非空 linksedgessourcetarget 必须引用节点 名称、ID 或数组索引。

业务图表

height: 60mm
option:
  series:
    - type: gauge
      min: 0
      max: 100
      progress:
        show: true
      detail:
        formatter: "{value}%"
      data:
        - { name: 完成率, value: 78 }

funnelgauge 使用有限数值数据;pictorialBar 复用柱图 坐标系和数据规则,并允许不加载外部资源的 path:// SVG 路径。

错误行为

配置错误不会中断整篇 Markdown。对应围栏会变成局部错误占位:

<div class="md-echarts-error" role="alert">
  ECharts 图表配置无效:option.series.0.data 必须是非空数组……
</div>

错误消息包含配置路径,便于用户或 AI Agent 定位并修正 YAML。 宿主还可以通过 onError 收集结构化错误。

markdown.use(markdownItECharts, {
  onError(error, context) {
    console.warn(error.code, error.message, context.tokenIndex);
  }
});

Markdown-it 接入

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 做安全过滤,需要保留包生成的 figurefigcaption、隐藏配置节点及 data-echarts-* 属性。

浏览器渲染

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,避免输出停留在动画中间帧。

安全边界

默认策略会拒绝:

  • custom 系列及 renderItem
  • JavaScript、HTTP、文件和 Data URL 外部资源;
  • HTML formatter
  • 原型污染属性;
  • 未允许的 toolbox 功能;
  • 非内置数据转换;
  • 超过节点数、深度、字符串、数组、系列、层级节点或关系边数量 上限的配置。

这些限制适用于不受信任的 Markdown。宿主如需扩大能力,应在明确 风险后扩展验证器,而不是直接执行 YAML 中的 JavaScript。

扩展系列验证

各系列规则注册在 echartsSeriesValidators。新增图形时需要同步:

  1. 将系列类型加入协议支持列表;
  2. 在浏览器引擎中注册对应 ECharts Chart
  3. 注册该系列的语义验证器;
  4. 增加有效、缺少数据、错误数据结构和真实 SVG 渲染测试。

开发与验证

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

参考: