我被要求使用 D3.js 在 React 中实现密度图,但我的图在屏幕上没有显示任何内容。我对图表和数据可视化不太熟悉,所以我很挣扎。大多数情况下,我一直在遵循此处显示的示例:https://www.react-graph-gallery.com/densis-plot
在控制台中,我可以看到这个错误
Error: <path> attribute d: Expected number, "M10,NaNL12,NaNC14,Na…".
我正在使用的数据集看起来像这样
[1768, 1809, 3091, 2314, 2845, 3068, 2402, 2514, 2899, 2793, 3297, 2550, 3525, 2863, 3193, 1726, 2356, 2275, 2591, 2496, 1644, 2912, 2801, 2394, 3147, 3373, 2446, 2473, 2350, 1854, 3022, 3274, 3059, 1483, 1844, 2234, 2295, 2053, 2111, 2410, 2256, 2224, 2756, 2733, 2244, 1980, 2558, 3166, 2713, 2177, 2231, 1942]
下面是我的图表组件的正在进行的代码:
import { useEffect, useMemo, useState } from "react";
import * as d3 from "d3";
import * as statsPackage from "simple-statistics";
import importedData from "./data"
import AxisBottom from "./axisBottom";
import AxisLeft from "./AxisTop";
const MARGIN = { top: 30, right: 30, bottom: 50, left: 50 };
type DensityChartProps = {
width?: number;
height?: number;
filteredDataset: number[];
};
export const DensityChart = ({ width = 700, height = 400, filteredDataset}: DensityChartProps) => {
const boundsWidth = width - MARGIN.right - MARGIN.left;
const boundsHeight = height - MARGIN.top - MARGIN.bottom;
const usedData = filteredDataset?.map(num => Math.round(num) ) ?? importedData;
const [states, setStates] = useState({
stDev: statsPackage.standardDeviation(usedData),
mean: statsPackage.mean(usedData),
stDevs: 1
})
useEffect(() => {
console.log("states", states);
console.log("all numbers? ", filteredData.every(num => typeof num === "number"))
}, [states])
const filteredData = usedData.filter((value) =>
{
try {
return Math.abs(value - states.mean) <= states.stDevs * states.stDev;
} catch (error) {
console.error("Error filtering data:", error);
return false;
}
}
) ;
const xScale = useMemo(() => {
try {
return d3.scaleLinear().domain([0, 1000]).range([10, boundsWidth - 10]);
} catch (error) {
console.error("Error creating xScale:", error);
return d3.scaleLinear().domain([0, 1]).range([10, boundsWidth - 10]);
}
}, [filteredData, width, states.stDevs]);
// Compute kernel density estimation
const density = useMemo(() => {
const kde = kernelDensityEstimator(kernelEpanechnikov(7), xScale.ticks(40));
return kde(filteredData);
}, [xScale]);
const yScale = useMemo(() => {
try {
const max = Math.max(...density.map((d) => d[1]));
console.log("d3.scaleLinear().range([boundsHeight, 0]).domain([0, max])", d3.scaleLinear().range([boundsHeight, 0]).domain([0, max]))
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]);
}
}, [filteredData, height, states.stDevs]);
const path = useMemo(() => {
try {
const lineGenerator = d3.line().x((d) => {
console.log("xScale(d[0]", xScale(d[0]));
return xScale(d[0])
})
.y((d) => {
console.log("yScale(d[1]", yScale(d[1]));
return yScale(d[1])
})
.curve(d3.curveBasis);
console.log("lineGenerator(density)", lineGenerator(density));
return lineGenerator(density);
} catch (error) {
console.error("Error creating path:", error);
return "";
}
}, [density]);
const handleStdDeviationChange = (e) => {
try {
setStates({ ...states, stDevs: Number(e.target.value) });
} catch (error) {
console.error("Error setting standard deviation:", error);
}
};
return (
<>
<svg width={width} height={height}>
<g
width={boundsWidth}
height={boundsHeight}
transform={`translate(${[MARGIN.left, MARGIN.top].join(",")})`}
>
<path
d={path}
fill="blue"
opacity={0.4}
stroke="black"
strokeWidth={1}
strokeLinejoin="round"
/>
{/* X axis, use an additional translation to appear at the bottom */}
<g transform={`translate(0, ${boundsHeight})`}>
<AxisLeft yScale={yScale} pixelsPerTick={40} />
</g>
<g transform={`translate(0, ${boundsHeight})`}>
<AxisBottom xScale={xScale} pixelsPerTick={40} />
</g>
</g>
</svg>
<p>Standard deviation: {states.stDev}</p>
<br />
<p>Mean: {states.mean}</p>
<br />
<label>Display Data Within N Standard Deviations: </label>
<input
type="number"
value={states.stDevs}
onChange={handleStdDeviationChange}
step="0.1"
/>
</>
);
};
export default DensityChart;
// TODO: improve types
// Function to compute density
function kernelDensityEstimator(kernel: (v: number) => number, X: number[]) {
return function (V: number[]) {
return X.map((x) => [x, d3.mean(V, (v) => kernel(x - v))]);
};
}
function kernelEpanechnikov(k: number) {
return function (v: number) {
return Math.abs((v /= k)) <= 1 ? (0.75 * (1 - v * v)) / k : 0;
};
}
但是,如果我使用如下所示的虚拟数据集,它似乎工作正常:
75, 104, 369, 300, 92, 64, 265, 35, 287, 69, 52, 23, 287, 87, 114, 114, 98, 137, 87, 90, 63, 69, 80, 113, 58, 115, 30, 35, 92, 460, 74, 72, 63, 115, 60, 75, 31, 277, 52, 218, 132, 316, 127, 87, 449, 46, 345, 48, 184, 149, 345, 92, 749, 93, 9502, 138, 48, 87, 103, 32, 93, 57, 109, 127, 149, 78, 162, 173, 87, 184, 288, 576, 460, 150, 127, 92, 84, 115, 218, 404, 52, 85, 66, 52, 201, 287, 69, 114, 379, 115, 161, 91, 231, 230, 822, 115, 80, 58, 207, 171, …]
我想我一定犯了一个非常愚蠢的错误,但我会很感激任何帮助。
您的
xscale
有 domain([0, 1000])
导致您的所有 KDE 存储桶 (
xScale.ticks(40)
) 都来自 [0 ... 1000]
您的所有样本数据都在
[2000 ... 3000]
范围内。
不太合适。
结果你的 KDE 值都是
0
。
你的图表是一条水平线。