Skip to content

echarts 封装使用

下载封装资源

查看封装代码
ts
//希望使用5.x版本的话,需要在package.json中更新版本号,并切换引用方式
import * as echarts from 'echarts'
import type { App } from 'vue'
import EchartContainer from './components/EchartContainer.vue'
import heNanGeoJson from './map/410000.json'
import statisticsDark from './theme/statistics-dark.json'
import statistics from './theme/statistics.json'

export { getEchartImage, useChartHighlight } from './tools'
export { EchartContainer }

/* eslint-disable @typescript-eslint/consistent-type-imports */
declare module 'vue' {
  export interface GlobalComponents {
    /** 自定义chart组件 */
    EchartContainer: typeof EchartContainer
  }
}

// app.use(echartsPlugin)
const echartsPlugin = {
  install(app: App) {
    // @ts-ignore 5.x版本注册地图
    echarts.registerMap('heNanMap', { geoJSON: heNanGeoJson })
    // echarts.registerMap('china', { geoJSON: china });
    // echarts.registerMap('china_city', { geoJSON: chinaCity });
    echarts.registerTheme('statistics', statistics)
    echarts.registerTheme('statistics-dark', statisticsDark)

    app.component('EchartContainer', EchartContainer)
  }
}

export default echartsPlugin
vue
<template>
  <div :style="{ width, height }" ref="chartDom" />
