实现echarts主题随项目主题切换

文章详细描述了如何在Vue3项目中结合Vuetify框架,使Echarts图表能够根据项目的dark和light主题动态切换样式。主要步骤包括配置和注册自定义Echarts主题,监听项目主题变化以重绘图表,以及处理窗口大小变化时的resize事件。同时,文章提供了vue3和vue2版本的代码示例来说明具体实现方法。
摘要由CSDN通过智能技术生成

前言

项目中很多时候都带有dark/light两中主题类型,通过switch标签控制,但是echarts图形是通过canvas标签绘制,其背景颜色和字体样式并不会随着项目主题类型的切换而切换。所以需要额外设置监听主题事件,主要实现思路如下:

  1. 配置echarts主题,并引入注册
  2. 配置选项并绘制图形
  3. 监听系统主题变化,根据其改变echarts主题
  4. 监听屏幕变化,实现大小屏切换resize效果

实现效果

dark主题:

在这里插入图片描述

light主题:

在这里插入图片描述

项目环境

配置echarts主题

其实最新的echarts(5.x.x)已支持dark/light模式两种主题,但是很多时候还是满足不了项目需求,比如主题下的primarywarnningerror等样式,就可以使用**registerThemeAPI**注册主题样式,先配置主题样式(地址:主题编辑器),然后进行注册,这个官网已经给出了教程

vuetifydark样式其实和echartsdark样式相似,故不用使用**registerTheme**注册,主要区别在于backgroundColorcolor的不同,所以使用在init()之前使用条件判断,如下:

let baseOptions = {};
// vm.$vuetify.theme.dark:是否是dark模式
if (vm.$vuetify.theme.dark) {
  baseOptions = {
    backgroundColor: themeConfig.themes.dark.backgroundColor,
    textStyle: {
      color: themeConfig.themes.dark.color,
    },
  };
}

绘制echarts图形

主要是通过onDrawLine()实现,实现思路如下:

  1. 判断vm.$vuetify.theme.dark (项目dark主题)是否为true
  2. 如果为true,配置backgroundColortextStyle.color ,使用echartsdark主题
  3. 如果为false,不配置backgroundColortextStyle.color ,使用echarts中的light(即默认主题)
let lineChart = {};
const splitLine = {
  show: true,
  lineStyle: {
    // 实线
    // type: 'solid',
    // width: 0.5,
    // opacity: 0.8,

    // 虚线
    type: [5, 5],
    dashOffset: 0,
    shadowBlur: 0,
  },
};
const axisLine = { show: false };
const axisTick = { show: false };
const onDrawLine = () => {
  lineChart = vm.$echarts.init(
    document.getElementById("line-chart"),
    vm.$vuetify.theme.dark ? "dark" : "light"
  );
  let baseOptions = {};
  if (vm.$vuetify.theme.dark) {
    baseOptions = {
      backgroundColor: themeConfig.themes.dark.backgroundColor,
      textStyle: {
        color: themeConfig.themes.dark.color,
      },
    };
  }
  const option = {
    ...baseOptions,
    color: [
      $themeColors.error,
      $themeColors.danger,
      $themeColors.warning,
      $themeColors.low,
    ],
    title: {
      // text: 'Stacked Line'
    },
    tooltip: {
      trigger: "axis",
    },
    legend: {
      data: ["严重", "高", "中", "低"],

      // 放在底部
      bottom: 0,
    },
    grid: {
      top: "5%",
      left: "2%",
      right: "3%",
      bottom: "10%",
      containLabel: true,
    },
    xAxis: {
      type: "category",
      boundaryGap: false,
      data: [
        "10/09",
        "10/10",
        "10/11",
        "10/12",
        "10/13",
        "10/14",
        "10/15",
        "10/16",
        "10/17",
        "10/18",
        "10/19",
        "10/20",
        "10/21",
        "10/22",
        "10/23",
      ],
      splitLine,

      axisLine,
      axisTick,
    },
    yAxis: {
      type: "value",
      splitLine,
      interval: 90,
      max: 450,
      axisLine,
    },

    // 数据值
    series: [
      {
        name: "严重",
        type: "line",
        lineStyle: {
          width: 5,
        },

        // 显示点为实圆心
        stack: "Total",
        showSymbol: false,
        symbol: "none",
        data: [
          80, 150, 180, 270, 210, 160, 160, 202, 265, 210, 270, 255, 290, 360,
          375,
        ],
      },
      {
        name: "高",
        type: "line",
        showSymbol: false,
        symbol: "none",
        lineStyle: {
          width: 5,
        },
        data: [
          80, 125, 105, 130, 215, 195, 140, 160, 230, 300, 220, 170, 210, 200,
          280,
        ],
      },
      {
        name: "中",
        type: "line",
        showSymbol: false,
        symbol: "none",
        lineStyle: {
          width: 5,
        },
        data: [
          80, 99, 82, 90, 115, 115, 74, 75, 130, 155, 125, 90, 140, 130, 180,
        ],
      },
      {
        name: "低",
        type: "line",
        showSymbol: false,
        symbol: "none",
        lineStyle: {
          width: 5,
        },
        data: [
          10, 19, 32, 50, 15, 105, 14, 55, 30, 145, 115, 80, 200, 100, 150,
        ],
      },
    ],
  };

  lineChart.setOption(option)
};

