echarts 趋势图拖拽缩放的方式配置y轴方向的上下限,附源码

接上一篇:echarts 直角坐标系中多个系列多个y轴展示方案
因为最近比较忙,一直没时间写这个。之前有小伙伴在评论区留言想要知道这个效果怎么写的,这里把代码贴下。

这个点击上下缩放的效果是之前别的项目上的,在某些特殊功能上还是比较有用的。但因为封装的功能和我这边的需求不一致,而且不是很好理解,然后我就参考了下代码自己写了一套,有需要的小伙伴可以参考下,主要是思路。

效果图(简书这个图片经常加载不出来,不知道你们是否能看到下面这个gif)

3.gif

4.gif

操作方式就是点击折线后高亮,鼠标滚轮进行缩放,拖拽设置纵向位置,点击chart空白处取消高亮并关闭纵向缩放功能。

下面是组件设计要点:

一、点击线条进行拖拽缩放功能点

1.点击线条高亮,这里采用的是降低其他线条的透明度
2.初始可以缩放,初始时需要重新更改y轴的max和min,扩大范围
3.在y轴缩放时需要将dataZoom事件返回的start,end换算为 startValue 和 endValue,因为start,end是百分比,我们扩大了纵向的范围的,不能使用百分比
4.为了不让x轴跳动,组件内设置x轴的onZero为false,脱离y轴0刻度
5.保留x轴缩放功能,内部缩放的方式在高亮时被禁用,slider方式无限制

二、扩展功能,可以定时刷新图表需要考虑的功能点有:

1.需要保证选择线的高亮不变
2.y轴缩放不变
3.x轴缩放不变
4.legend的选中状态不变
以上功能都需要在各自触发时将信息存储起来,由组件内部会在合适的地方记录这些信息,initYZoom方法第三个参数是是否开启状态继承,默认是true,即后续chart更新将保留之前的状态

当前代码需要的依赖包

"echarts": "^5.3.3","element-ui": "^2.7.2","vue": "^2.5.2","less": "^3.9.0","less-loader": "^4.1.0"
注意:这是vue2的demo!,vue3可自行修改代码

代码如下 github地址

dragLineMixin.js 这里用mixin的方式,可自行更改为你喜欢的方式

/**
 * 纵向缩放chart扩展插件
 * @author:yangfeng
 * @time: 20220817
 * */
