vue中如何动态实现多图表,双y轴以及数据拟合功能

970 阅读3分钟

「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。

一.功能描述

步骤1

分别点击 “x轴字段”,“y轴字段” 下拉框选择需要画图的字段(x轴只支持一个,y轴最多支持2个),如图所示:

步骤2

点击 “添加画图信息” 按钮,弹出弹框,填写图表的名字(后续将作为图表的 title 进行展示)及备注信息,如图所示:

步骤3

点击 “确认画图” 按钮获取数据进行画图,点击 “取消画图” 按钮则取消画图并关闭弹框及初始化数据,如图所示:

步骤4

对于画好的图,可以通过图表右下角的下拉框选择公式进行数据统计拟合,默认不拟合,如图所示:

步骤5

如果是多张图,还可以通过图表正上方的下拉框选择相应的图表进行切换展示,如图所示:

以上步骤1-3按理来说可无限画图,但是最终页面只允许展示最多4张图表,当超过4张图表时,可按步骤5的方式进行查看;结合实际项目需求,默认需要展示的图表,我们才会通过接口请求数据进行画图,不需要展示的图表,当选择它时才会去通过接口请求数据进行画图;这样可以减少接口请求的次数,提高性能。在这里我使用的是本地数据,不存在这样的问题。

二、依赖

  • Vue
  • Element ui
  • ECharts

三、功能实现

1.本地画图数据 chartData.json

{
    "data":[
        [
            [6, 82],
            [9, 110],
            [12, 140],
            [15, 153],
            [18, 160],
            [21, 165],
            [24, 172],
            [27, 175],
            [30, 178],
            [33, 180],
            [36, 183],
            [39, 186],
            [42, 188],
            [45, 188],
            [48, 190],
            [51, 190],
            [54, 188],
            [57, 188],
            [60, 188],
            [63, 183],
            [66, 183],
            [69, 180],
            [72, 178],
            [75, 175],
            [78, 170],
            [81, 170],
            [84, 168],
            [87, 168],
            [90, 165]
        ],[
            [6, 35],
            [9, 56],
            [12, 62],
            [15, 78],
            [18, 89],
            [21, 92],
            [24, 93],
            [27, 95],
            [30, 96],
            [33, 98],
            [36, 102],
            [39, 105],
            [42, 108],
            [45, 110],
            [48, 115],
            [51, 120],
            [54, 125],
            [57, 135],
            [60, 140],
            [63, 130],
            [66, 130],
            [69, 126],
            [72, 120],
            [75, 120],
            [78, 118],
            [81, 115],
            [84, 110],
            [87, 102],
            [90, 98]
        ]
    ]
}

2.HTML代码

