组件功能
这是一个渐变堆叠柱状图组件,主要功能包括:
- 在一根柱子上同时显示高、中、低三种危险级别数据
- 使用渐变色区分不同危险级别(高危红色、中危橙色、低危蓝色)
- 悬停显示详细数据信息(包括总量和各级别数据)
- 自适应容器大小变化
使用方法在父组件中引入组件并传入数据
<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>