设备状态图表-甘特图

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"
            }
        ]
相关推荐
B.-1 小时前
Remix 学习 - @remix-run/react 中主要的 hooks
前端·javascript·学习·react.js·web
2302_802836162 小时前
JS考核答案
javascript
罗_三金2 小时前
微信小程序读写NFC标签(实现NFC标签快速拉起小程序)实战
前端·javascript·微信小程序·小程序
双普拉斯2 小时前
微信小程序实现转盘抽奖,可以自定义编辑奖项列表
javascript·微信小程序·小程序
徐同保2 小时前
el-table的树形结构结合多选框使用,实现单选父子联动,全选,反选功能
javascript
J总裁的小芒果2 小时前
vue3-print打印eletable某一行的数据
javascript·vue.js·elementui
大大。3 小时前
el-input 只能输入数字和一个小数点,或者只能输入正整数
前端·javascript·vue.js
盼盼盼3 小时前
如何避免在使用 Context API 时出现状态管理的常见问题?
前端·javascript·react.js
超雄代码狂4 小时前
JavaScript web API完结篇---多案例
开发语言·前端·javascript
陈逸子风4 小时前
.net core8 使用JWT鉴权(附当前源码)
vue3·webapi·权限·流程