封装渐变堆叠柱状图组件附完整代码

发布于:2025-05-28 ⋅ 阅读:(23) ⋅ 点赞:(0)

在这里插入图片描述

组件功能

这是一个渐变堆叠柱状图组件,主要功能包括:

  1. 在一根柱子上同时显示高、中、低三种危险级别数据
  2. 使用渐变色区分不同危险级别(高危红色、中危橙色、低危蓝色)
  3. 悬停显示详细数据信息(包括总量和各级别数据)
  4. 自适应容器大小变化

使用方法在父组件中引入组件并传入数据

<template>
  <JianbianZhu :warningData="warningData" :warningSevenItem="warningSevenItem" />
</template>

<script>
export default {
  data() {
    return {
      warningData: {
        high: [30, 40, 35, 50, 60, 40, 80],    // 高危数据
        middle: [70, 60, 65, 60, 60, 80, 70],  // 中危数据
        low: [50, 70, 80, 70, 60, 70, 60]      // 低危数据
      },
      warningSevenItem: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] // X轴标签
    }
  }
}
</script>

核心代码实现

1. 堆叠柱状图配置

// 核心实现:创建堆叠柱状图,三个系列分别代表低、中、高危
series: [
  {
    name: '低危',
    type: 'bar',
    stack: '总量',  // 设置堆叠,关键属性
    data: lowData,
    barWidth: '20%',
    itemStyle: {
      color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
        { offset: 0, color: '#70B2F7' },  // 顶部颜色
        { offset: 0.5, color: '#52A2FF' }, // 中间颜色
        { offset: 1, color: '#1970C2' }   // 底部颜色
      ])
    }
  },
  {
    name: '中危',
    type: 'bar',
    stack: '总量',
    data: middleData,
    // 中危渐变色配置...
  },
  {
    name: '高危',
    type: 'bar',
    stack: '总量',
    data: highData,
    // 高危渐变色配置...
  }
]

2. 渐变色实现

// 通过LinearGradient创建渐变效果
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  { offset: 0, color: '#FF2E2E' },  // 顶部颜色(更鲜艳)
  { offset: 0.5, color: '#FF5252' }, // 中间颜色
  { offset: 1, color: '#FF8A8A' }   // 底部颜色(过渡到中危)
]),

3. 数据提示框配置

tooltip: {
  trigger: 'axis',
  formatter: function(params) {
    const index = params[0].dataIndex;
    const date = xAxisData[index];
    const total = totalData[index] || 0;
    
    let result = `${date}<br/>总数: ${total}<br/>`;
    
    // 添加各危险级别数据
    params.forEach((param) => {
      let value = param.value || 0;
      result += `${param.seriesName}: ${value}<br/>`;
    });
    
    return result;
  }
}

4. 数据变化更新

// 监听数据变化,只在真正变化时更新图表
watch(() => [props.warningData, props.warningSevenItem], ([newWarningData, newWarningSevenItem]) => {
  const newWarningDataStr = JSON.stringify(newWarningData);
  const newWarningSevenItemStr = JSON.stringify(newWarningSevenItem);
  
  // 检查数据是否有变化
  const dataChanged = newWarningDataStr !== prevWarningData.value || 
                      newWarningSevenItemStr !== prevWarningSevenItem.value;
  
  if (dataChanged) {
    if (chartInstance) {
      updateChart();
    } else {
      initChart();
    }
  }
}, { deep: true });

自定义调整

  1. 修改显示顺序:调整series数组中三个对象的顺序即可改变柱状图中高中低危的堆叠顺序

  2. 调整颜色:修改各系列的LinearGradient配置可以改变渐变色效果

  3. 调整圆角:目前顶部系列设置了borderRadius: [2, 2, 0, 0]实现顶部圆角效果

完整组件代码:

<template>
  <div ref="chartContainer" class="chart-container">
    <div ref="chart" class="chart"></div>
  </div>
</template>

<script lang="ts">
import { defineComponent, onMounted, ref, watch, onUnmounted } from 'vue';
import * as echarts from 'echarts';

