Echarts常见问题整理

前言

本次项目中负责了一个使用Echarts做的页面,涉及的图表类型有
柱状图(横竖两种)、折线图、气泡图、漏斗图、饼图
期间也遇到了不少问题,但是大部分的问题都是可以通过Echarts官方文档上找到解决办法的。下面记录一下问题及解决办法和Echarts的一些学习、码字心得。

一、常见问题记录

1.如何创建一个自适应的Echarts图表(动态给Echarts图表当前高度初始化+窗口缩放Echarts图表自适应)

讲道理这个问题一开始着实困扰我了大半天,因为页面需要自适应,根本不知道图表一开始具体的 Height 是多少。而且此次布局使用的是Flex布局,分左中右三块,每块上下各有一个图表,加一起是六个。这里有两种解决思路,各有优缺点吧,这次用的第二个。

提供的思路并非最优解,只是在解决本次项目的过程中效果比较好,如果有什么更好的想法欢迎指出

动态高度初始化

思路一:通过css样式aspect-ratio(宽高比)解决

因为采用的是Flex布局,而且是左中右三块,左右25%,中间50%,因此宽度是一定有的

此时,给图表的容器加上一个宽高比aspect-ratio,高度设置为100%,图表在初始化的时候就能根据宽高比初始化出来。

优点:

  1. 操作简单,css直接加就ok
  2. 对于宽高比确定的图表完美契合
  3. 图表不会变形

缺点

  1. 分配的宽度过大的话会影响整体布局(超出盒子范围)
  2. 不适合难以确定宽高比 或 图表不要求定型 的情况没办法使用

思路二:通过在父组件dom操作获取当前高度clientHeight(只读),作为参数调用子组件方法,在子组件获取到当前高度后初始化。

优点

  1. 布局不会乱
  2. 适合难以确定宽高比 或 图表不要求定型 的情况

缺点

  1. 这样写的高度是定死的除非再次传参初始化,否则在不同大小的显示器上就只有刷新后才能正常自适应

代码实现
父组件:关键代码

1
2
3
4
5
<Histogram ref="echarts1" />
// 有五个类似的组件
for (var i = 1; i <= 5; i++) {
this.$refs['echarts'+i].setHeight(document.getElementsByClassName('side-echarts-container')[0].clientHeight)
}

子组件:关键代码

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
<template>
<div>
<div id="hstogram" class="charts" :style="{'height':sideEchartsContainerHeight+'px','width':' 100%'}" />
</div>
</template>

<script>
import * as echarts from 'echarts'
import { getBarGraphData } from '../../api/dataStatistics.js'
var option = {
// color: ['rgb(245,33,45)', 'rgb(255,229,143)', 'rgb(11,115,255)'],
color: [new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 1,
color: '#f00'
}, {
offset: 0,
color: '#f99'
}]),
new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 1,
color: 'rgb(249, 173, 21)'
}, {
offset: 0,
color: 'rgb(255,255,153)'
}]),
new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 1,
color: 'rgb(11,115,255)'
}, {
offset: 0,
color: 'rgba(0,255,255)'
}])
],
legend: {
textStyle: {
color: 'white'
},
top: 25
},
grid: {
show: true,
borderColor: 'rgba(255, 255, 255,0.5)'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
xAxis: {
type: 'category',
axisLabel: {
interval: 0, // 显示全部x坐标
// rotate: 35,
color: 'rgba(255, 255, 255,1)'
},
nameTextStyle: {
align: 'center'
},
splitLine: {
show: true,
lineStyle: {
color: 'rgba(255, 255, 255,0.5)',
width: 1,
type: 'dashed'
}
}
},
yAxis: {
axisLabel: {
color: 'rgba(255, 255, 255,1)'
},
splitLine: {
lineStyle: {
color: 'rgba(255, 255, 255,0.5)',
width: 1,
type: 'dashed'
}
}
},
series: [
{
type: 'bar',
barWidth: 10,
itemStyle: {
//柱形图圆角,鼠标移上去效果,如果只是一个数字则说明四个参数全部设置为那么多
normal: {
//柱形图圆角,初始化效果
barBorderRadius:[15, 15, 0, 0]
}
}
},
{
type: 'bar',
barWidth: 10,
itemStyle: {
//柱形图圆角,鼠标移上去效果,如果只是一个数字则说明四个参数全部设置为那么多
normal: {
//柱形图圆角,初始化效果
barBorderRadius:[15, 15, 0, 0]
}
}
},
{
type: 'bar',
barWidth: 10,
itemStyle: {
//柱形图圆角,鼠标移上去效果,如果只是一个数字则说明四个参数全部设置为那么多
normal: {
//柱形图圆角,初始化效果
barBorderRadius:[15, 15, 0, 0]
}
}
},
]
}

