Skip to content

Commit e9db208

Browse files
authored
Merge pull request #25 from EarthyScience/la/rm_buggy
implement a 2d axis in another canvas
2 parents 4919d99 + e3c8089 commit e9db208

File tree

8 files changed

+412
-53
lines changed

8 files changed

+412
-53
lines changed

src/components/CanvasGeometry.tsx

Lines changed: 46 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ import { Center, OrbitControls, Environment, Html } from '@react-three/drei'
66
import { variables, GetArray } from '@/components/ZarrLoaderLRU'
77
import { useEffect, useState } from 'react';
88
// import { useEffect, useState } from 'react';
9-
import { Leva, useControls } from 'leva'
10-
import { lightTheme } from '@/utils/levaTheme'
9+
import { useControls } from 'leva'
10+
// import { Leva } from 'leva'
11+
// import { lightTheme } from '@/utils/levaTheme'
1112
import { ArrayToTexture, DefaultCube } from './TextureMakers';
1213
import { DataCube, PointCloud, UVCube } from './PlotObjects';
1314
import { TimeSeries } from './TimeSeries';
15+
// import { PlaneAxis } from './PlaneAxis';
16+
import { PlotArea } from './PlotArea'
1417
import { GetColorMapTexture } from '@/utils/colormap';
1518

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

105108
{/* Volume Plots */}
106109
{plotter == "volume" && <>
107110
<DataCube volTexture={texture} shape={shape} colormap={colormap}/>
108-
<mesh onClick={() => setShowTimeSeries(true)}>
111+
<UVCube shape={shape} setTimeSeriesLocs={setTimeSeriesLocs}/>
112+
{/* <mesh onClick={() => setShowTimeSeries(true)}>
109113
<UVCube shape={shape} setTimeSeriesLocs={setTimeSeriesLocs}/>
110-
</mesh>
111-
114+
</mesh> */}
112115
</>}
113-
114116
{/* Point Clouds Plots */}
115117
{plotter == "point-cloud" && <PointCloud textures={{texture,colormap}} />}
116118

117-
118119
{/* Time Series Plots */}
119-
{showTimeSeries && <>
120-
121-
<TimeSeries timeSeriesLocs={timeSeriesLocs} DSInfo={{variable:variable, storePath:storeURL}} scaling={{...valueScales,colormap}}/>
122-
<Html
123-
fullscreen
124-
style={{
125-
pointerEvents: 'none', // Prevents capturing mouse events
126-
}}
127-
>
128-
{/* Stand in button to remove time-series stuff */}
129-
<button style={{
130-
position: 'absolute',
131-
bottom: '100px',
132-
right: '100px',
133-
padding: '8px 16px',
134-
backgroundColor: '#3498db',
135-
color: 'white',
136-
border: 'none',
137-
borderRadius: '4px',
138-
cursor: 'pointer',
139-
pointerEvents: 'auto'
140-
141-
}}
142-
onClick={()=>setShowTimeSeries(false)}
143-
>
144-
Hide Time Series
145-
</button>
146-
</Html>
147-
</>}
148-
149-
150-
151120
<OrbitControls minPolarAngle={0} maxPolarAngle={Math.PI / 2} enablePan={false}/>
152121
<Environment preset="city" />
153122

154123
</Canvas>
155124
</div>
156-
<Leva theme={lightTheme} />
125+
{showTimeSeries && <>
126+
127+
<TimeSeries timeSeriesLocs={timeSeriesLocs} DSInfo={{variable:variable, storePath:storeURL}} scaling={{...valueScales,colormap}}/>
128+
<Html
129+
fullscreen
130+
style={{
131+
pointerEvents: 'none', // Prevents capturing mouse events
132+
}}
133+
>
134+
{/* Stand in button to remove time-series stuff */}
135+
<button style={{
136+
position: 'absolute',
137+
bottom: '100px',
138+
right: '100px',
139+
padding: '8px 16px',
140+
backgroundColor: '#3498db',
141+
color: 'white',
142+
border: 'none',
143+
borderRadius: '4px',
144+
cursor: 'pointer',
145+
pointerEvents: 'auto'
146+
147+
}}
148+
onClick={()=>setShowTimeSeries(false)}
149+
>
150+
Hide Time Series
151+
</button>
152+
</Html>
153+
</>}
154+
<PlotArea
155+
data={[[-5,0,0], [1,5,0], [2,0,0], [3,1,0], [4,0,0], [5,1,0], [60,-1.5,0]]}
156+
// now we need to add the time series data to the plot area
157+
// data={timeSeries} // but it needs to be in the right format
158+
lineColor="orangered"
159+
lineWidth={5}
160+
/>
161+
{/* <Leva theme={lightTheme} /> */}
157162
</>
158163
)
159164
}