/*
  注意事项:
  1.y轴必须是value类型,使用前提dataZoom需要指定xAxisIndex 和yAxisIndex
  2.可以不设置y轴max,min,不设置会取对应系列的最大最小值,若设置了则初始以设置的为准
  3.每个y轴都必须有一个唯一dataZoom匹配,因为缩放的是dataZoom
  4.保留横向缩放功能
  5.因为y轴要缩放用,则x轴会被设置为脱离y轴0刻度
*/
export default {
  data() {
    return {
      yDataZoomTimer: null,// y轴缩放时用于节流
      saveInfo: {}, // 存储缩放后的状态信息,针对定时任务的场景
    }
  },
  methods: {
    isNull(val) {
      return val === null || val === void 0 || val === '' || (val).toString() === 'NaN'
    },
    // y轴缩放配置 start ----------------

    // 清除用于节流缩放的定时器
    clearYDataZoomTimer() {
      this.yDataZoomTimer && clearTimeout(this.yDataZoomTimer)
    },

    // 重新给y轴赋值足够大的max,min值【这样初始就可以缩放】,并给对应y轴dataZoom赋值startValue和endValue
    setYMaxMin(option) {
      if (!option || !option.yAxis) return
      if (!Array.isArray(option.yAxis)) { // y轴修改为数组
        option.yAxis = [option.yAxis]
      }

      option.yAxis.map((item, yAxisIndex) => {
        let {
          max,
          min
        } = this.getSeriesItemYMaxMin(option, option.series.find(subItem => subItem.yAxisIndex === yAxisIndex)) // 根据数据获取最大最小值

        // 没有给y轴设置最大最小值,则使用数据中的极值
        let yMax = !this.isNull(item.max) ? item.max : max
        let yMin = !this.isNull(item.min) ? item.min : min
        // console.log(item, max, min,)
        // console.log('xxx', yMax, yMin)
        // 修改当前y轴对应的dataZoom startValue和endValue
        let findZoom = option.dataZoom.find(subItem => subItem.yAxisIndex === yAxisIndex)
        if (findZoom) { // 有对应的dataZoom
          delete findZoom.start
          delete findZoom.end
          let seriesItem = option.series.find(subItem => subItem.yAxisIndex === yAxisIndex)
          findZoom.startValue = yMin
          findZoom.endValue = yMax
          findZoom.id = `yZoom-${yAxisIndex}-${seriesItem.name}` // seriesItem,给对应dataZoom赋值id
          this.$set(findZoom, 'disabled', true) // 给option的dataZoom设置此属性,重要!!

          // 重新设置最大最小值, 放大缩小最大最小值,这样初始就可以拖动了 - 解决初始没法拖动的问题
          let rang = Math.abs(yMax - yMin)
          item.max = yMax + rang * 10000 // 这里设置一万倍
          item.min = yMin - rang * 10000
        }
      })

      // 让x轴脱离y轴0刻度
      if (Array.isArray(option.xAxis)) {
        option.xAxis.map(item => {
          !item.axisLine && (item.axisLine = {})
          item.axisLine.onZero = false
        })
      } else {
        !option.xAxis.axisLine && (option.xAxis.axisLine = {})
        option.xAxis.axisLine.onZero = false
      }
    },

    /**
     * 获取当前系列的y值最大最小值
     * @option: chart 的optin配置
     * @seriesItem:需要找极值的series项
     * @return {max,min}
     * */
    getSeriesItemYMaxMin(option, seriesItem) {
      let yDATA = [] // 取出对应series的y值
      seriesItem.data.map(item => { // 取出
        if (this.isNull(item)) return
        if (Array.isArray(item)) { // 数组取第二项
          yDATA.push(item[1])
        } else if (typeof item === 'object') { // 针对echarts 的data数据类型进行取值
          yDATA.push(item.value)
        } else { // 数值类型
          yDATA.push(item)
        }
      })
      // 取出对应series的y值最大最小值
      return {
        max: Math.max(...yDATA),
        min: Math.min(...yDATA)
      }
    },

    // 关闭缩放并取消高亮
    closeZoomHighLight(option) {
      option.dataZoom.map(item => {
        if (item.type !== 'slider') { // slider的保持缩放,
          item.disabled = true
        }
      })

      // 取消高亮
      // myChart.dispatchAction({
      //     type: 'downplay',
      //     seriesIndex: option.series.map((item, index) => index)
      // });
      // console.log(option, 111)

      // 设置透明度来达到高亮效果
      option.series.map(item => {
        !item.lineStyle && (item.lineStyle = {})
        item.lineStyle.opacity = 1

        !item.itemStyle && (item.itemStyle = {})
        item.itemStyle.opacity = 1
      })
    },
    /**
     * 高亮缩放指定seriesIndex的项,只有高亮的系列或者slider类型的才能缩放
     * */
    openZoomHighLight(option, seriesIndex) {
      let yAxisIndex = option.series[seriesIndex].yAxisIndex
      let findItem = option.dataZoom.find(item => item.yAxisIndex === yAxisIndex)
      findItem.disabled = false
      // myChart.dispatchAction({
      //     type: 'highlight',
      //     seriesIndex: seriesIndex
      // });

      // 设置透明度来达到高亮效果
      option.series.map(item => {
        !item.lineStyle && (item.lineStyle = {})
        item.lineStyle.opacity = 0.3

        !item.itemStyle && (item.itemStyle = {})
        item.itemStyle.opacity = 0.3
      })
      option.series[seriesIndex].lineStyle.opacity = 1
      option.series[seriesIndex].itemStyle.opacity = 1
      // console.log(option.series)
    },

    /**
     * 点击到系列上,高亮此系列
     * @option
     * @seriesIndex:需要高亮系列的索引
     * */
    highLightSeries(option, seriesIndex) {
      if (seriesIndex !== null && seriesIndex > -1) {
        this.closeZoomHighLight(option) // 关闭所有缩放
        this.openZoomHighLight(option, seriesIndex) // 开启缩放并选中
      }
    },
    /**
     * 取消高亮系列
     * */
    closeHighLightSeries(option) {
      this.closeZoomHighLight(option) // 关闭所有缩放
      // 开启x轴缩放 - 没有指定yAxisIndex的,认定为x轴
      option.dataZoom.map(item => {
        if (this.isNull(item.yAxisIndex)) {
          item.disabled = false
        }
      })
    },

    /**
     * 开启y轴缩放功能
     * @option:该echarts的option
     * @chartInstance: echart 渲染的实例,用于绑定事件
     * @keepAlive: Boolean 是否继承上次的缩放状态,针对定时刷新图表情况,能保留之前x,y缩放、高亮、legend选中状态
     *             Object:若是对象类型,该对象将会和之前存储的saveInfo对象合并【针对某些不需要完全继承上次状态的场景,在改组件外部修改了状态信息,需要以修改的为准】
     * */
    initYZoom(option, chartInstance, keepAlive = true) {
      if (!chartInstance) {
        console.error('echarts 实例不存在, 开启y轴缩放失败')
        return
      }

      // option属性赋值
      this.setYMaxMin(option) // 重新给y轴赋值max,min值
      // 给x轴对应的系列赋值id
      option.dataZoom.map((item, index) => {
        if (!item.id) {
          item.id = `${item.xAxisIndex}-${item.type}-${index}-zoom`
        }
      })
      // console.log(option, 6666)

      if (keepAlive) { // 继承上次缩放状态
        this.extendZoomInfo(option, chartInstance, keepAlive)
      } else {
        this.saveInfo = {} // 清空
      }

      // 利用getZr方法获取点击事件,点击开启缩放
      chartInstance.getZr().on('click', (params) => {
        // console.log(params, 222)
        let target = params.target
        if (target) { // 点击线条,开启缩放
          let seriesIndex = target.seriesIndex || null // 【注意:echarts 版本不一样这里可能不一样】
          if (!seriesIndex) {
            Object.keys(target).map(key => {
              if (~key.indexOf('__ec_inner_')) {
                'seriesIndex' in target[key] && (seriesIndex = target[key].seriesIndex)
              }
            })
          }

          this.highLightSeries(option, seriesIndex)
        } else { // 未点击线条,闭关缩放功能并取消高亮
          this.closeHighLightSeries(option)
        }
        this.$emit('dataYZoom', params, this.saveZoomInfo(option)) // 将缩放后的上下限抛出,方便父组件使用
      });

      // 因为在取消缩放后 echarts并没有保存缩放值,而且还需要对超出显示范围的缩放进行处理。因此这里需要dataZoom方法
      chartInstance.on('dataZoom', (params) => {
        // console.log(params, 'dataZoom')
        let dataZoomId; // 缩放的dataZoom id
        let start; // 当前缩放开始百分比
        let end; // 当前缩放结束百分比
        if (params.batch) { // 【注意:echarts 版本不一样这里可能不一样】
          let data = params.batch[0]
          dataZoomId = data.dataZoomId // 当前缩放的dataZoom
          start = data.start
          end = data.end
        } else {
          dataZoomId = params.dataZoomId
          start = params.start
          end = params.end
        }

        // 对x轴缩放时,存储start end
        if (dataZoomId && !dataZoomId.startsWith('yZoom-')) {
          this.setXDataZoom(option, chartInstance, {id: dataZoomId, start, end}) // 存储x轴缩放信息
        }

        // 换算为比例
        start = start / 100
        end = end / 100
        this.clearYDataZoomTimer() // 清除定时器

        // 对某条线进行缩放时,将对应y轴dataZoom的startValue和endValue重新赋值【因为更改了y轴min和max,直接返回的start 和end是百分比,不能直接使用】
        if (dataZoomId && dataZoomId.startsWith('yZoom-')) { // 是y轴缩放的逻辑

          // 根据y轴的最大最小值将当前缩放的start end 换算为startValue和endValue
          let currentDataZoom = option.dataZoom.find(item => item.id === dataZoomId)
          if (!currentDataZoom) return
          // 对应的y轴
          let yAxisIndex = currentDataZoom.yAxisIndex
          let {max, min} = option.yAxis[yAxisIndex] // 这里的最大最小值是放大了区间后的值
          let rang = Math.abs(max - min)
          let startValue = min + rang * start // 注意都是min开始的,因为start、end是占比
          let endValue = min + rang * end // 注意都是min开始的,因为start、end是占比

          // 处理边界条件,缩放时保证所有数据都在可视区域内
          let seriesItem = option.series[yAxisIndex] // 获取对应的series项
          let {max: yMax, min: yMin} = this.getSeriesItemYMaxMin(option, seriesItem)
          // console.log('y值', `${yMin}-${yMax}`,`${startValue}-${endValue}`)
          // 超出范围处理,保证缩放后的数据都在可视范围内
          let dispatchZoom = false // 是否调用dispatchAction
          if (yMax > endValue) {
            endValue = yMax
            dispatchZoom = true
          }
          if (yMin < startValue) {
            startValue = yMin
            dispatchZoom = true
          }
          // console.log(currentDataZoom.startValue,'-',currentDataZoom.endValue)

          // 保存当前缩放值
          currentDataZoom.startValue = startValue
          currentDataZoom.endValue = endValue

          if (dispatchZoom) { // 只有在超出界限的时候才需要调用这个,如果不限定每次都调用这个将会出现卡顿
            this.yDataZoomTimer = setTimeout(() => { // 节流,防止数据量太大的卡顿
              let dataZoomIndex = option.dataZoom.findIndex(item => item.id === dataZoomId)
              chartInstance.dispatchAction({
                type: 'dataZoom',
                dataZoomIndex,
                // 开始位置的数值
                startValue: startValue,
                // 结束位置的数值
                endValue: endValue
              })
            })
          }

          // console.log(startValue,'-', endValue, dataZoomIndex, option.dataZoom[dataZoomIndex])

          this.$emit('dataYZoom', params, this.saveZoomInfo(option)) // 将缩放后的上下限抛出,方便父组件使用
        }
        // })
      })

      // legend 选择
      chartInstance.on('legendselectchanged', (params) => {
        // console.log(params.selected, 555)
        this.saveZoomInfo(option, {
          legendSelected: params.selected // 记录legend的选中状态
        })
      })

      // y轴缩放加载完成事件
      this.$emit('dataYZoomFinished', this.saveZoomInfo(option))
    },

    /**
     * 获取y轴可缩放的对应系列当前上下限
     * @option: chart的option 配置
     * @return [{seriesName:系列名,upper:上限,lower:下限}]
     * */
    getSeriesUpperLower(option) {
      let arr = option.dataZoom.filter(item => item.id && item.id.startsWith('yZoom-'))
      let resArr = arr.map(item => {
        let IdInfo = item.id.split('-')
        return {
          seriesName: IdInfo[2],
          upper: item.endValue, // 上限
          lower: item.startValue, // 下限
        }
      })
      return resArr
    },
    // 获取当前高亮的系列
    getHighLightSeriesName(option) {
      let filterArr = option.series.filter(item => item.lineStyle && item.lineStyle.opacity === 1)
      // console.log(option,filterArr, 'nnn')
      if (filterArr.length > 1) { // 多个则说明没有选择
        return ''
      } else if (filterArr.length === 1) {
        return filterArr[0].name
      } else {
        return ''
      }
    },

    /**
     * 缩放时存储x轴的缩放信息
     * */
    setXDataZoom(option, chartInstance, {id, start, end}) {
      let findItem = option.dataZoom.find(item => item.id === id)
      let xAxisIndex = findItem.xAxisIndex || 0 // xAxisIndex可能为数组

      // 将缩放相同x轴的dataZoom,缩放设置为当前值
      option.dataZoom.map((item, index) => {
        if (Array.isArray(xAxisIndex) || Array.isArray(item.xAxisIndex)) { // 是数组
          if (JSON.stringify(item.xAxisIndex) === JSON.stringify(xAxisIndex)) {
            item.start = start
            item.end = end
          }
        } else if (item.xAxisIndex === xAxisIndex) {
          item.start = start
          item.end = end

          // chartInstance.dispatchAction({
          //   type: 'dataZoom',
          //   index,
          //   // 开始位置的数值
          //   start,
          //   // 结束位置的数值
          //   end
          // })
        }
      })
      // console.log(option, 'xxxxxxxx')

      this.saveZoomInfo(option)
    },

    /**
     * 存储必要的缩放的信息,若有定时任务让图形可继承上次状态
     * @option: 当前chart option
     * @obj:其他信息
     * */
    saveZoomInfo(option, obj) {
      // console.log(option, 222)
      // x轴对应的dataZoom的缩放信息
      let xDataZoomInfo = []
      option.dataZoom.map(item => {
        if (!item.id.startsWith('yZoom-')) {
          xDataZoomInfo.push({
            id: item.id,
            start: item.start,
            end: item.end
          })
        }
      })
      let info = {
        seriesInfo: this.getSeriesUpperLower(option), // 每个系列的上下限
        xDataZoomInfo, //  x轴对应的dataZoom的缩放信息
        highLightSeriesName: this.getHighLightSeriesName(option) // 当前高亮的seriesName
      }
      this.saveInfo = {
        ...this.saveInfo,
        ...info,
        ...obj
      }
      // console.log(info, 7777)
      return this.saveInfo
    },

    // 继承之前的缩放状态 start ----------
    extendZoomInfo(option, chartInstance, obj) {
      // 传递了对象的以对象为准
      if (obj && typeof obj === 'object' && Object.keys(obj).length) {
        this.saveInfo = {
          ...this.saveInfo,
          ...obj
        }
      }
      if (!this.saveInfo || !Object.keys(this.saveInfo).length) return
      let oldInfo = this.saveInfo
      // console.log(this.saveInfo, 333)
      // 保持高亮状态
      let seriesName = oldInfo.highLightSeriesName
      if (seriesName) {
        let seriesIndex = option.series.findIndex(item => item.name === seriesName)
        this.highLightSeries(option, seriesIndex)
      }

      // 保持y轴缩放
      let seriesInfo = oldInfo.seriesInfo
      if (seriesInfo && seriesInfo.length) {
        option.dataZoom.map(item => {
          if (item.id && item.id.startsWith('yZoom-')) {
            let IdInfo = item.id.split('-')
            let findInfo = seriesInfo.find(sub => sub.seriesName === IdInfo[2])
            if (findInfo) { // 更新为上次的上下限
              item.endValue = findInfo.upper
              item.startValue = findInfo.lower
            }
          }
        })
      }

      // 保持x轴缩放
      let xDataZoomInfo = oldInfo.xDataZoomInfo
      if (xDataZoomInfo && xDataZoomInfo.length) {
        option.dataZoom.map(item => {
          if (!item.id.startsWith('yZoom-')) {
            let findInfo = xDataZoomInfo.find(sub => sub.id === item.id)
            if (findInfo) {
              item.start = findInfo.start
              item.end = findInfo.end
            }
          }
        })
      }

      // 继承之前legend的选中状态
      let legendSelected = oldInfo.legendSelected // 格式如[true,false,xxx]
      if (option.legend && legendSelected && Object.keys(legendSelected).length) {
        option.legend.selected = legendSelected
      }
    }
    // 继承之前的缩放状态 end ----------

    // y轴缩放配置 end ----------------
  },
  beforeDestroy() {
    this.clearYDataZoomTimer() // 清除定时器
  }
}

