本次项目中负责了一个使用Echarts做的页面,涉及的图表类型有
柱状图(横竖两种)、折线图、气泡图、漏斗图、饼图
期间也遇到了不少问题,但是大部分的问题都是可以通过Echarts官方文档上找到解决办法的。下面记录一下问题及解决办法和Echarts的一些学习、码字心得。
讲道理这个问题一开始着实困扰我了大半天,因为页面需要自适应,根本不知道图表一开始具体的 Height 是多少。而且此次布局使用的是Flex布局,分左中右三块,每块上下各有一个图表,加一起是六个
。这里有两种解决思路,各有优缺点吧,这次用的第二个。
提供的思路并非最优解,只是在解决本次项目的过程中效果比较好,如果有什么更好的想法欢迎指出
因为采用的是Flex布局
,而且是左中右三块,左右25%,中间50%,因此宽度是一定有的
此时,给图表的容器加上一个宽高比aspect-ratio,高度设置为100%
,图表在初始化的时候就能根据宽高比初始化出来。
优点:
css直接加
就ok宽高比确定
的图表完美契合不会变形
缺点
影响整体布局
(超出盒子范围)不适合
难以确定宽高比 或 图表不要求定型 的情况没办法使用优点
布局不会乱
适合
难以确定宽高比 或 图表不要求定型 的情况缺点
高度是定死的
,除非再次传参初始化
,否则在不同大小的显示器上就只有刷新
后才能正常自适应
。代码实现
父组件:关键代码
<Histogram ref="echarts1" />
// 有五个类似的组件
for (var i = 1; i <= 5; i++) {
this.$refs['echarts'+i].setHeight(document.getElementsByClassName('side-echarts-container')[0].clientHeight)
}
子组件:关键代码
<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()
// 监听实现
window.addEventListener('resize', function() {
// bubbleChart 是图表的实例 echartsInstance(调用过init函数后)
bubbleChart.resize()
})
// 手动调用
chartsResize(){
bubbleChart.resize()
}
通过全局设置监听,实现在窗口缩放时图表的重绘,当然这个监听对象加给谁都行,但是要记得销毁
。
本次项目中将每个图表都搞成了一个组件(维护起来比较方便),然后在组件里面写的监听。
其实感觉正确的写法应该是在父组件加监听,调6个子组件的resize()
方法的,但是项目比较急,就没维护这块。
通过在option
中设置grid
的四个值即可:
option = {
grid: {
show:true, // 显示网格线
top:10,
left:10,
// bottom:10,
// right:10
}
}
grid
设置两个值即可确定图表的大小和位置。值可以是像 20
这样的具体像素值,可以是像 '20%'
这样相对于容器高宽的百分比,也可以是 'left', 'center', 'right'
。如果 left 的值为'left', 'center', 'right'
,组件会根据相应的位置自动对齐
。
值得一提的是,grid
中containLabel
如果为true
的话,grid
决定的是包括了坐标轴标签在内的所有内容所形成的矩形的位置,常用于防止标签溢出
的场景。为false
,则只算由图标形成的区域。
网格的背景只有在设置show:true时才会起作用,默认为透明
本次在写项目时,由于存在检索范围这一条件,导致x坐标的个数有时候很密集,有时候有很稀疏,所以就有了要根据x坐标的个数来动态改变柱状图柱子的宽度,来保证图表不会引起误解(1月份的柱子太宽占到了2月份的坐标)。
图表的样式修改一般在series
内的itemStyle
里面,我们可以通过barwidth
来修改柱状图柱条的宽度。
动态改变宽度的前提条件是我们需要知道数据量,也就是请求成功后的返回值。
// 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
更新配置即可。
两种解决办法
option = {
xAxis: {
axisLabel: {
//interval: 0, // 显示全部x坐标
rotate: 35,
color: 'rgba(255, 255, 255,1)'
},
}
}
option = {
xAxis: {
axisLabel: {
interval: 1, // 是否显示全部x坐标
// rotate: 35,
color: 'rgba(255, 255, 255,1)'
},
}
}
如果我们不给图表设置颜色,图表会默认从['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc']
中挑选颜色。但是单纯设置颜色有时候丑的一批,所以加上渐变效果会好很多
不知道为什么官方文档上没搜到,Echarts内部是带有一个渐变色生成器的
更多关于echarts.graphic的细节请参阅
就是不知道为啥,官方文档上没给出来这个东西。但其实是可以直接使用的,比如我希望柱状图的填充为渐变填充,我就可以这么写
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出来的对象搞成配置项了,下面是官方的写法:
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渐变色
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)'
}])
// ... 以此类推
]
}
官方文档写法
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
}
}]
}
本次项目中因为需要隔一段时间执行一遍动画(科技感,要动态),所以就写了个计时器清空数据再放进去。
timer2 = setInterval(function () {
myEcharts.setOption({ // 展示/更新 数据
series:[
{ data: [] }
]
})
myEcharts.setOption({ // 展示/更新 数据
series:[
{ data: that.charsData }
]
})
}, 6000)
上面这样写在其他图(柱状、饼、漏斗、气泡)里面都是可以正常出来重新加载的过度动画的。但是到了折线图里却没了反应
。
折线图只有在第一次执行setOption时才有过度动画
,之后就没有了,思来想去没想出来啥问题。就避开了这个问题
这里使用了myEcharts.clear()
方法清除了当前实例里面的内容(不是销毁
),之后通过重新setOption
来放入配置和数据实现动画加载。
然后问题来了,重新加载的图表走的是默认颜色!即使在新的配置里面写渐变色也还是没有起作用
人直接傻了,想不通为啥。
解决办法
看了看官方文档,发现setOption
接收的不止一个参数,他有一个notMerge
这个参数,表示了是否不跟之前设置的 option 进行合并。默认为 false。
改成true
后解决了这个问题。
具体原因还是没搞明白。按理说默认合并也会和数据一样覆盖之前的数据啊,猜测是因为没有用官方写的渐变而是用了new实现,有待测试。
乍一看下data和dataSet好像没什么区别,只是一个需要切割数据到每个series里,一个可以统一使用。但其实在一些特殊情况下,是不支持使用dataSet的。
举个例子:使用 dataset 同时使用 appendData
,只支持系列使用自己的 series.data 时使用 appendData。
没什么好说的,给那个系列哪个系列就用这组数据。
优点是
直观易理解
适于对一些特殊图表类型进行一定的数据类型定制
。缺点是
常需要有数据处理的过程,分割到不同系列
不利于多个系列共享一份数据
,也不利于基于原始数据进行图表类型、系列的映射安排
。相对于data来说,最直观的就是我们不需要做分割数据的处理。
优点是
能够贴近这样的数据可视化常见思维方式
:(I) 提供数据,(II) 指定数据到视觉的映射,从而形成图表。数据和其他配置可以被分离开来
。数据常变,其他配置常不变。分开易于分别管理。数据可以被多个系列或者组件复用
,对于大数据量的场景,不必为每个系列创建一份数据。支持更多的数据的常用格式
,例如二维数组、对象数组等,一定程度上避免使用者为了数据格式而进行转换。缺点是
可能需要对dataset进行映射
,要理解维度和映射
的意思。在部分场景下不适用,比如上面的例子。
根据之前的学习来看,应该是在数据量特别大的时候会用到一些特定的方法,在使用这些方法时不支持与dataset一起使用。假如我们数据量不是很大的情况下,还是dataset会方便好用一点
data就不用说了吧,主要说下dataset
dataset常见的数据格式有下面两种
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图,这个没了解呢哈哈)。
通过一定的配置,我们能够
用第一个例子来帮助理解下:
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 的行上面。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
来配置映射,效果和上面说的差不多,但是能够指定某列去映射
,写个例子大家去官网上瞅瞅吧
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'
}
}
]
};
5分钟上手ECharts
主要目的是看图表有哪几个部分,要改的话能够想起来去哪个属性里面看文档。这是其一
其二是对EChaarts有一个大体的了解
之后根据自己需求删删改改写配置
不为其他,只为他快、准、狠。
根据博客提供的信息去官方文档上搜这个配置项。看看有没有细节性问题,或者博客中没写的、与自己使用的场景不符合的情况。
在对图表有了大体认识后,确定需要改的东西叫什么,然后去搜配置项或者api,一个一个阅读吧,莫得办法。或者直接找大佬来帮忙看看也行。