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

最后

若對你有幫助,請點個贊吧,若能打賞不勝感激,謝謝支持!
本文地址:http://www.lxweimin.com/p/755db59cf4e4?v=1682584623209,轉載請注明出處,謝謝。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內容