最近有一个需求是当用户点击堆叠柱状图时,在提示框中详细列出数据的同时,还要将堆叠的几个数据用柱状图展示出来,效果如下
经过查阅资料,可以通过自定义tooltip进行实现,其中的图表通过js二次构建echarts实现,由于tooltip中的图表不需要交互操作,所以就导出图片的base64,然后用图片渲染。
在实现过程中遇到几个问题需要处理:
1,如何自定义tooltip
2,引入渲染图表导出图片非常耗时,自定义内容必须要写成异步的
3,自定义内容由于是异步的,会二次更改tooltip容器宽高,造成内容溢出屏幕,如何将内容调整到屏幕可视区
下面是核心代码区,解决上面三个问题
import * as echarts from 'echarts';
var myChart = echarts.init(this.$el);
var options = {
...,
tooltip: {
trigger: 'axis',
triggerOn: 'click', //为提高性能,通过点击才生成数据
confine: true, //防止tooltip超出手机端屏幕,重新定位tooltip
formatter: function(params, ticket, callback) {
//在异步操作的回调函数中调用callback
axios.get("xxx.do").then(res => {
var textHtml = `<div>${res.data}</div>`;
callback(ticket, textHtml); //固定调用格式
});
return "加载中";
}
},
...
}
myChart.setOption(option);
下面是实现的全部代码
<template>
<div class="echarts-container"></div>
</template>
<script>
import * as echarts from 'echarts';
export default {
name: 'echarts',
components: {},
props: {},
data() {
return {};
},
computed: {},
created() {},
mounted() {
this.init();
},
destroyed() {},
methods: {
init() {
var myChart = echarts.init(this.$el);
var option = {
tooltip: {
trigger: 'axis',
triggerOn: 'click',
confine: true, //防止tooltip超出手机端屏幕,重新定位tooltip
axisPointer: {
type: 'shadow'
},
formatter: function(params, ticket, callback) {
var group1 = params.slice(0, params.length - 1); // 季度数据分组
var group2 = params.at(-1); //同步数据分组
//创建tooltip中需要的echarts容器
var divDom = document.createElement('div');
divDom.style.width = '500px';
divDom.style.height = '500px';
var myChart = echarts.init(divDom);
//根据tooltip数据的不同,动态创建配置项
var option = {
xAxis: {
show: false,
data: group1.map(param => param.seriesName)
},
yAxis: {
show: false
},
series: [{
name: '销量金额',
type: 'bar',
data: group1.map(param => {
return {
value: param.value,
itemStyle: {
color: param.color
}
};
})
}]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
// 等待echarts渲染完毕,生成图片的base64数据,与自定义的div相结合,通过callback传回给echarts进行渲染
myChart.on('finished', () => {
var imgData = myChart.getDataURL();
var title = `<div style>${group1[0].name}年报:${group1.map(param => param.value).reduce((a, b) => a + b)}万</div>`;
var items = '';
group1.forEach(param => {
const { color, seriesName, value } = param;
items += `<div style="display: flex;justify-content: flex-start;align-items: center"><i style="display: inline-block;width: 8px;height:8px;background-color: ${color};margin-right: 4px;"></i><span>${seriesName}:</span><span>${value}万</span></div>`;
});
const { color, seriesName, value } = group2;
var first = `<div style="display: flex;justify-content: flex-start;align-items: center"><i style="display: inline-block;width: 8px;height:8px;background-color: ${color};margin-right: 4px;"></i><span>${seriesName}:</span><span style="color: red;">${value}%</span></div>`;
var html = `<div style="width:250px; height:auto;">
${title}
<div style="display: flex;align-items: stretch;">
<div style="flex:auto;">
${first}
<div style="height: 6px;"></div>
${items}
</div>
<div style="flex:none;width: 120px;">
<div style="width: 100%;height: 100%;">
<img src="${imgData}" width="100%" height="100%" />
</div>
</div>
</div>
</div>`;
callback(ticket, html);
});
return '<div>请稍后...</div>';
}
},
legend: {},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: [2015, 2016, 2017, 2018, 2019, 2020, 2021]
},
yAxis: {},
series: [{
name: '一季度',
type: 'bar',
stack: 'total',
label: {
show: true
},
selectedMode: true,
animationDelay: function(idx) {
return idx * 50 + 1000;
},
data: [320, 302, 301, 334, 390, 330, 320]
},
{
name: '二季度',
type: 'bar',
stack: 'total',
label: {
show: true
},
selectedMode: true,
animationDelay: function(idx) {
return idx * 50 + 1000;
},
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '三季度',
type: 'bar',
stack: 'total',
label: {
show: true
},
selectedMode: true,
animationDelay: function(idx) {
return idx * 50 + 1000;
},
data: [220, 182, 191, 234, 290, 330, 310]
},
{
name: '四季度',
type: 'bar',
stack: 'total',
label: {
show: true
},
selectedMode: true,
animationDelay: function(idx) {
return idx * 50 + 1000;
},
data: [150, 212, 201, 154, 190, 330, 410]
},
{
name: '同比',
type: 'line',
stack: 'total',
label: {
show: true
},
selectedMode: true,
animationDelay: function(idx) {
return idx * 50 + 1000;
},
data: [820, 832, 901, 934, 1290, 1330, 1320]
}
],
animationEasing: 'elasticOut',
animationDuration: 2000,
barWidth: 30
};
myChart.setOption(option);
}
}
};
</script>
<style lang="scss" scoped>
.echarts-container {
height: 500px;
border: 1px solid #000;
background-color: #f8f8f8;
}
</style>
<style scoped></style>