echarts自定义tooltip内嵌图表

793 阅读1分钟

最近有一个需求是当用户点击堆叠柱状图时,在提示框中详细列出数据的同时,还要将堆叠的几个数据用柱状图展示出来,效果如下

tooltip.gif

经过查阅资料,可以通过自定义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>