</template>
<script lang="ts" setup>
import type { ECharts } from 'echarts'
import * as echart from 'echarts'
import { onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'

const props = withDefaults(
  defineProps<{
    width?: string | number
    height?: string | number
    theme?: string
    /** echart 的option ,防止template中报错这里设置any */
    option: any
  }>(),
  { width: '100%', height: '100%', theme: 'statistics' }
)

const chartDom = ref<HTMLDivElement>()
// ehcart实例不应该为响应式的
const echartInstance = shallowRef<ECharts>()

onMounted(() => {
  if (chartDom.value) {
    echartInstance.value = echart.init(chartDom.value, props.theme)
    echartInstance.value.setOption(props.option || {})
    const observer = new ResizeObserver(() => {
      echartInstance.value?.resize()
    })
    chartDom.value.parentElement && observer.observe(chartDom.value.parentElement)
  }
})
onBeforeUnmount(() => {
  !echartInstance.value?.isDisposed && echartInstance.value?.dispose()
})

watch(
  () => props.option,
  option => {
    echartInstance.value?.setOption(option || {})
  },
  { deep: true }
)
defineExpose({
  getInstance: () => echartInstance.value,
  setOption: (option: echarts.EChartsOption) => {
    echartInstance.value?.setOption(option || {})
  },
  changeTheme: (theme: string) => {
    echartInstance.value?.dispose()
    echartInstance.value = echart.init(chartDom.value, theme)
    echartInstance.value.setOption(props.option || {})
    return echartInstance.value
  }
})
</script>
ts
import * as echart from 'echarts'
import { onUnmounted, shallowReactive } from 'vue'
export const getEchartImage = (option: {
  width: string | number
  height: string | number
  chartOption: any
  theme?: string
  backgroundColor?: string
}) => {
  const { chartOption, height, width, backgroundColor = '#fff', theme } = option
  const dom = document.createElement('div')
  dom.style.height = height + ''
  dom.style.width = width + ''
  const instance = echart.init(dom, theme)
  instance.setOption(chartOption)
  instance.resize() // 调整图表大小
  return instance.getDataURL({ backgroundColor })
}

export const useChartHighlight = () => {
  const hignLightInfo = shallowReactive({
    /** 图表实例 */
    chartInstance: null as echarts.ECharts | null,
    /** 数据的个数 */
    len: 0,
    inited: false,
    /** 动画标识 */
    identifier: -1,
    /** 记录时间 */
    time: 0,
    /** 当前自动到第几个 */
    currentIndex: 0,
    /** 第几个series 默认0 */
    seriesIndex: 0 as number | number[]
  })

  const requestAnimationFunction = (timeStamp: number) => {
    const { currentIndex, len, time, chartInstance, seriesIndex } = hignLightInfo
    if (timeStamp > time + 1000) {
      const lastIndex = currentIndex === 0 ? len - 1 : currentIndex - 1
      // 如果要取消高亮系列:
      chartInstance?.dispatchAction({ type: 'downplay', seriesIndex, dataIndex: lastIndex })
      chartInstance?.dispatchAction({
        type: 'showTip', // 根据 tooltip 的配置项显示提示框。
        // 系列的 index,在 tooltip 的 trigger 为 axis 的时候可选。
        seriesIndex: typeof seriesIndex === 'number' ? seriesIndex || 0 : 0,
        dataIndex: currentIndex
      })
      chartInstance?.dispatchAction({ type: 'highlight', seriesIndex, dataIndex: currentIndex })
      hignLightInfo.currentIndex = currentIndex + 1
      if (currentIndex === len) {
        hignLightInfo.currentIndex = 0
      }
      hignLightInfo.time = timeStamp
    }
    hignLightInfo.identifier = window.requestAnimationFrame(requestAnimationFunction)
  }
  const cancelAnimation = () => {
    const { currentIndex, len, seriesIndex } = hignLightInfo
    const lastIndex = currentIndex === 0 ? len - 1 : currentIndex - 1
    hignLightInfo.chartInstance?.dispatchAction({
      type: 'downplay',
      seriesIndex,
      dataIndex: lastIndex
    })
    window.cancelAnimationFrame(hignLightInfo.identifier)
  }
  const startAnimation = () => {
    const { len, identifier } = hignLightInfo
    window.cancelAnimationFrame(identifier)
    len > 0 && (hignLightInfo.identifier = window.requestAnimationFrame(requestAnimationFunction))
  }
  onUnmounted(() => {
    cancelAnimation()

    const { chartInstance } = hignLightInfo
    chartInstance?.getDom()?.removeEventListener('mouseenter', cancelAnimation)
    chartInstance?.getDom()?.removeEventListener('mouseleave', startAnimation)
  })

  function chartHignlight(option: {
    /** 第几个series 默认0 */
    seriesIndex?: number | number[]
    chartInstance: echarts.ECharts
    /** 数据的个数 */
    len: number
    /** 不立即执行 */
    notImmediate?: boolean
  }) {
    if (hignLightInfo.inited && option.chartInstance === hignLightInfo.chartInstance)
      return console.warn('不能重复调用')
    if (!option.chartInstance) return console.warn('请传入chart实例')
    if (!option.len || typeof option.len !== 'number') return console.warn('请传入正确的数据个数')
    hignLightInfo.chartInstance = option.chartInstance
    hignLightInfo.len = option.len || 0
    hignLightInfo.seriesIndex =
      typeof option.seriesIndex === 'number' ? option.seriesIndex || 0 : option.seriesIndex || [0]
    hignLightInfo.chartInstance.getDom().addEventListener('mouseenter', cancelAnimation)
    hignLightInfo.chartInstance.getDom().addEventListener('mouseleave', startAnimation)
    hignLightInfo.inited = true
    if (option.notImmediate) return () => startAnimation()
    startAnimation()
  }

  return { chartHignlight }
}

基本使用

查看代码
vue
<template>
  <EchartContainer :option height="400px" ref="echartContainerRef" />
</template>
<script lang="ts" setup>
import { useData } from 'vitepress'
import { nextTick, ref, watch } from 'vue'
import EchartContainer from './components/EchartContainer.vue'

const echartContainerRef = ref<InstanceType<typeof EchartContainer>>()
const { isDark } = useData() // 这里是vitepress中的主题,实际场景按需求
watch(
  isDark,
  flag => nextTick(() => echartContainerRef.value?.changeTheme(flag ? 'dark' : 'light')),
  { immediate: true }
)
const option = {
  title: {
    text: 'Referer of a Website',
    subtext: 'Fake Data',
    left: 'center'
  },
  tooltip: {
    trigger: 'item'
  },
  legend: {
    orient: 'vertical',
    left: 'left'
  },
  series: [
    {
      name: 'Access From',
      type: 'pie',
      radius: '50%',
      data: [
        { value: 1048, name: 'Search Engine' },
        { value: 735, name: 'Direct' },
        { value: 580, name: 'Email' },
        { value: 484, name: 'Union Ads' },
        { value: 300, name: 'Video Ads' }
      ],
      emphasis: {
        itemStyle: {
          shadowBlur: 10,
          shadowOffsetX: 0,
          shadowColor: 'rgba(0, 0, 0, 0.5)'
        }
      }
    }
  ]
}
</script>

项自动高亮

查看代码
vue
<template>
  <EchartContainer :option height="400px" ref="echartContainerRef" />
</template>
<script lang="ts" setup>
import { useData } from 'vitepress'
import { nextTick, ref, watch } from 'vue'
import EchartContainer from './components/EchartContainer.vue'
import { useChartHighlight } from './tools'

const option = {
  xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
  },
  yAxis: {
    type: 'value'
  },
  tooltip: {},
  series: [
    {
      data: [120, 200, 150, 80, 70, 110, 130],
      type: 'bar',
      showBackground: true,
      backgroundStyle: {
        color: 'rgba(180, 180, 180, 0.2)'
      }
    }
  ]
}

