echarts堆叠立体柱状图+混合折线+轮播

<template>
  <div :id="echartsId" style="width: 100%"></div>
</template>

<script setup>
  import { onMounted, ref, defineProps, watch, defineEmits, onUnmounted } from 'vue'
  import * as echarts from 'echarts'
  const emit = defineEmits(['openDialog'])
  const props = defineProps({
    // id
    echartsId: {
      type: String,
      default: 'echartsId',
    },
    // 切换重新渲染
    echartsBool: {
      type: Boolean,
      default: null,
    },
    // y轴数据上面的名字
    yAxisJsonName: {
      type: String,
      default: '单位:个数',
    },
    // y轴字体样式
    nameTextStyle: {
      type: Object,
      default: () => {
        return {
          color: '#fff',
          fontSize: 14,
          fontWeight: 400,
          verticalAlign: 'bottom',
          // 左上字体位置
          padding: [0, -20, 10, 20],
          fontFamily: 'PingFang SC',
        }
      },
    },
    // y轴字体样式
    nameTextStyleTwo: {
      type: Object,
      default: () => {
        return {
          color: '#fff',
          fontSize: 14,
          fontWeight: 400,
          verticalAlign: 'bottom',
          // 左上字体位置
          padding: [0, 20, 10, -20],
          fontFamily: 'PingFang SC',
        }
      },
    },
    // 主体宽度
    barWidth: {
      type: String,
      default: '40',
    },
    // dataListLength 大小==长度约束柱状宽度  定时器==个数 两个不会同时出现大小暂时不用
    dataListLength: {
      type: Number,
      default: 6,
    },
    // x轴展示的名字
    xAxisJson: {
      type: Array,
      default: () => {
        return []
      },
    },
    //y轴的展示名字
    yAxisJson: {
      type: Array,
      default: () => {
        return []
      },
    },
    // data展示数据
    dataJson: {
      type: Object,
      default: () => {
        return []
        // return [
        //   [10, 20, 30, 5, 8],
        //   [8, 10, 0, 30, 5],
        //   [30, 5, 0, 10, 20],
        //   [20, 30, 0, 8, 10],
        //   [5, 8, 10, 20, 30],
        // ]
      },
    },
    // legend方向
    legendDirection: {
      type: Object,
      default: () => {
        return {
          bottom: '0',
          top: '0',
          left: '0',
          right: '0',
        }
      },
    },
    // grid方向
    gridDirection: {
      type: Object,
      default: () => {
        return {
          bottom: '10%',
          top: '25%',
          right: '6%',
          left: '6%',
        }
      },
    },
    // 柱体颜色 定义的是左右渐变 ,如果不需要传过来一样的就好了
    barColorArray: {
      type: Object,
      default: () => {
        return {
          // 从左到右
          colorArr1: ['#0E3C69', '#0E336E', '#5433FF', '#20BDFF'],
          colorArr2: ['#FE8C00', '#FE8C00', '#FE8C00', '#F83600'],
          colorArr3: ['#2CA6A6', '#2CA6A6', '#43E3E3', '#247C7D'],
          colorArr4: ['#18D4A0', '#18D4A0', '#AAFFA9', '#11FFBD'],
          colorArr5: ['#823CF4', '#823CF4', '#823CF4', '#D015E0'],
        }
      },
    },
    // 顶片颜色 同柱体颜色一样
    upColorArray: {
      type: Object,
      default: () => {
        return {
          colorArr1: ['#2484BA', '#2484BA', '#2D9DD6', '#2D9DD6'],
          colorArr2: ['#FDB600', '#FDB600', '#FDB600', '#FDB600'],
          colorArr3: ['#44E6E6', '#44E6E6', '#44E6E6', '#44E6E6'],
          colorArr4: ['#1DF9BC', '#1DF9BC', '#1DF9BC', '#1DF9BC'],
          colorArr5: ['#6515E8', '#6515E8', '#6515E8', '#6515E8'],
        }
      },
    },
    // 底部颜色
    bottomColorArray: {
      type: Array,
      default: () => {
        return []
      },
    },
    // 开启底部颜色不
    openBottomColor: {
      type: Boolean,
      default: false,
    },
    // 类型判断 ==>整改率==line
    zglType: {
      type: String,
      default: '',
    },
    formatterParams: {
      type: Array,
      default: [
        { name: '隐患事件督办', color: '#455AFF' },
        { name: '不安全事件督办', color: '#FC7A00' },
        { name: '风险督办', color: '#8AFFAD' },
        { name: '重大作业督办', color: '#3BCCCD' },
        { name: '其他', color: '#AF24E8' },
      ],
    },
  })
  //组织颜色
  let setColor = function (colorArr) {
    let color = {
      type: 'linear',
      x: 0,
      x2: 1,
      y: 0,
      y2: 0,
      /* 此处决定阴暗面 若为横向柱状图则x,y轴调换
					x: 0,
					x2: 0,
					y: 0,
					y2: 1, */
      colorStops: [
        {
          offset: 0,
          color: colorArr[0],
        },
        {
          offset: 0.5,
          color: colorArr[1],
        },
        {
          offset: 1,
          color: colorArr[2],
        },
        {
          offset: 0.5,
          color: colorArr[3],
        },
      ],
    }
    return color
  }
  // 设置每一项的叠加总数,方便serve的顶部渲染
  const upStack = ref({
    upStackArr1: [],
    upStackArr2: [],
    upStackArr3: [],
    upStackArr4: [],
    upStackArr5: [],
  })
  const serveStack = () => {
    const stackLength = props.dataJson.length
    for (let i = 0; i < stackLength; i++) {
      const currentStackArr = props.dataJson[i]
      let sumArr = []
      if (i === 0) {
        sumArr = currentStackArr
      } else {
        const prevStackArr = upStack.value[`upStackArr${i}`]
        sumArr = currentStackArr.map((value, index) => value + prevStackArr[index])
      }
      upStack.value[`upStackArr${i + 1}`] = sumArr
    }
  }
  const series = ref([])
  const bf_series = ref([])
  const actionSeries = () => {
    series.value = []
    props.yAxisJson?.forEach((el, index) => {
      if (el == '整改率' || el == '评价率') {
        series.value.push(
          // 线体
          {
            type: 'line',
            name: el,
            data: props.dataJson[index], //Y轴上的高度
            smooth: true,
            yAxisIndex: 1,
            tooltip: {
              valueFormatter: function (value) {
                return value + ' %'
              },
            },
          }
        )
      } else {
        //   stack: 'total', 堆叠字段
        series.value.push(
          // 柱体
          {
            z: 1,
            type: 'bar',
            name: el,
            barGap: '15%', //相邻柱子间距
            itemStyle: {
              borderRadius: [0, 0, 0, 0], //柱子四周圆角
              color: setColor(props.barColorArray[`colorArr${index + 1}`]), //柱子左右颜色(深,浅)
            },
            data: props.dataJson[index], //Y轴上的高度
            // 做了滚动暂时不用了
            // barWidth: props.dataJson[0]?.length > props.dataListLength ? props.barWidth : '40',
            barWidth: '40',
            // barWidth:props.barWidth,
            stack: 'total',
          },
          //   每一个柱体的顶部
          {
            z: 3,
            name: el,
            type: 'pictorialBar',
            symbolPosition: 'end',
            data: upStack.value[`upStackArr${index + 1}`], //此数据对应顶部组件
            symbol: 'diamond',
            symbolOffset: ['0%', '-50%'],
            // 做了滚动暂时不用了
            // symbolSize: computedBarWidth(),
            symbolSize: [40 - 4, (10 * (40 - 10)) / 40],
            itemStyle: {
              normal: {
                borderColor: props.upColorArray[`colorArr${index + 1}`][2],
                borderWidth: 1, //加上棱角分明
                color: setColor(props.upColorArray[`colorArr${index + 1}`]), //柱子左右颜色(深,浅)
              },
            },
            tooltip: {
              show: false,
            },
            stack: 'total',
          }
        )
      }
    })
    // 底部 暂时不需要
    if (props.openBottomColor) {
      series.value.push({
        z: 2,
        name: '柱子1',
        type: 'pictorialBar',
        data: [0, 0, 0, 0, 0], //此数据对应底部组件
        symbol: 'diamond', //底部组件形状,不写默认为椭圆
        symbolOffset: ['0%', '50%'], //与柱子的偏移角度
        symbolSize: [25, 10], //底面[宽,高]
        itemStyle: {
          normal: {
            color: setColor(props.bottomColorArray), //底面左右颜色(深,浅)
          },
        },
        tooltip: {
          show: false,
        },
        stack: 'total',
      })
    }
    bf_series.value = series.value
  }
  var option = {
    dataZoom: [
      {
        show: false, // 是否显示滑动条
        xAxisIndex: 0, // 表示从X轴的零刻度线开始的
        startValue: 0, // 数据窗口范围的起始数值
        endValue: props.dataListLength - 1, // 数据窗口范围的结束数值(一次性展示几个)
      },
    ],
    tooltip: {
      trigger: 'axis',
      axisPointer: {
        type: 'shadow',
      },
      formatter: function (params) {
        var tooltipContent = '' // 初始化 tooltip 内容字符串
        let all = 0
        params.forEach((item) => {
          if (props.zglType == 'zgl') {
            if (item.seriesName == '未治理数' || item.seriesName == '已治理数') all += item.value
          } else if (props.zglType == 'pjl') {
            if (
              item.seriesName == '未评价数' ||
              item.seriesName == '已评价数' ||
              item.seriesName == '已关闭数'
            )
              all += item.value
          } else all += item.value
        })
        tooltipContent += `<div class='tooltipContent'>${
          props.zglType == 'zgl' ? '汇总数' : props.zglType == 'pjl' ? '评价总数' : '总数'
        }:<span style='color:#FF3A3A'>${all}</span></div>`
        params.forEach((item) => {
          props.formatterParams.forEach((el) => {
            if (item.seriesName == el.name) {
              tooltipContent += `<div class='tooltipContent'>${item.seriesName}:<span style='color:${
                el.color
              }'>${item.value + ''}${el.unit ? el.unit : ''}</span></div>`
            }
          })
        })
        return "<div class='tooltipBox'>" + tooltipContent + '</div>' // 返回自定义的 tooltip 内容
      },
      // 自定义 tooltip 的样式
      extraCssText:
        'width: 120px;height: 140px;padding:0;margin:0 ;background: #0A1A34;border: 1px solid #59AFF9B4;box-shadow: 0px 1px 12px 0px #03FBFFA6 inset;',
    },
    legend: {
      show: true,
      data: props.yAxisJson,
      selectedMode: true,
      textStyle: {
        color: '#fff',
        fontSize: '14',
      },
      top: props.legendDirection.top,
      bottom: props.legendDirection.bottom,
      left: props.legendDirection.left,
      right: props.legendDirection.right,
    },
    grid: {
      right: props.gridDirection.right,
      top: props.gridDirection.top,
      bottom: props.gridDirection.bottom,
      left: props.gridDirection.left,
    },
    xAxis: {
      type: 'category',
      axisLabel: {
        color: '#FFFFFF',
      },
      axisLine: {
        show: true,
        lineStyle: {
          color: '#1B3F66',
        },
      },
      axisTick: {
        show: false,
      },
      data: props.xAxisJson,
    },
    yAxis: [],
    series: [],
  }
  // legend 监听处理的事
  const legendselectAction = (selected_false_name) => {
    let indexArray = []
    let bf_upStack = JSON.parse(JSON.stringify(upStack.value))
    //   找到名字下标
    selected_false_name.forEach((el) => {
      indexArray.push(props.yAxisJson.indexOf(el))
    })
    //   找到下标一致的数据,对其他的数据进行相减得到新的up数据
    indexArray.forEach((el) => {
      // 找到对应需要减的下标
      for (let index = el + 1; index < 5; index++) {
        const currentStackArr = props.dataJson[el]
        // bf_upStack[`upStackArr${index}`] = [];
        var sy = []
        // 遍历数组,对应下标进行相减
        for (var i = 0; i < bf_upStack[`upStackArr${index + 1}`].length; i++) {
          sy.push(bf_upStack[`upStackArr${index + 1}`][i] - currentStackArr[i])
        }
        bf_upStack[`upStackArr${index + 1}`] = sy
      }
    })
    //   重新赋值server
    props.yAxisJson.forEach((el, index) => {
      series.value.forEach((item) => {
        if (el == item.name && item.type != 'bar' && item.type != 'line') {
          item.data = bf_upStack[`upStackArr${index + 1}`]
        }
      })
    })
  }
  // y轴的维度有几个
  let yAxis = {
    y1: {
      name: props.yAxisJsonName,
      nameTextStyle: props.nameTextStyle,
      type: 'value',
      max: props.yAxisJson.max,
      position: 'left',
      // 字体样式
      axisLabel: {
        formatter: '{value}',
        color: '#fff', //更改坐标轴文字颜色
        fontSize: 12, //更改坐标轴文字大小
      },
      axisLine: {
        show: true,
        lineStyle: {
          color: '#1B3F66',
        },
      },
      splitLine: {
        show: true,
        lineStyle: {
          type: 'dashed', // 将轴线设置为虚线
          color: '#35414C',
        },
      },
    },
    y2: {
      type: 'value',
      name: '单位:比例',
      max: 100,
      position: 'right',
      // 字体样式
      axisLabel: {
        formatter: '{value}',
        color: '#fff', //更改坐标轴文字颜色
        fontSize: 12, //更改坐标轴文字大小
      },
      axisLine: {
        show: true,
        lineStyle: {
          color: '#1B3F66',
        },
      },
      splitLine: {
        show: true,
        lineStyle: {
          type: 'dashed', // 将轴线设置为虚线
          color: '#35414C',
        },
      },
      nameTextStyle: props.nameTextStyleTwo,
    },
  }
  let timer = '' // 定时器
  const stopAnimation = () => {
    clearInterval(timer)
    timer = null
  }
  // 通过props.dataJson[0] 进行长度判断是否滚动
  const startAnimation = () => {
    timer = setInterval(() => {
      // 每次向后滚动一个,最后一个从头开始
      if (option.dataZoom[0].endValue === props.dataJson[0]?.length - 1) {
        option.dataZoom[0].startValue = 0 // 数据窗口范围的起始数值
        option.dataZoom[0].endValue = props.dataListLength - 1 // 数据窗口范围的结束数值
      } else {
        option.dataZoom[0].startValue = option.dataZoom[0].startValue + 1 // 数据窗口范围的起始数值
        option.dataZoom[0].endValue = option.dataZoom[0].endValue + 1 // 数据窗口范围的结束数值
      }
      chart.setOption(option)
    }, 3000)
  }
  const initData = () => {
    // 设置每一项的叠加总数,
    serveStack()
    // 对series进行处理
    actionSeries()
    option.xAxis.data = []
    option.legend.data = []
    option.xAxis.data = props.xAxisJson
    option.legend.data = props.yAxisJson
    option.series = series.value
    option.yAxis = []
    option.yAxis.push(yAxis.y1)
    // 如果有整改率和评价率的就要一个y2比例维度
    if (props.zglType == 'zgl' || props.zglType == 'pjl') {
      option.yAxis.push(yAxis.y2)
    }
    chart.setOption(option, true)
    // 统计维度x轴为公司和数据长度大于设置的长度的才开启滚动
    if (props.dataJson[0]?.length > props.dataListLength - 1) {
      startAnimation()
    }
  }
  let chart = ''
  onMounted(() => {
    const chartDom = document.getElementById(`${props.echartsId}`)
    chart = echarts.init(chartDom)
    initData()
    // 监听 legend 的点击事件
    chart.on('legendselectchanged', function (params) {
      let selected_false_name = []
      for (const key in params.selected) {
        const element = params.selected[key]
        // 找到为false的name
        if (!element) {
          selected_false_name.push(key)
        }
      }
      legendselectAction(selected_false_name)
      option.series = series.value
      chart.setOption(option, false)
    })
    chart.on('click', function (params) {
      emit('openDialog', params)
    })
  })
  onUnmounted(() => {
    stopAnimation()
  })
  watch(
    () => props.echartsBool,
    (newValue, oldValue) => {
      // 清除之前的视图
      chart.clear()
      stopAnimation()
      initData()
    },
    { deep: true }
  )
  const computedBarWidth = () => {
    // 如果x轴长度小于4都是固定宽度,大于4后才会动态宽度
    if (props.zglType && props.dataJson[0]?.length > props.dataListLength) {
      return ['50%', '8%']
    } else if (props.zglType) {
      return [40 - 4, (10 * (40 - 10)) / 40]
    } else {
      return [props.barWidth - 4, (10 * (props.barWidth - 10)) / props.barWidth]
    }
  }
</script>
<style>
  .tooltipContent {
    color: #99afcc;
    text-align: left;
    font-size: 14px;
    span {
      font-size: 14px;
      margin-left: 10px;
    }
  }
  .tooltipBox {
    width: 155px;
    height: 178px;
    background: url('~@/assets/digitalSafetySupervisionBigData/echartsColumnarbody_tooltip_bg.png')
      center/155px 178px;
    margin: -10px;
    padding: 20px;
  }
</style>

展示效果:目前是上面代码根据数量的多少进行一个重复渲染,因为这种方案不会让柱体宽度缩小影响观看效果,当然,上面代码也有跟随数量的多少进行宽度的变化,注释没删。
问题点:当某项数据为0的时候,顶部颜色不匹配,希望大佬看见能帮忙解决

  • 8
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值