閒時基於上次封裝的圖表元件做了一層最佳化,話不多說,直接上程式碼吧
Step-1: 新建一個config.ts檔案
export const config = {
labelKey: "name",
valueKey: "value",
};
export type chartDataItemType = {
[key: string]: string | number;
};
// 當前支援顯示的圖表型別(折線,柱形)
export type chartSupportType = "line" | "bar";
export type chartDataType = {
name: string;
data: chartDataItemType[];
type: chartSupportType;
labelKey?: string;
valueKey?: string;
options?: any;
};
export type chartOptionsType = {
[key: string]: any;
};
// 基礎圖例顏色資料
export const baseColorArr = [
"#3CD7D7",
"#F9CB28",
"#A089FF",
"#5CC4FF",
"#FF9292",
"#5CC4FF",
"#FF8AEC",
"#FFB35B",
];
// 陰影區域顏色資料
export const areaColorArr = [
"#C5F3F3",
"#FDEFBF",
"#dcd3ff",
"#bde7ff",
"#ffd3d3",
"#bde7ff",
"#ffd0f7",
"#ffe0bd",
];
// 圖例頂部字型大小
export const defaultLabelFontsize = 8;
export const getColor = (index, type: "default" | "area" = "default") => {
const colorArr = type === "default" ? baseColorArr : areaColorArr;
const colorIndex = index % colorArr.length;
return colorArr[colorIndex];
};
// 圖表底部捲軸預設樣式
export const defaultDataZoomOptions: any[] = [
{
type: "slider",
show: true,
xAxisIndex: [0],
start: 0,
end: 30,
textStyle: {
color: "#ccd7d7",
},
},
];
// 獲取捲軸樣式
export const getDataZoom = (seriesLength, showZoomLength) => {
if (seriesLength > showZoomLength) {
return defaultDataZoomOptions;
} else {
return [];
}
};
// 獲取折線圖、柱形圖基礎配置
export const getLineBarBaseOptions = (seriesLength, showZoomLength = 7) => {
return {
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
},
},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true,
},
yAxis: {
type: "value",
},
dataZoom: getDataZoom(seriesLength, showZoomLength),
};
};
// 獲取折線系列配置
export const getLineSeriesOptions = (index) => {
return {
tooltip: {
trigger: "axis",
},
areaStyle: {
color: getColor(index, "area"),
},
smooth: true,
label: {
show: true, //開啟顯示
position: "top", //在上方顯示
//數值樣式
color: getColor(index, "default"),
fontSize: defaultLabelFontsize,
},
};
};
// 獲取柱形系列配置
export const getBarSeriesOptions = (index) => {
return {
barWidth: 10,
label: {
show: true, //開啟顯示
position: "top", //在上方顯示
//數值樣式
color: getColor(index, "default"),
fontSize: defaultLabelFontsize,
},
itemStyle: {
borderRadius: [6, 6, 0, 0],
},
};
};
Step-2: 新建一個index.vue檔案
<!--
/**
* Author: 前端小高
* Date: 2023-05-08 14:58
* Desc: MyChart 檔案描述
*/
-->
<template>
<div ref="chartDom" class="chart-dom"></div>
</template>
<script name="MyChart" lang="ts" setup>
import { ref, computed, watch, onMounted, onBeforeUnmount, PropType, nextTick } from "vue";
import * as ECharts from "echarts";
import { debounce } from "lodash";
import {
baseColorArr,
chartDataType,
chartOptionsType,
chartSupportType,
chartDataItemType,
getLineBarBaseOptions,
getLineSeriesOptions,
getBarSeriesOptions,
config,
} from "./config";
const props = defineProps({
color: {
type: Array as PropType<string[]>,
default: () => {
return baseColorArr;
},
},
// 超過該數字時顯示捲軸
showZoomLimit: {
type: Number,
default: 7,
},
// 座標軸是否兩邊留白
isBoundaryGap: {
type: Boolean,
default: false,
},
baseOptions: {
type: Object as PropType<chartOptionsType>,
default: () => {
return {};
},
},
data: {
type: Array as PropType<chartDataType[]>,
require: true,
},
});
const emits = defineEmits(["chart-click"]);
const initSeriesData = () => {
if (props?.data?.length) {
getXAxisDataArr();
const tempSeriesArr = props?.data.map((item: chartDataType, index: number) => {
const chartOptions = getSeriesItemOptions(item, index);
return {
name: item.name,
data: getValueArr(item.data, item?.valueKey),
type: item.type,
...chartOptions,
};
});
const tempLegendDataArr = props?.data.map((item: chartDataType) => item.name);
legendDataArr.value = tempLegendDataArr;
seriesArr.value = tempSeriesArr;
}
};
const getXAxisDataArr = () => {
const labelName = props?.data?.[0]?.labelKey || config.labelKey;
const xDataArr = props?.data?.[0]?.data.map((item) => item[labelName]);
dataArr.value = xDataArr as string[];
};
const callbackMap = {
line: getLineSeriesOptions,
bar: getBarSeriesOptions,
};
const getSeriesItemDefaultOptions = (type: chartSupportType = "line") => {
return callbackMap[type];
};
const getChartBaseOptions = () => {
return getLineBarBaseOptions;
};
const getSeriesItemOptions = (chartItem: chartDataType, index = 0) => {
const cb = getSeriesItemDefaultOptions(chartItem.type);
const defaultOptions = cb(index);
if (chartItem?.options) {
const mergeOptions = Object.assign({}, defaultOptions, chartItem?.options);
return mergeOptions;
} else {
return defaultOptions;
}
};
const getBaseChartOptions = () => {
const cb = getChartBaseOptions();
const baseOptions = cb(dataArr.value.length, props.showZoomLimit);
if (props?.baseOptions) {
const mergeOptions = Object.assign({}, baseOptions, props?.baseOptions);
return mergeOptions;
} else {
return baseOptions;
}
};
const getValueArr = (arr: chartDataItemType[], valueKey?: string) => {
const keyName = valueKey ? valueKey : config.valueKey;
return arr.map((item) => Number(item[keyName]));
};
const resizeHandler = () => {
chartExample.resize();
};
const resizeHandlerOrigin = debounce(resizeHandler, 300);
const dataArr = ref<any[]>([]);
const seriesArr = ref<any[]>([]);
const legendDataArr = ref<string[]>([]);
const getOptions = computed(() => {
const baseOptions = getBaseChartOptions();
const options = {
color: props.color,
...baseOptions,
legend: {
data: legendDataArr.value,
},
xAxis: {
type: "category",
data: dataArr.value,
boundaryGap: props.isBoundaryGap,
axisTick: {
alignWithLabel: true,
},
},
series: seriesArr.value,
};
return options;
});
watch(
() => props.data,
() => {
init();
},
{
deep: true,
}
);
const chartDom = ref();
let chartExample: any = null;
const initChart = () => {
if (chartExample) {
// 若存在圖表例項,則先執行銷燬操作
chartExample.dispose();
}
nextTick(() => {
chartExample = ECharts.init(chartDom.value);
const options = getOptions.value;
chartExample.setOption(options, true);
initResizerListener();
initChartEvent();
});
};
const initResizerListener = () => {
window.removeEventListener("resize", resizeHandlerOrigin);
window.addEventListener("resize", resizeHandlerOrigin);
};
const initChartEvent = () => {
cancelClickEvent();
chartExample.on("click", (params) => {
emits("chart-click", params);
});
};
const cancelClickEvent = () => {
chartExample.off("click");
};
const init = () => {
initSeriesData();
initChart();
};
onMounted(() => {
init();
});
onBeforeUnmount(() => {
cancelClickEvent();
window.removeEventListener("resize", resizeHandlerOrigin);
chartExample.dispose();
});
</script>
<style lang="scss" scoped>
.chart-dom {
height: 300px;
}
</style>
Step-3:頁面下的引用方式
<!--
/**
* Author: 前端小高
* Date: 2023-05-08 14:55
* Desc: ChartComp 圖表元件
*/
-->
<template>
<my-chart :data="dataArr"></my-chart>
</template>
<script name="ChartComp" lang="ts" setup>
import { ref } from "vue";
import { chartDataType } from "@/common/MyChart/config";
const dataArr = ref<chartDataType[]>([
{
name: "測試資料列1",
type: "line",
data: [
{
name: "1.1",
value: 1,
},
{
name: "1.2",
value: 2,
},
{
name: "1.3",
value: 3,
},
{
name: "1.1",
value: 1,
},
],
},
]);
</script>
頁面展示效果如下
程式碼Gitee地址
對比基礎版本的echarts封裝元件,2.0版本做了如下最佳化
- 只需傳入渲染資料及展示資料型別,即可渲染
- 其餘圖表配置可以按需傳入
- (不足之處:當前只做了折線圖及柱形圖的支援,其他型別圖表各位可以按需擴充套件)
Remark: 如果有幫助到各位或者給各位封裝元件提供了一點思路,不妨點個贊吧。。。