const echartContainerRef = ref<InstanceType<typeof EchartContainer>>()
const { chartHignlight } = useChartHighlight()
const { isDark } = useData() // 这里是vitepress中的主题,实际场景按需求
watch(
  isDark,
  flag =>
    nextTick(() => {
      // 切换主题意味着重新渲染,需要重新应用高亮
      const instance = echartContainerRef.value?.changeTheme(flag ? 'dark' : 'light')
      instance && chartHignlight({ chartInstance: instance, len: option.series[0].data.length })
    }),
  { immediate: true }
)
</script>

地图展示及自定义缩放按钮

查看代码
vue
<template>
  <EchartContainer :option height="400px" ref="echartContainerRef" />
</template>
<script lang="ts" setup>
import * as echarts from 'echarts'
import { useData } from 'vitepress'
import { nextTick, ref, watch } from 'vue'
import EchartContainer from './components/EchartContainer.vue'
import heNanGeoJson from './map/410000.json'

// @ts-ignore 5.x版本注册地图
echarts.registerMap('heNanMap', { geoJSON: heNanGeoJson })
const echartContainerRef = ref<InstanceType<typeof EchartContainer>>()
const { isDark } = useData() // 这里是vitepress中的主题,实际场景按需求
watch(
  isDark,
  flag => nextTick(() => echartContainerRef.value?.changeTheme(flag ? 'dark' : 'light')),
  { immediate: true }
)
const setChartZoom = (zoom: number) => {
  echartContainerRef.value?.setOption({
    series: [{ zoom: zoom }]
  })
}

const option = {
  tooltip: {},
  toolbox: {
    show: true,
    feature: {
      myDataZoomLess: {
        show: true,
        title: '缩小',
        icon: 'path://M19 19V5H5v14zm0-16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zm-2 8v2H7v-2z',
        onclick(this: any) {
          // 应用新的缩放配置
          const [series] = this.api?.getOption()?.series || []
          setChartZoom(series.zoom * 0.9)
        }
      },
      myDataZoomMore: {
        show: true,
        title: '放大',
        icon: 'path://M11 17h2v-4h4v-2h-4V7h-2v4H7v2h4zm-6 4q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm0-2h14V5H5zM5 5v14z',

        onclick(this: any) {
          const [series] = this.api?.getOption()?.series || []
          setChartZoom(series.zoom * 1.1)
        }
      }
    }
  },
  series: [
    {
      name: '分布',
      colorBy: 'data',
      type: 'map',
      label: { show: true },
      map: 'heNanMap',
      roam: true,
      data: [
        { name: '郑州市', value: Math.floor(Math.random() * 1000) },
        { name: '洛阳市', value: Math.floor(Math.random() * 1000) },
        { name: '开封市', value: Math.floor(Math.random() * 1000) },
        { name: '许昌市', value: Math.floor(Math.random() * 1000) }
      ].map(v => {
        return {
          ...v,
          itemStyle: {
            areaColor: v.value > 100 ? '#f26c4f' : '#54a0ff'
          }
        }
      })
    }
  ]
} as echarts.EChartsOption
</script>

自定义主题

查看代码
vue
<template>
  <EchartContainer :option height="400px" ref="echartContainerRef" />
</template>
<script lang="ts" setup>
import * as echarts from 'echarts'
import { useData } from 'vitepress'
import { nextTick, ref, watch } from 'vue'
import EchartContainer from './components/EchartContainer.vue'
import statisticsDark from './theme/statistics-dark.json'
import statistics from './theme/statistics.json'

