diff --git a/app/routes/home.tsx b/app/routes/home.tsx index 5808bdb..2e4b69f 100644 --- a/app/routes/home.tsx +++ b/app/routes/home.tsx @@ -42,19 +42,33 @@ export default function Home() { const [plotType, setPlotType] = useState("scatter"); const [colorscale, setColorscale] = useState(COLORSCALES[0]); + // First row after header = axis sizes (X max, Y max, Z max); rest = plot data. + const plotData = rows.length > 1 ? rows.slice(1) : rows; + const axisSizes = + rows.length > 1 && x && y && z + ? (() => { + const sx = Number(rows[0][x]); + const sy = Number(rows[0][y]); + const sz = Number(rows[0][z]); + return [sx, sy, sz].every((n) => Number.isFinite(n)) + ? { x: sx, y: sy, z: sz } + : null; + })() + : null; + // 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 xVals = plotData.map((r) => Number(r[x])); + const yVals = plotData.map((r) => Number(r[y])); + const zVals = plotData.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(); - for (let i = 0; i < rows.length; i++) { + for (let i = 0; i < plotData.length; i++) { const k = `${xVals[i]},${yVals[i]}`; if (!keyToZ.has(k)) keyToZ.set(k, zVals[i]); } @@ -75,9 +89,9 @@ export default function Home() { const Plotly = await import("plotly.js-dist-min"); if (cancelled) return; - 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 xVals = plotData.map((r) => Number(r[x])); + const yVals = plotData.map((r) => Number(r[y])); + const zVals = plotData.map((r) => Number(r[z])); const grid = buildSurfaceGrid(); const useSurface = plotType === "surface" && grid !== null; @@ -104,6 +118,21 @@ export default function Home() { }, }; + const sceneLayout: { + xaxis: { title: { text: string }; range?: [number, number] }; + yaxis: { title: { text: string }; range?: [number, number] }; + zaxis: { title: { text: string }; range?: [number, number] }; + } = { + xaxis: { title: { text: x } }, + yaxis: { title: { text: y } }, + zaxis: { title: { text: z } }, + }; + if (axisSizes) { + sceneLayout.xaxis.range = [0, axisSizes.x]; + sceneLayout.yaxis.range = [0, axisSizes.y]; + sceneLayout.zaxis.range = [0, axisSizes.z]; + } + Plotly.newPlot( plotRef.current!, [trace], @@ -111,11 +140,7 @@ export default function Home() { paper_bgcolor: "#0b0d12", plot_bgcolor: "#0b0d12", font: { color: "#cfd3ff" }, - scene: { - xaxis: { title: { text: x } }, - yaxis: { title: { text: y } }, - zaxis: { title: { text: z } }, - }, + scene: sceneLayout, margin: { l: 0, r: 0, t: 0, b: 0 }, }, { responsive: true } @@ -198,6 +223,12 @@ export default function Home() { file:rounded file:px-2 file:py-1" /> +

+ First data row = axis sizes (X max, Y max, Z max). Plot uses rows after that.{" "} + + Example (surface) + +