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

D3.js+React面积图X轴域偏移问题:非3倍数时间数据异常

React + D3.js AreaChart X轴域偏移问题(非3倍数起始时间)

问题现象

开发的React+D3.js AreaChart组件存在X轴边界偏移问题:当数据集起始时间为3的整点倍数(如09:00、12:00)时,图表显示正常;若起始时间为非3倍数(如10:00、14:00),X轴的左右域会出现不必要的偏移。尝试调整xScale的domain和range参数后,问题仍未解决。

数据示例

正常显示数据集(起始时间为3倍数)

const validData = [
  { time: '09:00', value: 12 },
  { time: '10:00', value: 18 },
  { time: '11:00', value: 22 },
  { time: '12:00', value: 15 },
  { time: '15:00', value: 19 }
];

导致偏移的数据集(起始时间非3倍数)

const invalidData = [
  { time: '10:00', value: 15 },
  { time: '11:00', value: 20 },
  { time: '12:00', value: 17 },
  { time: '13:00', value: 25 },
  { time: '16:00', value: 21 }
];

完整组件代码

import React, { useEffect, useRef } from 'react';
import * as d3 from 'd3';

const AreaChart = ({ data }) => {
  const svgRef = useRef();

  useEffect(() => {
    const svg = d3.select(svgRef.current);
    svg.selectAll('*').remove();

    const width = 800;
    const height = 400;
    const margin = { top: 20, right: 30, bottom: 40, left: 50 };

    // X轴时间比例尺
    const xScale = d3.scaleTime()
      .domain(d3.extent(data, d => new Date(`2024-01-01T${d.time}`)))
      .range([margin.left, width - margin.right]);

    // Y轴数值比例尺
    const yScale = d3.scaleLinear()
      .domain([0, d3.max(data, d => d.value)])
      .range([height - margin.bottom, margin.top]);

    // 面积生成器
    const area = d3.area()
      .x(d => xScale(new Date(`2024-01-01T${d.time}`)))
      .y0(height - margin.bottom)
      .y1(d => yScale(d.value));

    // 绘制面积路径
    svg.append('path')
      .datum(data)
      .attr('fill', 'steelblue')
      .attr('d', area);

    // X轴配置(每3小时一个刻度)
    const xAxis = d3.axisBottom(xScale)
      .ticks(d3.timeHour.every(3))
      .tickFormat(d => d3.timeFormat('%H:%M')(d));

    // 绘制X轴
    svg.append('g')
      .attr('transform', `translate(0, ${height - margin.bottom})`)
      .call(xAxis);

    // Y轴配置
    const yAxis = d3.axisLeft(yScale);

    // 绘制Y轴
    svg.append('g')
      .attr('transform', `translate(${margin.left}, 0)`)
      .call(yAxis);
  }, [data]);

  return <svg ref={svgRef} width={800} height={400} />;
};

export default AreaChart;

问题根源

问题出在X轴的ticks(d3.timeHour.every(3))配置上。D3的轴生成器默认会自动调整比例尺的domain,让刻度对齐到指定的时间间隔(这里是3小时)。当数据集的起始时间不是3的整点倍数时,D3会将domain向左扩展到最近的3倍数整点,向右扩展到下一个3倍数整点,导致X轴域出现偏移。

解决方案

提供两种可行方案,可根据需求选择:

方案1:强制X轴域匹配数据范围,自定义刻度

保持X轴严格对应数据的时间范围,手动生成从数据起始点开始的3小时间隔刻度:

// 修改X轴比例尺和轴生成部分
const xDomain = d3.extent(data, d => new Date(`2024-01-01T${d.time}`));
const xScale = d3.scaleTime()
  .domain(xDomain)
  .range([margin.left, width - margin.right]);

// 手动生成3小时间隔的刻度数组
const ticks = [];
let currentTick = new Date(xDomain[0]);
while (currentTick <= xDomain[1]) {
  ticks.push(new Date(currentTick));
  currentTick.setHours(currentTick.getHours() + 3);
}

const xAxis = d3.axisBottom(xScale)
  .tickValues(ticks) // 使用自定义刻度
  .tickFormat(d => d3.timeFormat('%H:%M')(d));

方案2:调整domain对齐3倍数整点,避免偏移

如果希望刻度仍对齐3的整点,但不想X轴域偏移,可以手动将domain调整为包含数据范围的最近3倍数整点区间:

// 修改X轴比例尺的domain计算
const xDomain = d3.extent(data, d => new Date(`2024-01-01T${d.time}`));

// 计算起始时间最近的前一个3倍数整点
const startHour = xDomain[0].getHours();
const adjustedStart = new Date(xDomain[0]);
adjustedStart.setHours(Math.floor(startHour / 3) * 3, 0, 0, 0);

// 计算结束时间最近的后一个3倍数整点
const endHour = xDomain[1].getHours();
const adjustedEnd = new Date(xDomain[1]);
adjustedEnd.setHours(Math.ceil(endHour / 3) * 3, 0, 0, 0);

const xScale = d3.scaleTime()
  .domain([adjustedStart, adjustedEnd])
  .range([margin.left, width - margin.right]);

方案区别

  • 方案1:X轴严格贴合数据的时间范围,刻度从数据起始点开始按3小时递增,适合需要精确展示数据时间区间的场景。
  • 方案2:刻度对齐3的整点,同时domain扩展到最近的3倍数整点,保证刻度的规整性,适合强调时间刻度对齐的场景。

内容的提问来源于stack exchange,提问作者Roman Sharapov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 06:08:20