dragLineChart.vue

<!--
可拖拽组件
-->
<template>
  <div class="chart-base">
    <div style="width:100%;height:100%;" :id="id" ref="chartRef"></div>
    <div v-if="isChartNull(option)" class="noData">暂无数据</div>
  </div>
</template>
<script>
  import * as echarts from 'echarts'
  import dragLineMixin from './dragLineMixin'

  export default {
    name: 'dragLineChart',
    mixins: [dragLineMixin],
    data() {
      return {
        myChart: '',
      }
    },
    props: {
      id: {
        type: String,
        default: 'ChartBox'
      },
      option: {
        type: Object,
        default() {
          return {}
        }
      }
    },
    mounted() {
      // this.myChart = this.$echarts.init(this.$refs.chartRef)
      this.myChart = echarts.init(this.$refs.chartRef)
      this.drawLine()
      // this.initEvent() // event
    },
    methods: {
      isChartNull(chartOption) {
        let bool = true
        if (chartOption && chartOption.series) {
          if (Object.prototype.toString.call(chartOption.series) === '[object Array]') {
            chartOption.series.map(item => {
              if (item.data && item.data.length) bool = false
            })
          } else {
            chartOption.series.data && chartOption.series.data.length && (bool = false)
          }
        }
        return bool
      },

      initEvents() {
        this.myChart.on('dataZoom', (params) => {
          this.$emit('dataZoom', params)
        })
      },
      drawLine() { // 绘制图表
        this.myChart.clear()
        this.myChart.setOption(this.option)

        this.initEvents()
      },
      resetChartData() { // 刷新数据
        this.myChart.setOption(this.option, true)
      },
      /*    showLoading() { // 显示加载动画
            this.myChart.showLoading()
          },
          hideLoading() { // 关闭加载动画
            this.myChart.hideLoading()
          } */
    },
    watch: {
      option: {
        deep: true,
        handler: function (value) {
          this.resetChartData()
        }
      }
    },
    beforeDestroy() {
      this.myChart && this.myChart.dispose()
    }
  }
