echarts图表双Y轴时,如何让左右两边刻度保持一致

如下图,左右两边的Y轴线不在同一刻度上。

image.png

使用vue2代码修改后的效果:


image.png

注意:直接将option配置放在echarts官网示例中运行是没效果的,原因是修改的option.yAxis.interval后,没有执行【myChart.clear(); 与 myChart.setOption(option);】两个函数。

echarts的option代码如下:

<template>
  <div>
    <JEcharts ref="JEcharts" :option="option" :emptyState="false" :height="300" />
  </div>
</template>

<script>
export default {
  name: '',
  components: {},
  computed: {},
  data() {
    return {
      option: {
        // color: ['#70b603', '#e3935d'],
        title: {
          text: '能源结构分布趋势',
          top: 15,
          left: 'center',
          textStyle: {
            color: '#333',
            fontSize: 15,
          },
        },
        tooltip: {
          trigger: 'axis',
        },
        legend: {
          type: 'scroll',
          top: '48', //与上方的距离 可百分比% 可像素px
        },
        grid: {
          top: 100,
          left: 50,
          right: 60,
          bottom: '3%',
          containLabel: true,
        },
        toolbox: {
          // x: '1250', //下载的字体显示不全
          right: 25,
        },
        xAxis: {
          type: 'category',
          // boundaryGap: true,
          data: ['1月', '2月', '3月', '4月', '5月'],
          // data: [],
        },
        yAxis: [
          {
            type: 'value',
            name: '用电量(kW·h)',
            nameTextStyle: {
              width: 200,
              padding: [0, 0, 0, 50],
              fontSize: 12,
              color: '#127ffb', // 设置 Y 轴标题颜色为红色
              lineHeight: 20, // 行高
            },
            splitLine: {
              show: true,
              lineStyle: {
                color: '#ccc',
                type: 'dashed',
              },
            },
            axisLine: {
              //坐标轴轴线相关设置。数学上的x轴
              show: false, //false 不显示
              lineStyle: {
                color: '#797777',
              },
            },
            axisLabel: {
              color: '#127ffb',
              fontSize: 12, // Y轴字体大小 12px
            },
            axisTick: {
              show: false, //false 不显示
            },
            // min: 0,
            // splitNumber: 5, // 与 yAxis[1] 保持相同分割数,使两侧刻度线对齐
            interval: 100,
            min: (value) => (value.min > 0 || isNaN(value.min) ? 0 : value.min),
            max: (value) => {
              //刻度分割成5等份。注意:yAxis[0]下标,vue2的箭头函数的this指向实例(vue3语法不是用this)
              try {
                let echartsOption = this.option || option
                let splitNumber = 5 //分为五份
                let yAxisObj = echartsOption.yAxis[0]
                if (isNaN(value.max)) {
                  if (yAxisObj.interval != 1) yAxisObj.interval = 1
                  return splitNumber
                }
                if (!echartsOption) throw new Error('echartsOption is null!')
                if (value.max > 10) value.max = Math.ceil(value.max / splitNumber) * splitNumber // 确保大于10时,最大值能被5整除,这样刻度值就不会出现小数
                let { max, min } = value
                let getDigitLen = (num1 = '', num2 = '') => {
                  return Math.max((String(num1).split('.')[1] || '').length, (String(num2).split('.')[1] || '').length)
                }
                if (min < 0) {
                  let len = getDigitLen(max, min) //小数点长度
                  max = (max * Math.pow(10, len) - min * Math.pow(10, len)) / Math.pow(10, len) // 防止精度丢失
                }
                let digitLen = getDigitLen(max) //小数点长度
                let interval = (Number(String(max).replace('.', '')) * 100) / splitNumber / Math.pow(10, digitLen + 2) //分割间隔,分为五份!乘100(再除多100)目的是能完全整除5,否则会有精度丢失(测试到第七位小数精度也会丢失,请另找方法处理)
                if (yAxisObj.interval != interval) yAxisObj.interval = interval //没这个条件,会进入死循环
                return value.max
              } catch (error) {
                console.error('echarts图表yAxis[0]的max函数运行出错了: ', error)
                return value.max
              }
            },
          },
          {
            type: 'value',
            name: '环比上升率%',
            nameTextStyle: {
              width: 200,
              padding: [0, 0, 0, 50],
              fontSize: 12,
              color: '#127ffb', // 设置 Y 轴标题颜色为红色
              lineHeight: 20, // 行高
            },
            splitLine: {
              show: false, // 右侧轴不重复显示网格线(建议隐藏,有时计算出来差1个像素可以忽略)
              lineStyle: {
                color: '#ccc',
                type: 'dashed',
              },
            },
            axisLine: {
              //坐标轴轴线相关设置。数学上的x轴
              show: false, //false 不显示
              lineStyle: {
                color: '#797777',
              },
            },
            axisLabel: {
              color: '#127ffb',
              fontSize: 12, // Y轴字体大小 12px
            },
            axisTick: {
              show: false, //false 不显示
            },
            // min: 0,
            // splitNumber: 5, // 与 yAxis[0] 保持相同分割数,使两侧刻度线对齐
            interval: 100,
            min: (value) => (value.min > 0 || isNaN(value.min) ? 0 : value.min),
            max: (value) => {
              //刻度分割成5等份。注意:yAxis[0]下标,vue2的箭头函数的this指向实例(vue3语法不是用this)
              try {
                let echartsOption = this.option || option
                let splitNumber = 5 //分为五份
                let yAxisObj = echartsOption.yAxis[1]
                if (isNaN(value.max)) {
                  if (yAxisObj.interval != 1) yAxisObj.interval = 1
                  return splitNumber
                }
                if (!echartsOption) throw new Error('echartsOption is null!')
                if (value.max > 10) value.max = Math.ceil(value.max / splitNumber) * splitNumber // 确保大于10时,最大值能被5整除,这样刻度值就不会出现小数
                let { max, min } = value
                let getDigitLen = (num1 = '', num2 = '') => {
                  return Math.max((String(num1).split('.')[1] || '').length, (String(num2).split('.')[1] || '').length)
                }
                if (min < 0) {
                  let len = getDigitLen(max, min) //小数点长度
                  max = (max * Math.pow(10, len) - min * Math.pow(10, len)) / Math.pow(10, len) // 防止精度丢失
                }
                let digitLen = getDigitLen(max) //小数点长度
                let interval = (Number(String(max).replace('.', '')) * 100) / splitNumber / Math.pow(10, digitLen + 2) //分割间隔,分为五份!乘100(再除多100)目的是能完全整除5,否则会有精度丢失(测试到第七位小数精度也会丢失,请另找方法处理)
                if (yAxisObj.interval != interval) yAxisObj.interval = interval //没这个条件,会进入死循环
                return value.max
              } catch (error) {
                console.error('echarts图表yAxis[1]的max函数运行出错了: ', error)
                return value.max
              }
            },
          },
        ],
        series: [
          {
            name: '空调',
            type: 'bar',
            yAxisIndex: [0],
            stack: 'total',
            barMaxWidth: '30',
            // color: '#70b603',
            data: [121, 32, 101, 134, 90],
            // data: [],
          },
          {
            name: '风扇',
            type: 'bar',
            yAxisIndex: [0],
            stack: 'total',
            barMaxWidth: '30',
            // color: '#e3935d',
            data: [220, 181, 18, 234, 291],
            // data: [],
          },
          {
            name: '环比上升率',
            type: 'line',
            yAxisIndex: [1],
            // color: '#e3935d',
            data: [-0.1, 0.33, 0.065, 0.1, 0.312],
            // data: [],
          },
        ],
      },
    }
  },
  created() {},
  methods: {},
}
</script>
<style lang="less" scoped></style>