echarts.registerTheme('statistics', statistics)
echarts.registerTheme('statistics-dark', statisticsDark)
const option = {
  tooltip: {
    trigger: 'axis',
    axisPointer: {
      type: 'cross',
      label: {
        backgroundColor: '#6a7985'
      }
    }
  },
  legend: {
    data: ['Email', 'Union Ads', 'Video Ads', 'Direct', 'Search Engine']
  },
  grid: {
    left: '3%',
    top: '10%',
    right: '4%',
    bottom: '3%',
    containLabel: true
  },
  xAxis: [
    {
      type: 'category',
      boundaryGap: false,
      data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    }
  ],
  yAxis: [
    {
      type: 'value'
    }
  ],
  series: [
    {
      name: 'Email',
      type: 'line',
      stack: 'Total',
      areaStyle: {},
      emphasis: {
        focus: 'series'
      },
      data: [120, 132, 101, 134, 90, 230, 210]
    },
    {
      name: 'Union Ads',
      type: 'line',
      stack: 'Total',
      areaStyle: {},
      emphasis: {
        focus: 'series'
      },
      data: [220, 182, 191, 234, 290, 330, 310]
    },
    {
      name: 'Video Ads',
      type: 'line',
      stack: 'Total',
      areaStyle: {},
      emphasis: {
        focus: 'series'
      },
      data: [150, 232, 201, 154, 190, 330, 410]
    },
    {
      name: 'Direct',
      type: 'line',
      stack: 'Total',
      areaStyle: {},
      emphasis: {
        focus: 'series'
      },
      data: [320, 332, 301, 334, 390, 330, 320]
    },
    {
      name: 'Search Engine',
      type: 'line',
      stack: 'Total',
      label: {
        show: true,
        position: 'top'
      },
      areaStyle: {},
      emphasis: {
        focus: 'series'
      },
      data: [820, 932, 901, 934, 1290, 1330, 1320]
    }
  ]
}

