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

React中使用D3绘制双折线图遇NaN错误,求解决方案

修复React + D3折线图的NaN路径错误

我来帮你排查这个问题——你遇到的<path>里的NaN错误主要是Y轴比例尺的域设置错误加上数据类型没有正确转换导致的,咱们一步步来修复:

核心问题分析

  1. Y轴Domain完全错误:你把Y轴的范围设成了Health_id的区间,这和你要展示的Disk_Free、Memory_Free完全不搭边,导致Y轴计算出来的坐标都是无效值。
  2. 字符串数据未转换:数据集里的Disk_Free和Memory_Free都是字符串类型,直接传给比例尺会导致数值计算失败。
  3. Line Generator复用问题:同一个lineGenerator连续设置y访问器会覆盖之前的配置,导致两条线的Y值计算混乱。
  4. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 17:47:53