如何用D3/SVG实现指定时间轴功能?附React代码求指导

时间轴实现改造方案
核心问题修正点
- 原代码按数组索引固定排列元素,未根据日期的时间顺序映射到对应坐标
- 缺少时间轴必备的刻度线、时间标签等基础样式
- 未利用D3的时间比例尺实现日期与像素坐标的精准转换
完整改造代码
先安装依赖:npm install d3 @types/d3
import React, { useRef, useEffect, useState } from 'react'; import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch'; import { scaleTime, axisBottom, select } from 'd3'; const Timeline = () => { const svgRef = useRef<SVGSVGElement>(null); const [timelineWidth, setTimelineWidth] = useState(800); const startDate = new Date(2020, 0, 1); const endDate = new Date(2023, 11, 31); const numDates = 100; // 生成并排序随机日期 const generateSortedDates = (start: Date, end: Date, count: number) => { const dateArray = []; const startMillis = start.getTime(); const endMillis = end.getTime(); const rangeMillis = endMillis - startMillis; for (let i = 0; i < count; i++) { const randomMillis = startMillis + Math.random() * rangeMillis; dateArray.push(new Date(randomMillis)); } return dateArray.sort((a, b) => a.getTime() - b.getTime()); }; const dates = generateSortedDates(startDate, endDate, numDates); // 初始化D3时间比例尺与轴 useEffect(() => { const handleResize = () => { if (svgRef.current) { const width = svgRef.current.parentElement?.clientWidth || 800; setTimelineWidth(width); renderAxis(width); } }; const renderAxis = (width: number) => { if (!svgRef.current) return; // 清除旧轴 select(svgRef.current).select('.timeline-axis').remove(); // 创建时间比例尺:将日期范围映射到SVG宽度范围(留左右边距) const xScale = scaleTime() .domain([startDate, endDate]) .range([50, width - 50]); // 创建底部时间轴 const xAxis = axisBottom(xScale) .ticks(12) // 显示12个刻度(约每年3个) .tickFormat(d => d.toLocaleDateString('en-US', { year: 'numeric', month: 'short' })); // 绘制轴 select(svgRef.current) .append('g') .attr('class', 'timeline-axis') .attr('transform', `translate(0, 60)`) .call(xAxis) .selectAll('text') .style('font-size', '10px') .style('fill', '#333'); }; window.addEventListener('resize', handleResize); handleResize(); return () => window.removeEventListener('resize', handleResize); }, [startDate, endDate]); // 生成时间点矩形 const renderDateRects = () => { const xScale = scaleTime() .domain([startDate, endDate]) .range([50, timelineWidth - 50]); return dates.map((date, idx) => { const x = xScale(date); return ( <g key={idx}> {/* 时间点标记 */} <rect x={x - 5} y={20} width={10} height={10} fill="steelblue" rx={2} /> {/* 日期标签(可选,避免重叠可隐藏) */} <text x={x} y={40} textAnchor="middle" fill="#333" fontSize="8" > {date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} </text> </g> ); }); }; return ( <div style={{ width: '100%', overflow: 'hidden', border: '1px solid #eee' }}> <TransformWrapper initialScale={1} minScale={0.5} maxScale={3} centerZoomedOut={true} > <TransformComponent> <svg ref={svgRef} width={timelineWidth} height={80}> {/* 时间轴背景线 */} <line x1="50" y1="60" x2={timelineWidth - 50} y2="60" stroke="#ddd" strokeWidth={2} /> {/* 时间点元素 */} <g>{renderDateRects()}</g> </svg> </TransformComponent> </TransformWrapper> </div> ); }; export default Timeline;
关键说明
- D3时间比例尺:通过
scaleTime将日期范围转换为SVG的像素坐标,确保时间点按实际先后顺序排列 - react-zoom-pan-pinch配置:设置缩放范围、初始缩放比例,适配长时间轴的拖拽浏览需求
- 响应式处理:监听容器宽度变化,动态调整时间轴宽度与刻度
- 样式优化:添加背景线、圆角矩形,调整标签位置避免重叠
内容的提问来源于stack exchange,提问作者BlueSea321
相关产品推荐
相关产品推荐

