React中使用D3绘制双折线图遇NaN错误,求解决方案
修复React + D3折线图的NaN路径错误
我来帮你排查这个问题——你遇到的<path>里的NaN错误主要是Y轴比例尺的域设置错误加上数据类型没有正确转换导致的,咱们一步步来修复:
核心问题分析
- Y轴Domain完全错误:你把Y轴的范围设成了
Health_id的区间,这和你要展示的Disk_Free、Memory_Free完全不搭边,导致Y轴计算出来的坐标都是无效值。 - 字符串数据未转换:数据集里的
Disk_Free和Memory_Free都是字符串类型,直接传给比例尺会导致数值计算失败。 - Line Generator复用问题:同一个
lineGenerator连续设置y访问器会覆盖之前的配置,导致两条线的Y值计算混乱。 - SVG缺失尺寸:你的SVG没有设置
width和height,即使生成了正确路径也无法显示。
修复步骤与代码
第一步:预处理数据,转换类型
先把字符串格式的数值转成数字,同时把Epoch时间直接转成Date对象,方便D3时间比例尺处理:
const processedData = data.map(d => ({ ...d, Epoch_Time: new Date(d.Epoch_Time * 1000), Disk_Free: parseFloat(d.Disk_Free), Memory_Free: parseInt(d.Memory_Free, 10) }));
第二步:为每个指标单独设置Y轴比例尺
因为Disk_Free和Memory_Free的数值范围差距极大,建议用双Y轴(或者对Memory值做归一化,比如转成GB单位):
// X轴比例尺(时间) const xScale = d3.scaleTime() .domain(d3.extent(processedData, d => d.Epoch_Time)) .range([margin.left, width - margin.right]); // Y轴1:对应Disk_Free const yScaleDisk = d3.scaleLinear() .domain(d3.extent(processedData, d => d.Disk_Free)) .range([height - margin.bottom, margin.top]); // Y轴2:对应Memory_Free const yScaleMem = d3.scaleLinear() .domain(d3.extent(processedData, d => d.Memory_Free)) .range([height - margin.bottom, margin.top]);
第三步:正确生成两条折线的路径
复用Line Generator时,每次生成前重新配置y访问器即可:
const lineGenerator = d3.line().x(d => xScale(d.Epoch_Time)); const lines = [ { path: lineGenerator.y(d => yScaleDisk(d.Disk_Free))(processedData), color: "red" }, { path: lineGenerator.y(d => yScaleMem(d.Memory_Free))(processedData), color: "blue" } ];
完整修正后的组件代码
import React from 'react'; import * as d3 from 'd3'; // 定义图表边距和尺寸 const margin = { top: 20, right: 50, bottom: 30, left: 50 }; const width = 800 - margin.left - margin.right; const height = 400 - margin.top - margin.bottom; class Chart extends React.Component { static getDerivedStateFromProps(nextProps, prevState) { const { data } = nextProps; if (!data) return {}; // 预处理数据:转换类型+转日期 const processedData = data.map(d => ({ ...d, Epoch_Time: new Date(d.Epoch_Time * 1000), Disk_Free: parseFloat(d.Disk_Free), Memory_Free: parseInt(d.Memory_Free, 10) })); // 初始化比例尺 const xScale = d3.scaleTime() .domain(d3.extent(processedData, d => d.Epoch_Time)) .range([margin.left, width - margin.right]); const yScaleDisk = d3.scaleLinear() .domain(d3.extent(processedData, d => d.Disk_Free)) .range([height - margin.bottom, margin.top]); const yScaleMem = d3.scaleLinear() .domain(d3.extent(processedData, d => d.Memory_Free)) .range([height - margin.bottom, margin.top]); // 生成折线路径 const lineGenerator = d3.line().x(d => xScale(d.Epoch_Time)); const lines = [ { path: lineGenerator.y(d => yScaleDisk(d.Disk_Free))(processedData), color: 'red' }, { path: lineGenerator.y(d => yScaleMem(d.Memory_Free))(processedData), color: 'blue' } ]; return { lines, xScale, yScaleDisk, yScaleMem }; } render() { const { lines, xScale, yScaleDisk, yScaleMem } = this.state || {}; if (!lines) return <div>Loading...</div>; return ( <div className="main-body"> <h4 style={{ textAlign: 'center' }}>Line Chart Test</h4> {/* 设置SVG完整尺寸 */} <svg width={width + margin.left + margin.right} height={height + margin.top + margin.bottom}> {/* 绘制折线 */} <g> {lines.map((line, i) => ( <path key={i} d={line.path} fill="none" stroke={line.color} strokeWidth={2} /> ))} </g> {/* 绘制X轴 */} <g transform={`translate(0, ${height - margin.bottom})`} ref={node => node && d3.select(node).call(d3.axisBottom(xScale))} /> {/* 绘制左侧Y轴(Disk_Free) */} <g transform={`translate(${margin.left}, 0)`} ref={node => node && d3.select(node).call(d3.axisLeft(yScaleDisk))} /> {/* 绘制右侧Y轴(Memory_Free) */} <g transform={`translate(${width - margin.right}, 0)`} ref={node => node && d3.select(node).call(d3.axisRight(yScaleMem))} /> </svg> </div> ); } } export default Chart;
额外提示
- 因为
Memory_Free的数值太大,和Disk_Free放在同一图表会导致Disk的折线几乎是一条直线,你可以把Memory值除以1024 * 1024 * 1024转成GB单位,这样图表会更直观。 - 在React中使用D3,建议把DOM操作(比如绘制坐标轴)放在
componentDidMount或useEffect(函数组件)中,getDerivedStateFromProps只负责计算数据和比例尺,代码结构会更清晰。
内容的提问来源于stack exchange,提问作者Mathew Pius
相关产品推荐
相关产品推荐

