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

React+D3.js绘制密度图报错:<path>属性d出现NaN值

问题分析与解决方案

核心问题

控制台报错Error: <path> attribute d: Expected number, "M10,NaNL12,NaNC14,Na…"的根本原因是X轴比例尺的定义域与数据集范围完全不匹配,导致后续密度计算和Y轴比例尺生成出现NaN值:

  • 你的数据集数值范围在1483 ~ 3525之间,但代码硬编码了X轴比例尺的定义域为[0, 1000]
  • 这使得xScale.ticks(40)生成的刻度全部落在0-1000区间,与实际数据无交集
  • 核密度计算时,所有kernel(x-v)的结果都是0(因为x在0-1000,v在1400+,差值绝对值远大于核函数带宽7)
  • Y轴比例尺的定义域被设为[0, 0],调用yScale(0)时返回NaN,最终路径生成失败

修复步骤

1. 动态计算X轴比例尺定义域

替换硬编码的[0,1000],基于实际数据范围生成带边距的定义域:

const xScale = useMemo(() => {
  try {
    const min = d3.min(filteredData);
    const max = d3.max(filteredData);
    // 添加10%边距避免图表贴边
    const padding = (max - min) * 0.1;
    return d3.scaleLinear()
      .domain([min - padding, max + padding])
      .range([10, boundsWidth - 10]);
  } catch (error) {
    console.error("Error creating xScale:", error);
    return d3.scaleLinear().domain([0, 1]).range([10, boundsWidth - 10]);
  }
}, [filteredData, width]);

2. 修复Y轴比例尺边界情况

当密度计算结果全为0时,设置默认最小值避免domain([0,0])的无效状态:

const yScale = useMemo(() => {
  try {
    const densities = density.map((d) => d[1]);
    const max = Math.max(...densities, 0.001); // 加入默认最小值防止全0
    return d3.scaleLinear()
      .range([boundsHeight, 0])
      .domain([0, max]);
  } catch (error) {
    console.error("Error creating yScale:", error);
    return d3.scaleLinear().range([boundsHeight, 0]).domain([0, 1]);
  }
}, [density, height]);

3. 修正坐标轴组件引用与渲染错误

  • 组件导入错误:import AxisLeft from "./AxisTop" 改为 import AxisLeft from "./AxisLeft"
  • 坐标轴渲染位置错误:移除X轴区域的AxisLeft,移到左侧正确位置:
<g>
  <AxisLeft yScale={yScale} pixelsPerTick={40} />
</g>
<g transform={`translate(0, ${boundsHeight})`}>
  <AxisBottom xScale={xScale} pixelsPerTick={40} />
</g>

4. 优化核函数带宽(可选)

当前带宽7过小,针对大数值范围的数据,可使用Silverman法则动态计算最优带宽:

function kernelEpanechnikovForData(data: number[]) {
  const stDev = statsPackage.standardDeviation(data);
  const n = data.length;
  // Silverman法则计算带宽
  const bandwidth = stDev * Math.pow(4/(3*n), 1/5);
  return kernelEpanechnikov(bandwidth);
}

// 密度计算中使用动态带宽
const density = useMemo(() => {
  const kernel = kernelEpanechnikovForData(filteredData);
  const kde = kernelDensityEstimator(kernel, xScale.ticks(40));
  return kde(filteredData);
}, [xScale, filteredData]);

额外优化建议

  • 初始化统计值时,用useEffect避免重复计算:
const [states, setStates] = useState<{stDev: number, mean: number, stDevs: number}>({
  stDev: 0,
  mean: 0,
  stDevs: 1
});

useEffect(() => {
  setStates({
    stDev: statsPackage.standardDeviation(usedData),
    mean: statsPackage.mean(usedData),
    stDevs: 1
  });
}, [usedData]);
  • 过滤数据时先校验统计值有效性:
const filteredData = useMemo(() => {
  if (!states.mean || !states.stDev) return usedData;
  return usedData.filter(value => {
    try {
      return Math.abs(value - states.mean) <= states.stDevs * states.stDev;
    } catch (error) {
      console.error("Error filtering data:", error);
      return false;
    }
  });
}, [usedData, states]);

内容的提问来源于stack exchange,提问作者arengit

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 19:55:54