1.背景:设备状态监控图表,监控不同状态的时间段,可以使用甘特图来展示效果
鼠标经过时的数据提示框
2、代码实现
<template>
<div
ref="ganttChartRefs"
:style="{ height: '6.2rem', width: '100%' }"
class="bg_screen"
></div>
</template>
<script lang="ts" setup>
import { onMounted, ref, watch, nextTick,onBeforeUnmount } from "vue";
import * as echarts from "echarts";
// import { GaugeChart } from "echarts/charts";
import { useDebounceFn, useResizeObserver } from "@vueuse/core";
// echarts.use([GaugeChart]); // 引入仪表盘 看版本 echart5以上不需要引入
const chartInstance = ref<any>(null);
const ganttChartRefs = ref<any>(null);
const myChart = ref<any>(null);
const props = defineProps({
chartData: {
type: Array,
default: () => [],
},
});
function setChartOptions(data:any) {
myChart.value = echarts.init(ganttChartRefs.value);
var types:any = [
{ value: "1", color: "#91cc75", label: '运行'},
{ value: "2", color: "#ccc", label: '停机'},
{ value: "3", color: "#fd666d", label: '故障'},
];
var datatemp = data.map((d:any) => ({ // 数据集处理
name: types.filter((a:any) => a.value == d.STATUSDESC)[0].label, // 状态名称
value: [
parseInt(d.GROUPID),
new Date(d.RUNTIME).getTime(), // 处理成时间辍的格式
new Date(d.END_TIME).getTime(),
],
itemStyle: {
normal: {
color: types.filter((a:any) => a.value == d.STATUSDESC)[0].color, // 状态颜色的绑定
},
},
}));
var groupedData = datatemp.reduce((acc:any, curr:any) => {
if (!acc[curr.value[0]]) {
acc[curr.value[0]] = [];
}
acc[curr.value[0]].push(curr);
return acc;
}, {}); // 状态值组合成一组 以便后面排序
var gaps:any = [];
const now = new Date();
// 更新起始时间为当前时间前24小时
const twentyFourHoursAgo = now.getTime() - 24 * 60 * 60 * 1000;
// 获取第一条数据的开始时间
const firstRecordTime = new Date(data[0]?.RUNTIME).getTime();
// 检查是否有空隙(即不足24小时时)
if (firstRecordTime > twentyFourHoursAgo) {
// 添加到 gaps 数组
gaps.unshift([parseInt(data[0]?.GROUPID), twentyFourHoursAgo, firstRecordTime]);
}
Object.keys(groupedData).forEach((groupID) => {
var groupData = groupedData[groupID];
// 按开始时间排序
groupData.sort((a:any, b:any) => a.value[1] - b.value[1]);
let lastEndTime = twentyFourHoursAgo; // 起始时间为当前时间前24小时
for (var i = 0; i < groupData.length; i++) {
var currentStartTime = groupData[i].value[1];
var currentEndTime = groupData[i].value[2];
if (currentStartTime > lastEndTime) {
gaps.push([parseInt(groupID), lastEndTime, currentStartTime]);
}
lastEndTime = Math.max(lastEndTime, currentEndTime);
}
// 检查最后一个时间段结束时间与24小时结束时间之间的空隙
if (lastEndTime < now.getTime()) {
gaps.push([parseInt(groupID), lastEndTime,now.getTime()]);
}
});
function renderItem(params:any, api: any) {
var categoryIndex = api.value(0);
var start = api.coord([api.value(1), categoryIndex]);
var end = api.coord([api.value(2), categoryIndex]);
var height = api.size([0, 1])[1] * 0.6;
var rectShape = echarts.graphic.clipRectByRect(
{
x: start[0],
y: start[1] - height / 2,
width: end[0] - start[0],
height: height,
},
{
x: params.coordSys.x,
y: params.coordSys.y,
width: params.coordSys.width,
height: params.coordSys.height,
}
);
var shapes;
shapes = rectShape && {
type: "rect",
transition: ["shape"],
shape: rectShape,
style: api.style(),
};
return shapes;
}
const series = [
{
type: "custom",
renderItem: renderItem,
encode: {
x: [1, 2],
y: 0,
},
data: datatemp,
},
{
type: "custom",
renderItem: renderItem,
z: -1, // 放在最底层
data: gaps,
},
];
const option = {
grid: {
left: "1%",
right: "1%",
top: "1%",
bottom: 2,
height: "28%",
width: "98%",
containLabel: true,
},
tooltip: {
show: true,
textStyle: {
fontSize: 10,
},
position: function (point:any, size:any) {
var mouseX = point[0];
var mouseY = point[1];
// 获取容器的宽度和 tooltip 的宽度
var containerWidth = size.viewSize&&size.viewSize[0];
var tooltipWidth = size.contentSize&&size.contentSize[0];
// 调整 tooltip 的位置
var offsetX = 10; // x 方向偏移量
var offsetY = 1; // y 方向偏移量
// 如果 tooltip 超出容器的右侧,将其显示在鼠标的左侧
if (mouseX + tooltipWidth + offsetX > containerWidth) {
// 新的位置坐标
var newX = mouseX - tooltipWidth - offsetX;
var newY = mouseY + offsetY;
// 返回新的位置坐标
return [newX, newY];
} else {
// tooltip 显示在鼠标的下方
var newX:number = mouseX + offsetX;
var newY:any = mouseY + offsetY;
// 返回新的位置坐标
return [newX, newY];
}
},
formatter: function (params:any) {
// 参数校验
if (!Array.isArray(params.value) || params.value.length < 2) {
return "";
}
try {
const startTime = params.value[1];
const endTime = params.value[2];
if (endTime < startTime) {
return "";
}
const duration = endTime - startTime;
const hours = Math.floor(duration / (1000 * 60 * 60));
const minutes = Math.floor(
(duration % (1000 * 60 * 60)) / (1000 * 60)
);
const seconds = Math.floor((duration % (1000 * 60)) / 1000);
// 获取带前导0的小时、分钟和秒
const formatTimeUnit = (unit:any) =>
unit < 10 ? "0" + unit : unit.toString();
const formattedStartTimeHours = formatTimeUnit(
new Date(startTime).getHours()
);
const formattedStartTimeMinutes = formatTimeUnit(
new Date(startTime).getMinutes()
);
const formattedStartTimeSeconds = formatTimeUnit(
new Date(startTime).getSeconds()
);
const formattedEndTimeHours = formatTimeUnit(
new Date(endTime).getHours()
);
const formattedEndTimeMinutes = formatTimeUnit(
new Date(endTime).getMinutes()
);
const formattedEndTimeSeconds = formatTimeUnit(
new Date(endTime).getSeconds()
);
const formattedTimeSeconds = formatTimeUnit(seconds);
let formattedDuration = `${hours}小时${minutes}分${formattedTimeSeconds}秒`;
// output 鼠标经过的时候展示的文本
let output = `${params.marker}${params.name}${
params.seriesIndex === 1
? `空闲时长:`
: `时长:`
}${formattedDuration}<br/>`;
output += `${params.marker}时间区间:${formattedStartTimeHours}:${formattedStartTimeMinutes}:${formattedStartTimeSeconds} - ${formattedEndTimeHours}:${formattedEndTimeMinutes}:${formattedEndTimeSeconds}`;
return output;
} catch (error) {
return ""; // 根据实际情况考虑是否返回错误信息或特定的格式化失败字符串
}
},
},
dataZoom: [
{
type: "inside",
filterMode: "none",
showDataShadow: false,
show: true,
},
],
xAxis: {
type: "time",
min: new Date().getTime() - 24 * 60 * 60 * 1000, // 24小时前
max: new Date().getTime(), // 当前时间
axisLabel: {
formatter: function (value:any) {
var date = new Date(value);
var hours = date.getHours();
var minutes = date.getMinutes();
var seconds = date.getSeconds();
// 如果分钟和秒都是0,则只显示小时(例如:00)
if (minutes === 0 && seconds === 0) {
return hours === 0
? "00:00"
: echarts.format.formatTime("hh:mm", value);
}
// 其他情况显示小时、分钟和秒(例如:01:30:45)
return echarts.format.formatTime("hh:mm:ss", value);
},
// 标签与轴线紧挨在一起
padding: [0, 0, 0, 0], // 标签的内边距(可根据实际情况调整)
// rotate: 30, // 旋转角度,避免标签重叠(可选)
},
splitNumber: 24,
splitLine: {
show: false,
},
axisLine: {
show: false, // 隐藏坐标轴线
},
axisTick: {
show: false, // 隐藏刻度线
},
},
yAxis: [
{
yAxisIndex: 0,
type: "category",
data: [""],
axisLine: {
show: false,
},
axisTick: {
show: false,
},
axisLabel: {
show: false,
},
},
],
series: series,
};
useResizeObserver(ganttChartRefs.value, resizeChart);
myChart.value.setOption(option);
}
// 窗口自适应并开启过渡动画
const resize = () => {
if (chartInstance.value) {
chartInstance.value.resize({ animation: { duration: 300 } });
}
};
// 重绘图表函数
const resizeChart = useDebounceFn(() => {
myChart.value?.resize();
}, 300);
const debouncedResize = useDebounceFn(resize, 500, { maxWait: 800 });
watch(
() => props.chartData,
() => {
nextTick(() => {
setChartOptions(props.chartData);
});
},
{
immediate: true,
}
);
onMounted(() => {
window.addEventListener("resize", debouncedResize);
});
onBeforeUnmount(() => {
myChart.value?.dispose();
myChart.value = null;
window.removeEventListener("resize", debouncedResize);
});
</script>
3.数据源:组件传过来 props.chartData
格式如下:
var data = [
{
"GROUPID": "0", // 组别 因为考虑到有多种设备情况 若只有一种 默认0就好
"END_TIME": "2024-08-25 18:35:31", // 结束时间
"RUNTIME": "2024-08-25 18:25:07", // 开始时间
"STATUSDESC": "1" // 状态值
},
{
"GROUPID": "0",
"END_TIME": "2024-08-26 00:25:49",
"RUNTIME": "2024-08-25 18:05:31",
"STATUSDESC": "2"
},
{
"GROUPID": "0",
"END_TIME": "2024-08-26 01:26:29",
"RUNTIME": "2024-08-26 00:25:49",
"STATUSDESC": "1"
},
{
"GROUPID": "0",
"END_TIME": "2024-08-26 04:31:38",
"RUNTIME": "2024-08-26 01:26:29",
"STATUSDESC": "1"
},
{
"GROUPID": "0",
"END_TIME": "2024-08-26 04:37:25",
"RUNTIME": "2024-08-26 11:31:38",
"STATUSDESC": "2"
},
{
"GROUPID": "0",
"END_TIME": "2024-08-26 06:52:45",
"RUNTIME": "2024-08-26 04:37:25",
"STATUSDESC": "3"
},
{
"GROUPID": "0",
"END_TIME": "2024-08-26 06:54:42",
"RUNTIME": "2024-08-26 06:52:45",
"STATUSDESC": "2"
},
{
"GROUPID": "0",
"END_TIME": "2024-08-26 08:01:53",
"RUNTIME": "2024-08-26 06:54:42",
"STATUSDESC": "1"
},
{
"GROUPID": "0",
"END_TIME": "2024-08-26 08:40:18",
"RUNTIME": "2024-08-26 08:01:53",
"STATUSDESC": "3"
},
{
"GROUPID": "0",
"END_TIME": "2024-08-26 08:53:59",
"RUNTIME": "2024-08-26 08:40:18",
"STATUSDESC": "1"
},
{
"GROUPID": "0",
"END_TIME": "2024-08-26 08:03:59",
"RUNTIME": "2024-08-26 08:53:59",
"STATUSDESC": "1"
},
{
"GROUPID": "0",
"END_TIME": "2024-08-26 09:29:19",
"RUNTIME": "2024-08-26 09:03:59",
"STATUSDESC": "1"
},
{
"GROUPID": "0",
"END_TIME": "2024-08-26 09:51:48",
"RUNTIME": "2024-08-26 09:29:19",
"STATUSDESC": "1"
},
{
"GROUPID": "0",
"END_TIME": "2024-08-26 12:01:42",
"RUNTIME": "2024-08-26 09:51:48",
"STATUSDESC": "1"
},
{
"GROUPID": "0",
"END_TIME": "2024-08-26 14:42:49",
"RUNTIME": "2024-08-26 12:01:42",
"STATUSDESC": "1"
}
]