Testing functionality
This commit is contained in:
+155
-36
@@ -1,71 +1,190 @@
|
||||
import type React from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import * as papa from "papaparse";
|
||||
import type { Route } from "./+types/home";
|
||||
|
||||
export function meta({ }: Route.MetaArgs) {
|
||||
return [
|
||||
{ title: "New React Router App" },
|
||||
{ name: "description", content: "Welcome to React Router!" },
|
||||
{ title: "3dplot-gen" },
|
||||
{ name: "description", content: "CSV → 3D Plot generator" },
|
||||
];
|
||||
}
|
||||
|
||||
type Row = Record<string, number>;
|
||||
|
||||
export default function Home() {
|
||||
const plotRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [rows, setRows] = useState<Row[]>([]);
|
||||
const [columns, setColumns] = useState<string[]>([]);
|
||||
const [x, setX] = useState("");
|
||||
const [y, setY] = useState("");
|
||||
const [z, setZ] = useState("");
|
||||
|
||||
// Render plot when axes change
|
||||
useEffect(() => {
|
||||
if (!plotRef.current || !x || !y || !z) return;
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
(async () => {
|
||||
const Plotly = await import("plotly.js-dist-min");
|
||||
if (cancelled) return;
|
||||
|
||||
const trace = {
|
||||
type: "scatter3d",
|
||||
mode: "markers",
|
||||
x: rows.map(r => r[x]),
|
||||
y: rows.map(r => r[y]),
|
||||
z: rows.map(r => r[z]),
|
||||
marker: {
|
||||
size: 4,
|
||||
color: rows.map(r => r[z]),
|
||||
colorscale: "Viridis",
|
||||
},
|
||||
};
|
||||
|
||||
Plotly.newPlot(
|
||||
plotRef.current!,
|
||||
[trace as any],
|
||||
{
|
||||
paper_bgcolor: "#0b0d12",
|
||||
plot_bgcolor: "#0b0d12",
|
||||
font: { color: "#cfd3ff" },
|
||||
scene: {
|
||||
xaxis: { title: { text: x } },
|
||||
yaxis: { title: { text: y } },
|
||||
zaxis: { title: { text: z } },
|
||||
},
|
||||
margin: { l: 0, r: 0, t: 0, b: 0 },
|
||||
},
|
||||
{ responsive: true }
|
||||
);
|
||||
})();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [rows, x, y, z]);
|
||||
|
||||
|
||||
function onFile(file: File) {
|
||||
papa.parse(file, {
|
||||
header: true,
|
||||
dynamicTyping: true,
|
||||
skipEmptyLines: true,
|
||||
complete: (res) => {
|
||||
const data = res.data as Row[];
|
||||
setRows(data);
|
||||
setColumns(Object.keys(data[0]));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function exportPNG() {
|
||||
const Plotly = await import("plotly.js-dist-min");
|
||||
|
||||
if (!plotRef.current) return;
|
||||
|
||||
Plotly.toImage(plotRef.current, {
|
||||
format: "png",
|
||||
width: 1600,
|
||||
height: 900,
|
||||
}).then((url) => {
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "3dplot-gen.png";
|
||||
a.click();
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen grid grid-cols-[280px_1fr]">
|
||||
{/* Sidebar */}
|
||||
<aside className="bg-panel p-4 space-y-4">
|
||||
<div className="text-lg font-semibold">
|
||||
3dplot-gen
|
||||
<div className="text-xs text-muted">
|
||||
CSV → 3D Plot
|
||||
</div>
|
||||
<div className="text-xs text-muted">CSV → 3D Plot</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<Control label="CSV file">
|
||||
<input
|
||||
type="file"
|
||||
accept=".csv"
|
||||
className="w-full text-sm file:bg-bg file:text-slate-300 file:border-0
|
||||
file:rounded file:px-2 file:py-1"
|
||||
/>
|
||||
</Control>
|
||||
<Control label="CSV file">
|
||||
<input
|
||||
type="file"
|
||||
accept=".csv"
|
||||
onChange={(e) =>
|
||||
e.target.files && onFile(e.target.files[0])
|
||||
}
|
||||
className="w-full text-sm file:bg-neutral-900 file:text-slate-300 file:border-0
|
||||
file:rounded file:px-2 file:py-1"
|
||||
/>
|
||||
</Control>
|
||||
|
||||
<Control label="X axis">
|
||||
<select className="w-full bg-bg border border-slate-700 rounded px-2 py-1">
|
||||
<option>-</option>
|
||||
</select>
|
||||
</Control>
|
||||
<Control label="X axis">
|
||||
<select
|
||||
className="w-full bg-neutral-900 border border-slate-700 rounded px-2 py-1"
|
||||
value={x}
|
||||
onChange={(e) => setX(e.target.value)}
|
||||
>
|
||||
<option value="">—</option>
|
||||
{columns.map(c => (
|
||||
<option key={c}>{c}</option>
|
||||
))}
|
||||
</select>
|
||||
</Control>
|
||||
|
||||
<Control label="Y axis">
|
||||
<select className="w-full bg-bg border border-slate-700 rounded px-2 py-1">
|
||||
<option>-</option>
|
||||
</select>
|
||||
</Control>
|
||||
<Control label="Y axis">
|
||||
<select
|
||||
className="w-full bg-neutral-900 border border-slate-700 rounded px-2 py-1"
|
||||
value={y}
|
||||
onChange={(e) => setY(e.target.value)}
|
||||
>
|
||||
<option value="">—</option>
|
||||
{columns.map(c => (
|
||||
<option key={c}>{c}</option>
|
||||
))}
|
||||
</select>
|
||||
</Control>
|
||||
|
||||
<Control label="Z axis">
|
||||
<select className="w-full bg-bg border border-slate-700 rounded px-2 py-1">
|
||||
<option>-</option>
|
||||
</select>
|
||||
</Control>
|
||||
</div>
|
||||
<Control label="Z axis">
|
||||
<select
|
||||
className="w-full bg-neutral-900 border border-slate-700 rounded px-2 py-1"
|
||||
value={z}
|
||||
onChange={(e) => setZ(e.target.value)}
|
||||
>
|
||||
<option value="">—</option>
|
||||
{columns.map(c => (
|
||||
<option key={c}>{c}</option>
|
||||
))}
|
||||
</select>
|
||||
</Control>
|
||||
|
||||
<button
|
||||
onClick={exportPNG}
|
||||
className="w-full mt-2 bg-accent text-black text-sm rounded px-2 py-1 hover:opacity-90"
|
||||
>
|
||||
Export PNG
|
||||
</button>
|
||||
</aside>
|
||||
|
||||
{/* Plot */}
|
||||
<main className="bg-plot">
|
||||
<div
|
||||
id="plot"
|
||||
className="w-full h-full"
|
||||
/>
|
||||
<div ref={plotRef} className="w-full h-full" />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Control({ label, children }: { label: string, children: React.ReactNode }) {
|
||||
function Control({
|
||||
label,
|
||||
children,
|
||||
}: {
|
||||
label: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs text-muted">{label}</label>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user