Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 46 additions & 41 deletions src/components/CanvasGeometry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import { Center, OrbitControls, Environment, Html } from '@react-three/drei'
import { variables, GetArray } from '@/components/ZarrLoaderLRU'
import { useEffect, useState } from 'react';
// import { useEffect, useState } from 'react';
import { Leva, useControls } from 'leva'
import { lightTheme } from '@/utils/levaTheme'
import { useControls } from 'leva'
// import { Leva } from 'leva'
// import { lightTheme } from '@/utils/levaTheme'
import { ArrayToTexture, DefaultCube } from './TextureMakers';
import { DataCube, PointCloud, UVCube } from './PlotObjects';
import { TimeSeries } from './TimeSeries';
// import { PlaneAxis } from './PlaneAxis';
import { PlotArea } from './PlotArea'
import { GetColorMapTexture } from '@/utils/colormap';

const colormaps = ['viridis', 'plasma', 'inferno', 'magma', 'Accent', 'Blues',
Expand Down Expand Up @@ -97,63 +100,65 @@ export function CanvasGeometry() {
return (
<>
<div className='canvas'>
<Canvas shadows
<Canvas shadows camera={{ position: [-4.5, 3, 4.5], fov: 50 }}
frameloop="demand"
>
<Center top position={[-1, 0, 1]}/>

{/* Volume Plots */}
{plotter == "volume" && <>
<DataCube volTexture={texture} shape={shape} colormap={colormap}/>
<mesh onClick={() => setShowTimeSeries(true)}>
<UVCube shape={shape} setTimeSeriesLocs={setTimeSeriesLocs}/>
{/* <mesh onClick={() => setShowTimeSeries(true)}>
<UVCube shape={shape} setTimeSeriesLocs={setTimeSeriesLocs}/>
</mesh>

</mesh> */}
</>}

{/* Point Clouds Plots */}
{plotter == "point-cloud" && <PointCloud textures={{texture,colormap}} />}


{/* Time Series Plots */}
{showTimeSeries && <>

<TimeSeries timeSeriesLocs={timeSeriesLocs} DSInfo={{variable:variable, storePath:storeURL}} scaling={{...valueScales,colormap}}/>
<Html
fullscreen
style={{
pointerEvents: 'none', // Prevents capturing mouse events
}}
>
{/* Stand in button to remove time-series stuff */}
<button style={{
position: 'absolute',
bottom: '100px',
right: '100px',
padding: '8px 16px',
backgroundColor: '#3498db',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
pointerEvents: 'auto'

}}
onClick={()=>setShowTimeSeries(false)}
>
Hide Time Series
</button>
</Html>
</>}



<OrbitControls minPolarAngle={0} maxPolarAngle={Math.PI / 2} enablePan={false}/>
<Environment preset="city" />

</Canvas>
</div>
<Leva theme={lightTheme} />
{showTimeSeries && <>

<TimeSeries timeSeriesLocs={timeSeriesLocs} DSInfo={{variable:variable, storePath:storeURL}} scaling={{...valueScales,colormap}}/>
<Html
fullscreen
style={{
pointerEvents: 'none', // Prevents capturing mouse events
}}
>
{/* Stand in button to remove time-series stuff */}
<button style={{
position: 'absolute',
bottom: '100px',
right: '100px',
padding: '8px 16px',
backgroundColor: '#3498db',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
pointerEvents: 'auto'

}}
onClick={()=>setShowTimeSeries(false)}
>
Hide Time Series
</button>
</Html>
</>}
<PlotArea
data={[[-5,0,0], [1,5,0], [2,0,0], [3,1,0], [4,0,0], [5,1,0], [60,-1.5,0]]}
// now we need to add the time series data to the plot area
// data={timeSeries} // but it needs to be in the right format
lineColor="orangered"
lineWidth={5}
/>
{/* <Leva theme={lightTheme} /> */}
</>
)
}
Expand Down
182 changes: 182 additions & 0 deletions src/components/FixedTicks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import { Text } from '@react-three/drei'
import { useThree, useFrame } from '@react-three/fiber'
import { useState, useMemo } from 'react'

interface ViewportBounds {
left: number;
right: number;
top: number;
bottom: number;
}

interface FixedTicksProps {
color?: string;
tickSize?: number;
fontSize?: number;
showGrid?: boolean;
gridOpacity?: number;
}


export function FixedTicks({
color = 'white',
tickSize = 4,
fontSize = 12,
showGrid = true,
gridOpacity = 0.1
}: FixedTicksProps) {
const { camera, viewport, size } = useThree()
const [bounds, setBounds] = useState<ViewportBounds>({ left: 0, right: 0, top: 0, bottom: 0 })
const [zoom, setZoom] = useState(camera.zoom)

const sizes = useMemo(() => {
// Convert from pixels to scene units
const pixelsPerUnit = size.height / (viewport.height * camera.zoom)
return {
tickSize: tickSize / pixelsPerUnit,
fontSize: fontSize / pixelsPerUnit,
labelOffset: tickSize / pixelsPerUnit
}
}, [size.height, viewport.height, camera.zoom, tickSize, fontSize])

// Update bounds when camera moves
// TODO: update bounds when camera zooms
useFrame(() => {
if (camera.zoom !== zoom) {
setZoom(camera.zoom) // this is not working properly
}
const worldWidth = viewport.width / camera.zoom
const worldHeight = viewport.height / camera.zoom

const newBounds = {
left: -worldWidth / 2 + camera.position.x,
right: worldWidth / 2 + camera.position.x,
top: worldHeight / 2 + camera.position.y,
bottom: -worldHeight / 2 + camera.position.y
}

setBounds(newBounds)
})

return (
<group>
{/* Grid Lines */}
{showGrid && (
<>
{/* Vertical grid lines */}
{Array.from({ length: 10 }, (_, i) => {
if (i === 0 || i === 9) return null; // Skip edges
const x = bounds.left + (bounds.right - bounds.left) * (i / 9)
return (
<line key={`vgrid-${i}`}>
<bufferGeometry>
<float32BufferAttribute
attach="attributes-position"
args={[new Float32Array([
x, bounds.top, 0,
x, bounds.bottom, 0
]), 3]}
/>
</bufferGeometry>
<lineDashedMaterial
color={color}
opacity={gridOpacity}
transparent
dashSize={0.5}
gapSize={0.5}
/>
</line>
)
})}

{/* Horizontal grid lines */}
{Array.from({ length: 8 }, (_, i) => {
if (i === 0 || i === 7) return null; // Skip edges
const y = bounds.bottom + (bounds.top - bounds.bottom) * (i / 7)
return (
<line key={`hgrid-${i}`}>
<bufferGeometry>
<float32BufferAttribute
attach="attributes-position"
args={[new Float32Array([
bounds.left, y, 0,
bounds.right, y, 0
]), 3]}
/>
</bufferGeometry>
<lineDashedMaterial
color={color}
opacity={gridOpacity}
transparent
dashSize={0.5}
gapSize={0.5}
/>
</line>
)
})}
</>
)}
{/* Top Edge Ticks */}
{Array.from({ length: 10 }, (_, i) => {
const x = bounds.left + (bounds.right - bounds.left) * (i / 9)
return (
<group key={`top-tick-${i}`} position={[x, bounds.top, 0]}>
<line>
<bufferGeometry>
<float32BufferAttribute
attach="attributes-position"
args={[new Float32Array([0, 0, 0, 0, -sizes.tickSize, 0]), 3]}
/>
</bufferGeometry>
<lineBasicMaterial color={color} />
</line>

{/* Only show labels for non-edge ticks */}
{i !== 0 && i !== 9 && (
<Text
position={[0, sizes.tickSize/4 - sizes.labelOffset, 0]}
fontSize={sizes.fontSize}
color={color}
anchorX="center"
anchorY="top"
>
{x.toFixed(1)}
{/* do x.toString() when is not a number */}
</Text>
)}
</group>
)
})}

{/* Right Edge Ticks */}
{Array.from({ length: 6 }, (_, i) => {
const y = bounds.bottom + (bounds.top - bounds.bottom) * (i / 6)
return (
<group key={`right-tick-${i}`} position={[bounds.right, y, 0]}>
<line>
<bufferGeometry>
<float32BufferAttribute
attach="attributes-position"
args={[new Float32Array([0, 0, 0, -sizes.tickSize, 0, 0]), 3]}
/>
</bufferGeometry>
<lineBasicMaterial color={color} />
</line>
{/* Only show labels for non-edge ticks */}
{i !== 0 && i !== 6 && (
<Text
position={[-sizes.tickSize - sizes.labelOffset, 0, 0]}
fontSize={sizes.fontSize}
color={color}
anchorX="right"
anchorY="middle"
>
{y.toFixed(1)}
</Text>
)}
</group>
)
})}
</group>
)
}
2 changes: 1 addition & 1 deletion src/components/Footer.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
position: fixed;
bottom: 0;
left: 0;
padding: 0.35rem 0.5rem;
padding: 0.25rem 0.5rem;
width: 100%;
}

