Testing for Area(surface) gen

This commit is contained in:
2026-01-29 20:44:38 +01:00
parent 2f6a12a934
commit e350fb687d
+103 -14
View File
@@ -12,6 +12,25 @@ export function meta({ }: Route.MetaArgs) {
type Row = Record<string, number>;
type PlotType = "scatter" | "surface";
const COLORSCALES = [
"Viridis",
"Plasma",
"Inferno",
"Magma",
"Cividis",
"Hot",
"Electric",
"Bluered",
"Portland",
"Rainbow",
"RdBu",
"Earth",
"Teal",
"Turbo",
] as const;
export default function Home() {
const plotRef = useRef<HTMLDivElement>(null);
@@ -20,8 +39,33 @@ export default function Home() {
const [x, setX] = useState("");
const [y, setY] = useState("");
const [z, setZ] = useState("");
const [plotType, setPlotType] = useState<PlotType>("scatter");
const [colorscale, setColorscale] = useState<string>(COLORSCALES[0]);
// Render plot when axes change
// Build surface grid from scattered (x,y,z) points. Returns null if data isn't a grid.
function buildSurfaceGrid(): {
xUnique: number[];
yUnique: number[];
zMatrix: number[][];
} | null {
const xVals = rows.map((r) => Number(r[x]));
const yVals = rows.map((r) => Number(r[y]));
const zVals = rows.map((r) => Number(r[z]));
const xUnique = [...new Set(xVals)].sort((a, b) => a - b);
const yUnique = [...new Set(yVals)].sort((a, b) => a - b);
const keyToZ = new Map<string, number>();
for (let i = 0; i < rows.length; i++) {
const k = `${xVals[i]},${yVals[i]}`;
if (!keyToZ.has(k)) keyToZ.set(k, zVals[i]);
}
if (xUnique.length * yUnique.length !== keyToZ.size) return null;
const zMatrix: number[][] = yUnique.map((yi) =>
xUnique.map((xj) => keyToZ.get(`${xj},${yi}`) ?? NaN)
);
return { xUnique, yUnique, zMatrix };
}
// Render plot when axes or options change
useEffect(() => {
if (!plotRef.current || !x || !y || !z) return;
@@ -35,18 +79,30 @@ export default function Home() {
const yVals = rows.map((r) => Number(r[y]));
const zVals = rows.map((r) => Number(r[z]));
const trace = {
type: "scatter3d" as const,
mode: "markers" as const,
x: xVals,
y: yVals,
z: zVals,
marker: {
size: 4,
color: zVals,
colorscale: "Viridis" as const,
},
};
const grid = buildSurfaceGrid();
const useSurface = plotType === "surface" && grid !== null;
const trace = useSurface && grid
? {
type: "surface" as const,
x: grid.xUnique,
y: grid.yUnique,
z: grid.zMatrix,
colorscale: colorscale as "Viridis",
colorbar: { title: { text: z } },
}
: {
type: "scatter3d" as const,
mode: "markers" as const,
x: xVals,
y: yVals,
z: zVals,
marker: {
size: 4,
color: zVals,
colorscale: colorscale as "Viridis",
},
};
Plotly.newPlot(
plotRef.current!,
@@ -69,7 +125,7 @@ export default function Home() {
return () => {
cancelled = true;
};
}, [rows, x, y, z]);
}, [rows, x, y, z, plotType, colorscale]);
function onFile(file: File) {
@@ -188,6 +244,39 @@ export default function Home() {
</select>
</Control>
<Control label="Plot type">
<select
className="w-full bg-neutral-900 border border-slate-700 rounded px-2 py-1"
value={plotType}
onChange={(e) => setPlotType(e.target.value as PlotType)}
>
<option value="scatter">Dots (scatter)</option>
<option value="surface">Surface (area)</option>
</select>
</Control>
<Control label="Colorscale">
<select
className="w-full bg-neutral-900 border border-slate-700 rounded px-2 py-1"
value={colorscale}
onChange={(e) => setColorscale(e.target.value)}
>
{COLORSCALES.map((name) => (
<option key={name} value={name}>
{name}
</option>
))}
</select>
</Control>
{plotType === "surface" &&
rows.length > 0 &&
buildSurfaceGrid() === null && (
<p className="text-xs text-amber-500/90">
Surface needs a full X×Y grid of points. Showing dots instead.
</p>
)}
<button
onClick={exportPNG}
className="w-full mt-2 bg-accent text-black text-sm rounded px-2 py-1 hover:opacity-90"