const echartContainerRef = ref<InstanceType<typeof EchartContainer>>()
const { isDark } = useData() // 这里是vitepress中的主题,实际场景按需求
watch(
  isDark,
  flag =>
    nextTick(() => {
      // 切换主题意味着重新渲染,需要重新应用高亮
      echartContainerRef.value?.changeTheme(flag ? 'statistics-dark' : 'statistics')
    }),
  { immediate: true }
)
</script>
json
{
  "color": [
    "#7A79FF",
    "#16DBCC",
    "#FAC858",
    "#FF81A9",
    "#8DC6FF",
    "#9CF196",
    "#774898",
    "#F73D93",
    "#E62A76",
    "#FFA5A5",
    "#9ED2C6",
    "#A9EEC2",
    "#A3D8F4",
    "#ECA0B6",
    "#9772FB"
  ],
  "grid": {
    "top": "16px",
    "bottom": "32px",
    "right": "5%"
  },
  "line": {
    "itemStyle": {
      "borderWidth": 1
    },
    "lineStyle": {
      "width": "2"
    },
    "symbolSize": "8",
    "symbol": "emptyCircle",
    "smooth": true
  },
  "bar": {
    "itemStyle": {
      "borderRadius": [5, 5, 0, 0]
    },
    "barMinHeight": 2
  },
  "pie": {
    "minAngle": 5,
    "grid": {
      "top": "16px",
      "bottom": "28px"
    }
  },
  "xAxis": {
    "splitLine": {
      "show": false
    },
    "axisLabel": {
      "show": true
    }
  },
  "yAxis": {},
  "categoryAxis": {
    "axisLine": {
      "show": true,
      "opacity": 0.4
    },
    "axisTick": {
      "show": true,
      "opacity": 0.4
    },
    "axisLabel": {
      "show": true
    },
    "splitLine": {
      "show": false
    },
    "splitArea": {
      "show": false,
      "areaStyle": {}
    }
  },
  "valueAxis": {
    "axisLine": {
      "show": true,
      "lineStyle": {
        "opacity": 0.4
      }
    },
    "axisTick": {
      "show": false,
      "lineStyle": {
        "opacity": 0.4
      }
    },
    "axisLabel": {
      "show": true
    },
    "splitLine": {
      "show": true,
      "lineStyle": { "opacity": 0.2 }
    },
    "splitArea": {
      "show": false,
      "areaStyle": {}
    }
  },
  "toolbox": {
    "iconStyle": {},
    "emphasis": {
      "iconStyle": {}
    }
  },
  "legend": {
    "textStyle": {}
  },
  "tooltip": {
    "show": true,
    "axisPointer": {
      "lineStyle": {
        "width": "1"
      },
      "crossStyle": {
        "width": "1"
      }
    }
  },
  "timeline": {
    "lineStyle": {
      "width": 1
    },
    "itemStyle": {
      "borderWidth": 1
    },
    "controlStyle": {
      "borderWidth": 0.5
    },
    "checkpointStyle": {},
    "label": {},
    "emphasis": {
      "itemStyle": {},
      "controlStyle": {
        "borderWidth": 0.5
      },
      "label": {}
    }
  },
  "visualMap": {}
}
json
{
  "color": [
    "#2ec9b2",
    "#b6a2de",
    "#5ab1ef",
    "#ffb980",
    "#d87a80",
    "#8d98b3",
    "#e5cf0d",
    "#97b552",
    "#95706d",
    "#dc69aa",
    "#07a2a4",
    "#9a7fd1",
    "#588dd5",
    "#f5994e",
    "#c05050",
    "#59678c",
    "#c9ab00",
    "#7eb00a",
    "#6f5553",
    "#c14089"
  ],
  "backgroundColor": "rgba(0,0,0,0)",
  "textStyle": {},
  "title": {
    "textStyle": {
      "color": "#5cd9e8"
    },
    "subtextStyle": {
      "color": "#ece7e7"
    }
  },
  "grid": {
    "top": "16px",
    "bottom": "32px",
    "right": "5%"
  },
  "line": {
    "label": {
      "color": "#fff"
    },
    "itemStyle": {
      "borderWidth": 1
    },
    "lineStyle": {
      "width": "2"
    },
    "symbolSize": "6",
    "symbol": "emptyCircle",
    "smooth": true
  },
  "bar": {
    "label": {
      "color": "#fff"
    },
    "itemStyle": {
      "borderRadius": [5, 5, 0, 0]
    },
    "barMinHeight": 2
  },
  "pie": {
    "label": {
      "color": "#fff"
    },
    "minAngle": 5,
    "itemStyle": {
      "borderWidth": "0",
      "borderColor": "#cccccc"
    },
    "grid": {
      "top": "16px",
      "bottom": "28px"
    }
  },
  "map": {
    "itemStyle": {
      "areaColor": "rgba(7,114,204, .8)",
      "borderColor": "#5AB1EF",
      "borderWidth": 0.5
    },
    "label": {
      "color": "#fff"
    },
    "selectedMode": false,
    "emphasis": {
      "label": {
        "show": true,
        "color": "#fff"
      },
      "itemStyle": {
        "areaColor": "#00aeef",
        "borderWidth": 1
      }
    }
  },
  "xAxis": {
    "splitLine": {
      "show": false
    },
    "axisLabel": {
      "show": true,
      "color": "#ffffff"
    }
  },
  "yAxis": {},
  "categoryAxis": {
    "axisLine": {
      "show": true,
      "lineStyle": {
        "color": "#ffffff",
        "opacity": 0.4
      }
    },
    "axisTick": {
      "show": true,
      "lineStyle": {
        "color": "#ffffff",
        "opacity": 0.4
      }
    },
    "axisLabel": {
      "show": true,
      "color": "#ffffff"
    },
    "splitLine": {
      "show": false
    },
    "splitArea": {
      "show": false,
      "areaStyle": {
        "color": ["rgba(250,250,250,0.3)", "rgba(200,200,200,0.3)"]
      }
    }
  },
  "valueAxis": {
    "axisLine": {
      "show": true,
      "lineStyle": {
        "color": "#ffffff",
        "opacity": 0.4
      }
    },
    "axisTick": {
      "show": false,
      "lineStyle": {
        "color": "#ffffff",
        "opacity": 0.4
      }
    },
    "axisLabel": {
      "show": true,
      "color": "#ffffff"
    },
    "splitLine": {
      "show": true,
      "lineStyle": {
        "color": "#ffffff",
        "opacity": 0.2
      }
    },
    "splitArea": {
      "show": false,
      "areaStyle": {
        "color": ["rgba(250,250,250,0.3)", "rgba(200,200,200,0.3)"]
      }
    }
  },
  "toolbox": {
    "iconStyle": {
      "borderColor": "#2ec7c9"
    },
    "emphasis": {
      "iconStyle": {
        "borderColor": "#18a4a6"
      }
    }
  },
  "legend": {
    "textStyle": {
      "color": "#ffffff"
    }
  },
  "tooltip": {
    "show": true,
    "axisPointer": {
      "lineStyle": {
        "color": "#008acd",
        "width": "1"
      },
      "crossStyle": {
        "color": "#008acd",
        "width": "1"
      }
    }
  },
  "timeline": {
    "lineStyle": {
      "color": "#008acd",
      "width": 1
    },
    "itemStyle": {
      "color": "#008acd",
      "borderWidth": 1
    },
    "controlStyle": {
      "color": "#008acd",
      "borderColor": "#008acd",
      "borderWidth": 0.5
    },
    "checkpointStyle": {
      "color": "#2ec7c9",
      "borderColor": "#2ec7c9"
    },
    "label": {
      "color": "#008acd"
    },
    "emphasis": {
      "itemStyle": {
        "color": "#a9334c"
      },
      "controlStyle": {
        "color": "#008acd",
        "borderColor": "#008acd",
        "borderWidth": 0.5
      },
      "label": {
        "color": "#008acd"
      }
    }
  },
  "visualMap": {
    "color": ["#5ab1ef", "#e0ffff"]
  }
}