export default {
name: 'Histogram',
data() {
return {
charsData: [], // 之后通过请求放数据,需要效果的可以先加进去看看
sideEchartsContainerHeight: 0
}
},
methods: {
setHeight(height){
this.sideEchartsContainerHeight = height
this.getData() // 请求默认数据(无参数)
this.$nextTick(function() {
this.echartsInit() // 初始化echarts
})
},
echartsInit() {
myEcharts = echarts.init(document.getElementById('hstogram')) // 获取图表节点
myEcharts.setOption(option)
}
}
}
</script>

窗口缩放时图表内容自适应

其实图表本身是通过canvas这个容器画出来的,canvas在画完后想要让内容自适应只有重绘了。好在Echarts官方提供了这个函数resize()

1
2
3
4
5
6
7
8
9
// 监听实现
window.addEventListener('resize', function() {
// bubbleChart 是图表的实例 echartsInstance(调用过init函数后)
bubbleChart.resize()
})
// 手动调用
chartsResize(){
bubbleChart.resize()
}

通过全局设置监听,实现在窗口缩放时图表的重绘,当然这个监听对象加给谁都行,但是要记得销毁

本次项目中将每个图表都搞成了一个组件(维护起来比较方便),然后在组件里面写的监听。
其实感觉正确的写法应该是在父组件加监听,调6个子组件的resize()方法的,但是项目比较急,就没维护这块。

2.调整Echarts图表大小、位置、显示网格线

通过在option中设置grid的四个值即可:

1
2
3
4
5
6
7
8
9
option = {
grid: {
show:true, // 显示网格线
top:10,
left:10
// bottom:10,
// right:10
}
}

grid设置两个值即可确定图表的大小和位置。值可以是像 20 这样的具体像素值,可以是像 '20%' 这样相对于容器高宽的百分比,也可以是 'left', 'center', 'right'。如果 left 的值为'left', 'center', 'right',组件会根据相应的位置自动对齐

值得一提的是,gridcontainLabel如果为true的话,grid决定的是包括了坐标轴标签在内的所有内容所形成的矩形的位置,常用于防止标签溢出的场景。为false,则只算由图标形成的区域。

网格的背景只有在设置show:true时才会起作用,默认为透明

3.柱状图根据x坐标数量动态判断柱状图柱条的宽度、防止x坐标过多导致重叠

本次在写项目时,由于存在检索范围这一条件,导致x坐标的个数有时候很密集,有时候有很稀疏,所以就有了要根据x坐标的个数来动态改变柱状图柱子的宽度,来保证图表不会引起误解(1月份的柱子太宽占到了2月份的坐标)。

宽度问题

图表的样式修改一般在series内的itemStyle里面,我们可以通过barwidth来修改柱状图柱条的宽度。

动态改变宽度的前提条件是我们需要知道数据量,也就是请求成功后的返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// getBarGraphData 发出请求,.then表示请求成功后的回调函数,res为请求的返回值
getBarGraphData().then(res => {
let baseLength = 10
let resLength = 40 / res.data.length // 此处应该是 (图表宽度 / {请求返回的数据条数 * 每个x坐标展示的柱条个数})。结果可以稍微小点,便于更好的去展示。这里只是演示用,先写死了。
myEcharts.setOption({
series: [
{ type: 'bar', barWidth: Math.min(baseLength, resLength) },
{ type: 'bar', barWidth: Math.min(baseLength, resLength) },
{ type: 'bar', barWidth: Math.min(baseLength, resLength) }
],
dataset: {
dimensions: ['product', '红', '黄', '蓝'],
source: that.charsData
}
})
})

初始化时可以不给数据,只写配置。在请求成功后通过setOption更新配置即可。

防止x坐标过多导致重叠

