Testing for Area(surface) gen
This commit is contained in:
+93
-4
@@ -12,6 +12,25 @@ export function meta({ }: Route.MetaArgs) {
|
|||||||
|
|
||||||
type Row = Record<string, number>;
|
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() {
|
export default function Home() {
|
||||||
const plotRef = useRef<HTMLDivElement>(null);
|
const plotRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -20,8 +39,33 @@ export default function Home() {
|
|||||||
const [x, setX] = useState("");
|
const [x, setX] = useState("");
|
||||||
const [y, setY] = useState("");
|
const [y, setY] = useState("");
|
||||||
const [z, setZ] = 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(() => {
|
useEffect(() => {
|
||||||
if (!plotRef.current || !x || !y || !z) return;
|
if (!plotRef.current || !x || !y || !z) return;
|
||||||
|
|
||||||
@@ -35,7 +79,19 @@ export default function Home() {
|
|||||||
const yVals = rows.map((r) => Number(r[y]));
|
const yVals = rows.map((r) => Number(r[y]));
|
||||||
const zVals = rows.map((r) => Number(r[z]));
|
const zVals = rows.map((r) => Number(r[z]));
|
||||||
|
|
||||||
const trace = {
|
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,
|
type: "scatter3d" as const,
|
||||||
mode: "markers" as const,
|
mode: "markers" as const,
|
||||||
x: xVals,
|
x: xVals,
|
||||||
@@ -44,7 +100,7 @@ export default function Home() {
|
|||||||
marker: {
|
marker: {
|
||||||
size: 4,
|
size: 4,
|
||||||
color: zVals,
|
color: zVals,
|
||||||
colorscale: "Viridis" as const,
|
colorscale: colorscale as "Viridis",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -69,7 +125,7 @@ export default function Home() {
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
}, [rows, x, y, z]);
|
}, [rows, x, y, z, plotType, colorscale]);
|
||||||
|
|
||||||
|
|
||||||
function onFile(file: File) {
|
function onFile(file: File) {
|
||||||
@@ -188,6 +244,39 @@ export default function Home() {
|
|||||||
</select>
|
</select>
|
||||||
</Control>
|
</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
|
<button
|
||||||
onClick={exportPNG}
|
onClick={exportPNG}
|
||||||
className="w-full mt-2 bg-accent text-black text-sm rounded px-2 py-1 hover:opacity-90"
|
className="w-full mt-2 bg-accent text-black text-sm rounded px-2 py-1 hover:opacity-90"
|
||||||
|
|||||||
Reference in New Issue
Block a user