让前端飞WEB前端程序开发web前端技术分享

echarts 直角坐标系中多个系列多个y轴展示方案

2022-08-18  本文已影响0人  阿巳交不起水电费

在做可视化数据展示的时候经常遇到多条折线数据显示到一个坐标系中,每条折线的纵轴跨度各不相同的情形,比如有的折线范围是0-100,有的是1000-10000,这样若是共用一个y轴将出现下面的情况:


image.png

可以看到蓝色的线被按在地上摩擦,这显然是不合理的。这还只是两条线,那6条、10条呢。

请看到最后,结尾有彩蛋哦。

一、6条以下的折线可以这样,个人非常推荐:

image.png

这里的数据都是我随机生成的,看起来趋势比较乱,见谅。对应的y轴可以用颜色区分,注意:这里没有显示y轴的splitLine。

代码如下,注意:我这个案例只考虑了最大4条线的情况

    <!--多y轴解决方案-->
<template>
  <div class="wrap">
    <!--路径图2-->
    <div class="com-box">
      <h1>多y轴折线图展示方案</h1>
      <div class="flex-box">
        <div class="line-chart" style="height: 400px;">
          <chart-base :option="chartOption"/>
        </div>
      </div>
    </div>

  </div>
</template>

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

export default {
  name: 'ManyYChart',
  components: {
    ChartBase
  },
  data() {
    return {
      chartOption: {},
    }
  },
  methods: {
    // 范围内随机数
    rand(m, n) {
      return Math.ceil(Math.random() * (n-m) + m)
    },
    /**
     * 让趋势图始终显示在中心位置
     * @float: 让内部趋势图上下浮动多少
     */
    setMaxMin(float = 0.1) {
      return {
        min: function (value) {
          let max = value.max
          let min = value.min
          let range = Math.abs(max - min)
          return min - float * range;
        },
        max: function (value) {
          let max = value.max
          let min = value.min
          let range = Math.abs(max - min)
          return max + float * range
        }
      }
    },
    getChartOption() {
      let color = [
        '#fab83e',
        '#e733a3',
        '#7482f2',
        '#51bcff',
        '#47d5e4',
      ]
      let option = {
        tooltip: {
          trigger: 'axis'
        },
        dataZoom: [
          {
            type: 'slider',
            bottom: '2%'
          },
          {
            type: 'inside',
          }
        ],
        legend: {
          left: 'center'
        },
        grid: {
          left: 70,
          right: 70,
          bottom: '12%',
          top: '15%',
          containLabel: true
        },
        xAxis: {
          type: 'value',
          boundaryGap:true
        },
        yAxis: [],
        series: []
      };
      return {
        option,
        color,
        /**
         * 构造追加y轴和series的新 item
         * @option:当前option
         * @index:第几个series
         * @seriesName:当前series name
         * @data:当前series data
         * */
        appendSeriesYAxisItem: ({option, index, seriesName, data}) => {
          let _color = color[index]
          let leftYNum = option.yAxis.filter(sub => sub.position === 'left').length
          let rightYNum = option.yAxis.filter(sub => sub.position === 'right').length
          let currentPosition = index % 2 === 0 ? 'left' : 'right'
          option.yAxis.push({
            type: 'value',
            position: currentPosition,
            offset: (currentPosition === 'left' ? leftYNum : rightYNum) * 90,
            name: seriesName,
            nameLocation: 'end',
            splitLine: {
              show: false
            },
            nameTextStyle: {
              color: _color
            },
            axisLine: {
              onZero: false,
              lineStyle: {
                color: _color
              }
            },
            axisLabel: {
              show: true,
              color: _color,
              formatter(params){
                return (params).toFixed(0)
              }
            },
            ...this.setMaxMin()
          })
          option.series.push({
            symbolSize: 2,
            name: seriesName,
            type: 'line',
            data: data,
            yAxisIndex: index,
            itemStyle: {
              color: _color
            },
            lineStyle: {
              width:2,
              color: _color
            }
          })
        }
      }
    },
    getChartData() {
      // 构造测试数据
      let resData = []
      for (let j = 0; j <= 3; j++) {
        resData.push({
          name: `line${j + 1}`,
          data: []
        })
      }
      let randArr = [[100,300],[1000, 2000],[-100,-30],[10,15],[800,900],[100,200],[30,60],[600,650],[1000,1500]]
      let _indexFn = ()=>{ // 随机下标
        return Math.floor(this.rand(0, randArr.length-1))
      }
      let _valFn=()=>{
        return this.rand(...randArr[_indexFn()])
      }

      for (let i = 1; i < 200; i++) {
        // resData[0].data.push([i, _valFn()])
        // resData[1].data.push([i, _valFn()])
        // resData[2].data.push([i, _valFn()])
        // resData[3].data.push([i, _valFn()])

        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)])
      }

      // 根据数据渲染chart
      let {option, appendSeriesYAxisItem} = this.getChartOption()
      resData.filter(item => item).map((item, index) => {
        appendSeriesYAxisItem({
          option,
          index,
          seriesName: item.name,
          data: item.data
        })
      })

      this.chartOption = option
    },
  },
  mounted() {
    this.getChartData()
  }
}
</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;
}

.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;
  }
}
</style>

ChartBase.vue代码如下:

<!--基础 chart-->
<template>
  <div class="chart-base">
    <div style="width:100%;height:100%;" ref="chartRef"></div>
    <div v-if="isChartNull(option)" class="noData">暂无数据</div>
  </div>
</template>
<script>

export default {
  name: 'chartBase',
  data() {
    return {
      myChart: '',
    }
  },
  props: {
    option: {
      type: Object,
      default() {
        return {}
      }
    }
  },
  watch: {
    option: {
      deep: true,
      handler: function (value) {
        this.resetChartData()
      }
    }
  },
  mounted() {
    this.myChart = this.$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)
    },
  },
  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>

二、需要显示横向分割线的解决办法

这种情况必须对左右的y轴设置maxmininterval,强制分割左右坐标轴为相同的份数,这里也是最麻烦的地方。

image.png
这里贴下大概的代码,也许不是最好的的实现方案(这个是之前同事想出来的,我后面也一直采纳的这种方法),但是可以给没有头绪的小伙伴借鉴的思路,想了半天不知道怎么文字描述这个原理,小伙伴们感兴趣的自己理解吧。

调用后的返回值是这样:


image.png image.png
image.png

三、6条以上y轴刻度不同的解决方案

image.png

这种情况就不用显示y轴了,显示了也没意义,y轴刻度不一样,这种情况的重点应该只是每条线的趋势。

这个还能这样玩:


3.gif

加上定时任务且有两个x轴:


4.gif

这个效果实现起来有点麻烦,是基于echartsdataZoom封装的,dataZoom可对y方向进行缩放,下面两种任选其一都可实现对y方向的缩放。这个效果看起来简单,但是真正实现的话里面有亿点点细节需要考虑,才知道有多少坑要踩。。。篇幅有限,小伙伴有兴趣的话后面再讲。

image.png

若对你有帮助,请点个赞吧,谢谢支持!
本文地址:https://www.jianshu.com/p/0dce0b877b7c,转载请注明出处,谢谢。

上一篇下一篇

猜你喜欢

热点阅读