两种解决办法

  1. 通过旋转一定的x坐标角度实现不重叠
    1
    2
    3
    4
    5
    6
    7
    8
    9
    option = {
    xAxis: {
    axisLabel: {
    //interval: 0, // 显示全部x坐标
    rotate: 35,
    color: 'rgba(255, 255, 255,1)'
    },
    }
    }
  2. 显示部分x坐标,隔几个个x坐标显示一个(间隔的x坐标数目固定)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    option = {
    xAxis: {
    axisLabel: {
    interval: 1, // 是否显示全部x坐标
    // rotate: 35,
    color: 'rgba(255, 255, 255,1)'
    },
    }
    }

4.Echarts实现渐变色及更新数据后渐变消失的问题

如果我们不给图表设置颜色,图表会默认从['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc']中挑选颜色。但是单纯设置颜色有时候丑的一批,所以加上渐变效果会好很多

线性渐变

不知道为什么官方文档上没搜到,Echarts内部是带有一个渐变色生成器的

更多关于echarts.graphic的细节请参阅

就是不知道为啥,官方文档上没给出来这个东西。但其实是可以直接使用的,比如我希望柱状图的填充为渐变填充,我就可以这么写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
option = {

color: [
// 第一个柱状图的颜色填充,参数依次对应 右/下/左/上 四个位置,1表示渐变色从正上方开始
// 第5个参数则是一个数组 用于配置颜色的渐变过程. 包含offset和color两个参数. offset的范围是0 ~ 1, 用于表示位置, color表示颜色
new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 1, // 100%处的颜色
color: '#f00'
}, {
offset: 0, // 0%处的颜色
color: '#f99'
}]),
// 第二个柱状图的颜色填充
new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 1, // 100%处的颜色
color: 'rgb(249, 173, 21)'
}, {
offset: 0, // 0%处的颜色
color: 'rgb(255,255,153)'
}])
// ... 以此类推
]
}

然后官方上貌似是直接把这个new出来的对象搞成配置项了,下面是官方的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
option = {
series:[{
color: {
type: 'linear',
x: 0, // 渐变起始位置横坐标。
y: 0, // 渐变起始位置纵坐标。
x2: 0, // 渐变终止位置横坐标。
y2: 1, // 渐变终止位置纵坐标。
// 组成渐变色的颜色。每个颜色包括 offset 与 color 属性,
// 前者表示渐变位置(类型:number),后者表示具体的颜色(类型:string)
colorStops: [{
offset: 0, color: 'red' // 0% 处的颜色
}, {
offset: 1, color: 'blue' // 100% 处的颜色
}],
// 如果为 false,则 colorStops 取值范围是 0 到 1;
// 如果为 true,则 x、 y、 x2、 y2、 colorStops 的坐标和元素是一致的
// (也就是说,原先用 1 表示物体最右侧,这时需要用元素实际宽度表示最右侧)。
global: false // 缺省为 false
}
}]
}

不是很清楚哪个好点,但是感觉上应该是直接配置会好点,而不是 new 调用。
### 径向渐变
new渐变色
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
option = {
color: [
// 第一个柱状图的颜色填充,参数依次对应 渐变中心位置横坐标/渐变中心位置纵坐标/渐变半径,默认值为0.5
// 第5个参数则是一个数组 用于配置颜色的渐变过程. 包含offset和color两个参数. offset的范围是0 ~ 1, 用于表示位置, color表示颜色
new echarts.graphic.RadialGradient(0.5, 0.5, 0.5, [{ // !!!! 此处是RadialGradient 而不是 LinearGradient
offset: 1, // 100%处的颜色
color: '#f00'
}, {
offset: 0, // 0%处的颜色
color: '#f99'
}]),
// 第二个柱状图的颜色填充
new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 1, // 100%处的颜色
color: 'rgb(249, 173, 21)'
}, {
offset: 0, // 0%处的颜色
color: 'rgb(255,255,153)'
}])
// ... 以此类推
]
}

官方文档写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
option = {
series:[{
color: {
type: 'radial', // !!!!! 变成了radial
x: 0.5,
y: 0.5,
r: 0.5,
// 组成渐变色的颜色。每个颜色包括 offset 与 color 属性,
// 前者表示渐变位置(类型:number),后者表示具体的颜色(类型:string)
colorStops: [{
offset: 0, color: 'red' // 0% 处的颜色
}, {
offset: 1, color: 'blue' // 100% 处的颜色
}],
// 如果为 false,则 colorStops 取值范围是 0 到 1;
// 如果为 true,则 x、 y、 x2、 y2、 colorStops 的坐标和元素是一致的
// (也就是说,原先用 1 表示物体最右侧,这时需要用元素实际宽度表示最右侧)。
global: false // 缺省为 false
}
}]
}

