对echart组件进行一个封装 个人笔记

152 阅读8分钟

对echart组件进行一个封装 个人笔记

图的id唯一性

主要是为了多个echart时有唯一标识符

通过uuid加表前缀的形式处理

 id: 'chart_' + uuidv4()

图表的响应式布局

思路如下

使用 myChart.getDom() 方法以及与 ElementResizeDetector 结合的 resize 方法的原因在于实现图表的响应式布局和确保图表正确显示。

示例代码

// 假设 'myChartContainer' 是一个包含在 HTML 中的元素的 ID
const myChart = echarts.init(document.getElementById('myChartContainer'));
​
// 获取 ECharts 图表的 DOM 元素
const chartDom = myChart.getDom();
​
// 打印 DOM 元素的宽度和高度
console.log(chartDom.offsetWidth, chartDom.offsetHeight);
​
// 使用 ElementResizeDetector 监听 DOM 元素的尺寸变化
const ERD = new ElementResizeDetector();
ERD.listenTo(chartDom, () => {
  myChart.resize(); // 调整图表以适应 DOM 元素尺寸的变化
});

在上面的代码中,我们首先通过 echarts.init() 创建了一个 ECharts 实例,并将其渲染到一个具有特定 ID 的 DOM 容器中。然后,我们使用 myChart.getDom() 获取了这个容器的引用,并使用 ElementResizeDetector 库来监听其尺寸变化,当尺寸变化时,我们调用 myChart.resize() 来更新图表的大小。

作用如下

  1. 响应式图表:在不同的屏幕尺寸或分辨率下,容器的尺寸可能会发生变化。通过监听容器尺寸的变化并调整图表大小,可以确保图表始终适应其容器,无论容器的当前尺寸如何。
  2. 保持图表比例:在容器尺寸变化后,如果图表大小没有相应调整,可能会导致图表内容变形或显示不完整。使用 resize 方法可以保持图表的原始设计比例。
  3. 提升用户体验:对于用户来说,无论在什么设备或屏幕尺寸下浏览,都能够获得一致的、视觉上令人愉悦的图表展示,这是非常重要的。
  4. 动态内容更新:在某些应用场景中,图表的数据或配置项可能会动态变化。即使在这种情况下,也需要图表的大小能够正确适应容器的变化。
  5. 避免硬编码尺寸:硬编码图表的宽度和高度通常不是一个好的实践,因为它降低了布局的灵活性。使用 resize 方法可以避免这个问题,让图表尺寸更加灵活。
  6. 跨浏览器兼容性:不同的浏览器可能对 CSS 和 DOM 的解析存在差异。通过编程方式监听和调整图表大小,可以减少这些差异带来的影响。
  7. 自动化处理:手动调整图表大小可能既耗时又容易出错。自动化这个过程可以减少人为错误,并提高开发效率。

多个折线图(series)

用于根据传入的数据和条件设置图表的系列(series)

条件判断:if (this.multiple)

这个判断用来决定是否需要创建多系列(multiple series)图表。当 multiple 属性为 true 时,意味着图表需要展示多个系列的数据。将多个series设置到一个option上

这时候可以用数组套上数组即可

if (this.multiple) {
  const series = this.data.map(item => {
        return {
            data: item.value,
            type: 'line',
            name: item.name,
            label: {
                show: true
            }
        }
    })
    lineOption.series = series
    lineOption.xAxis.data = this.xAxis
    lineOption.legend = {
        bottom: 0,
        show: true,
        itemWidth: 12,
        itemHeight: 12,
        icon: 'rect',
        data: this.data.map(item => item.name)
    }
    lineOption.grid.bottom = 32
}
  • this.data.map(item => {...}): 这里使用 map 函数遍历 data 数组。data 数组是传入组件的属性之一,预期包含多个数据点。
  • item.value: 假设每个 item 对象都有一个 value 数组,该数组包含该数据点的数值。
  • type: 'line': 设置系列类型为折线图。
  • name: item.name: 设置系列的名称,item.name 是数据点的名称或标识。
  • label: { show: true }: 设置显示数据标签,这里 label 配置确保每个数据点旁边的数值标签是可见的。

element-resize-detector 说明

element-resize-detector 是一个 JavaScript 库,用于检测 DOM 元素尺寸的变化。它特别有用于以下场景:

  1. 响应式布局:在响应式设计中,当元素尺寸发生变化时,你可能需要执行一些操作,比如重新渲染图表、调整内容布局等。
  2. 动态内容调整:对于动态改变尺寸的内容,例如图片上传预览、编辑器等,element-resize-detector 可以帮助你检测到这些变化,并进行相应的调整。
  3. 性能优化:对于一些性能敏感的操作,比如重新计算图表数据,你可能不希望在每次窗口尺寸变化时都执行。element-resize-detector 允许你只对目标元素的尺寸变化做出响应。
  4. 兼容性:它提供了跨浏览器的解决方案,可以确保在不同的浏览器和设备上都能正常工作。

