You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apache ECharts垂直时间线图表内容被裁剪问题求助

Apache ECharts垂直时间线图表内容裁剪问题排查与解决

问题描述

我正在使用Apache ECharts创建垂直时间线图表,用于展示特定日期的事件,事件间距由时间差决定,并设置了最小、最大间距约束。但图表未充分利用可用高度,出现内容被裁剪的情况,即使dataZoom滑块处于末端位置,仍有部分图表内容不可见。

相关代码片段

let event_data = {
  '2024-03-22': [
    ['DEVICE_SLEEP', 'DEVICE_SLEEP', '07:51:35'],
    ['DEVICE_SLEEP', 'DEVICE_SLEEP', '07:59:44'],
    ['DEVICE_SLEEP', 'DEVICE_SLEEP', '08:37:49'],
    ['DEVICE_STREAM', 'DEVICE_STREAM', '12:09:29'],
    ['DEVICE_RESET', 'DEVICE_RECORD', '12:10:39'],
    ['DEVICE_RECORD', 'DEVICE_RECORD', '12:10:40'],
    ['DEVICE_RESET', 'DEVICE_START', '12:10:54'],
    ['DEVICE_RESET', 'DEVICE_SLEEP', '12:20:29'],
    ['DEVICE_STREAM', 'DEVICE_STREAM', '12:29:28'],
    ['DEVICE_SLEEP', 'DEVICE_SLEEP', '22:43:05']
  ]
};
const chartHeight = myChart.getHeight();
const chartWidth = myChart.getWidth();
const maxVisibleYAxisValue = chartHeight;

function timeToSeconds(time) {
  const [hours, minutes, seconds] = time.split(':').map(Number);
  return hours * 3600 + minutes * 60 + seconds;
}
//ProcessData function to dynamically assign Y-axis positions
function processData(dateKey) {
  const events = event_data[dateKey];
  let prevTimeInSeconds = 0;
  const minYGap = 2.6;
  const maxYGap = 15;
  let currentPosition = 1.4;
  let totalSpaceRequired = 0;

  const processedEvents = events.map(function (item, index) {
    const currentTimeInSeconds = timeToSeconds(item[2]);
    let gap;

    if (index === 0) {
      gap = 0;
    } else {
      const timeDiffInSeconds = currentTimeInSeconds - prevTimeInSeconds;
      gap = Math.max(
        minYGap,
        Math.min((timeDiffInSeconds / (6 * 3600)) * maxYGap, maxYGap)
      );
    }

    currentPosition += gap;
    prevTimeInSeconds = currentTimeInSeconds;
    return {
      value: [5, currentPosition]
    };
  });

  totalSpaceRequired = currentPosition;
  return { processedEvents, totalSpaceRequired };
}

var option = {
  baseOption: {
    backgroundColor: '#1F2327',
    darkMode: false,
    grid: {
      left: '3%',
      right: '8%',
      bottom: '15%',
      containLabel: false
    },
    xAxis: {
      type: 'category',
      data: ['0', '1', '2', '3', '5', '6', '7', '8', '9', '10', '11'],
      axisLabel: { show: false },
      axisLine: { show: false },
      axisTick: { show: false }
    },
    yAxis: {
      type: 'value',
      min: 0,
      max: 24,
      axisLabel: { show: false },
      axisLine: { show: false },
      axisTick: { show: false },
      inverse: true,
      splitLine: { show: false }
    },
    timeline: {
      axisType: 'category',
      data: Object.keys(event_data)
    },
    series: []
  },
  options: []
};

// Populate options for each date
Object.keys(event_data).forEach(function (dateKey) {
  const { processedEvents, totalSpaceRequired } = processData(dateKey);
  let endPercentage = 100;
  if (totalSpaceRequired > maxVisibleYAxisValue) {
    endPercentage = (maxVisibleYAxisValue / totalSpaceRequired) * 100;
  }

  option.options.push({
    dataZoom: [
      {
        type: 'slider',
        yAxisIndex: 0,
        start: 0,
        end: endPercentage,
        filterMode: 'none',
      }
    ],
    series: [
      {
        type: 'line',
        data: processedEvents,
        coordinateSystem: 'cartesian2d',
        symbol: 'circle',
        symbolSize: 20,
        zlevel: 1,
        step: 'middle',
        markLine: {
          silent: true,
          symbol: ['none', 'none'],
          lineStyle: {
            type: 'solid',
            color: '#FFFFFF'
          },
          data: processedEvents.flatMap((item, index) => [
            index % 2 != 0
              ? [
                  {
                    coord: ['2', item.value[1]]
                  },
                  {
                    coord: ['6', item.value[1]]
                  }
                ]
              : [
                  {
                    coord: ['6', item.value[1]]
                  },
                  {
                    coord: ['9', item.value[1]]
                  }
                ]
          ])
        }
      }
    ]
  });
});

被裁剪的图表效果

被裁剪的时间线图表

问题原因分析

  • Y轴最大值固定冲突:Y轴配置中max:24是固定值,但processData计算出的事件总高度(示例中约为46.4)远大于该值,超出部分直接被裁剪,dataZoom无法作用于Y轴范围外的内容。
  • 坐标概念混淆:maxVisibleYAxisValue直接使用图表像素高度,混淆了像素坐标和图表数据坐标的逻辑,两者没有直接比例关系。
  • dataZoom配置错误:filterMode: 'none'仅缩放坐标轴但不处理数据,即使调整滑块,超出Y轴范围的内容依然无法显示。

解决方案

1. 动态设置Y轴最大值

移除baseOption中Y轴的固定max配置,在每个日期的options中根据事件总高度动态设置Y轴范围:

// 修改baseOption的yAxis,删除max:24
yAxis: {
  type: 'value',
  min: 0,
  axisLabel: { show: false },
  axisLine: { show: false },
  axisTick: { show: false },
  inverse: true,
  splitLine: { show: false }
},

// 在循环生成options时添加yAxis配置
Object.keys(event_data).forEach(function (dateKey) {
  const { processedEvents, totalSpaceRequired } = processData(dateKey);

  option.options.push({
    yAxis: {
      max: totalSpaceRequired // 用计算出的总空间作为Y轴最大值
    },
    dataZoom: [
      {
        type: 'slider',
        yAxisIndex: 0,
        start: 0,
        end: 100, // 初始显示全部内容
        filterMode: 'filter', // 启用数据过滤,确保缩放时内容正常显示
      }
    ],
    series: [/* 原有series配置 */]
  });
});

2. 优化事件间距计算(可选)

若希望事件间距更贴合图表高度,可将时间差的比例基准改为全天秒数(86400秒),并设定合理的Y轴数值基准:

// 修改processData中的gap计算逻辑
const chartHeightInDataUnits = 100; // 设定Y轴数值范围基准
gap = Math.max(
  minYGap,
  Math.min((timeDiffInSeconds / 86400) * chartHeightInDataUnits, maxYGap)
);

3. 移除错误的坐标赋值

删除const maxVisibleYAxisValue = chartHeight;,避免像素坐标与数据坐标的概念混淆,改用totalSpaceRequired作为Y轴范围的参考依据。

验证效果

修改后,Y轴会根据每个日期的事件总高度自动调整范围,dataZoom滑块可完整控制内容显示,不会出现裁剪问题,图表能充分利用可用高度。

内容的提问来源于stack exchange,提问作者Ajit Kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 16:40:54