当前位置: 首页 > article >正文

从excel提取和过滤数据到echarts中绘制图

主页面

介绍

echarts的事例页面,导入数据比较麻烦,此项目从excel中提取数据(含过滤数据),以注入页面.

代码说明

  1. 所有的需要从excel中读取的参数,从代码中替换.需以{{data}} 包含在内
  2. 使用绘制参数的解析代码参数可以解析出来所有参数
  3. 数据配置上传文件后,可以选择列
  4. 数据过滤条件设定后,点击绘制,此时才会过滤数据
  5. 代码块实际是返回echarts.options的信息,下面给了多个事例
  6. 图表设置实际没有使用,需要后端支持才可以,将编辑好的代码保存到数据库,或者取出
  7. 其他的参数,例如绘制参数,建议直接编辑代码完成,并且建议以变量的形式填写在最上方,以提供出来
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

页面代码 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-upload
                    class="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-option
                            v-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-option
                            v-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-item
                  v-for="(item, index) in customStatisticalDataColumnName"
                  :key="index"
                  :label="`统计参数:${item}`">
                  <el-select
                    v-model="drawForm.statisticalDataColumnCustom[index]"
                    placeholder="请选择统计数据列"
                  >
                    <el-option
                      v-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);

  //获取所有区间,以及初始化区间每个区间的频数为0
  let 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();


http://www.kler.cn/a/488864.html

相关文章:

  • Unity2D初级背包设计后篇 拓展举例与不足分析
  • LabVIEW 系统诊断
  • 远程和本地文件的互相同步
  • Spring——自动装配
  • Idea-离线安装SonarLint插件地址
  • H5通过URL Scheme唤醒手机地图APP
  • vulnhub靶场【DC系列】之5
  • 开源cJson用法
  • 汽车免拆诊断 | 2007款保时捷Carrera S车行驶中发动机冷却液温度报警灯异常点亮
  • ARTS-01
  • 攻防世界 Web_php_wrong_nginx_config
  • 7.在 Vue 3 中使用 ECharts 实现动态散点图:完整教程与代码解析
  • 解决bazel构建的项目段错误时coredump调试问题
  • RabbitMQ基本介绍及简单上手
  • 中国科学院|PPTAgent:超越文本到幻灯片的演示文稿生成与评估
  • 三化六防是什么
  • 现代企业架构白皮书(可以在线阅读完整PDF文件)
  • 基于 GEE Sentinel-1 数据集提取水体
  • MySQL_单行函数和多行函数
  • 【MySQL】ON与WHERE的区别(临时表)
  • [tesseract]Deserialize header failed: FIRC.lstmf
  • 【力扣热题100】—— Day20.多数元素
  • 【网络协议】动态路由协议
  • 深入探索AI核心模型:CNN、RNN、GAN与Transformer
  • 【Leetcode-移动零】利用将非零元素移动至数组前解决移动零问题(剪枝优化)
  • TypeScript语言的网络编程