element-resize-detector 的工作原理是使用一种机制(通常是通过在元素上设置一个可观察的属性,然后监听这个属性的变化)来检测元素尺寸的变化。当元素的尺寸发生变化时,它会触发一个回调函数,你的应用程序可以在该回调函数中执行相应的逻辑。

使用 element-resize-detector 时,你通常会这样做:

  1. 创建一个 ElementResizeDetector 实例。
  2. 使用该实例的 listenTo 方法来指定需要监听的元素。
  3. listenTo 方法提供一个回调函数,该函数将在元素尺寸变化时被调用。

例如:

import ElementResizeDetector from 'element-resize-detector';
​
// 创建一个 ERD 实例
const erd = new ElementResizeDetector();
​
// 获取要监听的 DOM 元素
const myElement = document.getElementById('myElement');
​
// 开始监听元素的尺寸变化
erd.listenTo(myElement, () => {
  // 当元素尺寸变化时,这个回调函数将被调用
  console.log('Element size has changed!');
  // 在这里执行你需要的操作,比如调整图表大小等
});

代码

需要安装依赖

npm install element-resize-detector
npm install echarts
npm install uuid
npm install sass

参数说明

props 传递参数说明
​
title (String): 用于设置图表标题的字符串,默认为空字符串。
​
data (Array): 一个数组,包含图表需要展示的数据对象,默认为空数组。数据对象应包含 name 和 value 字段,这些字段将用于图表的 X 轴和 Y 轴数据。
​
value (String): 指定数据对象中用于 Y 轴值的字段名,默认为 'value'。
​
name (String): 指定数据对象中用于 X 轴标签的字段名,默认为 'name'。
​
yAxisName (String): 设置 Y 轴名称的字符串,默认为空字符串。
​
xAxis (Array): 用于自定义 X 轴分类的数组,默认为空数组。
​
grid (Object): 设置图表栅格布局的对象,包含 top, left, right, bottom 和 containLabel 属性,具有默认值。
​
multiple (Boolean): 一个布尔值,指示图表是否应以多系列形式展示,默认为 false。
​
minInterval (Boolean): 一个布尔值,指示是否设置 Y 轴的最小间隔为 1,默认为 true

组件代码如下

<template>
    <div class="bar-chart ">
        <div class="chart">
            <div :id="id" style="width: 100%; height: 100%;" v-if="data.length"></div>
            <div v-else>正在加载中</div>
        </div>
        <div class="title">
            <h4>{{title}}</h4>
            <slot name="headerRight"></slot>
        </div>
        <div class="footer">
            <slot name="footer"></slot>
        </div>
    </div>
</template>
<script>
    import ElementResizeDetector from 'element-resize-detector'
    import * as echarts from 'echarts';
    import { v4 as uuidv4 } from 'uuid'; // 导入uuid库的v4函数
    export default {
        name: 'bar-echarts',
        props: {
            title: {
                type: String,
                default: ''
            },
            data: {
                type: Array,
                default: () => []
            },
            value: {
                type: String,
                default: 'value'
            },
            name: {
                type: String,
                default: 'name'
            },
            yAxisName: {
                type: String,
                default: ''
            },
            xAxis: {
                type: Array,
                default: () => []
            },
            grid: {
                type: Object,
                default: () => {
                    return {
                        top: 24,
                        left: 24,
                        right: 24,
                        bottom: 0,
                        containLabel: true
                    }
                }
            },
            multiple: {
                type: Boolean,
                default: false
            },
            minInterval: {
                type: Boolean,
                default: true
            }
        },
        data() {
            return {
                id: 'chart_' + uuidv4()
            }
        },
        watch: {
            data: 'render'
        },
        mounted() {
            this.render()
        },
        methods: {
            render() {
                if (!this.data.length) return
                const colorList = ['#2358a3', '#60C976', '#F5D162', '#FF9D4D', '#EC7471', '#A9D372', '#69C9AB', '#7678E6']
                this.$nextTick(() => {
                    if (document.getElementById(this.id)) {
                        echarts.dispose(document.getElementById(this.id))
                    }
                    const myChart = echarts.init(document.getElementById(this.id))
                    const barOption = {}
                    // if (this.minInterval) {
                    //     barOption.yAxis[0].minInterval = 1
                    // }
                    myChart.setOption(barOption)
                    myChart.resize()
                    const ERD = new ElementResizeDetector()
                    ERD.listenTo(myChart.getDom(), () => {
                        myChart.resize()
                    })
                })
            }
        }
    }
