【以横向条形图的形式展示不同停机坪(Y轴)在一天24小时(X轴,0-24点)内的状态变化】

低功耗蓝牙项目,需要一块懂省电的板

思澈 SF32LB52 芯片,BLE 协议栈深度优化,上手即开发

#要实现效果

  1. 以横向条形图的形式展示不同停机坪(Y轴)在一天24小时(X轴,0-24点)内的状态变化。
    能够清晰看到每个停机坪在哪些时间段是被占用的,哪些是空闲的。
    支持多任务段与空隙处理:

  2. 通过 convertData函数动态生成系列数据,支持一个停机坪在同一天内有多个不连续的任务段(例如:上午作业,中午空闲,下午维护)。
    利用“透明占位系列”(gap-i)和“任务系列”(task−{i})和“任务系列”(task-i)和任务系列task{i})堆叠的方式,正确渲染任务之间的时间间隔(空隙),确保时间轴的准确性。
    状态颜色区分:

  3. 根据任务状态(如“正在占用”、“已预约”、“空闲”、“维护”)自动映射不同的颜色(红、橙、绿、灰),直观反映停机坪的业务状态。
    内部标签显示:

  4. 在条形图内部直接显示任务状态文本(如“正在占用 (作业)”),方便用户快速识别,无需鼠标悬停。

  5. 设置了文本溢出截断(overflow: ‘truncate’),防止文字过长影响布局。
    自定义 Tooltip(提示框):

当鼠标悬停在图表上时,显示该停机坪当天所有任务段的详细列表,包括状态名称和具体的起止时间(例如:0:00 - 16:00)。

代码

<template>
  <div id="gantt-chart" ref="ganttChartRef"></div>
</template>



import * as echarts from 'echarts'

const ganttChartRef = ref<HTMLElement | null>(null);
let ganttChart: echarts.ECharts | null = null;

// --- 甘特图逻辑 ---

const initGanttChart = () => {
  nextTick(() => {
    if (!ganttChartRef.value) return;

    // 1. 模拟数据
    const rawTimeData = [
      { name: '01号停机坪', tasks: [{ status: '已预约 (巡检)', start: 0, end: 16 }, { status: '正在占用 (作业)', start: 16, end: 22 }, { status: '维护 (离线)', start: 22, end: 24 }] },
      { name: '02号停机坪', tasks: [{ status: '正在占用 (作业)', start: 0, end: 12 }, { status: '维护 (离线)', start: 12, end: 24 }] },
      { name: '03号停机坪', tasks: [{ status: '正在占用 (作业)', start: 0, end: 24 }] },
      { name: '04号停机坪', tasks: [{ status: '已预约 (物流)', start: 0, end: 14 }, { status: '已预约 (物流)', start: 14, end: 24 }] },
      { name: '05号停机坪', tasks: [{ status: '空闲可用', start: 0, end: 14 }, { status: '已预约 (巡检)', start: 14, end: 24 }] },
      { name: '06号停机坪', tasks: [{ status: '正在占用 (作业)', start: 0, end: 24 }] }
    ];

    const colorMap: Record<string, string> = {
      '正在占用 (作业)': '#c23531',
      '空闲可用': '#61a0a8',
      '已预约 (物流)': '#d48806',
      '已预约 (巡检)': '#d48806',
      '维护 (离线)': '#999999'
    };

    const convertData = (data: any[]) => {
      const categories = data.map(item => item.name);
      const seriesList: any[] = [];
      const maxTasks = Math.max(...data.map(item => item.tasks.length));

      for (let i = 0; i < maxTasks; i++) {
        // 占位系列
        seriesList.push({
          name: `gap-${i}`,
          type: 'bar',
          stack: 'total',
          silent: true,
          itemStyle: { color: 'transparent' },
          data: data.map(item => {
            const task = item.tasks[i];
            if (!task) return 0;
            if (i === 0) return task.start;
            const prevTask = item.tasks[i - 1];
            if (!prevTask) return task.start;
            const gap = task.start - prevTask.end;
            return gap > 0 ? gap : 0;
          })
        });

        // 任务系列
        seriesList.push({
          name: `task-${i}`,
          type: 'bar',
          stack: 'total',
          label: {
            show: true,
            position: 'inside',
            formatter: (params: any) => {
              const task = data[params.dataIndex].tasks[i];
              return task ? task.status : '';
            },
            color: '#fff',
            fontSize: 10,
            overflow: 'truncate',
            width: 60
          },
          itemStyle: {
            color: (params: any) => {
              const task = data[params.dataIndex].tasks[i];
              return task ? (colorMap[task.status] || '#ccc') : 'transparent';
            }
          },
          data: data.map(item => {
            const task = item.tasks[i];
            if (!task) return 0;
            return task.end - task.start;
          })
        });
      }
      return { categories, seriesList };
    };

    const result = convertData(rawTimeData);

    const option = {
      backgroundColor: 'transparent',
      tooltip: {
        trigger: 'axis',
        axisPointer: { type: 'shadow' },
        formatter: (params: any) => {
          if (!params || params.length === 0) return '';
          const dataIndex = params[0].dataIndex;
          const item = rawTimeData[dataIndex];
          let res = `<div style="font-weight:bold; margin-bottom:5px;">${item.name}</div>`;
          item.tasks.forEach((task: any) => {
            res += `<div style="display:flex; justify-content:space-between; gap:10px; font-size:12px;">
                      <span style="color:${colorMap[task.status] || '#fff'}">● ${task.status}</span>
                      <span>${task.start}:00 - ${task.end}:00</span>
                    </div>`;
          });
          return res;
        }
      },
      grid: {
        left: '15%',
        right: '5%',
        top: '5%',
        bottom: '10%'
      },
      xAxis: {
        type: 'value',
        min: 0,
        max: 24,
        axisLabel: { formatter: '{value}:00', color: '#fff' },
        splitLine: { show: true, lineStyle: { type: 'dashed', color: 'rgba(255,255,255,0.1)' } }
      },
      yAxis: {
        type: 'category',
        data: result.categories,
        axisTick: { show: false },
        axisLine: { show: false },
        axisLabel: { color: '#fff' }
      },
      series: result.seriesList
    };

    if (!ganttChart) {
      ganttChart = echarts.init(ganttChartRef.value);
    }
    ganttChart.setOption(option);
    
    // 监听容器大小变化
    window.addEventListener('resize', () => {
      ganttChart?.resize();
    });
  });
};

onMounted(() => {
  initGanttChart();
});

实现效果

在这里插入图片描述

低功耗蓝牙项目,需要一块懂省电的板

思澈 SF32LB52 芯片,BLE 协议栈深度优化,上手即开发

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值