### 重置数据渐变消失的问题
本次项目中因为需要隔一段时间执行一遍动画(科技感,要动态),所以就写了个计时器清空数据再放进去。
1
2
3
4
5
6
7
8
9
10
11
12
timer2 =  setInterval(function () {
myEcharts.setOption({ // 展示/更新 数据
series:[
{ data: [] }
]
})
myEcharts.setOption({ // 展示/更新 数据
series:[
{ data: that.charsData }
]
})
}, 6000)

上面这样写在其他图(柱状、饼、漏斗、气泡)里面都是可以正常出来重新加载的过度动画的。但是到了折线图里却没了反应
折线图只有在第一次执行setOption时才有过度动画,之后就没有了,思来想去没想出来啥问题。就避开了这个问题

这里使用了myEcharts.clear()方法清除了当前实例里面的内容(不是销毁),之后通过重新setOption来放入配置和数据实现动画加载。

然后问题来了,重新加载的图表走的是默认颜色!即使在新的配置里面写渐变色也还是没有起作用人直接傻了,想不通为啥。

解决办法
看了看官方文档,发现setOption接收的不止一个参数,他有一个notMerge这个参数,表示了是否不跟之前设置的 option 进行合并。默认为 false。改成true后解决了这个问题。

具体原因还是没搞明白。按理说默认合并也会和数据一样覆盖之前的数据啊,猜测是因为没有用官方写的渐变而是用了new实现,有待测试。

5.Echarts中data和dataset的含义、Echarts数据返回格式

Echarts中data和dataset的含义

乍一看下data和dataSet好像没什么区别,只是一个需要切割数据到每个series里,一个可以统一使用。但其实在一些特殊情况下,是不支持使用dataSet的。

举个例子:使用 dataset 同时使用 appendData,只支持系列使用自己的 series.data 时使用 appendData。

Data

没什么好说的,给那个系列哪个系列就用这组数据。

优点是

  • 直观易理解
  • 适于对一些特殊图表类型进行一定的数据类型定制

缺点是

  • 为匹配这种数据输入形式,常需要有数据处理的过程,分割到不同系列
  • 此外,不利于多个系列共享一份数据,也不利于基于原始数据进行图表类型、系列的映射安排

dataset

相对于data来说,最直观的就是我们不需要做分割数据的处理。

优点是

  • 能够贴近这样的数据可视化常见思维方式:(I) 提供数据,(II) 指定数据到视觉的映射,从而形成图表。
  • 数据和其他配置可以被分离开来。数据常变,其他配置常不变。分开易于分别管理。
  • 数据可以被多个系列或者组件复用,对于大数据量的场景,不必为每个系列创建一份数据。
  • 支持更多的数据的常用格式,例如二维数组、对象数组等,一定程度上避免使用者为了数据格式而进行转换。

缺点是

  • 可能需要对dataset进行映射,要理解维度和映射的意思。
  • 在部分场景下不适用,比如上面的例子。

根据之前的学习来看,应该是在数据量特别大的时候会用到一些特定的方法,在使用这些方法时不支持与dataset一起使用。假如我们数据量不是很大的情况下,还是dataset会方便好用一点

Echarts数据返回格式

data就不用说了吧,主要说下dataset

dataset常见的数据格式有下面两种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
dataset: {
// 提供一份数据。
source: [
['product', '2015', '2016', '2017'],
['Matcha Latte', 43.3, 85.8, 93.7],
['Milk Tea', 83.1, 73.4, 55.1],
['Cheese Cocoa', 86.4, 65.2, 82.5],
['Walnut Brownie', 72.4, 53.9, 39.1]
]
}
dataset: {
// 用 dimensions 指定了维度的顺序。直角坐标系中,
// 默认把第一个维度映射到 X 轴上,第二个维度映射到 Y 轴上。
// 如果不指定 dimensions,也可以通过指定 series.encode完成映射
dimensions: ['product', '2015', '2016', '2017'],
source: [
{product: 'Matcha Latte', '2015': 43.3, '2016': 85.8, '2017': 93.7},
{product: 'Milk Tea', '2015': 83.1, '2016': 73.4, '2017': 55.1},
{product: 'Cheese Cocoa', '2015': 86.4, '2016': 65.2, '2017': 82.5},
{product: 'Walnut Brownie', '2015': 72.4, '2016': 53.9, '2017': 39.1}
]
},

