React中使用D3绘制折线图出现NaN报错求助
报错原因与修复方案
1. 报错根源
你犯了React钩子的基础使用错误:useState返回的是包含状态值和更新函数的数组,而非直接的状态值。你写的const data = useState([35, 78, 38]);会让data变成[[35,78,38], setData]的结构,当D3尝试用这个数组生成路径时,yScale处理到第二个元素(更新函数)时无法解析为数字,直接返回NaN,就出现了<path> attribute d: Expected number的报错。
2. 修复后的代码
修正useState的解构方式,同时补全样式单位(避免样式不生效),并优化了y轴缩放逻辑:
import React, { useState, useRef, useEffect } from "react"; import * as d3 from 'd3' export const Chart = React.forwardRef((props, ref) => { // 正确解构useState,获取纯数据数组 const [data] = useState([35, 78, 38]); const svgRef = useRef(); useEffect(() => { const w = 400; const h = 100; const svg = d3.select(svgRef.current) .attr('width', w) .attr('height', h) .style('background', '#d3d3d4') .style('margin-left', '10px') // 补全单位px .style('margin-top', '10px') // 补全单位px .style('overflow', 'visible') // 坐标轴缩放设置 const xScale = d3.scaleLinear() .domain([0, data.length - 1]) .range([0, w]); // 用数据最大值作为y轴范围,适配数据高度 const yScale = d3.scaleLinear() .domain([0, d3.max(data)]) .range([h, 0]); const generateScaledLine = d3.line() .x((d, i) => xScale(i)) .y(yScale) .curve(d3.curveCardinal) // 绘制折线 svg.selectAll('.line') .data([data]) .join('path') .attr('d', d => generateScaledLine(d)) .attr('fill', 'none') .attr('stroke', 'black') .attr('stroke-width', 2); // 增加线条宽度,提升可读性 }, [data]); return ( <div> <svg ref={ svgRef } /> </div> ); });
3. React 18 + D3 新手友好示例
示例1:基础柱状图
import React, { useRef, useEffect } from 'react'; import * as d3 from 'd3'; export const BarChart = () => { const svgRef = useRef(); const data = [25, 40, 65, 80, 30]; useEffect(() => { const width = 400; const height = 200; const margin = { top: 20, right: 20, bottom: 30, left: 40 }; const svg = d3.select(svgRef.current) .attr('width', width) .attr('height', height); // 清除旧内容,避免重复渲染 svg.selectAll('*').remove(); // 坐标轴缩放 const xScale = d3.scaleBand() .domain(data.map((_, i) => `Item ${i+1}`)) .range([margin.left, width - margin.right]) .padding(0.2); const yScale = d3.scaleLinear() .domain([0, d3.max(data)]) .range([height - margin.bottom, margin.top]); // 绘制柱子 svg.selectAll('rect') .data(data) .join('rect') .attr('x', (d, i) => xScale(`Item ${i+1}`)) .attr('y', d => yScale(d)) .attr('width', xScale.bandwidth()) .attr('height', d => height - margin.bottom - yScale(d)) .attr('fill', '#4a90e2'); // 绘制坐标轴 const xAxis = d3.axisBottom(xScale); const yAxis = d3.axisLeft(yScale); svg.append('g') .attr('transform', `translate(0, ${height - margin.bottom})`) .call(xAxis); svg.append('g') .attr('transform', `translate(${margin.left}, 0)`) .call(yAxis); }, [data]); return <svg ref={svgRef} />; };
示例2:动态更新折线图
import React, { useState, useRef, useEffect } from 'react'; import * as d3 from 'd3'; export const DynamicLineChart = () => { const [data, setData] = useState([10, 30, 20, 50, 40]); const svgRef = useRef(); // 模拟自动更新数据 useEffect(() => { const interval = setInterval(() => { setData(prev => { const newData = [...prev]; newData.push(Math.floor(Math.random() * 60)); return newData.slice(-5); // 只保留最新5个数据点 }); }, 2000); return () => clearInterval(interval); }, []); useEffect(() => { const width = 400; const height = 200; const margin = { top: 20, right: 20, bottom: 30, left: 40 }; const svg = d3.select(svgRef.current) .attr('width', width) .attr('height', height); svg.selectAll('*').remove(); // 坐标轴缩放 const xScale = d3.scaleLinear() .domain([0, data.length - 1]) .range([margin.left, width - margin.right]); const yScale = d3.scaleLinear() .domain([0, d3.max(data)]) .range([height - margin.bottom, margin.top]); // 生成折线 const line = d3.line() .x((d, i) => xScale(i)) .y(d => yScale(d)) .curve(d3.curveMonotoneX); // 绘制折线 svg.append('path') .datum(data) .attr('fill', 'none') .attr('stroke', '#e74c3c') .attr('stroke-width', 2) .attr('d', line); // 绘制数据点 svg.selectAll('circle') .data(data) .join('circle') .attr('cx', (d, i) => xScale(i)) .attr('cy', d => yScale(d)) .attr('r', 4) .attr('fill', '#e74c3c'); // 绘制坐标轴 const xAxis = d3.axisBottom(xScale) .tickFormat(d => `Point ${d+1}`); const yAxis = d3.axisLeft(yScale); svg.append('g') .attr('transform', `translate(0, ${height - margin.bottom})`) .call(xAxis); svg.append('g') .attr('transform', `translate(${margin.left}, 0)`) .call(yAxis); }, [data]); return ( <div> <svg ref={svgRef} /> <button onClick={() => setData([...data, Math.floor(Math.random() * 60)])}>添加数据点</button> </div> ); };
内容的提问来源于stack exchange,提问作者Denis
相关产品推荐
相关产品推荐

