设备状态图表-甘特图

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"
            }
        ]
相关推荐
学不会•12 分钟前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
EasyNTS1 小时前
H.264/H.265播放器EasyPlayer.js视频流媒体播放器关于websocket1006的异常断连
javascript·h.265·h.264
活宝小娜3 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点3 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow3 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o3 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
刚刚好ā4 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
yqcoder5 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
会发光的猪。6 小时前
css使用弹性盒,让每个子元素平均等分父元素的4/1大小
前端·javascript·vue.js
天下代码客6 小时前
【vue】vue中.sync修饰符如何使用--详细代码对比
前端·javascript·vue.js