<template>
  <div class="app-container">
    <div class="div1 normalDivStyle">
      <el-form>
        <el-form-item label="X轴字段:">
          <el-select
            v-model="xColumn"
            clearable
            placeholder="请选择"
            style="width:100%"
          >
            <el-option
              v-for="(item,index) in xColumnList"
              :key="index"
              :label="item"
              :value="index"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="Y轴字段:">
          <el-select
            v-model="yColumn"
            multiple
            clearable
            placeholder="请选择"
            style="width:100%"
          >
            <el-option
              v-for="(item,index) in yColumnList"
              :key="index"
              :label="item"
              :value="index"
            />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button
            style="width:100%;margin-bottom:20px"
            type="danger"
            size="small"
            :disabled="xColumn!=='' && yColumn.length > 0 ? false : true"
            @click="addChartDialog=true"
          >
            添加画图信息</el-button>
        </el-form-item>
      </el-form>
    </div>

    <!-- 图表面板 -->
    <div class="div2 normalDivStyle">
      <!-- 图1 -->
      <div class="myChart">
        <div class="myChartSelect">
          <el-select
            v-model="chartValue1"
            placeholder="请选择"
            style="width:100%"
            @change="chartOnChange1(chartValue1,0,1)"
          >
            <el-option
              v-for="(item,index) in showData"
              :key="index"
              :label="item.chartName"
              :value="item.chartName"
            />
          </el-select>
        </div>
        <div id="myChart1" class="myChartCon" />
        <div class="myChartBtn">
          <el-select
            v-model="orderValue1"
            placeholder="请选择"
            style="width:100%"
            @change="orderOnChange1"
          >
            <el-option
              v-for="(item,index) in orderList"
              :key="index"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </div>
      </div>
      <!-- 图2 -->
      <div class="myChart">
        <div class="myChartSelect">
          <el-select
            v-model="chartValue2"
            placeholder="请选择"
            style="width:100%"
            @change="chartOnChange2(chartValue2,0,1)"
          >
            <el-option
              v-for="(item,index) in showData"
              :key="index"
              :label="item.chartName"
              :value="item.chartName"
            />
          </el-select>
        </div>
        <div id="myChart2" class="myChartCon" />
        <div class="myChartBtn">
          <el-select
            v-model="orderValue2"
            placeholder="请选择"
            style="width:100%"
            @change="orderOnChange2"
          >
            <el-option
              v-for="(item,index) in orderList"
              :key="index"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </div>
      </div>
      <!-- 图3 -->
      <div class="myChart">
        <div class="myChartSelect">
          <el-select
            v-model="chartValue3"
            placeholder="请选择"
            style="width:100%"
            @change="chartOnChange3(chartValue3,0,1)"
          >
            <el-option
              v-for="(item,index) in showData"
              :key="index"
              :label="item.chartName"
              :value="item.chartName"
            />
          </el-select>
        </div>
        <div id="myChart3" class="myChartCon" />
        <div class="myChartBtn">
          <el-select
            v-model="orderValue3"
            placeholder="请选择"
            style="width:100%"
            @change="orderOnChange3"
          >
            <el-option
              v-for="(item,index) in orderList"
              :key="index"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </div>
      </div>
      <!-- 图4 -->
      <div class="myChart">
        <div class="myChartSelect">
          <el-select
            v-model="chartValue4"
            placeholder="请选择"
            style="width:100%"
            @change="chartOnChange4(chartValue4,0,1)"
          >
            <el-option
              v-for="(item,index) in showData"
              :key="index"
              :label="item.chartName"
              :value="item.chartName"
            />
          </el-select>
        </div>
        <div id="myChart4" class="myChartCon" />
        <div class="myChartBtn">
          <el-select
            v-model="orderValue4"
            placeholder="请选择"
            style="width:100%"
            @change="orderOnChange4"
          >
            <el-option
              v-for="(item,index) in orderList"
              :key="index"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </div>
      </div>
    </div>

    <!-- 添加图表信息弹框 -->
    <el-dialog
      :visible.sync="addChartDialog"
      title="添加画图信息"
      :close-on-click-modal="false"
      @close="closeDialog('addChartForm')"
    >
      <el-form
        ref="addChartForm"
        :model="addChartForm"
        label-width="120px"
        :rules="rules"
      >
        <el-form-item style="margin-bottom: 20px;" label="图表名称" prop="chartName">
          <el-input v-model="addChartForm.chartName" placeholder="请输入图表名称" />
        </el-form-item>
        <el-form-item label="备注">
          <el-input v-model="addChartForm.remarks" type="textarea" placeholder="请输入备注" />
        </el-form-item>
      </el-form>
      <div style="text-align:right;">
        <el-button type="danger" @click="closeDialog('addChartForm')">取消画图</el-button>
        <el-button type="primary" @click="addChartConfirm('addChartForm')">确认画图</el-button>
      </div>
    </el-dialog>

  </div>
</template>

3.CSS代码

<style lang="scss">
.normalDivStyle{
  float: left;
  background-color: #fff;
  padding: 10px;
  box-shadow: 0 1px 1px rgb(0 0 0 / 10%);
  border-top: 3px solid #d2d6de;
}
// 私有样式
.div1{
  width: 14.5%;
  position: absolute;
  left: 20px;
  top: 20px;
  bottom: 20px;
}
.div2{
  width: 82.5%;
  position: absolute;
  right: 20px;
  top: 20px;
  bottom: 20px;
  .myChart{
    float: left;
    height: 50%;
    width: 50%;
    padding: 10px;
    border: 1px solid #EBEEF5;
    text-align: center;
    position: relative;
    .myChartCon{
      width: 100%;
      height: 98%;
    }
    .myChartSelect{
      position: absolute;
      top: 10px;
      left: 30%;
      width: 40%;
      text-align: right;
      z-index: 1;
      .el-select .el-input__inner{
        text-align: center;
        font-weight: bold;
      }
    }
    .myChartBtn{
      position: absolute;
      right: 10px;
      bottom: 10px;
      text-align: right;
      width: 120px;
    }
  }
}
</style>

4.JS代码

<script>
import { EleResize } from '@/utils/esresize'// 图表自适应
import ecStat from 'echarts-stat'
import chartData from '/static/chartData.json'// 引入本地画图数据

