将echarts封装成组件,达到只要调用方法,传入数据和相应的参数就能生成图表的效果,避免在项目中编写大量重复和累赘的echarts的配置代码,实现的思路如下:
接口返回的一般是json数据,所以首先要将json数据进行处理,处理成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:vh; | |
overflow: hidden; | |
} | |
</style> |
通过class来设置图表的样式
再看props
props: { | |
// 坐标对应的 传入数据指定的键值 | |
nameDataKey: { | |
type: String, | |
default: "name", | |
}, | |
// 数据对应的 传入数据指定的键值 | |
valueDataKey: { | |
type: String, | |
default: "value", | |
}, | |
// 图表标题 | |
chartTitle: { | |
type: String, | |
}, | |
}, |
nameDataKey和valueDataKey分别对应传入数据的键的名字和值和名字,比如,假如数据是这样
[ | |
{ | |
id: "physical", | |
// label | |
title: "physical", | |
// 排序 | |
sort:, | |
// 分数 | |
score:, | |
desc: "户外活动", | |
// 分数 | |
point:, | |
}, | |
{ | |
id: "taste", | |
title: "taste", | |
sort:, | |
score:, | |
desc: "味道", | |
// 分数 | |
point:, | |
}, | |
{ | |
id: "acceptance", | |
title: "acceptance", | |
sort:, | |
score:, | |
desc: "接受度", | |
// 分数 | |
point:, | |
}, | |
]; |
那么在组件上设置 name-data-key="title" value-data-key="score" 那图表的横坐标是title对应的值,竖坐标是score对应的值,这个在后面会详细说。
最后在看主要的方法
首先看处理json数据的方法
generateChartInData(list) { | |
let chartInData = {}; | |
// 保证list中的每个对象的属性名是相同的,也就是说一一对应 | |
for (let attr in list[0]) { | |
// 以每个属性名为名字构建数组 | |
chartInData[attr] = []; | |
} | |
list.forEach(function (item, index) { | |
for (let attr in item) { | |
// chartInData[attr] 为underfined时 初始化为空数组 | |
if (!chartInData[attr]) { | |
chartInData[attr] = []; | |
} | |
chartInData[attr].push(item[attr2]); | |
} | |
}); | |
chartInData["length"] = list.length; | |
return chartInData; | |
}, |
上面方法实现的效果是将json数组转换为一个包含以属性名命名数组的对象,例如传入的数据是这个格式
[ | |
{ | |
id: "physical", | |
// label | |
title: "physical", | |
// 排序 | |
sort:, | |
// 分数 | |
score:, | |
desc: "户外活动", | |
// 分数 | |
point:, | |
}, | |
{ | |
id: "taste", | |
title: "taste", | |
sort:, | |
score:, | |
desc: "味道", | |
// 分数 | |
point:, | |
}, | |
{ | |
id: "acceptance", | |
title: "acceptance", | |
sort:, | |
score:, | |
desc: "接受度", | |
// 分数 | |
point:, | |
}, | |
]; |
通过generateChartInData方法生成的数据如下:
{ | |
id: ["physical", "taste1","acceptance1"], | |
title: ["physical", "taste1", "acceptance1"], | |
sort: [,1,2], | |
score: [,25, 55], | |
desc: ["户外活动","味道1","接受度1"], | |
point: [,35,45], | |
length: | |
} |
将通过generateChartInData生成的数据,传入下面的方法中
// 生成图表数据 | |
chartDataFactory(dataType, chartInData) { | |
let chartOutData = {}; | |
switch (dataType) { | |
// 根据需求配置数据 | |
case "listData": | |
// 生成数组数据 | |
// 单个数据格式为 [,2,3] | |
// 多个数据格式为 [[,2,3],[1,2,4],[3,4,5]] | |
if (Array.isArray(chartInData) && chartInData.length >) { | |
let seriesList = []; | |
chartInData.forEach((item) => { | |
seriesList = [...seriesList, item[this.valueDataKey]]; | |
}); | |
chartOutData = { | |
xAxisData: chartInData[][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 =; 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 > && | |
Array.isArray(chartOutData.seriesData[]) | |
) { | |
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: "%", | |
data: chartOutData.seriesData, | |
emphasis: { | |
itemStyle: { | |
shadowBlur:, | |
shadowOffsetX:, | |
shadowColor: "rgba(, 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<=){ | |
return | |
} | |
// 如果list的子元素是数组 | |
if ( Array.isArray(list[])) { | |
// list是包含多条数据的数组 | |
chartInData = list.map((item) => { | |
// item 是数据列表 | |
return this.generateChartInData(item); | |
}); | |
} | |
// 如果list的子元素不是数组时对象 | |
if (!Array.isArray(list[])) { | |
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<=){ | |
return | |
} | |
// 如果list的子元素是数组 | |
if ( Array.isArray(list[])) { | |
// list是包含多条数据的数组 | |
chartInData = list.map((item) => { | |
// item 是数据列表 | |
return this.generateChartInData(item); | |
}); | |
} | |
// 如果list的子元素不是数组时对象 | |
if (!Array.isArray(list[])) { | |
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[]; | |
} | |
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": | |
// 生成数组数据 | |
// 单个数据格式为 [,2,3] | |
// 多个数据格式为 [[,2,3],[1,2,4],[3,4,5]] | |
if (Array.isArray(chartInData) && chartInData.length >) { | |
let seriesList = []; | |
chartInData.forEach((item) => { | |
seriesList = [...seriesList, item[this.valueDataKey]]; | |
}); | |
chartOutData = { | |
xAxisData: chartInData[][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 =; 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 attr in list[0]) { | |
// 以每个属性名为名字构建数组 | |
chartInData[attr] = []; | |
} | |
list.forEach(function (item, index) { | |
for (let attr in item) { | |
// chartInData[attr] 为underfined时 初始化为空数组 | |
if (!chartInData[attr]) { | |
chartInData[attr] = []; | |
} | |
chartInData[attr].push(item[attr2]); | |
} | |
}); | |
chartInData["length"] = list.length; | |
return chartInData; | |
}, | |
// 配置option | |
getOption(optionType, chartOutData) { | |
let option = {}; | |
let seriesList = []; | |
// 如果seriesData有数据,且seriesData的第一个元素是数组,说明传入的数据是多对象数组 | |
// 否则说明传入的是单个对象 | |
if ( | |
chartOutData.seriesData.length > && | |
Array.isArray(chartOutData.seriesData[]) | |
) { | |
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: "%", | |
data: chartOutData.seriesData, | |
emphasis: { | |
itemStyle: { | |
shadowBlur:, | |
shadowOffsetX:, | |
shadowColor: "rgba(, 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:, | |
// // 分数 | |
// score:, | |
// desc: "户外活动", | |
// // 分数 | |
// point:, | |
// }, | |
// { | |
// id: "taste", | |
// title: "taste", | |
// sort:, | |
// score:, | |
// desc: "味道", | |
// // 分数 | |
// point:, | |
// }, | |
// { | |
// id: "acceptance", | |
// title: "acceptance", | |
// sort:, | |
// score:, | |
// desc: "接受度", | |
// // 分数 | |
// point:, | |
// }, | |
// ], | |
// [ | |
// { | |
// id: "physical", | |
// // label | |
// title: "physical", | |
// // 排序 | |
// sort:, | |
// // 分数 | |
// score:, | |
// desc: "户外活动", | |
// // 分数 | |
// point:, | |
// }, | |
// { | |
// id: "taste", | |
// title: "taste", | |
// sort:, | |
// score:, | |
// desc: "味道", | |
// // 分数 | |
// point:, | |
// }, | |
// { | |
// id: "acceptance", | |
// title: "acceptance", | |
// sort:, | |
// score:, | |
// desc: "接受度", | |
// // 分数 | |
// point:, | |
// }, | |
// ] | |
// ]; | |
// 单个数据 | |
this.chartData = | |
[ | |
{ | |
id: "physical", | |
// label | |
title: "physical", | |
// 排序 | |
sort:, | |
// 分数 | |
score:, | |
desc: "户外活动", | |
// 分数 | |
point:, | |
}, | |
{ | |
id: "taste", | |
title: "taste", | |
sort:, | |
score:, | |
desc: "味道", | |
// 分数 | |
point:, | |
}, | |
{ | |
id: "acceptance", | |
title: "acceptance", | |
sort:, | |
score:, | |
desc: "接受度", | |
// 分数 | |
point:, | |
}, | |
]; | |
}, | |
}, | |
}; | |
</script> | |
<style> | |
.chart-container { | |
position: relative; | |
height:vh; | |
overflow: hidden; | |
} | |
</style> |
代码在下面
项目地址 https://gitee.com/joeyan3/joe-vue-demo-project