前言
本来是想按照option中的属性,一个一个详细总结echarts的用法,但是写上一篇toolbox工具栏详细总结时发现其实每一个属性展开都非常复杂,一个个挨着尝试、总结不仅很费时间,而且很枯燥、没有成就感。
俗话说有需求才有动力,于是我改变思路,通过找一些酷炫的数据可视化的例子,学习其中用到的属性和工具。在复现酷炫效果的过程中不仅能学到知识,而且可以将代码作为一个模板方便自己以后有类似需求时使用,一举两得。
正文
在刷各种app的时候,经常会看到动态排序柱状图,觉得非常酷炫。刚好在官网上就有动态排序柱状图的例子(官网中有很多各种图表的例子),于是我果断尝试了一下,并对尝试过程中用到的属性和遇到的问题进行了总结。(代码的注释是简单的解释,后文有更详细的总结)。最终的效果如下:
完整代码
<!DOCTYPE html><html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>动态排序柱状图</title>
<script src="js/echarts.js"></script>
<script src="js/jquery_3.6.1.js"></script>
</head>
<body>
<div id="main" style="width: 100%; height: 600px"></div>
<script>
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
const updateFrequency = 2000;
const countryColors = {
Australia: "#00008b",
Canada: "#f00",
China: "#ffde00",
Cuba: "#002a8f",
Finland: "#003580",
France: "#ed2939",
Germany: "#000",
Iceland: "#003897",
India: "#f93",
Japan: "#bc002d",
"North Korea": "#024fa2",
"South Korea": "#000",
"New Zealand": "#00247d",
Norway: "#ef2b2d",
Poland: "#dc143c",
Russia: "#d52b1e",
Turkey: "#e30a17",
"United Kingdom": "#00247d",
"United States": "#b22234",
};
$.when(
// 存在跨域问题
// $.getJSON("https://fastly.jsdelivr.net/npm/emoji-flags@1.3.0/data.json"),
// $.getJSON("https://echarts.apache.org/examples/data/asset/data/life-expectancy-table.json")
$.getJSON("./data/emoji-flags.json"),
$.getJSON("./data/life-expectancy-table.json")
).done(function (res0, res1) {
const flags = res0[0]; //flags是数组,数组中的每个元素是对象
const data = res1[0]; //data也是数组,里面的每个元素还是数组
const years = []; //存储data中的所有年份
for (let i = 0; i < data.length; ++i) {
if (years.length === 0 || years[years.length - 1] !== data[i][4]) {
years.push(data[i][4]);
}
}
let startIndex = 10;
let startYear = years[startIndex]; //从1890年开始
//获取指定国家的emoji,也就是缩写
function getFlag(countryName) {
if (!countryName) {
return "";
}
return (
flags.find(function (item) {
return item.name === countryName;
}) || {}
).emoji;
}
option = {
//坐标系底板
grid: {
top: 10,
bottom: 30,
left: 150,
right: 80,
},
//x坐标轴
xAxis: {
max: "dataMax", //x坐标轴刻度最大值为数据的最大值
axisLabel: { //坐标轴刻度标签的相关设置
formatter: function (n) {
return Math.round(n) + ""; //刻度取整数
},
},
},
//初始数据集
dataset: {
source: data.slice(1).filter(function (d) {
return d[4] === startYear;
}),
},
//y坐标轴
yAxis: {
type: "category", //类目轴
inverse: true, //是否反向坐标轴
max: 10, //因为y是类目轴,所以这里指的是类目的序数,也就是只显示前11个类目
axisLabel: {
show: true,
fontSize: 14,
formatter: function (value) {
//原本标签值加上缩写,flag| 表示缩写采用rich中的flag定义的样式
return value + "{flag|" + getFlag(value) + "}";
},
rich: { //定义富文本样式
flag: {
fontSize: 25,
padding: 5,
},
},
},
animationDuration: 300,
animationDurationUpdate: 300,
},
//系列
series: [
{
realtimeSort: true, //是否开启该系列的实时排序
seriesLayoutBy: "column", //指定如何对应dataset中的数据
type: "bar", //柱状图
itemStyle: { //图形样式
color: function (param) { //柱条的颜色,param参数就是item对应的柱子
return countryColors[param.value[3]] || "#5470c6";
},
},
encode: { //定义维度的编码(映射)
x: 0, //把第一个维度(输入)映射到x轴
y: 3, //把第四个维度(国家名称)映射到y轴
},
label: { //图形上的文本标签
show: true,
precision: 1, //标签的精度,表示保留一位小数
position: "right", //标签的位置
valueAnimation: true, //是否开启标签的数字动画
fontFamily: "monospace", //字体
},
},
],
animationDuration: 0, //初始动画时长
animationDurationUpdate: updateFrequency, //数据更新动画的时长
// animationEasing: "linear", //初始动画缓动效果,因为没有初始动画,所以不需要写
animationEasingUpdate: "linear", //数据更新动画缓动效果
//原生图形元素组件,用来在图表中绘制一些形状
graphic: {
elements: [
{
type: "text", //图形种类
right: 160,
bottom: 60,
style: {
text: startYear, //初始文本内容
font: "bolder 80px monospace",
fill: "rgba(100, 100, 100, 0.25)",
},
z: 100,
},
],
},
};
myChart.setOption(option);
for (let i = startIndex; i < years.length - 1; ++i) {
(function (i) {
//每隔updateFrequency更新一次数据,一开始立马更新一次
setTimeout(function () {
updateYear(years[i + 1]);
}, (i - startIndex) * updateFrequency);
})(i);
}
function updateYear(year) {
let source = data.slice(1).filter(function (d) {
return d[4] === year;
});
option.series[0].data = source; //更新系列中的数据
option.graphic.elements[0].style.text = year; //更新图表右下角的年份
myChart.setOption(option);
}
});
</script>
</body>
</html>
一、数据准备
$.when(
$.getJSON("https://fastly.jsdelivr.net/npm/emoji-flags@1.3.0/data.json"),
$.getJSON("https://echarts.apache.org/examples/data/asset/data/life-expectancy-table.json"))
.done(function (res0, res1) {
$.when().done()是jQuery中的函数,意思是when中的成功执行后,执行done中的回调函数,done中回调函数的参数res0和res1分别是when中两个参数的返回值。
用VS Code 的 live server插件打开网页,执行这段代码会出现跨域问题而报错
我懒得研究怎么配置插件才能解决这个问题,干脆直接把数据下下来存在本地了。这两个json文件 emoji-flags.json 和 life-expectancy-table.json 中的数据分别为:
emoji-flags.json中是国家的缩写、全称等,如下:
life-expectancy-table.json中是19个国家在某个年份的人均收入、寿命、总人口数据,如下:
二、option设置
(一)xAxis 和 yAxis
**1.**max 坐标轴刻度最大值
可以设置成特殊值 'dataMax',此时取数据在该轴上的最大值作为最大刻度。
不设置时会自动计算最大值保证坐标轴刻度的均匀分布。
在类目轴中,也可以设置为类目的序数(如类目轴 data: ['类A', '类B', '类C'] 中,序数 2 表示 '类C'。也可以设置为负数,如 -3)。
当设置成 function 形式时,可以根据计算得出的数据最大最小值设定坐标轴的最小值。如:
max: function(value) {
return value.max - 20;
}
其中 value 是一个包含 min 和 max 的对象,分别表示数据的最大最小值,这个函数可返回坐标轴的最大值,也可返回 null/undefined 来表示“自动计算最大值”(返回 null/undefined 从 v4.8.0 开始支持)。
2.axisLabel 坐标轴刻度标签相关设置
(1)其中formatter用来设置刻度标签的内容格式器,支持字符串模板和回调函数两种形式。
官网中的例子是这样的:
// 使用字符串模板,模板变量为刻度默认标签 {value}
formatter: '{value} kg'
// 使用函数模板,函数参数分别为刻度数值(类目),刻度的索引
formatter: function(value, index) { return value + 'kg'; }
如果是时间轴的话,形式更多一些,详情可以看文档:时间轴的formatter用法
(2)rich用来定义富文本,和formatter搭配使用,用来给标签做出更丰富的效果。
在这个排序柱状图的例子中,它用来给y轴的刻度标签中的国家的缩写设置了特殊样式,可以看到国家全称的文字和缩写的文字是不一样的。
formatter: function (value) { //原本标签值加上缩写,flag| 表示缩写采用rich中的flag定义的样式
return value + "{flag|" + getFlag(value) + "}";
},
rich: { //定义富文本样式
flag: {
fontSize: 25,
padding: 5,
},
},
格式是 {styleName|text content},给text content采用rich中的styleName定义的样式。
实际中,富文本还会用在其他很多地方,用来增强描述。详情见文档:rich的详细用法
3.inverse 是否反向坐标轴
坐标轴默认是向右,向上是变大,将该属性设为true,则相应的坐标轴会反过来
(二)dataset 数据集
数据集(dataset)是专门用来管理数据的组件。虽然每个系列都可以在 series.data 中设置数据,但是从 ECharts4 支持 数据集 开始,更推荐使用 数据集 来管理数据。因为这样,数据可以被多个组件复用,也方便进行 “数据和其他配置分离“ 的配置风格。毕竟,在运行时,数据是最常改变的,而其他配置大多并不会改变。
该例子中的代码:
dataset: {
source: data.slice(1).filter(function (d) {
return d[4] === startYear;
}),
},
1.数组的slice方法:arr.slice([start[, end]])表示浅拷贝原数组中指定范围的元素到一个新数组中并返回。
2.数组的filter方法:通过回调函数设置的条件将原数组中符合条件的元素筛选出来放到一个新数组中并返回。
所以上面代码的意思就是将data中第一个元素(名称数组)去掉,再筛选出年份等于开始年份的所有元素作为数据集。
(三)series 系列
在 echarts 里,系列(series)是指:一组数值以及他们映射成的图。
1.realtimeSort 是否开启动态排序
这个属性虽然只需要简单的设置为true即可,但却非常重要。顾名思义,开启之后,echarts会自动动态的根据数据值进行排序,否则只有动态、没有排序
2.seriesLayoutBy 指定如何对应dataset中的数据
因为是通过dataset来设置数据,而不是在每个系列中直接用series.data设置数据,所以需要告诉echarts取dataset中每一行还是每一列的数据作为一个系列。这一行(列)称为一个维度(dimension),对应的列(行)称为一个数据项(item),这个概念在下面还会用到。
默认是column,可以设置为row。
在这个例子中dataset是一个数组,其中的每个元素依然是一个数组(拥有5个元素),所以dataset是一个n*5的二维数组:
"Income", "Life Expectancy", "Population", "Country", "Year"
数据项1
数据项2
…
我们最终展示的是输入这一列,所以seriesLayoutBy属性设置为column。
此时每一列称为一个维度,输入这一列就是第一个维度,每一行就是一个数据项。
3.encode 定义维度的编码(映射)
文档中 echarts.apache.org/zh/option.h…
的解释是编码,当然这个词本身的意思就是编码,但是我感觉叫映射更好理解
encode 声明的基本结构如下,其中冒号左边是坐标系、标签等特定名称,如 'x', 'y', 'tooltip' 等,冒号右边是数据中的维度名(string 格式)或者维度的序号(number 格式,从 0 开始计数),可以指定一个或多个维度(使用数组)。
如果想使用维度名,则需要在dataset中用dimensions进行定义。
在这个例子中,意思是把第一个维度,也就是输入映射到x轴,把第4个维度,也就是国家名称映射到y轴
encode: { //定义维度的编码(映射)
x: 0, //把第一个维度(输入)映射到x轴
y: 3, //把第四个维度(国家名称)映射到y轴
},
4.itemStyle 设置图像样式
itemStyle: { //图形样式
color: function (param) { //柱条的颜色,param参数就是item对应的柱子
return countryColors[param.value[3]] || "#5470c6";
},
},
回想上面第2条中item的含义,这里的意思就是设置柱形图中每一个柱子的颜色。
param是每个item对应的数据,param.value[3]就是item的数据中的第4项,也就是根据其国家名称在提前设置的颜色的的对象中找到对应的颜色,如果没找到则用#5470c6作为颜色。
5.label 图形上的文本标签
可用于说明图形的一些数据信息,比如值,名称等。
(1)valueAnimation 是否开启标签的数字动画
如果设为true,则数字的变动是连续的;如果设为false,则数字的变化是突变的,比如这次显示的是100,当下次数据来时直接变成200,没有从100逐渐涨到200的过程。
(2)precision 标签数字的精度
设置成几则表示显示几位小数
(四)动画相关
1.animationDuration 初始动画时长
就是从空白到显示初始值时的动画的时长,这个例子中设置为0,表示当你看到图形时初始值就已经显示在上面。
2.animationDurationUpdate 数据更新动画的时长
注意这个值要和设置的数据更新的间隔相同,否则看起来会有突变,不够丝滑。
这个例子中是定义了一个全局常量 updateFrequency 进行引用
3.animationEasing 初始动画缓动效果
动画效果这部分大家应该都很熟悉,css和jQuery中都接触过,关键字也都差不多。
4.animationEasingUpdate 数据更新动画缓动效果
(五)graphic 原生图形元素组件
用来在图表中绘制一些形状,支持的类型有:
标准写法是:
graphic: {
elements: [
{type: 'rect', ...},
{type: 'circle', ...},
...
]
}
elements是所有图像元素的集合(数组),每个图形元素是一个对象。也可以直接省略elements,写成下面这种形式:
graphic: [
{type: 'rect', ...},
{type: 'circle', ...},
...
]
在这个例子中,通过graphic绘制的就是图表右下角的年份,如下图中的1980
三、数据更新
上面是针对每一年的dataset进行的设置,要让整个图表具有动态的效果,需要对dataset进行定时更新。这个例子中用了下面两个函数:
1.更新年份
function updateYear(year) {
let source = data.slice(1).filter(function (d) {
return d[4] === year;
});
option.series[0].data = source; //更新系列中的数据
option.graphic.elements[0].style.text = year; //更新图表右下角的年份
myChart.setOption(option);
}
在dataset部分已经讲过data.slice(1).filter的含义,所以这个函数的意义就是根据年份获取对应的数据,并更新系列中的数据和图表右下角的年份
2.设置定时执行
for (let i = startIndex; i < years.length - 1; ++i) {
(function (i) {
//每隔updateFrequency更新一次数据,一开始立马更新一次
setTimeout(function () {
updateYear(years[i + 1]);
}, (i - startIndex) * updateFrequency);
})(i);
}
( function ( i ) { } )表示定义一个匿名函数,该函数接收一个参数,
( function ( i ) { } )( i ) 表示调用这个匿名函数并传入参数 i
所以上面的代码就表示从起始索引开始,设置延时执行updateYear函数,延时时间等于更新时间乘以索引的差值。也就是每隔updateFrequency就更新一次数据。
值得注意的是上面的代码意味着,一开始就进行了一次更新,这就是为什么我们看到图表上的年份是从1900开始,而我们算出来startYear是1890。
结束语
带着实现一个具体功能的目的学习确实要高效很多,而且很有成就感;但是缺点就是知识点不够系统、不能一次掌握所有属性和配置,下次换一个例子可能还是需要对其中的一些属性进行查询和学习。
希望我能坚持在学习一些例子后,再把知识点提取出来进行总结。也希望这篇文章能对看到这里的你有所帮助~