</script>
<style scoped>
  .chart-base {
    position: relative;
    width: 100%;
    height: 100%;
    text-align: initial;
  }

  .noData {
    width: 200px;
    height: 100px;
    line-height: 100px;
    position: absolute;
    left: 50%;
    top: 50%;
    margin-left: -100px;
    margin-top: -50px;
    font-size: 28px;
  }
</style>

index.vue 这个是调用的例子,模拟了数据

<template>
  <div>

    <div class="wrap">
      <!--路径图2-->
      <div class="com-box">
        <h1>可拖拽缩放的方式配置上下限
          <el-button type="primary" @click="openTimerTick(true)" v-if="!timer">开启定时任务</el-button>
          <el-button type="primary" @click="openTimerTick(false)" v-else>关闭定时任务</el-button>
        </h1>
        <div class="flex-box">
          <div class="info">
            <div>
              右侧趋势图上下限为:<br/>
              <template v-for="(item,index) in chartInfoArr">
                <div :style="{color: item.color}">{{ item.seriesName }}<br/>
                  <div class="input-box">
                    <span>下限:</span>
                    <el-input v-model="item.lower" type="number" :disabled="!!timer" placeholder="下限" :style="{color: item.color}" @change="changeUpLow"/>
                    <span>上限:</span>
                    <el-input v-model="item.upper" type="number" :disabled="!!timer" placeholder="上限" :style="{color: item.color}" @change="changeUpLow"/>
                  </div>
                </div>
              </template>
            </div>
            <hr/>
          </div>
          <div class="line-chart" style="height: 700px;">
            <drag-line-chart :option="chartOption"
                             @dataYZoom="chartDataZoom"
                             @dataYZoomFinished="(val)=>chartDataZoom(null,val)"
                             ref="dragLineChart"/>
          </div>
        </div>
      </div>
    </div>

  </div>
