Skip to content
一、echarts 通用图表封装

场景:在统计场景下,图表必不可少,那怎么去有效的封装图表组件就是一个需要仔细考量的问题,不然就是各写各的,相同的图表组件很容易存在多个情况,代码冗余且混乱。

图表封装主要考虑几个问题:

  • 默认的样式和自定义传入的样式怎么共存
  • 当数据发生变化时,如何刷新图表数据
  • 当视口发生变化时,如何动态的改变图表大小
1. 默认的样式和自定义传入的样式怎么共存

主要思路:合并配置

具体:将传入的配置参数与图表中默认的配置参数,进行一次相同key的覆盖操作,如果没有则使用默认参数,如果传入则优先使用自定义参数。

  • 方案一:核心工具类
    js
    /**
      * 判读是不是Object
      * @param value
      * @return {boolean}
      */
    const isObject = (value) => {
       const type = typeof value;
       return value !== null && (type === "object" || type === "function");
    };
    
    /**
     * 合并两个对象,
       * 如果没有则默认第一个对象的值
       * 相同则覆盖
       * @param source 源对象
       * @param other 需要合并的对象
       * @return {*[]|*}
       */
       export const mergeObject = (source, other) => {
        if (!isObject(source) || !isObject(other)) {
          return other === undefined ? source : other;
        }
        // 合并两个对象的 key,另外要区分数组的初始值为 [], 否则原样输出
        return Object.keys({
          ...source,
          ...other,
        }).reduce(
          (acc, key) => {
            // 递归合并 value
            acc[key] = mergeObject(source[key], other[key]);
            return acc;
          },
          Array.isArray(source) ? [] : source
        );
    };
  • 方案二: 利用 echarts 自带的 setOption API中的 notMerge 参数进行设置,具体参考:echarts-setOption.png
2. 当数据发生变化时,如何刷新图表数据
  • echarts 提供了 setOption API,当数据发生改变时,调用该 API 即可
  • 基于 vue 的话,就需要使用 watch 监听函数了,比较优雅的方式就是,传入一个tag,监听这个 tag 即可,无需监听一个数据对象
3. 当视口发生变化时,如何动态的改变图表大小
4. 详细代码实现
  • 需要自定义的配置使用
    vue
    <template>
      <div ref="circular"/>
    </template>
    
    <script>
    // 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
    import * as echarts from "echarts/core";
    // 引入柱状图图表
    import { PieChart } from "echarts/charts";
    // 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
    import { SVGRenderer } from "echarts/renderers";
    // 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
    import { TooltipComponent, LegendComponent } from "echarts/components";
    import { mergeObject } from "@/utils/MergeObject";
    // 注册必须的组件
    echarts.use([TooltipComponent, LegendComponent, PieChart, SVGRenderer]);
    
    export default {
      name: "Circular",
      props: {
        legendData: {
          type: Array,
          default: () => {
            return ["数据源一", "数据源二", "数据源三"];
          },
        },
        seriesData: {
          type: Array,
          default: () => {
            return [
              { value: 100, name: "数据源一" },
              { value: 200, name: "数据源二" },
              { value: 300, name: "数据源三" },
            ];
          },
        },
        colors: {
          type: Array,
          default: () => {
            return ["#FFBD2B", "#0FF1FF", "#2F8FFF"];
          },
        },
        text: {
          type: String,
          default: "总计",
        },
        total: {
          type: Number,
          default: 0,
        },
        specialOptions: {
          type: Object,
          default: () => {},
        },
        // watch对数组的监听及其不友好,数据响应慢了直接监听不到
        tag: Boolean,
      },
      data() {
        return {
          circular: {},
        };
      },
      watch: {
        tag: {
          handler() {
            this.setChartOptions();
          },
        },
      },
      mounted() {
        this.initCharts().then((res) => {
          this.circular = res;
          this.resizeChart();
          this.setChartOptions();
        });
      },
      methods: {
        initCharts() {
          // There is a chart instance already initialized on the dom.
          return new Promise((resolve) => {
            const circular = echarts.init(this.$refs.circular, { renderer: "svg" });
            resolve(circular);
          });
        },
        setChartOptions() {
          // 统一配置
          const commonOption = {
            legendOptions: {
              // right: "10%",
              top: "16%",
              orient: "vertical",
              icon: "circle",
              itemHeight: 8,
              formatter: (name) => {
                const data = options.series[0].data;
                let tarValue = "";
                for (let i = 0; i < data.length; i++) {
                  if (data[i].name == name) {
                    tarValue = data[i].value;
                  }
                }
                const arr = ["{a|" + name + "}{b|(" + tarValue + ")}"];
                return arr.join("\n");
              },
              textStyle: {
                rich: {
                  a: {
                    fontSize: 12,
                    width: 80,
                    color: "#fff",
                  },
                  b: {
                    width: 60,
                    // align: "left",
                    fontSize: 12,
                    fontWeight: 500,
                    color: "#fff",
                  },
                },
              },
            },
            rich: {
              a: {
                fontFamily: "Poppins",
                fontWeight: 600,
                fontSize: 16,
                color: "#fff",
                align: "center",
              },
              b: {
                padding: [5, 0, 0, 0],
                align: "center",
                fontSize: 10,
                color: "#fff",
              },
              c: {
                fontFamily: "Poppins",
                fontWeight: 600,
                fontSize: 12,
                color: "#6BE9FC",
                align: "center",
              }
            },
          };
          // 总体配置
          let options = {
            legend: {
              data: this.legendData,
              ...commonOption.legendOptions,
            },
            series: [
              {
                type: "pie",
                radius: ["60%", "80%"],
                avoidLabelOverlap: false,
                label: {
                  show: true,
                  position: "center",
                  formatter: () => {
                    let formatters = [`{a|${this.total}}\n{b|${this.text}}`]
                    if (this.total === 0) {
                      formatters = [`{c|${this.text}}`]
                    }
                    return formatters;
                  },
                  rich: commonOption.rich,
                },
                labelLine: {
                  show: false,
                },
                data: this.seriesData,
                color: this.colors,
              },
            ],
          };
          // 合并自定义配置
          options = mergeObject(options, this.specialOptions);
          this.circular.setOption(options);
        },
        resizeChart() {
          const resizeObserver = new ResizeObserver(() => {
            const width = this.$refs.circular?.offsetWidth;
            this.circular.resize({ width, height: "auto" });
          });
          resizeObserver.observe(this.$refs.circular);
        },
      },
    };
    </script>

Released under the MIT License.