export default defineComponent({
  name: 'JianbianZhu',
  props: {
    warningData: {
      type: Object,
      required: true
    },
    warningSevenItem: {
      type: Array,
      required: true
    }
  },
  setup(props) {
    const chartRef = ref<HTMLElement | null>(null);
    let chartInstance: echarts.ECharts | null = null;
    
    // 保存上一次的数据快照,用于比较数据是否变化
    const prevWarningData = ref<string>('');
    const prevWarningSevenItem = ref<string>('');

    // 处理窗口大小变化
    const handleResize = () => {
      if (chartInstance) {
        chartInstance.resize();
      }
    };

    const initChart = () => {
      if (!chartRef.value) return;
      
      // 创建图表实例
      chartInstance = echarts.init(chartRef.value);
      
      // 准备数据
      const xAxisData = props.warningSevenItem;
      const highData = props.warningData.high || [];
      const middleData = props.warningData.middle || [];
      const lowData = props.warningData.low || [];
      
      // 计算总数据用于展示
      const totalData = highData.map((val: number, index: number) => {
        return val + (middleData[index] || 0) + (lowData[index] || 0);
      });
      
      // 更新数据快照
      prevWarningData.value = JSON.stringify(props.warningData);
      prevWarningSevenItem.value = JSON.stringify(props.warningSevenItem);
      
      // 配置图表选项
      const option = {
        tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'shadow'
          },
          formatter: function(params: any) {
            const index = params[0].dataIndex;
            const date = xAxisData[index];
            const total = totalData[index] || 0;
            
            let result = `${date}<br/>总数: ${total}<br/>`;
            
            // 按顺序添加高中低危数据
            params.forEach((param: any) => {
              let value = param.value || 0;
              result += `${param.seriesName}: ${value}<br/>`;
            });
            
            return result;
          }
        },
        legend: {
          data: ['低危', '中危', '高危'],
          textStyle: {
            color: 'rgba(255, 255, 255, 0.65)'
          },
          right: '5%',
          top: '0%'
        },
        grid: {
          left: '5%',
          right: '5%',
          bottom: '10%',
          top: '15%',
          containLabel: true
        },
        xAxis: {
          type: 'category',
          data: xAxisData,
          axisLine: {
            lineStyle: {
              color: 'rgba(255, 255, 255, 0.2)'
            }
          },
          axisLabel: {
            color: 'rgba(255, 255, 255, 0.65)',
            fontSize: 12,
            interval: 0,
            rotate: 0
          },
          axisTick: {
            show: false
          }
        },
        yAxis: {
          type: 'value',
          name: '',
          nameTextStyle: {
            color: 'rgba(255, 255, 255, 0.65)'
          },
          min: 0,
          axisLine: {
            show: false
          },
          axisTick: {
            show: false
          },
          splitLine: {
            lineStyle: {
              color: 'rgba(255, 255, 255, 0.1)',
              type: 'dashed',
              width: 0.5
            }
          },
          axisLabel: {
            color: 'rgba(255, 255, 255, 0.65)',
            fontSize: 12,
            formatter: function(value: number) {
              if (value >= 1000) {
                return Math.floor(value / 1000) + 'k';
              }
              return value;
            }
          }
        },
        series: [
        {
            name: '低危',
            type: 'bar',
            stack: '总量',
            data: lowData,
            barWidth: '20%',
            itemStyle: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: '#70B2F7' },  // 顶部颜色(与中危底部接近)
                { offset: 0.5, color: '#52A2FF' }, // 中间颜色
                { offset: 1, color: '#1970C2' }   // 底部颜色(更深)
              ])
            },
            emphasis: {
              itemStyle: {
                opacity: 0.9
              }
            },
            z: 10
          },
        
          {
            name: '中危',
            type: 'bar',
            stack: '总量',
            data: middleData,
            barWidth: '20%',
            itemStyle: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: '#FFA066' },  // 顶部颜色(与高危底部接近)
                { offset: 0.5, color: '#FFA647' }, // 中间颜色
                { offset: 1, color: '#FFD0A1' }   // 底部颜色(过渡到低危)
              ])
            },
            emphasis: {
              itemStyle: {
                opacity: 0.9
              }
            },
            z: 10
          },
          {
            name: '高危',
            type: 'bar',
            stack: '总量',
            data: highData,
            barWidth: '20%',
            itemStyle: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: '#FF2E2E' },  // 顶部颜色(更鲜艳)
                { offset: 0.5, color: '#FF5252' }, // 中间颜色
                { offset: 1, color: '#FF8A8A' }   // 底部颜色(过渡到中危)
              ]),
              borderRadius: [2, 2, 0, 0] // 只有最上面的柱子需要圆角
            },
            emphasis: {
              itemStyle: {
                opacity: 0.9
              }
            },
            z: 10
          },
        ],
        backgroundColor: 'transparent'
      };
      
      // 设置图表选项
      chartInstance.setOption(option);
      
      // 监听窗口大小变化自动调整图表大小
      window.addEventListener('resize', handleResize);
    };
    
    // 更新图表,不销毁实例
    const updateChart = () => {
      if (!chartInstance || !chartRef.value) return;
      
      // 准备数据
      const xAxisData = props.warningSevenItem;
      const highData = props.warningData.high || [];
      const middleData = props.warningData.middle || [];
      const lowData = props.warningData.low || [];
      
      // 计算总数据用于展示
      const totalData = highData.map((val: number, index: number) => {
        return val + (middleData[index] || 0) + (lowData[index] || 0);
      });
      
      // 更新数据快照
      prevWarningData.value = JSON.stringify(props.warningData);
      prevWarningSevenItem.value = JSON.stringify(props.warningSevenItem);
      
      // 解决方案:创建完整的配置,使用true参数强制重置所有配置
      // 这将确保tooltip格式化器使用最新数据
      chartInstance.clear();  // 清除当前图表
      
      // 完整重新配置图表
      const option = {
        tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'shadow'
          },
          formatter: function(params: any) {
            const index = params[0].dataIndex;
            const date = xAxisData[index];
            const total = totalData[index] || 0;
            
            let result = `${date}<br/>总数: ${total}<br/>`;
            
            // 按顺序添加高中低危数据
            params.forEach((param: any) => {
              let value = param.value || 0;
              result += `${param.seriesName}: ${value}<br/>`;
            });
            
            return result;
          }
        },
        legend: {
          data: ['高危', '中危', '低危'],
          textStyle: {
            color: 'rgba(255, 255, 255, 0.65)'
          },
          right: '5%',
          top: '0%'
        },
        grid: {
          left: '5%',
          right: '5%',
          bottom: '10%',
          top: '15%',
          containLabel: true
        },
        xAxis: {
          type: 'category',
          data: xAxisData,
          axisLine: {
            lineStyle: {
              color: 'rgba(255, 255, 255, 0.2)'
            }
          },
          axisLabel: {
            color: 'rgba(255, 255, 255, 0.65)',
            fontSize: 12,
            interval: 0,
            rotate: 0
          },
          axisTick: {
            show: false
          }
        },
        yAxis: {
          type: 'value',
          name: '',
          nameTextStyle: {
            color: 'rgba(255, 255, 255, 0.65)'
          },
          min: 0,
          axisLine: {
            show: false
          },
          axisTick: {
            show: false
          },
          splitLine: {
            lineStyle: {
              color: 'rgba(255, 255, 255, 0.1)',
              type: 'dashed',
              width: 0.5
            }
          },
          axisLabel: {
            color: 'rgba(255, 255, 255, 0.65)',
            fontSize: 12,
            formatter: function(value: number) {
              if (value >= 1000) {
                return Math.floor(value / 1000) + 'k';
              }
              return value;
            }
          }
        },
        series: [
          {
            name: '高危',
            type: 'bar',
            stack: '总量',
            data: highData,
            barWidth: '20%',
            itemStyle: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: '#FF2E2E' },  // 顶部颜色(更鲜艳)
                { offset: 0.5, color: '#FF5252' }, // 中间颜色
                { offset: 1, color: '#FF8A8A' }   // 底部颜色(过渡到中危)
              ]),
              borderRadius: [2, 2, 0, 0] // 只有最上面的柱子需要圆角
            },
            emphasis: {
              itemStyle: {
                opacity: 0.9
              }
            },
            z: 10
          },
          {
            name: '中危',
            type: 'bar',
            stack: '总量',
            data: middleData,
            barWidth: '20%',
            itemStyle: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: '#FFA066' },  // 顶部颜色(与高危底部接近)
                { offset: 0.5, color: '#FFA647' }, // 中间颜色
                { offset: 1, color: '#FFD0A1' }   // 底部颜色(过渡到低危)
              ])
            },
            emphasis: {
              itemStyle: {
                opacity: 0.9
              }
            },
            z: 10
          },
          {
            name: '低危',
            type: 'bar',
            stack: '总量',
            data: lowData,
            barWidth: '20%',
            itemStyle: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: '#70B2F7' },  // 顶部颜色(与中危底部接近)
                { offset: 0.5, color: '#52A2FF' }, // 中间颜色
                { offset: 1, color: '#1970C2' }   // 底部颜色(更深)
              ])
            },
            emphasis: {
              itemStyle: {
                opacity: 0.9
              }
            },
            z: 10
          }
        ],
        backgroundColor: 'transparent'
      };
      
      // 使用完整配置重新初始化图表
      chartInstance.setOption(option);
    };
    
    // 在组件挂载后初始化图表
    onMounted(() => {
      initChart();
    });
    
    // 监听数据变化,仅在数据真正变化时更新图表
    watch(() => [props.warningData, props.warningSevenItem], ([newWarningData, newWarningSevenItem]) => {
      const newWarningDataStr = JSON.stringify(newWarningData);
      const newWarningSevenItemStr = JSON.stringify(newWarningSevenItem);
      
      // 检查数据是否有变化
      const dataChanged = newWarningDataStr !== prevWarningData.value || 
                          newWarningSevenItemStr !== prevWarningSevenItem.value;
      
      if (dataChanged) {
        // 如果数据有变化,更新图表
        if (chartInstance) {
          updateChart();
        } else {
          initChart();
        }
      }
    }, { deep: true });
    
    // 组件卸载时清理资源
    onUnmounted(() => {
      if (chartInstance) {
        // 移除resize事件监听
        window.removeEventListener('resize', handleResize);
        chartInstance.dispose();
        chartInstance = null;
      }
    });
    
    return {
      chart: chartRef
    };
  }
});
</script>

<style scoped>
.chart-container {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.chart {
  width: 100%;
  height: 100%;
  min-height: 280px;
}
</style>