Office中有简单的自定义图表,但很多时候无法满足使用的要求。地图、关系图、坐标图等复杂的表无法绘制,图表中数据点拖拽、动态数据、数据在展示中的自由筛选等交互无法完成。本文通过ECharts在PPT中引入更现代的图表功能,让图标更加丰富、美观、可交互。关于图表的基本使用,文中提供了关系图可拖拽版本的实现,方便了复杂关系的显示。

基础思路

这篇文章的基本问题是这个:

我需要在PPT中展示一个非常复杂的关系,图包含大量节点和连线。关系图可以根据演示的情况实时变化,方便讨论。

所以大致思路是这样的,在PPT中添加网页控件,在网页控件中通过ECharts绘制需要的图形。过程中碰到的主要问题有:

  • PPT在插入控件、运行控件时产生不必要的提示
  • ECharts图表未提供符合要求的拖拽,拖拽后产生回弹或整体重排

完成后的效果可以见这里

为了方便测试,这里提供了完整项目的下载,需注意项目中未包括基础HTML一节中应下载的echarts-en.js,额外下载即可。

PPT设置

PPT的设置为,添加控件、控件自动运行、消除不必要提示,最终达到与原生表格没有差别的体验。

为了方便下面的叙述,以下内容均运行在Windows 10,Office 2016,文件列表如下:

1
2
3
4
5
drag-demo
|- main.pptm
|- main.html
|- main.js
`- echarts-en

添加控件

在开发工具一栏中,选择其他控件,其中可以找到Microsoft Web Browser,点击确定。

但因为ActiveX的关系,这边一般都会提示,“无法插入此ActiveX控件”。

为了解决问题,需要更改如下注册表(Win+R regedit),允许插入控件。如下键值若没有,新建即可,全部改为0。Office如果不是16的话根据Office版本更改最后一条。

1
2
3
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\ActiveX Compatibility\{8856F961-340A-11D0-A96B-00C04FD705A2}\Compatibility Flags
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Internet Explorer\ActiveX Compatibility\{8856F961-340A-11D0-A96B-00C04FD705A2}\Compatibility Flags
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\REGISTRY\MACHINE\Software\Microsoft\Office\16.0\Common\COM Compatibility\{8856F961-340A-11D0-A96B-00C04FD705A2}\Compatibility Flags

由于IE版本可能不同,这里还需要限制Web Browser使用特定版本的IE,将下面的键值改为十进制的10001。

1
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION\PowerPnt.exe

以上内容参考该指南,如果设置了还是不能成功插入可以进一步阅读指南。

控件自动运行

双击该控件可以打开VBA的编辑窗口,将如下代码添加到最开头。

1
2
3
4
5
6
Sub OnSlideShowPageChange()
Select Case ActivePresentation.SlideShowWindow.View.CurrentShowPosition
Case 1
WebBrowser1.Navigate (ActivePresentation.Path + "\main.html")
End Select
End Sub

这就是一个简单的VBA,每次切换页面都会调用OnSlideShowPageChange函数,在该函数中进行判断,若页码为Web Browser所在的页码则将页面转到本地的网页主页。

消除不必要提醒

即使这样操作后,网页主页加载本地的JS还是会提示,“为帮助保护你的安全,你的Web浏览器已经限制此文件现实可能访问你的计算机的活动内容。单击此处查看选项”。虽然所有JS写进网页里也是个办法,但终究不方便。这里可以通过在HTML中DOCTYPE后加入如下的一行,规避这一提醒。

1
<!-- saved from url=(0013)about:internet -->

全部完成之后由于使用的VBA,需要将PPT保存为“启用宏的PowerPoint演示文稿”(main.pptm),另存即可。

ECharts设置

ECharts设置为基础HTML,生成关系图,添加拖拽。

由于IE的兼容性问题,以下的代码都经过了一些例如用var替换const的操作,这也没办法。如果你知道如何将Chrome作为控件加入PPT,请一定留言告诉我。

基础HTML

基础的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<!-- saved from url=(0013)about:internet -->
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="echarts" style="width:100%;height:800px;"></div>
<script src="echarts-en.js"></script>
<script src="main.js"></script>
</body>
</html>

body中添加一个div用于显示图表,之后引入ECharts和配置文件。

echarts-en.js是ECharts包,为了方便离线使用,这里下载到了本地(下载地址见这里)。

生成关系图

ECharts的图形生成是通过设置Option字典完成的,也就是根据文档创建一个Option字典,通过chart.setOption(option)完成设置。

关系图对应的是series项下类型为graph的内容,代码如下:

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
var BASE_GRAPH_ID = 'relationship'
var chart = echarts.init(document.getElementById('echarts'));

var nodes = [
{ name: '节点1', value: [100, 200] },
{ name: '节点2', value: [300, 200] },
{ name: '节点3', value: [200, 100] },
{ name: '节点4', value: [200, 300] },
]

var links = [
[ [0, 1], '关系1', 0.2 ],
[ [1, 0], '关系2', 0.2 ],
[ [0, 2] ], [ [1, 2] ], [ [1, 3] ], [ [0, 3] ],
];

links = echarts.util.map(links, function(l) {
return {
source: l[0][0],
target: l[0][1],
label: l[1]?{
show: true,
formatter: l[1],
}:{},
lineStyle: l[2]?{ curveness: l[2] }:{}
}
})

var dataOption = {
title: { text: '可拖拽关系图' },
animation: false,
series : [{
id: BASE_GRAPH_ID,
type: 'graph',
coordinateSystem: 'cartesian2d',
symbolSize: 50,
label: { show: true },
edgeSymbol: ['none', 'arrow'],
edgeSymbolSize: [0, 10],
data: nodes,
links: links,
lineStyle: { width: 2 }
}],
xAxis: {
min: 0,
max: 400,
type: 'value',
show: false,
},
yAxis: {
min: 0,
max: 400,
type: 'value',
show: false,
},
};

chart.setOption(dataOption);

添加拖拽

由于其他布局都存在回弹或者自动重新排列的问题,这里没办法使用force布局提供的拖拽。也无法使用默认布局,默认布局在拖拽一个节点后其他节点会重新排布。所以最后我们选择坐标系布局,每一个节点预先设置位置,在坐标数量多的情况下也可以随机数或者借助另一个默认布局,确定节点的位置。

二维坐标的关系图节点没有默认的拖拽,而一般的图形均有拖拽的默认选项。所以实现的思路为,在关系图每一个节点上覆盖一个透明的可拖拽图形,拖拽透明图形的时候同步更改节点的位置。

实现代码如下,需要复制在上述代码后面:

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
var dragOption = {
graphic: echarts.util.map(nodes, function (item, i) {
return {
type: 'circle',
shape: { r: 25 },
position: chart.convertToPixel({seriesId: BASE_GRAPH_ID}, item.value),
invisible: true,
draggable: true,
z: 100,
ondrag: echarts.util.curry(onPointDragging, i),
}
}),
};

function onPointDragging(index) {
newPosition = chart.convertFromPixel({seriesId: BASE_GRAPH_ID}, this.position);
nodes[index].value = newPosition;
chart.setOption({
series: [{
id: BASE_GRAPH_ID,
data: nodes
}]
});
}

chart.setOption(dragOption);

其中涉及的一个问题是关系图的布局和单独图形的坐标系统不同,同样的[100, 100]在两个坐标系统中位置是不一样的。单独图形是直接根据像素确定的,关系图则有图形专门的坐标系统。通过convertToPixelconvertFromPixel可以完成转换。

我这里是使用Chrome完成的Debug,全部结束之后运行main.pptm,再放映幻灯片。