注意:这里的lineChart不用使用ref 声明,因为不需要做响应式

术语速查手册

echarts的配置项真的很多,有时候不知道哪个是哪个意思,找起来比较费时间,官网给出了术语速查手册,有助于理解记忆

在这里插入图片描述

实现动态切换

实现echart图形切换主要思路如下:

  1. 监听项目主题(vm.$vuetify.theme.dark
  2. 注销图形(dispose()
  3. 重绘图形

主要代码如下:

watch(
  () => vm.$vuetify.theme.dark,
  (value) => {
    lineChart.dispose();
    onDrawLine();
  },
  {
    // immediate: true,
    deep: true,
  }
);

**注意:**不能immediate不能为true ,组件初始化时,lineChart还没有init(),所以会报错,故第一次绘制需要在onMounted,主要实现如下:

onMounted(() => {
  onDrawLine();
});

实现大小屏切换

主要通过[window.onresize](https://juejin.cn/post/7168860905625796616)实现,因为项目需求,有时候会进行大小屏切换,主要实现代码如下:

onMounted(() => {
  onDrawLine();
  window.onresize = function () {
    lineChart.resize();
  };
});

onBeforeUnmount(() => {
  window.onresize = null;
});

注意: 在组件实例销毁时,必须注销window.onresize,原因如下:

  1. window.onresize是全局组件,不注销会影响其他组件的功能
  2. 系统性能变差,因为window.onresize只要屏幕有改变就会触发,如果不注销又多个组件使用,屏幕变化一次,window.onresize会被执行多次

vue3版本代码实现

<!--
* @Description:告警时间趋势 折线图
* @version:1.0.0
* @Author: yuanyu
* @Date: 2022-11-03 10:22:55
* @LastEditors: yuanyu
* @LastEditTime: 2022-12-09 16:17:23
-->
<template>
  <!-- <vue-apex-charts
    type="line"
    height="280"
    :options="apexChatData.lineChartSimple.chartOptions"
    :series="apexChatData.lineChartSimple.series"
  /> -->
  <div id="line-chart" style="min-height:295px;width:100%"></div>
</template>

<script>
import VueApexCharts from 'vue-apexcharts'
import {
  onMounted, getCurrentInstance, onBeforeUnmount, watch,
} from 'vue-demi'
import themeConfig from '@themeConfig'
import apexChatData from './apexChartData'

const $themeColors = themeConfig.themes.light
export default {
  components: { VueApexCharts },
  setup() {
    const vm = getCurrentInstance().proxy
    console.log('document', document.getElementById('line-chart'))
    let lineChart = {}
    const splitLine = {
      show: true,
      lineStyle: {
        // 实线
        // type: 'solid',
        // width: 0.5,
        // opacity: 0.8,

        // 虚线
        type: [5, 5],
        dashOffset: 0,
        shadowBlur: 0,
      },
    }
    const axisLine = { show: false }
    const axisTick = { show: false }
    const onDrawLine = () => {
      lineChart = vm.$echarts.init(document.getElementById('line-chart'), vm.$vuetify.theme.dark ? 'dark' : 'light')
      let baseOptions = {}
      if (vm.$vuetify.theme.dark) {
        baseOptions = {
          backgroundColor: themeConfig.themes.dark.backgroundColor,
          textStyle: {
            color: themeConfig.themes.dark.color,
          },
        }
      }
      const option = {
        ...baseOptions,
        color: [$themeColors.error, $themeColors.danger, $themeColors.warning, $themeColors.low],
        title: {
          // text: 'Stacked Line'
        },
        tooltip: {
          trigger: 'axis',
        },
        legend: {
          data: ['严重', '高', '中', '低'],

          // 放在底部
          bottom: 0,
        },
        grid: {
          top: '5%',
          left: '2%',
          right: '3%',
          bottom: '10%',
          containLabel: true,
        },
        xAxis: {
          type: 'category',
          boundaryGap: false,
          data: [
            '10/09',
            '10/10',
            '10/11',
            '10/12',
            '10/13',
            '10/14',
            '10/15',
            '10/16',
            '10/17',
            '10/18',
            '10/19',
            '10/20',
            '10/21',
            '10/22',
            '10/23',
          ],
          splitLine,

          axisLine,
          axisTick,
        },
        yAxis: {
          type: 'value',
          splitLine,
          interval: 90,
          max: 450,
          axisLine,
        },

        // 数据值
        series: [
          {
            name: '严重',
            type: 'line',
            lineStyle: {
              width: 5,
            },

            // 显示点为实圆心
            stack: 'Total',
            showSymbol: false,
            symbol: 'none',
            data: [80, 150, 180, 270, 210, 160, 160, 202, 265, 210, 270, 255, 290, 360, 375],
          },
          {
            name: '高',
            type: 'line',
            showSymbol: false,
            symbol: 'none',
            lineStyle: {
              width: 5,
            },
            data: [80, 125, 105, 130, 215, 195, 140, 160, 230, 300, 220, 170, 210, 200, 280],
          },
          {
            name: '中',
            type: 'line',
            showSymbol: false,
            symbol: 'none',
            lineStyle: {
              width: 5,
            },
            data: [80, 99, 82, 90, 115, 115, 74, 75, 130, 155, 125, 90, 140, 130, 180],
          },
          {
            name: '低',
            type: 'line',
            showSymbol: false,
            symbol: 'none',
            lineStyle: {
              width: 5,
            },
            data: [10, 19, 32, 50, 15, 105, 14, 55, 30, 145, 115, 80, 200, 100, 150],
          },
        ],
      }

      // option = {
      //   ...apexChatData.lineChartSimple.chartOptions,
      //   series: apexChatData.lineChartSimple.series.map(item => Object.assign(item, { type: 'line' })),
      // }
      console.log('apexChatData', apexChatData)
      lineChart.setOption(option)

      window.onresize = function () {
        console.log('改变了', vm.$vuetify.theme.dark)
        lineChart.resize()
      }
    }

    onMounted(() => {
      onDrawLine()
    })
    onBeforeUnmount(() => {
      window.onresize = null
    })
    watch(
      () => vm.$vuetify.theme.dark,
      value => {
        lineChart.dispose()
        onDrawLine()
      },
      {
        // immediate: true,
        deep: true,
      },
    )

    return {
      apexChatData,
    }
  },
}
</script>

vue2版本代码实现

<!--
* @Description:告警时间趋势 折线图
* @version:1.0.0
* @Author: xxxxxx
* @Date: 2022-12-09 14:50:38
* @LastEditors: xxxxxx
* @LastEditTime: 2022-12-09 16:41:23
-->
<template>
  <div id="line-chart" :style="`height:${height};width:${width}`"></div>
</template>

<script>
import themeConfig from '@themeConfig'

const $themeColors = themeConfig.themes.light
let lineChart = {}
const splitLine = {
  show: true,
  lineStyle: {
    // 实线
    // type: 'solid',
    // width: 0.5,
    // opacity: 0.8,

    // 虚线
    type: [5, 5],
    dashOffset: 0,
    shadowBlur: 0,
  },
}
const axisLine = { show: false }
const axisTick = { show: false }
export default {
  props: {
    // 高度
    height: {
      type: [String],
      require: true,
    },

    // 宽度
    width: {
      type: String,
      default: '100%',
    },
    option: {
			type: Object,
			default: {}
		},
  },
  data() {
    return {}
  },
  watch: {
    '$vuetify.theme.dark': {
      handler(value) {
        lineChart.dispose()
        this.onDrawLine()
      },
      deep: true,
    },
  },
  mounted() {
    this.onDrawLine()
  },
  beforeDestroy() {
    window.onresize = null
  },
  methods: {
    onDrawLine() {
      lineChart = this.$echarts.init(document.getElementById('line-chart'), this.$vuetify.theme.dark ? 'dark' : 'light')
      let baseOptions = {}
      if (this.$vuetify.theme.dark) {
        baseOptions = {
          backgroundColor: themeConfig.themes.dark.backgroundColor,
          textStyle: {
            color: themeConfig.themes.dark.color,
          },
        }
      }
      const option = {
        ...baseOptions,
        color: [$themeColors.error, $themeColors.danger, $themeColors.warning, $themeColors.low],
        title: {
          // text: 'Stacked Line'
        },
        tooltip: {
          trigger: 'axis',
        },
        legend: {
          data: ['严重', '高', '中', '低'],

          // 放在底部
          bottom: 0,
        },
        grid: {
          top: '5%',
          left: '2%',
          right: '3%',
          bottom: '10%',
          containLabel: true,
        },
        xAxis: {
          type: 'category',
          boundaryGap: false,
          data: [
            '10/09',
            '10/10',
            '10/11',
            '10/12',
            '10/13',
            '10/14',
            '10/15',
            '10/16',
            '10/17',
            '10/18',
            '10/19',
            '10/20',
            '10/21',
            '10/22',
            '10/23',
          ],
          splitLine,

          axisLine,
          axisTick,
        },
        yAxis: {
          type: 'value',
          splitLine,
          interval: 90,
          max: 450,
          axisLine,
        },

        // 数据值
        series: [
          {
            name: '严重',
            type: 'line',
            lineStyle: {
              width: 5,
            },

            // 显示点为实圆心
            stack: 'Total',
            showSymbol: false,
            symbol: 'none',
            data: [80, 150, 180, 270, 210, 160, 160, 202, 265, 210, 270, 255, 290, 360, 375],
          },
          {
            name: '高',
            type: 'line',
            showSymbol: false,
            symbol: 'none',
            lineStyle: {
              width: 5,
            },
            data: [80, 125, 105, 130, 215, 195, 140, 160, 230, 300, 220, 170, 210, 200, 280],
          },
          {
            name: '中',
            type: 'line',
            showSymbol: false,
            symbol: 'none',
            lineStyle: {
              width: 5,
            },
            data: [80, 99, 82, 90, 115, 115, 74, 75, 130, 155, 125, 90, 140, 130, 180],
          },
          {
            name: '低',
            type: 'line',
            showSymbol: false,
            symbol: 'none',
            lineStyle: {
              width: 5,
            },
            data: [10, 19, 32, 50, 15, 105, 14, 55, 30, 145, 115, 80, 200, 100, 150],
          },
        ],
      }
      lineChart.setOption(option)

      window.onresize = function () {
        console.log('改变了', this.$vuetify.theme.dark)
        lineChart.resize()
      }
    },
  },
}
</script>
<style scoped lang='scss'>
</style>

求解

在项目中,echarts图形会不止一个,如果每次使用watch监听主题变化,代码会变得非常冗余,目前的思路是封装一个echarts公共组件,不知道有没有大佬有更好的思路,求指点

参考链接

https://echarts.apache.org/examples/zh/editor.html?c=line-stack

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值