主页面
介绍
echarts的事例页面,导入数据比较麻烦,此项目从excel中提取数据(含过滤数据),以注入页面.
代码说明
- 所有的需要从excel中读取的参数,从代码中替换.需以{{data}} 包含在内
- 使用绘制参数的解析代码参数可以解析出来所有参数
- 数据配置上传文件后,可以选择列
- 数据过滤条件设定后,点击绘制,此时才会过滤数据
- 代码块实际是返回echarts.options的信息,下面给了多个事例
- 图表设置实际没有使用,需要后端支持才可以,将编辑好的代码保存到数据库,或者取出
- 其他的参数,例如绘制参数,建议直接编辑代码完成,并且建议以变量的形式填写在最上方,以提供出来
页面代码 commonChart.vue
<template><div class="app-container" style="height: 100vh;"><el-row style="height: 100%;"><el-col :span="10" style="height: 100%;"><el-tabs type="border-card" style="height: 100%;"><el-tab-pane label="图表设置" style="height: 100%;"><el-form ref="elForm" :model="chartForm" label-width="80px" style="height: 100%;"><el-form-item label="选择图表" prop="chartType" ><el-select v-model="chartForm.chartType" placeholder="请选择选择图表" clearable:style="{width: '100%'}"><el-option v-for="(item, index) in chartFormOptions" :key="index" :label="item.label":value="item.value" :disabled="item.disabled"></el-option></el-select></el-form-item></el-form></el-tab-pane><el-tab-pane label="代码编辑" style="height: 100%;"><vue-ace-editor v-model="chartForm.customChartCode" ref="aceEditor":width="'100%'":height="'100%'"mode='javascript'theme='github'/></el-tab-pane><el-tab-pane label="数据配置"><el-form :model="dataForm" ref="queryForm" :inline="false" label-width="80px"><el-form-item label="上传文件"><el-uploadclass="upload-demo"action="":before-upload="handleBeforeUpload":file-list="dataForm.fileList":show-file-list="false"><el-button size="mini" slot="trigger" type="primary">XLSX文件</el-button></el-upload></el-form-item><el-form-item label="数据过滤"><el-table :data="dataForm.dataFilter" style="width: 100%"><!-- 选择变量 --><el-table-column label="选择变量" prop="variable"><template v-slot="{ row, $index }"><el-select v-model="row.variable" placeholder="请选择变量"><el-optionv-for="(item, index) in dataHeader":key="index":label="item":value="index"></el-option></el-select></template></el-table-column><!-- 选择判断关系 --><el-table-column label="选择判断关系" prop="relation"><template v-slot="{ row, $index }"><el-select v-model="row.relation" placeholder="请选择关系"><el-option label="等于" value="equals"></el-option><el-option label="不等于" value="not_equals"></el-option><el-option label="大于" value="greater_than"></el-option><el-option label="小于" value="less_than"></el-option></el-select></template></el-table-column><!-- 选择判断值 --><el-table-column label="选择判断值" prop="value"><template v-slot="{ row, $index }"><el-select v-if="uniqueData[row.variable] && uniqueData[row.variable].length<10"v-model="row.value"placeholder="请选择值"><el-optionv-for="(item, index) in uniqueData[row.variable]":key="index":label="item":value="item"></el-option></el-select><el-input v-else v-model="row.value"></el-input></template></el-table-column><el-table-column label="操作" ><template v-slot="scope"><el-button type="primary" @click="addRow" size="small">+</el-button><el-button type="danger" @click="deleteRow(scope.$index)" size="small">x</el-button></template></el-table-column></el-table></el-form-item></el-form></el-tab-pane><el-tab-pane label="绘制参数"><el-form ref="elForm" :model="drawForm" label-width="180px"><el-form-item label="操作"><el-button type="primary" icon="el-icon-search" size="mini" @click="analyzeCode()">解析代码参数</el-button></el-form-item><el-form-itemv-for="(item, index) in customStatisticalDataColumnName":key="index":label="`统计参数:${item}`"><el-selectv-model="drawForm.statisticalDataColumnCustom[index]"placeholder="请选择统计数据列"><el-optionv-for="(item, index) in dataHeader":key="index":label="item":value="index"></el-option></el-select></el-form-item></el-form></el-tab-pane></el-tabs></el-col><el-col :span="14" style="height: 100%;"><el-card style="height: 100%;"><div slot="header" class="clearfix"><el-button type="primary" icon="el-icon-search" size="medium" @click="redrawChart()">绘制</el-button></div><div id="chart" style="height: 100%;width: 100%"/></el-card></el-col></el-row></div>
</template><script>
import * as echarts from "echarts";
import * as XLSX from 'xlsx';
import VueAceEditor from 'vue2-ace-editor';
import 'brace/mode/javascript'; // 引入 JavaScript 语言模式
import 'brace/theme/chrome';export default {components: {VueAceEditor,},data() {return {dialogVisible: true,// 图表配置参数chartForm: {//图表类型chartType: "",//自定义图表-代码块customChartCode: null,},// 数据配置参数dataForm: {fileList: [], // 存储上传的文件列表//过滤条件dataFilter: [{variable: "",relation: "",value: ""}]},// 绘制参数drawForm: {statisticalDataColumnCustom: []},//自定义图表的配置参数列表customStatisticalDataColumnName: [],chartFormOptions: [{"label": "自定义图表","value": 0},{"label": "正态分布图","value": 1}, {"label": "散点图","value": 2}],dataHeader: [],dataBody: [],uniqueData: [],filterData: [],validConditions: [],chart: null};},mounted() {this.initChart();this.$nextTick(() => {const aceEditor = this.$refs.aceEditor.editor;if (aceEditor) {console.log('Editor Loaded:', aceEditor); // 确认编辑器加载成功aceEditor.getSession().setMode('ace/mode/javascript'); // 强制设置语言模式aceEditor.setTheme('ace/theme/chrome'); // 强制设置主题aceEditor.setFontSize('14px'); // 设置字体大小aceEditor.setHighlightActiveLine(true); // 设置是否高亮活动行}});},methods: {onEditorLoaded(editor) {console.log('Editor Loaded:', editor); // 检查 editor 对象if (editor) {console.log('Ace Mode:', editor.getSession().getMode().$id); // 确认是否加载了 JavaScript 模式console.log('Ace Theme:', editor.getTheme()); // 确认主题}},analyzeCode() {//提取含{{}}的字符列表["{{d1}}", "{{d2}}"]let matchesName = this.chartForm.customChartCode.match(/\{\{(.*?)\}\}/g);let matchesNameLabel = []if (matchesName) {// 去除 {{ 和 }}列表matchesNameLabel = matchesName.map(match => match.slice(2, -2));}this.customStatisticalDataColumnName = matchesNameLabel;this.$message({message: '解析成功,提取自定义统计参数' + matchesNameLabel, type: 'warning'})},addRow() {// 添加一行数据到表单中this.dataForm.dataFilter.push({variable: '', relation: '', value: ''});},deleteRow(index) {// 删除对应的行this.dataForm.dataFilter.splice(index, 1);},handleBeforeUpload(file) {const reader = new FileReader()reader.onload = (e) => {const data = e.target.result;// 解析xlsx文件const workbook = XLSX.read(data, {type: 'binary'});// 获取第一个工作表const sheetName = workbook.SheetNames[0];const sheet = workbook.Sheets[sheetName];// 原始let data_json = XLSX.utils.sheet_to_json(sheet, {header: 1, defval: null});// 取出第一行作为表头this.dataHeader = data_json.shift(); // 删除并返回数组的第一个元素// 剩余的行作为表体this.dataBody = data_json;this.buildDynamicForms();this.$message({message: '解析文件成功,获取记录:' + this.dataBody.length + "条", type: 'warning'})// this.drawChart();};// 读取文件为二进制字符串reader.readAsBinaryString(file);return false; // 阻止默认的上传行为},buildDynamicForms() {//获取每列的distance值let headLength = this.dataHeader.length;for (let i = 0; i < headLength; i++) {this.uniqueData[i] = this.getUniqueColumn(this.dataBody, i);}},getUniqueColumn(data, index) {// 检查数据是否为空if (!data || !Array.isArray(data) || data.length === 0) {return [];}// 如果数据是二维数组if (Array.isArray(data[0])) {// 提取指定列并去重const column = data.map(row => row[index]);return [...new Set(column)];}// 如果数据是对象数组if (typeof data[0] === 'object') {// 提取指定列(即对象的属性)并去重const column = data.map(item => item[index]);return [...new Set(column)];}// 如果数据格式不匹配return [];},redrawChart() {this.filterDataByConditions();let opt = this.chartForm.customChartCode;let mapping = this.drawForm.statisticalDataColumnCustom;for (let i = 0; i < mapping.length; i++) {let data = this.filterData.map(row => row[mapping[i]]);opt = opt.replace("{{" + this.customStatisticalDataColumnName[i] + "}}", JSON.stringify(data));}let rx = (new Function(opt))();console.log(rx)this.chart.clear()this.chart.setOption(rx)},initChart() {let chartDom = document.getElementById("chart");this.chart = echarts.init(chartDom);},filterDataByConditions() {this.filterData = this.dataBody.filter(item => {this.validConditions = this.dataForm.dataFilter.filter(c => c.value !== "");return this.validConditions.every(row => {let rowKey = row.variable;let operator = row.relation;let rowValue = row.value;switch (operator) {case "equals":return item[rowKey] == rowValue;case "not_equals":return item[rowKey] != rowValue;case "greater_than":return item[rowKey] > rowValue;case "less_than":return item[rowKey] < rowValue;default:throw new Error(`Unknown operator: ${operator}`);}});});},}
}
;
</script>
<style scoped>
/* 或者使用 ::v-deep */
::v-deep .el-dialog__wrapper {pointer-events: none;
}::v-deep .el-dialog__body {padding-bottom: 5px
}::v-deep .el-dialog {pointer-events: auto;
}::v-deep .el-card__body{height: 100%;
}
::v-deep .el-tabs__content{height: 90% !important;
}</style>
代码事例
事例1 单散点分布 singleScatterDistribution
// 单散点分布 singleScatterDistribution
/*******************配置参数*******************/
//第一组的x,y,和size,都是一维数组
let dx = {{dx}};
let dy = {{dy}};
let dSize = {{dSize}};/*******************计算实体********************/
let dataX = dx.map((value, index) => [value, dy[index], dSize[index]]);return {toolbox: {show: true,feature: {dataZoom: {yAxisIndex: 'none'},dataView: {readOnly: false},restore: {},saveAsImage: {}}},xAxis: {},yAxis: {},legend: {data: ['数据名称'] // 图例项,对应 series 中的 name},dataZoom: [{type: 'slider',show: true,yAxisIndex: [0],//不过滤数据,只改变数轴范围。filterMode: 'none'},{type: 'inside',yAxisIndex: [0],filterMode: 'none'},//x缩放轴{type: 'slider',show: true,xAxisIndex: [0],filterMode: 'none',height: 20},{type: 'inside',xAxisIndex: [0],filterMode: 'none'}],series: [{name: '数据名称',symbolSize: function (data) {//return data[2]/100; // 返回 data 中的第三个值(size)return 7;},data: dataX,type: 'scatter',itemStyle: {color: '#5470C6' // 设置第一组数据的颜色}}]
}
事例2 双散点分布 doubleScatterDistribution
// 双散点分布 doubleScatterDistribution
/*******************配置参数*******************/
//第一组的x,y,和size,都是一维数组
let d1x = {{d1.x}};
let d1y = {{d1.y}};
let d1size = {{d1.size}};
//第二组的x,y,和size,都是一维数组
let d2x = {{d1.x}};
let d2y = {{d1.y}};
let d2size = {{d1.size}};/*******************计算实体********************/
let d1 = d1x.map((value, index) => [value, d1y[index], d1size[index]]);
let d2 = d2x.map((value, index) => [value, d2y[index], d2size[index]]);return {toolbox: {show: true,feature: {dataZoom: {yAxisIndex: 'none'},dataView: {readOnly: false},restore: {},saveAsImage: {}}},xAxis: {},yAxis: {},legend: {data: ['新数据', '旧数据'] // 图例项,对应 series 中的 name},dataZoom: [{type: 'slider',show: true,yAxisIndex: [0],//不过滤数据,只改变数轴范围。filterMode: 'none'},{type: 'inside',yAxisIndex: [0],filterMode: 'none'},//x缩放轴{type: 'slider',show: true,xAxisIndex: [0],filterMode: 'none',height: 20},{type: 'inside',xAxisIndex: [0],filterMode: 'none'}],series: [{name: '新数据',symbolSize: function (data) {//return data[2]/100; // 返回 data 中的第三个值(size)return 7;},data: d1,type: 'scatter',itemStyle: {color: '#5470C6' // 设置第一组数据的颜色}},{name: '旧数据',symbolSize: function (data) {//return data[2] + 5; // 返回 data 中的第三个值(size)return 7;},data: d2,type: 'scatter',itemStyle: {color: '#EE6666' // 设置第二组数据的颜色}}]
}
事例3 单正态分布图 singleNormalDistribution
// 单正态分布图 singleNormalDistribution
/*******************配置参数*******************/
// 组间距
let interval = 0.1;
// 参数实例 [1,2,3,4,5]
let da = {{data}}
// 标题
let title = "这里填写标题"
//图表颜色
let drawColor = "rgba(19,72,206,0.5)"/*******************计算实体********************/
//计算正态曲线
function fxNormalDistribution(array, std, mean) {let valueList = [];for (let i = 0; i < array.length; i++) {let ND =Math.sqrt(2 * Math.PI) *std *Math.pow(Math.E,-(Math.pow(array[i] - mean, 2) / (2 * Math.pow(std, 2))));valueList.push(ND.toFixed(3));}return valueList;
}//计算标准差
function getStd(data, mean) {let sumXY = function (x, y) {return Number(x) + Number(y);};let square = function (x) {return Number(x) * Number(x);};let deviations = data.map(function (x) {return x - mean;});return Math.sqrt(deviations.map(square).reduce(sumXY) / (data.length - 1));
}//对有序数组求中位数
function getMedianSorted(arr) {// 获取数组长度let len = arr.length;// 如果没有元素,返回undefined或你可以返回其他合适的值if (len === 0) {return undefined;}// 如果只有一个元素,那么它就是中位数if (len === 1) {return arr[0];}// 如果数组长度是奇数,返回中间的数if (len % 2 === 1) {return arr[Math.floor(len / 2)];}// 如果数组长度是偶数,返回中间两个数的平均值else {let mid1 = arr[len / 2 - 1];let mid2 = arr[len / 2];return (mid1 + mid2) / 2.0;}
}function getUniqueColumn(data, index) {// 检查数据是否为空if (!data || !Array.isArray(data) || data.length === 0) {return [];}// 如果数据是二维数组if (Array.isArray(data[0])) {// 提取指定列并去重const column = data.map(row => row[index]);return [...new Set(column)];}// 如果数据是对象数组if (typeof data[0] === 'object') {// 提取指定列(即对象的属性)并去重const column = data.map(item => item[index]);return [...new Set(column)];}// 如果数据格式不匹配return [];
}function getInterval(va, xAxisX) {for (let i = 0; i < xAxisX.length; i++) {if (xAxisX[i] > va) {if (i === 0) {return xAxisX[0].toString();} else {return xAxisX[i - 1].toString();}}}return xAxisX[xAxisX.length - 1].toString();
}function extracted(da) {//获取参数指定的公差,标准值,组间据da.sort((a, b) => a - b);let mid = getMedianSorted(da).toFixed(3) * 1.0;// 计算各项最大值,最小值,均值,以及cpk值,以及数组长度let length = da.length;let max = Math.max(...da);let min = Math.min(...da);let mean = da.reduce((sum, val) => sum + val, 0) / length;//依据原始数据重新计算数据X轴//x轴最大最小前后范围let dataRangeMinOP = 1;let dataRangeMaxOP = 1.1;// 设定区间起始和结束值,以及间距 ,X轴的步距let start = min - dataRangeMinOP;let end = max + dataRangeMaxOP;start = start.toFixed(0) * 1.0;// 计算区间数量let numIntervals = Math.ceil((end - start) / interval);//获取所有区间,以及初始化区间每个区间的频数为0let xAxis = [];let barYaxis = new Array(numIntervals).fill(0);for (let i = start; i <= end; i = i + interval) {let str = i.toFixed(1).toString();xAxis.push(str);}// 遍历数组并计算频数da.forEach((value) => {if (value >= start && value <= end) {// 找到值所在的区间let intervalIndex = Math.floor((value - start) / interval);// 增加该区间的频数barYaxis[intervalIndex]++;}});//正态曲线计算的基本数据和方法//计算标准差let std = getStd(da, mean);//依据x轴,计算正态分布值let lineYaxis = fxNormalDistribution(xAxis, std, mean);//求得分布曲线最大值,用以绘制纵向线let y1Max = Math.max(...lineYaxis);let midInterval = getInterval(mid, xAxis);//定义Y轴//定义实际数据的频数柱状图let barDataSet = {type: "bar",smooth: true,xAxisIndex: 0,yAxisIndex: 0,areaStyle: {opacity: 0,},markLine: {silent: true,label: {fontSize: 10},lineStyle: {color: "rgb(255,0,0)",},data: [],},data: barYaxis,itemStyle: {normal: {label: {formatter: "{c} %",show: false, //默认显示position: "top", //在上方显示textStyle: {//数值样式fontSize: 10,},},},},};//计算实际数据的正态分布图let lineDataSet = {type: "line",smooth: true,xAxisIndex: 0,yAxisIndex: 1,areaStyle: {opacity: 0,},markLine: {silent: true,label: {fontSize: 12},data: [[{name: "中位数:" + mid,coord: [midInterval, 0],},{coord: [midInterval, y1Max],},],],},data: lineYaxis,itemStyle: {normal: {label: {formatter: "{c} %",show: false, //开启显示position: "top", //在上方显示textStyle: {//数值样式fontSize: 10,},},},},};return {xAxis, barDataSet, lineDataSet};
}function getOptions() {x = extracted(da);return {color: drawColor,//工具栏。内置有导出图片,数据视图,动态类型切换,数据区域缩放,重置五个工具。toolbox: {show: true,feature: {dataZoom: {yAxisIndex: "none",},dataView: {readOnly: false},restore: {},saveAsImage: {},},},title: {text: title,left: "center"},grid: {top: 80},//提示框组件tooltip: {trigger: "axis",axisPointer: {type: "shadow",},},//图例legend: {right: "0%",top: "0",textStyle:{fontSize: 12}},xAxis: [{boundaryGap: true, type: "category", gridIndex: 0, data: x.xAxis},],//定义y轴,2个轴yAxis: [{type: "value", gridIndex: 0, max: function (value) {return value.max + 5;}},{type: "value", gridIndex: 0, max: function (value) {return (value.max + 0.2).toFixed(1);}}],series: [x.barDataSet, x.lineDataSet,],}
}
return getOptions();