</template>

<script>
import dragLineChart from './common/dragLineChart'

export default {
  name: 'eharts',
  components: {
    dragLineChart,
  },
  data() {
    return {
      chartOption: {},

      chartData: [], // 记录chart原始数据
      chartDataMaxMin: { // 记录图表数据的上下限,格式{lineSeriesName:{max:xx,min:xx}}
      },
      chartInfoArr: [], // 显示右侧图表的上下限并可编辑
      timer: null
    }
  },
  methods: {
    // 范围内随机数
    rand(m, n) {
      return Math.ceil(Math.random() * (n-m) + m)
    },
    // 重新刷新chart
    changeUpLow() {
      let option = this.chartOption
      // console.log(option, 111)
      this.chartInfoArr.map(item => {
        this.chartDataMaxMin[item.seriesName] = { // 记录当前上下限
          max: +item.upper,
          min: +item.lower
        }
      })

      this.renderChart(this.chartData) // 修改上下限后重新渲染chart

    },

    // 显示上下限具体信息
    chartDataZoom(params, ulInfoArr) {
      // console.log(ulInfoArr, 3333)
      let {color} = this.getOption()
      let chartInfoArr = []
      ulInfoArr.seriesInfo.map(({seriesName, upper, lower}) => {
        this.chartDataMaxMin[seriesName] = { // 记录当前上下限
          max: upper,
          min: lower
        }
        let colorIndex = this.chartOption.series.findIndex(subItem => subItem.name === seriesName)
        chartInfoArr.push({
          color: color[colorIndex],
          seriesName,
          upper: (upper).toFixed(2),
          lower: (lower).toFixed(2)
        })
      })
      this.chartInfoArr = chartInfoArr
    },
    getOption() {
      let option = {
        tooltip: {
          trigger: 'axis'
        },
        legend: {},
        grid: [{
          show: false,
          left: '3%',
          right: '4%',
          bottom: 380,
          top: 50,
          containLabel: false
        },{
          show: false,
          left: '3%',
          right: '4%',
          bottom: 80,
          top: 400,
          containLabel: false
        }],
        xAxis: [{
          // scale:true,
          type: 'category',
          boundaryGap: false,
          splitLine: {show: false},
          gridIndex:0,
        },{
          // scale:true,
          type: 'category',
          boundaryGap: false,
          splitLine: {show: false},
          gridIndex:1,
        }],
        yAxis: [],
        series: []
      };
      return {
        option,
        color: [
          '#fab83e',
          '#e733a3',
          '#7482f2',
          '#51bcff',
          '#47d5e4',
        ]
      }
    },
    renderChart(resData) {
      this.chartData = resData // 记录原始数据
      // console.log(resData, 11)
      // option赋值
      let {option, color} = this.getOption()
      let yAxis = [] // y轴
      let dataZoom = [ // 缩放设置
        {
          type: 'inside',
          id: 'x-inside-zoom0',
          xAxisIndex: [0]
        },
        {
          type: 'slider',
          id: 'x-slider-zoom0',
          xAxisIndex: [0],
          top: 350,
          height:20,
        },
        {
          type: 'inside',
          id: 'x-inside-zoom1',
          xAxisIndex: [1]
        },
        {
          type: 'slider',
          id: 'x-slider-zoom1',
          xAxisIndex: [1],
          bottom:30,
          height:20
        }
      ]
      resData.map((item, index) => {
        let {data, name} = item
        let maxMin = this.chartDataMaxMin[name]
        let showLine = false
        let gridIndex = index < 2 ? 0 : 1 // 前面两条线放上面,后面的放下面
        yAxis.push({
          type: 'value',
          max: maxMin.max, // 上下浮动
          min: maxMin.min,
          axisLine: {show: showLine},
          axisTick: {show: showLine},
          axisLabel: {show: showLine},
          splitLine: {show: showLine},
          gridIndex: gridIndex,
        })
        option.series.push({
          name: name,
          type: 'line',
          triggerLineEvent: true,
          yAxisIndex: index,
          xAxisIndex:gridIndex,
          data: data,
          symbol: 'none',
          lineStyle: {
            color: color[index]
          },
          itemStyle: {
            color: color[index]
          },
          // animation:false
        })

        dataZoom.push({
          type: 'inside',
          // disabled: true,
          yAxisIndex: index,
        })

      })

      option.yAxis = yAxis
      option.dataZoom = dataZoom
      // console.log(option, 5555)
      this.chartOption = option

      // 以外部设置线的上下限为准,不使用内部的y方向上下限,两种方式
      // 方式一:通过设置keepAlive.seriesInfo为对象的方式覆盖原有状态,这样y轴的max和min可以不指定
      // this.$refs['dragLineChart'].initYZoom(this.chartOption, this.$refs['dragLineChart'].myChart, {
      //   // 以当前上下限为准,y轴上下限交由外部控制,不传则完全交由内部控制
      //   seriesInfo: Object.keys(this.chartDataMaxMin).map(seriesName => {
      //     return {
      //       seriesName,
      //       lower: +this.chartDataMaxMin[seriesName].min,
      //       upper: +this.chartDataMaxMin[seriesName].max
      //     }
      //   })
      // })

      // 方式二:通过设置keepAlive.seriesInfo为false的方式,这样内部将以y轴的max,min作为上下限,因为这里手动维护了chartDataMaxMin
      this.$refs['dragLineChart'].initYZoom(this.chartOption, this.$refs['dragLineChart'].myChart, {seriesInfo:false})
    },
    getChartData() {
      // 构造测试数据
      let resData = []
      for (let j = 0; j <= 4; j++) {
        resData.push({
          name: `line${j + 1}`,
          data: []
        })
      }
      for (let i = 1; i < 300; i++) {
        resData[0].data.push([i, this.rand(30, 100)])
        resData[1].data.push([i, this.rand(-30, -3)])
        resData[2].data.push([i, this.rand(5000, 6000)])
        resData[3].data.push([i, this.rand(0, 10)])
        resData[4].data.push([i, this.rand(800, 900)])
      }

      // 构造初始数据的上下限
      resData.map(item => {
        let dataY = item.data.map(item => item[1])
        let max = Math.max(...dataY)
        let min = Math.min(...dataY)
        let jc = Math.abs(max - min)

        this.chartDataMaxMin[item.name] = {
          max: max + 0.1 * jc, // 上下浮动
          min: min - 0.1 * jc,
        }
      })

      this.renderChart(resData)
    },
    // 定时任务相关 start --------------------

    // 清除定时器
    clearTimerTick() {
      this.timer && clearTimeout(this.timer)
      this.timer = null
    },
    // 开启定时任务,模拟定时刷新数据的情况,
    openTimerTick(bool) {
      if (bool) {
        this.timer = setTimeout(() => {
          // 构造测试数据
          let resData = this.chartData
          let x = resData[0].data[resData[0].data.length - 1][0] + 1
          // 末尾追加一项
          resData[0].data.push([x, this.rand(30, 100)])
          resData[1].data.push([x, this.rand(-30, -3)])
          resData[2].data.push([x, this.rand(5000, 6000)])
          resData[3].data.push([x, this.rand(0, 10)])
          resData[4].data.push([x, this.rand(800, 900)])

          // 删除首项
          resData.map(item => {
            item.data = item.data.slice(1)
          })
          this.renderChart(resData)

          this.openTimerTick(true)
        }, 1000)
      } else {
        this.clearTimerTick()
      }
    },

    // 定时任务相关 end --------------------

  },
  mounted() {
    this.getChartData()
  },
  beforeDestroy() {
    this.clearTimerTick()
  }
}
</script>

