为什么我的ReactJS D3散点图在鼠标输入时会中断,以及如何修复



可视化效果很好,但当我将鼠标悬停在其中一个圆上时就会中断。

Codepen 中的Viz

控制台错误:

Uncaught TypeError: d.Time.substring is not a function

以下是从父(应用程序(组件中触发错误的块。这个块的目的是对提取的数据进行变异,这样我就可以使用日期对象来使用D3的scaleTime构建yScale。这是有效的,直到鼠标事件触发状态更改为止。

console.log(data[0].Time); // Returns "36:50"
data.forEach((d) => {
let minutes = d.Time.substring(0, 2);
let seconds = d.Time.substring(3);
d.Time = new Date(1976, 6, 28, 0, minutes, seconds);
});
console.log(data[0].Time); // Returns Date Wed Jul 28 1976 00:36:50 GMT-0400 (Eastern Daylight Time)

在我看来,当数据加载时,我只需要运行一次这段代码,但我还没有想好如何做到这一点。我尝试将它添加到获取JSON数据的useEffect钩子中,但我不知道如何做到这一点。我发现了许多csv文件的例子(使用行参数(,但到目前为止,在获取数据的useEffect Hook中解析json文件的例子还没有。当我尝试在D3获取后运行上面的函数时,状态中的数据仍然为null(默认状态(。

这就是我获取数据的方式:

useEffect(() => {
d3.json(url).then((json) => {
setData(json);
});
}, []);

这是应用程序组件的完整代码:

// App Parent Component
const App = () => {
const [data, setData] = useState(null);
const [hoveredValue, setHoveredValue] = useState(null);
const [mousePosition, setMousePosition] = useState(initialMousePosition);
useEffect(() => {
d3.json(url).then((json) => {
setData(json);
});
}, []);
const handleMouseMove = useCallback(
(event) => {
const { clientX, clientY } = event;
setMousePosition({ x: clientX, y: clientY });
},
[setMousePosition]
);
if (!data) {
return <pre>Loading...</pre>;
}
console.log(data[0].Time); // Returns "36:50"
data.forEach((d) => {
let minutes = d.Time.substring(0, 2);
let seconds = d.Time.substring(3);
d.Time = new Date(1976, 6, 28, 0, minutes, seconds);
});
console.log(data[0].Time); // Returns Date Wed Jul 28 1976 00:36:50 GMT-0400 (Eastern Daylight Time)
const xValue = (d) => d.Year;
const leftAxisLabel = "Time in minutes";
const yValue = (d) => d.Time;
const bottomAxisLabel = "Year";
const yAxisTickFormat = d3.timeFormat("%M:%S");
const xScale = d3
.scaleLinear()
.domain(d3.extent(data, xValue))
.range([0, innerWidth])
.nice();
const yScale = d3
.scaleTime()
.domain(d3.extent(data, yValue))
.range([0, innerHeight])
.nice();
return (
<div>
<Tooltip hoveredValue={hoveredValue} mousePosition={mousePosition} />
<div id="viz-container">
<div id="title">{titleLabel}</div>
<div id="subtitle">{subtitleLabel}</div>
<div id="left-axis-label">{leftAxisLabel}</div>
<div id="bottom-axis-label">{bottomAxisLabel}</div>
<Legend innerWidth={innerWidth} innerHeight={innerHeight} />
<svg id="svg" width={width} height={height}>
<g transform={`translate(${margin.left}, ${margin.top})`}>
<g id="y-axis" className="y-axis">
<AxisLeft
yScale={yScale}
innerWidth={innerWidth}
yAxisTickFormat={yAxisTickFormat}
tickOffset={20}
/>
</g>
<g id="x-axis" className="x-axis">
<AxisBottom
xScale={xScale}
innerHeight={innerHeight}
tickOffset={8}
/>
</g>
<Marks
data={data}
xScale={xScale}
xValue={xValue}
yScale={yScale}
yValue={yValue}
setHoveredValue={setHoveredValue}
handleMouseMove={handleMouseMove}
/>
</g>
</svg>
</div>
</div>
);
};

这是渲染圆的子组件,我希望能够用鼠标悬停(以显示工具提示(:

// Marks (Dots) Component
const Marks = ({
data,
xScale,
xValue,
yScale,
yValue,
setHoveredValue,
handleMouseMove
}) =>
data.map((d, i) => (
<>
<circle
key={data[i].Place}
data-xvalue={xValue(d)}
data-yvalue={yValue(d)}
className="dot"
cx={xScale(xValue(d))}
cy={yScale(yValue(d))}
r="7"
fill={data[i].Doping ? "#FF4500" : "#228C22"}
opacity="0.7"
onMouseEnter={() => setHoveredValue([0, 0])} // Set to [xValue(d), yValue(d)] after debugging
onMouseLeave={() => setHoveredValue(null)}
onMouseMove={handleMouseMove}
/>
</>
));

这是我第一次在Stack Overflow上发帖。对我可能违反的任何礼仪规则表示歉意,并非常感谢您的帮助,包括自助文档的链接。

我能看到的一个快速解决方案是在继续之前在forEach中添加一个d.Time存在的检查,由于某种原因,数据在数组中的项似乎与预期的项不同,因为Time属性不存在或至少不是字符串。

很可能您的setData(json);在useEffect中需要解析jsonsetData(JSON.parse(json));

非常感谢freeCodeCamp的manjupillai16提供的两个解决方案。

问题是,任何触发重新渲染的状态更改都会再次运行forEach块,只有数据从提取的字符串中变为日期对象。

以下是使用效果的解决方案:

useEffect(() => {
if (data) {
data.forEach((d) => {
let minutes = d.Time.substring(0, 2);
let seconds = d.Time.substring(3);
d.Time = new Date(1976, 6, 28, 0, minutes, seconds);
});
setLoaded(true);
}
}, [data]);

以下是在数据发生突变后使用条件语句跳过块的解决方案:

if (typeof data[0].Time === "string") {
data.forEach((d) => {
let minutes = d.Time.substring(0, 2);
let seconds = d.Time.substring(3);
d.Time = new Date(1976, 6, 28, 0, minutes, seconds);
});
}

最新更新