当前位置: 首页 > news >正文

基于Echarts进行图表组件的封装

什么是Echarts

是一个使用js实现的开源可视库,提供了多种图表,但是当我们在项目中进行使用的时候可能就是需要进行一系列的相关配置如: 标题,类型,x轴,y轴等,当我们使用较为频繁的时候就容易导致代码的冗余,并且整体将echarts进行安装引入也是比较大的,我们可以按照自己的需要进行对应组件的引入

Echarts的使用

安装Echarts

Echarts官网-安装echarst并实现按需引入Echarts图表和组件

基于Echarts相关配置进行组件的封装

// components/BarChart.vue
/* @/components/BarChart.vue */<template><div ref="chartDom" :style="{ height: getHeight }"></div>
</template><script setup lang="ts">
import { echarts, type ECOption } from '@/utils/echarts'
import { ref, shallowRef, watch, computed, onMounted, onBeforeUnmount, type ShallowRef, type Ref } from 'vue'
import type { EChartsType } from 'echarts/types/dist/core'
import type {XAXisOption, YAXisOption, LegendComponentOption, BarSeriesOption, DataZoomComponentOption
} from 'echarts/types/dist/shared'
import resize from '@/utils/resize'
import type { ChartSetting } from '@/types/ChartData'//定义组件属性
const props = withDefaults(defineProps<{//数据data?: Array<string | number>//x轴数据xAxisData?: Array<string>//图表标题title?: string//系列配置series?: Array<BarSeriesOption>//x轴配置xAxis?: Array<XAXisOption>//y轴配置yAxis?: Array<YAXisOption>//图例配置legend?: LegendComponentOption//区域缩放配置dataZoom?: Array<DataZoomComponentOption>//图形高度height?: number | string//数据集datasetSource?: Array<any>//综合配置options: ChartSetting}>(),{data: () => [],xAxisData: () => [],title: 'ECharts柱状图',}
)
//要渲染的Dom元素
const chartDom: Ref<HTMLDivElement | null> = ref(null)
//渲染的chart对象要用shallowRef
const chart: ShallowRef<EChartsType | null | undefined> = shallowRef(null)
//高度同时支持string和number
const getHeight = computed(() => {return typeof props.height === 'number' ? props.height + 'px' : props.height
})
//监听数据变化,重新绘制
watch(() => props,() => {drawChart()},{ deep: true }
)//绘制
async function drawChart() {let datasetSource: Array<any> | undefined = props.datasetSource,series: Array<BarSeriesOption> = [],xAxisData: Array<string> = props.xAxisDatalet chartType = props.options.chartType || 'bar'; // 默认为柱状图,如果需要折线图则设置为 'line'if (props.options) {if (props.options.apiMethod) {//获取接口数据作为数据集let allx = await props.options.apiMethod()datasetSource = allx.dataif (props.options.xProp) {//根据配置的x轴属性名生成x轴数据xAxisData = []datasetSource?.forEach(data => {xAxisData.push(data[props.options.xProp])})}}if (props.options.seriesOption) {props.options.seriesOption.forEach((opt: any) => {series.push({name: '车牌',barMaxWidth: 30,emphasis: { focus: 'series' },label: { show: true, position: 'top', color: 'inherit' },...opt,type: chartType, // 设置图表类型})})}}// else {//   series = props.series ? props.series : [{//     name: '车牌',//     type: 'bar',//     barMaxWidth: 30,//     emphasis: { focus: 'self' },//     label: { show: true, position: 'inside', color: '#fff' },//     data: props.data//   }]// }let xAxis: Array<XAXisOption> = props.xAxis ? props.xAxis : [{type: 'category',axisTick: { show: false },data: xAxisData}]let yAxis: Array<YAXisOption> = props.yAxis ? props.yAxis : [{ type: 'value', minInterval: 1 }]let legend: LegendComponentOption = props.legend ? props.legend : {show: true,type: 'scroll',orient: 'horizontal',top: 25,left: 'center'}let dataZoom: Array<DataZoomComponentOption> = props.dataZoom ? props.dataZoom : []const options: ECOption = {backgroundColor: '',title: {text: props.title},tooltip: {trigger: 'axis',axisPointer: {type: 'shadow'},// appendToBody:true},legend: legend,grid: {left: 10,right: 10,bottom: props.dataZoom ? 40 : 10,containLabel: true},toolbox: {show: true,feature: {magicType: { type: ['line', 'bar'] },dataView: { readOnly: false },saveAsImage: {}}},xAxis: xAxis,yAxis: yAxis,dataZoom: dataZoom,dataset: {source: datasetSource},series: series}//开启notMerge保证配置数据不会叠加chart.value?.setOption(options, { notMerge: true });
}const { chartObject, addResize, removeResize } = resize()
onMounted(() => {chart.value = echarts.init(chartDom.value);drawChart()//添加窗口自适应chartObject.value = chart.valueaddResize()
})onBeforeUnmount(() => {removeResize()chart.value?.dispose()
})
</script>

在父组件中使用封装好的组件

<template><el-row :gutter="16"><el-col v-for="(item, index) in chartOptionList" :key="index" :lg="12" style="margin-bottom: 10px;"><el-card><BarChart :title="item.title" :height="item.height || 300" :options="item.chartOption" /></el-card></el-col></el-row>
</template><script setup lang="ts">
import BarChart from '@/components/BarChart.vue'
import type { ChartSetting } from '@/types/ChartData'
import { ref } from 'vue'
//axios api请求方法
import { getPayRecord, delicacies } from '@/services/http'interface ChartCard {//标题title?: string,//高度height?: number,//图表y轴配置yAxis?: Array<any>chartOption: ChartSetting
}
const chartOptionList = ref<ChartCard[]>([{title: '图1',chartOption: {apiMethod: () => getPayRecord(),xProp: 'name',chartType: 'bar', // 设置为 'line' 以生成折线图seriesOption: [{ name: '数量', encode: { x: 'name', y: 'count' } }]}},{title: '图2',chartOption: {apiMethod: () => delicacies(),xProp: 'name',chartType: 'line', // 设置为 'line' 以生成折线图seriesOption: [{ name: '销量', encode: { x: 'name', y: 'saleNum' } },{ name: '好评', encode: { x: 'name', y: 'positiveReviews' } },]}},
])
</script>

效果展示

在这里插入图片描述
其他:
在这里插入图片描述

// types.ChartData.ts
export interface SeriesData {name?: stringdata?: number[]color?: stringyAxisIndex?: numberradius?: string | string[]itemStyle?: anyencode?: {x?: stringy?: stringitemName?: stringvalue?: string}
}export interface ChartSetting {//api接口方法apiMethod: Function// x轴属性名xProp: stringchartType: string|undefined // 设置为 'line' 以生成折线图//图例配置seriesOption: SeriesData[]
}
// reqTypes.ts
import axiosInstance from '../utils/request'export interface ApiResult<T> {code: numbermessage: stringdata: T
}
export async function get<T>(url: string, params?: any): Promise<ApiResult<T>> {const response = await axiosInstance.get<ApiResult<T>>(url, { params })return response.data
}
export async function post<T>(url: string, data?: any): Promise<ApiResult<T>> {const response = await axiosInstance.post<ApiResult<T>>(url, data)return response.data
}
export async function put<T>(url: string, data?: any): Promise<ApiResult<T>> {const response = await axiosInstance.put<ApiResult<T>>(url, data)return response.data
}
export async function del<T>(url: string, params?: any): Promise<ApiResult<T>> {const response = await axiosInstance.delete<ApiResult<T>>(url, { params })return response.data
}// utils.echarts.ts
/* @/utils/echarts.ts */
/**在ts中实现按需引入echarts 图表和组件*/
import * as Echarts from 'echarts/core'
import { BarChart, PieChart, LineChart } from 'echarts/charts'
import {// 标题组件TitleComponent,// 图例组件LegendComponent,// 提示框组件TooltipComponent,// 坐标系网格组件GridComponent,// 数据集组件DatasetComponent,// 内置数据转换器组件 (filter, sort)TransformComponent,// 工具栏组件ToolboxComponent,// 区域缩放组件DataZoomComponent,// 原生图形元素组件
} from 'echarts/components'
// 标签自动布局、全局过渡动画等特性
import { LabelLayout, UniversalTransition } from 'echarts/features'
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步 
// 使用canvas进行渲染 也可以使用 SVGRenderer 进行渲染
import { CanvasRenderer } from 'echarts/renderers'
import type {// 系列类型的定义后缀都为 SeriesOptionBarSeriesOption, // 柱状图PieSeriesOption, //饼图LineSeriesOption, // 折线/面积图
} from 'echarts/charts'
import type {// 组件类型的定义后缀都为 ComponentOptionTitleComponentOption,TooltipComponentOption,GridComponentOption,DatasetComponentOption,ToolboxComponentOption,DataZoomComponentOption,GraphicComponentOption,
} from 'echarts/components'
import type { ComposeOption } from 'echarts/core'// 注册必须的组件
Echarts.use([TitleComponent,LegendComponent,TooltipComponent,GridComponent,DatasetComponent,TransformComponent,ToolboxComponent,DataZoomComponent,GridComponent,LabelLayout,UniversalTransition,CanvasRenderer,BarChart,PieChart,LineChart,
])// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
export type ECOption = ComposeOption<| BarSeriesOption| PieSeriesOption| LineSeriesOption| TitleComponentOption| TooltipComponentOption| GridComponentOption| DatasetComponentOption| ToolboxComponentOption| DataZoomComponentOption| GraphicComponentOption
>export const echarts = Echarts
// request.ts  这里的baseUrl 是基于EasyMock进行模拟的数据
/*** 使用 axios.create() 创建了一个 axios 实例,并设置了基本 URL 和请求超时时间。我们还添加了请求和响应拦截器**/
import axios, {AxiosInstance,AxiosResponse,InternalAxiosRequestConfig,
} from 'axios'
const axiosInstance: AxiosInstance = axios.create({baseURL:'https://mock.presstime.cn/mock/6686990ecb2f4f1158f2a7b8/screen-big-sys',timeout: 5000,
})
// 添加请求拦截器
// 自定义请求头
axiosInstance.interceptors.request.use((config: InternalAxiosRequestConfig) => {// 在发送请求之前做些什么const token = localStorage.getItem('ACCESS_TOKEN')if (token) {// 配置请求头config.headers.Authorization = 'Bearer ' + token}return config},(error: any) => {// 处理请求错误return Promise.reject(error)}
)
// 添加响应拦截器
axiosInstance.interceptors.response.use((response: AxiosResponse) => {// 对响应数据做点什么return response},(error: any) => {// 处理响应错误return Promise.reject(error)}
)
export default axiosInstance
// resize.ts
/*** 实现页面大小的自适应* ECharts提供的API会发现,它提供了一个resize 方法 重新渲染图表结合window.addEventListener* 功能:echarts图表自适应窗口变化封装方法*/
//echarts图表自适应窗口变化封装方法
import { ref } from 'vue'
import { debounce } from 'lodash'export default function () {//echarts图的实例const chartObject = ref()//使用防抖debounce函数,减少resize的次数const chartResizeHandler = debounce(() => {if (chartObject.value) {chartObject.value.resize()}}, 100)const initResizeEvent = () => {//添加窗口大小变化监听window.addEventListener('resize', chartResizeHandler)}const destroyResizeEvent = () => {//移除窗口大小变化监听window.removeEventListener('resize', chartResizeHandler)}const addResize = () => {initResizeEvent()}const removeResize = () => {destroyResizeEvent()}return {chartObject,addResize,removeResize,}
}
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import { createPinia } from 'pinia'
import 'element-plus/dist/index.css'
// 实现持久化标记
import { createPersistedState } from 'pinia-plugin-persistedstate'const app = createApp(App)
const pinia = createPinia()
// 使用pinia-plugin-persistedstate插件
pinia.use(createPersistedState())app.use(ElementPlus)
app.mount('#app')

相关文章:

  • 在Linux/Debian/Ubuntu中出现“Could not get lock /var/lib/dpkg/lock-frontend”问题的解决办法
  • maven项目、idea抽风问题解决
  • 【React性能优化】父组件渲染如何避免子组件不必要的渲染
  • xcrun: error: unable to find utility “simctl“, not a developer tool or in PATH
  • 从硬件角度看Linux的内存管理
  • Map Set(Java篇详解)
  • 自定义代理编辑控件类TSpinBoxDelegate
  • 【区分vue2和vue3下的element UI PageHeader 页头组件,分别详细介绍属性,事件,方法如何使用,并举例】
  • Linux之文本三剑客
  • 基于单片机技术的按键扫描电路分析
  • 缠中说禅李彪08年“假死”具体原因探讨
  • $(this) 和 this 关键字在 jQuery 中有何不同?
  • centos7安装mqtt服务端
  • vue中数组出现__ob__: Observer属性,导致不能正确使用问题解决
  • 一个人的开发团队:前后端与调动AI
  • [微信小程序] 使用ES6特性Class后出现编译异常
  • Android 初级面试者拾遗(前台界面篇)之 Activity 和 Fragment
  • Angular 响应式表单之下拉框
  • CSS魔法堂:Absolute Positioning就这个样
  • GitUp, 你不可错过的秀外慧中的git工具
  • JS基础之数据类型、对象、原型、原型链、继承
  • Swoft 源码剖析 - 代码自动更新机制
  • uva 10370 Above Average
  • 持续集成与持续部署宝典Part 2:创建持续集成流水线
  • 如何邀请好友注册您的网站(模拟百度网盘)
  • 使用 Xcode 的 Target 区分开发和生产环境
  • #数学建模# 线性规划问题的Matlab求解
  • #我与Java虚拟机的故事#连载19:等我技术变强了,我会去看你的 ​
  • (a /b)*c的值
  • (Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六
  • (zt)基于Facebook和Flash平台的应用架构解析
  • (附源码)计算机毕业设计ssm本地美食推荐平台
  • (企业 / 公司项目)前端使用pingyin-pro将汉字转成拼音
  • (转载)PyTorch代码规范最佳实践和样式指南
  • .NET : 在VS2008中计算代码度量值
  • .Net Core与存储过程(一)
  • .NET 实现 NTFS 文件系统的硬链接 mklink /J(Junction)
  • .NET使用HttpClient以multipart/form-data形式post上传文件及其相关参数
  • .Net组件程序设计之线程、并发管理(一)
  • 。。。。。
  • ?
  • @require_PUTNameError: name ‘require_PUT‘ is not defined 解决方法
  • [BZOJ] 2427: [HAOI2010]软件安装
  • [C#]手把手教你打造Socket的TCP通讯连接(一)
  • [C++]:for循环for(int num : nums)
  • [cb]UIGrid+UIStretch的自适应
  • [C和指针].(美)Kenneth.A.Reek(ED2000.COM)pdf
  • [dfs搜索寻找矩阵中最长递减序列]魔法森林的秘密路径
  • [Fri 26 Jun 2015 ~ Thu 2 Jul 2015] Deep Learning in arxiv
  • [iOS]GCD(一)
  • [JS真好玩] 掘金创作者必备: 监控每天是谁取关了你?
  • [LeetCode] 93. Restore IP Addresses 复原IP地址
  • [nlp] 损失缩放(Loss Scaling)loss sacle
  • [Python] scikit-learn之mean_squared_error函数(Mean Squared Error(MSE))介绍和使用案例
  • [python]python os模块 常用命令