JEcharts.vue组件

<template>
  <div class="JEcharts">
    <a-empty
      v-if="isShowEmpty"
      :class="emptyClassName"
      style="
        height: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
        flex-direction: column;
        border: 1px solid #e8e8e8;
        margin: auto;
      "
    />
    <div v-show="!isShowEmpty" :id="echartsID" :style="{ height: countHeight }"></div>
  </div>
</template>

<script>
import * as echarts from 'echarts'
import 'echarts-wordcloud'
export default {
  name: 'JEcharts',
  props: {
    emptyClassName: {
      type: String,
      default: '',
    },

    height: {
      type: Number | String,
      default: 0,
      required: false,
    },
    option: {
      type: Object,
      default: () => {},
      required: false,
    },
    //是否需要展示【空状态:暂无数据】,默认不展示
    emptyState: {
      type: Boolean,
      default: false,
      required: false,
    },
    theme: {
      type: Object | String,
      default: '',
    },
    echartInitOptions: {
      type: Object,
      default: undefined,
    },
  },
  computed: {
    isShowEmpty() {
      if (!this.option && this.emptyState) {
        return true
      } else {
        if (Object.keys(this.option).length == 0 && this.emptyState) {
          return true
        } else {
          return false
        }
      }
    },
    countHeight() {
      if (this.height === 0) {
        return '100%'
      }
      if (typeof this.height === 'number') {
        return `${this.height}px`
      }
      if (typeof this.height === 'string') {
        if (isNaN(Number(this.height))) {
          return this.height //可能的单位是px,rem,em,vh,%
        } else {
          return `${this.height}px`
        }
      }
    },
  },
  watch: {
    option: {
      immediate: true,
      handler(nVal, oVal) {
        this.$nextTick(function () {
          // 由于Vue无法监听到深层对象,所以在父页面中需要对option重新赋值
          // 1.【this.option = Object.assign({},this.option);】推荐
          // 2.【this.$refs.JEcharts.setOption(this.option)】
          this.setOption(nVal)
        })
      },
      deep: true,
    },
    $route: function (newRoute, oldRoute) {
      this.$nextTick(() => {
        if (this.myChart && !this.myChart.isDisposed()) {
          this.myChart.resize()
        }
      })
    },
  },

  data() {
    return {
      echartsID: `JEchartsId-${parseInt(Math.random() * 1e12)}`, //生成唯一的ID
      myChart: null,
    }
  },
  created() {},
  mounted() {
    this.initEchart()
  },
  beforeDestroy() {
    if (this.myChart) {
      this.myChart.dispose()
      this.myChart.clear() //清空画布
    }

    if (this.instanceObserver) {
      if (document.getElementById(this.echartsID)) {
        this.instanceObserver.unobserve(document.getElementById(this.echartsID))
      }
      this.instanceObserver.disconnect() // 停止所有观察
    }
    if (this.windowResize) {
      window.removeEventListener('resize', this.windowResize)
    }
  },
  destroyed() {},
  activated() {},
  methods: {
    initEchart() {
      if (!this.myChart && document.getElementById(this.echartsID)) {
        this.myChart = echarts.init(document.getElementById(this.echartsID), this.theme, this.echartInitOptions)
        this.handleYAxisAxisLabelFormatter(this.option)
        this.myChart.setOption(this.option || {})

        this.myChart.on('finished', () => this.$emit('finish'))
        this.$nextTick(() => {
          this.ResizeObserver()
        })
      }
    },
    setOption(option = {}) {
      if (this.isShowEmpty) return
      if (this.myChart) {
        this.handleYAxisAxisLabelFormatter(option)
        this.myChart.clear() //清空画布(如果不清空画布,有数据缓存的bug,图表有旧数据存在)
        this.myChart.setOption(option)
      } else {
        this.initEchart()
      }
    },
    //使用ResizeObserver监听内容变化引起的高度变化,然后刷新图表
    ResizeObserver() {
      if (window.ResizeObserver) {
        // 创建一个ResizeObserver实例,并定义回调函数
        this.instanceObserver = new ResizeObserver((entries) => {
          let { width, height } = entries[0].contentRect
          let chartWidth = this.myChart.getHeight()
          let chartHeight = this.myChart.getWidth()
          //宽度/高度无变化(向上取整,原因有可能出现小数)!宽度/高度相差2px以内,默认是宽度/高度无变化
          if (
            Math.abs(Math.ceil(width) - Math.ceil(chartWidth)) > 2 ||
            Math.abs(Math.ceil(height) - Math.ceil(chartHeight)) > 2
          ) {
            //放大或缩小屏幕时,重新渲染图表,执行动画
            this.myChart.clear() //清空画布
            this.myChart.resize()
            this.myChart.setOption(this.option) //动画会重新渲染
          }
        })
        // 开始观察目标元素
        this.instanceObserver.observe(document.getElementById(this.echartsID))
      } else {
        //旧浏览器不支持ResizeObserver,需要兼容处理
        this.windowResize = () => {
          clearTimeout(this.timer)
          this.timer = setTimeout(() => {
            this.myChart.clear() //清空画布
            this.myChart.resize()
            this.myChart.setOption(this.option) //动画会重新渲染
            let timer2 = setTimeout(() => {
              this.myChart.resize()
              clearTimeout(timer2)
            }, 1500)
            clearTimeout(this.timer)
          }, 500)
        }
        window.addEventListener('resize', this.windowResize)
      }
    },
    //Y轴,数字过大,缩减数字后,在其后面添加【万】【亿】
    handleYAxisAxisLabelFormatter(option) {
      let handleYAxis = (obj) => {
        if (obj.axisLabel && obj.axisLabel._formatter === true) {
          delete obj.axisLabel._formatter
          obj.axisLabel.formatter = (value, index) => {
            return this.axisLabelFormatter(value, index, option.series)
          }
        }
      }
      if (Object.prototype.toString.call(option.yAxis) == '[object Array]') {
        for (let i = 0; i < option.yAxis.length; i++) {
          handleYAxis(option.yAxis[i])
        }
      }
      if (Object.prototype.toString.call(option.yAxis) == '[object Object]') {
        handleYAxis(option.yAxis)
      }
    },
    //数字后面可能带【万、亿】
    axisLabelFormatter(value, index, series) {
      let unit = ''
      let max = 0
      series = series || []
      for (let i = 0; i < series.length; i++) {
        let num = Math.max.apply(null, series[i].data)
        max = Math.max(num, max)
      }
      if (max > 1e4 && max <= 1e8) {
        unit = '万'
        value = Math.floor((value * 100) / 1e4) / 100 //保留两位小数,向下取整
      }
      if (max > 1e8) {
        unit = '亿'
        value = Math.floor((value * 100) / 1e8) / 100 //保留两位小数,向下取整
      }
      return value + unit
    },
  },
}
</script>

<style lang="less" scoped>
.JEcharts {
  height: 100%;
  min-height: 10px;
  position: relative;
}
</style>
<style lang="less">
//大屏Echarts tooltip提示框组件颜色修改
.JEcharts-fullsceen > div > div:nth-child(2) {
  background-color: rgba(4, 15, 39, 0.9) !important;
  border-color: #2b709f !important;
  box-shadow: inset 0px 0px 10px #009cae !important;
  &,
  * {
    color: #fff !important;
  }
}
</style>

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容