Vue echarts封装实现流程
loyd3 人气:0将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: 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>
代码在下面
加载全部内容