将echarts封装成组件,达到只要调用方法,传入数据和相应的参数就能生成图表的效果,避免在项目中编写大量重复和累赘的echarts的配置代码,实现的思路如下:
下面对我自己封装的组件 EchartsGenerate 逐步解释
首先看template
<template>
<div>
<slot></slot>
</div>
</template>
这里使用插槽slot是因为,有时候图表的样式要根据页面进行调整,所以用插槽好方便自定义样式,就比如下面的代码:
<echarts-generate ref="echarts" name-data-key="title" value-data-key="score">
<!-- 中间的元素设置id -->
<div class="chart-container" id="chart-sample"></div>
</echarts-generate>
<style>
.chart-container {
position: relative;
height: 50vh;
overflow: hidden;
}
</style>
通过class来设置图表的样式
再看props
props: {
// 坐标对应的 传入数据指定的键值
nameDataKey: {
type: String,
default: "name",
},
// 数据对应的 传入数据指定的键值
valueDataKey: {
type: String,
default: "value",
},
// 图表标题
chartTitle: {
type: String,
},
},
nameDataKey和valueDataKey分别对应传入数据的键的名字和值和名字,比如,假如数据是这样
[
{
id: "physical1",
// label
title: "physical1",
// 排序
sort: 0,
// 分数
score: 11,
desc: "户外活动1",
// 分数
point: 25,
},
{
id: "taste1",
title: "taste1",
sort: 1,
score: 25,
desc: "味道1",
// 分数
point: 35,
},
{
id: "acceptance1",
title: "acceptance1",
sort: 2,
score: 55,
desc: "接受度1",
// 分数
point: 45,
},
];
那么在组件上设置 name-data-key="title" value-data-key="score"
那图表的横坐标是title对应的值,竖坐标是score对应的值,这个在后面会详细说。
最后在看主要的方法
首先看处理json数据的方法
generateChartInData(list) {
let chartInData = {};
// 保证list中的每个对象的属性名是相同的,也就是说一一对应
for (let attr1 in list[0]) {
// 以每个属性名为名字构建数组
chartInData[attr1] = [];
}
list.forEach(function (item, index) {
for (let attr2 in item) {
// chartInData[attr2] 为underfined时 初始化为空数组
if (!chartInData[attr2]) {
chartInData[attr2] = [];
}
chartInData[attr2].push(item[attr2]);
}
});
chartInData["length"] = list.length;
return chartInData;
},
上面方法实现的效果是将json数组转换为一个包含以属性名命名数组的对象,例如传入的数据是这个格式
[
{
id: "physical1",
// label
title: "physical1",
// 排序
sort: 0,
// 分数
score: 11,
desc: "户外活动1",
// 分数
point: 25,
},
{
id: "taste1",
title: "taste1",
sort: 1,
score: 25,
desc: "味道1",
// 分数
point: 35,
},
{
id: "acceptance1",
title: "acceptance1",
sort: 2,
score: 55,
desc: "接受度1",
// 分数
point: 45,
},
];
通过generateChartInData方法生成的数据如下:
{
id: ["physical1", "taste1","acceptance1"],
title: ["physical1", "taste1", "acceptance1"],
sort: [ 0,1,2],
score: [11,25, 55],
desc: ["户外活动1","味道1","接受度1"],
point: [25,35,45],
length: 3
}
将通过generateChartInData生成的数据,传入下面的方法中
// 生成图表数据
chartDataFactory(dataType, chartInData) {
let chartOutData = {};
switch (dataType) {
// 根据需求配置数据
case "listData":
// 生成数组数据
// 单个数据格式为 [1,2,3]
// 多个数据格式为 [[1,2,3],[1,2,4],[3,4,5]]
if (Array.isArray(chartInData) && chartInData.length > 0) {
let seriesList = [];
chartInData.forEach((item) => {
seriesList = [...seriesList, item[this.valueDataKey]];
});
chartOutData = {
xAxisData: chartInData[0][this.nameDataKey],
seriesData: seriesList,
};
} else {
chartOutData = {
xAxisData: chartInData[this.nameDataKey],
seriesData: chartInData[this.valueDataKey],
};
}
break;
case "objectData":
// 生成对象数据
// 数据格式为
// {name:"", value:""}
chartOutData = {
seriesData: this.generateObjectData(
chartInData,
this.nameDataKey,
this.valueDataKey
),
};
break;
}
return chartOutData;
},
// 生成对象数据源
// 属性为 name和value
// chartInData 生成的图表数据
// nameKey name对应的键
// valueKey value对应的键
generateObjectData(chartInData, nameKey, valueKey) {
let objectList = [];
for (var i = 0; i < chartInData["length"]; i++) {
let objectItem = {
name: "",
value: "",
};
objectItem.name = chartInData[nameKey][i];
objectItem.value = chartInData[valueKey][i];
objectList = [...objectList, objectItem];
}
return objectList;
},
在chartDataFactory这个方法里面就用到了前面提到的nameDataKey和valueDataKey。然后这个方法处理了多条数据的,可以参考下
下面是将echarts图表的配置都封装在getOption这个方法里面,同时把chartDataFactory生成的数据传入这个方法
// 配置option
getOption(optionType, chartOutData) {
let option = {};
let seriesList = [];
// 如果seriesData有数据,且seriesData的第一个元素是数组,说明传入的数据是多对象数组
// 否则说明传入的是单个对象
if (
chartOutData.seriesData.length > 0 &&
Array.isArray(chartOutData.seriesData[0])
) {
seriesList = chartOutData.seriesData.map((item) => {
return (item = {
data: item,
});
});
} else {
seriesList = [
{
data: chartOutData.seriesData,
},
];
}
switch (optionType) {
// 基础折线图
case "lineOption":
// 遍历后添加其他属性
seriesList = seriesList.map((item) => {
return (item = {
data: item.data,
type: "line",
});
});
option = {
title: {
text: this.chartTitle,
},
xAxis: {
type: "category",
data: chartOutData.xAxisData,
},
yAxis: {
type: "value",
},
series: seriesList,
};
break;
// 基础柱状图
case "barOption":
seriesList = seriesList.map((item) => {
return (item = {
data: item.data,
type: "bar",
});
});
option = {
xAxis: {
type: "category",
data: chartOutData.xAxisData,
},
yAxis: {
type: "value",
},
series: seriesList,
};
break;
// 基础饼图
case "pieOption":
option = {
title: {
text: this.chartTitle,
left: "center",
},
tooltip: {
trigger: "item",
},
legend: {
orient: "vertical",
left: "left",
},
series: [
{
name: "Access From",
type: "pie",
radius: "50%",
data: chartOutData.seriesData,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: "rgba(0, 0, 0, 0.5)",
},
},
},
],
};
break;
}
return option;
},
后续开发在getOption这个方法里添加配置
最后是生成图表的方法
// 生成图表
// domRef 图表标识 id
// dataType 图表数据类型
// optionType option类型
// list 要生成图表的数据列表
generateChart(domRef, dataType, optionType, list) {
let chartInData = null;
if (document.getElementById(domRef) || this.$refs[domRef]) {
let chartDom = this.initChartDom(domRef);
// 存在表格的话先进行销毁
if (chartDom) {
let chart = this.getChart(domRef);
// 存在表格的话先进行销毁
if (chart) {
chart.dispose();
}
// 如果传入数据为空,则返回
if(list.length<=0){
return
}
// 如果list的子元素是数组
if ( Array.isArray(list[0])) {
// list是包含多条数据的数组
chartInData = list.map((item) => {
// item 是数据列表
return this.generateChartInData(item);
});
}
// 如果list的子元素不是数组时对象
if (!Array.isArray(list[0])) {
chartInData = this.generateChartInData(list);
}
let data = this.chartDataFactory(dataType, chartInData);
let option = this.getOption(optionType, data);
if (option && typeof option === "object") {
chartDom.setOption(option);
}
}
}
},
其中图表标识最好是通过id,使用ref会没效果
完整代码如下
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
name: "EchartsGenerate",
data() {
return {
instances: {},
chartDom: null,
};
},
props: {
// 坐标对应的 传入数据指定的键值
nameDataKey: {
type: String,
default: "name",
},
// 数据对应的 传入数据指定的键值
valueDataKey: {
type: String,
default: "value",
},
// 图表标题
chartTitle: {
type: String,
},
},
created() {},
mounted() {},
components: {},
methods: {
// 用于用户数据不需要处理
// 直接传入数据源生成图表
generateChartWithData(domRef, optionType, data) {
if (document.getElementById(domRef) || this.$refs[domRef]) {
let chartDom = this.initChartDom(domRef);
// 存在表格的话先进行销毁
if (chartDom) {
let chart = this.getChart(domRef);
// 存在表格的话先进行销毁
if (chart) {
chart.dispose();
}
let option = this.getOption(optionType, data);
if (option && typeof option === "object") {
chartDom.setOption(option);
}
}
}
},
// 生成图表
// domRef 图表标识 id
// dataType 图表数据类型
// optionType option类型
// list 要生成图表的数据列表
generateChart(domRef, dataType, optionType, list) {
let chartInData = null;
if (document.getElementById(domRef) || this.$refs[domRef]) {
let chartDom = this.initChartDom(domRef);
// 存在表格的话先进行销毁
if (chartDom) {
let chart = this.getChart(domRef);
// 存在表格的话先进行销毁
if (chart) {
chart.dispose();
}
// 如果传入数据为空,则返回
if(list.length<=0){
return
}
// 如果list的子元素是数组
if ( Array.isArray(list[0])) {
// list是包含多条数据的数组
chartInData = list.map((item) => {
// item 是数据列表
return this.generateChartInData(item);
});
}
// 如果list的子元素不是数组时对象
if (!Array.isArray(list[0])) {
chartInData = this.generateChartInData(list);
}
let data = this.chartDataFactory(dataType, chartInData);
let option = this.getOption(optionType, data);
if (option && typeof option === "object") {
chartDom.setOption(option);
}
}
}
},
getCanvas(item) {
if (this.isDomSupported() && typeof item === "string") {
item = document.getElementById(item) || this.$refs[item];
} else if (item && item.length) {
item = item[0];
}
if (item && item.canvas) {
item = item.canvas;
}
return item;
},
// 获取图表
getChart(key) {
const canvas = this.getCanvas(key);
return Object.values(this.instances)
.filter((c) => c.canvas === canvas)
.pop();
},
isDomSupported() {
return typeof window !== "undefined" && typeof document !== "undefined";
},
// 初始化图表dom
// 可以通过id和ref两种属性进行初始化
initChartDom(domRef) {
let chartDom = null;
let initDom = document.getElementById(domRef) || this.$refs[domRef];
chartDom = this.$echarts.init(initDom, null, {
renderer: "canvas",
useDirtyRect: false,
});
return chartDom;
},
// 生成图表数据
chartDataFactory(dataType, chartInData) {
let chartOutData = {};
switch (dataType) {
// 根据需求配置数据
case "listData":
// 生成数组数据
// 单个数据格式为 [1,2,3]
// 多个数据格式为 [[1,2,3],[1,2,4],[3,4,5]]
if (Array.isArray(chartInData) && chartInData.length > 0) {
let seriesList = [];
chartInData.forEach((item) => {
seriesList = [...seriesList, item[this.valueDataKey]];
});
chartOutData = {
xAxisData: chartInData[0][this.nameDataKey],
seriesData: seriesList,
};
} else {
chartOutData = {
xAxisData: chartInData[this.nameDataKey],
seriesData: chartInData[this.valueDataKey],
};
}
break;
case "objectData":
// 生成对象数据
// 数据格式为
// {name:"", value:""}
chartOutData = {
seriesData: this.generateObjectData(
chartInData,
this.nameDataKey,
this.valueDataKey
),
};
break;
}
return chartOutData;
},
// 生成对象数据源
// 属性为 name和value
// chartInData 生成的图表数据
// nameKey name对应的键
// valueKey value对应的键
generateObjectData(chartInData, nameKey, valueKey) {
let objectList = [];
for (var i = 0; i < chartInData["length"]; i++) {
let objectItem = {
name: "",
value: "",
};
objectItem.name = chartInData[nameKey][i];
objectItem.value = chartInData[valueKey][i];
objectList = [...objectList, objectItem];
}
return objectList;
},
// 生成图表需要的数据
// list - 对象数组
generateChartInData(list) {
let chartInData = {};
// 保证list中的每个对象的属性名是相同的,也就是说一一对应
for (let attr1 in list[0]) {
// 以每个属性名为名字构建数组
chartInData[attr1] = [];
}
list.forEach(function (item, index) {
for (let attr2 in item) {
// chartInData[attr2] 为underfined时 初始化为空数组
if (!chartInData[attr2]) {
chartInData[attr2] = [];
}
chartInData[attr2].push(item[attr2]);
}
});
chartInData["length"] = list.length;
return chartInData;
},
// 配置option
getOption(optionType, chartOutData) {
let option = {};
let seriesList = [];
// 如果seriesData有数据,且seriesData的第一个元素是数组,说明传入的数据是多对象数组
// 否则说明传入的是单个对象
if (
chartOutData.seriesData.length > 0 &&
Array.isArray(chartOutData.seriesData[0])
) {
seriesList = chartOutData.seriesData.map((item) => {
return (item = {
data: item,
});
});
} else {
seriesList = [
{
data: chartOutData.seriesData,
},
];
}
switch (optionType) {
case "lineOption":
// 遍历后添加其他属性
seriesList = seriesList.map((item) => {
return (item = {
data: item.data,
type: "line",
});
});
option = {
title: {
text: this.chartTitle,
},
xAxis: {
type: "category",
data: chartOutData.xAxisData,
},
yAxis: {
type: "value",
},
series: seriesList,
};
break;
case "barOption":
seriesList = seriesList.map((item) => {
return (item = {
data: item.data,
type: "bar",
});
});
option = {
xAxis: {
type: "category",
data: chartOutData.xAxisData,
},
yAxis: {
type: "value",
},
series: seriesList,
};
break;
case "pieOption":
option = {
title: {
text: this.chartTitle,
left: "center",
},
tooltip: {
trigger: "item",
},
legend: {
orient: "vertical",
left: "left",
},
series: [
{
name: "Access From",
type: "pie",
radius: "50%",
data: chartOutData.seriesData,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: "rgba(0, 0, 0, 0.5)",
},
},
},
],
};
break;
}
return option;
},
},
};
</script>
<style>
</style>
使用的示例代码如下:
<template>
<div>
<!-- 子组件设置ref -->
<echarts-generate ref="echarts" name-data-key="title" value-data-key="score">
<!-- 中间的元素设置id -->
<div class="chart-container" id="chart-sample"></div>
</echarts-generate>
</div>
</template>
<script>
import EchartsGenerate from "@/components/charts/EchartsGenerate";
export default {
name: "EchartsSample",
data() {
return {
//传入的json数据
chartData: [],
};
},
created() {},
async mounted() {
await this.getData();
// 通过调用子组件的方法生成图表,设置id获取元素
// 无法通过ref获取
this.$refs.echarts.generateChart(
"chart-sample",
"listData",
"barOption",
this.chartData
);
},
components: {
"echarts-generate": EchartsGenerate,
},
methods: {
async getData() {
// 多个数据
// this.chartData = [
// [
// {
// id: "physical-activity",
// // label
// title: "physical activity",
// // 排序
// sort: 0,
// // 分数
// score: 13,
// desc: "户外活动",
// // 分数
// point: 20,
// },
// {
// id: "taste",
// title: "taste",
// sort: 1,
// score: 20,
// desc: "味道",
// // 分数
// point: 30,
// },
// {
// id: "acceptance",
// title: "acceptance",
// sort: 2,
// score: 50,
// desc: "接受度",
// // 分数
// point: 40,
// },
// ],
// [
// {
// id: "physical1",
// // label
// title: "physical1",
// // 排序
// sort: 0,
// // 分数
// score: 11,
// desc: "户外活动1",
// // 分数
// point: 25,
// },
// {
// id: "taste1",
// title: "taste1",
// sort: 1,
// score: 25,
// desc: "味道1",
// // 分数
// point: 35,
// },
// {
// id: "acceptance1",
// title: "acceptance1",
// sort: 2,
// score: 55,
// desc: "接受度1",
// // 分数
// point: 45,
// },
// ]
// ];
// 单个数据
this.chartData =
[
{
id: "physical1",
// label
title: "physical1",
// 排序
sort: 0,
// 分数
score: 11,
desc: "户外活动1",
// 分数
point: 25,
},
{
id: "taste1",
title: "taste1",
sort: 1,
score: 25,
desc: "味道1",
// 分数
point: 35,
},
{
id: "acceptance1",
title: "acceptance1",
sort: 2,
score: 55,
desc: "接受度1",
// 分数
point: 45,
},
];
},
},
};
</script>
<style>
.chart-container {
position: relative;
height: 50vh;
overflow: hidden;
}
</style>