Expand Down
28 changes: 28 additions & 0 deletions src/components/PlaneAxis.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as THREE from 'three'
import { useMemo } from 'react'
interface PlaneAxisProps {
data?: [number, number, number][];
lineColor?: string;
lineWidth?: number;
}

export function PlaneAxis({ data, lineColor = '#ff0000', lineWidth = 2 }: PlaneAxisProps) {

const lineMesh = useMemo(() => {
if (!data?.length) return null;

const points = data.map(([x, y, z]) => new THREE.Vector3(x, y, z));
const curve = new THREE.CatmullRomCurve3(points);
const geometry = new THREE.BufferGeometry().setFromPoints(curve.getPoints(50));
const material = new THREE.LineBasicMaterial({ color: lineColor, linewidth: lineWidth });

return new THREE.Line(geometry, material);
}, [data, lineColor, lineWidth]);

return (
<>
{/* Plot line if data exists */}
{lineMesh && <primitive object={lineMesh} />}
</>
)
}
47 changes: 47 additions & 0 deletions src/components/PlotArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Canvas } from '@react-three/fiber'
import { OrbitControls } from '@react-three/drei'
import { FixedTicks } from './FixedTicks'
import { PlotLine } from './PlotLine'

interface PlotAreaProps {
data: [number, number, number][]
lineColor?: string
lineWidth?: number
}

export function PlotArea({ data, lineColor = 'orangered', lineWidth = 2 }: PlotAreaProps) {
return (
<div
className='plot-canvas'
style={{
position: 'absolute',
bottom: '48px', // Account for footer
left: 0,
width: '100%',
height: '15vh', // 15% of viewport height
background: '#00000099'
}}
>
<Canvas
camera={{ position: [0, 0, 15] }}
frameloop="demand"
>
<PlotLine
data={data}
color={lineColor}
showPoints={true}
pointSize={1}
pointColor="white"
lineWidth={lineWidth}
/>
<FixedTicks color="white" />
<OrbitControls
enableRotate={false}
enablePan={true}
enableZoom={true}
zoomSpeed={0.85}
/>
</Canvas>
</div>
)
}
Loading
Loading