维度和映射

维度

显然,相对于第一种来说,第二种可读性更高一点。但是第一种的灵活性更高

我们可以把维度理解为横纵两个方向(不考虑3d图,这个没了解呢哈哈)。

通过一定的配置,我们能够

  • 将横向维度的数据映射到x轴上,纵向的数据叫做横向维度的数据项
  • 将纵向维度的数据映射到x轴上,横向的数据叫做纵向维度的数据项

用第一个例子来帮助理解下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
dataset: {
// 提供一份数据。
// 横向维度就是每一行,纵向维度就是每一列。
// 在此种数据格式下,我们可以轻松地理解维度的含义。
// 我们可以将这份数据看做一个坐标系,第一列和第一行可以是xy轴(任意)
// 后面的数据对应的是x轴和y轴所对应的值
// 比如我把Matcha Latte当为x轴,那么43.3的意义是Matcha Latte在2015年的值
// 或者我把2015当为x轴,那么43.3的意义是在2015年Matcha Latte的值
source: [
['product', '2015', '2016', '2017'],
['Matcha Latte', 43.3, 85.8, 93.7],
['Milk Tea', 83.1, 73.4, 55.1],
['Cheese Cocoa', 86.4, 65.2, 82.5],
['Walnut Brownie', 72.4, 53.9, 39.1]
]
}

要实现上述更改xy轴的效果,可以使用 seriesLayoutBy 配置项,改变图表对于行列的理解。seriesLayoutBy 可取值:

  • 'column': 默认值。系列被安放到 dataset 的列上面。
  • 'row': 系列被安放到 dataset 的行上面。
    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
    option = {
    dataset:[
    source: [
    ['product', '2015', '2016', '2017'],
    ['Matcha Latte', 43.3, 85.8, 93.7],
    ['Milk Tea', 83.1, 73.4, 55.1],
    ['Cheese Cocoa', 86.4, 65.2, 82.5],
    ['Walnut Brownie', 72.4, 53.9, 39.1]
    ]
    ],
    xAxis: [
    {type: 'category', gridIndex: 0},
    {type: 'category', gridIndex: 1}
    ],
    yAxis: [
    {gridIndex: 0},
    {gridIndex: 1}
    ],
    grid: [
    {bottom: '55%'},
    {top: '55%'}
    ],
    series: [
    // 这几个系列会在第一个直角坐标系中,每个系列对应到 dataset 的每一行。
    {type: 'bar', seriesLayoutBy: 'row'},
    {type: 'bar', seriesLayoutBy: 'row'},
    {type: 'bar', seriesLayoutBy: 'row'},
    // 这几个系列会在第二个直角坐标系中,每个系列对应到 dataset 的每一列。
    {type: 'bar', xAxisIndex: 1, yAxisIndex: 1},
    {type: 'bar', xAxisIndex: 1, yAxisIndex: 1},
    {type: 'bar', xAxisIndex: 1, yAxisIndex: 1},
    {type: 'bar', xAxisIndex: 1, yAxisIndex: 1}
    ]
    }
映射

一般通过series.encode来配置映射,效果和上面说的差不多,但是能够指定某列去映射,写个例子大家去官网上瞅瞅吧

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
	var option = {
dataset: {
source: [
['score', 'amount', 'product'],
[89.3, 58212, 'Matcha Latte'],
[57.1, 78254, 'Milk Tea'],
[74.4, 41032, 'Cheese Cocoa'],
[50.1, 12755, 'Cheese Brownie'],
[89.7, 20145, 'Matcha Cocoa'],
[68.1, 79146, 'Tea'],
[19.6, 91852, 'Orange Juice'],
[10.6, 101852, 'Lemon Juice'],
[32.7, 20112, 'Walnut Brownie']
]
},
xAxis: {},
yAxis: {type: 'category'},
series: [
{
type: 'bar',
encode: {
// 将 "amount" 列映射到 X 轴。
x: 'amount',
// 将 "product" 列映射到 Y 轴。
y: 'product'
}
}
]
};
作者

徐云飞

发布于

2023-02-05

更新于

2020-04-01

许可协议

评论