<style scoped lang="less">
.wrap {
  background: #f8f8f8;
  overflow: hidden;
  display: flex;
  justify-content: space-around;
  /*flex-direction: ;*/
  flex-wrap: wrap;
  align-items: center;
}

.line-chart {
  width: 100%;
  height: 330px;
  flex: auto;
}

.com-box {
  margin: 60px;
  width: 100%;
  background: rgba(255, 255, 255, 1);
  box-shadow: -.02rem .07rem .15rem 1px rgba(203, 204, 204, 0.18);
  border-radius: .03rem;
  /*margin: 20px auto;*/
  box-sizing: border-box;
  padding: 15px 60px;
  /deep/ .el-input__inner{
    height: 35px;
    line-height: 35px;
  }
}

.info {
  font-size: 20px;
  text-align: left;
  width: 1000px;
  margin-right: 10px;
  line-height: 40px;
  border: 1px solid #cccccc;
  box-sizing: border-box;
  padding: 5px;
}

.flex-box {
  display: flex;
  justify-content: space-between;
  align-content: flex-start;
  margin-top: 10px;
}

.input-box {
  display: flex;
  justify-content: space-between;
  align-content: center;
  margin-bottom: 5px;

  > span {
    align-self: center;
    flex: none;
    width: 50px;
    margin-right: 5px;
  }
}

