封装渐变堆叠柱状图组件附完整代码
组件功能
这是一个渐变堆叠柱状图组件,主要功能包括:
- 在一根柱子上同时显示高、中、低三种危险级别数据
- 使用渐变色区分不同危险级别(高危红色、中危橙色、低危蓝色)
- 悬停显示详细数据信息(包括总量和各级别数据)
- 自适应容器大小变化
使用方法在父组件中引入组件并传入数据
<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 });
自定义调整
-
修改显示顺序:调整series数组中三个对象的顺序即可改变柱状图中高中低危的堆叠顺序
-
调整颜色:修改各系列的LinearGradient配置可以改变渐变色效果
-
调整圆角:目前顶部系列设置了
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>