zxl
2026-03-25 73e0b3791990bd60c06c2c0388aae9f9faf538a6
src/views/dataAnalysis/components/DataReLineChart.vue
@@ -22,13 +22,204 @@
      type: Boolean,
      default: false,
    },
    enablePointClick: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      chartEntity: null,
      zrClickHandler: null,
      zrMoveHandler: null,
      zrMouseOutHandler: null,
      lastHoverIndex: -1,
      lastEmitName: '',
      lastEmitAt: 0,
      lastLegendToggleAt: 0,
    }
  },
  methods: {
    getOffsetPoint(event) {
      if (!event) {
        return null
      }
      if (event.offsetX !== undefined && event.offsetY !== undefined) {
        return [event.offsetX, event.offsetY]
      }
      if (event.event && event.event.offsetX !== undefined && event.event.offsetY !== undefined) {
        return [event.event.offsetX, event.event.offsetY]
      }
      return null
    },
    isPointInGrid(point) {
      if (!this.chartEntity || !this.chartEntity.containPixel || !point) {
        return false
      }
      return this.chartEntity.containPixel({ gridIndex: 0 }, point)
    },
    getXData() {
      return this.chartData && this.chartData.xData ? this.chartData.xData : []
    },
    resolveNearestIndexByOffsetX(offsetX) {
      let xData = this.getXData()
      if (!this.chartEntity || !this.chartEntity.convertToPixel || !xData.length) {
        return -1
      }
      let nearestIndex = -1
      let minDistance = Number.MAX_VALUE
      for (let i = 0; i < xData.length; i++) {
        let xPixel = this.chartEntity.convertToPixel({ xAxisIndex: 0 }, xData[i])
        if (Array.isArray(xPixel)) {
          xPixel = xPixel[0]
        }
        if (xPixel === undefined || xPixel === null || Number.isNaN(Number(xPixel))) {
          continue
        }
        let distance = Math.abs(Number(offsetX) - Number(xPixel))
        if (distance < minDistance) {
          minDistance = distance
          nearestIndex = i
        }
      }
      if (nearestIndex === -1) {
        return -1
      }
      return nearestIndex
    },
    resolveClickIndex(event, params) {
      let xData = this.getXData()
      if (!xData.length) {
        return -1
      }
      let point = this.getOffsetPoint(event)
      if (!this.isPointInGrid(point)) {
        return -1
      }
      if (params && typeof params.dataIndex === 'number' && xData[params.dataIndex] !== undefined) {
        return params.dataIndex
      }
      if (params && params.name) {
        let nameIndex = xData.findIndex((item) => item === params.name)
        if (nameIndex > -1) {
          return nameIndex
        }
      }
      if (this.chartEntity && this.chartEntity.convertFromPixel && event) {
        let xAxisValue = this.chartEntity.convertFromPixel({ xAxisIndex: 0 }, [event.offsetX, event.offsetY])
        if (xAxisValue !== undefined && xAxisValue !== null && !Number.isNaN(Number(xAxisValue))) {
          let xIndex = Math.round(Number(xAxisValue))
          if (xIndex >= 0 && xIndex < xData.length) {
            return xIndex
          }
        }
      }
      if (event && event.offsetX !== undefined && event.offsetX !== null) {
        return this.resolveNearestIndexByOffsetX(event.offsetX)
      }
      return -1
    },
    emitChartClickByIndex(index, seriesName) {
      let xData = this.getXData()
      if (index < 0 || index >= xData.length) {
        return
      }
      let clickName = xData[index]
      let now = Date.now()
      if (this.lastEmitName === clickName && now - this.lastEmitAt < 120) {
        return
      }
      this.lastEmitName = clickName
      this.lastEmitAt = now
      this.$emit('chart-click', {
        name: clickName,
        seriesName: seriesName || null,
      })
    },
    hideTooltip() {
      if (!this.chartEntity) {
        return
      }
      this.chartEntity.dispatchAction({
        type: 'hideTip',
      })
    },
    bindChartClick() {
      if (!this.chartEntity) {
        return
      }
      this.chartEntity.off('click')
      if (this.chartEntity.getZr) {
        let zr = this.chartEntity.getZr()
        if (this.zrClickHandler) {
          zr.off('click', this.zrClickHandler)
        }
        if (this.zrMoveHandler) {
          zr.off('mousemove', this.zrMoveHandler)
        }
        if (this.zrMouseOutHandler) {
          zr.off('globalout', this.zrMouseOutHandler)
        }
      }
      if (!this.enablePointClick) {
        let chartDom = document.getElementById(this.domId)
        if (chartDom) {
          chartDom.style.cursor = 'default'
        }
        return
      }
      let chartDom = document.getElementById(this.domId)
      if (chartDom) {
        chartDom.style.cursor = 'pointer'
      }
      this.chartEntity.on('legendselectchanged', () => {
        this.lastLegendToggleAt = Date.now()
      })
      this.chartEntity.on('click', (params) => {
        if (params && params.componentType === 'legend') {
          return
        }
        if (Date.now() - this.lastLegendToggleAt < 180) {
          return
        }
        let index = this.resolveClickIndex(params && params.event ? params.event : null, params)
        this.emitChartClickByIndex(index, params ? params.seriesName : null)
      })
      this.zrMoveHandler = (event) => {
        let xData = this.getXData()
        if (!xData.length) {
          return
        }
        let xIndex = this.resolveClickIndex(event, null)
        if (xIndex < 0 || xIndex >= xData.length) {
          return
        }
        if (this.lastHoverIndex === xIndex) {
          return
        }
        this.lastHoverIndex = xIndex
        this.chartEntity.dispatchAction({
          type: 'showTip',
          seriesIndex: 0,
          dataIndex: xIndex,
        })
      }
      this.zrMouseOutHandler = () => {
        this.lastHoverIndex = -1
        this.hideTooltip()
      }
      this.zrClickHandler = (event) => {
        if (Date.now() - this.lastLegendToggleAt < 180) {
          return
        }
        let index = this.resolveClickIndex(event, null)
        this.emitChartClickByIndex(index, null)
      }
      let zr = this.chartEntity.getZr()
      zr.on('mousemove', this.zrMoveHandler)
      zr.on('globalout', this.zrMouseOutHandler)
      zr.on('click', this.zrClickHandler)
    },
    setChart() {
      if (!this.chartEntity) {
        let chartDom = document.getElementById(this.domId)
@@ -38,6 +229,8 @@
      var option = {
        tooltip: {
          trigger: 'axis',
          confine: true,
          extraCssText: 'pointer-events:none;',
          axisPointer: {
            // 坐标轴指示器,坐标轴触发有效
            type: 'line', // 默认为直线,可选为:'line' | 'shadow'
@@ -216,6 +409,7 @@
        ],
      }
      option && this.chartEntity.setOption(option, true)
      this.bindChartClick()
    },
    setPieUpChart() {
      if (!this.chartEntity) {
@@ -241,6 +435,8 @@
      var option = {
        tooltip: {
          trigger: 'axis',
          confine: true,
          extraCssText: 'pointer-events:none;',
          axisPointer: {
            // 坐标轴指示器,坐标轴触发有效
            type: 'line', // 默认为直线,可选为:'line' | 'shadow'
@@ -299,6 +495,7 @@
        series: [...data],
      }
      option && this.chartEntity.setOption(option, true)
      this.bindChartClick()
    },
    setOnlyLineChart() {
      if (!this.chartEntity) {
@@ -309,6 +506,8 @@
      var option = {
        tooltip: {
          trigger: 'axis',
          confine: true,
          extraCssText: 'pointer-events:none;',
          axisPointer: {
            // 坐标轴指示器,坐标轴触发有效
            type: 'line', // 默认为直线,可选为:'line' | 'shadow'
@@ -442,6 +641,7 @@
        ],
      }
      option && this.chartEntity.setOption(option, true)
      this.bindChartClick()
    },
  },
}
@@ -449,5 +649,7 @@
<style lang="less" scoped>
.line-chart {
  height: 20vh;
  position: relative;
  overflow: hidden;
}
</style>