.content-info{
  text-align: left;
  line-height: 30px;
  font-size: 16px;
  >div{
    padding: 10px;
  }

}
</style>

组件使用注意事项:

1.每条线也就是每个series对应一个y轴,每个y轴对应一个dataZoom【纵向缩放原理其实就是借用的echarts 的dataZoom功能实现的】
2.在取得chart 实例后,最后调用initYZoom方法开启纵向缩放功能,内部需要chart 实例绑定事件,如click, 缩放,legend点击
3.这里组件通过mixin的方式嵌入到基础chart中【也可以单独抽离成js文件引入,但是需要在合适的地方将内部用于节流的定时器清除,vue 的$set也要修改,然后自己找准时机将chart更新】
4.组件支持使用多个x轴,保留缩放功能,但在高亮时禁用内部x轴缩放

可参考下面这个最基础配置帮助理解上面的代码,功能都是在这上面衍生出来的

option = {
  xAxis: [
    {
      type: 'category',
      gridIndex: 0,
      data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    },
    {
      type: 'category',
      gridIndex: 1,
      data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    }
  ],
  grid: [
    {
      show: false,
      left: '3%',
      right: '4%',
      bottom: 380,
      top: 50,
      containLabel: false
    },
    {
      show: false,
      left: '3%',
      right: '4%',
      bottom: 80,
      top: 400,
      containLabel: false
    }
  ],
  yAxis: [
    {
      type: 'value',
      gridIndex: 0
    },
    {
      type: 'value',
      gridIndex: 1
    }
  ],
  dataZoom: [
    // x轴缩放
    // 上
    {
      type: 'inside',
      xAxisIndex: 0
    },
    {
      type: 'slider',
      top: 350,
      xAxisIndex: 0
    },
    // 下
    {
      type: 'inside',
      xAxisIndex: 1
    },
    {
      type: 'slider',
      bottom: 10,
      xAxisIndex: 1
    },

    // y轴缩放
    {
      type: 'inside',
      yAxisIndex: 0
    },
    {
      type: 'inside',
      yAxisIndex: 1
    }
  ],
  series: [
    {
      data: [150, 230, 224, 218, 135, 147, 260],
      type: 'line',
      xAxisIndex: 0,
      yAxisIndex: 0
    },
    {
      data: [150, 230, 224, 218, 135, 147, 260],
      type: 'line',
      xAxisIndex: 1,
      yAxisIndex: 1
    }
  ]
};

image.png

最后

若对你有帮助,请点个赞吧,若能打赏不胜感激,谢谢支持!
本文地址:https://www.jianshu.com/p/755db59cf4e4?v=1682584623209,转载请注明出处,谢谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容