</script>
<style lang="scss" scoped>
.bar-chart {
    margin-bottom: 16px;
    border: none;
    box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1);
    &:hover {
        box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1);
        cursor: unset;
    }
    .title {
        margin-top: 20px;
        margin-left: 5px;
        display: flex;
        justify-content: center;
    }
    .chart {
        margin-top: 16px;
        height: 300px;
        text-align: left;
    }
    .exception-wrap {
        display: flex;
        flex-wrap: wrap;
    }
    .exception-wrap .exception-wrap-item {
        border: 1px solid #DCDEE5;
        margin: 10px;
        height: 420px;
        padding-top: 22px;
    }
    .exception-wrap-item.exception-part   {
        height: 260px;
        padding-top: 48px;
        flex: 1;
    }
    .exception-wrap-item.exception-gray {
        background-color: #F5F6FA;
    }
    .exception-wrap .exception-wrap-item .text-wrap {
        display: flex;
        align-items: center;
        justify-content: center;
        color: #3A84FF;
        font-size: 14px;
        margin-top: 12px;
    }
    .exception-wrap .exception-wrap-item .text-wrap.text-part {
        font-size: 12px;
        margin-top: 10px;
    }
    .exception-wrap .exception-wrap-item .text-subtitle {
        color: #979BA5;
        font-size: 14px;
        text-align: center;
        margin-top: 14px;
    }
    .text-wrap .text-btn {
        margin: 0 10px;
    }
    .text-wrap .text-btn:hover {
        cursor: pointer;
    }
}
</style>

option代码放进去即可

{
                        color: colorList,
                        grid: {
                            top: this.yAxisName ? 40 : 24,
                            left: 20,
                            right: 20,
                            bottom: 0,
                            containLabel: true
                        },
                        tooltip: {
                            trigger: 'axis',
                            axisPointer: {
                                type: 'line'
                            },
                            extraCssText: 'box-shadow: 0px 2px 20px 0px rgba(0, 0, 0, 0.1);border-radius: 2px'
                        },
                        xAxis: {
                            type: 'category',
                            data: this.data.map(item => item[this.name]),
                            axisLabel: {
                                color: '#979BA5'
                            },
                            axisLine: {
                                lineStyle: {
                                    color: '#DCDEE5'
                                }
                            }
                        },
                        yAxis: {
                            name: this.yAxisName,
                            nameTextStyle: {
                                color: '#979BA5',
                                fontSize: 12,
                                padding: 10
                            },
                            type: 'value',
                            axisLabel: {
                                color: '#979BA5'
                            },
                            axisLine: {
                                lineStyle: {
                                    color: '#DCDEE5'
                                }
                            },
                            minInterval: 1
                        },
                        series: [{
                            data: this.data.map(item => item[this.value]),
                            type: 'line',
                            name: '数量',
                            label: {
                                show: true
                            }
                        }]
                    }
                    if (this.multiple) {
                        const series = this.data.map(item => {
                            return {
                                data: item.value,
                                type: 'line',
                                name: item.name,
                                label: {
                                    show: true
                                }
                            }
                        })
                        barOption.series = series
                        barOption.xAxis.data = this.xAxis
                        barOption.legend = {
                            bottom: 0,
                            show: true,
                            itemWidth: 12,
                            itemHeight: 12,
                            icon: 'rect',
                            data: this.data.map(item => item.name)
                        }
                        barOption.grid.bottom = 32
                    }

一个测试组件的代码 (哈哈,用vue3写的测试用例)

<template>
    <div class="top-list">
        <div class="echart-list">
            <div class="echart-item" v-for="(item, i) in chartDataList" :key="i">
                <bar-echarts style="height: 100%;" :title="item.chartTitle" :data="item.chartData" :x-axis="xAxis" :multiple="multiple"/>
            </div>
        </div>
    </div>
</template><script lang="ts" setup>
import { ref,onMounted } from 'vue';
import BarEcharts from './barEcharts.vue';
​
const multiple = ref(false)
const chartDataList = ref([
        {
          chartTitle: '测试1',
          chartData: [{ name: '类别A', value: 1200 },{ name: '类别B', value: 1100 }],
        },
        {
          chartTitle: '测试2',
          chartData: [{ name: '类别B', value: 2200 },{ name: '类别C', value: 1200 }],
        },
        // ...其他数据
      ])
      const xAxis = ['类别A', '类别B', '类别C', '类别D']
​
const updated =async ()=>{
  // 使用setTimeout模拟异步操作,如API请求
  await setTimeout(() => {
    multiple.value = true
    // 这里更新chartDataList的数据
    chartDataList.value = [
      // 假设这是更新后的数据
      { chartTitle: '更新后-测试1', chartData: [{ name: '类别A', value: [100,200] },{name: '类别B', value: [200,110]} ] },
      { chartTitle: '更新后-测试2', chartData: [{ name: '类别A', value: [100,200] },{name: '类别B', value: [200,110]} ]},
      // ...其他更新后的数据
    ];
    console.log(chartDataList.value)
  }, 3000); // 3秒后更新数据
}
​
// 使用onMounted钩子在组件挂载后执行异步操作
onMounted(()=>{
        updated()
})
​
</script><style lang="scss" scope>
.top-list{
    width: 100%;
    .echart-list{
        width: 100%;
        display: flex;
        justify-content: space-between;
        .echart-item{
            width: 23%;
            height: 80%;
        }
    }
}
</style>