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
相关产品推荐
相关产品推荐