export default {
  data() {
    return {
      // 选择画图参数
      xColumnList: {
        'age': '年龄'
      },
      yColumnList: {
        'weight': '体重',
        'height': '身高'
      },
      xColumn: '',
      yColumn: [],

      // 添加图表信息弹框
      addChartDialog: false,
      addChartForm: {
        chartName: '',
        remarks: ''
      },

      // 页面需要显示的图表信息
      showData: [],

      // 拟合下拉数据
      orderList: [{
        label: '不拟合',
        value: 0
      }, {
        label: '1阶多项式',
        value: 1
      }, {
        label: '2阶多项式',
        value: 2
      }, {
        label: '3阶多项式',
        value: 3
      }, {
        label: '4阶多项式',
        value: 4
      }, {
        label: '5阶多项式',
        value: 5
      }],
      orderValue1: 0,
      orderValue2: 0,
      orderValue3: 0,
      orderValue4: 0,

      // 画图下拉选择
      chartValue1: '',
      chartValue2: '',
      chartValue3: '',
      chartValue4: '',

      // 表单验证规则
      rules: {
        chartName: [
          { required: true, message: '图表名称不能为空', trigger: 'blur' },
          { pattern: /^\S+$/, message: '不允许有空格', trigger: 'blur' }
        ]
      }

    }
  },

  methods: {
    // 确认画图
    addChartConfirm(formName) {
      this.$refs[formName].validate(valid => {
        if (valid) {
          var legendData = [] // 显示的标签
          for (var index in this.yColumn) {
            legendData.push(this.yColumnList[this.yColumn[index]])
          }

          var resultData = [] // 画图数据
          if (this.yColumn.length === 2) { // 双y轴数据
            resultData = chartData.data
          } else { // 单y轴数据
            if (this.yColumn[0] === 'height') {
              resultData.push(chartData.data[0])
            } else {
              resultData.push(chartData.data[1])
            }
          }

          // 画图数据,多张图
          this.showData.push({
            chartName: this.addChartForm.chartName,
            chartData: resultData,
            legendData: legendData,
            xLabel: this.xColumnList[this.xColumn]
          })

          // 画图的公共方法
          this.drawChart(this.showData, 0)
        }
      })
    },

    // 画图的公共方法
    drawChart(showData, order) {
      if (showData.length > 0) {
        for (var i in this.showData) {
          if (Number(i) < 4) {
            var mychart = 'myChart' + (Number(i) + 1)
            this.initCharts(showData[i].chartName, showData[i].legendData, showData[i].chartData, showData[i].xLabel, order, document.getElementById(mychart))
            // 图表下拉框默认值
            if (i === '0') {
              this.chartValue1 = this.showData[i].chartName
            } else if (i === '1') {
              this.chartValue2 = this.showData[i].chartName
            } else if (i === '2') {
              this.chartValue3 = this.showData[i].chartName
            } else if (i === '3') {
              this.chartValue4 = this.showData[i].chartName
            }
            this.addChartDialog = false
          } else {
            break
          }
        }
      }
    },

    // 画图的基本配置信息
    initCharts(chartName, legendData, data, xLabel, order, domName) {
      var myChart
      if (myChart !== null && myChart !== '' && myChart !== undefined) {
        myChart.dispose()// 图表销毁
      }

      // 基于准备好的dom,初始化echarts实例
      myChart = this.$echarts.init(domName)

      // 图表自适应
      const listener = function() {
        myChart.resize()
      }
      EleResize.on(domName, listener)

      // 指定图表的配置项和数据
      const colors = ['#5470C6', '#91CC75', '#000', '#fbd379']
      const colors1 = ['#003fff', '#37a900']
      const position = ['left', 'right']
      var yAxis = []
      var series = []
      for (var i in legendData) {
        // y轴配置(最多2个)
        yAxis.push({
          type: 'value',
          name: legendData[i],
          position: position[i],
          axisLine: {
            show: true,
            lineStyle: {
              color: colors[i]
            }
          },
          axisLabel: {
            formatter: legendData[i] === '身高' ? '{value} cm' : '{value} 斤'
          }
        })

        // y轴数据
        series.push({
          name: legendData[i],
          data: data[i],
          type: 'scatter',
          yAxisIndex: i, // 指定哪个y轴
          emphasis: {
            focus: 'series',
            label: {
              show: true,
              formatter: function(param) {
                return param.data[2]
              },
              position: 'top'
            }
          },
          itemStyle: {
            normal: {
              color: colors[i]
            }
          }
        })

        // 数据拟合
        if (order !== 0) {
          // polynomial:多项式回归
          var myRegression = ecStat.regression('polynomial', data[i], order)
          // 将内部数据进行从小到大排序
          myRegression.points.sort(function(a, b) {
            return a[0] - b[0]
          })
          series.push({
            name: 'line',
            type: 'line',
            smooth: true,
            data: myRegression.points,
            yAxisIndex: i, // 指定哪个y轴
            symbolSize: 0.1,
            symbol: 'circle',
            itemStyle: {
              normal: {
                lineStyle: {
                  color: colors1[i]
                }
              }
            },
            markPoint: {
              itemStyle: {
                color: 'transparent'
              },
              label: {
                show: true,
                position: 'left',
                formatter: myRegression.expression,
                color: colors[i],
                fontSize: 14
              },
              data: [{
                coord: myRegression.points[myRegression.points.length - 1]
              }]
            }
          })
        }
      }

      var option = {
        title: {
          text: chartName,
          left: 'center'
        },
        tooltip: { // 提示框配置
          trigger: 'item', //  触发类型:'item'图形触发(散点图,饼图等无类目轴的图表中使用); 'axis'坐标轴触发;'none':什么都不触发。
          axisPointer: { // 坐标轴指示器配置项。
            type: 'cross', // 'line':直线指示器, 'shadow':阴影指示器,'none':无指示器,'cross':十字准星指示器。
            axis: 'auto', // 指示器的坐标轴。
            snap: true, // 坐标轴指示器是否自动吸附到点上
            crossStyle: {
              color: '#999'
            }
          },
          formatter: function(arg) {
            var message = ''
            if (arg.componentType === 'series') {
              message = xLabel + ':' + arg.data[0] + '<br>' + arg.seriesName + ':' + arg.data[1]
            } else {
              message = xLabel + ':' + arg.data.coord[0] + '<br>' + arg.seriesName + ':' + arg.data.coord[1]
            }
            return message
          }
        },
        legend: {
          data: legendData,
          bottom: -5
        },
        xAxis: [
          {
            type: 'value',
            name: xLabel,
            nameGap: 35,
            nameLocation: 'middle',
            nameTextStyle: {
              fontWeight: 700,
              color: colors[2]
            },
            axisPointer: {
              type: 'shadow'
            }
          }
        ],
        yAxis: yAxis,
        series: series
      }

      // 清除图表,必须加,否则数据不拟合时没效果
      myChart.clear()

      // 绘制图表
      option && myChart.setOption(option)
    },

    // 下拉框1
    chartOnChange1(val, order, type) {
      this.showData.filter(item => {
        if (val === item.chartName) {
          this.initCharts(item.chartName, item.legendData, item.chartData, item.xLabel, order, document.getElementById('myChart1'))
        }
      })
      if (type === 1) {
        this.orderValue1 = 0
      }
    },
    // 数据拟合
    orderOnChange1(val) {
      this.orderValue1 = val
      this.chartOnChange1(this.chartValue1, this.orderValue1, 0)
    },

    // 下拉框2
    chartOnChange2(val, order, type) {
      this.showData.filter(item => {
        if (val === item.chartName) {
          this.initCharts(item.chartName, item.legendData, item.chartData, item.xLabel, order, document.getElementById('myChart2'))
        }
      })
      if (type === 1) {
        this.orderValue2 = 0
      }
    },
    // 数据拟合
    orderOnChange2(val) {
      this.orderValue2 = val
      this.chartOnChange2(this.chartValue2, this.orderValue2, 0)
    },

    // 下拉框3
    chartOnChange3(val, order, type) {
      this.showData.filter(item => {
        if (val === item.chartName) {
          this.initCharts(item.chartName, item.legendData, item.chartData, item.xLabel, order, document.getElementById('myChart3'))
        }
      })
      if (type === 1) {
        this.orderValue3 = 0
      }
    },
    // 数据拟合
    orderOnChange3(val) {
      this.orderValue3 = val
      this.chartOnChange3(this.chartValue3, this.orderValue3, 0)
    },

    // 下拉框4
    chartOnChange4(val, order, type) {
      this.showData.filter(item => {
        if (val === item.chartName) {
          this.initCharts(item.chartName, item.legendData, item.chartData, item.xLabel, order, document.getElementById('myChart4'))
        }
      })
      if (type === 1) {
        this.orderValue4 = 0
      }
    },
    // 数据拟合
    orderOnChange4(val) {
      this.orderValue4 = val
      this.chartOnChange4(this.chartValue4, this.orderValue4, 0)
    },

    // 关闭弹框
    closeDialog(formName) {
      // 重置表单验证规则
      this.$refs[formName].resetFields()
      this.addChartForm.chartName = ''
      this.addChartForm.remarks = ''
      this.xColumn = ''
      this.yColumn = []
      this.orderValue1 = 0
      this.orderValue2 = 0
      this.orderValue3 = 0
      this.orderValue4 = 0
      this.addChartDialog = false
    }
  }
}
</script>

5.页面效果

四、参考文章