vue2 echarts line 实现过程线上添加标注点,给提示框(tooltip)添加点击事件(点击显示弹出窗),框选添加标注点功能

110 阅读1分钟
<template>
  <div class="resultsReview-chart" id="resultsReviewChart">
    <div id="chart" class="chart-container" />
    <div id="chartMarkBox" class="chartMarkBox">
  </div>
</template>
<script>
import * as echarts from "echarts@5.1.2";

export default {
  data() {
    return {
      batchMarker: []// 框选数据    
    }
  },
  mounted() {
    this.initCharts();

    window.onresize = () => {
      this.chart.resize({ width: "auto", height: "auto" });
    };

    this.initTooltipClick();
  },
  methods: {
    // 无数据添加标注方法
    handleTooltipClick(event){
        if (event.target.id === 'clickElement') {
            const param1 = event.target.dataset.param1;
            const param2 = event.target.dataset.param2;
            if (this.props.showMarker) {
                this.setState({
                    markerParam: {
                        ym: param1,
                        value: null,
                        name: param2,
                        content: null,
                        tagType: null,
                    },
                });
                const markerBox = document.getElementById('chartMarkBox');
                markerBox.style.display = 'block';
                markerBox.style.left = tooltipX + 'px';
                markerBox.style.top = tooltipY + 'px';
            } else {
                message.warn('请先点击开始标注');
            }
        }
    };
    initTooltipClick() {
      // 处理tooltip中添加的点击事件,还未点击时已触发事件
      // 获取tooltip父元素并为其添加点击事件监听器
      const tooltipContainerParent = document.querySelector('.chart-container');

      if (tooltipContainerParent) {
          tooltipContainerParent.parentElement.addEventListener('click', this.handleTooltipClick);
      }
    },
    // 获取y轴最大最小值
    genMinAndMax(yData){
        const min = _.minBy(yData, (i) => {
            if (i !== null) {
                return Number(i);
            } else {
                return null;
            }
        });
        const max = _.maxBy(yData, (i) => {
            if (i !== null) {
                return Number(i);
            } else {
                return null;
            }
        });
        const minValue = min ? Number(min) : 0;
        const maxValue = max ? Number(max) : 0;
        let resultMin, resultMax;
        let step = maxValue - minValue;

        if (step > 0 && step < 2) {
            const middle = (maxValue + minValue) / 2;
            resultMin = (Number((middle - 1.5).toFixed(2)) * 100) / 100;
            resultMax = (Number((middle + 1.5).toFixed(2)) * 100) / 100;
        } else {
            if (step === 0) {
                step = 1;
            }
            resultMin = minValue === 0 ? 0 : (Number((minValue - (step / 0.6) * 0.2).toFixed(2)) * 100) / 100;
            resultMax = (Number(((step / 0.6) * 0.2 + maxValue).toFixed(2)) * 100) / 100;
        }

        if (resultMin < 0 && minValue > 0) {
            //如果没有小于0的数 将最小值设为0
            resultMin = 0;
        }

        // // //最小最大值相等时
        // if (step === 0) {
        //     step = 1;
        // }
        return [resultMin, resultMax];
    },
    // 获取y轴间隔,使过程线始终保持在中间位置
    getInterval(num1, num2) {
        const diff = Math.floor(num2 - num1);
        let interval = 1;
        while (diff / interval > 10) {
            interval *= 10;
        }
        while (diff / interval < 1) {
            interval /= 10;
        }
        if (interval < 1) {
            interval = 1;
        }
        return interval;
    },
    initOption() {
      const seriesData = [-2.15, -2.14, -2.15, -2.15, -2.14, -2.15, -2.16, -2.15, -2.15, -2.15, -2.15, -2.15, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null];
      const xAxisData = ['2023-03-30 00:00:00', '2023-03-30 04:00:00', '2023-03-30 08:00:00', '2023-03-30 12:00:00', '2023-03-30 16:00:00', '2023-03-30 20:00:00', '2023-03-31 00:00:00', '2023-03-31 04:00:00', '2023-03-31 08:00:00', '2023-03-31 12:00:00', '2023-03-31 16:00:00', '2023-03-31 20:00:00', '2023-04-01 00:00:00', '2023-04-01 04:00:00', '2023-04-01 08:00:00', '2023-04-01 12:00:00', '2023-04-01 16:00:00', '2023-04-01 20:00:00', '2023-04-02 00:00:00', '2023-04-02 04:00:00', '2023-04-02 08:00:00', '2023-04-02 12:00:00', '2023-04-02 16:00:00', '2023-04-02 20:00:00', '2023-04-03 00:00:00', '2023-04-03 04:00:00', '2023-04-03 08:00:00', '2023-04-03 12:00:00', '2023-04-03 16:00:00', '2023-04-03 20:00:00'];
      const [min, max] = this.genMinAndMax(seriesData);
      const avg = (max - min) / 2 + min;
      const markerData = [
        // 处理x轴数据不存在时显示标记
        {
          coord: [14, avg],   // 第一个值代表数据在X轴的位置,第二个值根据最大最小值取中间值,当y值不存在时,标记无法显示,给一个默认值
          name: '标注',
          symbol: "circle',
          symbolSize: 12,
          value: '数据',
        },
        // x轴数据存在时可正常显示标记
        {
          xAxis: '2023-03-31 20:00:00',
          yAxis: -2.15,
          name: '标注',
          symbol: "circle',
          symbolSize: 12,
          value: '数据2',
        }
      ]

      return {
        grid: {
            left: '5%',
            right: '5%',
            bottom: 40,
            top: 60,
            containLabel: true,
        },
        tooltip: {
            trigger: 'axis',
            triggerOn: 'mousemove|click',
            enterable: true,
            axisPointer: {
                // 坐标轴指示器,坐标轴触发有效
                type: 'shadow', // 默认为直线,可选为:'line' | 'shadow'
            },
            backgroundColor: 'rgba(255, 255, 255, 0.96)',
            textStyle: {
                color: '#595959',
                fontSize: 12,
                // lineHeight: 26,
            },
            //  padding: [8, 10],
            extraCssText: 'box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.12);opacity: 0.96;',
            position: function(point, params, dom, rect, size) {
                const [mouseX, mouseY] = point;
                let x = mouseX,
                    y = mouseY;
                const { offsetWidth: domWidth, offsetHeight: domHeight } = dom;
                const [viewWidth, viewHeight] = size.viewSize;
                if (mouseX + domWidth > viewWidth) {
                    x = x - domWidth;
                }
                if (mouseY + domHeight > viewHeight) {
                    y = y - domHeight;
                }
                tooltipX = x;
                tooltipY = y;
                return [x, y];
            },
            formatter: (params) => {
              return `
                <div class="self-tooltip-box">
                  <span class="y-data">${params[0].name}<span/><br/>
                  <div class="self-tooltip">
                    <span class="y-data">${params[0].seriesName}:</span><span class="y-name">${params[0].data || ''}</span><br/>
                    <div data-param1="${params[0].name}" data-param2="${params[0].seriesName}" id="clickElement" class="link">添加标注</div>
                  </div>
                </div>
              `;
            }
        },
        toolbox: {
            show: true,
            right: '3%',
            // showTitle: false, // 隐藏默认文字,否则两者位置会重叠
            feature: {
                brush: {
                    type: ['rect', 'clear'],
                    title: {
                        rect: '框选',
                        clear: '清除框选',
                    },
                },
            },
        },
        brush: {
            toolbox: ['rect', 'clear'],
            brushStyle: {
                borderWidth: 2,
                color: 'rgba(0,0,0,0)',
                borderColor: '#E80B0B',
            },
            xAxisIndex: 0,
            throttleType: 'debounce',
            throttleDelay: 1500,
        },
        xAxis: {
          type: 'category',
          data: xAxisData,
          axisTick: {
              alignWithLabel: true,
          },
          axisLabel: {
              textStyle: {
                  color: 'rgba(191,191,191,1)',
              },
          },
          axisLine: {
              show: true,
              onZero: false,
              lineStyle: {
                  type: 'solid',
                  color: 'rgba(191,191,191,1)',
              },
          },
          axisPointer: {
              lineStyle: {
                  color: '#FF4D4F',
              },
          },
        },
        yAxis: {
          type: 'value',
          name: '水位(m)',
          inverse: false,
          nameLocation: 'end',
          nameTextStyle: {
              color: '#BFBFBF',
              fontSize: 14,
              lineHeight: 22,
          },
          splitLine: {
              show: true,
              lineStyle: {
                  type: 'dashed',
              },
          },
          axisLine: {
              show: false,
          },
          axisTick: {
              show: false,
          },
          axisLabel: {
              show: true,
              textStyle: {
                  color: 'rgba(191,191,191,1)',
              },
              formatter: function(value, index) {
                  let copyValue = value;
                  if (typeof copyValue === 'number') {
                      copyValue = copyValue.toString();
                      if (copyValue.split('.')[1] && copyValue.split('.')[1].length > 3) {
                          return value.toFixed(3);
                      } else {
                          return value.toFixed(2);
                      }
                  } else {
                      return value;
                  }
              },
          },
          min: min,
          max: max,
          interval: getInterval(mins, maxs),
        },
        series: [
          {
              name: '水位(m)',
              type: 'line',
              data: seriesData,
              itemStyle: {
                  color: '#1890ff',
                  width: 3,
              },
              symbolSize: 8,
              large: true,
              markPoint: {
                  // symbolSize: [24, 24],
                  symbolOffset: [0, '-100%'],
                  label: {
                      fontSize: 10,
                      offset: [0, 16],
                      color: '#595959',
                  },
                  data: markerData,
              },
              yAxisIndex: 0,
              zlevel: 1,
              selectedMode: 'multiple',
              select: {
                  itemStyle: {
                      color: '#fa8c16',
                  },
              },
          },
        ],
        dataZoom: [
          {
              type: 'inside',
              start: 0,
              end: 100,
              minValueSpan: 3,
              // realtime: true,
          },
          {
              start: 0,
              end: 100,
              minValueSpan: 3,
              handleIcon:
                  'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
              handleSize: '80%',
              handleStyle: {
                  color: '#fff',
                  shadowBlur: 3,
                  shadowColor: 'rgba(0, 0, 0, 0.6)',
                  shadowOffsetX: 2,
                  shadowOffsetY: 2,
              },
              bottom: 0,
              // realtime: true,
          },
        ],
      }
    }
    initCharts() {
      this.chart = echarts.init(document.getElementById("chart"), "dark");
      const option = this.initOption();
      this.chart.setOption(option);
      // 点击添加标注
      this.chart.on('click', params => {
        // 处理点击事件
        const domWidth = document.getElementById('resultsReviewChart').getBoundingClientRect()
            .width;
        const markerBox = document.getElementById('chartMarkBox');
        markerBox.style.display = 'block';
        markerBox.style.left =
            params.event.offsetX > domWidth - 327
                ? params.event.offsetX - 327 + 'px'
                : params.event.offsetX + 25 + 'px';
        markerBox.style.top = params.event.offsetY - 80 + 'px';
      })

      // 框选
      this.chart.on('brushEnd', (params) => {
        const xAxisArr = params.areas[0].coordRange[0];
        const xAxisData = .slice(xAxisArr[0], xAxisArr[1]);

        this.batchMarker = option.xAxis.data;
        //  const domWidth = document.getElementById('resultsReviewChart').getBoundingClientRect().width;
        const markerBox = document.getElementById('chartMarkBox');
        markerBox.style.display = 'block';
        markerBox.style.left = 527 + 'px';
        markerBox.style.top = 200 + 'px';
      });
      // 清除框选
      this.chart.on('brush', (params) => {
        if (params.command === 'clear') {
            // 执行清除选区后的操作
            this.cleanEdit();
        }
      });
    }
  }
  
}
</script>

<style lang="scss" scoped>
.chart-container {
  width: 100%;
  height: calc(100vh - 340px);
  padding: 16px 24px;
  .self-tooltip-box{
      width: 196px;
      font-size: 12px;
      padding: 8px 16px;
      font-weight: 400;
      .self-tooltip{
          .y-data{
              font-family: Microsoft YaHei UI;
              font-style: normal;
              font-weight: normal;
              color: #595959;
              font-size: 12px;
              line-height: 22px;  
          }
          .y-name{
              float: right;
              color: #595959;
              font-size: 12px;
              line-height: 22px; 
          }
          .link{
              font-size: 12px;
              line-height: 22px;
              margin-top: 8px;
              color: #1890FF;
              cursor: pointer;
          }
      }
  }
}
.chartMarkBox{
  width: 327px; 
  // height: 200px;
  position: absolute; 
  display: none;
  // min-height: 118px; 
  background: #ffffff;
  box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.2);
  border-radius: 4px;
  padding: 16px 16px 0 16px;
  z-index: 99;
}
</style>