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

在做可视化数据展示的时候经常遇到多条折线数据显示到一个坐标系中,每条折线的纵轴跨度各不相同的情形,比如有的折线范围是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,转载请注明出处,谢谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容