基于D3.js的折线图改造:实现趋势面积图与增强功能
React+D3 折线图改造为趋势面积图实现方案
1. 改造为趋势面积图并添加趋势线
基于现有折线图的D3逻辑,新增面积生成器(d3.area),同时保留原折线作为趋势线:
import React, { useEffect, useRef } from 'react'; import * as d3 from 'd3'; const TrendAreaChart = ({ data }) => { const svgRef = useRef(); useEffect(() => { const svg = d3.select(svgRef.current); svg.selectAll('*').remove(); // 清空重绘 // 定义尺寸和边距 const margin = { top: 60, right: 30, bottom: 50, left: 60 }; const width = 800 - margin.left - margin.right; const height = 400 - margin.top - margin.bottom; // 构建比例尺 const xScale = d3.scaleTime() .domain(d3.extent(data, d => d.time)) .range([0, width]); const yScale = d3.scaleLinear() .domain([0, d3.max(data, d => d.value)]) .range([height, 0]); // 定义面积生成器 const areaGenerator = d3.area() .x(d => xScale(d.time)) .y0(height) // 面积底部对齐y轴0点 .y1(d => yScale(d.value)) .curve(d3.curveMonotoneX); // 平滑曲线 // 定义趋势线生成器(保留原折线逻辑) const lineGenerator = d3.line() .x(d => xScale(d.time)) .y(d => yScale(d.value)) .curve(d3.curveMonotoneX); // 添加绘图容器 const g = svg.append('g') .attr('transform', `translate(${margin.left}, ${margin.top})`); // 绘制面积 g.append('path') .attr('d', areaGenerator(data)) .attr('fill', '#8884d8') .attr('opacity', 0.3); // 绘制趋势线 g.append('path') .attr('d', lineGenerator(data)) .attr('stroke', '#8884d8') .attr('stroke-width', 2) .attr('fill', 'none'); // 添加坐标轴(复用原有逻辑) g.append('g') .attr('transform', `translate(0, ${height})`) .call(d3.axisBottom(xScale)); g.append('g') .call(d3.axisLeft(yScale)); }, [data]); return <svg ref={svgRef} width={800} height={400}></svg>; }; export default TrendAreaChart;
核心要点:用d3.area定义填充区域,设置y0为容器底部,y1为数据值,搭配平滑曲线;趋势线复用原折线的lineGenerator,叠加在面积上方。
2. 添加当前时间轴线与标记
在上述代码基础上,计算当前时间在x轴的位置,添加垂直轴线和圆点标记:
// 在useEffect的g容器创建后添加以下代码 const currentTime = new Date(); const currentX = xScale(currentTime); // 添加当前时间垂直轴线 g.append('line') .attr('x1', currentX) .attr('x2', currentX) .attr('y1', 0) .attr('y2', height) .attr('stroke', '#ff7300') .attr('stroke-width', 1) .attr('stroke-dasharray', '4,4'); // 添加当前时间点标记 const currentDataPoint = data.find(d => Math.abs(d.time.getTime() - currentTime.getTime()) < 3600000 // 匹配最近1小时内的数据点 ); if (currentDataPoint) { const currentY = yScale(currentDataPoint.value); g.append('circle') .attr('cx', currentX) .attr('cy', currentY) .attr('r', 5) .attr('fill', '#ff7300') .attr('stroke', '#fff') .attr('stroke-width', 2); } // 可选:添加当前时间文本标注 g.append('text') .attr('x', currentX) .attr('y', margin.top - 20) .attr('text-anchor', 'middle') .attr('fill', '#ff7300') .text('当前时间');
核心要点:通过xScale(currentTime)获取当前时间对应的x坐标,绘制虚线轴线;匹配最近的数据点绘制高亮圆点,也可扩展为区域标记(用rect元素)。
3. 顶部匹配刻度的告警组件
利用D3的x轴刻度信息,在React层面渲染顶部告警图标,确保与下方图表刻度对齐:
// 在组件内部添加刻度获取逻辑 const [xTicks, setXTicks] = useState([]); useEffect(() => { if (!data.length) return; const xScale = d3.scaleTime() .domain(d3.extent(data, d => d.time)) .range([0, 800 - 60 - 30]); // 与图表宽度一致 // 获取x轴刻度 const ticks = xScale.ticks(); setXTicks(ticks); }, [data]); // 在svg上方渲染告警组件 return ( <div style={{ position: 'relative', width: '800px' }}> {/* 顶部告警图标栏 */} <div style={{ position: 'absolute', top: 10, left: 60, width: 800 - 60 - 30, display: 'flex', justifyContent: 'space-around' }}> {xTicks.map((tick, idx) => { const tickX = d3.scaleTime() .domain(d3.extent(data, d => d.time)) .range([0, 800 - 60 - 30])(tick); // 模拟告警数据,实际根据业务逻辑判断 const hasAlert = idx % 3 === 0; return ( <div key={idx} style={{ position: 'absolute', left: `${tickX}px` }} > {hasAlert && <span style={{ color: '#ff4d4f', fontSize: '20px' }}>⚠️</span>} </div> ); })} </div> {/* 图表svg */} <svg ref={svgRef} width={800} height={400}></svg> </div> );
核心要点:通过xScale.ticks()获取x轴的刻度时间,计算每个刻度对应的水平位置,在绝对定位的容器中渲染告警图标,确保与下方图表刻度完全对齐。
内容的提问来源于stack exchange,提问作者The Old County
相关产品推荐
相关产品推荐

