「概览数据整体,按需关注数据细节」是数据可视化的基本需求。前文讲到的两类图表都是静态图表,数据结构往往只有一层。当你的数据具有多个层次(树形结构),如数据按男女划分之后,对应每个性别,会有进一步的年龄层次的划分,对于各年龄层次,可能还会有城乡户籍、受教育程度的区分,多层次的信息很难在单一的静态图表中展示。
本例中笔者将使用旭日图作为核心图表,展示如何可视化多层次的数据。并且在最后,笔者会做一个扩展,演示如何联动两类型图表,更直观地反映数据特征,这一部分可能会涉及较复杂的编码内容,大家可以选择性学习。
本例的数据由笔者随机生成,最终的页面可以点击链接查看。
正式开始
-
数据获取:
本例的数据也将直接给出,并且这部分数据将不单独保存在其他文件中,而直接写入到代码。本例采用该方法,是因为旭日图的同一层次显示类似于扇形图,数据量并不大,写入代码中不会导致结构过于混乱。本手册面向的是非编码职业的读者,我们采用这个不专业的方法,也是为了省去额外的解释内容,将重点放在如何可视化数据上。
不过,即便数据量不大,该部分仍旧占用了大量的空间。为了节省篇幅,除了最一开始导入数据的步骤,以及最终展示代码全貌外,其余的内容将略去数据部分,望读者注意,避免错误的粘贴导致图表出错。
-
数据分析:
旭日图所需的数据结构,是字典型(数据对的集合)。我们展示一部分的数据,如下所示:
var data = [{ name: '正式平台', value: 9487, children: [{ name: '有地址', value: 9357, children: [{ name: '6选3', value: 2851, children: [{ name: '北京市', value: 2336 },{ name: '山东省', value: 425 },{ name: '天津市', value: 83 },{ name: '上海市', value: 7 }] },{ name:'3+1+2', value:5321 }] }] }]
如果读者觉得直接看这个不甚明了,可以结合下方的图示理解。
graph TB 正式平台-->有地址-->6选3 6选3-->北京市 6选3-->山东省 6选3-->天津市 6选3-->上海市 有地址-->3+1+2
对于任意一个节点,都包含三个信息:[name],[value],[children],分别代表节点名称、节点的值、子节点,其中子节点中又是包含有这三个信息的节点排列。
一个节点如果没有子节点,则不需要填入[children]信息。特别的,对于Echarts,如果一个节点是父节点(存在子节点),可不为其填入[value]值。在绘制图表时,value值将取子节点[value]值之和。
需要注意的是,父节点的[value]值可能不等于子节点的[value]值之和(可能存在数据缺失),所以出现该情况时,务必录入父节点的[value]值,保证图表的正确性。
数据准备全部完成,接下来我们准备绘制初始图表。
-
初始图表:
有了前两个案例的学习,读者应当了解了Echarts代码的一些特点。本节将直接给出最初始图表(带有简单解释),然后逐步美化图表显示,最后增加交互效果。进度会稍有加快,但会更加凸显重点。
-
初始的图表。我们直接给出代码,读者可以将其粘贴到[txt]文件中,保存后修改后缀为[.html],直接打开即可看到当前效果。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>ECharts</title> <!-- 引入 echarts.js --> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts-gl/dist/echarts-gl.min.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts-stat/dist/ecStat.min.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/dist/extension/dataTool.min.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/map/js/china.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/map/js/world.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/dist/extension/bmap.min.js"></script> <script type="text/javascript" src="https://www.jb51.net/jslib/jquery/jquery.min.js"></script> </head> <body> <!-- 为ECharts准备一个具备大小(宽高)的Dom --> <div id="sun1" style="width:100%;height:900px;float:left;"></div> <script type="text/javascript"> // 基于准备好的dom,初始化echarts实例 var myChartSun1 = echarts.init(document.getElementById('sun1')); //旭日图所需数据结构 var data = [{ name: '正式平台', value: 9487, children: [{ name: '有地址', value: 9357, children: [{ name: '6选3', value: 2851, children: [{ name: '北京市', value: 2336 },{ name: '山东省', value: 425 },{ name: '天津市', value: 83 },{ name: '上海市', value: 7 }] }, { name: '3+1+2', value: 5321, children: [{ name: '广东省', value: 2068 },{ name: '福建省', value: 1582, //数据下可设置格式 // itemStyle: { // shadowBlur: 10, // shadowColor: '#99ffff' // }, },{ name: '重庆市', value: 718 },{ name: '江苏省', value: 301 },{ name: '湖北省', value: 257 },{ name: '河北省', value: 222 },{ name: '湖南省', value: 116 },{ name: '辽宁省', value: 57 }] }, { name: '7选3-浙江', value: 28 }, { name: '未改革', value: 1157 , children:[{ name:'安徽省', value: 460 },{ name:'陕西省', value:286 },{ name:'四川省', value:126 },{ name:'新疆', value:114 },{ name:'云南省', value:66 },{ name:'广西', value:34 },{ name:'江西省', value:27 },{ name:'甘肃省', value:17 },{ name:'河南省', value:14 },{ name:'山西省', value:13 }] }] }, { name: '无地址', value: 130 }] }, { name: '培训平台', value: 3215, children: [{ name: '智校测试校', children: [{ name: '培训', value: 1449 }, { name: '排课', value: 1325 }] },{ name:'新课改培训', value: 441 }] }, { name: '测试+演示平台', value: 1046, children: [{ name: '测试', value: 464 }, { name: '演示', value: 582 } ] }, { name: '职教+旧平台', value: 1239 }]; var option = { backgroundColor:'#000', tooltip: { trigger: 'item', formatter: function (params) { return params.name + ' : ' + params.value; } }, visualMap: { min: 0, max: 2000, calculable: true, inRange: { color: ['#50a3ba', '#eac736', '#d94e5d'] }, textStyle: { color: '#fff' } }, series: [{ type: 'sunburst', id:'sunburst1', // highlightPolicy: 'ancestor', data: data, center:['50%','50%'], //旭日图半径90%,中心预留10像素 radius: [10, '90%'], //高亮当前及其子区间 //highlightPolicy: 'descendant', label: { rotate: 'radial', fontSize: 15 } }] }; // 使用刚指定的配置项和数据显示图表。 myChartSun1.setOption(option); </script> </body> </html>
代码中几乎没有新的内容,希望读者可以举一反三,自行了解下每部分代码的效果。当前图表如下:
旭日图本身支持数据下钻,点击任意环形块,都会呈现出以点击节点为根节点(初始节点)的新图:
当前的图表不分主次地呈现了全部数据,如果你将其展示给你的老板,你应该很难凸显出图表想要表达的数据特点。实际上这样的一张图表,大多数人都无法理解它想表达的内容。
接下来笔者将介绍如何美化这张图表,使其能够更鲜明地体现数据。
-
-
逐步美化图表:
-
读者应该能注意到,这张旭日图分四个层次,当前各层次的厚度都相同。通常我们会减少最外层环的厚度(降维效果,将面积信息转化为长度信息),并且增加中心空白(背景黑色)的半径,代码在[series]中添加,新增的代码如下:
series: [{ type: 'sunburst', id:'sunburst1', // highlightPolicy: 'ancestor', data: data, center:['50%','50%'], radius: [10, '90%'], //高亮当前及其子区间 //highlightPolicy: 'descendant', label: { rotate: 'radial', fontSize: 15 }, //levels设置各层次环形宽度 levels: [{}, { //内圈半径 r0: '10%', //外圈半径 r: '30%', itemStyle: { //边框宽度 borderWidth: 2 } }, { r0: '30%', r: '50%', itemStyle: { borderWidth: 2 } }, { r0: '50%', r: '70%', itemStyle: { borderWidth: 2 } }, { r0: '70%', r: '75%', label: { //文字显示在外侧 position: 'outside', //距离环形的距离 padding: 3, }, itemStyle: { borderWidth: 3, //阴影半径 shadowBlur: 10, //阴影颜色,当前是亮色 shadowColor: '#99ffff' } }] }]
我们通过设置内圈外圈的半径,限制每一层环形的宽度。最外层环形仅有5%的宽度,无法容纳文字,所以我们选择将文字内容悬浮于图形之外。并且为了凸显外圈内容,我们增加了阴影[shadow]效果,并且选择亮色,使其能在黑色背景下高亮最外圈内容。
新增代码后的图表效果如下:
-
旭日图支持数据下钻,但除了点击操作外,其他任何操作都不会影响图表显示。我们接下来新增几个焦点动画,使得鼠标悬浮于节点之上时,可以强调当前节点。代码在[series]中添加,我们首先新增能强调当前节点的部分代码:
//选定区域 emphasis: { itemStyle: { //环形颜色 color: 'red', //边框颜色 borderColor: '#fff', shadowBlur: 10, shadowColor: '#99ffff' } }, //高亮区域 highlight: { itemStyle: { color: 'orange', borderColor: '#fff', shadowBlur: 10, shadowColor: '#99ffff' } },
我们分别设置了当前选定区域和高亮区域的焦点效果。你可能会有疑问,这两者的区别在哪里?读者已经清楚了解到,旭日图的数据是树状的层级结构,当用户选中父节点时,Echarts同时会高亮子节点。也就是说,[emphasis]设置的是当前选中区域的样式,[highlight]设置的是子节点区域的样式。
读者可以自行观察交互效果,更加便于理解。当前效果如下图:
-
平心而论,你真的能很快注意到我们当前选定的内容么?很显然是不能的。因为非选定的区域也带有多种颜色,一般读者很难将两者区分开。所以除了强调选定节点,非选定的节点我们需要弱化其显示效果。代码在[series]中添加,新增的代码如下:
//非选定区域 downplay: { itemStyle: { color: '#1e1e1e', shadowBlur: 0, //透明度 opacity: 0.3 }, label: { //透明度 opacity: 0.3 } },
如代码所示,我们降低了非选定区域内容的透明度,调整了区域背景和边框颜色,使得这部分内容不容易被注意到。调整后的图形如下:
我们选择了相同的区域,读者可以对比前后两张图片。很显然,调整后的图片更加凸显重点。并且得益于Echarts出色的动画渐变效果,切换选择区域不会给用户带来很突兀的感觉。
现在给出最终的完整代码,读者可以根据官方文档自行修改,或者使用自己的数据,生成自己的专属图表:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>ECharts</title> <!-- 引入 echarts.js --> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts-gl/dist/echarts-gl.min.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts-stat/dist/ecStat.min.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/dist/extension/dataTool.min.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/map/js/china.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/map/js/world.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/dist/extension/bmap.min.js"></script> <script type="text/javascript" src="https://www.jb51.net/jslib/jquery/jquery.min.js"></script> </head> <body> <!-- 为ECharts准备一个具备大小(宽高)的Dom --> <div id="sun1" style="width:100%;height:900px;float:left;"></div> <script type="text/javascript"> // 基于准备好的dom,初始化echarts实例 var myChartSun1 = echarts.init(document.getElementById('sun1')); //旭日图所需数据结构 var data = [{ name: '正式平台', value: 9487, children: [{ name: '有地址', value: 9357, children: [{ name: '6选3', value: 2851, children: [{ name: '北京市', value: 2336 },{ name: '山东省', value: 425 },{ name: '天津市', value: 83 },{ name: '上海市', value: 7 }] }, { name: '3+1+2', value: 5321, children: [{ name: '广东省', value: 2068 },{ name: '福建省', value: 1582, //数据下可设置格式 // itemStyle: { // shadowBlur: 10, // shadowColor: '#99ffff' // }, },{ name: '重庆市', value: 718 },{ name: '江苏省', value: 301 },{ name: '湖北省', value: 257 },{ name: '河北省', value: 222 },{ name: '湖南省', value: 116 },{ name: '辽宁省', value: 57 }] }, { name: '7选3-浙江', value: 28 }, { name: '未改革', value: 1157 , children:[{ name:'安徽省', value: 460 },{ name:'陕西省', value:286 },{ name:'四川省', value:126 },{ name:'新疆', value:114 },{ name:'云南省', value:66 },{ name:'广西', value:34 },{ name:'江西省', value:27 },{ name:'甘肃省', value:17 },{ name:'河南省', value:14 },{ name:'山西省', value:13 }] }] }, { name: '无地址', value: 130 }] }, { name: '培训平台', value: 3215, children: [{ name: '智校测试校', children: [{ name: '培训', value: 1449 }, { name: '排课', value: 1325 }] },{ name:'新课改培训', value: 441 }] }, { name: '测试+演示平台', value: 1046, children: [{ name: '测试', value: 464 }, { name: '演示', value: 582 } ] }, { name: '职教+旧平台', value: 1239 }]; var option = { backgroundColor:'#000', tooltip: { trigger: 'item', formatter: function (params) { return params.name + ' : ' + params.value; } }, visualMap: { min: 0, max: 2000, calculable: true, inRange: { color: ['#50a3ba', '#eac736', '#d94e5d'] }, textStyle: { color: '#fff' } }, series: [{ type: 'sunburst', id:'sunburst1', // highlightPolicy: 'ancestor', data: data, center:['50%','50%'], radius: [10, '90%'], //高亮当前及其子区间 //highlightPolicy: 'descendant', label: { rotate: 'radial', fontSize: 15 }, //选定区域 emphasis: { itemStyle: { //环形颜色 color: 'red', //边框颜色 borderColor: '#fff', shadowBlur: 10, shadowColor: '#99ffff' } }, //高亮区域 highlight: { itemStyle: { color: 'orange', borderColor: '#fff', shadowBlur: 10, shadowColor: '#99ffff' } }, //非选定区域 downplay: { itemStyle: { color: '#1e1e1e', shadowBlur: 0, //透明度 opacity: 0.3 }, label: { //透明度 opacity: 0.3 } }, //levels设置各层次环形宽度 levels: [{}, { //内圈半径 r0: '10%', //外圈半径 r: '30%', itemStyle: { //边框宽度 borderWidth: 2 } }, { r0: '30%', r: '50%', itemStyle: { borderWidth: 2 } }, { r0: '50%', r: '70%', itemStyle: { borderWidth: 2 } }, { r0: '70%', r: '75%', label: { //文字显示在外侧 position: 'outside', //距离环形的距离 padding: 3, }, itemStyle: { borderWidth: 3, //阴影半径 shadowBlur: 10, //阴影颜色,当前是亮色 shadowColor: '#99ffff' } }] }] }; // 使用刚指定的配置项和数据显示图表。 myChartSun1.setOption(option); </script> </body> </html>
-
-
动画交互(选学,但无疑是最吸引人的部分):
接下来笔者将介绍本章节最核心的部分。虽然是选学,但是笔者强烈希望读者可以了解这部分内容,并且将其应用于自己的图表中(即使不了解,你也可以通过修改数据使用它)。
旭日图类似于扇形图,实际情况中很多人对这种扇形的面积信息很不敏感,无法从图形中获得各层次节点的数值分布。对于这个需求,直方图更加适合。于是笔者考虑,能否将两个图表组合起来,当用户选择某节点时,通过直方图将子节点的数值信息展示出来。
在本节中,笔者将分三步介绍交互效果的实现过程:
- 在旭日图旁创建一个新的直方图,显示旭日图第一层级的数据;
- 新建一个[mouseover]事件,当用户鼠标悬浮于某节点时,直方图的数据更新为其子节点的[value]值;
- 引入新维度的数据。读者可以结合业务需求选择性学习该部分内容。
接下来我们正式开始。
-
创建直方图:
创建直方图的方式与前文所述的散点图类似,区别是直方图的x轴类目相对较少,这部分不再赘述。我们主要是调整旭日图的显示位置,即令旭日图的容器宽度减半且左移,再右侧新建一个容器,以便展示直方图。此时的完整代码如下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>ECharts</title> <!-- 引入 echarts.js --> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts-gl/dist/echarts-gl.min.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts-stat/dist/ecStat.min.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/dist/extension/dataTool.min.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/map/js/china.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/map/js/world.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/dist/extension/bmap.min.js"></script> <script type="text/javascript" src="https://www.jb51.net/jslib/jquery/jquery.min.js"></script> </head> <body> <!-- 为ECharts准备一个具备大小(宽高)的Dom --> <!-- float设置浮动方式,保证两个div显示在同一排(因为我们的容器宽度是自适应的) --> <div id="sun1" style="width:50%;height:900px;float:left;"></div> <div id="bar1" style="width:50%;height:900px;float:left;"></div> <script type="text/javascript"> // 基于准备好的dom,初始化echarts实例 var myChartSun1 = echarts.init(document.getElementById('sun1')); var myChartBar1 = echarts.init(document.getElementById('bar1')); //旭日图所需数据结构 var data = [{ name: '正式平台', value: 9487, children: [{ name: '有地址', value: 9357, children: [{ name: '6选3', value: 2851, children: [{ name: '北京市', value: 2336 },{ name: '山东省', value: 425 },{ name: '天津市', value: 83 },{ name: '上海市', value: 7 }] }, { name: '3+1+2', value: 5321, children: [{ name: '广东省', value: 2068 },{ name: '福建省', value: 1582, //数据下可设置格式 // itemStyle: { // shadowBlur: 10, // shadowColor: '#99ffff' // }, },{ name: '重庆市', value: 718 },{ name: '江苏省', value: 301 },{ name: '湖北省', value: 257 },{ name: '河北省', value: 222 },{ name: '湖南省', value: 116 },{ name: '辽宁省', value: 57 }] }, { name: '7选3-浙江', value: 28 }, { name: '未改革', value: 1157 , children:[{ name:'安徽省', value: 460 },{ name:'陕西省', value:286 },{ name:'四川省', value:126 },{ name:'新疆', value:114 },{ name:'云南省', value:66 },{ name:'广西', value:34 },{ name:'江西省', value:27 },{ name:'甘肃省', value:17 },{ name:'河南省', value:14 },{ name:'山西省', value:13 }] }] }, { name: '无地址', value: 130 }] }, { name: '培训平台', value: 3215, children: [{ name: '智校测试校', children: [{ name: '培训', value: 1449 }, { name: '排课', value: 1325 }] },{ name:'新课改培训', value: 441 }] }, { name: '测试+演示平台', value: 1046, children: [{ name: '测试', value: 464 }, { name: '演示', value: 582 } ] }, { name: '职教+旧平台', value: 1239 }]; var option = { backgroundColor:'#000', tooltip: { trigger: 'item', formatter: function (params) { return params.name + ' : ' + params.value; } }, visualMap: { min: 0, max: 2000, calculable: true, inRange: { color: ['#50a3ba', '#eac736', '#d94e5d'] }, textStyle: { color: '#fff' } }, series: [{ type: 'sunburst', id:'sunburst1', // highlightPolicy: 'ancestor', data: data, center:['50%','50%'], radius: [10, '90%'], //高亮当前及其子区间 //highlightPolicy: 'descendant', label: { rotate: 'radial', fontSize: 15 }, //选定区域 emphasis: { itemStyle: { //环形颜色 color: 'red', //边框颜色 borderColor: '#fff', shadowBlur: 10, shadowColor: '#99ffff' } }, //高亮区域 highlight: { itemStyle: { color: 'orange', borderColor: '#fff', shadowBlur: 10, shadowColor: '#99ffff' } }, //非选定区域 downplay: { itemStyle: { color: '#1e1e1e', shadowBlur: 0, //透明度 opacity: 0.3 }, label: { //透明度 opacity: 0.3 } }, //levels设置各层次环形宽度 levels: [{}, { //内圈半径 r0: '10%', //外圈半径 r: '30%', itemStyle: { //边框宽度 borderWidth: 2 } }, { r0: '30%', r: '50%', itemStyle: { borderWidth: 2 } }, { r0: '50%', r: '70%', itemStyle: { borderWidth: 2 } }, { r0: '70%', r: '75%', label: { //文字显示在外侧 position: 'outside', //距离环形的距离 padding: 3, }, itemStyle: { borderWidth: 3, //阴影半径 shadowBlur: 10, //阴影颜色,当前是亮色 shadowColor: '#99ffff' } }] }] }; //另一个option,设置直方图的图表信息 var option1 = { backgroundColor:'#000', tooltip: { trigger: 'item', formatter: function (params) { return params.name + ' : ' + params.value; } }, visualMap: { min: 0, max: 2000, calculable: true, show: false, seriesIndex:0, inRange: { color: ['#50a3ba', '#eac736', '#d94e5d'] }, textStyle: { color: '#fff' } }, //柱形图设置 legend:{ data:['调用次数','学校数','真实学校数'], textStyle:{ color:'#fff' }, top: '1%', selected:{ '调用次数':true, '学校数':false, '真实学校数':false } }, xAxis: { type: 'category', data:['正式平台','培训平台','职教+旧平台','测试+演示平台'], axisLine:{ lineStyle:{ type:'solid', color:'#fff', width:1 } }, //旋转90度显示 // axisLabel:{ // rotate:90 // } }, yAxis: [{ name:'调用次数', type: 'value', //坐标内分割线 splitLine:{ lineStyle:{ type:'dashed' } }, axisLine:{ lineStyle:{ color:'#fff', width:1,//这里是为了突出显示加上的 } } },{//是否显示数值分割线 splitLine:{ show:false }, //max:100, axisLine:{ lineStyle:{ color:'#fff', width:1,//这里是为了突出显示加上的 } } }], grid: { left: '10%', right: '10%', bottom: '7%', containLabel: true }, series: { name:'调用次数', id: 'bar1', type: 'bar', //柱形图添加边框及阴影 itemStyle:{ normal:{ borderWidth: 1, borderColor: '#fff', shadowBlur: 5, shadowColor: '#99ffff' } }, data: data } }; // 使用刚指定的配置项和数据显示图表。 myChartSun1.setOption(option); myChartBar1.setOption(option1); </script> </body> </html>
我们创建了初始的直方图,显示在旭日图右方。
Echarts更新动画的方式为重新绘制画布,而重新绘制画布会导致旭日图当前下钻的效果被重置。如果将两个图表放在一个容器中,每次更新直方图,旭日图也会被重置为最初的状态。只有将两者分开,旭日图的下钻状态(根节点为某个子节点)才会被保留。
当前数据取自旭日图,自动显示第一层的内容。关于直方图的坐标轴美化方法,可以参考第一个故事中的散点图的设置。现在的图表如下所示:
好了,我们成功拼接了两个图表。接下来我们需要做的,是将两个图表联动起来。
-
新建[mouseover]事件:
为了实现两个图表的联动,我们需要新建一个[mouseover](鼠标悬浮)事件。当鼠标悬浮在旭日图的某个节点上时,若其有子节点,则根据其子节点数据重新绘制直方图。
我们在<script>代码块中编码,写入该事件,完整代码如下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>ECharts</title> <!-- 引入 echarts.js --> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts-gl/dist/echarts-gl.min.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts-stat/dist/ecStat.min.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/dist/extension/dataTool.min.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/map/js/china.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/map/js/world.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/dist/extension/bmap.min.js"></script> <script type="text/javascript" src="https://www.jb51.net/jslib/jquery/jquery.min.js"></script> </head> <body> <!-- 为ECharts准备一个具备大小(宽高)的Dom --> <!-- float设置浮动方式,保证两个div显示在同一排(因为我们的容器宽度是自适应的) --> <div id="sun1" style="width:50%;height:900px;float:left;"></div> <div id="bar1" style="width:50%;height:900px;float:left;"></div> <script type="text/javascript"> // 基于准备好的dom,初始化echarts实例 var myChartSun1 = echarts.init(document.getElementById('sun1')); var myChartBar1 = echarts.init(document.getElementById('bar1')); //旭日图所需数据结构 var data = [{ name: '正式平台', value: 9487, children: [{ name: '有地址', value: 9357, children: [{ name: '6选3', value: 2851, children: [{ name: '北京市', value: 2336 },{ name: '山东省', value: 425 },{ name: '天津市', value: 83 },{ name: '上海市', value: 7 }] }, { name: '3+1+2', value: 5321, children: [{ name: '广东省', value: 2068 },{ name: '福建省', value: 1582, //数据下可设置格式 // itemStyle: { // shadowBlur: 10, // shadowColor: '#99ffff' // }, },{ name: '重庆市', value: 718 },{ name: '江苏省', value: 301 },{ name: '湖北省', value: 257 },{ name: '河北省', value: 222 },{ name: '湖南省', value: 116 },{ name: '辽宁省', value: 57 }] }, { name: '7选3-浙江', value: 28 }, { name: '未改革', value: 1157 , children:[{ name:'安徽省', value: 460 },{ name:'陕西省', value:286 },{ name:'四川省', value:126 },{ name:'新疆', value:114 },{ name:'云南省', value:66 },{ name:'广西', value:34 },{ name:'江西省', value:27 },{ name:'甘肃省', value:17 },{ name:'河南省', value:14 },{ name:'山西省', value:13 }] }] }, { name: '无地址', value: 130 }] }, { name: '培训平台', value: 3215, children: [{ name: '智校测试校', children: [{ name: '培训', value: 1449 }, { name: '排课', value: 1325 }] },{ name:'新课改培训', value: 441 }] }, { name: '测试+演示平台', value: 1046, children: [{ name: '测试', value: 464 }, { name: '演示', value: 582 } ] }, { name: '职教+旧平台', value: 1239 }]; var option = { backgroundColor:'#000', tooltip: { trigger: 'item', formatter: function (params) { return params.name + ' : ' + params.value; } }, visualMap: { min: 0, max: 2000, calculable: true, inRange: { color: ['#50a3ba', '#eac736', '#d94e5d'] }, textStyle: { color: '#fff' } }, series: [{ type: 'sunburst', id:'sunburst1', // highlightPolicy: 'ancestor', data: data, center:['50%','50%'], radius: [10, '90%'], //高亮当前及其子区间 //highlightPolicy: 'descendant', label: { rotate: 'radial', fontSize: 15 }, //选定区域 emphasis: { itemStyle: { //环形颜色 color: 'red', //边框颜色 borderColor: '#fff', shadowBlur: 10, shadowColor: '#99ffff' } }, //高亮区域 highlight: { itemStyle: { color: 'orange', borderColor: '#fff', shadowBlur: 10, shadowColor: '#99ffff' } }, //非选定区域 downplay: { itemStyle: { color: '#1e1e1e', shadowBlur: 0, //透明度 opacity: 0.3 }, label: { //透明度 opacity: 0.3 } }, //levels设置各层次环形宽度 levels: [{}, { //内圈半径 r0: '10%', //外圈半径 r: '30%', itemStyle: { //边框宽度 borderWidth: 2 } }, { r0: '30%', r: '50%', itemStyle: { borderWidth: 2 } }, { r0: '50%', r: '70%', itemStyle: { borderWidth: 2 } }, { r0: '70%', r: '75%', label: { //文字显示在外侧 position: 'outside', //距离环形的距离 padding: 3, }, itemStyle: { borderWidth: 3, //阴影半径 shadowBlur: 10, //阴影颜色,当前是亮色 shadowColor: '#99ffff' } }] }] }; var option1 = { backgroundColor:'#000', tooltip: { trigger: 'item', formatter: function (params) { return params.name + ' : ' + params.value; } }, visualMap: { min: 0, max: 2000, calculable: true, show: false, seriesIndex:0, inRange: { color: ['#50a3ba', '#eac736', '#d94e5d'] }, textStyle: { color: '#fff' } }, //柱形图设置 legend:{ data:['调用次数','学校数','真实学校数'], textStyle:{ color:'#fff' }, top: '1%', selected:{ '调用次数':true, '学校数':false, '真实学校数':false } }, xAxis: { type: 'category', data:['正式平台','培训平台','职教+旧平台','测试+演示平台'], axisLine:{ lineStyle:{ type:'solid', color:'#fff', width:1 } }, //旋转90度显示 // axisLabel:{ // rotate:90 // } }, yAxis: [{ name:'调用次数', type: 'value', //坐标内分割线 splitLine:{ lineStyle:{ type:'dashed' } }, axisLine:{ lineStyle:{ color:'#fff', width:1,//这里是为了突出显示加上的 } } },{//是否显示数值分割线 splitLine:{ show:false }, //max:100, axisLine:{ lineStyle:{ color:'#fff', width:1,//这里是为了突出显示加上的 } } }], grid: { left: '10%', right: '10%', bottom: '7%', containLabel: true }, series: { name:'调用次数', id: 'bar1', type: 'bar', //柱形图添加边框及阴影 itemStyle:{ normal:{ borderWidth: 1, borderColor: '#fff', shadowBlur: 5, shadowColor: '#99ffff' } }, data: data } }; //新建mouseover事件,且仅当对象为series时启发,并传入回调函数parmas,parmas包含子节点的信息 myChartSun1.on('mouseover','series', function (params) { //仅当选取对象为旭日图seriesType===sunburst时生效 if(params.seriesType === 'sunburst' ){ //新建一个空的横坐标类目的列表 var xAxisData = []; //存在子元素时,直方图显示子元素结果 if(params.data.children){ //将横坐标类目push入 xAxisData 中 for(var i = 0; i < params.data.children.length; i++){ xAxisData.push(params.data.children[i].name); }; //重新绘制直方图 myChartBar1.setOption({ xAxis:{ data:xAxisData }, series:[{ id:'bar1', data: params.data.children }] }) } } }); // 使用刚指定的配置项和数据显示图表。 myChartSun1.setOption(option); myChartBar1.setOption(option1); </script> </body> </html>
笔者用通俗语言简单解释一下代码内容。这段代码完成的工作相当简单,就是当事件触发时,将当前节点的全部子节点 [name] 信息保存,并传入给直方图的横坐标,并且将 [data] 替换为全部子节点的 [value] 值。根据这些调整,Echarts将自动选择合理的动画效果,展示新的直方图。
最终的图表效果如下:
-
引入新维度的数据:
所谓引入新的数据,实际上是扩充直方图每个类目下数据的项数。如下图所示,对于每个类目,除了显示引擎的调用次数外,我们还新增了[学校数]和[真实学校数]两项,用以比较三个数据间的比例情况。
这里笔者不再给出完整代码,具体的实现方式读者可以自己思考。
-
图表的意义-它述说了怎样的故事:
数据本身是虚拟出来的,不过我们可以尝试分析下通过图表能够获得的信息。
-
目前使用引擎的地区中,政策为‘3+1+2’的地区使用次数最多,‘6选3’次之,‘未改革’地区最少,说明我们的排课引擎主要的用户还是新高考改革地区。
‘3+1+2’地区虽然是改革的第一年,但很快就超越了‘6选3’地区。之后的引擎改进,应当把‘3+1+2’地区的政策特征作为主要考虑的对象(之前主要满足‘6选3’地区的需求);
引擎的使用集中于几个省份;
读者可以自己思考下,通过图表还可以获得哪些信息(建议读者更换自己的真实数据进行分析)。
-
故事结束
第三个故事主要讲解旭日图的使用,以及如何联动两个图表。
我们都知道,扇形图(旭日图)主要体现比例的分配,而直方图主要体现具体的数值(包括最大值最小值),以及数值的分布。显然,两个图表在数据的抽象化表达上是互补的。我们将两个图表结合,并且提供一定的联动效果,用户通过自己的操作,可以获得相当多的信息。
这种图表组合其实代表了一种新的数据展示方式。我们不再生成静态图表,而是引导用户和图表进行交互。
这种做法保持了图表的直观性,同时又允许读者按自己的方法来探索数据。
是的,在这里我们不仅扮演了数据导游的身份,还将数据本身“玩具化”。任何一个用户,不需要专业的知识,只通过简单的操作就可以去探索数据。这种独特的体验,一般的静态图表是无法带来的。
当然,动态的图表无法被引用在ppt(或者所有只能引用静态图片的软件)中。要么把这个图表作为一个单独的h5页面,以超链接的形式引用。要么,可以了解下hype4,应用这个高保真原型绘制工具,将图表与h5页面有机组合在一起(笔者有hype的操作手册编写计划,但是时间未定)。