src/components/FixedTicks.tsx

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import { Text } from '@react-three/drei'
2+
import { useThree, useFrame } from '@react-three/fiber'
3+
import { useState, useMemo } from 'react'
4+
5+
interface ViewportBounds {
6+
left: number;
7+
right: number;
8+
top: number;
9+
bottom: number;
10+
}
11+
12+
interface FixedTicksProps {
13+
color?: string;
14+
tickSize?: number;
15+
fontSize?: number;
16+
showGrid?: boolean;
17+
gridOpacity?: number;
18+
}
19+
20+
21+
export function FixedTicks({
22+
color = 'white',
23+
tickSize = 4,
24+
fontSize = 12,
25+
showGrid = true,
26+
gridOpacity = 0.1
27+
}: FixedTicksProps) {
28+
const { camera, viewport, size } = useThree()
29+
const [bounds, setBounds] = useState<ViewportBounds>({ left: 0, right: 0, top: 0, bottom: 0 })
30+
const [zoom, setZoom] = useState(camera.zoom)
31+
32+
const sizes = useMemo(() => {
33+
// Convert from pixels to scene units
34+
const pixelsPerUnit = size.height / (viewport.height * camera.zoom)
35+
return {
36+
tickSize: tickSize / pixelsPerUnit,
37+
fontSize: fontSize / pixelsPerUnit,
38+
labelOffset: tickSize / pixelsPerUnit
39+
}
40+
}, [size.height, viewport.height, camera.zoom, tickSize, fontSize])
41+
42+
// Update bounds when camera moves
43+
// TODO: update bounds when camera zooms
44+
useFrame(() => {
45+
if (camera.zoom !== zoom) {
46+
setZoom(camera.zoom) // this is not working properly
47+
}
48+
const worldWidth = viewport.width / camera.zoom
49+
const worldHeight = viewport.height / camera.zoom
50+
51+
const newBounds = {
52+
left: -worldWidth / 2 + camera.position.x,
53+
right: worldWidth / 2 + camera.position.x,
54+
top: worldHeight / 2 + camera.position.y,
55+
bottom: -worldHeight / 2 + camera.position.y
56+
}
57+
58+
setBounds(newBounds)
59+
})
60+
61+
return (
62+
<group>
63+
{/* Grid Lines */}
64+
{showGrid && (
65+
<>
66+
{/* Vertical grid lines */}
67+
{Array.from({ length: 10 }, (_, i) => {
68+
if (i === 0 || i === 9) return null; // Skip edges
69+
const x = bounds.left + (bounds.right - bounds.left) * (i / 9)
70+
return (
71+
<line key={`vgrid-${i}`}>
72+
<bufferGeometry>
73+
<float32BufferAttribute
74+
attach="attributes-position"
75+
args={[new Float32Array([
76+
x, bounds.top, 0,
77+
x, bounds.bottom, 0
78+
]), 3]}
79+
/>
80+
</bufferGeometry>
81+
<lineDashedMaterial
82+
color={color}
83+
opacity={gridOpacity}
84+
transparent
85+
dashSize={0.5}
86+
gapSize={0.5}
87+
/>
88+
</line>
89+
)
90+
})}
91+
92+
{/* Horizontal grid lines */}
93+
{Array.from({ length: 8 }, (_, i) => {
94+
if (i === 0 || i === 7) return null; // Skip edges
95+
const y = bounds.bottom + (bounds.top - bounds.bottom) * (i / 7)
96+
return (
97+
<line key={`hgrid-${i}`}>
98+
<bufferGeometry>
99+
<float32BufferAttribute
100+
attach="attributes-position"
101+
args={[new Float32Array([
102+
bounds.left, y, 0,
103+
bounds.right, y, 0
104+
]), 3]}
105+
/>
106+
</bufferGeometry>
107+
<lineDashedMaterial
108+
color={color}
109+
opacity={gridOpacity}
110+
transparent
111+
dashSize={0.5}
112+
gapSize={0.5}
113+
/>
114+
</line>
115+
)
116+
})}
117+
</>
118+
)}
119+
{/* Top Edge Ticks */}
120+
{Array.from({ length: 10 }, (_, i) => {
121+
const x = bounds.left + (bounds.right - bounds.left) * (i / 9)
122+
return (
123+
<group key={`top-tick-${i}`} position={[x, bounds.top, 0]}>
124+
<line>
125+
<bufferGeometry>
126+
<float32BufferAttribute
127+
attach="attributes-position"
128+
args={[new Float32Array([0, 0, 0, 0, -sizes.tickSize, 0]), 3]}
129+
/>
130+
</bufferGeometry>
131+
<lineBasicMaterial color={color} />
132+
</line>
133+
134+
{/* Only show labels for non-edge ticks */}
135+
{i !== 0 && i !== 9 && (
136+
<Text
137+
position={[0, sizes.tickSize/4 - sizes.labelOffset, 0]}
138+
fontSize={sizes.fontSize}
139+
color={color}
140+
anchorX="center"
141+
anchorY="top"
142+
>
143+
{x.toFixed(1)}
144+
{/* do x.toString() when is not a number */}
145+
</Text>
146+
)}
147+
</group>
148+
)
149+
})}
150+
151+
{/* Right Edge Ticks */}
152+
{Array.from({ length: 6 }, (_, i) => {
153+
const y = bounds.bottom + (bounds.top - bounds.bottom) * (i / 6)
154+
return (
155+
<group key={`right-tick-${i}`} position={[bounds.right, y, 0]}>
156+
<line>
157+
<bufferGeometry>
158+
<float32BufferAttribute
159+
attach="attributes-position"
160+
args={[new Float32Array([0, 0, 0, -sizes.tickSize, 0, 0]), 3]}
161+
/>
162+
</bufferGeometry>
163+
<lineBasicMaterial color={color} />
164+
</line>
165+
{/* Only show labels for non-edge ticks */}
166+
{i !== 0 && i !== 6 && (
167+
<Text
168+
position={[-sizes.tickSize - sizes.labelOffset, 0, 0]}
169+
fontSize={sizes.fontSize}
170+
color={color}
171+
anchorX="right"
172+
anchorY="middle"
173+
>
174+
{y.toFixed(1)}
175+
</Text>
176+
)}
177+
</group>
178+
)
179+
})}
180+
</group>
181+
)
182+
}

