在 Markdown 中无缝集成 ECharts 图表的技术实践

引言

在现代 Web 开发中,数据可视化已成为不可或缺的一部分。ECharts 作为百度开源的一款优秀的数据可视化库,以其丰富的图表类型和灵活的配置选项广受欢迎。而 Markdown 作为一种轻量级标记语言,因其简洁易用的特性,被广泛应用于文档编写和技术博客中。本文将介绍如何实现在 Markdown 文档中无缝嵌入 ECharts 图表的技术方案。

技术背景

Markdown 及其扩展性

Markdown 最初由 John Gruber 创建,旨在实现” 易读易写” 的纯文本格式。随着发展,Markdown 通过扩展语法支持了更多功能,如代码块、表格等。其中,代码块语法通常用于展示代码片段:

1
2
3
``` language
code here
```

这种语法为我们嵌入 ECharts 图表提供了天然的接口。

ECharts 简介

ECharts 是一个使用 JavaScript 实现的开源可视化库,可以流畅运行在 PC 和移动设备上,兼容当前绝大部分浏览器。它提供了直观、交互丰富、可高度个性化定制的数据可视化图表。

实现方案

核心思路

我们的目标是在 Markdown 文档中通过特定语法嵌入 ECharts 图表配置,并在渲染时自动将其转换为交互式图表。具体实现步骤如下:

  1. 识别 Markdown 中的 ECharts 代码块
  2. 解析其中的 JSON 配置
  3. 动态创建图表容器
  4. 初始化 ECharts 实例并应用配置
  5. 处理响应式布局

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const renderer = new marked.Renderer();
renderer.code = ({ lang, text }) => {
if (lang === 'echarts') {
try {
const chartData = JSON.parse(text.trim()); // 使用trim()移除可能的空白字符
const randomId = 'echart-' + Math.random().toString(36).substr(2, 9);

setTimeout(() => {
if (typeof echarts !== 'undefined') {
const chart = echarts.init(document.getElementById(randomId));
chart.setOption(chartData);

// 响应式调整大小
window.addEventListener('resize', function() {
chart.resize();
});
} else {
console.error('ECharts is not loaded');
}
}, 0);

return `<div id="${randomId}" style="width: 100%; height: 400px;"></div>`;
} catch (e) {
console.error('解析ECharts数据失败:', e);
return `<pre><code>${text}</code></pre>`;
}
} else {
return `<pre><code>${text}</code></pre>`;
}
};

关键点解析

  1. 自定义 Markdown 渲染器:通过扩展 marked.js 的渲染器,我们能够自定义代码块的渲染逻辑。

  2. ECharts 代码块识别:当代码块语言标记为 echarts 时,我们将其内容视为 ECharts 配置。

  3. JSON 配置解析:使用 JSON.parse 解析代码块内容,确保其是有效的 ECharts 配置。

  4. 动态容器创建:为每个图表生成唯一 ID,并创建相应大小的容器 div。

  5. 异步初始化:使用 setTimeout 确保 DOM 加载完成后再初始化图表。

  6. 响应式处理:监听窗口大小变化事件,自动调整图表尺寸。

使用示例

在 Markdown 文档中嵌入 ECharts 图表非常简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 标题一

## 标题二

### 标题三

#### 标题四-echarts图

``` echarts
{
"title": {
"text": "销售趋势图"
},
"xAxis": {
"type": "category",
"data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
},
"yAxis": {
"type": "value"
},
"series": [{
"data": [150, 230, 224, 218, 135, 147, 260],
"type": "line"
}]
}
```

渲染后将自动显示一个折线图,并随窗口大小变化自动调整。

EChartsInMarkdown示例渲染图片

完整示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ECharts In MarkDwon</title>
</head>

<body>
<div id="responseContent"></div>
<!-- 引入 marked.js 库用于 Markdown 渲染 -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- 引入 ECharts 库 -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<script>
const renderer = new marked.Renderer();
renderer.code = ({ lang, text }) => {
if (lang === 'echarts') {
try {
const chartData = JSON.parse(text.trim()); // 使用trim()移除可能的空白字符
const randomId = 'echart-' + Math.random().toString(36).substr(2, 9);

setTimeout(() => {
if (typeof echarts !== 'undefined') {
const chart = echarts.init(document.getElementById(randomId));
chart.setOption(chartData);

// 响应式调整大小
window.addEventListener('resize', function() {
chart.resize();
});
} else {
console.error('ECharts is not loaded');
}
}, 0);

return `<div id="${randomId}" style="width: 100%; height: 400px;"></div>`;
} catch (e) {
console.error('解析ECharts数据失败:', e);
return `<pre><code>${text}</code></pre>`;
}
} else {
return `<pre><code>${text}</code></pre>`;
}
};

marked.setOptions({
renderer: renderer,
highlight: function (code, lang) {
return code;
}
});

let markDownStr = "# 标题一\n\n## 标题二\n\n### 标题三\n\n#### 标题四-echarts图\n\n```echarts\n{\"xAxis\":{\"type\":\"category\",\"data\":[\"Mon\",\"Tue\",\"Wed\",\"Thu\",\"Fri\",\"Sat\",\"Sun\"]},\"yAxis\":{\"type\":\"value\"},\"series\":[{\"data\":[150,230,224,218,135,147,260],\"type\":\"line\"}]}\n```";
console.log(markDownStr);
const responseContentEl = document.getElementById('responseContent');
responseContentEl.innerHTML = marked.parse(markDownStr);
</script>
</body>

</html>

技术优势

  1. 无缝集成:保持 Markdown 简洁性的同时增加可视化能力
  2. 开发友好:前端开发者熟悉的 JSON 配置方式
  3. 灵活性高:支持 ECharts 所有图表类型和配置选项
  4. 响应式设计:自动适应不同屏幕尺寸
  5. 错误处理:配置解析失败时优雅降级为代码块显示

潜在问题与解决方案

  1. JSON 解析错误:通过 try-catch 捕获异常,失败时回退为普通代码块显示
  2. ECharts 未加载:检查全局变量是否存在,给出明确错误提示
  3. 性能考虑:大量图表时可能影响性能,建议按需加载或虚拟滚动
  4. 安全性:确保 JSON.parse 的内容来自可信源,避免 XSS 攻击

扩展可能性

  1. 主题支持:通过额外参数指定图表主题
  2. 数据动态加载:支持从 URL 加载数据配置
  3. 交互事件:暴露图表事件供外部使用
  4. 服务端渲染:生成静态图片作为 fallback

结论

通过在 Markdown 中嵌入 ECharts 图表的技术方案,我们成功地将数据可视化能力无缝集成到文档编写流程中。这种方法既保留了 Markdown 的简洁性,又充分利用了 ECharts 强大的可视化功能,特别适合技术文档、数据分析报告等需要结合文字说明和数据展示的场景。

实现的核心在于灵活运用 Markdown 解析器的扩展能力和 ECharts 的动态初始化机制。随着 Web 技术的不断发展,这种轻量级集成方案将为内容创作者提供更强大的表达工具。