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 echartsPluginvue
<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"]
}
}