src/components/Footer.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
position: fixed;
1010
bottom: 0;
1111
left: 0;
12-
padding: 0.35rem 0.5rem;
12+
padding: 0.25rem 0.5rem;
1313
width: 100%;
1414
}
1515

src/components/PlaneAxis.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as THREE from 'three'
2+
import { useMemo } from 'react'
3+
interface PlaneAxisProps {
4+
data?: [number, number, number][];
5+
lineColor?: string;
6+
lineWidth?: number;
7+
}
8+
9+
export function PlaneAxis({ data, lineColor = '#ff0000', lineWidth = 2 }: PlaneAxisProps) {
10+
11+
const lineMesh = useMemo(() => {
12+
if (!data?.length) return null;
13+
14+
const points = data.map(([x, y, z]) => new THREE.Vector3(x, y, z));
15+
const curve = new THREE.CatmullRomCurve3(points);
16+
const geometry = new THREE.BufferGeometry().setFromPoints(curve.getPoints(50));
17+
const material = new THREE.LineBasicMaterial({ color: lineColor, linewidth: lineWidth });
18+
19+
return new THREE.Line(geometry, material);
20+
}, [data, lineColor, lineWidth]);
21+
22+
return (
23+
<>
24+
{/* Plot line if data exists */}
25+
{lineMesh && <primitive object={lineMesh} />}
26+
</>
27+
)
28+
}

src/components/PlotArea.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { Canvas } from '@react-three/fiber'
2+
import { OrbitControls } from '@react-three/drei'
3+
import { FixedTicks } from './FixedTicks'
4+
import { PlotLine } from './PlotLine'
5+
6+
interface PlotAreaProps {
7+
data: [number, number, number][]
8+
lineColor?: string
9+
lineWidth?: number
10+
}
11+
12+
export function PlotArea({ data, lineColor = 'orangered', lineWidth = 2 }: PlotAreaProps) {
13+
return (
14+
<div
15+
className='plot-canvas'
16+
style={{
17+
position: 'absolute',
18+
bottom: '48px', // Account for footer
19+
left: 0,
20+
width: '100%',
21+
height: '15vh', // 15% of viewport height
22+
background: '#00000099'
23+
}}
24+
>
25+
<Canvas
26+
camera={{ position: [0, 0, 15] }}
27+
frameloop="demand"
28+
>
29+
<PlotLine
30+
data={data}
31+
color={lineColor}
32+
showPoints={true}
33+
pointSize={1}
34+
pointColor="white"
35+
lineWidth={lineWidth}
36+
/>
37+
<FixedTicks color="white" />
38+
<OrbitControls
39+
enableRotate={false}
40+
enablePan={true}
41+
enableZoom={true}
42+
zoomSpeed={0.85}
43+
/>
44+
</Canvas>
45+
</div>
46+
)
47+
}

0 commit comments

Comments
 (0)